/** * 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 org.deephacks.confit.serialization; import org.deephacks.confit.Id; import org.deephacks.confit.model.Bean; import org.deephacks.confit.model.BeanId; import org.deephacks.confit.model.Events; import org.deephacks.confit.model.Schema; import org.deephacks.confit.model.Schema.SchemaProperty; import org.deephacks.confit.model.Schema.SchemaPropertyList; import org.deephacks.confit.model.Schema.SchemaPropertyRef; import org.deephacks.confit.model.Schema.SchemaPropertyRefList; import org.deephacks.confit.model.Schema.SchemaPropertyRefMap; import org.deephacks.confit.serialization.Conversion.Converter; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static org.deephacks.confit.serialization.Reflections.forName; final class BeanToObjectConverter implements Converter<Bean, Object> { private Conversion conversion = Conversion.get(); private ConcurrentHashMap<String, Class<?>> classCache = new ConcurrentHashMap<>(); @Override public Object convert(Bean source, Class<? extends Object> specificType) { final Object instance; try { instance = newInstance(specificType); } catch (Exception e) { throw new UnsupportedOperationException(e); } Map<String, Object> valuesToInject = new HashMap<>(); Map<BeanId, Object> instanceCache = new HashMap<>(); return convert(source, instance, valuesToInject, instanceCache); } private Object convert(Bean source, Object instance, Map<String, Object> valuesToInject, Map<BeanId, Object> instanceCache) { instanceCache.put(source.getId(), instance); convertProperty(source, valuesToInject); convertPropertyList(source, valuesToInject); convertPropertyRef(source, valuesToInject, instanceCache); convertPropertyRefList(source, valuesToInject, instanceCache); convertPropertyRefMap(source, valuesToInject, instanceCache); Schema schema = source.getSchema(); if (!schema.getId().isSingleton()) { // do not try to inject lookup id: the field is static final valuesToInject.put(getIdField(instance.getClass()), source.getId().getInstanceId()); } inject(instance, valuesToInject); return instance; } private void inject(Object instance, Map<String, Object> values) { List<Field> fields = findFields(instance.getClass()); for (Field field : fields) { field.setAccessible(true); Object value = values.get(field.getName()); if (value == null) { continue; } try { field.set(instance, value); } catch (IllegalArgumentException | IllegalAccessException e) { throw new UnsupportedOperationException(e); } } } private void convertPropertyRefMap(Bean source, Map<String, Object> values, Map<BeanId, Object> instanceCache) { for (SchemaPropertyRefMap prop : source.getSchema().get(SchemaPropertyRefMap.class)) { List<BeanId> beans = source.getReference(prop.getName()); if (beans == null) { continue; } Map<Object, Object> c = newMap(loadClass(prop.getMapType())); for (BeanId beanId : beans) { Bean b = beanId.getBean(); if (b != null) { Object beanInstance = instanceCache.get(beanId); if (beanInstance == null) { try { beanInstance = newInstance(loadClass(b.getSchema().getType())); } catch (Exception e) { throw new UnsupportedOperationException(e); } beanInstance = convert(b, beanInstance, new HashMap<String, Object>(), instanceCache); } c.put(beanId.getInstanceId(), beanInstance); } } values.put(prop.getFieldName(), c); } } private void convertPropertyRefList(Bean source, Map<String, Object> values, Map<BeanId, Object> instanceCache) { for (SchemaPropertyRefList prop : source.getSchema().get(SchemaPropertyRefList.class)) { List<BeanId> references = source.getReference(prop.getName()); if (references == null) { continue; } Collection<Object> c = newCollection(loadClass(prop.getCollectionType())); for (BeanId beanId : references) { Bean b = beanId.getBean(); if (b != null) { Object beanInstance = instanceCache.get(beanId); if (beanInstance == null) { String type = b.getSchema().getType(); try { beanInstance = newInstance(loadClass(type)); } catch (Exception e) { throw new UnsupportedOperationException(e); } beanInstance = convert(b, beanInstance, new HashMap<String, Object>(), instanceCache); } c.add(beanInstance); } } values.put(prop.getFieldName(), c); } } private void convertPropertyRef(Bean source, Map<String, Object> values, Map<BeanId, Object> instanceCache) { for (SchemaPropertyRef prop : source.getSchema().get(SchemaPropertyRef.class)) { BeanId id = source.getFirstReference(prop.getName()); if (id == null) { continue; } Bean ref = id.getBean(); if (ref == null) { continue; } Schema refSchema = ref.getSchema(); if (refSchema == null) { throw Events.CFG101_SCHEMA_NOT_EXIST(ref.getId().getSchemaName()); } SchemaPropertyRef schemaRef = source.getSchema().get(SchemaPropertyRef.class, prop.getName()); Object beanInstance = instanceCache.get(id); if (beanInstance == null) { try { beanInstance = newInstance(loadClass(refSchema.getType())); } catch (Exception e) { throw new UnsupportedOperationException(e); } beanInstance = convert(ref, beanInstance, new HashMap<String, Object>(), instanceCache); } values.put(schemaRef.getFieldName(), beanInstance); } } private void convertPropertyList(Bean source, Map<String, Object> values) { for (SchemaPropertyList prop : source.getSchema().get(SchemaPropertyList.class)) { List<String> vals = source.getValues(prop.getName()); String field = prop.getFieldName(); if (vals == null) { continue; } Collection<Object> c = newCollection(loadClass(prop.getCollectionType())); for (String val : vals) { Object converted = conversion.convert(val, loadClass(prop.getType())); c.add(converted); } values.put(field, c); } } private void convertProperty(Bean source, Map<String, Object> values) { for (SchemaProperty prop : source.getSchema().get(SchemaProperty.class)) { String value = source.getSingleValue(prop.getName()); String field = prop.getFieldName(); Object converted = conversion.convert(value, loadClass(prop.getType())); values.put(field, converted); } } private static String getIdField(Class<?> clazz) { for (Field field : findFields(clazz)) { field.setAccessible(true); Annotation annotation = field.getAnnotation(Id.class); if (annotation != null) { return field.getName(); } } throw new RuntimeException("Class [" + clazz + "] does not decalare @Id."); } @SuppressWarnings("unchecked") private static Collection<Object> newCollection(Class<?> clazz) { if (!clazz.isInterface()) { try { return (Collection<Object>) clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } if (List.class.isAssignableFrom(clazz)) { return new ArrayList<>(); } else if (Set.class.isAssignableFrom(clazz)) { return new HashSet<>(); } throw new UnsupportedOperationException("Class [" + clazz + "] is not supported."); } @SuppressWarnings("unchecked") private static Map<Object, Object> newMap(Class<?> clazz) { if (!clazz.isInterface()) { try { return (Map<Object, Object>) clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } if (Map.class.isAssignableFrom(clazz)) { return new HashMap<>(); } else if (ConcurrentMap.class.isAssignableFrom(clazz)) { return new ConcurrentHashMap<>(); } throw new UnsupportedOperationException("Class [" + clazz + "] is not supported."); } private Class<?> loadClass(String className) { Class<?> clazz = classCache.get(className); if (clazz != null) { return clazz; } clazz = forName(className); classCache.put(className, clazz); return clazz; } public static <T> T newInstance(Class<T> type) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { return Reflections.newInstance(type); } /** * Find list field (including private) on a specific class. Searches list * super-classes up to {@link Object}. * * @param clazz * Class to inspect * @return list found fields. */ public static List<Field> findFields(final Class<?> clazz) { List<Field> foundFields = new ArrayList<>(); Class<?> searchType = clazz; while (!Object.class.equals(searchType) && (searchType != null)) { Field[] fields = searchType.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); foundFields.add(field); } searchType = searchType.getSuperclass(); } return foundFields; } }