Added external dependencies as appropriate
This commit is contained in:
parent
5da8dde1a6
commit
3078279ab1
@ -17,6 +17,8 @@ package dorkbox.util.generics;
|
|||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
import net.jodah.typetools.TypeResolver;
|
||||||
|
|
||||||
public final
|
public final
|
||||||
class ClassHelper {
|
class ClassHelper {
|
||||||
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018 dorkbox, llc.
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.generics;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class must be compiled for java 8+, and is needed by the TypeResolver in some situations
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class DefaultMethodHelper {
|
|
||||||
@SuppressWarnings("Since15")
|
|
||||||
static
|
|
||||||
boolean isDefaultMethod(Method m) {
|
|
||||||
return m.isDefault();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,612 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018 dorkbox, llc.
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.generics;
|
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.lang.reflect.AccessibleObject;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.GenericArrayType;
|
|
||||||
import java.lang.reflect.Member;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.lang.reflect.TypeVariable;
|
|
||||||
import java.security.AccessController;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import dorkbox.util.OS;
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhanced type resolution utilities.
|
|
||||||
*
|
|
||||||
* From: https://github.com/jhalterman/typetools
|
|
||||||
* @author Jonathan Halterman
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("restriction")
|
|
||||||
public final
|
|
||||||
class TypeResolver {
|
|
||||||
/**
|
|
||||||
* Cache of type variable/argument pairs
|
|
||||||
*/
|
|
||||||
private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> TYPE_VARIABLE_CACHE = Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>());
|
|
||||||
|
|
||||||
private static volatile boolean CACHE_ENABLED = true;
|
|
||||||
private static boolean RESOLVES_LAMBDAS;
|
|
||||||
|
|
||||||
private static Method GET_CONSTANT_POOL;
|
|
||||||
private static Method GET_CONSTANT_POOL_SIZE;
|
|
||||||
private static Method GET_CONSTANT_POOL_METHOD_AT;
|
|
||||||
|
|
||||||
private static final Map<String, Method> OBJECT_METHODS = new HashMap<String, Method>();
|
|
||||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPERS;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
Unsafe unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<Unsafe>() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Unsafe run() throws Exception {
|
|
||||||
final Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
|
||||||
f.setAccessible(true);
|
|
||||||
|
|
||||||
return (Unsafe) f.get(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool");
|
|
||||||
String constantPoolName = OS.javaVersion < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool";
|
|
||||||
Class<?> constantPoolClass = Class.forName(constantPoolName);
|
|
||||||
GET_CONSTANT_POOL_SIZE = constantPoolClass.getDeclaredMethod("getSize");
|
|
||||||
GET_CONSTANT_POOL_METHOD_AT = constantPoolClass.getDeclaredMethod("getMethodAt", int.class);
|
|
||||||
|
|
||||||
// setting the methods as accessible
|
|
||||||
Field overrideField = AccessibleObject.class.getDeclaredField("override");
|
|
||||||
long overrideFieldOffset = unsafe.objectFieldOffset(overrideField);
|
|
||||||
unsafe.putBoolean(GET_CONSTANT_POOL, overrideFieldOffset, true);
|
|
||||||
unsafe.putBoolean(GET_CONSTANT_POOL_SIZE, overrideFieldOffset, true);
|
|
||||||
unsafe.putBoolean(GET_CONSTANT_POOL_METHOD_AT, overrideFieldOffset, true);
|
|
||||||
|
|
||||||
// additional checks - make sure we get a result when invoking the Class::getConstantPool and
|
|
||||||
// ConstantPool::getSize on a class
|
|
||||||
Object constantPool = GET_CONSTANT_POOL.invoke(Object.class);
|
|
||||||
GET_CONSTANT_POOL_SIZE.invoke(constantPool);
|
|
||||||
|
|
||||||
for (Method method : Object.class.getDeclaredMethods()) {
|
|
||||||
OBJECT_METHODS.put(method.getName(), method);
|
|
||||||
}
|
|
||||||
|
|
||||||
RESOLVES_LAMBDAS = true;
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<Class<?>, Class<?>> types = new HashMap<Class<?>, Class<?>>();
|
|
||||||
types.put(boolean.class, Boolean.class);
|
|
||||||
types.put(byte.class, Byte.class);
|
|
||||||
types.put(char.class, Character.class);
|
|
||||||
types.put(double.class, Double.class);
|
|
||||||
types.put(float.class, Float.class);
|
|
||||||
types.put(int.class, Integer.class);
|
|
||||||
types.put(long.class, Long.class);
|
|
||||||
types.put(short.class, Short.class);
|
|
||||||
types.put(void.class, Void.class);
|
|
||||||
PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(types);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An unknown type.
|
|
||||||
*/
|
|
||||||
public static final
|
|
||||||
class Unknown {
|
|
||||||
private
|
|
||||||
Unknown() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables the internal caching of resolved TypeVariables. (Default is true)
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
void enableCache() {
|
|
||||||
CACHE_ENABLED = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables the internal caching of resolved TypeVariables. (Default is true)
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
void disableCache() {
|
|
||||||
TYPE_VARIABLE_CACHE.clear();
|
|
||||||
CACHE_ENABLED = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the raw class representing the argument for the {@code type} using type variable information from the
|
|
||||||
* {@code subType}. If no arguments can be resolved then {@code Unknown.class} is returned.
|
|
||||||
*
|
|
||||||
* @param type to resolve argument for
|
|
||||||
* @param subType to extract type variable information from
|
|
||||||
*
|
|
||||||
* @return argument for {@code type} else {@link Unknown}.class if no type arguments are declared
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if more or less than one argument is resolved for the {@code type}
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
<T, S extends T> Class<?> resolveRawArgument(Class<T> type, Class<S> subType) {
|
|
||||||
return resolveRawArgument(resolveGenericType(type, subType), subType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the raw class representing the argument for the {@code genericType} using type variable information from
|
|
||||||
* the {@code subType}. If {@code genericType} is an instance of class, then {@code genericType} is returned. If no
|
|
||||||
* arguments can be resolved then {@code Unknown.class} is returned.
|
|
||||||
*
|
|
||||||
* @param genericType to resolve argument for
|
|
||||||
* @param subType to extract type variable information from
|
|
||||||
*
|
|
||||||
* @return argument for {@code genericType} else {@link Unknown}.class if no type arguments are declared
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if more or less than one argument is resolved for the {@code genericType}
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
Class<?> resolveRawArgument(Type genericType, Class<?> subType) {
|
|
||||||
Class<?>[] arguments = resolveRawArguments(genericType, subType);
|
|
||||||
if (arguments == null) {
|
|
||||||
return Unknown.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length != 1) {
|
|
||||||
throw new IllegalArgumentException("Expected 1 argument for generic type " + genericType + " but found " + arguments.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return arguments[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of raw classes representing arguments for the {@code type} using type variable information from
|
|
||||||
* the {@code subType}. Arguments for {@code type} that cannot be resolved are returned as {@code Unknown.class}. If
|
|
||||||
* no arguments can be resolved then {@code null} is returned.
|
|
||||||
*
|
|
||||||
* @param type to resolve arguments for
|
|
||||||
* @param subType to extract type variable information from
|
|
||||||
*
|
|
||||||
* @return array of raw classes representing arguments for the {@code type} else {@code null} if no type arguments are
|
|
||||||
* declared
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
<T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Class<S> subType) {
|
|
||||||
return resolveRawArguments(resolveGenericType(type, subType), subType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
|
|
||||||
Class<?>[] result = null;
|
|
||||||
Class<?> functionalInterface = null;
|
|
||||||
|
|
||||||
// Handle lambdas
|
|
||||||
if (RESOLVES_LAMBDAS && 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the generic {@code type} using type variable information from the {@code subType} else {@code null} if the
|
|
||||||
* generic type cannot be resolved.
|
|
||||||
*
|
|
||||||
* @param type to resolve generic type for
|
|
||||||
* @param subType to extract type variable information from
|
|
||||||
*
|
|
||||||
* @return generic {@code type} else {@code null} if it cannot be resolved
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
Type resolveGenericType(Class<?> type, Type subType) {
|
|
||||||
Class<?> rawType;
|
|
||||||
if (subType instanceof ParameterizedType) {
|
|
||||||
rawType = (Class<?>) ((ParameterizedType) subType).getRawType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rawType = (Class<?>) subType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type.equals(rawType)) {
|
|
||||||
return subType;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type result;
|
|
||||||
if (type.isInterface()) {
|
|
||||||
for (Type superInterface : rawType.getGenericInterfaces()) {
|
|
||||||
if (superInterface != null && !superInterface.equals(Object.class)) {
|
|
||||||
if ((result = resolveGenericType(type, superInterface)) != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type superClass = rawType.getGenericSuperclass();
|
|
||||||
if (superClass != null && !superClass.equals(Object.class)) {
|
|
||||||
if ((result = resolveGenericType(type, superClass)) != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the raw class for the {@code genericType}, using the type variable information from the {@code subType}
|
|
||||||
* else {@link Unknown} if the raw class cannot be resolved.
|
|
||||||
*
|
|
||||||
* @param genericType to resolve raw class for
|
|
||||||
* @param subType to extract type variable information from
|
|
||||||
*
|
|
||||||
* @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
Class<?> resolveRawClass(Type genericType, Class<?> subType) {
|
|
||||||
return resolveRawClass(genericType, subType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
Reference<Map<TypeVariable<?>, Type>> ref = TYPE_VARIABLE_CACHE.get(targetType);
|
|
||||||
Map<TypeVariable<?>, Type> map = ref != null ? ref.get() : null;
|
|
||||||
|
|
||||||
if (map == null) {
|
|
||||||
map = new HashMap<TypeVariable<?>, Type>();
|
|
||||||
|
|
||||||
// Populate lambdas
|
|
||||||
if (functionalInterface != null) {
|
|
||||||
populateLambdaArgs(functionalInterface, targetType, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CACHE_ENABLED) {
|
|
||||||
TYPE_VARIABLE_CACHE.put(targetType, new WeakReference<Map<TypeVariable<?>, Type>>(map));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the {@code map} with with variable/argument pairs for the given {@code types}.
|
|
||||||
*/
|
|
||||||
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 (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved.
|
|
||||||
*/
|
|
||||||
public 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the {@code map} with variable/argument pairs for the {@code functionalInterface}.
|
|
||||||
*/
|
|
||||||
private static
|
|
||||||
void populateLambdaArgs(Class<?> functionalInterface, final Class<?> lambdaType, Map<TypeVariable<?>, Type> map) {
|
|
||||||
if (RESOLVES_LAMBDAS) {
|
|
||||||
// Find SAM
|
|
||||||
for (Method m : functionalInterface.getMethods()) {
|
|
||||||
if (!isDefaultMethod(m) && !Modifier.isStatic(m.getModifiers()) && !m.isBridge()) {
|
|
||||||
// Skip methods that override Object.class
|
|
||||||
Method objectMethod = OBJECT_METHODS.get(m.getName());
|
|
||||||
if (objectMethod != null && Arrays.equals(m.getTypeParameters(), objectMethod.getTypeParameters())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get functional interface's type params
|
|
||||||
Type returnTypeVar = m.getGenericReturnType();
|
|
||||||
Type[] paramTypeVars = m.getGenericParameterTypes();
|
|
||||||
|
|
||||||
Member member = getMemberRef(lambdaType);
|
|
||||||
if (member == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate return type argument
|
|
||||||
if (returnTypeVar instanceof TypeVariable) {
|
|
||||||
Class<?> returnType = member instanceof Method
|
|
||||||
? ((Method) member).getReturnType()
|
|
||||||
: ((Constructor<?>) member).getDeclaringClass();
|
|
||||||
returnType = wrapPrimitives(returnType);
|
|
||||||
if (!returnType.equals(Void.class)) {
|
|
||||||
map.put((TypeVariable<?>) returnTypeVar, returnType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<?>[] arguments = member instanceof Method
|
|
||||||
? ((Method) member).getParameterTypes()
|
|
||||||
: ((Constructor<?>) member).getParameterTypes();
|
|
||||||
|
|
||||||
// Populate object type from arbitrary object method reference
|
|
||||||
int paramOffset = 0;
|
|
||||||
if (paramTypeVars.length > 0 && paramTypeVars[0] instanceof TypeVariable &&
|
|
||||||
paramTypeVars.length == arguments.length + 1) {
|
|
||||||
Class<?> instanceType = member.getDeclaringClass();
|
|
||||||
map.put((TypeVariable<?>) paramTypeVars[0], instanceType);
|
|
||||||
paramOffset = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle additional arguments that are captured from the lambda's enclosing scope
|
|
||||||
int argOffset = 0;
|
|
||||||
if (paramTypeVars.length < arguments.length) {
|
|
||||||
argOffset = arguments.length - paramTypeVars.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate type arguments
|
|
||||||
for (int i = 0; i + argOffset < arguments.length; i++) {
|
|
||||||
if (paramTypeVars[i] instanceof TypeVariable) {
|
|
||||||
map.put((TypeVariable<?>) paramTypeVars[i + paramOffset], wrapPrimitives(arguments[i + argOffset]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
boolean isDefaultMethod(Method m) {
|
|
||||||
return OS.javaVersion >= 8 && DefaultMethodHelper.isDefaultMethod(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
Member getMemberRef(Class<?> type) {
|
|
||||||
Object constantPool;
|
|
||||||
try {
|
|
||||||
constantPool = GET_CONSTANT_POOL.invoke(type);
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Member result = null;
|
|
||||||
for (int i = getConstantPoolSize(constantPool) - 1; i >= 0; i--) {
|
|
||||||
Member member = getConstantPoolMethodAt(constantPool, i);
|
|
||||||
// Skip SerializedLambda constructors and members of the "type" class
|
|
||||||
if (member == null || (member instanceof Constructor && member.getDeclaringClass()
|
|
||||||
.getName()
|
|
||||||
.equals("java.lang.invoke.SerializedLambda")) ||
|
|
||||||
member.getDeclaringClass()
|
|
||||||
.isAssignableFrom(type)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = member;
|
|
||||||
|
|
||||||
// Return if not valueOf method
|
|
||||||
if (!(member instanceof Method) || !isAutoBoxingMethod((Method) member)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
boolean isAutoBoxingMethod(Method method) {
|
|
||||||
Class<?>[] parameters = method.getParameterTypes();
|
|
||||||
return method.getName()
|
|
||||||
.equals("valueOf") && parameters.length == 1 && parameters[0].isPrimitive() && wrapPrimitives(parameters[0]).equals(
|
|
||||||
method.getDeclaringClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
Class<?> wrapPrimitives(Class<?> clazz) {
|
|
||||||
return clazz.isPrimitive() ? PRIMITIVE_WRAPPERS.get(clazz) : clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
int getConstantPoolSize(Object constantPool) {
|
|
||||||
try {
|
|
||||||
return (Integer) GET_CONSTANT_POOL_SIZE.invoke(constantPool);
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static
|
|
||||||
Member getConstantPoolMethodAt(Object constantPool, int i) {
|
|
||||||
try {
|
|
||||||
return (Member) GET_CONSTANT_POOL_METHOD_AT.invoke(constantPool, i);
|
|
||||||
} catch (Exception ignore) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
TypeResolver() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Martin Grotzke
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.serialization;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo;
|
|
||||||
import com.esotericsoftware.kryo.Serializer;
|
|
||||||
import com.esotericsoftware.kryo.io.Input;
|
|
||||||
import com.esotericsoftware.kryo.io.Output;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A kryo {@link Serializer} for lists created via {@link Arrays#asList(Object...)}.
|
|
||||||
* <p>
|
|
||||||
* Note: This serializer does not support cyclic references, so if one of the objects
|
|
||||||
* gets set the list as attribute this might cause an error during deserialization.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class ArraysAsListSerializer extends Serializer<List<?>> {
|
|
||||||
|
|
||||||
private static
|
|
||||||
Class<?> getPrimitiveWrapperClass(final Class<?> c) {
|
|
||||||
if (c.isPrimitive()) {
|
|
||||||
if (c.equals(Long.TYPE)) {
|
|
||||||
return Long.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Integer.TYPE)) {
|
|
||||||
return Integer.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Double.TYPE)) {
|
|
||||||
return Double.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Float.TYPE)) {
|
|
||||||
return Float.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Boolean.TYPE)) {
|
|
||||||
return Boolean.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Character.TYPE)) {
|
|
||||||
return Character.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Short.TYPE)) {
|
|
||||||
return Short.class;
|
|
||||||
}
|
|
||||||
else if (c.equals(Byte.TYPE)) {
|
|
||||||
return Byte.class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
private Field _arrayField;
|
|
||||||
|
|
||||||
public
|
|
||||||
ArraysAsListSerializer() {
|
|
||||||
try {
|
|
||||||
_arrayField = Class.forName("java.util.Arrays$ArrayList")
|
|
||||||
.getDeclaredField("a");
|
|
||||||
_arrayField.setAccessible(true);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
// Immutable causes #copy(obj) to return the original object
|
|
||||||
setImmutable(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void write(final Kryo kryo, final Output output, final List<?> obj) {
|
|
||||||
try {
|
|
||||||
final Object[] array = (Object[]) _arrayField.get(obj);
|
|
||||||
output.writeInt(array.length, true);
|
|
||||||
final Class<?> componentType = array.getClass()
|
|
||||||
.getComponentType();
|
|
||||||
kryo.writeClass(output, componentType);
|
|
||||||
for (final Object item : array) {
|
|
||||||
kryo.writeClassAndObject(output, item);
|
|
||||||
}
|
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
// Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write...
|
|
||||||
// handles SerializationException specifically (resizing the buffer)...
|
|
||||||
throw e;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
List<?> read(final Kryo kryo, final Input input, final Class<List<?>> type) {
|
|
||||||
final int length = input.readInt(true);
|
|
||||||
Class<?> componentType = kryo.readClass(input)
|
|
||||||
.getType();
|
|
||||||
if (componentType.isPrimitive()) {
|
|
||||||
componentType = getPrimitiveWrapperClass(componentType);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final Object items = Array.newInstance(componentType, length);
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
Array.set(items, i, kryo.readClassAndObject(input));
|
|
||||||
}
|
|
||||||
return Arrays.asList((Object[]) items);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Martin Grotzke
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.serialization;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo;
|
|
||||||
import com.esotericsoftware.kryo.Serializer;
|
|
||||||
import com.esotericsoftware.kryo.factories.SerializerFactory;
|
|
||||||
import com.esotericsoftware.kryo.serializers.FieldSerializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A kryo {@link FieldSerializer} that allows to exclusively include or exclude fields that
|
|
||||||
* are attributed with user-specific annotations. This can be for example useful when serializing beans that carry
|
|
||||||
* references to a dependency injection framework. As an example for Spring:
|
|
||||||
* <p/>
|
|
||||||
* <pre>
|
|
||||||
* {@code
|
|
||||||
* Set<Class<? extends Annotation>> marks = new HashSet<>();
|
|
||||||
* marks.add(Autowired.class);
|
|
||||||
* SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
|
|
||||||
* Kryo kryo = new Kryo();
|
|
||||||
* kryo.setDefaultSerializer(factory);
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
* <p/>
|
|
||||||
* The resulting {@link Kryo} instance would ignore all fields that are annotated with Spring's {@code @Autowired}
|
|
||||||
* annotation.
|
|
||||||
* <p/>
|
|
||||||
* Similarly, it is possible to created a serializer which does the opposite such that the resulting serializer
|
|
||||||
* would only serialize fields that are annotated with the specified annotations.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:rafael.wth@web.de">Rafael Winterhalter</a>
|
|
||||||
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class FieldAnnotationAwareSerializer<T> extends FieldSerializer<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory for creating instances of {@link FieldAnnotationAwareSerializer}.
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
class Factory implements SerializerFactory {
|
|
||||||
|
|
||||||
private final Collection<Class<? extends Annotation>> marked;
|
|
||||||
private final boolean disregarding;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new factory. See {@link FieldAnnotationAwareSerializer#FieldAnnotationAwareSerializer(
|
|
||||||
*com.esotericsoftware.kryo.Kryo, Class, java.util.Collection, boolean)}
|
|
||||||
* for additional information on the constructor parameters.
|
|
||||||
*
|
|
||||||
* @param marked The annotations that will be considered of the resulting converter.
|
|
||||||
* @param disregarding If {@code true}, the serializer will ignore all annotated fields,
|
|
||||||
* if set to {@code false} it will exclusively look at annotated fields.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
Factory(final Collection<Class<? extends Annotation>> marked, final boolean disregarding) {
|
|
||||||
this.marked = marked;
|
|
||||||
this.disregarding = disregarding;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Serializer<?> makeSerializer(final Kryo kryo, final Class<?> type) {
|
|
||||||
return new FieldAnnotationAwareSerializer<Object>(kryo, type, this.marked, this.disregarding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Set<Class<? extends Annotation>> marked;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether annotated fields should be excluded from serialization.
|
|
||||||
* <p/>
|
|
||||||
* {@code true} if annotated fields should be excluded from serialization,
|
|
||||||
* {@code false} if only annotated fields should be included from serialization.
|
|
||||||
*/
|
|
||||||
private final boolean disregarding;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new field annotation aware serializer.
|
|
||||||
*
|
|
||||||
* @param kryo The {@link Kryo} instace.
|
|
||||||
* @param type The type of the class being serialized.
|
|
||||||
* @param marked The annotations this serializer considers for its serialization process. Be aware tha
|
|
||||||
* a serializer with {@code disregarding} set to {@code false} will never be able to
|
|
||||||
* serialize fields that are not annotated with any of these annotations since it is not
|
|
||||||
* possible to add fields to a {@link FieldSerializer} once it is created. See the
|
|
||||||
* documentation to {@link FieldAnnotationAwareSerializer#addAnnotation(Class)} and
|
|
||||||
* {@link FieldAnnotationAwareSerializer#removeAnnotation(Class)} for further information.
|
|
||||||
* @param disregarding If {@code true}, the serializer will ignore all annotated fields,
|
|
||||||
* if set to {@code false} it will exclusively look at annotated fields.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
FieldAnnotationAwareSerializer(final Kryo kryo,
|
|
||||||
final Class<?> type,
|
|
||||||
final Collection<Class<? extends Annotation>> marked,
|
|
||||||
final boolean disregarding) {
|
|
||||||
super(kryo, type);
|
|
||||||
this.disregarding = disregarding;
|
|
||||||
this.marked = new HashSet<Class<? extends Annotation>>(marked);
|
|
||||||
rebuildCachedFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
void rebuildCachedFields() {
|
|
||||||
// In order to avoid rebuilding the cached fields twice, the super constructor's call
|
|
||||||
// to this method will be suppressed. This can be done by a simple check of the initialization
|
|
||||||
// state of a property of this subclass.
|
|
||||||
if (this.marked == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
super.rebuildCachedFields();
|
|
||||||
removeFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void removeFields() {
|
|
||||||
final CachedField<?>[] cachedFields = getFields();
|
|
||||||
for (final CachedField<?> cachedField : cachedFields) {
|
|
||||||
final Field field = cachedField.getField();
|
|
||||||
if (isRemove(field)) {
|
|
||||||
// if (TRACE) {
|
|
||||||
// trace("kryo", String.format("Ignoring field %s tag: %s", this.disregarding ? "without" : "with", cachedField));
|
|
||||||
// }
|
|
||||||
super.removeField(field.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
boolean isRemove(final Field field) {
|
|
||||||
return !isMarked(field) ^ this.disregarding;
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
boolean isMarked(final Field field) {
|
|
||||||
for (final Annotation annotation : field.getAnnotations()) {
|
|
||||||
final Class<? extends Annotation> annotationType = annotation.annotationType();
|
|
||||||
if (this.marked.contains(annotationType)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an annotation to the annotations that are considered by this serializer.
|
|
||||||
* <p/>
|
|
||||||
* <b>Important</b>: This will not have an effect if the serializer was configured
|
|
||||||
* to exclusively serialize annotated fields by setting {@code disregarding} to
|
|
||||||
* {@code false}. This is similar to the contract of this serializer's superclass
|
|
||||||
* {@link FieldSerializer} which does not allow to add fields that were formerly
|
|
||||||
* removed. If this was possible, instances that were serialized before this field
|
|
||||||
* was added could not longer be properly deserialized. In order to make this contract
|
|
||||||
* break explicit, you need to create a new instance of this serializer if you want to
|
|
||||||
* include new fields to a serializer that exclusively serializes annotated fields.
|
|
||||||
*
|
|
||||||
* @param clazz The annotation class to be added.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the method call had an effect.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean addAnnotation(final Class<? extends Annotation> clazz) {
|
|
||||||
if (this.disregarding && this.marked.add(clazz)) {
|
|
||||||
initializeCachedFields();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an annotation to the annotations that are considered by this serializer.
|
|
||||||
* <p/>
|
|
||||||
* <b>Important</b>: This will not have an effect if the serializer was configured
|
|
||||||
* to not serialize annotated fields by setting {@code disregarding} to
|
|
||||||
* {@code true}. This is similar to the contract of this serializer's superclass
|
|
||||||
* {@link FieldSerializer} which does not allow to add fields that were formerly
|
|
||||||
* removed. If this was possible, instances that were serialized before this field
|
|
||||||
* was added could not longer be properly deserialized. In order to make this contract
|
|
||||||
* break explicit, you need to create a new instance of this serializer if you want to
|
|
||||||
* include new fields to a serializer that ignores annotated fields for serialization.
|
|
||||||
*
|
|
||||||
* @param clazz The annotation class to be removed.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the method call had an effect.
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean removeAnnotation(final Class<? extends Annotation> clazz) {
|
|
||||||
if (!this.disregarding && this.marked.remove(clazz)) {
|
|
||||||
initializeCachedFields();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ class FileSerializer extends Serializer<File> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
File read(Kryo kryo, Input input, Class<File> type) {
|
File read(final Kryo kryo, final Input input, final Class<? extends File> type) {
|
||||||
String path = input.readString();
|
String path = input.readString();
|
||||||
return new File(path);
|
return new File(path);
|
||||||
}
|
}
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Martin Grotzke
|
|
||||||
*
|
|
||||||
* 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 dorkbox.util.serialization;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import com.esotericsoftware.kryo.Kryo;
|
|
||||||
import com.esotericsoftware.kryo.Serializer;
|
|
||||||
import com.esotericsoftware.kryo.io.Input;
|
|
||||||
import com.esotericsoftware.kryo.io.Output;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A kryo {@link Serializer} for unmodifiable {@link Collection}s and {@link Map}s
|
|
||||||
* created via {@link Collections}.
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
class UnmodifiableCollectionsSerializer extends Serializer<Object> {
|
|
||||||
|
|
||||||
private
|
|
||||||
enum UnmodifiableCollection {
|
|
||||||
COLLECTION(Collections.unmodifiableCollection(Arrays.asList(""))
|
|
||||||
.getClass(), SOURCE_COLLECTION_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableCollection((Collection<?>) sourceCollection);
|
|
||||||
}
|
|
||||||
}, RANDOM_ACCESS_LIST(Collections.unmodifiableList(new ArrayList<Void>())
|
|
||||||
.getClass(), SOURCE_COLLECTION_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableList((List<?>) sourceCollection);
|
|
||||||
}
|
|
||||||
}, LIST(Collections.unmodifiableList(new LinkedList<Void>())
|
|
||||||
.getClass(), SOURCE_COLLECTION_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableList((List<?>) sourceCollection);
|
|
||||||
}
|
|
||||||
}, SET(Collections.unmodifiableSet(new HashSet<Void>())
|
|
||||||
.getClass(), SOURCE_COLLECTION_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableSet((Set<?>) sourceCollection);
|
|
||||||
}
|
|
||||||
}, SORTED_SET(Collections.unmodifiableSortedSet(new TreeSet<Void>())
|
|
||||||
.getClass(), SOURCE_COLLECTION_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableSortedSet((SortedSet<?>) sourceCollection);
|
|
||||||
}
|
|
||||||
}, MAP(Collections.unmodifiableMap(new HashMap<Void, Void>())
|
|
||||||
.getClass(), SOURCE_MAP_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableMap((Map<?, ?>) sourceCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, SORTED_MAP(Collections.unmodifiableSortedMap(new TreeMap<Void, Void>())
|
|
||||||
.getClass(), SOURCE_MAP_FIELD) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object create(final Object sourceCollection) {
|
|
||||||
return Collections.unmodifiableSortedMap((SortedMap<?, ?>) sourceCollection);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static
|
|
||||||
UnmodifiableCollection valueOfType(final Class<?> type) {
|
|
||||||
for (final UnmodifiableCollection item : values()) {
|
|
||||||
if (item.type.equals(type)) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("The type " + type + " is not supported.");
|
|
||||||
}
|
|
||||||
private final Class<?> type;
|
|
||||||
private final Field sourceCollectionField;
|
|
||||||
|
|
||||||
UnmodifiableCollection(final Class<?> type, final Field sourceCollectionField) {
|
|
||||||
this.type = type;
|
|
||||||
this.sourceCollectionField = sourceCollectionField;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract
|
|
||||||
Object create(Object sourceCollection);
|
|
||||||
|
|
||||||
}
|
|
||||||
private static final Field SOURCE_COLLECTION_FIELD;
|
|
||||||
private static final Field SOURCE_MAP_FIELD;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
SOURCE_COLLECTION_FIELD = Class.forName("java.util.Collections$UnmodifiableCollection")
|
|
||||||
.getDeclaredField("c");
|
|
||||||
SOURCE_COLLECTION_FIELD.setAccessible(true);
|
|
||||||
|
|
||||||
|
|
||||||
SOURCE_MAP_FIELD = Class.forName("java.util.Collections$UnmodifiableMap")
|
|
||||||
.getDeclaredField("m");
|
|
||||||
SOURCE_MAP_FIELD.setAccessible(true);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException("Could not access source collection" + " field in java.util.Collections$UnmodifiableCollection.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@link UnmodifiableCollectionsSerializer} and registers its serializer
|
|
||||||
* for the several unmodifiable Collections that can be created via {@link Collections},
|
|
||||||
* including {@link Map}s.
|
|
||||||
*
|
|
||||||
* @param manager the {@link SerializationManager} instance to set the serializer on.
|
|
||||||
*
|
|
||||||
* @see Collections#unmodifiableCollection(Collection)
|
|
||||||
* @see Collections#unmodifiableList(List)
|
|
||||||
* @see Collections#unmodifiableSet(Set)
|
|
||||||
* @see Collections#unmodifiableSortedSet(SortedSet)
|
|
||||||
* @see Collections#unmodifiableMap(Map)
|
|
||||||
* @see Collections#unmodifiableSortedMap(SortedMap)
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
void registerSerializers(final SerializationManager manager) {
|
|
||||||
final UnmodifiableCollectionsSerializer serializer = new UnmodifiableCollectionsSerializer();
|
|
||||||
UnmodifiableCollection.values();
|
|
||||||
for (final UnmodifiableCollection item : UnmodifiableCollection.values()) {
|
|
||||||
manager.register(item.type, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void write(final Kryo kryo, final Output output, final Object object) {
|
|
||||||
try {
|
|
||||||
final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.valueOfType(object.getClass());
|
|
||||||
// the ordinal could be replaced by s.th. else (e.g. a explicitely managed "id")
|
|
||||||
output.writeInt(unmodifiableCollection.ordinal(), true);
|
|
||||||
kryo.writeClassAndObject(output, unmodifiableCollection.sourceCollectionField.get(object));
|
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
// Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write...
|
|
||||||
// handles SerializationException specifically (resizing the buffer)...
|
|
||||||
throw e;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object read(final Kryo kryo, final Input input, final Class<Object> clazz) {
|
|
||||||
final int ordinal = input.readInt(true);
|
|
||||||
final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.values()[ordinal];
|
|
||||||
final Object sourceCollection = kryo.readClassAndObject(input);
|
|
||||||
return unmodifiableCollection.create(sourceCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Object copy(Kryo kryo, Object original) {
|
|
||||||
try {
|
|
||||||
final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.valueOfType(original.getClass());
|
|
||||||
Object sourceCollectionCopy = kryo.copy(unmodifiableCollection.sourceCollectionField.get(original));
|
|
||||||
return unmodifiableCollection.create(sourceCollectionCopy);
|
|
||||||
} catch (final RuntimeException e) {
|
|
||||||
// Don't eat and wrap RuntimeExceptions
|
|
||||||
throw e;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user