org.codehaus.groovy.ast.tools.GenericsUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-eclipse-batch Show documentation
Show all versions of groovy-eclipse-batch Show documentation
Groovy Eclipse Compiler wrapped for Batch Use from Maven
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.codehaus.groovy.ast.tools;
import groovy.lang.Tuple2;
import groovy.transform.stc.IncorrectTypeHintException;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.ParserPlugin;
import org.codehaus.groovy.control.ResolveVisitor;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.memoize.ConcurrentSoftCache;
import org.codehaus.groovy.runtime.memoize.EvictableCache;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import static groovy.lang.Tuple.tuple;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.plus;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
/**
* Utility methods to deal with generic types.
*/
public class GenericsUtils {
public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY;
public static final String JAVA_LANG_OBJECT = "java.lang.Object";
/**
* Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a
* class uses generic type <T,U,V>
(redirectGenericTypes), is used with actual type parameters
* <java.lang.String, U,V>
, then a class or interface using generic types <T,V>
* will be aligned to <java.lang.String,V>
*
* @param redirectGenericTypes the type arguments or the redirect class node
* @param parameterizedTypes the actual type arguments used on this class node
* @param alignmentTarget the generic type arguments to which we want to align to
* @return aligned type arguments
* @deprecated You shouldn't call this method because it is inherently unreliable
*/
@Deprecated
public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) {
if (alignmentTarget == null) return EMPTY_GENERICS_ARRAY;
if (parameterizedTypes == null || parameterizedTypes.length == 0) return alignmentTarget;
GenericsType[] generics = new GenericsType[alignmentTarget.length];
for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) {
final GenericsType currentTarget = alignmentTarget[i];
GenericsType match = null;
if (redirectGenericTypes != null) {
for (int j = 0; j < redirectGenericTypes.length && match == null; j++) {
GenericsType redirectGenericType = redirectGenericTypes[j];
if (redirectGenericType.isCompatibleWith(currentTarget.getType())) {
if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) {
// check if there's a potential better match
boolean skip = false;
for (int k = j + 1; k < redirectGenericTypes.length && !skip; k++) {
GenericsType ogt = redirectGenericTypes[k];
if (ogt.isPlaceholder() && ogt.isCompatibleWith(currentTarget.getType()) && ogt.getName().equals(currentTarget.getName())) {
skip = true;
}
}
if (skip) continue;
}
match = parameterizedTypes[j];
if (currentTarget.isWildcard()) {
// if alignment target is a wildcard type
// then we must make best effort to return a parameterized
// wildcard
ClassNode lower = currentTarget.getLowerBound() != null ? match.getType() : null;
ClassNode[] currentUpper = currentTarget.getUpperBounds();
ClassNode[] upper = currentUpper != null ? new ClassNode[currentUpper.length] : null;
if (upper != null) {
for (int k = 0; k < upper.length; k++) {
upper[k] = currentUpper[k].isGenericsPlaceHolder() ? match.getType() : currentUpper[k];
}
}
match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower);
match.setWildcard(true);
}
}
}
}
if (match == null) {
match = currentTarget;
}
generics[i] = match;
}
return generics;
}
/**
* Generates a wildcard generic type in order to be used for checks against class nodes.
* See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}.
*
* @param types the type to be used as the wildcard upper bound
* @return a wildcard generics type
*/
public static GenericsType buildWildcardType(final ClassNode... types) {
ClassNode base = ClassHelper.makeWithoutCaching("?");
GenericsType gt = new GenericsType(base, types, null);
gt.setWildcard(true);
return gt;
}
public static Map extractPlaceholders(ClassNode cn) {
Map ret = new HashMap<>();
extractPlaceholders(cn, ret);
return ret;
}
/**
* For a given classnode, fills in the supplied map with the parameterized
* types it defines.
*
* @param node the class node to check
* @param map the generics type information collector
*/
public static void extractPlaceholders(ClassNode node, Map map) {
if (node == null) return;
if (node.isArray()) {
extractPlaceholders(node.getComponentType(), map);
return;
}
if (!node.isUsingGenerics() || !node.isRedirectNode()) return;
GenericsType[] parameterized = node.getGenericsTypes();
if (parameterized == null || parameterized.length == 0) return;
GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes();
if (redirectGenericsTypes == null ||
(node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */ ) {
redirectGenericsTypes = parameterized;
}
if (redirectGenericsTypes.length != parameterized.length) {
throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" +
"\nExpected: " + node.getName() + toGenericTypesString(redirectGenericsTypes) +
"\nSupplied: " + node.getName() + toGenericTypesString(parameterized));
}
List valueList = new LinkedList<>();
for (int i = 0; i < redirectGenericsTypes.length; i++) {
GenericsType redirectType = redirectGenericsTypes[i];
if (redirectType.isPlaceholder()) {
GenericsType.GenericsTypeName name = new GenericsType.GenericsTypeName(redirectType.getName());
if (!map.containsKey(name)) {
GenericsType value = parameterized[i];
map.put(name, value);
valueList.add(value);
}
}
}
for (GenericsType value : valueList) {
if (value.isWildcard()) {
ClassNode lowerBound = value.getLowerBound();
if (lowerBound != null) {
extractPlaceholders(lowerBound, map);
}
ClassNode[] upperBounds = value.getUpperBounds();
if (upperBounds != null) {
for (ClassNode upperBound : upperBounds) {
extractPlaceholders(upperBound, map);
}
}
} else if (!value.isPlaceholder()) {
extractPlaceholders(value.getType(), map);
}
}
}
public static String toGenericTypesString(GenericsType[] genericsTypes) {
if (genericsTypes == null) return "";
StringBuilder sb = new StringBuilder("<");
for (int i = 0, n = genericsTypes.length; i < n; i++) {
sb.append(genericsTypes[i].toString());
if (i < n - 1) {
sb.append(",");
}
}
sb.append("> ");
return sb.toString();
}
/**
* Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()}
* or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type
* arguments. This method allows returning a parameterized interface given the parameterized class
* node which implements this interface.
*
* @param hint the class node where generics types are parameterized
* @param target the interface we want to parameterize generics types
* @return a parameterized interface class node
* @deprecated Use #parameterizeType instead
*/
@Deprecated
public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) {
return parameterizeType(hint, target);
}
/**
* Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()}
* or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type
* arguments. This method allows returning a parameterized interface given the parameterized class
* node which implements this interface.
*
* @param hint the class node where generics types are parameterized
* @param target the interface we want to parameterize generics types
* @return a parameterized interface class node
*/
public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) {
if (hint.isArray()) {
if (target.isArray()) {
return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray();
}
return target;
}
if (!target.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) {
ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint);
if (!hint.equals(nextSuperClass)) {
Map genericsSpec = createGenericsSpec(hint);
extractSuperClassGenerics(hint, nextSuperClass, genericsSpec);
ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass);
return parameterizeType(result, target);
}
}
Map genericsSpec = createGenericsSpec(hint);
ClassNode targetRedirect = target.redirect();
genericsSpec = createGenericsSpec(targetRedirect, genericsSpec);
extractSuperClassGenerics(hint, targetRedirect, genericsSpec);
return correctToGenericsSpecRecurse(genericsSpec, targetRedirect);
}
public static ClassNode nonGeneric(ClassNode type) {
if (type.isUsingGenerics()) {
final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName());
nonGen.setRedirect(type);
nonGen.setGenericsTypes(null);
nonGen.setUsingGenerics(false);
return nonGen;
}
if (type.isArray() && type.getComponentType().isUsingGenerics()) {
return type.getComponentType().getPlainNodeReference().makeArray();
}
return type;
}
public static ClassNode newClass(ClassNode type) {
return type.getPlainNodeReference();
}
public static ClassNode makeClassSafe(Class klass) {
return makeClassSafeWithGenerics(ClassHelper.make(klass));
}
public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) {
GenericsType[] genericsTypes = new GenericsType[1];
genericsTypes[0] = new GenericsType(genericsType);
return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes);
}
public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) {
ClassNode plainNodeReference = newClass(type);
if (genericTypes != null && genericTypes.length > 0) {
plainNodeReference.setGenericsTypes(genericTypes);
if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true);
}
return plainNodeReference;
}
public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) {
if (type.isArray()) {
return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray();
}
GenericsType[] gtypes = EMPTY_GENERICS_ARRAY;
if (genericTypes != null) {
gtypes = new GenericsType[genericTypes.length];
System.arraycopy(genericTypes, 0, gtypes, 0, gtypes.length);
}
return makeClassSafe0(type, gtypes);
}
public static MethodNode correctToGenericsSpec(Map genericsSpec, MethodNode mn) {
if (genericsSpec == null) return mn;
if (mn.getGenericsTypes() != null) genericsSpec = addMethodGenerics(mn, genericsSpec);
ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType());
Parameter[] origParameters = mn.getParameters();
Parameter[] newParameters = new Parameter[origParameters.length];
for (int i = 0; i < origParameters.length; i++) {
Parameter origParameter = origParameters[i];
newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, origParameter.getType()), origParameter.getName(), origParameter.getInitialExpression());
}
return new MethodNode(mn.getName(), mn.getModifiers(), correctedType, newParameters, mn.getExceptions(), mn.getCode());
}
public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type) {
return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList());
}
/**
* @since 2.4.1
*/
public static ClassNode[] correctToGenericsSpecRecurse(Map genericsSpec, ClassNode[] types) {
if (types == null || types.length == 1) return types;
ClassNode[] newTypes = new ClassNode[types.length];
boolean modified = false;
for (int i = 0; i < types.length; i++) {
newTypes[i] = correctToGenericsSpecRecurse(genericsSpec, types[i], new ArrayList());
modified = modified || (types[i] != newTypes[i]);
}
if (!modified) return types;
return newTypes;
}
public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type, List exclusions) {
if (type.isArray()) {
return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray();
}
if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) {
String name = type.getGenericsTypes()[0].getName();
exclusions = plus(exclusions, name); // GROOVY-7722
type = genericsSpec.get(name);
if (type != null && type.isGenericsPlaceHolder()) {
if (type.getGenericsTypes() == null) {
// correct "T -> U" (no generics)
ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName());
placeholder.setGenericsPlaceHolder(true);
return makeClassSafeWithGenerics(type, new GenericsType(placeholder));
/* GRECLIPSE edit -- GROOVY-9059
} else if (!name.equals(type.getUnresolvedName())) {
return correctToGenericsSpecRecurse(genericsSpec, type, exclusions);
}
*/
} else if (type.hasMultiRedirect()) {
// convert "S -> T -> U" to "T -> U"
type = type.asGenericsType().getUpperBounds()[0];
// continue to resolve "T -> U" if "T" is a placeholder
return correctToGenericsSpecRecurse(genericsSpec, type, exclusions);
}
// GRECLIPSE end
}
}
if (type == null) type = ClassHelper.OBJECT_TYPE;
GenericsType[] oldgTypes = type.getGenericsTypes();
GenericsType[] newgTypes = EMPTY_GENERICS_ARRAY;
if (oldgTypes != null) {
newgTypes = new GenericsType[oldgTypes.length];
for (int i = 0; i < newgTypes.length; i++) {
GenericsType oldgType = oldgTypes[i];
if (oldgType.isWildcard()) {
ClassNode[] oldUpper = oldgType.getUpperBounds();
ClassNode[] upper = null;
if (oldUpper != null) {
// correct "? extends T" or "? extends T & I"
upper = new ClassNode[oldUpper.length];
for (int j = 0; j < oldUpper.length; j++) {
upper[j] = correctToGenericsSpecRecurse(genericsSpec, oldUpper[j], exclusions);
}
}
ClassNode oldLower = oldgType.getLowerBound();
ClassNode lower = null;
if (oldLower != null) {
// correct "? super T"
lower = correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions);
}
GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower);
fixed.setWildcard(true);
newgTypes[i] = fixed;
} else if (oldgType.isPlaceholder()) {
// correct "T"
newgTypes[i] = new GenericsType(genericsSpec.getOrDefault(oldgType.getName(), ClassHelper.OBJECT_TYPE));
} else {
// correct "List", etc.
newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions));
}
}
}
return makeClassSafeWithGenerics(type, newgTypes);
}
public static ClassNode correctToGenericsSpec(Map genericsSpec, GenericsType type) {
ClassNode ret = null;
if (type.isPlaceholder()) {
String name = type.getName();
ret = genericsSpec.get(name);
}
if (ret == null) ret = type.getType();
return ret;
}
public static ClassNode correctToGenericsSpec(Map genericsSpec, ClassNode type) {
if (type.isArray()) {
return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray();
}
if (type.isGenericsPlaceHolder() && type.getGenericsTypes() != null) {
String name = type.getGenericsTypes()[0].getName();
type = genericsSpec.get(name);
if (type != null && type.isGenericsPlaceHolder()
/* GRECLIPSE edit -- GROOVY-9059
&& !name.equals(type.getUnresolvedName())) {
*/
&& type.hasMultiRedirect()) {
type = type.asGenericsType().getUpperBounds()[0];
// GRECLIPSE end
return correctToGenericsSpec(genericsSpec, type);
}
}
if (type == null) type = ClassHelper.OBJECT_TYPE;
return type;
}
public static Map createGenericsSpec(ClassNode current) {
return createGenericsSpec(current, Collections.emptyMap());
}
public static Map createGenericsSpec(ClassNode current, Map oldSpec) {
Map ret = new HashMap(oldSpec);
// ret contains the type specs, what we now need is the type spec for the
// current class. To get that we first apply the type parameters to the
// current class and then use the type names of the current class to reset
// the map. Example:
// class A{}
// class B extends A {}
// first we have: T->Number
// we apply it to A -> A
// resulting in: V->Number,W->Long,X->String
GenericsType[] sgts = current.getGenericsTypes();
if (sgts != null) {
ClassNode[] spec = new ClassNode[sgts.length];
for (int i = 0; i < spec.length; i++) {
spec[i] = correctToGenericsSpec(ret, sgts[i]);
}
GenericsType[] newGts = current.redirect().getGenericsTypes();
if (newGts == null) return ret;
ret.clear();
for (int i = 0; i < spec.length; i++) {
ret.put(newGts[i].getName(), spec[i]);
}
}
return ret;
}
public static Map addMethodGenerics(MethodNode current, Map oldSpec) {
Map newSpec = new HashMap<>(oldSpec);
GenericsType[] gts = current.getGenericsTypes();
if (gts != null) {
for (GenericsType gt : gts) {
String name = gt.getName();
ClassNode type = gt.getType();
/* GRECLIPSE edit
if (gt.isPlaceholder()) {
ClassNode redirect;
if (gt.getUpperBounds() != null) {
redirect = gt.getUpperBounds()[0];
} else if (gt.getLowerBound() != null) {
redirect = gt.getLowerBound();
} else {
redirect = ClassHelper.OBJECT_TYPE;
}
if (redirect.isGenericsPlaceHolder()) {
// "T extends U" or "T super U"
type = redirect;
} else {
// "T" or "T extends Thing" or "T super Thing"
type = ClassHelper.makeWithoutCaching(name);
type.setGenericsPlaceHolder(true);
type.setRedirect(redirect);
}
}
*/
newSpec.put(name, type);
}
}
return newSpec;
}
public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map spec) {
// TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections,
// but operates on ClassNodes instead of GenericsType
if (target == null || type == target) return;
if (type.isArray() && target.isArray()) {
extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec);
} else if (type.isArray() && JAVA_LANG_OBJECT.equals(target.getName())) {
// Object is superclass of arrays but no generics involved
} else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) {
// structural match route
if (target.isGenericsPlaceHolder()) {
spec.put(target.getGenericsTypes()[0].getName(), type);
} else {
extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec);
}
} else {
// find matching super class or interface
ClassNode superClass = getSuperClass(type, target);
if (superClass != null) {
extractSuperClassGenerics(correctToGenericsSpecRecurse(createGenericsSpec(type), superClass), target, spec);
} else {
// if we reach here, we have an unhandled case
throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this.");
}
}
}
public static ClassNode getSuperClass(ClassNode type, ClassNode target) {
ClassNode superClass = ClassHelper.getNextSuperClass(type, target);
if (superClass == null) {
if (ClassHelper.isPrimitiveType(type)) {
superClass = ClassHelper.getNextSuperClass(ClassHelper.getWrapper(type), target);
}
}
return superClass;
}
private static void extractSuperClassGenerics(final GenericsType[] usage, final GenericsType[] declaration, final Map spec) {
// if declaration does not provide generics, there is no connection to make
if (declaration == null || declaration.length == 0) return;
// if usage is a raw type, remove type parameters from spec
if (usage == null) {
for (GenericsType dt : declaration) {
String name = dt.getName();
ClassNode type = spec.get(name);
if (type != null && type.isGenericsPlaceHolder()
&& type.getUnresolvedName().equals(name)) {
type = type.asGenericsType().getUpperBounds()[0];
spec.put(name, type);
}
}
return;
}
if (usage.length != declaration.length) return;
for (int i = 0, n = usage.length; i < n; i += 1) {
GenericsType ui = usage[i];
GenericsType di = declaration[i];
if (di.isPlaceholder()) {
spec.put(di.getName(), ui.getType());
} else if (di.isWildcard()) {
if (ui.isWildcard()) {
extractSuperClassGenerics(ui.getLowerBound(), di.getLowerBound(), spec);
extractSuperClassGenerics(ui.getUpperBounds(), di.getUpperBounds(), spec);
} else {
ClassNode cu = ui.getType();
extractSuperClassGenerics(cu, di.getLowerBound(), spec);
ClassNode[] upperBounds = di.getUpperBounds();
if (upperBounds != null) {
for (ClassNode cn : upperBounds) {
extractSuperClassGenerics(cu, cn, spec);
}
}
}
} else {
extractSuperClassGenerics(ui.getType(), di.getType(), spec);
}
}
}
private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map spec) {
if (usage == null || declaration == null || declaration.length == 0) return;
// both have generics
for (int i = 0; i < usage.length; i++) {
ClassNode ui = usage[i];
ClassNode di = declaration[i];
if (di.isGenericsPlaceHolder()) {
spec.put(di.getGenericsTypes()[0].getName(), di);
} else if (di.isUsingGenerics()) {
extractSuperClassGenerics(ui.getGenericsTypes(), di.getGenericsTypes(), spec);
}
}
}
public static ClassNode[] parseClassNodesFromString(final String option, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage) {
try {
ModuleNode moduleNode = ParserPlugin.buildAST("Dummy<" + option + "> dummy;", compilationUnit.getConfiguration(), compilationUnit.getClassLoader(), null);
DeclarationExpression dummyDeclaration = (DeclarationExpression) ((ExpressionStatement) moduleNode.getStatementBlock().getStatements().get(0)).getExpression();
// the returned node is DummyNode genericsSpec, GenericsType[] oldPlaceHolders) {
if (oldPlaceHolders == null || oldPlaceHolders.length == 0) return oldPlaceHolders;
if (genericsSpec.isEmpty()) return oldPlaceHolders;
GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length];
for (int i = 0; i < oldPlaceHolders.length; i++) {
GenericsType old = oldPlaceHolders[i];
if (!old.isPlaceholder())
throw new GroovyBugError("Given generics type " + old + " must be a placeholder!");
ClassNode fromSpec = genericsSpec.get(old.getName());
if (fromSpec != null) {
newTypes[i] = fromSpec.asGenericsType();
} else {
ClassNode[] upper = old.getUpperBounds();
ClassNode[] newUpper = upper;
if (upper != null && upper.length > 0) {
ClassNode[] upperCorrected = new ClassNode[upper.length];
for (ClassNode classNode : upper) {
upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec, classNode);
}
upper = upperCorrected;
}
ClassNode lower = old.getLowerBound();
ClassNode newLower = correctToGenericsSpecRecurse(genericsSpec, lower);
if (lower == newLower && upper == newUpper) {
newTypes[i] = oldPlaceHolders[i];
} else {
ClassNode newPlaceHolder = ClassHelper.make(old.getName());
GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower);
gt.setPlaceholder(true);
newTypes[i] = gt;
}
}
}
return newTypes;
}
private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.getBoolean("groovy.enable.parameterized.type.cache");
/**
* Convenience method for {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)}
* when the {@code tryToFindExactType} boolean is {@code false}.
*/
public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) {
return findParameterizedTypeFromCache(genericsClass, actualType, false);
}
/**
* Try to get the parameterized type from the cache.
* If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode, ClassNode, boolean)}
*/
public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, boolean tryToFindExactType) {
if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
return findParameterizedType(genericsClass, actualType, tryToFindExactType);
}
SoftReference sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), key -> new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType)));
return null == sr ? null : sr.get();
}
/**
* Convenience method for {@link #findParameterizedType(ClassNode, ClassNode, boolean)}
* when the {@code tryToFindExactType} boolean is {@code false}.
*/
public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType) {
return findParameterizedType(genericsClass, actualType, false);
}
/**
* Get the parameterized type by searching the whole class hierarchy according to generics class and actual receiver.
* {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)} is strongly recommended for better performance.
*
* @param genericsClass the generics class
* @param actualType the actual type
* @param tryToFindExactType whether to try to find exact type
* @return the parameterized type
*/
public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType, boolean tryToFindExactType) {
ClassNode parameterizedType = null;
if (null == genericsClass.getGenericsTypes()) {
return parameterizedType;
}
GenericsType[] declaringGenericsTypes = genericsClass.getGenericsTypes();
List classNodeList = new LinkedList<>(getAllSuperClassesAndInterfaces(actualType));
classNodeList.add(0, actualType);
LinkedList parameterizedTypeCandidateList = new LinkedList<>();
for (ClassNode cn : classNodeList) {
if (cn == genericsClass) {
continue;
}
if (tryToFindExactType && null != cn.getGenericsTypes() && hasNonPlaceHolders(cn)) {
parameterizedTypeCandidateList.add(cn);
}
if (!(genericsClass.equals(cn.redirect()))) {
continue;
}
if (isGenericsTypeArraysLengthEqual(declaringGenericsTypes, cn.getGenericsTypes())) {
parameterizedType = cn;
break;
}
}
if (null == parameterizedType) {
if (!parameterizedTypeCandidateList.isEmpty()) {
parameterizedType = parameterizedTypeCandidateList.getLast();
}
}
return parameterizedType;
}
private static boolean isGenericsTypeArraysLengthEqual(GenericsType[] declaringGenericsTypes, GenericsType[] actualGenericsTypes) {
return null != actualGenericsTypes && declaringGenericsTypes.length == actualGenericsTypes.length;
}
private static List getAllSuperClassesAndInterfaces(ClassNode actualReceiver) {
List superClassAndInterfaceList = new LinkedList<>();
List allSuperClassNodeList = getAllUnresolvedSuperClasses(actualReceiver);
superClassAndInterfaceList.addAll(allSuperClassNodeList);
superClassAndInterfaceList.addAll(actualReceiver.getAllInterfaces());
for (ClassNode superClassNode : allSuperClassNodeList) {
superClassAndInterfaceList.addAll(superClassNode.getAllInterfaces());
}
return superClassAndInterfaceList;
}
private static List getAllUnresolvedSuperClasses(ClassNode actualReceiver) {
List superClassNodeList = new LinkedList<>();
for (ClassNode cn = actualReceiver.getUnresolvedSuperClass(); null != cn && ClassHelper.OBJECT_TYPE != cn; cn = cn.getUnresolvedSuperClass()) {
superClassNodeList.add(cn);
}
return superClassNodeList;
}
private static final EvictableCache> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
/**
* Clear the parameterized type cache
* It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675
*/
public static void clearParameterizedTypeCache() {
PARAMETERIZED_TYPE_CACHE.clearAll();
}
/**
* map declaring generics type to actual generics type, e.g. GROOVY-7204:
* declaring generics types: T, S extends Serializable
* actual generics types : String, Long
*
* the result map is [
* T: String,
* S: Long
* ]
*
* The resolved types can not help us to choose methods correctly if the argument is a string: T: Object, S: Serializable
* so we need actual types: T: String, S: Long
*/
public static Map makeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver) {
return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false).getV1();
}
/**
* The method is similar with {@link GenericsUtils#makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)},
* The main difference is that the method will try to map all placeholders found to the relevant exact types,
* but the other will not try even if the parameterized type has placeholders
*
* @param declaringClass the generics class node declaring the generics types
* @param actualReceiver the sub-class class node
* @return the placeholder-to-actualtype mapping
* @since 3.0.0
*/
public static Map makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver) {
List parameterizedTypeList = new LinkedList<>();
Map result = makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver, parameterizedTypeList);
return connectGenericsTypes(result);
}
private static Map makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver, List parameterizedTypeList) {
Tuple2
© 2015 - 2024 Weber Informatics LLC | Privacy Policy