package org.fluentlenium.configuration;
import org.atteo.classindex.ClassIndex;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Abstract registry of FluentLenium factories.
*
* @param <T> type of factories
* @param <R> type of reflective factories
*/
public abstract class AbstractFactoryRegistryImpl<T extends Factory, R extends ReflectiveFactory> {
protected final Class<T> factoryType;
protected final Class<R> reflectiveFactoryType;
protected Map<String, T> factories = new LinkedHashMap<>();
/**
* Creates a new factory registry.
*
* @param factoryType type of factories
* @param reflectiveFactoryType type of reflective factories
*/
public AbstractFactoryRegistryImpl(Class<T> factoryType, Class<R> reflectiveFactoryType) {
this.factoryType = factoryType;
this.reflectiveFactoryType = reflectiveFactoryType;
Iterable<Class<? extends T>> factoryClasses = ClassIndex.getSubclasses(factoryType);
L:
for (Class<? extends T> factoryClass : factoryClasses) {
if (factoryClass.isAnnotationPresent(IndexIgnore.class)) {
continue;
}
for (Class<?> iface : factoryClass.getInterfaces()) {
if (iface.isAnnotationPresent(IndexIgnore.class)) {
continue L;
}
}
if (Modifier.isAbstract(factoryClass.getModifiers())) {
continue;
}
if (!Modifier.isPublic(factoryClass.getModifiers())) {
continue;
}
T factory;
try {
factory = factoryClass.getConstructor().newInstance();
} catch (NoSuchMethodException e) {
throw new ConfigurationException(factoryClass + " should have a public default constructor.", e);
} catch (Exception e) {
throw new ConfigurationException(factoryClass + " can't be instantiated.", e);
}
register(factory);
}
}
/**
* Get the default factory.
*
* @return default factory
*/
public T getDefault() {
List<T> factoriesList;
synchronized (this) {
factoriesList = new ArrayList<>(factories.values());
}
Collections.sort(factoriesList, new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
FactoryPriority annotation1 = o1.getClass().getAnnotation(FactoryPriority.class);
int p1 = annotation1 == null ? 0 : annotation1.value();
FactoryPriority annotation2 = o2.getClass().getAnnotation(FactoryPriority.class);
int p2 = annotation2 == null ? 0 : annotation2.value();
return Integer.compare(p2, p1);
}
});
List<T> filteredFactories = new ArrayList<>();
for (T factory : factoriesList) {
if (factory instanceof ReflectiveFactory) {
if (((ReflectiveFactory) factory).isAvailable()) {
filteredFactories.add(factory);
}
} else {
filteredFactories.add(factory);
}
}
return getDefault(filteredFactories);
}
/**
* Get the default factory from given list of available factories.
*
* @param filteredFactories available factories
* @return default factory
*/
protected abstract T getDefault(List<T> filteredFactories);
/**
* Get the factory registered under the given name.
*
* @param name name of the factory
* @return factory
*/
public T get(String name) {
if (name == null) {
return getDefault();
}
synchronized (this) {
T factory = factories.get(name);
if (factory == null) {
R reflectiveFactory = newReflectiveInstance(name);
if (reflectiveFactory.isAvailable()) {
factories.put(name, (T) reflectiveFactory);
factory = (T) reflectiveFactory;
} else {
handleNoFactoryAvailable(name);
}
}
return factory;
}
}
/**
* Handle the case when no factory is available for given name
*
* @param name request factory name
*/
protected abstract void handleNoFactoryAvailable(String name);
/**
* Creates an instance of reflective factory.
*
* @param name name of the instance to create.
* @return new instance
*/
protected abstract R newReflectiveInstance(String name);
/**
* Register a new factory.
* <p>
* It will use {@link FactoryName} value as the default name.
* <p>
* It will also register the factory under names returned by {@link FactoryNames#getNames()}} if
* it implements {@link FactoryNames}.
*
* @param factory factory to register
*/
public final void register(T factory) {
FactoryName annotation = factory.getClass().getAnnotation(FactoryName.class);
String annotationName = annotation == null ? null : annotation.value();
List<String> names = new ArrayList<>();
if (annotationName != null) {
names.add(annotationName);
}
if (factory instanceof FactoryNames) {
names.addAll(Arrays.asList(((FactoryNames) factory).getNames()));
}
if (names.isEmpty()) {
throw new ConfigurationException("Factory " + factory.getClass().getName()
+ " has no name defined. Use @FactoryName annotation or implement FactoryNames.");
}
synchronized (this) {
registerImpl(names, factory);
}
}
private void registerImpl(List<String> names, T factory) {
boolean registered = false;
for (String name : names) {
if (!registered) {
if (factories.containsKey(name)) {
T exitingFactory = factories.get(name);
if (!exitingFactory.getClass().isAnnotationPresent(DefaultFactory.class)) {
throw new ConfigurationException(
"A factory is already registered with this name: " + name + " (" + factories.get(name) + ")");
}
}
factories.put(name, factory);
registered = true;
}
if (!factories.containsKey(name)) {
factories.put(name, factory);
}
}
}
}