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.util.List;
044    import java.util.Map;
045    import java.util.logging.Level;
046    import java.util.logging.Logger;
047    
048    public abstract class BaseCommand extends AbstractCommand 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 BaseCommand() 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 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      public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
151        if (options.containsKey("h") || options.containsKey("help")) {
152          throw new UnsupportedOperationException("Implement me");
153        } else {
154    
155          InvocationMatcher matcher = descriptor.invoker("main");
156          InvocationMatch<BaseCommand> match = null;
157          try {
158            match = matcher.match(name, options, args);
159          }
160          catch (org.crsh.cli.SyntaxException e) {
161            throw new SyntaxException(e.getMessage());
162          }
163          return resolveInvoker(match);
164        }
165      }
166    
167      public ScriptException toScript(Throwable cause) {
168        if (cause instanceof ScriptException) {
169          return (ScriptException)cause;
170        } else {
171          return new ScriptException(cause);
172        }
173      }
174    
175      public CommandInvoker<?, ?> resolveInvoker(String line) {
176        InvocationMatcher analyzer = descriptor.invoker("main");
177        InvocationMatch<BaseCommand> match;
178        try {
179          match = analyzer.match(line);
180        }
181        catch (org.crsh.cli.SyntaxException e) {
182          throw new SyntaxException(e.getMessage());
183        }
184        return resolveInvoker(match);
185      }
186    
187      public final void execute(String s) throws ScriptException, IOException {
188        InvocationContext<?> context = peekContext();
189        CommandInvoker invoker = context.resolve(s);
190        invoker.open(context);
191        invoker.flush();
192        invoker.close();
193      }
194    
195      public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<BaseCommand> match) {
196    
197        //
198        final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
199    
200        //
201        Class consumedType;
202        Class producedType;
203        if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
204          Type ret = invoker.getGenericReturnType();
205          consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
206          producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
207        } else {
208          consumedType = Void.class;
209          producedType = Object.class;
210          Class<?>[] parameterTypes = invoker.getParameterTypes();
211          for (int i = 0;i < parameterTypes.length;i++) {
212            Class<?> parameterType = parameterTypes[i];
213            if (InvocationContext.class.isAssignableFrom(parameterType)) {
214              Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
215              producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
216              break;
217            }
218          }
219        }
220        final Class _consumedType = consumedType;
221        final Class _producedType = producedType;
222    
223        //
224        return new CommandInvoker<Object, Object>() {
225    
226          /** . */
227          PipeCommand real;
228    
229          public Class<Object> getProducedType() {
230            return _producedType;
231          }
232    
233          public Class<Object> getConsumedType() {
234            return _consumedType;
235          }
236    
237          public void open(final CommandContext<Object> consumer) {
238    
239            //
240            final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
241            final Resolver resolver = new Resolver() {
242              public <T> T resolve(Class<T> type) {
243                if (type.equals(InvocationContext.class)) {
244                  return type.cast(invocationContext);
245                } else {
246                  return null;
247                }
248              }
249            };
250    
251            // Push context
252            pushContext(invocationContext);
253    
254            //  Set the unmatched part
255            BaseCommand.this.unmatched = match.getRest();
256    
257            //
258            Object ret;
259            try {
260              ret = invoker.invoke(resolver, BaseCommand.this);
261            }
262            catch (org.crsh.cli.SyntaxException e) {
263              throw new SyntaxException(e.getMessage());
264            } catch (InvocationException e) {
265              throw toScript(e.getCause());
266            }
267    
268            // It's a pipe command
269            if (ret instanceof PipeCommand) {
270              real = (PipeCommand)ret;
271              real.doOpen(invocationContext);
272            } else {
273              if (ret != null) {
274                peekContext().getWriter().print(ret);
275              }
276            }
277          }
278          public void provide(Object element) throws IOException {
279            if (real != null) {
280              real.provide(element);
281            } else {
282              // We just drop the elements
283            }
284          }
285          public void flush() throws IOException {
286            if (real != null) {
287              real.flush();
288            } else {
289              peekContext().flush();
290            }
291          }
292          public void close() throws IOException {
293            if (real != null) {
294              try {
295                real.close();
296              }
297              finally {
298                popContext();
299              }
300            } else {
301              InvocationContext<?> context = popContext();
302              context.close();
303            }
304            BaseCommand.this.unmatched = null;
305          }
306        };
307      }
308    }