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    int nameLength = name.length();
091    if (nameLength == 0) {
092      throw new IntrospectionException("Command name cannot be null");
093    } else {
094      for (int i = 0;i < nameLength;i++) {
095        char c = name.charAt(i);
096        if (i == 0) {
097          if (!Character.isLetter(c)) {
098            throw new IntrospectionException("Invalid command name <" + name + "> does not start with a letter");
099          }
100        } else {
101          if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_' && c != '-') {
102            throw new IntrospectionException("Invalid command name <" + name + "> char " + c + " at position " + i + " is now allowed");
103          }
104        }
105      }
106    }
107
108    //
109    this.description = description;
110    this.optionMap = new LinkedHashMap<String, OptionDescriptor>();
111    this.arguments = new ArrayList<ArgumentDescriptor>();
112    this.options = new ArrayList<OptionDescriptor>();
113    this.name = name;
114    this.parameters = new ArrayList<ParameterDescriptor>();
115    this.listArgument = false;
116    this.shortOptionNames = new HashSet<String>();
117    this.longOptionNames = new HashSet<String>();
118
119    //
120    this.uOptionMap = Collections.unmodifiableMap(optionMap);
121    this.uParameters = Collections.unmodifiableList(parameters);
122    this.uOptions = Collections.unmodifiableList(options);
123    this.uArguments = Collections.unmodifiableList(arguments);
124    this.uShortOptionNames = shortOptionNames;
125    this.uLongOptionNames = longOptionNames;
126  }
127
128  /**
129   * Add a parameter to the command.
130   *
131   * @param parameter the parameter to add
132   * @throws IntrospectionException any introspection exception that would prevent the parameter to be added
133   * @throws NullPointerException if the parameter is null
134   * @throws IllegalArgumentException if the parameter is already associated with another command
135   */
136  protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
137
138    //
139    if (parameter == null) {
140      throw new NullPointerException("No null parameter accepted");
141    }
142
143    //
144    if (parameter instanceof OptionDescriptor) {
145      OptionDescriptor option = (OptionDescriptor)parameter;
146      for (String optionName : option.getNames()) {
147        String name;
148        if (optionName.length() == 1) {
149          name = "-" + optionName;
150          if (shortOptionNames.contains(name)) {
151            throw new IntrospectionException("Duplicate option " + name);
152          } else {
153            shortOptionNames.add(name);
154          }
155        } else {
156          name = "--" + optionName;
157          if (longOptionNames.contains(name)) {
158            throw new IntrospectionException();
159          } else {
160            longOptionNames.add(name);
161          }
162        }
163        optionMap.put(name, option);
164      }
165      options.add(option);
166      ListIterator<ParameterDescriptor> i = parameters.listIterator();
167      while (i.hasNext()) {
168        ParameterDescriptor next = i.next();
169        if (next instanceof ArgumentDescriptor) {
170          i.previous();
171          break;
172        }
173      }
174      i.add(parameter);
175    } else if (parameter instanceof ArgumentDescriptor) {
176      ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
177      if (argument.getMultiplicity() == Multiplicity.MULTI) {
178        if (listArgument) {
179          throw new IntrospectionException();
180        }
181        listArgument = true;
182      }
183      arguments.add(argument);
184      parameters.add(argument);
185    } else {
186      throw new AssertionError("Unreachable");
187    }
188  }
189
190  public abstract CommandDescriptor<T> getOwner();
191
192  public final int getDepth() {
193    CommandDescriptor<T> owner = getOwner();
194    return owner == null ? 0 : 1 + owner.getDepth();
195  }
196
197
198  public final void printUsage(Appendable to) throws IOException {
199    print(Format.USAGE, to);
200  }
201
202  public final void printMan(Appendable to) throws IOException {
203    print(Format.MAN, to);
204  }
205
206  public final void print(Format format, Appendable to) throws IOException {
207    format.print(this, to);
208  }
209
210  /**
211   * @return the command subordinates as a map.
212   */
213  public abstract Map<String, ? extends CommandDescriptor<T>> getSubordinates();
214
215  /**
216   * Returns a specified subordinate.
217   *
218   * @param name the subordinate name
219   * @return the subordinate command or null
220   */
221  public final CommandDescriptor<T> getSubordinate(String name) {
222    return getSubordinates().get(name);
223  }
224
225  /**
226   * Returns the command parameters, the returned collection contains the command options and
227   * the command arguments.
228   *
229   * @return the command parameters
230   */
231  public final List<ParameterDescriptor> getParameters() {
232    return uParameters;
233  }
234
235  /**
236   * Returns the command option names.
237   *
238   * @return the command option names
239   */
240  public final Set<String> getOptionNames() {
241    return uOptionMap.keySet();
242  }
243
244  /**
245   * Returns the command short option names.
246   *
247   * @return the command long option names
248   */
249  public final Set<String> getShortOptionNames() {
250    return uShortOptionNames;
251  }
252
253  /**
254   * Returns the command long option names.
255   *
256   * @return the command long option names
257   */
258  public final Set<String> getLongOptionNames() {
259    return uLongOptionNames;
260  }
261
262  /**
263   * Returns the command options.
264   *
265   * @return the command options
266   */
267  public final Collection<OptionDescriptor> getOptions() {
268    return uOptions;
269  }
270
271  /**
272   * Returns a command option by its name.
273   *
274   * @param name the option name
275   * @return the option
276   */
277  public final OptionDescriptor getOption(String name) {
278    return optionMap.get(name);
279  }
280
281  /**
282   * Find an command option by its name, this will look through the command hierarchy.
283   *
284   * @param name the option name
285   * @return the option or null
286   */
287  public final OptionDescriptor resolveOption(String name) {
288    OptionDescriptor option = getOption(name);
289    if (option == null) {
290      CommandDescriptor<T> owner = getOwner();
291      if (owner != null) {
292        option = owner.resolveOption(name);
293      }
294    }
295    return option;
296  }
297
298  /**
299   * Returns a list of the command arguments.
300   *
301   * @return the command arguments
302   */
303  public final List<ArgumentDescriptor> getArguments() {
304    return uArguments;
305  }
306
307  /**
308   * Returns a a specified argument by its index.
309   *
310   * @param index the argument index
311   * @return the command argument
312   * @throws IllegalArgumentException if the index is not within the bounds
313   */
314  public final ArgumentDescriptor getArgument(int index) throws IllegalArgumentException {
315    if (index < 0) {
316      throw new IllegalArgumentException();
317    }
318    if (index >= arguments.size()) {
319      throw new IllegalArgumentException();
320    }
321    return arguments.get(index);
322  }
323
324  /**
325   * Returns the command name.
326   *
327   * @return the command name
328   */
329  public final String getName() {
330    return name;
331  }
332
333  /**
334   * Returns the command description.
335   *
336   * @return the command description
337   */
338  public final Description getDescription() {
339    return description;
340  }
341
342  /**
343   * Returns the command usage, shortcut for invoking <code>getDescription().getUsage()</code> on this
344   * object.
345   *
346   * @return the command usage
347   */
348  public final String getUsage() {
349    return description != null ? description.getUsage() : "";
350  }
351
352  public abstract CommandInvoker<T, ?> getInvoker(InvocationMatch<T> match);
353
354  public final InvocationMatcher<T> matcher() {
355    return new InvocationMatcher<T>(this);
356  }
357
358  public final CompletionMatcher<T> completer() {
359    return new CompletionMatcher<T>(this);
360  }
361
362}