• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.beust.jcommander;
2 
3 import com.beust.jcommander.internal.Lists;
4 import com.beust.jcommander.internal.Sets;
5 
6 import java.lang.annotation.Annotation;
7 import java.lang.reflect.Field;
8 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method;
10 import java.lang.reflect.Modifier;
11 import java.lang.reflect.ParameterizedType;
12 import java.lang.reflect.Type;
13 import java.util.Collections;
14 import java.util.List;
15 import java.util.Set;
16 
17 /**
18  * Encapsulate a field or a method annotated with @Parameter or @DynamicParameter
19  */
20 public class Parameterized {
21 
22   // Either a method or a field
23   private Field field;
24   private Method method;
25   private Method getter;
26 
27   // Either of these two
28   private WrappedParameter wrappedParameter;
29   private ParametersDelegate parametersDelegate;
30 
Parameterized(WrappedParameter wp, ParametersDelegate pd, Field field, Method method)31   public Parameterized(WrappedParameter wp, ParametersDelegate pd,
32       Field field, Method method) {
33     wrappedParameter = wp;
34     this.method = method;
35     this.field = field;
36     if (this.field != null) {
37       setFieldAccessible(this.field);
38     }
39     parametersDelegate = pd;
40   }
41 
42   /**
43    * Recursive handler for describing the set of classes while
44    * using the setOfClasses parameter as a collector
45    *
46    * @param inputClass the class to analyze
47    * @param setOfClasses the set collector to collect the results
48      */
describeClassTree(Class<?> inputClass, Set<Class<?>> setOfClasses)49   private static void describeClassTree(Class<?> inputClass, Set<Class<?>> setOfClasses) {
50     // can't map null class
51     if(inputClass == null) {
52       return;
53     }
54 
55     // don't further analyze a class that has been analyzed already
56     if(Object.class.equals(inputClass) || setOfClasses.contains(inputClass)) {
57       return;
58     }
59 
60     // add to analysis set
61     setOfClasses.add(inputClass);
62 
63     // perform super class analysis
64     describeClassTree(inputClass.getSuperclass(), setOfClasses);
65 
66     // perform analysis on interfaces
67     for(Class<?> hasInterface : inputClass.getInterfaces()) {
68       describeClassTree(hasInterface, setOfClasses);
69     }
70   }
71 
72   /**
73    * Given an object return the set of classes that it extends
74    * or implements.
75    *
76    * @param arg object to describe
77    * @return set of classes that are implemented or extended by that object
78    */
describeClassTree(Class<?> inputClass)79   private static Set<Class<?>> describeClassTree(Class<?> inputClass) {
80     if(inputClass == null) {
81       return Collections.emptySet();
82     }
83 
84     // create result collector
85     Set<Class<?>> classes = Sets.newLinkedHashSet();
86 
87     // describe tree
88     describeClassTree(inputClass, classes);
89 
90     return classes;
91   }
92 
parseArg(Object arg)93   public static List<Parameterized> parseArg(Object arg) {
94     List<Parameterized> result = Lists.newArrayList();
95 
96     Class<?> rootClass = arg.getClass();
97 
98     // get the list of types that are extended or implemented by the root class
99     // and all of its parent types
100     Set<Class<?>> types = describeClassTree(rootClass);
101 
102     // analyze each type
103     for(Class<?> cls : types) {
104 
105       // check fields
106       for (Field f : cls.getDeclaredFields()) {
107         Annotation annotation = f.getAnnotation(Parameter.class);
108         Annotation delegateAnnotation = f.getAnnotation(ParametersDelegate.class);
109         Annotation dynamicParameter = f.getAnnotation(DynamicParameter.class);
110         if (annotation != null) {
111           result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null,
112                   f, null));
113         } else if (dynamicParameter != null) {
114           result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null,
115                   f, null));
116         } else if (delegateAnnotation != null) {
117           result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation,
118                   f, null));
119         }
120       }
121 
122       // check methods
123       for (Method m : cls.getDeclaredMethods()) {
124         m.setAccessible(true);
125         Annotation annotation = m.getAnnotation(Parameter.class);
126         Annotation delegateAnnotation = m.getAnnotation(ParametersDelegate.class);
127         Annotation dynamicParameter = m.getAnnotation(DynamicParameter.class);
128         if (annotation != null) {
129           result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null,
130                   null, m));
131         } else if (dynamicParameter != null) {
132           result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null,
133                   null, m));
134         } else if (delegateAnnotation != null) {
135           result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation,
136                   null, m));
137         }
138       }
139     }
140 
141     return result;
142   }
143 
getWrappedParameter()144   public WrappedParameter getWrappedParameter() {
145     return wrappedParameter;
146   }
147 
getType()148   public Class<?> getType() {
149     if (method != null) {
150       return method.getParameterTypes()[0];
151     } else {
152       return field.getType();
153     }
154   }
155 
getName()156   public String getName() {
157     if (method != null) {
158       return method.getName();
159     } else {
160       return field.getName();
161     }
162   }
163 
get(Object object)164   public Object get(Object object) {
165     try {
166       if (method != null) {
167         if (getter == null) {
168             getter = method.getDeclaringClass()
169                 .getMethod("g" + method.getName().substring(1));
170         }
171         return getter.invoke(object);
172       } else {
173         return field.get(object);
174       }
175     } catch (SecurityException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
176       throw new ParameterException(e);
177     } catch (NoSuchMethodException e) {
178       // Try to find a field
179       String name = method.getName();
180       String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
181       Object result = null;
182       try {
183         Field field = method.getDeclaringClass().getDeclaredField(fieldName);
184         if (field != null) {
185           setFieldAccessible(field);
186           result = field.get(object);
187         }
188       } catch(NoSuchFieldException | IllegalAccessException ex) {
189         // ignore
190       }
191       return result;
192     }
193   }
194 
195   @Override
hashCode()196   public int hashCode() {
197     final int prime = 31;
198     int result = 1;
199     result = prime * result + ((field == null) ? 0 : field.hashCode());
200     result = prime * result + ((method == null) ? 0 : method.hashCode());
201     return result;
202   }
203 
204   @Override
equals(Object obj)205   public boolean equals(Object obj) {
206     if (this == obj)
207       return true;
208     if (obj == null)
209       return false;
210     if (getClass() != obj.getClass())
211       return false;
212     Parameterized other = (Parameterized) obj;
213     if (field == null) {
214       if (other.field != null)
215         return false;
216     } else if (!field.equals(other.field))
217       return false;
218     if (method == null) {
219       if (other.method != null)
220         return false;
221     } else if (!method.equals(other.method))
222       return false;
223     return true;
224   }
225 
isDynamicParameter(Field field)226   public boolean isDynamicParameter(Field field) {
227     if (method != null) {
228       return method.getAnnotation(DynamicParameter.class) != null;
229     } else {
230       return this.field.getAnnotation(DynamicParameter.class) != null;
231     }
232   }
233 
setFieldAccessible(Field f)234   private static void setFieldAccessible(Field f) {
235     if (Modifier.isFinal(f.getModifiers())) {
236       throw new ParameterException(
237         "Cannot use final field " + f.getDeclaringClass().getName() + "#" + f.getName() + " as a parameter;"
238         + " compile-time constant inlining may hide new values written to it.");
239     }
240     f.setAccessible(true);
241   }
242 
errorMessage(Method m, Exception ex)243   private static String errorMessage(Method m, Exception ex) {
244     return "Could not invoke " + m + "\n    Reason: " + ex.getMessage();
245   }
246 
set(Object object, Object value)247   public void set(Object object, Object value) {
248     try {
249       if (method != null) {
250         method.invoke(object, value);
251       } else {
252           field.set(object, value);
253       }
254     } catch (IllegalAccessException | IllegalArgumentException ex) {
255       throw new ParameterException(errorMessage(method, ex));
256     } catch (InvocationTargetException ex) {
257       // If a ParameterException was thrown, don't wrap it into another one
258       if (ex.getTargetException() instanceof ParameterException) {
259         throw (ParameterException) ex.getTargetException();
260       } else {
261         throw new ParameterException(errorMessage(method, ex));
262       }
263     }
264   }
265 
getDelegateAnnotation()266   public ParametersDelegate getDelegateAnnotation() {
267     return parametersDelegate;
268   }
269 
getGenericType()270   public Type getGenericType() {
271     if (method != null) {
272       return method.getGenericParameterTypes()[0];
273     } else {
274       return field.getGenericType();
275     }
276   }
277 
getParameter()278   public Parameter getParameter() {
279     return wrappedParameter.getParameter();
280   }
281 
282   /**
283    * @return the generic type of the collection for this field, or null if not applicable.
284    */
findFieldGenericType()285   public Type findFieldGenericType() {
286     if (method != null) {
287       return null;
288     } else {
289       if (field.getGenericType() instanceof ParameterizedType) {
290         ParameterizedType p = (ParameterizedType) field.getGenericType();
291         Type cls = p.getActualTypeArguments()[0];
292         if (cls instanceof Class) {
293           return cls;
294         }
295       }
296     }
297 
298     return null;
299   }
300 
isDynamicParameter()301   public boolean isDynamicParameter() {
302     return wrappedParameter.getDynamicParameter() != null;
303   }
304 
305 }
306