All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.ast.tools.GenericsUtils Maven / Gradle / Ivy

There is a newer version: 3.0.8-01
Show newest version
/*
 *  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, ClassNode> resultAndParameterizedTypeTuple = doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true); ClassNode parameterizedType = resultAndParameterizedTypeTuple.getV2(); Map result = resultAndParameterizedTypeTuple.getV1(); if (hasPlaceHolders(parameterizedType) && !parameterizedTypeList.contains(parameterizedType)) { parameterizedTypeList.add(parameterizedType); result.putAll(makeDeclaringAndActualGenericsTypeMapOfExactType(parameterizedType, actualReceiver, parameterizedTypeList)); } return connectGenericsTypes(result); } private static Tuple2, ClassNode> doMakeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver, boolean tryToFindExactType) { ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType); if (null == parameterizedType) { return tuple(Collections.emptyMap(), parameterizedType); } Map result = new LinkedHashMap<>(); result.putAll(makePlaceholderAndParameterizedTypeMap(declaringClass)); result.putAll(makePlaceholderAndParameterizedTypeMap(parameterizedType)); result = connectGenericsTypes(result); return tuple(result, parameterizedType); } private static Map makePlaceholderAndParameterizedTypeMap(ClassNode declaringClass) { if (null == declaringClass) { return Collections.emptyMap(); } Map result = new LinkedHashMap<>(); ClassNode redirectDeclaringClass = declaringClass.redirect(); GenericsType[] declaringGenericsTypes = declaringClass.getGenericsTypes(); GenericsType[] redirectDeclaringGenericsTypes = redirectDeclaringClass.getGenericsTypes(); if (null != declaringGenericsTypes && null != redirectDeclaringGenericsTypes) { for (int i = 0, n = declaringGenericsTypes.length; i < n; i++) { result.put(redirectDeclaringGenericsTypes[i], declaringGenericsTypes[i]); } } return result; } private static Map connectGenericsTypes(Map genericsTypeMap) { Map result = new LinkedHashMap<>(); outer: for (Map.Entry entry : genericsTypeMap.entrySet()) { GenericsType key = entry.getKey(); GenericsType value = entry.getValue(); if (value.isPlaceholder()) { for (Map.Entry genericsTypeMapEntry : genericsTypeMap.entrySet()) { GenericsType genericsTypeMapEntryValue = genericsTypeMapEntry.getValue(); if (!genericsTypeMapEntryValue.isPlaceholder() && (genericsTypeMapEntry.getKey().getName().equals(value.getName()))) { result.put(key, genericsTypeMapEntryValue); // connected to actual type continue outer; } } } result.put(key, value); } return result; } /** * Checks if the type has any non-placeholder (aka resolved) generics. * * @since 3.0.0 */ public static boolean hasNonPlaceHolders(final ClassNode type) { return checkPlaceHolders(type, gt -> !gt.isPlaceholder()); } /** * Checks if the type has any placeholder (aka unresolved) generics. * * @since 3.0.0 */ public static boolean hasPlaceHolders(final ClassNode type) { return checkPlaceHolders(type, gt -> gt.isPlaceholder()); } private static boolean checkPlaceHolders(final ClassNode type, final Predicate p) { if (type != null) { GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes != null) { for (GenericsType genericsType : genericsTypes) { if (p.test(genericsType)) { return true; } } } } return false; } /** * Checks for any placeholder (aka unresolved) generics. */ public static boolean hasUnresolvedGenerics(final ClassNode type) { if (type.isGenericsPlaceHolder()) return true; if (type.isArray()) { return hasUnresolvedGenerics(type.getComponentType()); } GenericsType[] genericsTypes = type.getGenericsTypes(); if (genericsTypes != null) { for (GenericsType genericsType : genericsTypes) { if (genericsType.isPlaceholder()) return true; ClassNode lowerBound = genericsType.getLowerBound(); ClassNode[] upperBounds = genericsType.getUpperBounds(); if (lowerBound != null) { if (hasUnresolvedGenerics(lowerBound)) return true; } else if (upperBounds != null) { for (ClassNode upperBound : upperBounds) { if (hasUnresolvedGenerics(upperBound)) return true; } } else { if (hasUnresolvedGenerics(genericsType.getType())) return true; } } } return false; } /** * Gets the parameter and return types of the abstract method of SAM. * * If the abstract method is not parameterized, we will get generics placeholders, e.g. T, U * For example, the abstract method of {@link java.util.function.Function} is *
     *      R apply(T t);
     * 
* * We parameterize the above interface as {@code Function}, then the abstract method will be *
     *      Integer apply(String t);
     * 
* * When we call {@code parameterizeSAM} on the ClassNode {@code Function}, * we can get parameter types and return type of the above abstract method, * i.e. ClassNode {@code ClassHelper.STRING_TYPE} and {@code ClassHelper.Integer_TYPE} * * @param samType the class node which contains only one abstract method * * @since 3.0.0 */ public static Tuple2 parameterizeSAM(final ClassNode samType) { MethodNode abstractMethod = ClassHelper.findSAM(samType); Map generics = makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), samType); Function resolver = t -> { if (t.isGenericsPlaceHolder()) { return findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), generics); } return t; }; ClassNode[] parameterTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(resolver).toArray(ClassNode[]::new); ClassNode returnType = resolver.apply(abstractMethod.getReturnType()); return tuple(parameterTypes, returnType); } /** * Gets the actual type according to the placeholder name. * * @param placeholderName the placeholder name (i.e. "T", "E", etc.) * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)} */ public static ClassNode findActualTypeByGenericsPlaceholderName(final String placeholderName, final Map genericsPlaceholderAndTypeMap) { Function resolver = gt -> { if (gt.isWildcard()) { if (gt.getLowerBound() != null) { return gt.getLowerBound(); } if (gt.getUpperBounds() != null) { return gt.getUpperBounds()[0]; } } return gt.getType(); }; return genericsPlaceholderAndTypeMap.entrySet().stream() .filter(e -> e.getKey().getName().equals(placeholderName)) .map(Map.Entry::getValue).map(resolver).findFirst().orElse(null); } private static class ParameterizedTypeCacheKey { private ClassNode genericsClass; private ClassNode actualType; public ParameterizedTypeCacheKey(ClassNode genericsClass, ClassNode actualType) { this.genericsClass = genericsClass; this.actualType = actualType; } public ClassNode getGenericsClass() { return genericsClass; } @SuppressWarnings("unused") public void setGenericsClass(ClassNode genericsClass) { this.genericsClass = genericsClass; } public ClassNode getActualType() { return actualType; } @SuppressWarnings("unused") public void setActualType(ClassNode actualType) { this.actualType = actualType; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ParameterizedTypeCacheKey cacheKey = (ParameterizedTypeCacheKey) o; return genericsClass == cacheKey.genericsClass && actualType == cacheKey.actualType; } @Override public int hashCode() { return Objects.hash(genericsClass, actualType); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy