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        return new CommandInvoker<Object, Object>() {
238    
239          /** . */
240          PipeCommand real;
241    
242          public Class<Object> getProducedType() {
243            return _producedType;
244          }
245    
246          public Class<Object> getConsumedType() {
247            return _consumedType;
248          }
249    
250          public void open(final CommandContext<Object> consumer) {
251    
252            //
253            final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
254            final Resolver resolver = new Resolver() {
255              public <T> T resolve(Class<T> type) {
256                if (type.equals(InvocationContext.class)) {
257                  return type.cast(invocationContext);
258                } else {
259                  return null;
260                }
261              }
262            };
263    
264            // Push context
265            pushContext(invocationContext);
266    
267            //  Set the unmatched part
268            CRaSHCommand.this.unmatched = match.getRest();
269    
270            //
271            Object ret;
272            try {
273              ret = invoker.invoke(resolver, CRaSHCommand.this);
274            }
275            catch (org.crsh.cli.SyntaxException e) {
276              throw new org.crsh.command.SyntaxException(e.getMessage());
277            } catch (InvocationException e) {
278              throw toScript(e.getCause());
279            }
280    
281            // It's a pipe command
282            if (ret instanceof PipeCommand) {
283              real = (PipeCommand)ret;
284              real.doOpen(invocationContext);
285            } else {
286              if (ret != null) {
287                peekContext().getWriter().print(ret);
288              }
289            }
290          }
291          public void provide(Object element) throws IOException {
292            if (real != null) {
293              real.provide(element);
294            } else {
295              // We just drop the elements
296            }
297          }
298          public void flush() throws IOException {
299            if (real != null) {
300              real.flush();
301            } else {
302              peekContext().flush();
303            }
304          }
305          public void close() throws IOException {
306            if (real != null) {
307              try {
308                real.close();
309              }
310              finally {
311                popContext();
312              }
313            } else {
314              InvocationContext<?> context = popContext();
315              context.close();
316            }
317            CRaSHCommand.this.unmatched = null;
318          }
319        };
320      }
321    }