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 /*
021 * Copyright (C) 2012 eXo Platform SAS.
022 *
023 * This is free software; you can redistribute it and/or modify it
024 * under the terms of the GNU Lesser General Public License as
025 * published by the Free Software Foundation; either version 2.1 of
026 * the License, or (at your option) any later version.
027 *
028 * This software is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
031 * Lesser General Public License for more details.
032 *
033 * You should have received a copy of the GNU Lesser General Public
034 * License along with this software; if not, write to the Free
035 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
036 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
037 */
038
039 package org.crsh.cli.descriptor;
040
041 import org.crsh.cli.impl.descriptor.IntrospectionException;
042 import org.crsh.cli.impl.Multiplicity;
043 import org.crsh.cli.impl.lang.Util;
044
045 import java.io.IOException;
046 import java.util.ArrayList;
047 import java.util.Collection;
048 import java.util.Collections;
049 import java.util.Formatter;
050 import java.util.HashSet;
051 import java.util.LinkedHashMap;
052 import java.util.List;
053 import java.util.ListIterator;
054 import java.util.Map;
055 import java.util.Set;
056
057 import static org.crsh.cli.impl.lang.Util.tuples;
058
059 public abstract class CommandDescriptor<T> {
060
061 /** . */
062 private static final Set<String> MAIN_SINGLETON = Collections.singleton("main");
063
064 /** . */
065 private final String name;
066
067 /** . */
068 private final Description description;
069
070 /** . */
071 private final Map<String, OptionDescriptor> optionMap;
072
073 /** . */
074 private final Set<String> shortOptionNames;
075
076 /** . */
077 private final Set<String> longOptionNames;
078
079 /** . */
080 private boolean listArgument;
081
082 /** . */
083 private final List<OptionDescriptor> options;
084
085 /** . */
086 private final List<ArgumentDescriptor> arguments;
087
088 /** . */
089 private final List<ParameterDescriptor> parameters;
090
091 /** . */
092 private final Map<String, OptionDescriptor> uOptionMap;
093
094 /** . */
095 private final Set<String> uShortOptionNames;
096
097 /** . */
098 private final Set<String> uLongOptionNames;
099
100 /** . */
101 private final List<OptionDescriptor> uOptions;
102
103 /** . */
104 private final List<ArgumentDescriptor> uArguments;
105
106 /** . */
107 private final List<ParameterDescriptor> uParameters;
108
109 protected CommandDescriptor(String name, Description description) throws IntrospectionException {
110
111 //
112 this.description = description;
113 this.optionMap = new LinkedHashMap<String, OptionDescriptor>();
114 this.arguments = new ArrayList<ArgumentDescriptor>();
115 this.options = new ArrayList<OptionDescriptor>();
116 this.name = name;
117 this.parameters = new ArrayList<ParameterDescriptor>();
118 this.listArgument = false;
119 this.shortOptionNames = new HashSet<String>();
120 this.longOptionNames = new HashSet<String>();
121
122 //
123 this.uOptionMap = Collections.unmodifiableMap(optionMap);
124 this.uParameters = Collections.unmodifiableList(parameters);
125 this.uOptions = Collections.unmodifiableList(options);
126 this.uArguments = Collections.unmodifiableList(arguments);
127 this.uShortOptionNames = shortOptionNames;
128 this.uLongOptionNames = longOptionNames;
129 }
130
131 /**
132 * Add a parameter to the command.
133 *
134 * @param parameter the parameter to add
135 * @throws IntrospectionException any introspection exception that would prevent the parameter to be added
136 * @throws NullPointerException if the parameter is null
137 * @throws IllegalArgumentException if the parameter is already associated with another command
138 */
139 protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
140
141 //
142 if (parameter == null) {
143 throw new NullPointerException("No null parameter accepted");
144 }
145
146 //
147 if (parameter instanceof OptionDescriptor) {
148 OptionDescriptor option = (OptionDescriptor)parameter;
149 for (String optionName : option.getNames()) {
150 String name;
151 if (optionName.length() == 1) {
152 name = "-" + optionName;
153 shortOptionNames.add(name);
154 } else {
155 name = "--" + optionName;
156 longOptionNames.add(name);
157 }
158 optionMap.put(name, option);
159 }
160 options.add(option);
161 ListIterator<ParameterDescriptor> i = parameters.listIterator();
162 while (i.hasNext()) {
163 ParameterDescriptor next = i.next();
164 if (next instanceof ArgumentDescriptor) {
165 i.previous();
166 break;
167 }
168 }
169 i.add(parameter);
170 } else if (parameter instanceof ArgumentDescriptor) {
171 ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
172 if (argument.getMultiplicity() == Multiplicity.MULTI) {
173 if (listArgument) {
174 throw new IntrospectionException();
175 }
176 listArgument = true;
177 }
178 arguments.add(argument);
179 parameters.add(argument);
180 }
181 }
182
183 public abstract Class<T> getType();
184
185 public abstract CommandDescriptor<T> getOwner();
186
187 public final int getDepth() {
188 CommandDescriptor<T> owner = getOwner();
189 return owner == null ? 0 : 1 + owner.getDepth();
190 }
191
192 public final void printUsage(Appendable writer) throws IOException {
193 int depth = getDepth();
194 switch (depth) {
195 case 0: {
196 Map<String, ? extends CommandDescriptor<T>> methods = getSubordinates();
197 if (methods.size() == 1) {
198 methods.values().iterator().next().printUsage(writer);
199 } else {
200 writer.append("usage: ").append(getName());
201 for (OptionDescriptor option : getOptions()) {
202 option.printUsage(writer);
203 }
204 writer.append(" COMMAND [ARGS]\n\n");
205 writer.append("The most commonly used ").append(getName()).append(" commands are:\n");
206 String format = " %1$-16s %2$s\n";
207 for (CommandDescriptor<T> method : methods.values()) {
208 Formatter formatter = new Formatter(writer);
209 formatter.format(format, method.getName(), method.getUsage());
210 }
211 }
212 break;
213 }
214 case 1: {
215
216 CommandDescriptor<T> owner = getOwner();
217 int length = 0;
218 List<String> parameterUsages = new ArrayList<String>();
219 List<String> parameterBilto = new ArrayList<String>();
220 boolean printName = !owner.getSubordinates().keySet().equals(MAIN_SINGLETON);
221
222 //
223 writer.append("usage: ").append(owner.getName());
224
225 //
226 for (OptionDescriptor option : owner.getOptions()) {
227 writer.append(" ");
228 StringBuilder sb = new StringBuilder();
229 option.printUsage(sb);
230 String usage = sb.toString();
231 writer.append(usage);
232
233 length = Math.max(length, usage.length());
234 parameterUsages.add(usage);
235 parameterBilto.add(option.getUsage());
236 }
237
238 //
239 writer.append(printName ? (" " + getName()) : "");
240
241 //
242 for (ParameterDescriptor parameter : getParameters()) {
243 writer.append(" ");
244 StringBuilder sb = new StringBuilder();
245 parameter.printUsage(sb);
246 String usage = sb.toString();
247 writer.append(usage);
248
249 length = Math.max(length, usage.length());
250 parameterBilto.add(parameter.getUsage());
251 parameterUsages.add(usage);
252 }
253 writer.append("\n\n");
254
255 //
256 String format = " %1$-" + length + "s %2$s\n";
257 for (String[] tuple : tuples(String.class, parameterUsages, parameterBilto)) {
258 Formatter formatter = new Formatter(writer);
259 formatter.format(format, tuple[0], tuple[1]);
260 }
261
262 //
263 writer.append("\n\n");
264 break;
265 }
266 default:
267 throw new UnsupportedOperationException("Does not make sense");
268 }
269
270
271 }
272
273 public final void printMan(Appendable writer) throws IOException {
274 int depth = getDepth();
275 switch (depth) {
276 case 0: {
277 Map<String, ? extends CommandDescriptor<T>> methods = getSubordinates();
278 if (methods.size() == 1) {
279 methods.values().iterator().next().printMan(writer);
280 } else {
281
282 // Name
283 writer.append("NAME\n");
284 writer.append(Util.MAN_TAB).append(getName());
285 if (getUsage().length() > 0) {
286 writer.append(" - ").append(getUsage());
287 }
288 writer.append("\n\n");
289
290 // Synopsis
291 writer.append("SYNOPSIS\n");
292 writer.append(Util.MAN_TAB).append(getName());
293 for (OptionDescriptor option : getOptions()) {
294 writer.append(" ");
295 option.printUsage(writer);
296 }
297 writer.append(" COMMAND [ARGS]\n\n");
298
299 //
300 String man = getDescription().getMan();
301 if (man.length() > 0) {
302 writer.append("DESCRIPTION\n");
303 Util.indent(Util.MAN_TAB, man, writer);
304 writer.append("\n\n");
305 }
306
307 // Common options
308 if (getOptions().size() > 0) {
309 writer.append("PARAMETERS\n");
310 for (OptionDescriptor option : getOptions()) {
311 writer.append(Util.MAN_TAB);
312 option.printUsage(writer);
313 String optionText = option.getDescription().getBestEffortMan();
314 if (optionText.length() > 0) {
315 writer.append("\n");
316 Util.indent(Util.MAN_TAB_EXTRA, optionText, writer);
317 }
318 writer.append("\n\n");
319 }
320 }
321
322 //
323 writer.append("COMMANDS\n");
324 for (CommandDescriptor<T> method : methods.values()) {
325 writer.append(Util.MAN_TAB).append(method.getName());
326 String methodText = method.getDescription().getBestEffortMan();
327 if (methodText.length() > 0) {
328 writer.append("\n");
329 Util.indent(Util.MAN_TAB_EXTRA, methodText, writer);
330 }
331 writer.append("\n\n");
332 }
333 }
334 break;
335 }
336 case 1: {
337
338 CommandDescriptor<T> owner = getOwner();
339
340 //
341 boolean printName = !owner.getSubordinates().keySet().equals(MAIN_SINGLETON);
342
343 // Name
344 writer.append("NAME\n");
345 writer.append(Util.MAN_TAB).append(owner.getName());
346 if (printName) {
347 writer.append(" ").append(getName());
348 }
349 if (getUsage().length() > 0) {
350 writer.append(" - ").append(getUsage());
351 }
352 writer.append("\n\n");
353
354 // Synopsis
355 writer.append("SYNOPSIS\n");
356 writer.append(Util.MAN_TAB).append(owner.getName());
357 for (OptionDescriptor option : owner.getOptions()) {
358 writer.append(" ");
359 option.printUsage(writer);
360 }
361 if (printName) {
362 writer.append(" ").append(getName());
363 }
364 for (OptionDescriptor option : getOptions()) {
365 writer.append(" ");
366 option.printUsage(writer);
367 }
368 for (ArgumentDescriptor argument : getArguments()) {
369 writer.append(" ");
370 argument.printUsage(writer);
371 }
372 writer.append("\n\n");
373
374 // Description
375 String man = getDescription().getMan();
376 if (man.length() > 0) {
377 writer.append("DESCRIPTION\n");
378 Util.indent(Util.MAN_TAB, man, writer);
379 writer.append("\n\n");
380 }
381
382 // Parameters
383 List<OptionDescriptor> options = new ArrayList<OptionDescriptor>();
384 options.addAll(owner.getOptions());
385 options.addAll(getOptions());
386 if (options.size() > 0) {
387 writer.append("\nPARAMETERS\n");
388 for (ParameterDescriptor parameter : Util.join(owner.getOptions(), getParameters())) {
389 writer.append(Util.MAN_TAB);
390 parameter.printUsage(writer);
391 String parameterText = parameter.getDescription().getBestEffortMan();
392 if (parameterText.length() > 0) {
393 writer.append("\n");
394 Util.indent(Util.MAN_TAB_EXTRA, parameterText, writer);
395 }
396 writer.append("\n\n");
397 }
398 }
399
400 //
401 break;
402 }
403 default:
404 throw new UnsupportedOperationException("Does not make sense");
405 }
406 }
407
408
409 /**
410 * Returns the command subordinates as a map.
411 *
412 * @return the subordinates
413 */
414 public abstract Map<String, ? extends CommandDescriptor<T>> getSubordinates();
415
416 public abstract CommandDescriptor<T> getSubordinate(String name);
417
418 /**
419 * Returns the command parameters, the returned collection contains the command options and
420 * the command arguments.
421 *
422 * @return the command parameters
423 */
424 public final List<ParameterDescriptor> getParameters() {
425 return uParameters;
426 }
427
428 /**
429 * Returns the command option names.
430 *
431 * @return the command option names
432 */
433 public final Set<String> getOptionNames() {
434 return uOptionMap.keySet();
435 }
436
437 /**
438 * Returns the command short option names.
439 *
440 * @return the command long option names
441 */
442 public final Set<String> getShortOptionNames() {
443 return uShortOptionNames;
444 }
445
446 /**
447 * Returns the command long option names.
448 *
449 * @return the command long option names
450 */
451 public final Set<String> getLongOptionNames() {
452 return uLongOptionNames;
453 }
454
455 /**
456 * Returns the command options.
457 *
458 * @return the command options
459 */
460 public final Collection<OptionDescriptor> getOptions() {
461 return uOptions;
462 }
463
464 /**
465 * Returns a command option by its name.
466 *
467 * @param name the option name
468 * @return the option
469 */
470 public final OptionDescriptor getOption(String name) {
471 return optionMap.get(name);
472 }
473
474 /**
475 * Find an command option by its name, this will look through the command hierarchy.
476 *
477 * @param name the option name
478 * @return the option or null
479 */
480 public final OptionDescriptor findOption(String name) {
481 OptionDescriptor option = getOption(name);
482 if (option == null) {
483 CommandDescriptor<T> owner = getOwner();
484 if (owner != null) {
485 option = owner.findOption(name);
486 }
487 }
488 return option;
489 }
490
491 /**
492 * Returns a list of the command arguments.
493 *
494 * @return the command arguments
495 */
496 public final List<ArgumentDescriptor> getArguments() {
497 return uArguments;
498 }
499
500 /**
501 * Returns a a specified argument by its index.
502 *
503 * @param index the argument index
504 * @return the command argument
505 * @throws IllegalArgumentException if the index is not within the bounds
506 */
507 public final ArgumentDescriptor getArgument(int index) throws IllegalArgumentException {
508 if (index < 0) {
509 throw new IllegalArgumentException();
510 }
511 if (index >= arguments.size()) {
512 throw new IllegalArgumentException();
513 }
514 return arguments.get(index);
515 }
516
517 /**
518 * Returns the command name.
519 *
520 * @return the command name
521 */
522 public final String getName() {
523 return name;
524 }
525
526 /**
527 * Returns the command description.
528 *
529 * @return the command description
530 */
531 public final Description getDescription() {
532 return description;
533 }
534
535 /**
536 * Returns the command usage, shortcut for invoking <code>getDescription().getUsage()</code> on this
537 * object.
538 *
539 * @return the command usage
540 */
541 public final String getUsage() {
542 return description != null ? description.getUsage() : "";
543 }
544 }