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.impl.descriptor.CommandDescriptorImpl;
023    import org.crsh.cli.descriptor.CommandDescriptor;
024    import org.crsh.cli.impl.descriptor.HelpDescriptor;
025    import org.crsh.cli.impl.completion.CompletionMatch;
026    import org.crsh.cli.impl.Delimiter;
027    import org.crsh.cli.impl.descriptor.IntrospectionException;
028    import org.crsh.cli.impl.completion.CompletionException;
029    import org.crsh.cli.impl.completion.CompletionMatcher;
030    import org.crsh.cli.impl.lang.CommandFactory;
031    import org.crsh.cli.impl.invocation.InvocationException;
032    import org.crsh.cli.impl.invocation.InvocationMatch;
033    import org.crsh.cli.impl.invocation.InvocationMatcher;
034    import org.crsh.cli.impl.invocation.Resolver;
035    import org.crsh.cli.spi.Completer;
036    import org.crsh.cli.spi.Completion;
037    import org.crsh.shell.InteractionContext;
038    import org.crsh.util.TypeResolver;
039    
040    import java.io.IOException;
041    import java.io.PrintWriter;
042    import java.io.StringWriter;
043    import java.lang.reflect.Type;
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 CRaSHCommand extends GroovyCommand 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 CRaSHCommand() 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(CommandContext 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 org.crsh.command.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      static ScriptException toScript(Throwable cause) {
152        if (cause instanceof ScriptException) {
153          return (ScriptException)cause;
154        } if (cause instanceof groovy.util.ScriptException) {
155          // Special handling for groovy.util.ScriptException
156          // which may be thrown by scripts because it is imported by default
157          // by groovy imports
158          String msg = cause.getMessage();
159          ScriptException translated;
160          if (msg != null) {
161            translated = new ScriptException(msg);
162          } else {
163            translated = new ScriptException();
164          }
165          translated.setStackTrace(cause.getStackTrace());
166          return translated;
167        } else {
168          return new ScriptException(cause);
169        }
170      }
171    
172      public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
173        if (options.containsKey("h") || options.containsKey("help")) {
174          throw new UnsupportedOperationException("Implement me");
175        } else {
176    
177          InvocationMatcher matcher = descriptor.invoker("main");
178          InvocationMatch<CRaSHCommand> match = null;
179          try {
180            match = matcher.match(name, options, args);
181          }
182          catch (org.crsh.cli.SyntaxException e) {
183            throw new org.crsh.command.SyntaxException(e.getMessage());
184          }
185          return resolveInvoker(match);
186        }
187      }
188    
189      public CommandInvoker<?, ?> resolveInvoker(String line) {
190        InvocationMatcher analyzer = descriptor.invoker("main");
191        InvocationMatch<CRaSHCommand> match;
192        try {
193          match = analyzer.match(line);
194        }
195        catch (org.crsh.cli.SyntaxException e) {
196          throw new org.crsh.command.SyntaxException(e.getMessage());
197        }
198        return resolveInvoker(match);
199      }
200    
201      public final void execute(String s) throws ScriptException, IOException {
202        InvocationContext<?> context = peekContext();
203        CommandInvoker invoker = context.resolve(s);
204        invoker.open(context);
205        invoker.flush();
206        invoker.close();
207      }
208    
209      public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<CRaSHCommand> match) {
210    
211        //
212        final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
213    
214        //
215        Class consumedType;
216        Class producedType;
217        if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
218          Type ret = invoker.getGenericReturnType();
219          consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
220          producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
221        } else {
222          consumedType = Void.class;
223          producedType = Object.class;
224          Class<?>[] parameterTypes = invoker.getParameterTypes();
225          for (int i = 0;i < parameterTypes.length;i++) {
226            Class<?> parameterType = parameterTypes[i];
227            if (InvocationContext.class.isAssignableFrom(parameterType)) {
228              Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
229              producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
230              break;
231            }
232          }
233        }
234        final Class _consumedType = consumedType;
235        final Class _producedType = producedType;
236    
237        //
238        if (consumedType == Void.class) {
239    
240          return new CommandInvoker<Object, Object>() {
241    
242            /** . */
243            private CommandContext session;
244    
245            public void setSession(CommandContext session) {
246              this.session = session;
247            }
248    
249            public Class<Object> getProducedType() {
250              return _producedType;
251            }
252    
253            public Class<Object> getConsumedType() {
254              return _consumedType;
255            }
256    
257            public void open(final InteractionContext<Object> consumer) {
258    
259              //
260              pushContext(new InvocationContextImpl<Object>(consumer, session));
261              CRaSHCommand.this.unmatched = match.getRest();
262              final Resolver resolver = new Resolver() {
263                public <T> T resolve(Class<T> type) {
264                  if (type.equals(InvocationContext.class)) {
265                    return type.cast(peekContext());
266                  } else {
267                    return null;
268                  }
269                }
270              };
271    
272              //
273              Object o;
274              try {
275                o = invoker.invoke(resolver, CRaSHCommand.this);
276              } catch (org.crsh.cli.SyntaxException e) {
277                throw new org.crsh.command.SyntaxException(e.getMessage());
278              } catch (InvocationException e) {
279                throw toScript(e.getCause());
280              }
281              if (o != null) {
282                peekContext().getWriter().print(o);
283              }
284            }
285            public void setPiped(boolean piped) {
286            }
287            public void provide(Object element) throws IOException {
288              // We just drop the elements
289            }
290            public void flush() throws IOException {
291              peekContext().flush();
292            }
293            public void close() {
294              CRaSHCommand.this.unmatched = null;
295              popContext();
296            }
297          };
298        } else {
299          return new CommandInvoker<Object, Object>() {
300    
301            /** . */
302            PipeCommand real;
303    
304            /** . */
305            boolean piped;
306    
307            /** . */
308            private CommandContext session;
309    
310            public Class<Object> getProducedType() {
311              return _producedType;
312            }
313    
314            public Class<Object> getConsumedType() {
315              return _consumedType;
316            }
317    
318            public void setSession(CommandContext session) {
319              this.session = session;
320            }
321    
322            public void setPiped(boolean piped) {
323              this.piped = piped;
324            }
325    
326            public void open(final InteractionContext<Object> consumer) {
327    
328              //
329              final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer, session);
330    
331              //
332              pushContext(invocationContext);
333              CRaSHCommand.this.unmatched = match.getRest();
334              final Resolver resolver = new Resolver() {
335                public <T> T resolve(Class<T> type) {
336                  if (type.equals(InvocationContext.class)) {
337                    return type.cast(invocationContext);
338                  } else {
339                    return null;
340                  }
341                }
342              };
343              try {
344                real = (PipeCommand)invoker.invoke(resolver, CRaSHCommand.this);
345              }
346              catch (org.crsh.cli.SyntaxException e) {
347                throw new org.crsh.command.SyntaxException(e.getMessage());
348              } catch (InvocationException e) {
349                throw toScript(e.getCause());
350              }
351    
352              //
353              real.setPiped(piped);
354              real.doOpen(invocationContext);
355            }
356    
357            public void provide(Object element) throws IOException {
358              real.provide(element);
359            }
360    
361            public void flush() throws IOException {
362              real.flush();
363            }
364    
365            public void close() {
366              try {
367                real.close();
368              }
369              finally {
370                popContext();
371              }
372            }
373          };
374        }
375      }
376    }