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.cli.descriptor;
021    
022    import org.crsh.cli.impl.completion.CompletionMatcher;
023    import org.crsh.cli.impl.descriptor.IntrospectionException;
024    import org.crsh.cli.impl.Multiplicity;
025    import org.crsh.cli.impl.invocation.CommandInvoker;
026    import org.crsh.cli.impl.invocation.InvocationMatch;
027    import org.crsh.cli.impl.invocation.InvocationMatcher;
028    
029    import java.io.IOException;
030    import java.util.ArrayList;
031    import java.util.Collection;
032    import java.util.Collections;
033    import java.util.HashSet;
034    import java.util.LinkedHashMap;
035    import java.util.List;
036    import java.util.ListIterator;
037    import java.util.Map;
038    import java.util.Set;
039    
040    public abstract class CommandDescriptor<T> {
041    
042      /** . */
043      private final String name;
044    
045      /** . */
046      private final Description description;
047    
048      /** . */
049      private final Map<String, OptionDescriptor> optionMap;
050    
051      /** . */
052      private final Set<String> shortOptionNames;
053    
054      /** . */
055      private final Set<String> longOptionNames;
056    
057      /** . */
058      private boolean listArgument;
059    
060      /** . */
061      private final List<OptionDescriptor> options;
062    
063      /** . */
064      private final List<ArgumentDescriptor> arguments;
065    
066      /** . */
067      private final List<ParameterDescriptor> parameters;
068    
069      /** . */
070      private final Map<String, OptionDescriptor> uOptionMap;
071    
072      /** . */
073      private final Set<String> uShortOptionNames;
074    
075      /** . */
076      private final Set<String> uLongOptionNames;
077    
078      /** . */
079      private final List<OptionDescriptor> uOptions;
080    
081      /** . */
082      private final List<ArgumentDescriptor> uArguments;
083    
084      /** . */
085      private final List<ParameterDescriptor> uParameters;
086    
087      protected CommandDescriptor(String name, Description description) throws IntrospectionException {
088    
089        //
090        this.description = description;
091        this.optionMap = new LinkedHashMap<String, OptionDescriptor>();
092        this.arguments = new ArrayList<ArgumentDescriptor>();
093        this.options = new ArrayList<OptionDescriptor>();
094        this.name = name;
095        this.parameters = new ArrayList<ParameterDescriptor>();
096        this.listArgument = false;
097        this.shortOptionNames = new HashSet<String>();
098        this.longOptionNames = new HashSet<String>();
099    
100        //
101        this.uOptionMap = Collections.unmodifiableMap(optionMap);
102        this.uParameters = Collections.unmodifiableList(parameters);
103        this.uOptions = Collections.unmodifiableList(options);
104        this.uArguments = Collections.unmodifiableList(arguments);
105        this.uShortOptionNames = shortOptionNames;
106        this.uLongOptionNames = longOptionNames;
107      }
108    
109      /**
110       * Add a parameter to the command.
111       *
112       * @param parameter the parameter to add
113       * @throws IntrospectionException any introspection exception that would prevent the parameter to be added
114       * @throws NullPointerException if the parameter is null
115       * @throws IllegalArgumentException if the parameter is already associated with another command
116       */
117      protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
118    
119        //
120        if (parameter == null) {
121          throw new NullPointerException("No null parameter accepted");
122        }
123    
124        //
125        if (parameter instanceof OptionDescriptor) {
126          OptionDescriptor option = (OptionDescriptor)parameter;
127          for (String optionName : option.getNames()) {
128            String name;
129            if (optionName.length() == 1) {
130              name = "-" + optionName;
131              if (shortOptionNames.contains(name)) {
132                throw new IntrospectionException();
133              } else {
134                shortOptionNames.add(name);
135              }
136            } else {
137              name = "--" + optionName;
138              if (longOptionNames.contains(name)) {
139                throw new IntrospectionException();
140              } else {
141                longOptionNames.add(name);
142              }
143            }
144            optionMap.put(name, option);
145          }
146          options.add(option);
147          ListIterator<ParameterDescriptor> i = parameters.listIterator();
148          while (i.hasNext()) {
149            ParameterDescriptor next = i.next();
150            if (next instanceof ArgumentDescriptor) {
151              i.previous();
152              break;
153            }
154          }
155          i.add(parameter);
156        } else if (parameter instanceof ArgumentDescriptor) {
157          ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
158          if (argument.getMultiplicity() == Multiplicity.MULTI) {
159            if (listArgument) {
160              throw new IntrospectionException();
161            }
162            listArgument = true;
163          }
164          arguments.add(argument);
165          parameters.add(argument);
166        } else {
167          throw new AssertionError("Unreachable");
168        }
169      }
170    
171      public abstract CommandDescriptor<T> getOwner();
172    
173      public final int getDepth() {
174        CommandDescriptor<T> owner = getOwner();
175        return owner == null ? 0 : 1 + owner.getDepth();
176      }
177    
178    
179      public final void printUsage(Appendable to) throws IOException {
180        print(Format.USAGE, to);
181      }
182    
183      public final void printMan(Appendable to) throws IOException {
184        print(Format.MAN, to);
185      }
186    
187      public final void print(Format format, Appendable to) throws IOException {
188        format.print(this, to);
189      }
190    
191      /**
192       * @return the command subordinates as a map.
193       */
194      public abstract Map<String, ? extends CommandDescriptor<T>> getSubordinates();
195    
196      /**
197       * Returns a specified subordinate.
198       *
199       * @param name the subordinate name
200       * @return the subordinate command or null
201       */
202      public final CommandDescriptor<T> getSubordinate(String name) {
203        return getSubordinates().get(name);
204      }
205    
206      /**
207       * Returns the command parameters, the returned collection contains the command options and
208       * the command arguments.
209       *
210       * @return the command parameters
211       */
212      public final List<ParameterDescriptor> getParameters() {
213        return uParameters;
214      }
215    
216      /**
217       * Returns the command option names.
218       *
219       * @return the command option names
220       */
221      public final Set<String> getOptionNames() {
222        return uOptionMap.keySet();
223      }
224    
225      /**
226       * Returns the command short option names.
227       *
228       * @return the command long option names
229       */
230      public final Set<String> getShortOptionNames() {
231        return uShortOptionNames;
232      }
233    
234      /**
235       * Returns the command long option names.
236       *
237       * @return the command long option names
238       */
239      public final Set<String> getLongOptionNames() {
240        return uLongOptionNames;
241      }
242    
243      /**
244       * Returns the command options.
245       *
246       * @return the command options
247       */
248      public final Collection<OptionDescriptor> getOptions() {
249        return uOptions;
250      }
251    
252      /**
253       * Returns a command option by its name.
254       *
255       * @param name the option name
256       * @return the option
257       */
258      public final OptionDescriptor getOption(String name) {
259        return optionMap.get(name);
260      }
261    
262      /**
263       * Find an command option by its name, this will look through the command hierarchy.
264       *
265       * @param name the option name
266       * @return the option or null
267       */
268      public final OptionDescriptor resolveOption(String name) {
269        OptionDescriptor option = getOption(name);
270        if (option == null) {
271          CommandDescriptor<T> owner = getOwner();
272          if (owner != null) {
273            option = owner.resolveOption(name);
274          }
275        }
276        return option;
277      }
278    
279      /**
280       * Returns a list of the command arguments.
281       *
282       * @return the command arguments
283       */
284      public final List<ArgumentDescriptor> getArguments() {
285        return uArguments;
286      }
287    
288      /**
289       * Returns a a specified argument by its index.
290       *
291       * @param index the argument index
292       * @return the command argument
293       * @throws IllegalArgumentException if the index is not within the bounds
294       */
295      public final ArgumentDescriptor getArgument(int index) throws IllegalArgumentException {
296        if (index < 0) {
297          throw new IllegalArgumentException();
298        }
299        if (index >= arguments.size()) {
300          throw new IllegalArgumentException();
301        }
302        return arguments.get(index);
303      }
304    
305      /**
306       * Returns the command name.
307       *
308       * @return the command name
309       */
310      public final String getName() {
311        return name;
312      }
313    
314      /**
315       * Returns the command description.
316       *
317       * @return the command description
318       */
319      public final Description getDescription() {
320        return description;
321      }
322    
323      /**
324       * Returns the command usage, shortcut for invoking <code>getDescription().getUsage()</code> on this
325       * object.
326       *
327       * @return the command usage
328       */
329      public final String getUsage() {
330        return description != null ? description.getUsage() : "";
331      }
332    
333      public abstract CommandInvoker<T, ?> getInvoker(InvocationMatch<T> match);
334    
335      public final InvocationMatcher<T> matcher() {
336        return new InvocationMatcher<T>(this);
337      }
338    
339      public final CompletionMatcher<T> completer() {
340        return new CompletionMatcher<T>(this);
341      }
342    
343    }