Skip to content

Commit

Permalink
GH-492 - Shade parts from TypeResolver of TypeTools.
Browse files Browse the repository at this point in the history
The current implementation fails in some scenarios. Including the full type tools seems overkill and as it stands, it has only tested with JDK 9 and still uses Unsafe.

Rewriting GenericTools would look pretty similar for our needs.
  • Loading branch information
michael-simons committed Dec 10, 2018
1 parent 1786dd5 commit b60ed67
Showing 1 changed file with 204 additions and 36 deletions.
240 changes: 204 additions & 36 deletions core/src/main/java/org/neo4j/ogm/metadata/reflect/GenericUtils.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
/*
* Copyright (c) 2002-2018 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
* Copyright 2002-2019 the original author or authors.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
* 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
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
* 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.neo4j.ogm.metadata.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;

/**
* Contains a stripped down version of <a href="https://github.com/jhalterman">Jonathan Halterman's</a>
* <a href="https://github.com/jhalterman/typetools/blob/master/src/main/java/net/jodah/typetools/TypeResolver.java">TypeResolver</a>
* from <a href="https://github.com/jhalterman/typetools">Typetools</a>.
*
* @author Frantisek Hartman
* @author Michael J. Simons
*/
public class GenericUtils {
public final class GenericUtils {

/**
* Helper to check whether a given field is a generic field (A field described by a type variable).
*
* @param field The field to check for a type variable
* @return True, if {@code field} is a generic field.
*/
public static boolean isGenericField(Field field) {
return field.getGenericType() instanceof TypeVariable;
}

/**
* Tries to discover type of given field
Expand All @@ -35,47 +57,193 @@ public class GenericUtils {
*/
public static Class findFieldType(Field field, Class concreteClass) {

if (field.getGenericType() instanceof Class) {
Class<?>[] arguments = resolveRawArguments(field.getGenericType(), concreteClass);
if (arguments == null || arguments.length == 0 || arguments[0] == Unknown.class) {
return field.getType();
}

TypeVariable[] typeParameters = field.getDeclaringClass().getTypeParameters();
for (int i = 0; i < typeParameters.length; i++) {
if (typeParameters[i].getName().equals(field.getGenericType().getTypeName())) {
return arguments[0];
}

ParameterizedType genericSuperclass = findMatchingSuperclass(concreteClass, field);
if (genericSuperclass != null) {
return (Class) genericSuperclass.getActualTypeArguments()[i];
}
}
/**
* Returns an array of raw classes representing arguments for the {@code genericType} using type variable information
* from the {@code subType}. Arguments for {@code genericType} that cannot be resolved are returned as
* {@code Unknown.class}. If no arguments can be resolved then {@code null} is returned.
*
* @param genericType to resolve arguments for
* @param subType to extract type variable information from
* @return array of raw classes representing arguments for the {@code genericType} else {@code null} if no type
* arguments are declared
*/
private static Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
Class<?>[] result = null;
Class<?> functionalInterface = null;

// Handle lambdas
if (subType.isSynthetic()) {
Class<?> fi = genericType instanceof ParameterizedType
&& ((ParameterizedType) genericType).getRawType() instanceof Class
? (Class<?>) ((ParameterizedType) genericType).getRawType()
: genericType instanceof Class ? (Class<?>) genericType : null;
if (fi != null && fi.isInterface())
functionalInterface = fi;
}

if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] arguments = paramType.getActualTypeArguments();
result = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++)
result[i] = resolveRawClass(arguments[i], subType, functionalInterface);
} else if (genericType instanceof TypeVariable) {
result = new Class[1];
result[0] = resolveRawClass(genericType, subType, functionalInterface);
} else if (genericType instanceof Class) {
TypeVariable<?>[] typeParams = ((Class<?>) genericType).getTypeParameters();
result = new Class[typeParams.length];
for (int i = 0; i < typeParams.length; i++)
result[i] = resolveRawClass(typeParams[i], subType, functionalInterface);
}

return result;
}

private static Class<?> resolveRawClass(Type genericType, Class<?> subType, Class<?> functionalInterface) {
if (genericType instanceof Class) {
return (Class<?>) genericType;
} else if (genericType instanceof ParameterizedType) {
return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType, functionalInterface);
} else if (genericType instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) genericType;
Class<?> component = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface);
return Array.newInstance(component, 0).getClass();
} else if (genericType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) genericType;
genericType = getTypeVariableMap(subType, functionalInterface).get(variable);
genericType = genericType == null ? resolveBound(variable)
: resolveRawClass(genericType, subType, functionalInterface);
}

