io.github.lukehutch.fastclasspathscanner.json.ClassFields Maven / Gradle / Ivy
Show all versions of fast-classpath-scanner Show documentation
/*
* This file is part of FastClasspathScanner.
*
* Author: Luke Hutchison
*
* Hosted at: https://github.com/lukehutch/fast-classpath-scanner
*
* --
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Luke Hutchison
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.lukehutch.fastclasspathscanner.json;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The list of fields that can be (de)serialized (non-final, non-transient, non-synthetic, accessible), and their
* corresponding resolved (concrete) types.
*/
class ClassFields {
/**
* The list of fields that can be (de)serialized (non-final, non-transient, non-synthetic, accessible), and
* their corresponding resolved (concrete) types.
*
*
* For arrays, the {@link Type} will be a {@code Class>} reference where {@link Class#isArray()} is true, and
* {@link Class#getComponentType()} is the element type (the element type will itself be an array-typed
* {@code Class>} reference for multi-dimensional arrays).
*
*
* For generics, the {@link Type} will be an implementation of {@link ParameterizedType}.
*/
final List fieldOrder = new ArrayList<>();
/** Map from field name to field and resolved type. */
final Map fieldNameToFieldTypeInfo = new HashMap<>();
/** If non-null, this is the field that has an {@link Id} annotation. */
Field idField;
public ClassFields(final Class> cls, final boolean resolveTypes, final boolean onlySerializePublicFields,
final ClassFieldCache classFieldCache) {
// Find declared accessible fields in all superclasses, and resolve generic types
final Set visibleFieldNames = new HashSet<>();
final List> fieldSuperclassReversedOrder = new ArrayList<>();
TypeResolutions currTypeResolutions = null;
for (Type currType = cls; currType != Object.class && currType != null;) {
Class> currRawType;
ParameterizedType currParameterizedType;
if (currType instanceof ParameterizedType) {
currParameterizedType = (ParameterizedType) currType;
currRawType = (Class>) currParameterizedType.getRawType();
} else if (currType instanceof Class>) {
currParameterizedType = null;
currRawType = (Class>) currType;
} else {
// Class definitions should not be of type WildcardType or GenericArrayType
throw new IllegalArgumentException("Illegal class type: " + currType);
}
final Field[] fields = currRawType.getDeclaredFields();
final List fieldOrderWithinClass = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
final Field field = fields[i];
// Mask superclass fields if subclass has a field of the same name
if (visibleFieldNames.add(field.getName())) {
// Check for @Id annotation
final boolean isIdField = field.isAnnotationPresent(Id.class);
if (isIdField) {
if (idField != null) {
throw new IllegalArgumentException(
"More than one @Id annotation: " + idField.getDeclaringClass() + "." + idField
+ " ; " + currRawType.getName() + "." + field.getName());
}
idField = field;
}
if (JSONUtils.fieldIsSerializable(field, onlySerializePublicFields)) {
// Resolve field type variables, if any, using the current type resolutions. This will
// completely resolve some types (in the superclass), if the subclass extends a concrete
// version of a generic superclass, but it will only partially resolve variables in
// superclasses in general.
final Type fieldGenericType = field.getGenericType();
final Type fieldTypePartiallyResolved = currTypeResolutions != null && resolveTypes
? currTypeResolutions.resolveTypeVariables(fieldGenericType)
: fieldGenericType;
// Save field and its partially resolved type
final FieldTypeInfo fieldTypeInfo = new FieldTypeInfo(field, fieldTypePartiallyResolved,
classFieldCache);
fieldNameToFieldTypeInfo.put(field.getName(), fieldTypeInfo);
fieldOrderWithinClass.add(fieldTypeInfo);
} else if (isIdField) {
throw new IllegalArgumentException(
"@Id annotation field must be accessible, final, and non-transient: "
+ currRawType.getName() + "." + field.getName());
}
}
}
// Save fields group in the order they were defined in the class, but in reverse order of superclasses
fieldSuperclassReversedOrder.add(fieldOrderWithinClass);
// Move up to superclass, resolving superclass type variables using current class' type resolutions
// e.g. if the current resolutions list is { T => Integer }, and the current class is C, all fields
// of type T were resolved above to type Integer. If C extends B, then resolve B to B,
// and look up B's own generic type to produce the list of resolutions for fields in B (e.g. if B is
// defined as B, then after resolving B to B, we can produce a new list of resolutions,
// { V => Integer } ).
final Type genericSuperType = currRawType.getGenericSuperclass();
if (resolveTypes) {
if (genericSuperType instanceof ParameterizedType) {
// Resolve TypeVariables in the generic supertype of the class, using the current type resolutions
final Type resolvedSupertype = currTypeResolutions == null ? genericSuperType
: currTypeResolutions.resolveTypeVariables(genericSuperType);
// Produce new type resolutions for the superclass, by comparing its concrete to its generic type
currTypeResolutions = resolvedSupertype instanceof ParameterizedType
? new TypeResolutions((ParameterizedType) resolvedSupertype)
: null;
// Iterate to superclass
currType = resolvedSupertype;
} else if (genericSuperType instanceof Class>) {
// In the case of a raw class, the generic supertype may already have resolved type variables,
// e.g. "class A extends B"
currType = genericSuperType;
currTypeResolutions = null;
} else {
throw new IllegalArgumentException("Got unexpected supertype " + genericSuperType);
}
} else {
// If not resolving types, just move up to supertype
currType = genericSuperType;
}
}
// Reverse the order of field visibility, so that ancestral superclass fields appear top-down, in field
// definition order (if not masked by same-named fields in subclasses), followed by fields in sublcasses.
for (int i = fieldSuperclassReversedOrder.size() - 1; i >= 0; i--) {
final List fieldGroupingForClass = fieldSuperclassReversedOrder.get(i);
fieldOrder.addAll(fieldGroupingForClass);
}
}
}