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 020package org.crsh.cli.descriptor; 021 022import org.crsh.cli.impl.completion.CompletionMatcher; 023import org.crsh.cli.impl.descriptor.IntrospectionException; 024import org.crsh.cli.impl.Multiplicity; 025import org.crsh.cli.impl.invocation.CommandInvoker; 026import org.crsh.cli.impl.invocation.InvocationMatch; 027import org.crsh.cli.impl.invocation.InvocationMatcher; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.HashSet; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.ListIterator; 037import java.util.Map; 038import java.util.Set; 039 040public 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}