use of org.apache.xbean.recipe.ObjectRecipe in project component-runtime by Talend.
the class ReflectionService method createObject.
private Object createObject(final ClassLoader loader, final Function<Supplier<Object>, Object> contextualSupplier, final Class clazz, final String[] args, final String name, final Map<String, Object> config) {
if (PropertyEditors.canConvert(clazz) && config.size() == 1) {
// direct conversion using the configured
// converter/editor
final Object configValue = config.values().iterator().next();
if (String.class.isInstance(configValue)) {
return PropertyEditors.getValue(clazz, String.class.cast(configValue));
}
}
final String prefix = name + ".";
final ObjectRecipe recipe = new ObjectRecipe(clazz);
recipe.allow(org.apache.xbean.recipe.Option.FIELD_INJECTION);
recipe.allow(org.apache.xbean.recipe.Option.PRIVATE_PROPERTIES);
recipe.allow(org.apache.xbean.recipe.Option.CASE_INSENSITIVE_PROPERTIES);
recipe.allow(org.apache.xbean.recipe.Option.IGNORE_MISSING_PROPERTIES);
// allows to access not matched properties
recipe.setProperty("rawProperties", new UnsetPropertiesRecipe());
// directly
ofNullable(args).ifPresent(recipe::setConstructorArgNames);
final Map<String, Object> specificMapping = config.entrySet().stream().filter(e -> e.getKey().startsWith(prefix)).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
// extract map configuration
final Map<String, Object> mapEntries = specificMapping.entrySet().stream().filter(e -> {
final String key = e.getKey();
final int idxStart = key.indexOf('[', prefix.length());
return idxStart > 0 && ((idxStart > ".key".length() && key.substring(idxStart - ".key".length(), idxStart).equals(".key")) || (idxStart > ".value".length() && key.substring(idxStart - ".value".length(), idxStart).equals(".value")));
}).sorted(this::sortIndexEntry).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
mapEntries.keySet().forEach(specificMapping::remove);
final Map<String, Object> preparedMaps = new HashMap<>();
for (final Map.Entry<String, Object> entry : mapEntries.entrySet()) {
final String key = entry.getKey();
final int idxStart = key.indexOf('[', prefix.length());
String enclosingName = key.substring(prefix.length(), idxStart);
if (enclosingName.endsWith(".key")) {
enclosingName = enclosingName.substring(0, enclosingName.length() - ".key".length());
} else if (enclosingName.endsWith(".value")) {
enclosingName = enclosingName.substring(0, enclosingName.length() - ".value".length());
} else {
throw new IllegalArgumentException("'" + key + "' is not supported, it is considered as a map binding");
}
if (preparedMaps.containsKey(enclosingName)) {
continue;
}
final Type genericType = findField(enclosingName.substring(enclosingName.indexOf('.') + 1), clazz).getGenericType();
if (!ParameterizedType.class.isInstance(genericType)) {
throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic map and not a " + genericType);
}
final ParameterizedType pt = ParameterizedType.class.cast(genericType);
if (pt.getActualTypeArguments().length != 2 || !Class.class.isInstance(pt.getActualTypeArguments()[0]) || !Class.class.isInstance(pt.getActualTypeArguments()[1])) {
throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic map with a key and value class type (" + pt + ")");
}
final Class<?> keyType = Class.class.cast(pt.getActualTypeArguments()[0]);
final Class<?> valueType = Class.class.cast(pt.getActualTypeArguments()[1]);
preparedMaps.put(enclosingName, createMap(prefix + enclosingName, Map.class, createObjectFactory(loader, contextualSupplier, keyType), createObjectFactory(loader, contextualSupplier, valueType), createMapCollector(Class.class.cast(pt.getRawType()), keyType, valueType), new HashMap<>(mapEntries)));
}
// extract list configuration
final Map<String, Object> listEntries = specificMapping.entrySet().stream().filter(e -> {
final String key = e.getKey();
final int idxStart = key.indexOf('[', prefix.length());
final int idxEnd = key.indexOf(']', prefix.length());
final int sep = key.indexOf('.', prefix.length() + 1);
return idxStart > 0 && key.endsWith("]") && (sep > idxEnd || sep < 0);
}).sorted(this::sortIndexEntry).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
listEntries.keySet().forEach(specificMapping::remove);
final Map<String, Object> preparedLists = new HashMap<>();
for (final Map.Entry<String, Object> entry : listEntries.entrySet()) {
final String key = entry.getKey();
final int idxStart = key.indexOf('[', prefix.length());
final String enclosingName = key.substring(prefix.length(), idxStart);
if (preparedLists.containsKey(enclosingName)) {
continue;
}
final Type genericType = findField(enclosingName, clazz).getGenericType();
if (Class.class.isInstance(genericType)) {
final Class<?> arrayClass = Class.class.cast(genericType);
if (arrayClass.isArray()) {
// we could use Array.newInstance but for now use the list, shouldn't impact
// much the perf
final Collection<?> list = Collection.class.cast(createList(loader, contextualSupplier, prefix + enclosingName, List.class, arrayClass.getComponentType(), toList(), createObjectFactory(loader, contextualSupplier, arrayClass.getComponentType()), new HashMap<>(listEntries)));
// we need that conversion to ensure the type matches
final Object array = Array.newInstance(arrayClass.getComponentType(), list.size());
int idx = 0;
for (final Object o : list) {
Array.set(array, idx++, o);
}
preparedLists.put(enclosingName, array);
continue;
}
// else let it fail with the "collection" error
}
if (!ParameterizedType.class.isInstance(genericType)) {
throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic collection and not a " + genericType);
}
final ParameterizedType pt = ParameterizedType.class.cast(genericType);
if (pt.getActualTypeArguments().length != 1 || !Class.class.isInstance(pt.getActualTypeArguments()[0])) {
throw new IllegalArgumentException(clazz + "#" + enclosingName + " should use concrete class items and not a " + pt.getActualTypeArguments()[0]);
}
final Type itemType = pt.getActualTypeArguments()[0];
preparedLists.put(enclosingName, createList(loader, contextualSupplier, prefix + enclosingName, Class.class.cast(pt.getRawType()), Class.class.cast(itemType), toList(), createObjectFactory(loader, contextualSupplier, itemType), new HashMap<>(listEntries)));
}
// extract nested Object configurations
final Map<String, Object> objectEntries = specificMapping.entrySet().stream().filter(e -> {
final String key = e.getKey();
return key.indexOf('.', prefix.length() + 1) > 0;
}).sorted((o1, o2) -> {
final String key1 = o1.getKey();
final String key2 = o2.getKey();
if (key1.equals(key2)) {
return 0;
}
final String nestedName1 = key1.substring(prefix.length(), key1.indexOf('.', prefix.length() + 1));
final String nestedName2 = key2.substring(prefix.length(), key2.indexOf('.', prefix.length() + 1));
final int idxStart1 = nestedName1.indexOf('[');
final int idxStart2 = nestedName2.indexOf('[');
if (idxStart1 > 0 && idxStart2 > 0 && nestedName1.substring(0, idxStart1).equals(nestedName2.substring(0, idxStart2))) {
final int idx1 = Integer.parseInt(nestedName1.substring(idxStart1 + 1, nestedName1.length() - 1));
final int idx2 = Integer.parseInt(nestedName2.substring(idxStart2 + 1, nestedName2.length() - 1));
return idx1 - idx2;
}
return key1.compareTo(key2);
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (o, o2) -> {
throw new IllegalArgumentException("Can't merge " + o + " and " + o2);
}, LinkedHashMap::new));
objectEntries.keySet().forEach(specificMapping::remove);
final Map<String, Object> preparedObjects = new HashMap<>();
for (final Map.Entry<String, Object> entry : objectEntries.entrySet()) {
final String nestedName = entry.getKey().substring(prefix.length(), entry.getKey().indexOf('.', prefix.length() + 1));
if (nestedName.endsWith("]")) {
// complex lists
final int idxStart = nestedName.indexOf('[');
if (idxStart > 0) {
final String listName = nestedName.substring(0, idxStart);
final Field field = findField(listName, clazz);
if (ParameterizedType.class.isInstance(field.getGenericType())) {
final ParameterizedType pt = ParameterizedType.class.cast(field.getGenericType());
if (Class.class.isInstance(pt.getRawType())) {
final Class<?> rawType = Class.class.cast(pt.getRawType());
if (Set.class.isAssignableFrom(rawType)) {
addListElement(loader, contextualSupplier, config, prefix, preparedObjects, nestedName, listName, pt, () -> new HashSet<>(2));
} else if (Collection.class.isAssignableFrom(rawType)) {
addListElement(loader, contextualSupplier, config, prefix, preparedObjects, nestedName, listName, pt, () -> new ArrayList<>(2));
} else {
throw new IllegalArgumentException("unsupported configuration type: " + pt);
}
continue;
} else {
throw new IllegalArgumentException("unsupported configuration type: " + pt);
}
} else {
throw new IllegalArgumentException("unsupported configuration type: " + field.getType());
}
} else {
throw new IllegalArgumentException("unsupported configuration type: " + nestedName);
}
}
final Field field = findField(nestedName, clazz);
preparedObjects.put(nestedName, createObject(loader, contextualSupplier, field.getType(), findArgsName(field.getType()), prefix + nestedName, config));
}
// other entries can be directly set
final Map<String, Object> normalizedConfig = specificMapping.entrySet().stream().filter(e -> e.getKey().startsWith(prefix) && e.getKey().substring(prefix.length()).indexOf('.') < 0).collect(toMap(e -> {
final String specificConfig = e.getKey().substring(prefix.length());
final int index = specificConfig.indexOf('[');
if (index > 0) {
final int end = specificConfig.indexOf(']', index);
if (end > index) {
// > 0 would work too
// here we need to normalize it to let xbean understand it
String leadingString = specificConfig.substring(0, index);
if (leadingString.endsWith(".key") || leadingString.endsWith(".value")) {
// map
leadingString = leadingString.substring(0, leadingString.lastIndexOf('.'));
}
return leadingString + specificConfig.substring(end + 1);
}
}
return specificConfig;
}, Map.Entry::getValue));
// now bind it all to the recipe and builder the instance
preparedMaps.forEach(recipe::setProperty);
preparedLists.forEach(recipe::setProperty);
preparedObjects.forEach(recipe::setProperty);
normalizedConfig.forEach(recipe::setProperty);
return recipe.create(loader);
}
Aggregations