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 }