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.shell.impl.command.spi;
021    
022    import org.crsh.cli.descriptor.CommandDescriptor;
023    import org.crsh.cli.descriptor.Format;
024    import org.crsh.cli.impl.Delimiter;
025    import org.crsh.cli.impl.completion.CompletionException;
026    import org.crsh.cli.impl.completion.CompletionMatch;
027    import org.crsh.cli.impl.completion.CompletionMatcher;
028    import org.crsh.cli.impl.invocation.InvocationMatch;
029    import org.crsh.cli.impl.invocation.InvocationMatcher;
030    import org.crsh.cli.impl.lang.Util;
031    import org.crsh.cli.spi.Completer;
032    import org.crsh.cli.spi.Completion;
033    import org.crsh.command.RuntimeContext;
034    import org.crsh.shell.ErrorKind;
035    
036    import java.io.IOException;
037    import java.util.Collections;
038    import java.util.List;
039    import java.util.Map;
040    
041    /**
042     * A command as seen by the shell.
043     */
044    public abstract class Command<T> {
045    
046      /**
047       * Returns the command descriptor.
048       *
049       * @return the descriptor
050       */
051      public abstract CommandDescriptor<T> getDescriptor();
052    
053      /**
054       * Returns a completer for this command.
055       *
056       * @param context the related runtime context
057       * @return the completer
058       * @throws CommandException anything that would prevent completion to happen
059       */
060      protected abstract Completer getCompleter(RuntimeContext context) throws CommandException;
061    
062      /**
063       * Resolve the real match for a specified invocation match.
064       *
065       * @param match the match
066       * @return the command
067       */
068      protected abstract CommandMatch<?, ?> resolve(InvocationMatch<T> match);
069    
070      public final String describe(final InvocationMatch<T> match, Format format) {
071    
072        //
073        final CommandMatch<?, ?> commandMatch = resolve(match);
074    
075        //
076        if (format instanceof Format.Man) {
077          final Format.Man man = (Format.Man)format;
078          format = new Format.Man() {
079            @Override
080            public void printSynopsisSection(CommandDescriptor<?> descriptor, Appendable stream) throws IOException {
081              man.printSynopsisSection(descriptor, stream);
082    
083              // Extra stream section
084              if (match.getDescriptor().getSubordinates().isEmpty()) {
085                stream.append("STREAM\n");
086                stream.append(Util.MAN_TAB);
087                printFQN(descriptor, stream);
088                stream.append(" <").append(commandMatch.getConsumedType().getName()).append(", ").append(commandMatch.getProducedType().getName()).append('>');
089                stream.append("\n\n");
090              }
091            }
092          };
093        }
094    
095        //
096        try {
097          StringBuffer buffer = new StringBuffer();
098          match.getDescriptor().print(format, buffer);
099          return buffer.toString();
100        }
101        catch (IOException e) {
102          throw new AssertionError(e);
103        }
104      }
105    
106      /**
107       * Provide completions for the specified arguments.
108       *
109       * @param context the command context
110       * @param line the original command line arguments
111       * @return the completions
112       */
113      public final CompletionMatch complete(RuntimeContext context, String line) throws CommandException {
114        CompletionMatcher matcher = getDescriptor().completer();
115        Completer completer = getCompleter(context);
116        try {
117          return matcher.match(completer, line);
118        }
119        catch (CompletionException e) {
120          // command.log.log(Level.SEVERE, "Error during completion of line " + line, e);
121          return new CompletionMatch(Delimiter.EMPTY, Completion.create());
122        }
123      }
124    
125      /**
126       * Returns a description of the command or null if none can be found.
127       *
128       * @param line the usage line
129       * @param format the description format
130       * @return the description
131       */
132      public final String describe(String line, Format format) throws CommandException {
133        CommandDescriptor<T> descriptor = getDescriptor();
134        InvocationMatcher<T> analyzer = descriptor.matcher();
135        InvocationMatch<T> match;
136        try {
137          match = analyzer.parse(line);
138        }
139        catch (org.crsh.cli.impl.SyntaxException e) {
140          throw new CommandException(ErrorKind.SYNTAX, "Syntax exception when evaluating " + descriptor.getName(), e);
141        }
142        return describe(match, format);
143      }
144    
145      /**
146       * Provides an invoker for the command line specified as a command line to parse.
147       *
148       * @param line the command line arguments
149       * @return the command
150       */
151      public final CommandInvoker<?, ?> resolveInvoker(String line) throws CommandException {
152        return resolveCommand(line).getInvoker();
153      }
154    
155      public final CommandMatch<?, ?> resolveCommand(String line) throws CommandException {
156        CommandDescriptor<T> descriptor = getDescriptor();
157        InvocationMatcher<T> analyzer = descriptor.matcher();
158        InvocationMatch<T> match;
159        try {
160          match = analyzer.parse(line);
161        }
162        catch (org.crsh.cli.impl.SyntaxException e) {
163          throw new CommandException(ErrorKind.SYNTAX, "Syntax exception when evaluating "+ getDescriptor().getName(), e);
164        }
165        return resolve(match);
166      }
167    
168      /**
169       * Provides an invoker for the command line specified in a detyped manner.
170       *
171       * @param options the base options
172       * @param subordinate the subordinate command name, might null
173       * @param subordinateOptions the subordinate options
174       * @param arguments arguments
175       * @return the command
176       */
177      public final CommandMatch<?, ?> resolveCommand(Map<String, ?> options, String subordinate, Map<String, ?> subordinateOptions, List<?> arguments) throws CommandException {
178        InvocationMatcher<T> matcher = getDescriptor().matcher();
179    
180        //
181        InvocationMatch<T> match;
182        try {
183          if (options != null && options.size() > 0) {
184            for (Map.Entry<String, ?> option : options.entrySet()) {
185              matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
186            }
187          }
188    
189          //
190          if (subordinate != null && subordinate.length() > 0) {
191            matcher = matcher.subordinate(subordinate);
192    
193            // Minor : remove that and use same signature
194            if (subordinateOptions != null && subordinateOptions.size() > 0) {
195              for (Map.Entry<String, ?> option : subordinateOptions.entrySet()) {
196                matcher = matcher.option(option.getKey(), Collections.singletonList(option.getValue()));
197              }
198            }
199          }
200    
201          //
202          match = matcher.arguments(arguments != null ? arguments : Collections.emptyList());
203        }
204        catch (org.crsh.cli.impl.SyntaxException e) {
205          throw new CommandException(ErrorKind.EVALUATION, "Could not resolve command " + getDescriptor().getName(), e);
206        }
207    
208        //
209        return resolve(match);
210      }
211    }