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     */
019    
020    package org.crsh.command;
021    
022    import org.crsh.cli.descriptor.CommandDescriptor;
023    import org.crsh.cli.impl.Delimiter;
024    import org.crsh.cli.impl.completion.CompletionException;
025    import org.crsh.cli.impl.completion.CompletionMatch;
026    import org.crsh.cli.impl.completion.CompletionMatcher;
027    import org.crsh.cli.impl.descriptor.CommandDescriptorImpl;
028    import org.crsh.cli.impl.descriptor.HelpDescriptor;
029    import org.crsh.cli.impl.descriptor.IntrospectionException;
030    import org.crsh.cli.impl.invocation.InvocationException;
031    import org.crsh.cli.impl.invocation.InvocationMatch;
032    import org.crsh.cli.impl.invocation.InvocationMatcher;
033    import org.crsh.cli.impl.invocation.Resolver;
034    import org.crsh.cli.impl.lang.CommandFactory;
035    import org.crsh.cli.spi.Completer;
036    import org.crsh.cli.spi.Completion;
037    import org.crsh.util.TypeResolver;
038    
039    import java.io.IOException;
040    import java.io.PrintWriter;
041    import java.io.StringWriter;
042    import java.lang.reflect.Type;
043    import java.lang.reflect.UndeclaredThrowableException;
044    import java.util.List;
045    import java.util.Map;
046    import java.util.logging.Level;
047    import java.util.logging.Logger;
048    
049    public abstract class BaseCommand extends AbstractCommand implements ShellCommand {
050    
051      /** . */
052      private final Logger log = Logger.getLogger(getClass().getName());
053    
054      /** . */
055      private final CommandDescriptorImpl<?> descriptor;
056    
057      /** The unmatched text, only valid during an invocation. */
058      protected String unmatched;
059    
060      protected BaseCommand() throws IntrospectionException {
061        this.descriptor = HelpDescriptor.create(new CommandFactory(getClass().getClassLoader()).create(getClass()));
062        this.unmatched = null;
063      }
064    
065      /**
066       * Returns the command descriptor.
067       *
068       * @return the command descriptor
069       */
070      public CommandDescriptor<?> getDescriptor() {
071        return descriptor;
072      }
073    
074      protected final String readLine(String msg) {
075        return readLine(msg, true);
076      }
077    
078      protected final String readLine(String msg, boolean echo) {
079        if (context instanceof InvocationContext) {
080          return ((InvocationContext)context).readLine(msg, echo);
081        } else {
082          throw new IllegalStateException("Cannot invoke read line without an invocation context");
083        }
084      }
085    
086      public final String getUnmatched() {
087        return unmatched;
088      }
089    
090      public final CompletionMatch complete(RuntimeContext context, String line) {
091    
092        // WTF
093        CompletionMatcher analyzer = descriptor.completer("main");
094    
095        //
096        Completer completer = this instanceof Completer ? (Completer)this : null;
097    
098        //
099        this.context = context;
100        try {
101          return analyzer.match(completer, line);
102        }
103        catch (CompletionException e) {
104          log.log(Level.SEVERE, "Error during completion of line " + line, e);
105          return new CompletionMatch(Delimiter.EMPTY, Completion.create());
106        }
107        finally {
108          this.context = null;
109        }
110      }
111    
112      public final String describe(String line, DescriptionFormat mode) {
113    
114        // WTF
115        InvocationMatcher analyzer = descriptor.invoker("main");
116    
117        //
118        InvocationMatch match;
119        try {
120          match = analyzer.match(line);
121        }
122        catch (org.crsh.cli.SyntaxException e) {
123          throw new SyntaxException(e.getMessage());
124        }
125    
126        //
127        try {
128          switch (mode) {
129            case DESCRIBE:
130              return match.getDescriptor().getUsage();
131            case MAN:
132              StringWriter sw = new StringWriter();
133              PrintWriter pw = new PrintWriter(sw);
134              match.getDescriptor().printMan(pw);
135              return sw.toString();
136            case USAGE:
137              StringWriter sw2 = new StringWriter();
138              PrintWriter pw2 = new PrintWriter(sw2);
139              match.getDescriptor().printUsage(pw2);
140              return sw2.toString();
141          }
142        }
143        catch (IOException e) {
144          throw new AssertionError(e);
145        }
146    
147        //
148        return null;
149      }
150    
151      public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
152        InvocationMatcher matcher = descriptor.invoker("main");
153        InvocationMatch<BaseCommand> match;
154        try {
155          match = matcher.match(name, options, args);
156        }
157        catch (org.crsh.cli.SyntaxException e) {
158          throw new SyntaxException(e.getMessage());
159        }
160        return resolveInvoker(match);
161      }
162    
163      public UndeclaredThrowableException toScript(Throwable cause) {
164        return new UndeclaredThrowableException(cause);
165      }
166    
167      public CommandInvoker<?, ?> resolveInvoker(String line) {
168        InvocationMatcher analyzer = descriptor.invoker("main");
169        InvocationMatch<BaseCommand> match;
170        try {
171          match = analyzer.match(line);
172        }
173        catch (org.crsh.cli.SyntaxException e) {
174          throw new SyntaxException(e.getMessage());
175        }
176        return resolveInvoker(match);
177      }
178    
179      public final void execute(String s) throws ScriptException, IOException {
180        InvocationContext<?> context = peekContext();
181        CommandInvoker invoker = context.resolve(s);
182        invoker.open(context);
183        invoker.flush();
184        invoker.close();
185      }
186    
187      public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<BaseCommand> match) {
188    
189        //
190        final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
191    
192        //
193        Class consumedType;
194        Class producedType;
195        if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
196          Type ret = invoker.getGenericReturnType();
197          consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
198          producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
199        } else {
200          consumedType = Void.class;
201          producedType = Object.class;
202          Class<?>[] parameterTypes = invoker.getParameterTypes();
203          for (int i = 0;i < parameterTypes.length;i++) {
204            Class<?> parameterType = parameterTypes[i];
205            if (InvocationContext.class.isAssignableFrom(parameterType)) {
206              Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
207              producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
208              break;
209            }
210          }
211        }
212        final Class _consumedType = consumedType;
213        final Class _producedType = producedType;
214    
215        // Do we have a pipe command or not ?
216        if (PipeCommand.class.isAssignableFrom(match.getInvoker().getReturnType())) {
217          return new CommandInvoker<Object, Object>() {
218    
219            /** . */
220            PipeCommand real;
221    
222            public Class<Object> getProducedType() {
223              return _producedType;
224            }
225    
226            public Class<Object> getConsumedType() {
227              return _consumedType;
228            }
229    
230            public void open(CommandContext<? super Object> consumer) {
231              // Java is fine with that but not intellij....
232              CommandContext<Object> consumer2 = (CommandContext<Object>)consumer;
233              open2(consumer2);
234            }
235    
236            public void open2(final CommandContext<Object> consumer) {
237    
238              //
239              final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
240    
241              // Push context
242              pushContext(invocationContext);
243    
244              //  Set the unmatched part
245              BaseCommand.this.unmatched = match.getRest();
246    
247              //
248              Object ret;
249              try {
250                ret = invoker.invoke(BaseCommand.this);
251              }
252              catch (org.crsh.cli.SyntaxException e) {
253                throw new SyntaxException(e.getMessage());
254              } catch (InvocationException e) {
255                throw toScript(e.getCause());
256              }
257    
258              // It's a pipe command
259              if (ret != null) {
260                real = (PipeCommand)ret;
261                real.doOpen(invocationContext);
262              }
263            }
264            public void provide(Object element) throws IOException {
265              if (real != null) {
266                real.provide(element);
267              }
268            }
269            public void flush() throws IOException {
270              if (real != null) {
271                real.flush();
272              } else {
273                peekContext().flush();
274              }
275            }
276            public void close() throws IOException {
277              if (real != null) {
278                try {
279                  real.close();
280                }
281                finally {
282                  popContext();
283                }
284              } else {
285                InvocationContext<?> context = popContext();
286                context.close();
287              }
288              BaseCommand.this.unmatched = null;
289            }
290          };
291        } else {
292          return new CommandInvoker<Object, Object>() {
293    
294            public Class<Object> getProducedType() {
295              return _producedType;
296            }
297    
298            public Class<Object> getConsumedType() {
299              return _consumedType;
300            }
301    
302            public void open(CommandContext<? super Object> consumer) {
303              // Java is fine with that but not intellij....
304              CommandContext<Object> consumer2 = (CommandContext<Object>)consumer;
305              open2(consumer2);
306            }
307    
308            public void open2(final CommandContext<Object> consumer) {
309    
310              //
311              final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
312    
313              // Push context
314              pushContext(invocationContext);
315    
316              //  Set the unmatched part
317              BaseCommand.this.unmatched = match.getRest();
318            }
319            public void provide(Object element) throws IOException {
320              // Drop everything
321            }
322            public void flush() throws IOException {
323              // peekContext().flush();
324            }
325            public void close() throws IOException, UndeclaredThrowableException {
326    
327              //
328              final Resolver resolver = new Resolver() {
329                public <T> T resolve(Class<T> type) {
330                  if (type.equals(InvocationContext.class)) {
331                    return type.cast(peekContext());
332                  } else {
333                    return null;
334                  }
335                }
336              };
337    
338              //
339              Object ret;
340              try {
341                ret = invoker.invoke(resolver, BaseCommand.this);
342              }
343              catch (org.crsh.cli.SyntaxException e) {
344                throw new SyntaxException(e.getMessage());
345              } catch (InvocationException e) {
346                throw toScript(e.getCause());
347              }
348    
349              //
350              if (ret != null) {
351                peekContext().getWriter().print(ret);
352              }
353    
354              //
355              InvocationContext<?> context = popContext();
356              context.flush();
357              context.close();
358              BaseCommand.this.unmatched = null;
359            }
360          };
361        }
362      }
363    }