/* * 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.bunjlabs.fuga.dependency; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class DependencyManager { private final Logger log = LogManager.getLogger(DependencyManager.class); private final Map<Class, Object> dependencies = new HashMap<>(); /** * Create new dependency manager. */ public DependencyManager() { } /** * Creates new object from specified class and inject dependences to * contructor and public fields. * * @param <T> Type of injectable object. * @param injectable Injectable class. * @return object with ijected dependences. * @throws InjectException if is unable to iject dependences */ public <T> T inject(Class<T> injectable) throws InjectException { Constructor<T> annotatedConstructor = getAnnotatedConstructor(injectable); if (annotatedConstructor != null) { return injectToConstructor(annotatedConstructor); } T obj; try { obj = injectable.getConstructor().newInstance(); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new InjectException("Unable to instatiate class by default constructor of " + injectable.getName(), e); } injectToFields(obj, getAnnotatedFields(injectable)); return obj; } private <T> T injectToConstructor(Constructor<T> injactableConstructor) throws InjectException { Object[] parameters = new Object[injactableConstructor.getParameterCount()]; Class[] parameterTypes = injactableConstructor.getParameterTypes(); for (int i = 0; i < parameters.length; i++) { parameters[i] = getDependency(parameterTypes[i]); } try { return injactableConstructor.newInstance(parameters); } catch (SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new InjectException("Unable to inject by constructor", ex); } } private void injectToFields(Object injectable, List<Field> injectableFields) throws InjectException { for (Field f : injectableFields) { injectField(injectable, f); } } private void injectField(Object injectable, Field injectableField) throws InjectException { try { Class cls = injectableField.getType(); Object injectOnject = getDependency(cls); injectableField.set(injectable, injectOnject); } catch (IllegalArgumentException | IllegalAccessException e) { throw new InjectException("Unable to inject field", e); } } private Constructor getAnnotatedConstructor(Class injactable) { return Stream.of(injactable.getConstructors()) .filter((Constructor c) -> c.isAnnotationPresent(Inject.class)) .findFirst().orElse(null); } private List<Field> getAnnotatedFields(Class injectable) { return Stream.of(injectable.getFields()) .filter((Field f) -> f.isAnnotationPresent(Inject.class)) .collect(Collectors.toList()); } private Object getDependency(Class cls) throws InjectException { if (dependencies.containsKey(cls)) { return getDependency(cls, dependencies.get(cls)); } else { for (Map.Entry<Class, Object> e : dependencies.entrySet()) { if (cls.isAssignableFrom(e.getKey())) { return getDependency(cls, e.getValue()); } } } throw new InjectException("No suitable dependency for " + cls.getName()); } private Object getDependency(Class cls, Object obj) throws InjectException { if (obj != null) { return obj; } return inject(cls); } /** * Register specified class and object as dependency. * * @param cls Dependency class. * @param obj Dependency object. */ public void register(Class cls, Object obj) { dependencies.put(cls, obj); } /** * Register specified objects as dependencies. * * @param objs Dependency objects. */ public void register(Object... objs) { for (Object obj : objs) { register(obj.getClass(), obj); } } /** * Register specified classes as dependencies. * * @param clss Dependency classess. */ public void register(Class... clss) { for (Class cls : clss) { register(cls, null); } } /** * Creates new object from specified class, inject dependences to contructor * and public fields and register this object as dependency. * * Calling this method is identical to code: * <pre> * T obj = inject(injectable); * register(injectable, obj); * </pre> * * @param <T> Type of injectable object. * @param injectable Injectable class. * @return injected object. * @throws InjectException if is unable to iject dependences */ public <T> T registerAndInject(Class<T> injectable) throws InjectException { T obj = inject(injectable); register(injectable, obj); return obj; } }