| /* |
| * 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 antlr.RecognitionException; |
| import antlr.TokenStreamException; |
| import groovy.transform.stc.IncorrectTypeHintException; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.antlr.AntlrParserPlugin; |
| import org.codehaus.groovy.antlr.parser.GroovyLexer; |
| import org.codehaus.groovy.antlr.parser.GroovyRecognizer; |
| 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.stmt.EmptyStatement; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.ResolveVisitor; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.syntax.ParserException; |
| import org.codehaus.groovy.syntax.Reduction; |
| import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; |
| |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Utility methods to deal with generic types. |
| * |
| * @author Cedric Champeau |
| * @author Paul King |
| */ |
| public class GenericsUtils { |
| public static final GenericsType[] EMPTY_GENERICS_ARRAY = new GenericsType[0]; |
| |
| /** |
| * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a |
| * class uses generic type <pre><T,U,V></pre> (redirectGenericTypes), is used with actual type parameters |
| * <pre><java.lang.String, U,V></pre>, then a class or interface using generic types <pre><T,V></pre> |
| * will be aligned to <pre><java.lang.String,V></pre> |
| * @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<String, GenericsType> extractPlaceholders(ClassNode cn) { |
| Map<String, GenericsType> ret = new HashMap<String, GenericsType>(); |
| extractPlaceholders(cn, ret); |
| return ret; |
| } |
| |
| /** |
| * For a given classnode, fills in the supplied map with the parameterized |
| * types it defines. |
| * @param node |
| * @param map |
| */ |
| public static void extractPlaceholders(ClassNode node, Map<String, GenericsType> 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) redirectGenericsTypes = parameterized; |
| for (int i = 0; i < redirectGenericsTypes.length; i++) { |
| GenericsType redirectType = redirectGenericsTypes[i]; |
| if (redirectType.isPlaceholder()) { |
| String name = redirectType.getName(); |
| if (!map.containsKey(name)) { |
| GenericsType value = parameterized[i]; |
| map.put(name, value); |
| 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); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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) && StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(target, hint)) { |
| ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); |
| if (!hint.equals(nextSuperClass)) { |
| Map<String, ClassNode> genericsSpec = createGenericsSpec(hint); |
| extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); |
| ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); |
| return parameterizeType(result, target); |
| } |
| } |
| Map<String, ClassNode> 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 = new GenericsType[0]; |
| 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<String,ClassNode> genericsSpec, MethodNode mn) { |
| 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<String,ClassNode> genericsSpec, ClassNode type) { |
| return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList<String>()); |
| } |
| |
| /** |
| * @since 2.4.1 |
| */ |
| public static ClassNode[] correctToGenericsSpecRecurse(Map<String,ClassNode> 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<String>()); |
| modified = modified || (types[i]!=newTypes[i]); |
| } |
| if (!modified) return types; |
| return newTypes; |
| } |
| |
| public static ClassNode correctToGenericsSpecRecurse(Map<String,ClassNode> genericsSpec, ClassNode type, List<String> exclusions) { |
| if (type.isArray()) { |
| return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); |
| } |
| if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) { |
| String name = type.getGenericsTypes()[0].getName(); |
| type = genericsSpec.get(name); |
| if (type != null && type.isGenericsPlaceHolder() && type.getGenericsTypes() == null) { |
| ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); |
| placeholder.setGenericsPlaceHolder(true); |
| type = makeClassSafeWithGenerics(type, new GenericsType(placeholder)); |
| } |
| } |
| if (type == null) type = ClassHelper.OBJECT_TYPE; |
| GenericsType[] oldgTypes = type.getGenericsTypes(); |
| GenericsType[] newgTypes = GenericsType.EMPTY_ARRAY; |
| if (oldgTypes != null) { |
| newgTypes = new GenericsType[oldgTypes.length]; |
| for (int i = 0; i < newgTypes.length; i++) { |
| GenericsType oldgType = oldgTypes[i]; |
| if (oldgType.isPlaceholder() ) { |
| if (genericsSpec.get(oldgType.getName())!=null) { |
| newgTypes[i] = new GenericsType(genericsSpec.get(oldgType.getName())); |
| } else { |
| newgTypes[i] = new GenericsType(ClassHelper.OBJECT_TYPE); |
| } |
| } else if (oldgType.isWildcard()) { |
| ClassNode oldLower = oldgType.getLowerBound(); |
| ClassNode lower = oldLower!=null?correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions):null; |
| ClassNode[] oldUpper = oldgType.getUpperBounds(); |
| ClassNode[] upper = null; |
| if (oldUpper!=null) { |
| upper = new ClassNode[oldUpper.length]; |
| for (int j = 0; j < oldUpper.length; j++) { |
| upper[j] = correctToGenericsSpecRecurse(genericsSpec,oldUpper[j], exclusions); |
| } |
| } |
| GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); |
| fixed.setName(oldgType.getName()); |
| fixed.setWildcard(true); |
| newgTypes[i] = fixed; |
| } else { |
| newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec,correctToGenericsSpec(genericsSpec, oldgType), exclusions)); |
| } |
| } |
| } |
| return makeClassSafeWithGenerics(type, newgTypes); |
| } |
| |
| public static ClassNode correctToGenericsSpec(Map<String, ClassNode> 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<String,ClassNode> genericsSpec, ClassNode type) { |
| if (type.isArray()) { |
| return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); |
| } |
| if (type.isGenericsPlaceHolder()) { |
| String name = type.getGenericsTypes()[0].getName(); |
| type = genericsSpec.get(name); |
| } |
| if (type == null) type = ClassHelper.OBJECT_TYPE; |
| return type; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static Map<String,ClassNode> createGenericsSpec(ClassNode current) { |
| return createGenericsSpec(current, Collections.EMPTY_MAP); |
| } |
| |
| public static Map<String,ClassNode> createGenericsSpec(ClassNode current, Map<String,ClassNode> oldSpec) { |
| Map<String,ClassNode> ret = new HashMap<String,ClassNode>(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<V,W,X>{} |
| // class B<T extends Number> extends A<T,Long,String> {} |
| // first we have: T->Number |
| // we apply it to A<T,Long,String> -> A<Number,Long,String> |
| // 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<String,ClassNode> addMethodGenerics(MethodNode current, Map<String,ClassNode> oldSpec) { |
| Map<String,ClassNode> ret = new HashMap<String,ClassNode>(oldSpec); |
| // ret starts with the original type specs, now add gts for the current method if any |
| GenericsType[] sgts = current.getGenericsTypes(); |
| if (sgts != null) { |
| for (GenericsType sgt : sgts) { |
| ret.put(sgt.getName(), sgt.getType()); |
| } |
| } |
| return ret; |
| } |
| |
| public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map<String,ClassNode> 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 (target.isGenericsPlaceHolder() || type.equals(target) || !StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(type, target)) { |
| // structural match route |
| if (target.isGenericsPlaceHolder()) { |
| spec.put(target.getGenericsTypes()[0].getName(),type); |
| } else { |
| extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); |
| } |
| } else { |
| // have first to find matching super class or interface |
| Map <String,ClassNode> genSpec = createGenericsSpec(type); |
| ClassNode superClass = ClassHelper.getNextSuperClass(type,target); |
| if (superClass!=null){ |
| ClassNode corrected = GenericsUtils.correctToGenericsSpecRecurse(genSpec, superClass); |
| extractSuperClassGenerics(corrected, 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."); |
| } |
| } |
| } |
| |
| private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map<String, ClassNode> spec) { |
| // if declaration does not provide generics, there is no connection to make |
| if (usage==null || declaration==null || declaration.length==0) return; |
| if (usage.length!=declaration.length) return; |
| |
| // both have generics |
| for (int i=0; i<usage.length; i++) { |
| 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<String, ClassNode> 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) { |
| GroovyLexer lexer = new GroovyLexer(new StringReader("DummyNode<" + option + ">")); |
| final GroovyRecognizer rn = GroovyRecognizer.make(lexer); |
| try { |
| rn.classOrInterfaceType(true); |
| final AtomicReference<ClassNode> ref = new AtomicReference<ClassNode>(); |
| AntlrParserPlugin plugin = new AntlrParserPlugin() { |
| @Override |
| public ModuleNode buildAST(final SourceUnit sourceUnit, final ClassLoader classLoader, final Reduction cst) throws ParserException { |
| ref.set(makeTypeWithArguments(rn.getAST())); |
| return null; |
| } |
| }; |
| plugin.buildAST(null, null, null); |
| ClassNode parsedNode = ref.get(); |
| // the returned node is DummyNode<Param1, Param2, Param3, ...) |
| GenericsType[] parsedNodeGenericsTypes = parsedNode.getGenericsTypes(); |
| if (parsedNodeGenericsTypes == null) { |
| return null; |
| } |
| ClassNode[] signature = new ClassNode[parsedNodeGenericsTypes.length]; |
| for (int i = 0; i < parsedNodeGenericsTypes.length; i++) { |
| final GenericsType genericsType = parsedNodeGenericsTypes[i]; |
| signature[i] = resolveClassNode(sourceUnit, compilationUnit, mn, usage, genericsType.getType()); |
| } |
| return signature; |
| } catch (RecognitionException e) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); |
| } catch (TokenStreamException e) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); |
| } catch (ParserException e) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); |
| } |
| return null; |
| } |
| |
| private static ClassNode resolveClassNode(final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage, final ClassNode parsedNode) { |
| ClassNode dummyClass = new ClassNode("dummy",0, ClassHelper.OBJECT_TYPE); |
| dummyClass.setModule(new ModuleNode(sourceUnit)); |
| dummyClass.setGenericsTypes(mn.getDeclaringClass().getGenericsTypes()); |
| MethodNode dummyMN = new MethodNode( |
| "dummy", |
| 0, |
| parsedNode, |
| Parameter.EMPTY_ARRAY, |
| ClassNode.EMPTY_ARRAY, |
| EmptyStatement.INSTANCE |
| ); |
| dummyMN.setGenericsTypes(mn.getGenericsTypes()); |
| dummyClass.addMethod(dummyMN); |
| ResolveVisitor visitor = new ResolveVisitor(compilationUnit) { |
| @Override |
| public void addError(final String msg, final ASTNode expr) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, msg, usage.getLineNumber(), usage.getColumnNumber())); |
| } |
| }; |
| visitor.startResolving(dummyClass, sourceUnit); |
| return dummyMN.getReturnType(); |
| } |
| |
| /** |
| * transforms generics types from an old context to a new context using the given spec. This method assumes |
| * all generics types will be placeholders. WARNING: The resulting generics types may or may not be placeholders |
| * after the transformation. |
| * @param genericsSpec the generics context information spec |
| * @param oldPlaceHolders the old placeholders |
| * @return the new generics types |
| */ |
| public static GenericsType[] applyGenericsContextToPlaceHolders(Map<String, ClassNode> 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) { |
| if (fromSpec.isGenericsPlaceHolder()) { |
| ClassNode[] upper = new ClassNode[]{fromSpec.redirect()}; |
| newTypes[i] = new GenericsType(fromSpec, upper, null); |
| } else { |
| newTypes[i] = new GenericsType(fromSpec); |
| } |
| } else { |
| ClassNode[] upper = old.getUpperBounds(); |
| ClassNode[] newUpper = upper; |
| if (upper!=null && upper.length>0) { |
| ClassNode[] upperCorrected = new ClassNode[upper.length]; |
| for (int j=0;j<upper.length;j++) { |
| upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec,upper[j]); |
| } |
| 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; |
| } |
| } |