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