/* * Copyright 2017 MongoDB, Inc. * * 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.bson.codecs.pojo; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static java.lang.String.format; import static java.lang.reflect.Modifier.isStatic; import static java.lang.reflect.Modifier.isTransient; import static java.util.Arrays.asList; import static java.util.Collections.reverse; import static org.bson.assertions.Assertions.notNull; final class PojoBuilderHelper { @SuppressWarnings("unchecked") static <T> void configureClassModelBuilder(final ClassModelBuilder<T> classModelBuilder, final Class<T> clazz) { classModelBuilder.type(notNull("clazz", clazz)); ArrayList<Annotation> annotations = new ArrayList<Annotation>(); Set<String> fieldNames = new HashSet<String>(); Map<String, TypeParameterMap> fieldTypeParameterMap = new HashMap<String, TypeParameterMap>(); Class<? super T> currentClass = clazz; TypeData<?> parentClassTypeData = null; while (currentClass.getSuperclass() != null) { annotations.addAll(asList(currentClass.getDeclaredAnnotations())); List<String> genericTypeNames = new ArrayList<String>(); for (TypeVariable<? extends Class<? super T>> classTypeVariable : currentClass.getTypeParameters()) { genericTypeNames.add(classTypeVariable.getName()); } for (Field field : currentClass.getDeclaredFields()) { if (!fieldNames.add(field.getName()) || isTransient(field.getModifiers()) || isStatic(field.getModifiers())) { continue; } TypeParameterMap typeParameterMap = getTypeParameterMap(genericTypeNames, field); fieldTypeParameterMap.put(field.getName(), typeParameterMap); FieldModelBuilder<?> fieldModelBuilder = getFieldBuilder(field, field.getType()); if (parentClassTypeData != null) { specializeFieldModelBuilder(fieldModelBuilder, typeParameterMap, parentClassTypeData.getTypeParameters()); } classModelBuilder.addField(fieldModelBuilder); field.setAccessible(true); } parentClassTypeData = getTypeData(currentClass.getGenericSuperclass(), currentClass); currentClass = currentClass.getSuperclass(); } reverse(annotations); classModelBuilder.annotations(annotations); classModelBuilder.fieldNameToTypeParameterMap(fieldTypeParameterMap); Constructor<T> noArgsConstructor = null; for (Constructor<?> constructor : clazz.getDeclaredConstructors()) { if (constructor.getParameterTypes().length == 0) { noArgsConstructor = (Constructor<T>) constructor; noArgsConstructor.setAccessible(true); } } classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl<T>(clazz.getSimpleName(), noArgsConstructor)); } @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> TypeData<T> getTypeData(final Type genericType, final Class<T> clazz) { TypeData.Builder<T> builder = TypeData.builder(clazz); if (genericType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) genericType; for (Type argType : pType.getActualTypeArguments()) { getNestedTypeData(builder, argType); } } return builder.build(); } @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> void getNestedTypeData(final TypeData.Builder<T> builder, final Type type) { if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; TypeData.Builder paramBuilder = TypeData.builder((Class) pType.getRawType()); for (Type argType : pType.getActualTypeArguments()) { getNestedTypeData(paramBuilder, argType); } builder.addTypeParameter(paramBuilder.build()); } else if (type instanceof TypeVariable) { builder.addTypeParameter(TypeData.builder(Object.class).build()); } else if (type instanceof Class) { builder.addTypeParameter(TypeData.builder((Class) type).build()); } } private static <T> FieldModelBuilder<T> getFieldBuilder(final Field field, final Class<T> clazz) { return FieldModel.<T>builder(field); } @SuppressWarnings("unchecked") static <T> FieldModelBuilder<T> configureFieldModelBuilder(final FieldModelBuilder<T> builder, final Field field) { return builder .fieldName(field.getName()) .documentFieldName(field.getName()) .typeData((TypeData<T>) getTypeData(field.getGenericType(), field.getType())) .annotations(asList(field.getDeclaredAnnotations())) .fieldSerialization(new FieldModelSerializationImpl<T>()) .fieldAccessor(new FieldAccessorImpl<T>(field, field.getName())); } private static TypeParameterMap getTypeParameterMap(final List<String> genericTypeNames, final Field field) { int classParamIndex = genericTypeNames.indexOf(field.getGenericType().toString()); TypeParameterMap.Builder builder = TypeParameterMap.builder(); if (classParamIndex != -1) { builder.addIndex(classParamIndex); } else { Type type = field.getGenericType(); if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; for (int i = 0; i < pt.getActualTypeArguments().length; i++) { classParamIndex = genericTypeNames.indexOf(pt.getActualTypeArguments()[i].toString()); if (classParamIndex != -1) { builder.addIndex(i, classParamIndex); } } } } return builder.build(); } @SuppressWarnings("unchecked") private static <V> void specializeFieldModelBuilder(final FieldModelBuilder<V> fieldModelBuilder, final TypeParameterMap typeParameterMap, final List<TypeData<?>> fieldTypeParameters) { if (typeParameterMap.hasTypeParameters() && !fieldTypeParameters.isEmpty()) { TypeData<V> specializedFieldType = fieldModelBuilder.getTypeData(); Map<Integer, Integer> fieldToClassParamIndexMap = typeParameterMap.getFieldToClassParamIndexMap(); Integer classTypeParamRepresentsWholeField = fieldToClassParamIndexMap.get(-1); if (classTypeParamRepresentsWholeField != null) { specializedFieldType = (TypeData<V>) fieldTypeParameters.get(classTypeParamRepresentsWholeField); } else { TypeData.Builder<V> builder = TypeData.builder(fieldModelBuilder.getTypeData().getType()); List<TypeData<?>> typeParameters = new ArrayList<TypeData<?>>(fieldModelBuilder.getTypeData().getTypeParameters()); for (int i = 0; i < typeParameters.size(); i++) { for (Map.Entry<Integer, Integer> mapping : fieldToClassParamIndexMap.entrySet()) { if (mapping.getKey().equals(i)) { typeParameters.set(i, fieldTypeParameters.get(mapping.getValue())); } } } builder.addTypeParameters(typeParameters); specializedFieldType = builder.build(); } fieldModelBuilder.typeData(specializedFieldType); } } static <V> V stateNotNull(final String property, final V value) { if (value == null) { throw new IllegalStateException(format("%s cannot be null", property)); } return value; } private PojoBuilderHelper() { } }