001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019package org.crsh.command;
020
021import org.crsh.cli.descriptor.CommandDescriptor;
022import org.crsh.cli.impl.Delimiter;
023import org.crsh.cli.impl.completion.CompletionException;
024import org.crsh.cli.impl.completion.CompletionMatch;
025import org.crsh.cli.impl.completion.CompletionMatcher;
026import org.crsh.cli.impl.descriptor.CommandDescriptorImpl;
027import org.crsh.cli.impl.descriptor.HelpDescriptor;
028import org.crsh.cli.impl.invocation.InvocationException;
029import org.crsh.cli.impl.invocation.InvocationMatch;
030import org.crsh.cli.impl.invocation.InvocationMatcher;
031import org.crsh.cli.impl.invocation.Resolver;
032import org.crsh.cli.impl.lang.CommandFactory;
033import org.crsh.cli.spi.Completer;
034import org.crsh.cli.spi.Completion;
035import org.crsh.shell.ErrorType;
036import org.crsh.util.TypeResolver;
037
038import java.io.IOException;
039import java.io.PrintWriter;
040import java.io.StringWriter;
041import java.lang.reflect.Type;
042import java.lang.reflect.UndeclaredThrowableException;
043import java.util.List;
044import java.util.Map;
045import java.util.logging.Level;
046
047/** @author Julien Viet */
048public class BaseShellCommand<CC extends BaseCommand> implements ShellCommand {
049
050  /** . */
051  private final Class<CC> clazz;
052
053  /** . */
054  private final CommandDescriptorImpl<CC> descriptor;
055
056  public BaseShellCommand(Class<CC> clazz) {
057
058    //
059    CommandFactory factory = new CommandFactory(getClass().getClassLoader());
060
061    //
062    this.clazz = clazz;
063    this.descriptor = HelpDescriptor.create(factory.create(clazz));
064  }
065
066  public CommandDescriptor<? extends BaseCommand> getDescriptor() {
067    return descriptor;
068  }
069
070  public final CompletionMatch complete(RuntimeContext context, String line) throws CommandCreationException {
071
072    // WTF
073    CompletionMatcher analyzer = descriptor.completer("main");
074
075    //
076    CC command = createCommand();
077
078    //
079    Completer completer = command instanceof Completer ? (Completer)command : null;
080
081    //
082    command.context = context;
083    try {
084      return analyzer.match(completer, line);
085    }
086    catch (CompletionException e) {
087      command.log.log(Level.SEVERE, "Error during completion of line " + line, e);
088      return new CompletionMatch(Delimiter.EMPTY, Completion.create());
089    }
090    finally {
091      command.context = null;
092    }
093  }
094
095  public final String describe(String line, DescriptionFormat mode) {
096
097    // WTF
098    InvocationMatcher analyzer = descriptor.invoker("main");
099
100    //
101    InvocationMatch match;
102    try {
103      match = analyzer.match(line);
104    }
105    catch (org.crsh.cli.SyntaxException e) {
106      throw new SyntaxException(e.getMessage());
107    }
108
109    //
110    try {
111      switch (mode) {
112        case DESCRIBE:
113          return match.getDescriptor().getUsage();
114        case MAN:
115          StringWriter sw = new StringWriter();
116          PrintWriter pw = new PrintWriter(sw);
117          match.getDescriptor().printMan(pw);
118          return sw.toString();
119        case USAGE:
120          StringWriter sw2 = new StringWriter();
121          PrintWriter pw2 = new PrintWriter(sw2);
122          match.getDescriptor().printUsage(pw2);
123          return sw2.toString();
124      }
125    }
126    catch (IOException e) {
127      throw new AssertionError(e);
128    }
129
130    //
131    return null;
132  }
133
134  public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) throws CommandCreationException {
135    InvocationMatcher<CC> matcher = descriptor.invoker("main");
136    InvocationMatch<CC> match;
137    try {
138      match = matcher.match(name, options, args);
139    }
140    catch (org.crsh.cli.SyntaxException e) {
141      throw new SyntaxException(e.getMessage());
142    }
143    return resolveInvoker(match);
144  }
145
146  public CommandInvoker<?, ?> resolveInvoker(String line) throws CommandCreationException {
147    InvocationMatcher<CC> analyzer = descriptor.invoker("main");
148    InvocationMatch<CC> match;
149    try {
150      match = analyzer.match(line);
151    }
152    catch (org.crsh.cli.SyntaxException e) {
153      throw new SyntaxException(e.getMessage());
154    }
155    return resolveInvoker(match);
156  }
157
158  public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<CC> match) throws CommandCreationException {
159    return resolveInvoker2(match);
160  }
161
162  private CommandInvoker<?, ?> resolveInvoker2(final InvocationMatch<CC> match) throws CommandCreationException {
163
164    // Invoker
165    org.crsh.cli.impl.invocation.CommandInvoker<CC, ?> invoker = match.getInvoker();
166
167    // Necessary...
168    final CC command = createCommand();
169
170    /** The resolver. */
171    Resolver resolver = new Resolver() {
172      public <T> T resolve(Class<T> type) {
173        if (type.equals(InvocationContext.class)) {
174          return type.cast(command.peekContext());
175        } else {
176          return null;
177        }
178      }
179    };
180
181    // Do we have a pipe command or not ?
182    if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
183      org.crsh.cli.impl.invocation.CommandInvoker invoker2 = invoker;
184      return getPipeCommandInvoker(invoker2, command, resolver);
185    } else {
186
187      // A priori it could be any class
188      Class<?> producedType = Object.class;
189
190      // Override produced type from InvocationContext<P> if any
191      Class<?>[] parameterTypes = invoker.getParameterTypes();
192      for (int i = 0;i < parameterTypes.length;i++) {
193        Class<?> parameterType = parameterTypes[i];
194        if (InvocationContext.class.isAssignableFrom(parameterType)) {
195          Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
196          producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
197          break;
198        }
199      }
200
201      //
202      return getInvoker(invoker, command, producedType, resolver);
203    }
204  }
205
206  private CC createCommand() throws CommandCreationException {
207    CC command;
208    try {
209      command = clazz.newInstance();
210    }
211    catch (Exception e) {
212      String name = clazz.getSimpleName();
213      throw new CommandCreationException(name, ErrorType.INTERNAL, "Could not create command " + name + " instance", e);
214    }
215    return command;
216  }
217
218  private <C, P, PC extends PipeCommand<C, P>> CommandInvoker<C, P> getPipeCommandInvoker(
219      final org.crsh.cli.impl.invocation.CommandInvoker<CC, PC> invoker,
220      final CC instance,
221      final Resolver resolver) {
222    return new CommandInvoker<C, P>() {
223
224      /** . */
225      final Type ret = invoker.getGenericReturnType();
226
227      /** . */
228      final Class<C> consumedType = (Class<C>)TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
229
230      /** . */
231      final Class<P> producedType = (Class<P>)TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
232
233      PipeCommand<C, P> real;
234
235      public Class<P> getProducedType() {
236        return producedType;
237      }
238
239      public Class<C> getConsumedType() {
240        return consumedType;
241      }
242
243      public void open(CommandContext<? super P> consumer) {
244        // Java is fine with that but not intellij....
245        CommandContext<P> consumer2 = (CommandContext<P>)consumer;
246        open2(consumer2);
247      }
248
249      public void open2(final CommandContext<P> consumer) {
250
251        //
252        final InvocationContextImpl<P> invocationContext = new InvocationContextImpl<P>(consumer);
253
254        // Push context
255        instance.pushContext(invocationContext);
256
257        //  Set the unmatched part
258        instance.unmatched = invoker.getMatch().getRest();
259
260        //
261        PC ret;
262        try {
263          ret = invoker.invoke(resolver, instance);
264        }
265        catch (org.crsh.cli.SyntaxException e) {
266          throw new SyntaxException(e.getMessage());
267        } catch (InvocationException e) {
268          throw instance.toScript(e.getCause());
269        }
270
271        // It's a pipe command
272        if (ret != null) {
273          real = ret;
274          real.open(invocationContext);
275        }
276      }
277
278      public void provide(C element) throws IOException {
279        if (real != null) {
280          real.provide(element);
281        }
282      }
283
284      public void flush() throws IOException {
285        if (real != null) {
286          real.flush();
287        } else {
288          instance.peekContext().flush();
289        }
290      }
291
292      public void close() throws IOException {
293        if (real != null) {
294          try {
295            real.close();
296          }
297          finally {
298            instance.popContext();
299          }
300        } else {
301          InvocationContext<?> context = instance.popContext();
302          context.close();
303        }
304        instance.unmatched = null;
305      }
306    };
307  }
308
309  private <P> CommandInvoker<Void, P> getInvoker(
310      final org.crsh.cli.impl.invocation.CommandInvoker<CC, ?> invoker,
311      final CC instance,
312      final Class<P> _producedType,
313      final Resolver resolver) {
314    return new CommandInvoker<Void, P>() {
315
316      public Class<P> getProducedType() {
317        return _producedType;
318      }
319
320      public Class<Void> getConsumedType() {
321        return Void.class;
322      }
323
324      public void open(CommandContext<? super P> consumer) {
325        // Java is fine with that but not intellij....
326        CommandContext<P> consumer2 = (CommandContext<P>)consumer;
327        open2(consumer2);
328      }
329
330      public void open2(final CommandContext<P> consumer) {
331
332        //
333        final InvocationContextImpl<P> invocationContext = new InvocationContextImpl<P>(consumer);
334
335        // Push context
336        instance.pushContext(invocationContext);
337
338        //  Set the unmatched part
339        instance.unmatched = invoker.getMatch().getRest();
340      }
341
342      public void provide(Void element) throws IOException {
343        // Drop everything
344      }
345      public void flush() throws IOException {
346        // peekContext().flush();
347      }
348      public void close() throws IOException, UndeclaredThrowableException {
349
350        //
351        Object ret;
352        try {
353          ret = invoker.invoke(resolver, instance);
354        }
355        catch (org.crsh.cli.SyntaxException e) {
356          throw new SyntaxException(e.getMessage());
357        } catch (InvocationException e) {
358          throw instance.toScript(e.getCause());
359        }
360
361        //
362        if (ret != null) {
363          instance.peekContext().getWriter().print(ret);
364        }
365
366        //
367        InvocationContext<?> context = instance.popContext();
368        context.flush();
369        context.close();
370        instance.unmatched = null;
371      }
372    };
373  }
374}