blob: 32640083dfa6817ebeb00b4e4c08e60d1c0f4a0d [file] [log] [blame]
package com.beust.jcommander;
import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Sets;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Encapsulate a field or a method annotated with @Parameter or @DynamicParameter
*/
public class Parameterized {
// Either a method or a field
private Field field;
private Method method;
private Method getter;
// Either of these two
private WrappedParameter wrappedParameter;
private ParametersDelegate parametersDelegate;
public Parameterized(WrappedParameter wp, ParametersDelegate pd,
Field field, Method method) {
wrappedParameter = wp;
this.method = method;
this.field = field;
if (this.field != null) {
setFieldAccessible(this.field);
}
parametersDelegate = pd;
}
/**
* Recursive handler for describing the set of classes while
* using the setOfClasses parameter as a collector
*
* @param inputClass the class to analyze
* @param setOfClasses the set collector to collect the results
*/
private static void describeClassTree(Class<?> inputClass, Set<Class<?>> setOfClasses) {
// can't map null class
if(inputClass == null) {
return;
}
// don't further analyze a class that has been analyzed already
if(Object.class.equals(inputClass) || setOfClasses.contains(inputClass)) {
return;
}
// add to analysis set
setOfClasses.add(inputClass);
// perform super class analysis
describeClassTree(inputClass.getSuperclass(), setOfClasses);
// perform analysis on interfaces
for(Class<?> hasInterface : inputClass.getInterfaces()) {
describeClassTree(hasInterface, setOfClasses);
}
}
/**
* Given an object return the set of classes that it extends
* or implements.
*
* @param arg object to describe
* @return set of classes that are implemented or extended by that object
*/
private static Set<Class<?>> describeClassTree(Class<?> inputClass) {
if(inputClass == null) {
return Collections.emptySet();
}
// create result collector
Set<Class<?>> classes = Sets.newLinkedHashSet();
// describe tree
describeClassTree(inputClass, classes);
return classes;
}
public static List<Parameterized> parseArg(Object arg) {
List<Parameterized> result = Lists.newArrayList();
Class<?> rootClass = arg.getClass();
// get the list of types that are extended or implemented by the root class
// and all of its parent types
Set<Class<?>> types = describeClassTree(rootClass);
// analyze each type
for(Class<?> cls : types) {
// check fields
for (Field f : cls.getDeclaredFields()) {
Annotation annotation = f.getAnnotation(Parameter.class);
Annotation delegateAnnotation = f.getAnnotation(ParametersDelegate.class);
Annotation dynamicParameter = f.getAnnotation(DynamicParameter.class);
if (annotation != null) {
result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null,
f, null));
} else if (dynamicParameter != null) {
result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null,
f, null));
} else if (delegateAnnotation != null) {
result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation,
f, null));
}
}
// check methods
for (Method m : cls.getDeclaredMethods()) {
m.setAccessible(true);
Annotation annotation = m.getAnnotation(Parameter.class);
Annotation delegateAnnotation = m.getAnnotation(ParametersDelegate.class);
Annotation dynamicParameter = m.getAnnotation(DynamicParameter.class);
if (annotation != null) {
result.add(new Parameterized(new WrappedParameter((Parameter) annotation), null,
null, m));
} else if (dynamicParameter != null) {
result.add(new Parameterized(new WrappedParameter((DynamicParameter) dynamicParameter), null,
null, m));
} else if (delegateAnnotation != null) {
result.add(new Parameterized(null, (ParametersDelegate) delegateAnnotation,
null, m));
}
}
}
return result;
}
public WrappedParameter getWrappedParameter() {
return wrappedParameter;
}
public Class<?> getType() {
if (method != null) {
return method.getParameterTypes()[0];
} else {
return field.getType();
}
}
public String getName() {
if (method != null) {
return method.getName();
} else {
return field.getName();
}
}
public Object get(Object object) {
try {
if (method != null) {
if (getter == null) {
getter = method.getDeclaringClass()
.getMethod("g" + method.getName().substring(1));
}
return getter.invoke(object);
} else {
return field.get(object);
}
} catch (SecurityException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
throw new ParameterException(e);
} catch (NoSuchMethodException e) {
// Try to find a field
String name = method.getName();
String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
Object result = null;
try {
Field field = method.getDeclaringClass().getDeclaredField(fieldName);
if (field != null) {
setFieldAccessible(field);
result = field.get(object);
}
} catch(NoSuchFieldException | IllegalAccessException ex) {
// ignore
}
return result;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((field == null) ? 0 : field.hashCode());
result = prime * result + ((method == null) ? 0 : method.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Parameterized other = (Parameterized) obj;
if (field == null) {
if (other.field != null)
return false;
} else if (!field.equals(other.field))
return false;
if (method == null) {
if (other.method != null)
return false;
} else if (!method.equals(other.method))
return false;
return true;
}
public boolean isDynamicParameter(Field field) {
if (method != null) {
return method.getAnnotation(DynamicParameter.class) != null;
} else {
return this.field.getAnnotation(DynamicParameter.class) != null;
}
}
private static void setFieldAccessible(Field f) {
if (Modifier.isFinal(f.getModifiers())) {
throw new ParameterException(
"Cannot use final field " + f.getDeclaringClass().getName() + "#" + f.getName() + " as a parameter;"
+ " compile-time constant inlining may hide new values written to it.");
}
f.setAccessible(true);
}
private static String errorMessage(Method m, Exception ex) {
return "Could not invoke " + m + "\n Reason: " + ex.getMessage();
}
public void set(Object object, Object value) {
try {
if (method != null) {
method.invoke(object, value);
} else {
field.set(object, value);
}
} catch (IllegalAccessException | IllegalArgumentException ex) {
throw new ParameterException(errorMessage(method, ex));
} catch (InvocationTargetException ex) {
// If a ParameterException was thrown, don't wrap it into another one
if (ex.getTargetException() instanceof ParameterException) {
throw (ParameterException) ex.getTargetException();
} else {
throw new ParameterException(errorMessage(method, ex));
}
}
}
public ParametersDelegate getDelegateAnnotation() {
return parametersDelegate;
}
public Type getGenericType() {
if (method != null) {
return method.getGenericParameterTypes()[0];
} else {
return field.getGenericType();
}
}
public Parameter getParameter() {
return wrappedParameter.getParameter();
}
/**
* @return the generic type of the collection for this field, or null if not applicable.
*/
public Type findFieldGenericType() {
if (method != null) {
return null;
} else {
if (field.getGenericType() instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) field.getGenericType();
Type cls = p.getActualTypeArguments()[0];
if (cls instanceof Class) {
return cls;
}
}
}
return null;
}
public boolean isDynamicParameter() {
return wrappedParameter.getDynamicParameter() != null;
}
}