return genericType instanceof Class ? (Class<?>) genericType : Unknown.class;
}

private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType,
Class<?> functionalInterface) {
Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>();

// Populate interfaces
populateSuperTypeArgs(targetType.getGenericInterfaces(), map, functionalInterface != null);

// Populate super classes and interfaces
Type genericType = targetType.getGenericSuperclass();
Class<?> type = targetType.getSuperclass();
while (type != null && !Object.class.equals(type)) {
if (genericType instanceof ParameterizedType)
populateTypeArgs((ParameterizedType) genericType, map, false);
populateSuperTypeArgs(type.getGenericInterfaces(), map, false);

genericType = type.getGenericSuperclass();
type = type.getSuperclass();
}

// Populate enclosing classes
type = targetType;
while (type.isMemberClass()) {
genericType = type.getGenericSuperclass();
if (genericType instanceof ParameterizedType)
populateTypeArgs((ParameterizedType) genericType, map, functionalInterface != null);

type = type.getEnclosingClass();
}

return field.getType();
return map;
}

/**
* Find a generic superclass of given class that matches declaring class of given field
*
* @param clazz concrete class
* @param field field
* @return superclass as ParameterizedType
* Populates the {@code map} with with variable/argument pairs for the given {@code types}.
*/
private static ParameterizedType findMatchingSuperclass(Class clazz, Field field) {
Type superclass = clazz.getGenericSuperclass();
if (superclass == null) {
return null;
private static void populateSuperTypeArgs(final Type[] types, final Map<TypeVariable<?>, Type> map,
boolean depthFirst) {
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!depthFirst)
populateTypeArgs(parameterizedType, map, depthFirst);
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class)
populateSuperTypeArgs(((Class<?>) rawType).getGenericInterfaces(), map, depthFirst);
if (depthFirst)
populateTypeArgs(parameterizedType, map, depthFirst);
} else if (type instanceof Class) {
populateSuperTypeArgs(((Class<?>) type).getGenericInterfaces(), map, depthFirst);
}
}
}

/**
* Populates the {@code map} with variable/argument pairs for the given {@code type}.
*/
private static void populateTypeArgs(ParameterizedType type, Map<TypeVariable<?>, Type> map, boolean depthFirst) {
if (type.getRawType() instanceof Class) {
TypeVariable<?>[] typeVariables = ((Class<?>) type.getRawType()).getTypeParameters();
Type[] typeArguments = type.getActualTypeArguments();

if (type.getOwnerType() != null) {
Type owner = type.getOwnerType();
if (owner instanceof ParameterizedType)
populateTypeArgs((ParameterizedType) owner, map, depthFirst);
}

for (int i = 0; i < typeArguments.length; i++) {
TypeVariable<?> variable = typeVariables[i];
Type typeArgument = typeArguments[i];

if (superclass instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) superclass;
if (paramType.getRawType().equals(field.getDeclaringClass())) {
return paramType;
} else {
return findMatchingSuperclass(clazz.getSuperclass(), field);
if (typeArgument instanceof Class) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof GenericArrayType) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof ParameterizedType) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof TypeVariable) {
TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument;
if (depthFirst) {
Type existingType = map.get(variable);
if (existingType != null) {
map.put(typeVariableArgument, existingType);
continue;
}
}

Type resolvedType = map.get(typeVariableArgument);
if (resolvedType == null)
resolvedType = resolveBound(typeVariableArgument);
map.put(variable, resolvedType);
}
}
} else {
return findMatchingSuperclass(clazz.getSuperclass(), field);
}
}

/**
* Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved.
*/
private static Type resolveBound(TypeVariable<?> typeVariable) {
Type[] bounds = typeVariable.getBounds();
if (bounds.length == 0)
return Unknown.class;

Type bound = bounds[0];
if (bound instanceof TypeVariable)
bound = resolveBound((TypeVariable<?>) bound);

return bound == Object.class ? Unknown.class : bound;
}

/**
* An unknown type.
*/
private static final class Unknown {
}

private GenericUtils() {
}
}

0 comments on commit b60ed67

Please sign in to comment.