/* * Copyright 2013, Arondor * * 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 com.arondor.common.reflection.reflect.instantiator; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.arondor.common.reflection.api.catalog.AccessibleClassCatalog; import com.arondor.common.reflection.api.instantiator.InstantiationContext; import com.arondor.common.reflection.api.instantiator.ReflectionInstantiator; import com.arondor.common.reflection.api.parser.AccessibleClassParser; import com.arondor.common.reflection.catalog.SimpleAccessibleClassCatalog; import com.arondor.common.reflection.model.config.ElementConfiguration; import com.arondor.common.reflection.model.config.ListConfiguration; import com.arondor.common.reflection.model.config.MapConfiguration; import com.arondor.common.reflection.model.config.ObjectConfiguration; import com.arondor.common.reflection.model.config.PrimitiveConfiguration; import com.arondor.common.reflection.model.config.ReferenceConfiguration; import com.arondor.common.reflection.model.java.AccessibleClass; import com.arondor.common.reflection.model.java.AccessibleField; import com.arondor.common.reflection.parser.java.JavaAccessibleClassParser; import com.arondor.common.reflection.reflect.runtime.SimpleInstantiationContext; public class ReflectionInstantiatorReflect implements ReflectionInstantiator { private AccessibleClassParser accessibleClassParser; public synchronized AccessibleClassParser getAccessibleClassParser() { if (accessibleClassParser == null) { accessibleClassParser = new JavaAccessibleClassParser(); } return accessibleClassParser; } public synchronized void setAccessibleClassParser(AccessibleClassParser accessibleClassParser) { this.accessibleClassParser = accessibleClassParser; } private AccessibleClassCatalog accessibleClassCatalog; public synchronized AccessibleClassCatalog getAccessibleClassCatalog() { if (accessibleClassCatalog == null) { accessibleClassCatalog = new SimpleAccessibleClassCatalog(); } return accessibleClassCatalog; } public synchronized void setAccessibleClassCatalog(AccessibleClassCatalog accessibleClassCatalog) { this.accessibleClassCatalog = accessibleClassCatalog; } @Override public InstantiationContext createDefaultInstantiationContext() { return new SimpleInstantiationContext(); } public ReflectionInstantiatorReflect() { initLocalClassCache(); } private final Map<String, Class<?>> localClassCache = new HashMap<String, Class<?>>(); private void initLocalClassCache() { localClassCache.put("int", int.class); localClassCache.put("long", long.class); localClassCache.put("float", float.class); localClassCache.put("double", double.class); localClassCache.put("boolean", boolean.class); } private synchronized Class<?> resolveClass(String className) throws ClassNotFoundException { Class<?> clazz = localClassCache.get(className); if (clazz == null) { clazz = this.getClass().getClassLoader().loadClass(className); localClassCache.put(className, clazz); } return clazz; } private synchronized AccessibleClass resolveAccessibleClass(String className) throws ClassNotFoundException { AccessibleClass accessibleClass = null; try { accessibleClass = getAccessibleClassCatalog().getAccessibleClass(className); } catch (ClassNotFoundException e) { } if (accessibleClass == null) { Class<?> clazz = resolveClass(className); accessibleClass = getAccessibleClassParser().parseAccessibleClass(clazz); getAccessibleClassCatalog().addAccessibleClass(accessibleClass); } return accessibleClass; } private Method getSetterMethod(String className, String setterName, String setterClassName) throws ClassNotFoundException, NoSuchMethodException, SecurityException { Class<?> clazz = resolveClass(className); Class<?> setterClass = resolveClass(setterClassName); return clazz.getMethod(setterName, setterClass); } private <T> Constructor<T> getClassConstructor(String className, List<ElementConfiguration> constructorArguments) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException { Class<T> clazz = (Class<T>) resolveClass(className); if (clazz.equals(String.class)) { /** * Special case for Primitive Class */ return clazz.getConstructor(String.class); } for (Constructor<T> constructor : (Constructor<T>[]) clazz.getConstructors()) { if (constructor.getParameterTypes().length == constructorArguments.size()) { return constructor; } } throw new NoSuchMethodException("Could not find a constructor with " + constructorArguments.size() + " arguments for class " + className); } private Object doInstantiateObject(ObjectConfiguration objectConfiguration, InstantiationContext context) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { String className = objectConfiguration.getClassName(); Class<?> clazz = resolveClass(className); return doInstantiateObject(objectConfiguration, context, clazz); } @Override public <T> T instanciateObject(ObjectConfiguration objectConfiguration, Class<T> desiredClass, InstantiationContext context) { String className = objectConfiguration.getClassName(); try { Object instantiatedObject = doInstantiateObject(objectConfiguration, context); if (!(desiredClass.isAssignableFrom(instantiatedObject.getClass()))) { throw new InstantiationException("Could not assign " + instantiatedObject.getClass().getName() + " to " + desiredClass.getName()); } for (ObjectInstanciationHook hook : objectInstanciationHook) { hook.onObjectInstanciated(objectConfiguration, instantiatedObject); } return (T) instantiatedObject; } catch (ClassNotFoundException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (InstantiationException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (NoSuchMethodException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (SecurityException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (IllegalAccessException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (IllegalArgumentException e) { throw new RuntimeException("Could resolve class " + className, e); } catch (InvocationTargetException e) { throw new RuntimeException("Could resolve class " + className, e); } } private <T> T doInstantiateObject(ObjectConfiguration objectConfiguration, InstantiationContext context, Class<?> clazz) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { T object = null; if (objectConfiguration.getConstructorArguments() == null || objectConfiguration.getConstructorArguments().isEmpty()) { object = (T) clazz.newInstance(); } else if (objectConfiguration.getConstructorArguments().size() == 1 && Enum.class.isAssignableFrom(clazz)) { String enumValue = (String) instantiateElementConfiguration(objectConfiguration.getConstructorArguments() .get(0), String.class.getName(), context); return (T) Enum.valueOf((Class<Enum>) clazz, enumValue); } else { Constructor<T> constructor = getClassConstructor(objectConfiguration.getClassName(), objectConfiguration.getConstructorArguments()); Object arguments[] = new Object[constructor.getParameterTypes().length]; for (int argumentIndex = 0; argumentIndex < constructor.getParameterTypes().length; argumentIndex++) { String argumentClassName = constructor.getParameterTypes()[argumentIndex].getName(); ElementConfiguration argumentConfiguration = objectConfiguration.getConstructorArguments().get( argumentIndex); arguments[argumentIndex] = instantiateElementConfiguration(argumentConfiguration, argumentClassName, context); } object = constructor.newInstance(arguments); } setFields(object, objectConfiguration, context); if (objectConfiguration.isSingleton()) { context.putSharedObject(objectConfiguration.getObjectName(), object); } return object; } private Object instantiateElementConfiguration(ElementConfiguration elementConfiguration, String elementClassName, InstantiationContext context) throws ClassNotFoundException { switch (elementConfiguration.getFieldConfigurationType()) { case Primitive: Object convertedFieldValue = convertPrimitive(((PrimitiveConfiguration) elementConfiguration).getValue(), elementClassName); return convertedFieldValue; case Object: ObjectConfiguration objectConfiguration = (ObjectConfiguration) elementConfiguration; Class<?> clazz = resolveClass(objectConfiguration.getClassName()); Object resultObject = instanciateObject(objectConfiguration, clazz, context); return resultObject; case Reference: ReferenceConfiguration referenceConfiguration = (ReferenceConfiguration) elementConfiguration; return instanciateObject(referenceConfiguration.getReferenceName(), Object.class, context); case List: ListConfiguration listConfiguration = (ListConfiguration) elementConfiguration; List<Object> list = new ArrayList<Object>(); for (ElementConfiguration listElement : listConfiguration.getListConfiguration()) { list.add(instantiateElementConfiguration(listElement, String.class.getName(), context)); } return list; case Map: MapConfiguration mapConfiguration = (MapConfiguration) elementConfiguration; Map<Object, Object> map = new HashMap<Object, Object>(); for (Map.Entry<ElementConfiguration, ElementConfiguration> entry : mapConfiguration.getMapConfiguration() .entrySet()) { Object key = instantiateElementConfiguration(entry.getKey(), String.class.getName(), context); Object value = instantiateElementConfiguration(entry.getValue(), String.class.getName(), context); map.put(key, value); } return map; default: throw new RuntimeException("NOT IMPLEMENTED YET :" + elementConfiguration.getFieldConfigurationType()); } } private <T> void setFields(T object, ObjectConfiguration objectConfiguration, InstantiationContext context) throws ClassNotFoundException, InstantiationException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (objectConfiguration.getFields() == null || objectConfiguration.getFields().isEmpty()) { return; } for (Map.Entry<String, ElementConfiguration> fieldEntry : objectConfiguration.getFields().entrySet()) { String fieldName = fieldEntry.getKey(); String setterName = getAccessibleClassParser().attributeToSetter(fieldName); AccessibleClass accessibleClass = resolveAccessibleClass(objectConfiguration.getClassName()); AccessibleField accessibleField = accessibleClass.getAccessibleFields().get(fieldName); if (accessibleField == null) { throw new InstantiationException("Invalid field name : " + fieldName); } String fieldClassName = accessibleField.getClassName(); Method setterMethod = getSetterMethod(objectConfiguration.getClassName(), setterName, fieldClassName); ElementConfiguration fieldConfiguration = fieldEntry.getValue(); T fieldObject = (T) instantiateElementConfiguration(fieldConfiguration, fieldClassName, context); setterMethod.invoke(object, fieldObject); } } private final FastPrimitiveConverter fastPrimitiveConverter = new FastPrimitiveConverter(); private Object convertPrimitive(String value, String fieldClassName) { return fastPrimitiveConverter.convert(value, fieldClassName); } @Override public <T> T instanciateObject(String beanName, Class<T> desiredClass, InstantiationContext context) { { Object sharedObject = context.getSharedObject(beanName); if (sharedObject != null) { return (T) sharedObject; } } ObjectConfiguration sharedObjectConfiguration = context.getSharedObjectConfiguration(beanName); if (sharedObjectConfiguration == null) { throw new IllegalArgumentException("Invalid bean name :" + beanName); } T object = instanciateObject(sharedObjectConfiguration, desiredClass, context); if (sharedObjectConfiguration.isSingleton()) { context.putSharedObject(beanName, object); } return object; } private final List<ObjectInstanciationHook> objectInstanciationHook = new ArrayList<ReflectionInstantiator.ObjectInstanciationHook>(); @Override public HookHandler addObjectInstanciationHook(final ObjectInstanciationHook hook) { if (hook == null) { return new HookHandler() { @Override public void remove() { } }; } objectInstanciationHook.add(hook); return new HookHandler() { @Override public void remove() { objectInstanciationHook.remove(hook); } }; } }