org.bson.codecs.pojo.PojoBuilderHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mongo-java-driver Show documentation
Show all versions of mongo-java-driver Show documentation
The MongoDB Java Driver uber-artifact, containing mongodb-driver, mongodb-driver-core, and bson
The newest version!
/*
* Copyright 2008-present 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.Method;
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.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.isProtected;
import static java.lang.reflect.Modifier.isPublic;
import static java.util.Arrays.asList;
import static java.util.Collections.reverse;
import static org.bson.assertions.Assertions.notNull;
import static org.bson.codecs.pojo.PropertyReflectionUtils.getPropertyMethods;
import static org.bson.codecs.pojo.PropertyReflectionUtils.isGetter;
import static org.bson.codecs.pojo.PropertyReflectionUtils.toPropertyName;
final class PojoBuilderHelper {
@SuppressWarnings("unchecked")
static void configureClassModelBuilder(final ClassModelBuilder classModelBuilder, final Class clazz) {
classModelBuilder.type(notNull("clazz", clazz));
ArrayList annotations = new ArrayList();
Set propertyNames = new TreeSet();
Map propertyTypeParameterMap = new HashMap();
Class super T> currentClass = clazz;
String declaringClassName = clazz.getSimpleName();
TypeData> parentClassTypeData = null;
Map> propertyNameMap = new HashMap>();
while (!currentClass.isEnum() && currentClass.getSuperclass() != null) {
annotations.addAll(asList(currentClass.getDeclaredAnnotations()));
List genericTypeNames = new ArrayList();
for (TypeVariable extends Class super T>> classTypeVariable : currentClass.getTypeParameters()) {
genericTypeNames.add(classTypeVariable.getName());
}
PropertyReflectionUtils.PropertyMethods propertyMethods = getPropertyMethods(currentClass);
// Note that we're processing setters before getters. It's typical for setters to have more general types
// than getters (e.g.: getter returning ImmutableList, but setter accepting Collection), so by evaluating
// setters first, we'll initialize the PropertyMetadata with the more general type
for (Method method : propertyMethods.getSetterMethods()) {
String propertyName = toPropertyName(method);
propertyNames.add(propertyName);
PropertyMetadata> propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap,
TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames,
getGenericType(method));
if (propertyMetadata.getSetter() == null) {
propertyMetadata.setSetter(method);
for (Annotation annotation : method.getDeclaredAnnotations()) {
propertyMetadata.addWriteAnnotation(annotation);
}
}
}
for (Method method : propertyMethods.getGetterMethods()) {
String propertyName = toPropertyName(method);
propertyNames.add(propertyName);
// If the getter is overridden in a subclass, we only want to process that property, and ignore
// potentially less specific methods from super classes
PropertyMetadata> propertyMetadata = propertyNameMap.get(propertyName);
if (propertyMetadata != null && propertyMetadata.getGetter() != null) {
continue;
}
propertyMetadata = getOrCreateMethodPropertyMetadata(propertyName, declaringClassName, propertyNameMap,
TypeData.newInstance(method), propertyTypeParameterMap, parentClassTypeData, genericTypeNames,
getGenericType(method));
if (propertyMetadata.getGetter() == null) {
propertyMetadata.setGetter(method);
for (Annotation annotation : method.getDeclaredAnnotations()) {
propertyMetadata.addReadAnnotation(annotation);
}
}
}
for (Field field : currentClass.getDeclaredFields()) {
propertyNames.add(field.getName());
// Note if properties are present and types don't match, the underlying field is treated as an implementation detail.
PropertyMetadata> propertyMetadata = getOrCreateFieldPropertyMetadata(field.getName(), declaringClassName,
propertyNameMap, TypeData.newInstance(field), propertyTypeParameterMap, parentClassTypeData, genericTypeNames,
field.getGenericType());
if (propertyMetadata != null && propertyMetadata.getField() == null) {
propertyMetadata.field(field);
for (Annotation annotation : field.getDeclaredAnnotations()) {
propertyMetadata.addReadAnnotation(annotation);
propertyMetadata.addWriteAnnotation(annotation);
}
}
}
parentClassTypeData = TypeData.newInstance(currentClass.getGenericSuperclass(), currentClass);
currentClass = currentClass.getSuperclass();
}
if (currentClass.isInterface()) {
annotations.addAll(asList(currentClass.getDeclaredAnnotations()));
}
for (String propertyName : propertyNames) {
PropertyMetadata> propertyMetadata = propertyNameMap.get(propertyName);
if (propertyMetadata.isSerializable() || propertyMetadata.isDeserializable()) {
classModelBuilder.addProperty(createPropertyModelBuilder(propertyMetadata));
}
}
reverse(annotations);
classModelBuilder.annotations(annotations);
classModelBuilder.propertyNameToTypeParameterMap(propertyTypeParameterMap);
Constructor noArgsConstructor = null;
for (Constructor> constructor : clazz.getDeclaredConstructors()) {
if (constructor.getParameterTypes().length == 0
&& (isPublic(constructor.getModifiers()) || isProtected(constructor.getModifiers()))) {
noArgsConstructor = (Constructor) constructor;
noArgsConstructor.setAccessible(true);
}
}
classModelBuilder.instanceCreatorFactory(new InstanceCreatorFactoryImpl(new CreatorExecutable(clazz, noArgsConstructor)));
}
private static PropertyMetadata getOrCreateMethodPropertyMetadata(final String propertyName,
final String declaringClassName,
final Map> propertyNameMap,
final TypeData typeData,
final Map propertyTypeParameterMap,
final TypeData parentClassTypeData,
final List genericTypeNames,
final Type genericType) {
PropertyMetadata propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData);
if (!isAssignableClass(propertyMetadata.getTypeData().getType(), typeData.getType())) {
propertyMetadata.setError(format("Property '%s' in %s, has differing data types: %s and %s.", propertyName,
declaringClassName, propertyMetadata.getTypeData(), typeData));
}
cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType);
return propertyMetadata;
}
private static boolean isAssignableClass(final Class> propertyTypeClass, final Class> typeDataClass) {
return propertyTypeClass.isAssignableFrom(typeDataClass) || typeDataClass.isAssignableFrom(propertyTypeClass);
}
private static PropertyMetadata getOrCreateFieldPropertyMetadata(final String propertyName,
final String declaringClassName,
final Map> propertyNameMap,
final TypeData typeData,
final Map propertyTypeParameterMap,
final TypeData parentClassTypeData,
final List genericTypeNames,
final Type genericType) {
PropertyMetadata propertyMetadata = getOrCreatePropertyMetadata(propertyName, declaringClassName, propertyNameMap, typeData);
if (!propertyMetadata.getTypeData().getType().isAssignableFrom(typeData.getType())) {
return null;
}
cachePropertyTypeData(propertyMetadata, propertyTypeParameterMap, parentClassTypeData, genericTypeNames, genericType);
return propertyMetadata;
}
@SuppressWarnings("unchecked")
private static PropertyMetadata getOrCreatePropertyMetadata(final String propertyName,
final String declaringClassName,
final Map> propertyNameMap,
final TypeData typeData) {
PropertyMetadata propertyMetadata = (PropertyMetadata) propertyNameMap.get(propertyName);
if (propertyMetadata == null) {
propertyMetadata = new PropertyMetadata(propertyName, declaringClassName, typeData);
propertyNameMap.put(propertyName, propertyMetadata);
}
return propertyMetadata;
}
private static void cachePropertyTypeData(final PropertyMetadata propertyMetadata,
final Map propertyTypeParameterMap,
final TypeData parentClassTypeData,
final List genericTypeNames,
final Type genericType) {
TypeParameterMap typeParameterMap = getTypeParameterMap(genericTypeNames, genericType);
propertyTypeParameterMap.put(propertyMetadata.getName(), typeParameterMap);
propertyMetadata.typeParameterInfo(typeParameterMap, parentClassTypeData);
}
private static Type getGenericType(final Method method) {
return isGetter(method) ? method.getGenericReturnType() : method.getGenericParameterTypes()[0];
}
@SuppressWarnings("unchecked")
static PropertyModelBuilder createPropertyModelBuilder(final PropertyMetadata propertyMetadata) {
PropertyModelBuilder propertyModelBuilder = PropertyModel.builder()
.propertyName(propertyMetadata.getName())
.readName(propertyMetadata.getName())
.writeName(propertyMetadata.getName())
.typeData(propertyMetadata.getTypeData())
.readAnnotations(propertyMetadata.getReadAnnotations())
.writeAnnotations(propertyMetadata.getWriteAnnotations())
.propertySerialization(new PropertyModelSerializationImpl())
.propertyAccessor(new PropertyAccessorImpl(propertyMetadata))
.setError(propertyMetadata.getError());
if (propertyMetadata.getTypeParameters() != null) {
specializePropertyModelBuilder(propertyModelBuilder, propertyMetadata);
}
return propertyModelBuilder;
}
private static TypeParameterMap getTypeParameterMap(final List genericTypeNames, final Type propertyType) {
int classParamIndex = genericTypeNames.indexOf(propertyType.toString());
TypeParameterMap.Builder builder = TypeParameterMap.builder();
if (classParamIndex != -1) {
builder.addIndex(classParamIndex);
} else {
if (propertyType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) propertyType;
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 void specializePropertyModelBuilder(final PropertyModelBuilder propertyModelBuilder,
final PropertyMetadata propertyMetadata) {
if (propertyMetadata.getTypeParameterMap().hasTypeParameters() && !propertyMetadata.getTypeParameters().isEmpty()) {
TypeData specializedFieldType;
Map fieldToClassParamIndexMap = propertyMetadata.getTypeParameterMap().getPropertyToClassParamIndexMap();
Integer classTypeParamRepresentsWholeField = fieldToClassParamIndexMap.get(-1);
if (classTypeParamRepresentsWholeField != null) {
specializedFieldType = (TypeData) propertyMetadata.getTypeParameters().get(classTypeParamRepresentsWholeField);
} else {
TypeData.Builder builder = TypeData.builder(propertyModelBuilder.getTypeData().getType());
List> typeParameters = new ArrayList>(propertyModelBuilder.getTypeData().getTypeParameters());
for (int i = 0; i < typeParameters.size(); i++) {
for (Map.Entry mapping : fieldToClassParamIndexMap.entrySet()) {
if (mapping.getKey().equals(i)) {
typeParameters.set(i, propertyMetadata.getTypeParameters().get(mapping.getValue()));
}
}
}
builder.addTypeParameters(typeParameters);
specializedFieldType = builder.build();
}
propertyModelBuilder.typeData(specializedFieldType);
}
}
static V stateNotNull(final String property, final V value) {
if (value == null) {
throw new IllegalStateException(format("%s cannot be null", property));
}
return value;
}
private PojoBuilderHelper() {
}
}