blob: 11e4530888c3b6280d0adede207e05ee7306f19d [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2017 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.jsonrpc.json.adapters;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import com.google.gson.reflect.TypeToken;
/**
* Utilities for handling types in the JSON parser / serializer.
*/
public final class TypeUtils {
private TypeUtils() {}
/**
* Determine the actual type arguments of the given type token with regard to the given target type.
*/
public static Type[] getElementTypes(TypeToken<?> typeToken, Class<?> targetType) {
return getElementTypes(typeToken.getType(), typeToken.getRawType(), targetType);
}
private static Type[] getElementTypes(Type type, Class<?> rawType, Class<?> targetType) {
if (targetType.equals(rawType) && type instanceof ParameterizedType) {
Type mappedType;
if (type instanceof ParameterizedTypeImpl)
mappedType = type;
else
// Transform wildcards in the actual type arguments
mappedType = getMappedType(type, Collections.emptyMap());
return ((ParameterizedType) mappedType).getActualTypeArguments();
}
// Map the parameters of the raw type to the actual type arguments
Map<String, Type> varMapping = createVariableMapping(type, rawType);
if (targetType.isInterface()) {
// Look for superinterfaces that extend the target interface
Class<?>[] interfaces = rawType.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (Collection.class.isAssignableFrom(interfaces[i])) {
Type genericInterface = rawType.getGenericInterfaces()[i];
Type mappedInterface = getMappedType(genericInterface, varMapping);
return getElementTypes(mappedInterface, interfaces[i], targetType);
}
}
}
if (!rawType.isInterface()) {
// Visit the superclass if it extends the target class / implements the target interface
Class<?> rawSupertype = rawType.getSuperclass();
if (targetType.isAssignableFrom(rawSupertype)) {
Type genericSuperclass = rawType.getGenericSuperclass();
Type mappedSuperclass = getMappedType(genericSuperclass, varMapping);
return getElementTypes(mappedSuperclass, rawSupertype, targetType);
}
}
// No luck, return an array of Object types
Type[] result = new Type[targetType.getTypeParameters().length];
Arrays.fill(result, Object.class);
return result;
}
private static <T> Map<String, Type> createVariableMapping(Type type, Class<T> rawType) {
if (type instanceof ParameterizedType) {
TypeVariable<Class<T>>[] vars = rawType.getTypeParameters();
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
Map<String, Type> newVarMapping = new HashMap<>(capacity(vars.length));
for (int i = 0; i < vars.length; i++) {
Type actualType = Object.class;
if (i < args.length) {
actualType = args[i];
if (actualType instanceof WildcardType)
actualType = ((WildcardType) actualType).getUpperBounds()[0];
}
newVarMapping.put(vars[i].getName(), actualType);
}
return newVarMapping;
}
return Collections.emptyMap();
}
private static int capacity(int expectedSize) {
if (expectedSize < 3)
return expectedSize + 1;
else
return expectedSize + expectedSize / 3;
}
private static Type getMappedType(Type type, Map<String, Type> varMapping) {
if (type instanceof TypeVariable) {
String name = ((TypeVariable<?>) type).getName();
if (varMapping.containsKey(name))
return varMapping.get(name);
}
if (type instanceof WildcardType) {
return getMappedType(((WildcardType) type).getUpperBounds()[0], varMapping);
}
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] origArgs = pt.getActualTypeArguments();
Type[] mappedArgs = new Type[origArgs.length];
for (int i = 0; i < origArgs.length; i++) {
mappedArgs[i] = getMappedType(origArgs[i], varMapping);
}
return new ParameterizedTypeImpl(pt, mappedArgs);
}
return type;
}
private static class ParameterizedTypeImpl implements ParameterizedType {
private final Type ownerType;
private final Type rawType;
private final Type[] actualTypeArguments;
ParameterizedTypeImpl(ParameterizedType original, Type[] typeArguments) {
this(original.getOwnerType(), original.getRawType(), typeArguments);
}
ParameterizedTypeImpl(Type ownerType, Type rawType, Type[] typeArguments) {
this.ownerType = ownerType;
this.rawType = rawType;
this.actualTypeArguments = typeArguments;
}
@Override
public Type getOwnerType() {
return ownerType;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
if (ownerType != null) {
result.append(toString(ownerType));
result.append('$');
}
result.append(toString(rawType));
result.append('<');
for (int i = 0; i < actualTypeArguments.length; i++) {
if (i > 0)
result.append(", ");
result.append(toString(actualTypeArguments[i]));
}
result.append('>');
return result.toString();
}
private String toString(Type type) {
if (type instanceof Class<?>)
return ((Class<?>) type).getName();
else
return String.valueOf(type);
}
}
/**
* Return all possible types that can be expected when an element of the given type is parsed.
* If the type satisfies {@link #isEither(Type)}, a list of the corresponding type arguments is returned,
* otherwise a list containg the type itself is returned. Type parameters are <em>not</em> resolved
* by this method (use {@link #getElementTypes(TypeToken, Class)} to get resolved parameters).
*/
public static Collection<Type> getExpectedTypes(Type type) {
Collection<Type> result = new ArrayList<>();
collectExpectedTypes(type, result);
return result;
}
private static void collectExpectedTypes(Type type, Collection<Type> types) {
if (isEither(type)) {
if (type instanceof ParameterizedType) {
for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
collectExpectedTypes(typeArgument, types);
}
}
if (type instanceof Class) {
for (Type typeParameter : ((Class<?>) type).getTypeParameters()) {
collectExpectedTypes(typeParameter, types);
}
}
} else {
types.add(type);
}
}
/**
* Test whether the given type is Either.
*/
public static boolean isEither(Type type) {
if (type instanceof ParameterizedType) {
return isEither(((ParameterizedType) type).getRawType());
}
if (type instanceof Class) {
return Either.class.isAssignableFrom((Class<?>) type);
}
return false;
}
/**
* Test whether the given type is a two-tuple (pair).
*/
public static boolean isTwoTuple(Type type) {
if (type instanceof ParameterizedType) {
return isTwoTuple(((ParameterizedType) type).getRawType());
}
if (type instanceof Class) {
return Tuple.Two.class.isAssignableFrom((Class<?>) type);
}
return false;
}
}