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        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    }