/*
* 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.culturegraph.mf.commons.reflection;
import static java.util.Arrays.asList;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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.
* <p>
* Instances of this class wrap {@link Class}. The wrapped instance is available
* via {@link #getPlainClass()}.
*
* @author Christoph Böhme
*/
public final class ConfigurableClass<T> {
private static final String SETTER_PREFIX = "set";
private static final Set<Class<?>> ELIGIBLE_TYPES = new HashSet<>(
asList(boolean.class, int.class, String.class));
private final Class<T> plainClass;
private Map<String, Method> settersCache;
public ConfigurableClass(Class<T> plainClass) {
this.plainClass = plainClass;
}
public Class<T> getPlainClass() {
return plainClass;
}
public Map<String, Method> getSetters() {
if (settersCache == null) {
initSettersCache();
}
return settersCache;
}
private void initSettersCache() {
settersCache = new HashMap<>();
for (Method method : plainClass.getMethods()) {
if (isSetter(method)) {
final String setterName = method.getName().substring(
SETTER_PREFIX.length()).toLowerCase();
settersCache.put(setterName, method);
}
}
}
private boolean isSetter(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;
}
public Map<String,Class<?>> getSetterTypes() {
final Map<String, Class<?>> setterTypes = new HashMap<>();
for(Map.Entry<String, Method> method : getSetters().entrySet()) {
final Class<?> setterType = method.getValue().getParameterTypes()[0];
setterTypes.put(method.getKey(), setterType);
}
return setterTypes;
}
public T newInstance() {
return newInstance(Collections.emptyMap());
}
public T newInstance(Map<String, String> setterValues,
Object... constructorArgs) {
try {
final Constructor<T> constructor = findConstructor(constructorArgs);
final T instance = constructor.newInstance(constructorArgs);
applySetters(instance, setterValues);
return instance;
} catch (ReflectiveOperationException e) {
throw new ReflectionException("class could not be instantiated: " +
plainClass, e);
}
}
private Constructor<T> findConstructor(Object... arguments)
throws NoSuchMethodException{
@SuppressWarnings("unchecked") // getConstructors() returns correct types
final Constructor<T>[] constructors =
(Constructor<T>[]) plainClass.getConstructors();
for (Constructor<T> constructor : constructors) {
if (checkArgumentTypes(constructor, arguments)) {
return constructor;
}
}
throw new NoSuchMethodException(
"no appropriate constructor found for class " + plainClass);
}
private boolean checkArgumentTypes(Constructor<?> constructor,
Object[] constructorArgs) {
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(T target, Map<String, String> setterValues) {
for (Map.Entry<String, String> 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 (ReflectiveOperationException e) {
throw new ReflectionException("Cannot set " + setterName +
" on class " + target.getClass().getSimpleName(), e);
}
}
}
private Object convertValue(String value, Class<?> type) {
if (type == boolean.class) {
return Boolean.valueOf(value);
}
if (type == int.class) {
return Integer.valueOf(value);
}
if (type.isEnum()) {
@SuppressWarnings("unchecked") // protected by type.isEnum() check
final Class<Enum> enumType = (Class<Enum>) type;
return Enum.valueOf(enumType, value.toUpperCase());
}
return value;
}
}