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