/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* ***
*
* Community License: GPL 3.0
*
* This file is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* ***
*
* Available Commercial License: GraniteDS SLA 1.0
*
* This is the appropriate option if you are creating proprietary
* applications and you are not prepared to distribute and share the
* source code of your application under the GPL v3 license.
*
* Please visit http://www.granitedataservices.com/license for more
* details.
*/
package org.granite.client.tide.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Named;
import javax.inject.Singleton;
import org.granite.client.tide.Context;
import org.granite.client.tide.Factory;
/**
* @author William DRAI
*/
public class InstanceFactory {
private List<Factory<?>> initializers = new ArrayList<Factory<?>>();
private Map<String, Factory<?>> factoriesByName = new HashMap<String, Factory<?>>();
private Map<Class<?>, List<Factory<?>>> factoriesByType = new HashMap<Class<?>, List<Factory<?>>>();
public void initModules(Set<Class<?>> moduleClasses) {
for (Class<?> moduleClass : moduleClasses)
setupModule(moduleClass);
}
private void setupModule(Class<?> moduleClass) {
for (Method method : moduleClass.getMethods()) {
if (method.getDeclaringClass() != moduleClass || !Modifier.isStatic(method.getModifiers()))
continue;
Class<?> type = method.getReturnType();
if (type == Void.class || type == void.class) {
initializers.add(new MethodFactory<Void>(method));
}
else {
if (method.isAnnotationPresent(Named.class)) {
String name = method.getAnnotation(Named.class).value();
if ("".equals(name))
name = method.getName();
registerFactory(name, new MethodFactory<Object>(method, name));
}
else {
registerFactory(type, new MethodFactory<Object>(method));
}
}
}
}
public void registerFactory(String name, Factory<?> factory) {
factoriesByName.put(name, factory);
for (Class<?> type : factory.getTargetTypes())
registerFactory(type, factory);
}
public void registerFactory(Class<?> type, Factory<?> factory) {
List<Factory<?>> factories = factoriesByType.get(type);
if (factories == null) {
factories = new ArrayList<Factory<?>>();
factoriesByType.put(type, factories);
}
else {
if (factories.get(0).isSingleton() != factory.isSingleton())
throw new IllegalStateException("Cannot define different scopes for factories of type " + type);
}
factories.add(factory);
}
public void initContext(Context context) {
for (Factory<?> factory : initializers) {
if (factory.isSingleton() == context.isGlobal())
factory.create(context);
}
}
public Factory<?> forName(String name, boolean singleton) {
Factory<?> factory = factoriesByName.get(name);
if (factory == null)
return null;
return factory.isSingleton() || !singleton ? factory : null;
}
public List<Factory<?>> forType(Class<?> type, boolean singleton) {
List<Factory<?>> factories = new ArrayList<Factory<?>>();
for (Entry<Class<?>, List<Factory<?>>> me : factoriesByType.entrySet()) {
if (type.isAssignableFrom(me.getKey())) {
for (Factory<?> factory : me.getValue()) {
if ((factory.isSingleton() || !singleton) && !factories.contains(factory))
factories.add(factory);
}
}
}
return factories;
}
private static class MethodFactory<T> implements Factory<T> {
private final Method method;
private final String name;
private final boolean singleton;
public MethodFactory(Method method, String name) {
this.method = method;
this.name = name;
this.singleton = method.isAnnotationPresent(Singleton.class);
}
public MethodFactory(Method method) {
this(method, null);
}
public boolean isSingleton() {
return singleton;
}
public String getName() {
return name;
}
@SuppressWarnings("unchecked")
@Override
public T create(Context context) {
final Class<?>[] parametersTypes = method.getParameterTypes();
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Object[] args = new Object[parametersTypes.length];
for (int i = 0; i < parametersTypes.length; i++) {
if (parametersTypes[i] == Context.class) {
args[i] = context;
continue;
}
String name = null;
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation.annotationType() == Named.class) {
name = ((Named)annotation).value();
if ("".equals(name))
throw new RuntimeException("Can not inject @Named value without explicit name for method " + method.getName() + " argument " + i);
break;
}
}
if (name != null) {
args[i] = context.byName(name);
if (args[i] == null)
throw new RuntimeException("Cannot find injected value named " + name + " for arg " + i + " of method " + method.toGenericString());
}
else {
args[i] = context.byType(parametersTypes[i]);
if (args[i] == null)
throw new RuntimeException("Cannot find injected value of type " + parametersTypes[i] + " for arg " + i + " of method " + method.toGenericString());
}
}
try {
return (T)method.invoke(null, args);
}
catch (Exception e) {
throw new RuntimeException("Could not create instance with method " + method.toGenericString(), e);
}
}
public Set<Class<?>> getTargetTypes() {
Set<Class<?>> targetTypes = new HashSet<Class<?>>();
targetTypes.add(method.getReturnType());
for (Class<?> itf : method.getReturnType().getInterfaces()) {
Class<?> i = itf;
do {
targetTypes.add(i);
i = i.getSuperclass();
}
while (i != null && i != Object.class);
}
return targetTypes;
}
}
}