http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java new file mode 100644 index 0000000..653f327 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -0,0 +1,614 @@ +/* + * 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 (type.isArray() && target.getName().equals("java.lang.Object")) { + // Object is superclass of arrays but no generics involved + } 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; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java new file mode 100644 index 0000000..4edf92e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java @@ -0,0 +1,39 @@ +/* + * 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 org.codehaus.groovy.ast.Parameter; + +public class ParameterUtils { + public static boolean parametersEqual(Parameter[] a, Parameter[] b) { + if (a.length == b.length) { + boolean answer = true; + for (int i = 0; i < a.length; i++) { + if (!a[i].getType().equals(b[i].getType())) { + answer = false; + break; + } + } + return answer; + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java new file mode 100644 index 0000000..513144a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java @@ -0,0 +1,43 @@ +/* + * 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 org.codehaus.groovy.ast.PropertyNode; + +import java.lang.reflect.Modifier; + +public class PropertyNodeUtils { + /** + * Fields within the AST that have no explicit visibility are deemed to be properties + * and represented by a PropertyNode. The Groovy compiler creates accessor methods and + * a backing field for such property nodes. During this process, all modifiers + * from the property are carried over to the backing field (so a property marked as + * {@code transient} will have a {@code transient} backing field) but when creating + * the accessor methods we don't carry over modifier values which don't make sense for + * methods (this includes VOLATILE and TRANSIENT) but other modifiers are carried over, + * for example {@code static}. + * + * @param propNode the original property node + * @return the modifiers which make sense for an accessor method + */ + public static int adjustPropertyModifiersForMethod(PropertyNode propNode) { + // GROOVY-3726: clear volatile, transient modifiers so that they don't get applied to methods + return ~(Modifier.TRANSIENT | Modifier.VOLATILE) & propNode.getModifiers(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java new file mode 100644 index 0000000..1489a56 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java @@ -0,0 +1,746 @@ +/* + * 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 org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; +import static org.codehaus.groovy.ast.ClassHelper.getWrapper; +import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.isNumberType; +import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; +import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; + +/** + * This class provides helper methods to determine the type from a widening + * operation for example for a plus operation. + * <p> + * To determine the resulting type of for example a=exp1+exp2 we look at the + * conditions {@link #isIntCategory(ClassNode)}, {@link #isLongCategory(ClassNode)}, + * {@link #isBigIntCategory(ClassNode)}, {@link #isDoubleCategory(ClassNode)} and + * {@link #isBigDecCategory(ClassNode)} in that order. The first case applying to + * exp1 and exp2 is defining the result type of the expression. + * <p> + * If for example you look at x = 1 + 2l we have the first category applying to + * the number 1 being int, since the 1 is an int. The 2l is a long, therefore the + * int category will not apply and the result type can't be int. The next category + * in the list is long, and since both apply to long, the result type is a long. + * + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + * @author Cedric Champeau + */ +public class WideningCategories { + + private static final List<ClassNode> EMPTY_CLASSNODE_LIST = Collections.emptyList(); + + private static final Map<ClassNode, Integer> NUMBER_TYPES_PRECEDENCE = Collections.unmodifiableMap(new HashMap<ClassNode, Integer>() {{ + put(ClassHelper.double_TYPE, 0); + put(ClassHelper.float_TYPE, 1); + put(ClassHelper.long_TYPE, 2); + put(ClassHelper.int_TYPE, 3); + put(ClassHelper.short_TYPE, 4); + put(ClassHelper.byte_TYPE, 5); + }}); + + /** + * A comparator which is used in case we generate a virtual lower upper bound class node. In that case, + * since a concrete implementation should be used at compile time, we must ensure that interfaces are + * always sorted. It is not important what sort is used, as long as the result is constant. + */ + private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR = new Comparator<ClassNode>() { + public int compare(final ClassNode o1, final ClassNode o2) { + int interfaceCountForO1 = o1.getInterfaces().length; + int interfaceCountForO2 = o2.getInterfaces().length; + if (interfaceCountForO1 > interfaceCountForO2) return -1; + if (interfaceCountForO1 < interfaceCountForO2) return 1; + int methodCountForO1 = o1.getMethods().size(); + int methodCountForO2 = o2.getMethods().size(); + if (methodCountForO1 > methodCountForO2) return -1; + if (methodCountForO1 < methodCountForO2) return 1; + return o1.getName().compareTo(o2.getName()); + } + }; + + /** + * Used to check if a type is an int or Integer. + * @param type the type to check + */ + public static boolean isInt(ClassNode type) { + return int_TYPE == type; + } + + /** + * Used to check if a type is an double or Double. + * @param type the type to check + */ + public static boolean isDouble(ClassNode type) { + return double_TYPE == type; + } + + /** + * Used to check if a type is a float or Float. + * @param type the type to check + */ + public static boolean isFloat(ClassNode type) { + return float_TYPE == type; + } + + /** + * It is of an int category, if the provided type is a + * byte, char, short, int. + */ + public static boolean isIntCategory(ClassNode type) { + return type==byte_TYPE || type==char_TYPE || + type==int_TYPE || type==short_TYPE; + } + /** + * It is of a long category, if the provided type is a + * long, its wrapper or if it is a long category. + */ + public static boolean isLongCategory(ClassNode type) { + return type==long_TYPE || isIntCategory(type); + } + /** + * It is of a BigInteger category, if the provided type is a + * long category or a BigInteger. + */ + public static boolean isBigIntCategory(ClassNode type) { + return type==BigInteger_TYPE || isLongCategory(type); + } + /** + * It is of a BigDecimal category, if the provided type is a + * BigInteger category or a BigDecimal. + */ + public static boolean isBigDecCategory(ClassNode type) { + return type==BigDecimal_TYPE || isBigIntCategory(type); + } + /** + * It is of a double category, if the provided type is a + * BigDecimal, a float, double. C(type)=double + */ + public static boolean isDoubleCategory(ClassNode type) { + return type==float_TYPE || type==double_TYPE || + isBigDecCategory(type); + } + + /** + * It is of a floating category, if the provided type is a + * a float, double. C(type)=float + */ + public static boolean isFloatingCategory(ClassNode type) { + return type==float_TYPE || type==double_TYPE; + } + + public static boolean isNumberCategory(ClassNode type) { + return isBigDecCategory(type) || type.isDerivedFrom(Number_TYPE); + } + + /** + * Given a list of class nodes, returns the first common supertype. + * For example, Double and Float would return Number, while + * Set and String would return Object. + * @param nodes the list of nodes for which to find the first common super type. + * @return first common supertype + */ + public static ClassNode lowestUpperBound(List<ClassNode> nodes) { + if (nodes.size()==1) return nodes.get(0); + return lowestUpperBound(nodes.get(0), lowestUpperBound(nodes.subList(1, nodes.size()))); + } + + /** + * Given two class nodes, returns the first common supertype, or the class itself + * if there are equal. For example, Double and Float would return Number, while + * Set and String would return Object. + * + * This method is not guaranteed to return a class node which corresponds to a + * real type. For example, if two types have more than one interface in common + * and are not in the same hierarchy branch, then the returned type will be a + * virtual type implementing all those interfaces. + * + * Calls to this method are supposed to be made with resolved generics. This means + * that you can have wildcards, but no placeholder. + * + * @param a first class node + * @param b second class node + * @return first common supertype + */ + public static ClassNode lowestUpperBound(ClassNode a, ClassNode b) { + ClassNode lub = lowestUpperBound(a, b, null, null); + if (lub==null || !lub.isUsingGenerics()) return lub; + // types may be parameterized. If so, we must ensure that generic type arguments + // are made compatible + + if (lub instanceof LowestUpperBoundClassNode) { + // no parent super class representing both types could be found + // or both class nodes implement common interfaces which may have + // been parameterized differently. + // We must create a classnode for which the "superclass" is potentially parameterized + // plus the interfaces + ClassNode superClass = lub.getSuperClass(); + ClassNode psc = superClass.isUsingGenerics()?parameterizeLowestUpperBound(superClass, a, b, lub):superClass; + + ClassNode[] interfaces = lub.getInterfaces(); + ClassNode[] pinterfaces = new ClassNode[interfaces.length]; + for (int i = 0, interfacesLength = interfaces.length; i < interfacesLength; i++) { + final ClassNode icn = interfaces[i]; + if (icn.isUsingGenerics()) { + pinterfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub); + } else { + pinterfaces[i] = icn; + } + } + + return new LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, psc, pinterfaces); + } else { + return parameterizeLowestUpperBound(lub, a, b, lub); + + } + } + + /** + * Given a lowest upper bound computed without generic type information but which requires to be parameterized + * and the two implementing classnodes which are parameterized with potentially two different types, returns + * a parameterized lowest upper bound. + * + * For example, if LUB is Set<T> and a is Set<String> and b is Set<StringBuffer>, this + * will return a LUB which parameterized type matches Set<? extends CharSequence> + * @param lub the type to be parameterized + * @param a parameterized type a + * @param b parameterized type b + * @param fallback if we detect a recursive call, use this LUB as the parameterized type instead of computing a value + * @return the class node representing the parameterized lowest upper bound + */ + private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, final ClassNode a, final ClassNode b, final ClassNode fallback) { + if (!lub.isUsingGenerics()) return lub; + // a common super type exists, all we have to do is to parameterize + // it according to the types provided by the two class nodes + ClassNode holderForA = findGenericsTypeHolderForClass(a, lub); + ClassNode holderForB = findGenericsTypeHolderForClass(b, lub); + // let's compare their generics type + GenericsType[] agt = holderForA == null ? null : holderForA.getGenericsTypes(); + GenericsType[] bgt = holderForB == null ? null : holderForB.getGenericsTypes(); + if (agt==null || bgt==null || agt.length!=bgt.length) { + return lub; + } + GenericsType[] lubgt = new GenericsType[agt.length]; + for (int i = 0; i < agt.length; i++) { + ClassNode t1 = agt[i].getType(); + ClassNode t2 = bgt[i].getType(); + ClassNode basicType; + if (areEqualWithGenerics(t1, a) && areEqualWithGenerics(t2,b)) { + // we are facing a self referencing type ! + basicType = fallback; + } else { + basicType = lowestUpperBound(t1, t2); + } + if (t1.equals(t2)) { + lubgt[i] = new GenericsType(basicType); + } else { + lubgt[i] = GenericsUtils.buildWildcardType(basicType); + } + } + ClassNode plain = lub.getPlainNodeReference(); + plain.setGenericsTypes(lubgt); + return plain; + } + + private static ClassNode findGenericsTypeHolderForClass(ClassNode source, ClassNode type) { + if (isPrimitiveType(source)) source = getWrapper(source); + if (source.equals(type)) return source; + if (type.isInterface()) { + for (ClassNode interfaceNode : source.getAllInterfaces()) { + if (interfaceNode.equals(type)) { + ClassNode parameterizedInterface = GenericsUtils.parameterizeType(source, interfaceNode); + return parameterizedInterface; + } + } + } + ClassNode superClass = source.getUnresolvedSuperClass(); + // copy generic type information if available + if (superClass!=null && superClass.isUsingGenerics()) { + Map<String, GenericsType> genericsTypeMap = GenericsUtils.extractPlaceholders(source); + GenericsType[] genericsTypes = superClass.getGenericsTypes(); + if (genericsTypes!=null) { + GenericsType[] copyTypes = new GenericsType[genericsTypes.length]; + for (int i = 0; i < genericsTypes.length; i++) { + GenericsType genericsType = genericsTypes[i]; + if (genericsType.isPlaceholder() && genericsTypeMap.containsKey(genericsType.getName())) { + copyTypes[i] = genericsTypeMap.get(genericsType.getName()); + } else { + copyTypes[i] = genericsType; + } + } + superClass = superClass.getPlainNodeReference(); + superClass.setGenericsTypes(copyTypes); + } + } + if (superClass!=null) return findGenericsTypeHolderForClass(superClass, type); + return null; + } + + private static ClassNode lowestUpperBound(ClassNode a, ClassNode b, List<ClassNode> interfacesImplementedByA, List<ClassNode> interfacesImplementedByB) { + // first test special cases + if (a==null || b==null) { + // this is a corner case, you should not + // compare two class nodes if one of them is null + return null; + } + if (a.isArray() && b.isArray()) { + return lowestUpperBound(a.getComponentType(), b.getComponentType(), interfacesImplementedByA, interfacesImplementedByB).makeArray(); + } + if (a.equals(OBJECT_TYPE) || b.equals(OBJECT_TYPE)) { + // one of the objects is at the top of the hierarchy + GenericsType[] gta = a.getGenericsTypes(); + GenericsType[] gtb = b.getGenericsTypes(); + if (gta !=null && gtb !=null && gta.length==1 && gtb.length==1) { + if (gta[0].getName().equals(gtb[0].getName())) { + return a; + } + } + return OBJECT_TYPE; + } + if (a.equals(VOID_TYPE) || b.equals(VOID_TYPE)) { + if (!b.equals(a)) { + // one class is void, the other is not + return OBJECT_TYPE; + } + return VOID_TYPE; + } + + // now handle primitive types + boolean isPrimitiveA = isPrimitiveType(a); + boolean isPrimitiveB = isPrimitiveType(b); + if (isPrimitiveA && !isPrimitiveB) { + return lowestUpperBound(getWrapper(a), b, null, null); + } + if (isPrimitiveB && !isPrimitiveA) { + return lowestUpperBound(a, getWrapper(b), null, null); + } + if (isPrimitiveA && isPrimitiveB) { + Integer pa = NUMBER_TYPES_PRECEDENCE.get(a); + Integer pb = NUMBER_TYPES_PRECEDENCE.get(b); + if (pa!=null && pb!=null) { + if (pa<=pb) return a; + return b; + } + return a.equals(b)?a:lowestUpperBound(getWrapper(a), getWrapper(b), null, null); + } + if (isNumberType(a.redirect()) && isNumberType(b.redirect())) { + ClassNode ua = getUnwrapper(a); + ClassNode ub = getUnwrapper(b); + Integer pa = NUMBER_TYPES_PRECEDENCE.get(ua); + Integer pb = NUMBER_TYPES_PRECEDENCE.get(ub); + if (pa!=null && pb!=null) { + if (pa<=pb) return a; + return b; + } + } + + // handle interfaces + boolean isInterfaceA = a.isInterface(); + boolean isInterfaceB = b.isInterface(); + if (isInterfaceA && isInterfaceB) { + if (a.equals(b)) return a; + if (b.implementsInterface(a)) { + return a; + } + if (a.implementsInterface(b)) { + return b; + } + // each interface may have one or more "extends", so we must find those + // which are common + ClassNode[] interfacesFromA = a.getInterfaces(); + ClassNode[] interfacesFromB = b.getInterfaces(); + Set<ClassNode> common = new HashSet<ClassNode>(); + Collections.addAll(common, interfacesFromA); + Set<ClassNode> fromB = new HashSet<ClassNode>(); + Collections.addAll(fromB, interfacesFromB); + common.retainAll(fromB); + + if (common.size()==1) { + return common.iterator().next(); + } else if (common.size()>1) { + return buildTypeWithInterfaces(a, b, common); + } + + // we have two interfaces, but none inherits from the other + // so the only possible return type is Object + return OBJECT_TYPE; + } else if (isInterfaceB) { + return lowestUpperBound(b, a, null, null); + } else if (isInterfaceA) { + // a is an interface, b is not + + // a ClassNode superclass for an interface is not + // another interface but always Object. This implies that + // "extends" for an interface is understood as "implements" + // for a ClassNode. Therefore, even if b doesn't implement + // interface a, a could "implement" other interfaces that b + // implements too, so we must create a list of matching interfaces + List<ClassNode> matchingInterfaces = new LinkedList<ClassNode>(); + extractMostSpecificImplementedInterfaces(b, a, matchingInterfaces); + if (matchingInterfaces.isEmpty()) { + // no interface in common + return OBJECT_TYPE; + } + if (matchingInterfaces.size()==1) { + // a single match, which should be returned + return matchingInterfaces.get(0); + } + return buildTypeWithInterfaces(a,b, matchingInterfaces); + } + // both classes do not represent interfaces + if (a.equals(b)) { + return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + } + // test if one class inherits from the other + if (a.isDerivedFrom(b) || b.isDerivedFrom(a)) { + return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + } + + // Look at the super classes + ClassNode sa = a.getUnresolvedSuperClass(); + ClassNode sb = b.getUnresolvedSuperClass(); + + // extract implemented interfaces before "going up" + Set<ClassNode> ifa = new HashSet<ClassNode>(); + extractInterfaces(a, ifa); + Set<ClassNode> ifb = new HashSet<ClassNode>(); + extractInterfaces(b, ifb); + interfacesImplementedByA = interfacesImplementedByA==null?new LinkedList<ClassNode>(ifa):interfacesImplementedByA; + interfacesImplementedByB = interfacesImplementedByB==null?new LinkedList<ClassNode>(ifb):interfacesImplementedByB; + + // check if no superclass is defined + // meaning that we reached the top of the object hierarchy + if (sa==null || sb==null) return buildTypeWithInterfaces(OBJECT_TYPE, OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + + + // if one superclass is derived (or equals) another + // then it is the common super type + if (sa.isDerivedFrom(sb) || sb.isDerivedFrom(sa)) { + return buildTypeWithInterfaces(sa, sb, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB)); + } + // superclasses are on distinct hierarchy branches, so we + // recurse on them + return lowestUpperBound(sa, sb, interfacesImplementedByA, interfacesImplementedByB); + } + + private static void extractInterfaces(ClassNode node, Set<ClassNode> interfaces) { + if (node==null) return; + Collections.addAll(interfaces, node.getInterfaces()); + extractInterfaces(node.getSuperClass(), interfaces); + } + + /** + * Given the list of interfaces implemented by two class nodes, returns the list of the most specific common + * implemented interfaces. + * @param fromA + * @param fromB + * @return the list of the most specific common implemented interfaces + */ + private static List<ClassNode> keepLowestCommonInterfaces(List<ClassNode> fromA, List<ClassNode> fromB) { + if (fromA==null||fromB==null) return EMPTY_CLASSNODE_LIST; + Set<ClassNode> common = new HashSet<ClassNode>(fromA); + common.retainAll(fromB); + List<ClassNode> result = new ArrayList<ClassNode>(common.size()); + for (ClassNode classNode : common) { + addMostSpecificInterface(classNode, result); + } + return result; + } + + private static void addMostSpecificInterface(ClassNode interfaceNode, List<ClassNode> nodes) { + if (nodes.isEmpty()) nodes.add(interfaceNode); + for (int i = 0, nodesSize = nodes.size(); i < nodesSize; i++) { + final ClassNode node = nodes.get(i); + if (node.equals(interfaceNode)||node.implementsInterface(interfaceNode)) { + // a more specific interface exists in the list, keep it + return; + } + if (interfaceNode.implementsInterface(node)) { + // the interface beeing added is more specific than the one in the list, replace it + nodes.set(i, interfaceNode); + return; + } + } + // if we reach this point, this means the interface is new + nodes.add(interfaceNode); + } + + private static void extractMostSpecificImplementedInterfaces(final ClassNode type, final ClassNode inode, final List<ClassNode> result) { + if (type.implementsInterface(inode)) result.add(inode); + else { + ClassNode[] interfaces = inode.getInterfaces(); + for (ClassNode interfaceNode : interfaces) { + if (type.implementsInterface(interfaceNode)) result.add(interfaceNode); + } + if (result.isEmpty() && interfaces.length>0) { + // none if the direct interfaces match, but we must check "upper" in the hierarchy + for (ClassNode interfaceNode : interfaces) { + extractMostSpecificImplementedInterfaces(type, interfaceNode, result); + } + } + } + } + + /** + * Given two class nodes supposedly at the upper common level, returns a class node which is able to represent + * their lowest upper bound. + * @param baseType1 + * @param baseType2 + * @param interfaces interfaces both class nodes share, which their lowest common super class do not implement. + * @return the class node representing the lowest upper bound + */ + private static ClassNode buildTypeWithInterfaces(ClassNode baseType1, ClassNode baseType2, Collection<ClassNode> interfaces) { + boolean noInterface = interfaces.isEmpty(); + if (noInterface) { + if (baseType1.equals(baseType2)) return baseType1; + if (baseType1.isDerivedFrom(baseType2)) return baseType2; + if (baseType2.isDerivedFrom(baseType1)) return baseType1; + } + if (OBJECT_TYPE.equals(baseType1) && OBJECT_TYPE.equals(baseType2) && interfaces.size()==1) { + if (interfaces instanceof List) { + return ((List<ClassNode>) interfaces).get(0); + } + return interfaces.iterator().next(); + } + LowestUpperBoundClassNode type; + ClassNode superClass; + String name; + if (baseType1.equals(baseType2)) { + if (OBJECT_TYPE.equals(baseType1)) { + superClass = baseType1; + name = "Virtual$Object"; + } else { + superClass = baseType1; + name = "Virtual$"+baseType1.getName(); + } + } else { + superClass = OBJECT_TYPE; + if (baseType1.isDerivedFrom(baseType2)) { + superClass = baseType2; + } else if (baseType2.isDerivedFrom(baseType1)) { + superClass = baseType1; + } + name = "CommonAssignOf$"+baseType1.getName()+"$"+baseType2.getName(); + } + Iterator<ClassNode> itcn = interfaces.iterator(); + while (itcn.hasNext()) { + ClassNode next = itcn.next(); + if (superClass.isDerivedFrom(next) || superClass.implementsInterface(next)) { + itcn.remove(); + } + } + ClassNode[] interfaceArray = interfaces.toArray(new ClassNode[interfaces.size()]); + Arrays.sort(interfaceArray, INTERFACE_CLASSNODE_COMPARATOR); + type = new LowestUpperBoundClassNode(name, superClass, interfaceArray); + return type; + } + + /** + * This {@link ClassNode} specialization is used when the lowest upper bound of two types + * cannot be represented by an existing type. For example, if B extends A, C extends A + * and both C & B implement a common interface not implemented by A, then we use this class + * to represent the bound. + * + * At compile time, some classes like {@link org.codehaus.groovy.classgen.AsmClassGenerator} need + * to know about a real class node, so we compute a "compile time" node which will be used + * to return a name and a type class. + * + */ + public static class LowestUpperBoundClassNode extends ClassNode { + private static final Comparator<ClassNode> CLASS_NODE_COMPARATOR = new Comparator<ClassNode>() { + public int compare(final ClassNode o1, final ClassNode o2) { + String n1 = o1 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o1).name:o1.getName(); + String n2 = o2 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o2).name:o2.getName(); + return n1.compareTo(n2); + } + }; + private final ClassNode compileTimeClassNode; + private final String name; + private final String text; + + private final ClassNode upper; + private final ClassNode[] interfaces; + + public LowestUpperBoundClassNode(String name, ClassNode upper, ClassNode... interfaces) { + super(name, ACC_PUBLIC|ACC_FINAL, upper, interfaces, null); + this.upper = upper; + this.interfaces = interfaces; + boolean usesGenerics; + Arrays.sort(interfaces, CLASS_NODE_COMPARATOR); + compileTimeClassNode = upper.equals(OBJECT_TYPE) && interfaces.length>0?interfaces[0]:upper; + this.name = name; + usesGenerics = upper.isUsingGenerics(); + List<GenericsType[]> genericsTypesList = new LinkedList<GenericsType[]>(); + genericsTypesList.add(upper.getGenericsTypes()); + for (ClassNode anInterface : interfaces) { + usesGenerics |= anInterface.isUsingGenerics(); + genericsTypesList.add(anInterface.getGenericsTypes()); + for (MethodNode methodNode : anInterface.getMethods()) { + MethodNode method = addMethod(methodNode.getName(), methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode()); + method.setDeclaringClass(anInterface); // important for static compilation! + } + } + setUsingGenerics(usesGenerics); + if (usesGenerics) { + List<GenericsType> asArrayList = new ArrayList<GenericsType>(); + for (GenericsType[] genericsTypes : genericsTypesList) { + if (genericsTypes!=null) { + Collections.addAll(asArrayList, genericsTypes); + } + } + setGenericsTypes(asArrayList.toArray(new GenericsType[asArrayList.size()])); + } + StringBuilder sb = new StringBuilder(); + if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getName()); + for (ClassNode anInterface : interfaces) { + if (sb.length()>0) { + sb.append(" or "); + } + sb.append(anInterface.getName()); + } + this.text = sb.toString(); + } + + public String getLubName() { + return this.name; + } + + @Override + public String getName() { + return compileTimeClassNode.getName(); + } + + @Override + public Class getTypeClass() { + return compileTimeClassNode.getTypeClass(); + } + + @Override + public int hashCode() { + int result = super.hashCode(); +// result = 31 * result + (compileTimeClassNode != null ? compileTimeClassNode.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String getText() { + return text; + } + + @Override + public ClassNode getPlainNodeReference() { + ClassNode[] intf = interfaces==null?null:new ClassNode[interfaces.length]; + if (intf!=null) { + for (int i = 0; i < interfaces.length; i++) { + intf[i] = interfaces[i].getPlainNodeReference(); + } + } + LowestUpperBoundClassNode plain = new LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), intf); + return plain; + } + } + + /** + * Compares two class nodes, but including their generics types. + * @param a + * @param b + * @return true if the class nodes are equal, false otherwise + */ + private static boolean areEqualWithGenerics(ClassNode a, ClassNode b) { + if (a==null) return b==null; + if (!a.equals(b)) return false; + if (a.isUsingGenerics() && !b.isUsingGenerics()) return false; + GenericsType[] gta = a.getGenericsTypes(); + GenericsType[] gtb = b.getGenericsTypes(); + if (gta==null && gtb!=null) return false; + if (gtb==null && gta!=null) return false; + if (gta!=null && gtb!=null) { + if (gta.length!=gtb.length) return false; + for (int i = 0; i < gta.length; i++) { + GenericsType ga = gta[i]; + GenericsType gb = gtb[i]; + boolean result = ga.isPlaceholder()==gb.isPlaceholder() && ga.isWildcard()==gb.isWildcard(); + result = result && ga.isResolved() && gb.isResolved(); + result = result && ga.getName().equals(gb.getName()); + result = result && areEqualWithGenerics(ga.getType(), gb.getType()); + result = result && areEqualWithGenerics(ga.getLowerBound(), gb.getLowerBound()); + if (result) { + ClassNode[] upA = ga.getUpperBounds(); + if (upA!=null) { + ClassNode[] upB = gb.getUpperBounds(); + if (upB==null || upB.length!=upA.length) return false; + for (int j = 0; j < upA.length; j++) { + if (!areEqualWithGenerics(upA[j],upB[j])) return false; + } + } + } + if (!result) return false; + } + } + return true; + } + + /** + * Determines if the source class implements an interface or subclasses the target type. + * This method takes the {@link org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode lowest + * upper bound class node} type into account, allowing to remove unnecessary casts. + * @param source the type of interest + * @param targetType the target type of interest + */ + public static boolean implementsInterfaceOrSubclassOf(final ClassNode source, final ClassNode targetType) { + if (source.isDerivedFrom(targetType) || source.implementsInterface(targetType)) return true; + if (targetType instanceof WideningCategories.LowestUpperBoundClassNode) { + WideningCategories.LowestUpperBoundClassNode lub = (WideningCategories.LowestUpperBoundClassNode) targetType; + if (implementsInterfaceOrSubclassOf(source, lub.getSuperClass())) return true; + for (ClassNode classNode : lub.getInterfaces()) { + if (source.implementsInterface(classNode)) return true; + } + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java new file mode 100644 index 0000000..e514b59 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java @@ -0,0 +1,344 @@ +/* + * 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.classgen; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; +import org.codehaus.groovy.vmplugin.VMPluginFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Map; + +/** + * An Annotation visitor responsible for: + * <ul> + * <li>reading annotation metadata (@Retention, @Target, attribute types)</li> + * <li>verify that an <code>AnnotationNode</code> conforms to annotation meta</li> + * <li>enhancing an <code>AnnotationNode</code> AST to reflect real annotation meta</li> + * </ul> + */ +public class AnnotationVisitor { + private final SourceUnit source; + private final ErrorCollector errorCollector; + private AnnotationNode annotation; + private ClassNode reportClass; + + public AnnotationVisitor(SourceUnit source, ErrorCollector errorCollector) { + this.source = source; + this.errorCollector = errorCollector; + } + + public void setReportClass(ClassNode cn) { + reportClass = cn; + } + + public AnnotationNode visit(AnnotationNode node) { + this.annotation = node; + this.reportClass = node.getClassNode(); + + if (!isValidAnnotationClass(node.getClassNode())) { + addError("class " + node.getClassNode().getName() + " is not an annotation"); + return node; + } + + // check if values have been passed for all annotation attributes that don't have defaults + if (!checkIfMandatoryAnnotationValuesPassed(node)) { + return node; + } + + // if enum constants have been used, check if they are all valid + if (!checkIfValidEnumConstsAreUsed(node)) { + return node; + } + + Map<String, Expression> attributes = node.getMembers(); + for (Map.Entry<String, Expression> entry : attributes.entrySet()) { + String attrName = entry.getKey(); + Expression attrExpr = transformInlineConstants(entry.getValue()); + entry.setValue(attrExpr); + ClassNode attrType = getAttributeType(node, attrName); + visitExpression(attrName, attrExpr, attrType); + } + VMPluginFactory.getPlugin().configureAnnotation(node); + return this.annotation; + } + + private boolean checkIfValidEnumConstsAreUsed(AnnotationNode node) { + Map<String, Expression> attributes = node.getMembers(); + for (Map.Entry<String, Expression> entry : attributes.entrySet()) { + if (!validateEnumConstant(entry.getValue())) + return false; + } + return true; + } + + private boolean validateEnumConstant(Expression exp) { + if (exp instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) exp; + String name = pe.getPropertyAsString(); + if (pe.getObjectExpression() instanceof ClassExpression && name != null) { + ClassExpression ce = (ClassExpression) pe.getObjectExpression(); + ClassNode type = ce.getType(); + if (type.isEnum()) { + boolean ok = false; + try { + FieldNode enumField = type.getDeclaredField(name); + ok = enumField != null && enumField.getType().equals(type); + } catch(Exception ex) { + // ignore + } + if(!ok) { + addError("No enum const " + type.getName() + "." + name, pe); + return false; + } + } + } + } + return true; + } + + private Expression transformInlineConstants(Expression exp) { + if (exp instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) exp; + if (pe.getObjectExpression() instanceof ClassExpression) { + ClassExpression ce = (ClassExpression) pe.getObjectExpression(); + ClassNode type = ce.getType(); + if (type.isEnum() || !type.isResolved()) + return exp; + + try { + Field field = type.getTypeClass().getField(pe.getPropertyAsString()); + if (field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { + return new ConstantExpression(field.get(null)); + } + } catch(Exception e) { + // ignore, leave property expression in place and we'll report later + } + } + } else if (exp instanceof ListExpression) { + ListExpression le = (ListExpression) exp; + ListExpression result = new ListExpression(); + for (Expression e : le.getExpressions()) { + result.addExpression(transformInlineConstants(e)); + } + return result; + } + return exp; + } + + private boolean checkIfMandatoryAnnotationValuesPassed(AnnotationNode node) { + boolean ok = true; + Map attributes = node.getMembers(); + ClassNode classNode = node.getClassNode(); + for (MethodNode mn : classNode.getMethods()) { + String methodName = mn.getName(); + // if the annotation attribute has a default, getCode() returns a ReturnStatement with the default value + if (mn.getCode() == null && !attributes.containsKey(methodName)) { + addError("No explicit/default value found for annotation attribute '" + methodName + "'", node); + ok = false; + } + } + return ok; + } + + private ClassNode getAttributeType(AnnotationNode node, String attrName) { + ClassNode classNode = node.getClassNode(); + List methods = classNode.getMethods(attrName); + // if size is >1, then the method was overwritten or something, we ignore that + // if it is an error, we have to test it at another place. But size==0 is + // an error, because it means that no such attribute exists. + if (methods.isEmpty()) { + addError("'" + attrName + "'is not part of the annotation " + classNode, node); + return ClassHelper.OBJECT_TYPE; + } + MethodNode method = (MethodNode) methods.get(0); + return method.getReturnType(); + } + + private static boolean isValidAnnotationClass(ClassNode node) { + return node.implementsInterface(ClassHelper.Annotation_TYPE); + } + + protected void visitExpression(String attrName, Expression attrExp, ClassNode attrType) { + if (attrType.isArray()) { + // check needed as @Test(attr = {"elem"}) passes through the parser + if (attrExp instanceof ListExpression) { + ListExpression le = (ListExpression) attrExp; + visitListExpression(attrName, le, attrType.getComponentType()); + } else if (attrExp instanceof ClosureExpression) { + addError("Annotation list attributes must use Groovy notation [el1, el2]", attrExp); + } else { + // treat like a singleton list as per Java + ListExpression listExp = new ListExpression(); + listExp.addExpression(attrExp); + if (annotation != null) { + annotation.setMember(attrName, listExp); + } + visitExpression(attrName, listExp, attrType); + } + } else if (ClassHelper.isPrimitiveType(attrType)) { + visitConstantExpression(attrName, getConstantExpression(attrExp, attrType), ClassHelper.getWrapper(attrType)); + } else if (ClassHelper.STRING_TYPE.equals(attrType)) { + visitConstantExpression(attrName, getConstantExpression(attrExp, attrType), ClassHelper.STRING_TYPE); + } else if (ClassHelper.CLASS_Type.equals(attrType)) { + if (!(attrExp instanceof ClassExpression || attrExp instanceof ClosureExpression)) { + addError("Only classes and closures can be used for attribute '" + attrName + "'", attrExp); + } + } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) { + if (attrExp instanceof PropertyExpression) { + visitEnumExpression(attrName, (PropertyExpression) attrExp, attrType); + } else { + addError("Expected enum value for attribute " + attrName, attrExp); + } + } else if (isValidAnnotationClass(attrType)) { + if (attrExp instanceof AnnotationConstantExpression) { + visitAnnotationExpression(attrName, (AnnotationConstantExpression) attrExp, attrType); + } else { + addError("Expected annotation of type '" + attrType.getName() + "' for attribute " + attrName, attrExp); + } + } else { + addError("Unexpected type " + attrType.getName(), attrExp); + } + } + + public void checkReturnType(ClassNode attrType, ASTNode node) { + if (attrType.isArray()) { + checkReturnType(attrType.getComponentType(), node); + } else if (ClassHelper.isPrimitiveType(attrType)) { + return; + } else if (ClassHelper.STRING_TYPE.equals(attrType)) { + return; + } else if (ClassHelper.CLASS_Type.equals(attrType)) { + return; + } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) { + return; + } else if (isValidAnnotationClass(attrType)) { + return; + } else { + addError("Unexpected return type " + attrType.getName(), node); + } + } + + private ConstantExpression getConstantExpression(Expression exp, ClassNode attrType) { + if (exp instanceof ConstantExpression) { + return (ConstantExpression) exp; + } + String base = "Expected '" + exp.getText() + "' to be an inline constant of type " + attrType.getName(); + if (exp instanceof PropertyExpression) { + addError(base + " not a property expression", exp); + } else if (exp instanceof VariableExpression && ((VariableExpression)exp).getAccessedVariable() instanceof FieldNode) { + addError(base + " not a field expression", exp); + } else { + addError(base, exp); + } + return ConstantExpression.EMPTY_EXPRESSION; + } + + /** + * @param attrName the name + * @param expression the expression + * @param attrType the type + */ + protected void visitAnnotationExpression(String attrName, AnnotationConstantExpression expression, ClassNode attrType) { + AnnotationNode annotationNode = (AnnotationNode) expression.getValue(); + AnnotationVisitor visitor = new AnnotationVisitor(this.source, this.errorCollector); + // TODO track Deprecated usage and give a warning? + visitor.visit(annotationNode); + } + + protected void visitListExpression(String attrName, ListExpression listExpr, ClassNode elementType) { + for (Expression expression : listExpr.getExpressions()) { + visitExpression(attrName, expression, elementType); + } + } + + protected void visitConstantExpression(String attrName, ConstantExpression constExpr, ClassNode attrType) { + ClassNode constType = constExpr.getType(); + ClassNode wrapperType = ClassHelper.getWrapper(constType); + if (!hasCompatibleType(attrType, wrapperType)) { + addError("Attribute '" + attrName + "' should have type '" + attrType.getName() + + "'; but found type '" + constType.getName() + "'", constExpr); + } + } + + private static boolean hasCompatibleType(ClassNode attrType, ClassNode wrapperType) { + return wrapperType.isDerivedFrom(ClassHelper.getWrapper(attrType)); + } + + protected void visitEnumExpression(String attrName, PropertyExpression propExpr, ClassNode attrType) { + if (!propExpr.getObjectExpression().getType().isDerivedFrom(attrType)) { + addError("Attribute '" + attrName + "' should have type '" + attrType.getName() + "' (Enum), but found " + + propExpr.getObjectExpression().getType().getName(), + propExpr); + } + } + + protected void addError(String msg) { + addError(msg, this.annotation); + } + + protected void addError(String msg, ASTNode expr) { + this.errorCollector.addErrorAndContinue( + new SyntaxErrorMessage(new SyntaxException(msg + " in @" + this.reportClass.getName() + '\n', expr.getLineNumber(), expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), this.source) + ); + } + + public void checkCircularReference(ClassNode searchClass, ClassNode attrType, Expression startExp) { + if (!isValidAnnotationClass(attrType)) return; + if (!(startExp instanceof AnnotationConstantExpression)) { + addError("Found '" + startExp.getText() + "' when expecting an Annotation Constant", startExp); + return; + } + AnnotationConstantExpression ace = (AnnotationConstantExpression) startExp; + AnnotationNode annotationNode = (AnnotationNode) ace.getValue(); + if (annotationNode.getClassNode().equals(searchClass)) { + addError("Circular reference discovered in " + searchClass.getName(), startExp); + return; + } + ClassNode cn = annotationNode.getClassNode(); + for (MethodNode method : cn.getMethods()) { + if (method.getReturnType().equals(searchClass)) { + addError("Circular reference discovered in " + cn.getName(), startExp); + } + ReturnStatement code = (ReturnStatement) method.getCode(); + if (code == null) continue; + checkCircularReference(searchClass, method.getReturnType(), code.getExpression()); + } + } + +}
