| /****************************************************************************** |
| * 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; |
| } |
| |
| } |