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
020package org.crsh.cli.descriptor;
021
022import org.crsh.cli.impl.completion.CompletionMatcher;
023import org.crsh.cli.impl.descriptor.IntrospectionException;
024import org.crsh.cli.impl.Multiplicity;
025import org.crsh.cli.impl.invocation.CommandInvoker;
026import org.crsh.cli.impl.invocation.InvocationMatch;
027import org.crsh.cli.impl.invocation.InvocationMatcher;
028
029import java.io.IOException;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.HashSet;
034import java.util.LinkedHashMap;
035import java.util.List;
036import java.util.ListIterator;
037import java.util.Map;
038import java.util.Set;
039
040public 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}