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 }