org.metafacture.commons.reflection.ConfigurableClass Maven / Gradle / Ivy
/*
* Copyright 2016 Christoph Böhme
*
* 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.
*/
package org.metafacture.commons.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Provides method for creating and initialising classes. The
* {@link #newInstance(Map, Object...)} method creates an instance of the class
* using the first constructor that matches the varargs argument of the methods.
*
* Instances of this class wrap {@link Class}. The wrapped instance is available
* via {@link #getPlainClass()}.
*
* @param object type
* @author Christoph Böhme
*/
public final class ConfigurableClass {
private static final String SETTER_PREFIX = "set";
private static final Set> ELIGIBLE_TYPES = new HashSet<>(
Arrays.asList(boolean.class, int.class, String.class));
private final Class plainClass;
private Map settersCache;
/**
*
* Creates an instance of {@link ConfigurableClass} defined by a Class.
*
* @param plainClass the plain class of object type T
*/
public ConfigurableClass(final Class plainClass) {
this.plainClass = plainClass;
}
/**
* Gets the plain class of the ConfigurableClass.
*
* @return the Class
*/
public Class getPlainClass() {
return plainClass;
}
/**
* Gets all public "set" methods of this class.
*
* @return the Map of the setter methods of this class
*/
public Map getSetters() {
if (settersCache == null) {
initSettersCache();
}
return settersCache;
}
private void initSettersCache() {
settersCache = new HashMap<>();
for (final Method method : plainClass.getMethods()) {
if (isSetter(method)) {
final String setterName = method.getName().substring(
SETTER_PREFIX.length()).toLowerCase();
settersCache.put(setterName, method);
}
}
}
private boolean isSetter(final Method method) {
if (method.getParameterTypes().length == 1) {
final Class> type = method.getParameterTypes()[0];
if (ELIGIBLE_TYPES.contains(type) || type.isEnum()) {
return method.getName().startsWith(SETTER_PREFIX);
}
}
return false;
}
/**
* Gets the parameter type of the setter method.
*
* @param method the setter method
* @return the type
*/
public Class> getSetterType(final Method method) {
return method.getParameterTypes()[0];
}
/**
* Gets the parameter types of the setter methods.
*
* @return a Map of the setter method names and their types
*/
public Map> getSetterTypes() {
final Map> setterTypes = new HashMap<>();
for (final Map.Entry entry : getSetters().entrySet()) {
setterTypes.put(entry.getKey(), getSetterType(entry.getValue()));
}
return setterTypes;
}
/**
* Creates an empty instance of the class.
*
* @return a new instance
*/
public T newInstance() {
return newInstance(Collections.emptyMap());
}
/**
* Creates an instance of the class using the first constructor that matches the
* varargs argument of the methods.
*
* @param setterValues the Map of setter values
* @param constructorArgs the Object of args of the constructor
* @return the new instance
*/
public T newInstance(final Map setterValues, final Object... constructorArgs) {
try {
final Constructor constructor = findConstructor(constructorArgs);
final T instance = constructor.newInstance(constructorArgs);
applySetters(instance, setterValues);
return instance;
}
catch (final ReflectiveOperationException e) {
throw new ReflectionException("class could not be instantiated: " +
plainClass, e);
}
}
private Constructor findConstructor(final Object... arguments) throws NoSuchMethodException {
@SuppressWarnings("unchecked") // getConstructors() returns correct types
final Constructor[] constructors =
(Constructor[]) plainClass.getConstructors();
for (final Constructor constructor : constructors) {
if (checkArgumentTypes(constructor, arguments)) {
return constructor;
}
}
throw new NoSuchMethodException(
"no appropriate constructor found for class " + plainClass);
}
private boolean checkArgumentTypes(final Constructor> constructor, final Object[] constructorArgs) { // checkstyle-disable-line ReturnCount
final Class>[] argTypes = constructor.getParameterTypes();
if (argTypes.length != constructorArgs.length) {
return false;
}
for (int i = 0; i < argTypes.length; ++i) {
if (!argTypes[i].isAssignableFrom(constructorArgs[i].getClass())) {
return false;
}
}
return true;
}
private void applySetters(final T target, final Map setterValues) {
for (final Map.Entry setterValue : setterValues.entrySet()) {
final String setterName = setterValue.getKey().toLowerCase();
final Method setter = getSetters().get(setterName);
if (setter == null) {
throw new ReflectionException("Method " + target.getClass()
.getSimpleName() + "." + setterName + " does not exist");
}
final Class> valueType = setter.getParameterTypes()[0];
final Object value = convertValue(setterValue.getValue(), valueType);
try {
setter.invoke(target, value);
}
catch (final ReflectiveOperationException e) {
throw new ReflectionException("Cannot set " + setterName +
" on class " + target.getClass().getSimpleName(), e);
}
}
}
private > Object convertValue(final String value, final Class> type) {
final Object result;
if (type == boolean.class) {
result = Boolean.valueOf(value);
}
else if (type == int.class) {
result = Integer.valueOf(value);
}
else if (type.isEnum()) {
@SuppressWarnings("unchecked") // protected by type.isEnum() check
final Class enumType = (Class) type;
result = Enum.valueOf(enumType, value.toUpperCase());
}
else {
result = value;
}
return result;
}
}