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 }