• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.lang.annotation.Annotation;
25 import java.lang.reflect.Field;
26 import java.util.*;
27 import java.util.ResourceBundle;
28 
29 public class ParameterDescription {
30   private Object object;
31 
32   private WrappedParameter wrappedParameter;
33   private Parameter parameterAnnotation;
34   private DynamicParameter dynamicParameterAnnotation;
35 
36   /** The field/method */
37   private Parameterized parameterized;
38   /** Keep track of whether a value was added to flag an error */
39   private boolean assigned = false;
40   private ResourceBundle bundle;
41   private String description;
42   private JCommander jCommander;
43   private Object defaultObject;
44   /** Longest of the names(), used to present usage() alphabetically */
45   private String longestName = "";
46 
ParameterDescription(Object object, DynamicParameter annotation, Parameterized parameterized, ResourceBundle bundle, JCommander jc)47   public ParameterDescription(Object object, DynamicParameter annotation,
48       Parameterized parameterized,
49       ResourceBundle bundle, JCommander jc) {
50     if (! Map.class.isAssignableFrom(parameterized.getType())) {
51       throw new ParameterException("@DynamicParameter " + parameterized.getName()
52           + " should be of type "
53           + "Map but is " + parameterized.getType().getName());
54     }
55 
56     dynamicParameterAnnotation = annotation;
57     wrappedParameter = new WrappedParameter(dynamicParameterAnnotation);
58     init(object, parameterized, bundle, jc);
59   }
60 
ParameterDescription(Object object, Parameter annotation, Parameterized parameterized, ResourceBundle bundle, JCommander jc)61   public ParameterDescription(Object object, Parameter annotation, Parameterized parameterized,
62       ResourceBundle bundle, JCommander jc) {
63     parameterAnnotation = annotation;
64     wrappedParameter = new WrappedParameter(parameterAnnotation);
65     init(object, parameterized, bundle, jc);
66   }
67 
68   /**
69    * Find the resource bundle in the annotations.
70    * @return
71    */
72   @SuppressWarnings("deprecation")
findResourceBundle(Object o)73   private ResourceBundle findResourceBundle(Object o) {
74     ResourceBundle result = null;
75 
76     Parameters p = o.getClass().getAnnotation(Parameters.class);
77     if (p != null && ! isEmpty(p.resourceBundle())) {
78       result = ResourceBundle.getBundle(p.resourceBundle(), Locale.getDefault());
79     } else {
80       com.beust.jcommander.ResourceBundle a = o.getClass().getAnnotation(
81           com.beust.jcommander.ResourceBundle.class);
82       if (a != null && ! isEmpty(a.value())) {
83         result = ResourceBundle.getBundle(a.value(), Locale.getDefault());
84       }
85     }
86 
87     return result;
88   }
89 
isEmpty(String s)90   private boolean isEmpty(String s) {
91     return s == null || "".equals(s);
92   }
93 
initDescription(String description, String descriptionKey, String[] names)94   private void initDescription(String description, String descriptionKey, String[] names) {
95     this.description = description;
96     if (! "".equals(descriptionKey)) {
97       if (bundle != null) {
98         this.description = bundle.getString(descriptionKey);
99       }
100     }
101 
102     for (String name : names) {
103       if (name.length() > longestName.length()) longestName = name;
104     }
105   }
106 
107   @SuppressWarnings("unchecked")
init(Object object, Parameterized parameterized, ResourceBundle bundle, JCommander jCommander)108   private void init(Object object, Parameterized parameterized, ResourceBundle bundle,
109       JCommander jCommander) {
110     this.object = object;
111     this.parameterized = parameterized;
112     this.bundle = bundle;
113     if (this.bundle == null) {
114       this.bundle = findResourceBundle(object);
115     }
116     this.jCommander = jCommander;
117 
118     if (parameterAnnotation != null) {
119       String description;
120       if (Enum.class.isAssignableFrom(parameterized.getType())
121           && parameterAnnotation.description().isEmpty()) {
122         description = "Options: " + EnumSet.allOf((Class<? extends Enum>) parameterized.getType());
123       }else {
124         description = parameterAnnotation.description();
125       }
126       initDescription(description, parameterAnnotation.descriptionKey(),
127           parameterAnnotation.names());
128     } else if (dynamicParameterAnnotation != null) {
129       initDescription(dynamicParameterAnnotation.description(),
130           dynamicParameterAnnotation.descriptionKey(),
131           dynamicParameterAnnotation.names());
132     } else {
133       throw new AssertionError("Shound never happen");
134     }
135 
136     try {
137       defaultObject = parameterized.get(object);
138     } catch (Exception e) {
139     }
140 
141     //
142     // Validate default values, if any and if applicable
143     //
144     if (defaultObject != null) {
145       if (parameterAnnotation != null) {
146         validateDefaultValues(parameterAnnotation.names());
147       }
148     }
149   }
150 
validateDefaultValues(String[] names)151   private void validateDefaultValues(String[] names) {
152     String name = names.length > 0 ? names[0] : "";
153     validateValueParameter(name, defaultObject);
154   }
155 
getLongestName()156   public String getLongestName() {
157     return longestName;
158   }
159 
getDefault()160   public Object getDefault() {
161    return defaultObject;
162   }
163 
getDescription()164   public String getDescription() {
165     return description;
166   }
167 
getObject()168   public Object getObject() {
169     return object;
170   }
171 
getNames()172   public String getNames() {
173     StringBuilder sb = new StringBuilder();
174     String[] names = wrappedParameter.names();
175     for (int i = 0; i < names.length; i++) {
176       if (i > 0) sb.append(", ");
177       sb.append(names[i]);
178     }
179     return sb.toString();
180   }
181 
getParameter()182   public WrappedParameter getParameter() {
183     return wrappedParameter;
184   }
185 
getParameterized()186   public Parameterized getParameterized() {
187     return parameterized;
188   }
189 
isMultiOption()190   private boolean isMultiOption() {
191     Class<?> fieldType = parameterized.getType();
192     return fieldType.equals(List.class) || fieldType.equals(Set.class)
193         || parameterized.isDynamicParameter();
194   }
195 
addValue(String value)196   public void addValue(String value) {
197     addValue(value, false /* not default */);
198   }
199 
200   /**
201    * @return true if this parameter received a value during the parsing phase.
202    */
isAssigned()203   public boolean isAssigned() {
204     return assigned;
205   }
206 
207 
setAssigned(boolean b)208   public void setAssigned(boolean b) {
209     assigned = b;
210   }
211 
212   /**
213    * Add the specified value to the field. First, validate the value if a
214    * validator was specified. Then look up any field converter, then any type
215    * converter, and if we can't find any, throw an exception.
216    */
addValue(String value, boolean isDefault)217   public void addValue(String value, boolean isDefault) {
218     addValue(null, value, isDefault, true, -1);
219   }
220 
addValue(String name, String value, boolean isDefault, boolean validate, int currentIndex)221   Object addValue(String name, String value, boolean isDefault, boolean validate, int currentIndex) {
222     p("Adding " + (isDefault ? "default " : "") + "value:" + value
223         + " to parameter:" + parameterized.getName());
224     if(name == null) {
225       name = wrappedParameter.names()[0];
226     }
227     if (currentIndex == 00 && assigned && ! isMultiOption() && !jCommander.isParameterOverwritingAllowed()
228             || isNonOverwritableForced()) {
229       throw new ParameterException("Can only specify option " + name + " once.");
230     }
231 
232     if (validate) {
233       validateParameter(name, value);
234     }
235 
236     Class<?> type = parameterized.getType();
237 
238     Object convertedValue = jCommander.convertValue(getParameterized(), getParameterized().getType(), name, value);
239     if (validate) {
240       validateValueParameter(name, convertedValue);
241     }
242     boolean isCollection = Collection.class.isAssignableFrom(type);
243 
244     Object finalValue;
245     if (isCollection) {
246       @SuppressWarnings("unchecked")
247       Collection<Object> l = (Collection<Object>) parameterized.get(object);
248       if (l == null || fieldIsSetForTheFirstTime(isDefault)) {
249           l = newCollection(type);
250           parameterized.set(object, l);
251       }
252       if (convertedValue instanceof Collection) {
253           l.addAll((Collection) convertedValue);
254       } else {
255           l.add(convertedValue);
256       }
257       finalValue = l;
258     } else {
259       // If the field type is not a collection, see if it's a type that contains @SubParameters annotations
260       List<SubParameterIndex> subParameters = findSubParameters(type);
261       if (! subParameters.isEmpty()) {
262         // @SubParameters found
263         finalValue = handleSubParameters(value, currentIndex, type, subParameters);
264       } else {
265         // No, regular parameter
266         wrappedParameter.addValue(parameterized, object, convertedValue);
267         finalValue = convertedValue;
268       }
269     }
270     if (! isDefault) assigned = true;
271 
272     return finalValue;
273   }
274 
handleSubParameters(String value, int currentIndex, Class<?> type, List<SubParameterIndex> subParameters)275   private Object handleSubParameters(String value, int currentIndex, Class<?> type,
276       List<SubParameterIndex> subParameters) {
277     Object finalValue;// Yes, assign each following argument to the corresponding field of that object
278     SubParameterIndex sai = null;
279     for (SubParameterIndex si: subParameters) {
280       if (si.order == currentIndex) {
281         sai = si;
282         break;
283       }
284     }
285     if (sai != null) {
286       Object objectValue = parameterized.get(object);
287       try {
288         if (objectValue == null) {
289           objectValue = type.newInstance();
290           parameterized.set(object, objectValue);
291         }
292         wrappedParameter.addValue(parameterized, objectValue, value, sai.field);
293         finalValue = objectValue;
294       } catch (InstantiationException | IllegalAccessException e) {
295         throw new ParameterException("Couldn't instantiate " + type, e);
296       }
297     } else {
298       throw new ParameterException("Couldn't find where to assign parameter " + value + " in " + type);
299     }
300     return finalValue;
301   }
302 
getParameterAnnotation()303   public Parameter getParameterAnnotation() {
304     return parameterAnnotation;
305   }
306 
307   class SubParameterIndex {
308     int order = -1;
309     Field field;
310 
SubParameterIndex(int order, Field field)311     public SubParameterIndex(int order, Field field) {
312       this.order = order;
313       this.field = field;
314     }
315   }
316 
findSubParameters(Class<?> type)317   private List<SubParameterIndex> findSubParameters(Class<?> type) {
318     List<SubParameterIndex> result = new ArrayList<>();
319     for (Field field: type.getDeclaredFields()) {
320       Annotation subParameter = field.getAnnotation(SubParameter.class);
321       if (subParameter != null) {
322         SubParameter sa = (SubParameter) subParameter;
323         result.add(new SubParameterIndex(sa.order(), field));
324       }
325     }
326     return result;
327   }
328 
validateParameter(String name, String value)329   private void validateParameter(String name, String value) {
330     final Class<? extends IParameterValidator> validators[] = wrappedParameter.validateWith();
331     if (validators != null && validators.length > 0) {
332         for(final Class<? extends IParameterValidator> validator: validators) {
333           validateParameter(this, validator, name, value);
334         }
335     }
336   }
337 
validateValueParameter(String name, Object value)338   void validateValueParameter(String name, Object value) {
339     final Class<? extends IValueValidator> validators[] = wrappedParameter.validateValueWith();
340     if (validators != null && validators.length > 0) {
341       for(final Class<? extends IValueValidator> validator: validators) {
342         validateValueParameter(validator, name, value);
343       }
344     }
345   }
346 
validateValueParameter(Class<? extends IValueValidator> validator, String name, Object value)347   public static void validateValueParameter(Class<? extends IValueValidator> validator,
348       String name, Object value) {
349     try {
350       if (validator != NoValueValidator.class) {
351         p("Validating value parameter:" + name + " value:" + value + " validator:" + validator);
352       }
353       validator.newInstance().validate(name, value);
354     } catch (InstantiationException | IllegalAccessException e) {
355       throw new ParameterException("Can't instantiate validator:" + e);
356     }
357   }
358 
validateParameter(ParameterDescription pd, Class<? extends IParameterValidator> validator, String name, String value)359   public static void validateParameter(ParameterDescription pd,
360       Class<? extends IParameterValidator> validator,
361       String name, String value) {
362     try {
363 
364       if (validator != NoValidator.class) {
365         p("Validating parameter:" + name + " value:" + value + " validator:" + validator);
366       }
367       validator.newInstance().validate(name, value);
368       if (IParameterValidator2.class.isAssignableFrom(validator)) {
369         IParameterValidator2 instance = (IParameterValidator2) validator.newInstance();
370         instance.validate(name, value, pd);
371       }
372     } catch (InstantiationException | IllegalAccessException e) {
373       throw new ParameterException("Can't instantiate validator:" + e);
374     } catch(ParameterException ex) {
375       throw ex;
376     } catch(Exception ex) {
377       throw new ParameterException(ex);
378     }
379   }
380 
381   /*
382    * Creates a new collection for the field's type.
383    *
384    * Currently only List and Set are supported. Support for
385    * Queues and Stacks could be useful.
386    */
387   @SuppressWarnings("unchecked")
newCollection(Class<?> type)388   private Collection<Object> newCollection(Class<?> type) {
389     if (SortedSet.class.isAssignableFrom(type)) return new TreeSet();
390     else if (LinkedHashSet.class.isAssignableFrom(type)) return new LinkedHashSet();
391     else if (Set.class.isAssignableFrom(type)) return new HashSet();
392     else if (List.class.isAssignableFrom(type)) return new ArrayList();
393     else {
394       throw new ParameterException("Parameters of Collection type '" + type.getSimpleName()
395                                   + "' are not supported. Please use List or Set instead.");
396     }
397   }
398 
399   /*
400    * Tests if its the first time a non-default value is
401    * being added to the field.
402    */
fieldIsSetForTheFirstTime(boolean isDefault)403   private boolean fieldIsSetForTheFirstTime(boolean isDefault) {
404     return (!isDefault && !assigned);
405   }
406 
p(String string)407   private static void p(String string) {
408     if (System.getProperty(JCommander.DEBUG_PROPERTY) != null) {
409       JCommander.getConsole().println("[ParameterDescription] " + string);
410     }
411   }
412 
413   @Override
toString()414   public String toString() {
415     return "[ParameterDescription " + parameterized.getName() + "]";
416   }
417 
isDynamicParameter()418   public boolean isDynamicParameter() {
419     return dynamicParameterAnnotation != null;
420   }
421 
isHelp()422   public boolean isHelp() {
423     return wrappedParameter.isHelp();
424   }
425 
isNonOverwritableForced()426   public boolean isNonOverwritableForced() {
427     return wrappedParameter.isNonOverwritableForced();
428   }
429 }
430