package org.radargun.utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.radargun.config.*;
/**
* Converts a elements in definition into list of instances. Instance class
* is chosen according to element name as specified in {@link org.radargun.config.DefinitionElement#name()}
* on one of the classes passed in the constructor, or classes implementing class passed to the contructor.
* In the latter case, all JARs in lib/ are scanned for these implementations annotated with DefinitionElement
*
* @author Radim Vansa <rvansa@redhat.com>
*/
public class ReflexiveConverters {
protected abstract static class Base {
protected final Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
protected Base(Class<?>[] classes) {
for (Class<?> clazz : classes) {
DefinitionElement de = clazz.getAnnotation(DefinitionElement.class);
if (de == null) {
throw new IllegalArgumentException(clazz.getName() + " missing @DefinitionElement");
}
if (this.classes.containsKey(de.name())) {
throw new IllegalArgumentException("Trying to register " + clazz.getName() + " as '" + de.name()
+ "' but this is already used by " + this.classes.get(de.name()));
}
this.classes.put(de.name(), clazz);
}
}
protected <T> Base(Class<T> implementedClass) {
ClasspathScanner.scanClasspath(implementedClass, DefinitionElement.class, "org.radargun", clazz -> {
DefinitionElement de = clazz.getAnnotation(DefinitionElement.class);
if (this.classes.containsKey(de.name())) {
throw new IllegalArgumentException("Trying to register " + clazz.getName() + " as '" + de.name()
+ "' but this is already used by " + this.classes.get(de.name()));
}
classes.put(de.name(), clazz);
});
}
protected Object instantiate(String name, Definition definition) {
Class<?> clazz = classes.get(name);
if (clazz == null) throw new IllegalArgumentException(name);
Object item;
try {
Constructor<?> ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true);
item = ctor.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("Cannot instantiate " + clazz.getName(), e);
}
DefinitionElement de = clazz.getAnnotation(DefinitionElement.class);
if (definition instanceof ComplexDefinition) {
if (de.resolveType() == DefinitionElement.ResolveType.PASS_BY_MAP) {
PropertyHelper.setPropertiesFromDefinitions(item, ((ComplexDefinition) definition).getAttributeMap(), false, true);
} else if (de.resolveType() == DefinitionElement.ResolveType.PASS_BY_DEFINITION) {
PropertyHelper.setPropertiesFromDefinitions(item, Collections.singletonMap("", definition), false, true);
} else throw new IllegalStateException(de.resolveType().toString());
} else if (definition instanceof SimpleDefinition) {
PropertyHelper.setProperties(item, Collections.singletonMap("", ((SimpleDefinition) definition).value), false, true);
}
InitHelper.init(item);
return item;
}
public Collection<Class<?>> content() {
return classes.values();
}
}
/**
* Creates single instance of provided classes.
*/
public static class ObjectConverter extends Base implements DefinitionElementConverter<Object> {
/**
* Enumeration-based constructor.
* @param classes That can be instantiated.
*/
public ObjectConverter(Class<?>[] classes) {
super(classes);
}
/**
* Inheritance-based constructor - looks for all classes that inherit from this class
* and are annotated with {@link org.radargun.config.DefinitionElement}
* @param implementedClass
* @param <T>
*/
public <T> ObjectConverter(Class<T> implementedClass) {
super(implementedClass);
}
@Override
public Object convert(ComplexDefinition definition, Type type) {
List<ComplexDefinition.Entry> attributes = definition.getAttributes();
// if we have used templates, there may be multiple attributes with the same name
String name = null;
Definition def = null;
for (ComplexDefinition.Entry attr : attributes) {
if (name == null) {
name = attr.name;
def = attr.definition;
} else if (!name.equals(attr.name)) {
throw new IllegalArgumentException("Single attribute expected");
} else {
def = def.apply(attr.definition);
}
}
return instantiate(name, def);
}
@Override
public String convertToString(Object value) {
if (value == null) return "null";
DefinitionElement de = value.getClass().getAnnotation(DefinitionElement.class);
if (de == null)
throw new IllegalArgumentException("Object does not have DefinitionElement attached: " + value);
return String.format("%s -> %s", de.name(), value);
}
@Override
public int minAttributes() {
return 1;
}
@Override
public int maxAttributes() {
return 1;
}
}
/**
* Creates a list of instances of provided classes.
*/
public static class ListConverter extends Base implements DefinitionElementConverter<List> {
/**
* Enumeration-based constructor.
* @param classes That can be instantiated.
*/
public ListConverter(Class<?>[] classes) {
super(classes);
}
/**
* Inheritance-based constructor - looks for all classes that inherit from this class
* and are annotated with {@link org.radargun.config.DefinitionElement}
* @param implementedClass
* @param <T>
*/
public <T> ListConverter(Class<T> implementedClass) {
super(implementedClass);
}
@Override
public List convert(ComplexDefinition definition, Type type) {
if (type instanceof Class<?>) {
Class<?> clazz = (Class<?>) type;
if (!Collection.class.isAssignableFrom(clazz)) throw new IllegalArgumentException(type.toString());
} else if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
if (!Collection.class.isAssignableFrom((Class<?>) ptype.getRawType()))
throw new IllegalArgumentException(type.toString());
}
List list = new ArrayList();
for (ComplexDefinition.Entry entry : definition.getAttributes()) {
list.add(instantiate(entry.name, entry.definition));
}
return list;
}
@Override
public String convertToString(List list) {
StringBuilder sb = new StringBuilder("[ ");
boolean first = true;
for (Object item : list) {
if (!first) {
sb.append(", ");
}
first = false;
DefinitionElement de = item.getClass().getAnnotation(DefinitionElement.class);
if (de != null) {
sb.append(de.name()).append(" = ");
}
sb.append(String.valueOf(item));
}
return sb.append(" ]").toString();
}
@Override
public int minAttributes() {
return 0;
}
@Override
public int maxAttributes() {
return -1;
}
}
/**
* Converter that parses inline configuration in format definition-name=properties
* (the properties are optional).
*/
public static class SimpleConverter extends Base implements Converter<Object> {
/**
* Enumeration-based constructor.
* @param classes That can be instantiated.
*/
protected SimpleConverter(Class<?>[] classes) {
super(classes);
}
/**
* Inheritance-based constructor - looks for all classes that inherit from this class
* and are annotated with {@link org.radargun.config.DefinitionElement}
* @param implementedClass
* @param <T>
*/
protected <T> SimpleConverter(Class<T> implementedClass) {
super(implementedClass);
}
@Override
public Object convert(String string, Type type) {
int index = string.indexOf(' ');
if (index < 0) {
return instantiate(string, null);
} else {
Map<String, String> properties = Utils.parseParams(string.substring(index + 1));
ComplexDefinition definition = new ComplexDefinition();
for (Map.Entry<String, String> property : properties.entrySet()) {
definition.add(property.getKey(), new SimpleDefinition(property.getValue(), SimpleDefinition.Source.TEXT));
}
return instantiate(string.substring(0, index), definition);
}
}
@Override
public String convertToString(Object value) {
return value == null ? "null" : PropertyHelper.getDefinitionElementName(value.getClass()) + PropertyHelper.toString(value);
}
@Override
public String allowedPattern(Type type) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Class<?>> entry : this.classes.entrySet()) {
if (sb.length() != 0) sb.append("|");
sb.append('(').append(entry.getKey());
Map<String, Path> properties = PropertyHelper.getProperties(entry.getValue(), true, false, true);
Map<String, Path> mandatory = properties.entrySet().stream().filter(e -> !e.getValue().getTargetAnnotation().optional())
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
if (!mandatory.isEmpty()) {
sb.append(" +");
for (Map.Entry<String, Path> property : mandatory.entrySet()) {
sb.append(property.getKey()).append(":[^;]*;");
}
} else {
sb.append(" *");
}
for (Map.Entry<String, Path> property : properties.entrySet()) {
if (mandatory.containsKey(property.getKey())) continue;
sb.append('(').append(property.getKey()).append(":[^;]*;)?");
}
sb.append(')');
}
return sb.toString();
}
}
}