001    /*
002     * Copyright (C) 2010 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.cmdline.ClassDescriptor;
023    import org.crsh.cmdline.CommandCompletion;
024    import org.crsh.cmdline.CommandFactory;
025    import org.crsh.cmdline.Delimiter;
026    import org.crsh.cmdline.IntrospectionException;
027    import org.crsh.cmdline.annotations.Man;
028    import org.crsh.cmdline.annotations.Option;
029    import org.crsh.cmdline.OptionDescriptor;
030    import org.crsh.cmdline.ParameterDescriptor;
031    import org.crsh.cmdline.annotations.Usage;
032    import org.crsh.cmdline.matcher.*;
033    import org.crsh.cmdline.spi.Completer;
034    import org.crsh.cmdline.spi.ValueCompletion;
035    import org.crsh.util.TypeResolver;
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    import java.io.IOException;
040    import java.io.PrintWriter;
041    import java.io.StringWriter;
042    import java.lang.reflect.Method;
043    import java.lang.reflect.Type;
044    import java.lang.reflect.UndeclaredThrowableException;
045    
046    /**
047     * A real CRaSH command, the most powerful kind of command.
048     *
049     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
050     * @version $Revision$
051     */
052    public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
053    
054      /** . */
055      private final Logger log = LoggerFactory.getLogger(getClass());
056    
057      /** . */
058      private CommandContext context;
059    
060      /** . */
061      private boolean unquoteArguments;
062    
063      /** . */
064      private final ClassDescriptor<?> descriptor;
065    
066      /** The unmatched text, only valid during an invocation. */
067      private String unmatched;
068    
069      /** . */
070      @Option(names = {"h","help"})
071      @Usage("command usage")
072      @Man("Provides command usage")
073      private boolean help;
074    
075      protected CRaSHCommand() throws IntrospectionException {
076        this.context = null;
077        this.unquoteArguments = true;
078        this.descriptor = CommandFactory.create(getClass());
079        this.help = false;
080        this.unmatched = null;
081      }
082    
083      /**
084       * Returns the command descriptor.
085       *
086       * @return the command descriptor
087       */
088      public ClassDescriptor<?> getDescriptor() {
089        return descriptor;
090      }
091    
092      /**
093       * Returns true if the command wants its arguments to be unquoted.
094       *
095       * @return true if arguments must be unquoted
096       */
097      public final boolean getUnquoteArguments() {
098        return unquoteArguments;
099      }
100    
101      public final void setUnquoteArguments(boolean unquoteArguments) {
102        this.unquoteArguments = unquoteArguments;
103      }
104    
105      protected final String readLine(String msg) {
106        return readLine(msg, true);
107      }
108    
109      protected final String readLine(String msg, boolean echo) {
110        if (context instanceof InvocationContext) {
111          return ((InvocationContext)context).readLine(msg, echo);
112        } else {
113          throw new IllegalStateException("No current context of interaction with the term");
114        }
115      }
116    
117      public final String getUnmatched() {
118        return unmatched;
119      }
120    
121      @Override
122      protected final CommandContext getContext() {
123        return context;
124      }
125    
126      public final CommandCompletion complete(CommandContext context, String line) {
127    
128        // WTF
129        Matcher analyzer = Matcher.createMatcher("main", descriptor);
130    
131        //
132        Completer completer = this instanceof Completer ? (Completer)this : null;
133    
134        //
135        try {
136          this.context = context;
137    
138          //
139          return analyzer.complete(completer, line);
140        }
141        catch (CmdCompletionException e) {
142          log.error("Error during completion of line " + line, e);
143          return new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
144        } finally {
145          this.context = null;
146        }
147      }
148    
149      public final String describe(String line, DescriptionFormat mode) {
150    
151        // WTF
152        Matcher analyzer = Matcher.createMatcher("main", descriptor);
153    
154        //
155        CommandMatch match = analyzer.match(line);
156    
157        //
158        try {
159          switch (mode) {
160            case DESCRIBE:
161              return match.getDescriptor().getUsage();
162            case MAN:
163              StringWriter sw = new StringWriter();
164              PrintWriter pw = new PrintWriter(sw);
165              match.printMan(pw);
166              return sw.toString();
167            case USAGE:
168              StringWriter sw2 = new StringWriter();
169              PrintWriter pw2 = new PrintWriter(sw2);
170              match.printUsage(pw2);
171              return sw2.toString();
172          }
173        }
174        catch (IOException e) {
175          throw new AssertionError(e);
176        }
177    
178        //
179        return null;
180      }
181    
182      public final CommandInvoker<?, ?> createInvoker(final String line) {
183    
184        // Remove surrounding quotes if there are
185        if (unquoteArguments) {
186          // todo ?
187        }
188    
189        // WTF
190        Matcher analyzer = Matcher.createMatcher("main", descriptor);
191    
192        //
193        final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line);
194    
195        //
196        if (match instanceof MethodMatch) {
197    
198          //
199          final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match;
200    
201          //
202          boolean help = false;
203          for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) {
204            ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
205            if (parameterDesc instanceof OptionDescriptor<?>) {
206              OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
207              if (optionDesc.getNames().contains("h")) {
208                help = true;
209              }
210            }
211          }
212          final boolean doHelp = help;
213    
214          //
215          return new CommandInvoker() {
216    
217            Class consumedType = Void.class;
218            Class producedType = Void.class;
219    
220            {
221              // Try to find a command context argument
222              Method m = methodMatch.getDescriptor().getMethod();
223    
224              //
225              Class<?>[] parameterTypes = m.getParameterTypes();
226              for (int i = 0;i < parameterTypes.length;i++) {
227                Class<?> parameterType = parameterTypes[i];
228                if (InvocationContext.class.isAssignableFrom(parameterType)) {
229                  Type contextGenericParameterType = m.getGenericParameterTypes()[i];
230                  consumedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
231                  producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 1);
232                }
233              }
234            }
235    
236            public void invoke(InvocationContext context) throws Exception {
237    
238              if (doHelp) {
239                try {
240                  match.printUsage(context.getWriter());
241                }
242                catch (IOException e) {
243                  throw new AssertionError(e);
244                }
245              } else {
246                CRaSHCommand.this.context = context;
247                CRaSHCommand.this.unmatched = methodMatch.getRest();
248                try {
249                  org.crsh.cmdline.matcher.InvocationContext invocationContext = new org.crsh.cmdline.matcher.InvocationContext();
250                  invocationContext.setAttribute(InvocationContext.class, context);
251                  Object o = methodMatch.invoke(invocationContext, CRaSHCommand.this);
252                  if (o != null) {
253                    context.getWriter().print(o);
254                  }
255                } catch (CmdInvocationException e) {
256                  Throwable cause = e.getCause();
257                  if (cause instanceof Exception) {
258                    throw (Exception)cause;
259                  } else {
260                    throw new UndeclaredThrowableException(cause);
261                  }
262                } finally {
263                  CRaSHCommand.this.context = null;
264                  CRaSHCommand.this.unmatched = null;
265                }
266              }
267    
268              //
269            }
270    
271            public Class getProducedType() {
272              return producedType;
273            }
274    
275            public Class getConsumedType() {
276              return consumedType;
277            }
278          };
279        } else if (match instanceof ClassMatch) {
280    
281          //
282          final ClassMatch<?> classMatch = (ClassMatch)match;
283    
284          //
285          boolean help = false;
286          for (OptionMatch optionMatch : classMatch.getOptionMatches()) {
287            ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
288            if (parameterDesc instanceof OptionDescriptor<?>) {
289              OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
290              if (optionDesc.getNames().contains("h")) {
291                help = true;
292              }
293            }
294          }
295          final boolean doHelp = help;
296    
297          //
298          return new CommandInvoker<Void, Void>() {
299            public void invoke(InvocationContext<Void, Void> context) throws ScriptException {
300              try {
301                if (doHelp) {
302                  match.printUsage(context.getWriter());
303                } else {
304                  classMatch.printUsage(context.getWriter());
305                }
306              }
307              catch (IOException e) {
308                throw new AssertionError(e);
309              }
310            }
311    
312            public Class<Void> getProducedType() {
313              return Void.class;
314            }
315    
316            public Class<Void> getConsumedType() {
317              return Void.class;
318            }
319          };
320    
321        } else {
322          return null;
323        }
324      }
325    }