/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.wisdom.content.converters;
import java.lang.reflect.*;
import java.util.*;
/**
* Some utilities functions to extract type and generic metadata.
*/
public class ReflectionHelper {
private ReflectionHelper() {
// Avoid direct instantiation.
}
/**
* Gets the map of String to {@link org.wisdom.content.converters.ReflectionHelper.Property}. These properties
* are extracted from the given class. Properties are identified using the `setX` methods and fields. The map
* entry are the property name.
*
* @param clazz the class
* @param genericType the class with generic parameter if any
* @return the map containing the extracted properties
*/
public static Map<String, Property> getProperties(Class clazz, Type genericType) {
Map<String, Property> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
// Start by methods - public only, including overridden
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
// it's a setter.
String name = method.getName().substring("set".length());
Property property = map.get(name);
if (property == null) {
property = new Property();
map.put(name, property);
}
property.setter(method);
}
}
// All fields, but will not do anything for existing properties.
for (Field field : getAllFields(clazz)) {
String name = field.getName();
Property property = map.get(name);
if (property == null) {
property = new Property();
property.field(field);
map.put(name, property);
}
// Else the property has already a setter, the setter has to be used.
}
return map;
}
/**
* Property structure
*
* @see #getProperties(Class, Type)
*/
public static class Property {
private Field field;
private Class classOfProperty;
private Type genericOfProperty;
private Method setter;
private Property() {
// Avoid external instantiation.
}
/**
* Sets the current property.
*
* @param target the object containing the property
* @param value the value
* @throws InvocationTargetException if the property cannot be set because the method has thrown an exception
* @throws IllegalAccessException if the property cannot be set because the property is not accessible
*/
public void set(Object target, Object value) throws InvocationTargetException, IllegalAccessException {
if (setter != null) {
setter.invoke(target, value);
} else {
if (!field.isAccessible()) {
field.setAccessible(true);
}
field.set(target, value);
}
}
/**
* The class of the property.
*
* @return the class
*/
public Class<?> getClassOfProperty() {
return classOfProperty;
}
/**
* The generic type of the property.
*
* @return the type
*/
public Type getGenericTypeOfProperty() {
return genericOfProperty;
}
/**
* Sets this property to use the given setter method.
*
* @param setter the method object for the setter.
*/
public void setter(Method setter) {
this.setter = setter;
this.classOfProperty = setter.getParameterTypes()[0];
this.genericOfProperty = setter.getGenericParameterTypes()[0];
}
/**
* Sets this property to use a given field
*
* @param field the field
*/
public void field(Field field) {
this.field = field;
this.classOfProperty = field.getType();
this.genericOfProperty = field.getGenericType();
}
}
/**
* Get the list of class-type pairs that represent the type arguments of a
* {@link ParameterizedType parameterized} input type.
* <p>
* For any given {@link ClassTypePair#rawClass() class} part of each pair
* in the returned list, following rules apply:
* <ul>
* <li>If a type argument is a class then the class is returned as raw class.</li>
* <li>If the type argument is a generic array type and the generic component
* type is a class then class of the array is returned as raw class.</li>
* <li>If the type argument is a parameterized type and it's raw type is a
* class then that class is returned as raw class.</li>
* </ul>
* If the {@code type} is not an instance of ParameterizedType an empty
* list is returned.
*
* @param type parameterized type.
* @return the list of class-type pairs representing the actual type arguments.
* May be empty, but may never be {@code null}.
* @throws IllegalArgumentException if any of the generic type arguments is
* not a class, or a generic array type, or the generic component type
* of the generic array type is not class, or not a parameterized type
* with a raw type that is not a class.
*/
public static List<ClassTypePair> getTypeArgumentAndClass(final Type type) throws IllegalArgumentException {
final Type[] types = getTypeArguments(type);
if (types == null) {
return Collections.emptyList();
}
List<ClassTypePair> list = new ArrayList<>();
for (Type t : types) {
list.add(new ClassTypePair(erasure(t), t));
}
return list;
}
/**
* Get the {@link Class} representation of the given type.
* <p>
* This corresponds to the notion of the erasure in JSR-14.
*
* @param type type to provide the erasure for.
* @return the given type's erasure.
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> erasure(Type type) {
return EraserVisitor.ERASER.visit(type);
}
/**
* Get the type arguments for a parameterized type.
* <p>
* In case the type is not a {@link ParameterizedType parameterized type},
* the method returns {@code null}.
*
* @param type parameterized type.
* @return type arguments for a parameterized type, or {@code null} in case the input type is
* not a parameterized type.
*/
public static Type[] getTypeArguments(Type type) {
if (!(type instanceof ParameterizedType)) {
return null;
}
return ((ParameterizedType) type).getActualTypeArguments();
}
/**
* Gets the default value as String for the primitive types.
*
* @param type the primitive type
* @return the default value as String
*/
public static String getPrimitiveDefault(Class type) {
if (type == Boolean.class) {
return "false";
}
if (type == Character.class) {
return Character.toString((char) 0);
}
return "0";
}
/**
* Gets all fields of the given class and its parents (if any).
*
* @param cls the {@link Class} to query
* @return an array of Fields (possibly empty).
*/
public static List<Field> getAllFields(Class<?> cls) {
final List<Field> allFields = new ArrayList<Field>();
Class<?> currentClass = cls;
while (currentClass != null) {
final Field[] declaredFields = currentClass.getDeclaredFields();
Collections.addAll(allFields, declaredFields);
currentClass = currentClass.getSuperclass();
}
return allFields;
}
}