1 /** 2 * Copyright (C) 2010 the original author or authors. 3 * See the notice.md file distributed with this work for additional 4 * information regarding copyright ownership. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package com.beust.jcommander; 20 21 import com.beust.jcommander.validators.NoValidator; 22 import com.beust.jcommander.validators.NoValueValidator; 23 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.EnumSet; 27 import java.util.HashSet; 28 import java.util.LinkedHashSet; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.Map; 32 import java.util.ResourceBundle; 33 import java.util.Set; 34 import java.util.SortedSet; 35 import java.util.TreeSet; 36 37 public class ParameterDescription { 38 private Object m_object; 39 40 private WrappedParameter m_wrappedParameter; 41 private Parameter m_parameterAnnotation; 42 private DynamicParameter m_dynamicParameterAnnotation; 43 44 /** The field/method */ 45 private Parameterized m_parameterized; 46 /** Keep track of whether a value was added to flag an error */ 47 private boolean m_assigned = false; 48 private ResourceBundle m_bundle; 49 private String m_description; 50 private JCommander m_jCommander; 51 private Object m_default; 52 /** Longest of the names(), used to present usage() alphabetically */ 53 private String m_longestName = ""; 54 ParameterDescription(Object object, DynamicParameter annotation, Parameterized parameterized, ResourceBundle bundle, JCommander jc)55 public ParameterDescription(Object object, DynamicParameter annotation, 56 Parameterized parameterized, 57 ResourceBundle bundle, JCommander jc) { 58 if (! Map.class.isAssignableFrom(parameterized.getType())) { 59 throw new ParameterException("@DynamicParameter " + parameterized.getName() 60 + " should be of type " 61 + "Map but is " + parameterized.getType().getName()); 62 } 63 64 m_dynamicParameterAnnotation = annotation; 65 m_wrappedParameter = new WrappedParameter(m_dynamicParameterAnnotation); 66 init(object, parameterized, bundle, jc); 67 } 68 ParameterDescription(Object object, Parameter annotation, Parameterized parameterized, ResourceBundle bundle, JCommander jc)69 public ParameterDescription(Object object, Parameter annotation, Parameterized parameterized, 70 ResourceBundle bundle, JCommander jc) { 71 m_parameterAnnotation = annotation; 72 m_wrappedParameter = new WrappedParameter(m_parameterAnnotation); 73 init(object, parameterized, bundle, jc); 74 } 75 76 /** 77 * Find the resource bundle in the annotations. 78 * @return 79 */ 80 @SuppressWarnings("deprecation") findResourceBundle(Object o)81 private ResourceBundle findResourceBundle(Object o) { 82 ResourceBundle result = null; 83 84 Parameters p = o.getClass().getAnnotation(Parameters.class); 85 if (p != null && ! isEmpty(p.resourceBundle())) { 86 result = ResourceBundle.getBundle(p.resourceBundle(), Locale.getDefault()); 87 } else { 88 com.beust.jcommander.ResourceBundle a = o.getClass().getAnnotation( 89 com.beust.jcommander.ResourceBundle.class); 90 if (a != null && ! isEmpty(a.value())) { 91 result = ResourceBundle.getBundle(a.value(), Locale.getDefault()); 92 } 93 } 94 95 return result; 96 } 97 isEmpty(String s)98 private boolean isEmpty(String s) { 99 return s == null || "".equals(s); 100 } 101 initDescription(String description, String descriptionKey, String[] names)102 private void initDescription(String description, String descriptionKey, String[] names) { 103 m_description = description; 104 if (! "".equals(descriptionKey)) { 105 if (m_bundle != null) { 106 m_description = m_bundle.getString(descriptionKey); 107 } else { 108 // JCommander.getConsole().println("Warning: field " + object.getClass() + "." + field.getName() 109 // + " has a descriptionKey but no bundle was defined with @ResourceBundle, using " + 110 // "default description:'" + m_description + "'"); 111 } 112 } 113 114 for (String name : names) { 115 if (name.length() > m_longestName.length()) m_longestName = name; 116 } 117 } 118 119 @SuppressWarnings("unchecked") init(Object object, Parameterized parameterized, ResourceBundle bundle, JCommander jCommander)120 private void init(Object object, Parameterized parameterized, ResourceBundle bundle, 121 JCommander jCommander) { 122 m_object = object; 123 m_parameterized = parameterized; 124 m_bundle = bundle; 125 if (m_bundle == null) { 126 m_bundle = findResourceBundle(object); 127 } 128 m_jCommander = jCommander; 129 130 if (m_parameterAnnotation != null) { 131 String description; 132 if (Enum.class.isAssignableFrom(parameterized.getType()) 133 && m_parameterAnnotation.description().isEmpty()) { 134 description = "Options: " + EnumSet.allOf((Class<? extends Enum>) parameterized.getType()); 135 }else { 136 description = m_parameterAnnotation.description(); 137 } 138 initDescription(description, m_parameterAnnotation.descriptionKey(), 139 m_parameterAnnotation.names()); 140 } else if (m_dynamicParameterAnnotation != null) { 141 initDescription(m_dynamicParameterAnnotation.description(), 142 m_dynamicParameterAnnotation.descriptionKey(), 143 m_dynamicParameterAnnotation.names()); 144 } else { 145 throw new AssertionError("Shound never happen"); 146 } 147 148 try { 149 m_default = parameterized.get(object); 150 } catch (Exception e) { 151 } 152 153 // 154 // Validate default values, if any and if applicable 155 // 156 if (m_default != null) { 157 if (m_parameterAnnotation != null) { 158 validateDefaultValues(m_parameterAnnotation.names()); 159 } 160 } 161 } 162 validateDefaultValues(String[] names)163 private void validateDefaultValues(String[] names) { 164 String name = names.length > 0 ? names[0] : ""; 165 validateValueParameter(name, m_default); 166 } 167 getLongestName()168 public String getLongestName() { 169 return m_longestName; 170 } 171 getDefault()172 public Object getDefault() { 173 return m_default; 174 } 175 getDescription()176 public String getDescription() { 177 return m_description; 178 } 179 getObject()180 public Object getObject() { 181 return m_object; 182 } 183 getNames()184 public String getNames() { 185 StringBuilder sb = new StringBuilder(); 186 String[] names = m_wrappedParameter.names(); 187 for (int i = 0; i < names.length; i++) { 188 if (i > 0) sb.append(", "); 189 sb.append(names[i]); 190 } 191 return sb.toString(); 192 } 193 getParameter()194 public WrappedParameter getParameter() { 195 return m_wrappedParameter; 196 } 197 getParameterized()198 public Parameterized getParameterized() { 199 return m_parameterized; 200 } 201 isMultiOption()202 private boolean isMultiOption() { 203 Class<?> fieldType = m_parameterized.getType(); 204 return fieldType.equals(List.class) || fieldType.equals(Set.class) 205 || m_parameterized.isDynamicParameter(); 206 } 207 addValue(String value)208 public void addValue(String value) { 209 addValue(value, false /* not default */); 210 } 211 212 /** 213 * @return true if this parameter received a value during the parsing phase. 214 */ isAssigned()215 public boolean isAssigned() { 216 return m_assigned; 217 } 218 219 setAssigned(boolean b)220 public void setAssigned(boolean b) { 221 m_assigned = b; 222 } 223 224 /** 225 * Add the specified value to the field. First, validate the value if a 226 * validator was specified. Then look up any field converter, then any type 227 * converter, and if we can't find any, throw an exception. 228 */ addValue(String value, boolean isDefault)229 public void addValue(String value, boolean isDefault) { 230 p("Adding " + (isDefault ? "default " : "") + "value:" + value 231 + " to parameter:" + m_parameterized.getName()); 232 String name = m_wrappedParameter.names()[0]; 233 if (m_assigned && ! isMultiOption() && !m_jCommander.isParameterOverwritingAllowed() || isNonOverwritableForced()) { 234 throw new ParameterException("Can only specify option " + name + " once."); 235 } 236 237 validateParameter(name, value); 238 239 Class<?> type = m_parameterized.getType(); 240 241 Object convertedValue = m_jCommander.convertValue(this, value); 242 validateValueParameter(name, convertedValue); 243 boolean isCollection = Collection.class.isAssignableFrom(type); 244 245 if (isCollection) { 246 @SuppressWarnings("unchecked") 247 Collection<Object> l = (Collection<Object>) m_parameterized.get(m_object); 248 if (l == null || fieldIsSetForTheFirstTime(isDefault)) { 249 l = newCollection(type); 250 m_parameterized.set(m_object, l); 251 } 252 if (convertedValue instanceof Collection) { 253 l.addAll((Collection) convertedValue); 254 } else { // if (isMainParameter || m_parameterAnnotation.arity() > 1) { 255 l.add(convertedValue); 256 // } else { 257 // l. 258 } 259 } else { 260 m_wrappedParameter.addValue(m_parameterized, m_object, convertedValue); 261 } 262 if (! isDefault) m_assigned = true; 263 } 264 validateParameter(String name, String value)265 private void validateParameter(String name, String value) { 266 Class<? extends IParameterValidator> validator = m_wrappedParameter.validateWith(); 267 if (validator != null) { 268 validateParameter(this, validator, name, value); 269 } 270 } 271 validateValueParameter(String name, Object value)272 private void validateValueParameter(String name, Object value) { 273 Class<? extends IValueValidator> validator = m_wrappedParameter.validateValueWith(); 274 if (validator != null) { 275 validateValueParameter(validator, name, value); 276 } 277 } 278 validateValueParameter(Class<? extends IValueValidator> validator, String name, Object value)279 public static void validateValueParameter(Class<? extends IValueValidator> validator, 280 String name, Object value) { 281 try { 282 if (validator != NoValueValidator.class) { 283 p("Validating value parameter:" + name + " value:" + value + " validator:" + validator); 284 } 285 validator.newInstance().validate(name, value); 286 } catch (InstantiationException e) { 287 throw new ParameterException("Can't instantiate validator:" + e); 288 } catch (IllegalAccessException e) { 289 throw new ParameterException("Can't instantiate validator:" + e); 290 } 291 } 292 validateParameter(ParameterDescription pd, Class<? extends IParameterValidator> validator, String name, String value)293 public static void validateParameter(ParameterDescription pd, 294 Class<? extends IParameterValidator> validator, 295 String name, String value) { 296 try { 297 if (validator != NoValidator.class) { 298 p("Validating parameter:" + name + " value:" + value + " validator:" + validator); 299 } 300 validator.newInstance().validate(name, value); 301 if (IParameterValidator2.class.isAssignableFrom(validator)) { 302 IParameterValidator2 instance = (IParameterValidator2) validator.newInstance(); 303 instance.validate(name, value, pd); 304 } 305 } catch (InstantiationException e) { 306 throw new ParameterException("Can't instantiate validator:" + e); 307 } catch (IllegalAccessException e) { 308 throw new ParameterException("Can't instantiate validator:" + e); 309 } catch(ParameterException ex) { 310 throw ex; 311 } catch(Exception ex) { 312 throw new ParameterException(ex); 313 } 314 } 315 316 /* 317 * Creates a new collection for the field's type. 318 * 319 * Currently only List and Set are supported. Support for 320 * Queues and Stacks could be useful. 321 */ 322 @SuppressWarnings("unchecked") newCollection(Class<?> type)323 private Collection<Object> newCollection(Class<?> type) { 324 if (SortedSet.class.isAssignableFrom(type)) return new TreeSet(); 325 else if (LinkedHashSet.class.isAssignableFrom(type)) return new LinkedHashSet(); 326 else if (Set.class.isAssignableFrom(type)) return new HashSet(); 327 else if (List.class.isAssignableFrom(type)) return new ArrayList(); 328 else { 329 throw new ParameterException("Parameters of Collection type '" + type.getSimpleName() 330 + "' are not supported. Please use List or Set instead."); 331 } 332 } 333 334 /* 335 * Tests if its the first time a non-default value is 336 * being added to the field. 337 */ fieldIsSetForTheFirstTime(boolean isDefault)338 private boolean fieldIsSetForTheFirstTime(boolean isDefault) { 339 return (!isDefault && !m_assigned); 340 } 341 p(String string)342 private static void p(String string) { 343 if (System.getProperty(JCommander.DEBUG_PROPERTY) != null) { 344 JCommander.getConsole().println("[ParameterDescription] " + string); 345 } 346 } 347 348 @Override toString()349 public String toString() { 350 return "[ParameterDescription " + m_parameterized.getName() + "]"; 351 } 352 isDynamicParameter()353 public boolean isDynamicParameter() { 354 return m_dynamicParameterAnnotation != null; 355 } 356 isHelp()357 public boolean isHelp() { 358 return m_wrappedParameter.isHelp(); 359 } 360 isNonOverwritableForced()361 public boolean isNonOverwritableForced() { 362 return m_wrappedParameter.isNonOverwritableForced(); 363 } 364 } 365