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