| /* |
| * 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; |
| |
| import org.apache.groovy.ast.tools.ClassNodeUtils; |
| import org.apache.groovy.lang.annotation.Incubating; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.expr.BinaryExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.FieldExpression; |
| import org.codehaus.groovy.ast.expr.TupleExpression; |
| import org.codehaus.groovy.ast.stmt.BlockStatement; |
| import org.codehaus.groovy.ast.stmt.ExpressionStatement; |
| import org.codehaus.groovy.ast.stmt.Statement; |
| import org.codehaus.groovy.ast.tools.ParameterUtils; |
| import org.codehaus.groovy.control.CompilePhase; |
| import org.codehaus.groovy.transform.ASTTransformation; |
| import org.codehaus.groovy.transform.GroovyASTTransformation; |
| import org.codehaus.groovy.vmplugin.VMPluginFactory; |
| |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| import static java.util.Objects.requireNonNull; |
| import static java.util.stream.Collectors.toList; |
| import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock; |
| import static org.codehaus.groovy.ast.ClassHelper.SEALED_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.isObjectType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid; |
| import static org.codehaus.groovy.transform.RecordTypeASTTransformation.recordNative; |
| import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; |
| import static org.objectweb.asm.Opcodes.ACC_ANNOTATION; |
| import static org.objectweb.asm.Opcodes.ACC_ENUM; |
| import static org.objectweb.asm.Opcodes.ACC_INTERFACE; |
| import static org.objectweb.asm.Opcodes.ACC_PUBLIC; |
| import static org.objectweb.asm.Opcodes.ACC_STATIC; |
| import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; |
| |
| /** |
| * Represents a class in the AST. |
| * <p> |
| * A ClassNode should be created using the methods in ClassHelper. |
| * This ClassNode may be used to represent a class declaration or |
| * any other type. This class uses a proxy mechanism allowing to |
| * create a class for a plain name at AST creation time. In another |
| * phase of the compiler the real ClassNode for the plain name may be |
| * found. To avoid the need of exchanging this ClassNode with an |
| * instance of the correct ClassNode the correct ClassNode is set as |
| * redirect. Most method calls are then redirected to that ClassNode. |
| * <p> |
| * There are three types of ClassNodes: |
| * <ol> |
| * <li> Primary ClassNodes:<br> |
| * A primary ClassNode is one where we have a source representation |
| * which is to be compiled by Groovy and which we have an AST for. |
| * The groovy compiler will output one class for each such ClassNode |
| * that passes through AsmBytecodeGenerator... not more, not less. |
| * That means for example Closures become such ClassNodes too at |
| * some point. |
| * <li> ClassNodes create through different sources (typically created |
| * from a java.lang.reflect.Class object):<br> |
| * The compiler will not output classes from these, the methods |
| * usually do not contain bodies. These kind of ClassNodes will be |
| * used in different checks, but not checks that work on the method |
| * bodies. For example if such a ClassNode is a super class to a primary |
| * ClassNode, then the abstract method test and others will be done |
| * with data based on these. Theoretically it is also possible to mix both |
| * (1 and 2) kind of classes in a hierarchy, but this probably works only |
| * in the newest Groovy versions. Such ClassNodes normally have to |
| * isResolved() returning true without having a redirect.In the Groovy |
| * compiler the only version of this, that exists, is a ClassNode created |
| * through a Class instance |
| * <li> Labels:<br> |
| * ClassNodes created through ClassHelper.makeWithoutCaching. They |
| * are placeholders, its redirect points to the real structure, which can |
| * be a label too, but following all redirects it should end with a ClassNode |
| * from one of the other two categories. If ResolveVisitor finds such a |
| * node, it tries to set the redirects. Any such label created after |
| * ResolveVisitor has done its work needs to have a redirect pointing to |
| * case 1 or 2. If not the compiler may react strange... this can be considered |
| * as a kind of dangling pointer. |
| * </ol> |
| * <b>Note:</b> the redirect mechanism is only allowed for classes |
| * that are not primary ClassNodes. Typically this is done for classes |
| * created by name only. The redirect itself can be any type of ClassNode. |
| * <p> |
| * To describe generic type signature see {@link #getGenericsTypes()} and |
| * {@link #setGenericsTypes(GenericsType[])}. These methods are not proxied, |
| * they describe the type signature used at the point of declaration or the |
| * type signatures provided by the class. If the type signatures provided |
| * by the class are needed, then a call to {@link #redirect()} will help. |
| * |
| * @see org.codehaus.groovy.ast.ClassHelper |
| */ |
| public class ClassNode extends AnnotatedNode { |
| |
| private static class MapOfLists { |
| Map<Object, List<MethodNode>> map; |
| |
| List<MethodNode> get(Object key) { |
| return Optional.ofNullable(map) |
| .map(m -> m.get(key)).orElseGet(Collections::emptyList); |
| } |
| |
| void put(Object key, MethodNode value) { |
| if (map == null) map = new LinkedHashMap<>(); |
| map.computeIfAbsent(key, k -> new ArrayList<>(2)).add(value); |
| } |
| |
| void remove(Object key, MethodNode value) { |
| get(key).remove(value); |
| } |
| } |
| |
| public static final ClassNode[] EMPTY_ARRAY = new ClassNode[0]; |
| public static final ClassNode THIS = new ClassNode(Object.class); |
| public static final ClassNode SUPER = new ClassNode(Object.class); |
| |
| private String name; |
| private int modifiers; |
| private boolean syntheticPublic; |
| private ClassNode[] interfaces; |
| private MixinNode[] mixins; |
| private List<Statement> objectInitializers; |
| private List<ConstructorNode> constructors; |
| // TODO: initialize for primary nodes only! |
| private final MapOfLists methods = new MapOfLists(); |
| private List<MethodNode> methodsList = Collections.emptyList(); |
| private List<FieldNode> fields; |
| private List<PropertyNode> properties; |
| private Map<String, FieldNode> fieldIndex; |
| private ModuleNode module; |
| private CompileUnit compileUnit; |
| private boolean staticClass; |
| private boolean scriptBody; |
| private boolean script; |
| private ClassNode superClass; |
| protected boolean isPrimaryNode; |
| protected List<InnerClassNode> innerClasses; |
| // TODO: initialize for primary nodes only!! |
| private List<ClassNode> permittedSubclasses = new ArrayList<>(); |
| private List<AnnotationNode> typeAnnotations = Collections.emptyList(); |
| private List<RecordComponentNode> recordComponents = Collections.emptyList(); |
| |
| /** |
| * The AST Transformations to be applied during compilation. |
| */ |
| private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> transformInstances; |
| |
| // use this to synchronize access for the lazy init |
| protected final Object lazyInitLock = new Object(); |
| // only false when this classNode is constructed from a class |
| private volatile boolean lazyInitDone = true; |
| |
| // clazz!=null when resolved |
| protected Class<?> clazz; |
| // not null if the ClassNode is an array |
| private ClassNode componentType; |
| // if not null this instance is handled as proxy |
| // for the redirect |
| private ClassNode redirect; |
| // flag if the classes or its members are annotated |
| private boolean annotated; |
| |
| // type spec for generics |
| private GenericsType[] genericsTypes; |
| private boolean usesGenerics; |
| |
| // if set to true the name getGenericsTypes consists |
| // of 1 element describing the name of the placeholder |
| private boolean placeholder; |
| |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @param name the fully-qualified name of the class |
| * @param modifiers the modifiers; see {@link java.lang.reflect.Modifier Modifier} or {@link org.objectweb.asm.Opcodes Opcodes} |
| * @param superClass the base class; use "java.lang.Object" if no direct base class |
| * @param interfaces the interfaces |
| * @param mixins the mixins |
| */ |
| public ClassNode(final String name, final int modifiers, final ClassNode superClass, final ClassNode[] interfaces, final MixinNode[] mixins) { |
| this.name = name; |
| this.modifiers = modifiers; |
| |
| this.isPrimaryNode = true; |
| setSuperClass(superClass); |
| setInterfaces(interfaces); |
| setMixins(mixins); |
| } |
| |
| /** |
| * @param name the fully-qualified name of the class |
| * @param modifiers the modifiers; see {@link java.lang.reflect.Modifier Modifier} or {@link org.objectweb.asm.Opcodes Opcodes} |
| * @param superClass the base class; use "java.lang.Object" if no direct base class |
| */ |
| public ClassNode(final String name, final int modifiers, final ClassNode superClass) { |
| this(name, modifiers, superClass, ClassNode.EMPTY_ARRAY, MixinNode.EMPTY_ARRAY); |
| } |
| |
| /** |
| * Creates a non-primary {@code ClassNode} from a real class. |
| */ |
| public ClassNode(final Class<?> c) { |
| this(c.getName(), c.getModifiers(), null, null, MixinNode.EMPTY_ARRAY); |
| this.clazz = c; |
| this.lazyInitDone = false; |
| this.isPrimaryNode = false; |
| } |
| |
| /** |
| * Constructor used by {@code makeArray()} if a real class is available. |
| */ |
| private ClassNode(final Class<?> c, final ClassNode componentType) { |
| this(c); |
| this.componentType = componentType; |
| } |
| |
| /** |
| * Constructor used by {@code makeArray()} if no real class is available. |
| */ |
| private ClassNode(final ClassNode componentType) { |
| this(componentType.getName() + "[]", ACC_PUBLIC, ClassHelper.OBJECT_TYPE); |
| this.componentType = componentType.redirect(); |
| this.isPrimaryNode = false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Returns the {@code ClassNode} this node is a proxy for or the node itself. |
| */ |
| public ClassNode redirect() { |
| return (redirect == null ? this : redirect.redirect()); |
| } |
| |
| public boolean isRedirectNode() { |
| return (redirect != null); |
| } |
| |
| /** |
| * Sets this instance as proxy for the given {@code ClassNode}. |
| * |
| * @param node the class to redirect to; if {@code null} the redirect is removed |
| */ |
| public void setRedirect(ClassNode node) { |
| if (isPrimaryNode) throw new GroovyBugError("tried to set a redirect for a primary ClassNode (" + getName() + "->" + node.getName() + ")."); |
| if (node != null && !isGenericsPlaceHolder()) node = node.redirect(); |
| if (node == this) return; |
| redirect = node; |
| } |
| |
| /** |
| * Returns a {@code ClassNode} representing an array of the type represented |
| * by this. |
| */ |
| public ClassNode makeArray() { |
| ClassNode node; |
| if (redirect != null) { |
| node = redirect.makeArray(); |
| node.componentType = this; |
| } else if (clazz != null) { |
| Class<?> type = Array.newInstance(clazz, 0).getClass(); |
| // don't use the ClassHelper here! |
| node = new ClassNode(type, this); |
| } else { |
| node = new ClassNode(this); |
| } |
| return node; |
| } |
| |
| /** |
| * @return {@code true} if this instance is a primary {@code ClassNode} |
| */ |
| public boolean isPrimaryClassNode() { |
| return redirect().isPrimaryNode || (componentType != null && componentType.isPrimaryClassNode()); |
| } |
| |
| /** |
| * The complete class structure will be initialized only when really needed |
| * to avoid having too many objects during compilation. |
| */ |
| private void lazyClassInit() { |
| if (lazyInitDone) return; |
| synchronized (lazyInitLock) { |
| if (redirect != null) { |
| throw new GroovyBugError("lazyClassInit called on a proxy ClassNode, that must not happen. " + |
| "A redirect() call is missing somewhere!"); |
| } |
| if (lazyInitDone) return; |
| VMPluginFactory.getPlugin().configureClassNode(compileUnit, this); |
| lazyInitDone = true; |
| } |
| } |
| |
| /** |
| * Tracks the enclosing method for local inner classes. |
| */ |
| private MethodNode enclosingMethod; |
| |
| public MethodNode getEnclosingMethod() { |
| return redirect().enclosingMethod; |
| } |
| |
| public void setEnclosingMethod(MethodNode enclosingMethod) { |
| redirect().enclosingMethod = enclosingMethod; |
| } |
| |
| /** |
| * Indicates that this class has been "promoted" to public by Groovy when in |
| * fact there was no public modifier explicitly in the source code. That is, |
| * it remembers that it has applied Groovy's "public classes by default" rule. |
| * This property is typically only of interest to AST transform writers. |
| * |
| * @return {@code true} if node is public but had no explicit public modifier |
| */ |
| public boolean isSyntheticPublic() { |
| return syntheticPublic; |
| } |
| |
| public void setSyntheticPublic(boolean syntheticPublic) { |
| this.syntheticPublic = syntheticPublic; |
| } |
| |
| public void setSuperClass(final ClassNode superClass) { |
| if (redirect != null) { |
| redirect.setSuperClass(superClass); |
| } else { |
| this.superClass = superClass; |
| // GROOVY-10763: update generics indicator |
| if (superClass != null && !usesGenerics && isPrimaryNode) { |
| usesGenerics = superClass.isUsingGenerics(); |
| } |
| } |
| } |
| |
| /** |
| * @return the fields associated with this {@code ClassNode} |
| */ |
| public List<FieldNode> getFields() { |
| if (redirect != null) |
| return redirect.getFields(); |
| lazyClassInit(); |
| if (fields == null) |
| fields = new ArrayList<>(); |
| return fields; |
| } |
| |
| /** |
| * @return the interfaces implemented by this {@code ClassNode} |
| */ |
| public ClassNode[] getInterfaces() { |
| if (redirect != null) |
| return redirect.getInterfaces(); |
| lazyClassInit(); |
| return interfaces; |
| } |
| |
| public void setInterfaces(final ClassNode[] interfaces) { |
| if (redirect != null) { |
| redirect.setInterfaces(interfaces); |
| } else { |
| this.interfaces = interfaces; |
| // GROOVY-10763: update generics indicator |
| if (interfaces != null && !usesGenerics && isPrimaryNode) { |
| for (int i = 0, n = interfaces.length; i < n; i += 1) { |
| usesGenerics |= interfaces[i].isUsingGenerics(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return permitted subclasses of sealed type, may initially be empty in early compiler phases |
| */ |
| @Incubating |
| public List<ClassNode> getPermittedSubclasses() { |
| if (redirect != null) |
| return redirect.getPermittedSubclasses(); |
| lazyClassInit(); |
| return permittedSubclasses; |
| } |
| |
| @Incubating |
| public void setPermittedSubclasses(List<ClassNode> permittedSubclasses) { |
| if (redirect != null) { |
| redirect.setPermittedSubclasses(permittedSubclasses); |
| } else { |
| this.permittedSubclasses = permittedSubclasses; |
| } |
| } |
| |
| /** |
| * @return the mixins associated with this {@code ClassNode} |
| */ |
| public MixinNode[] getMixins() { |
| return redirect().mixins; |
| } |
| |
| public void setMixins(final MixinNode[] mixins) { |
| if (redirect != null) { |
| redirect.setMixins(mixins); |
| } else { |
| this.mixins = mixins; |
| } |
| } |
| |
| /** |
| * @return the methods associated with this {@code ClassNode} |
| */ |
| public List<MethodNode> getMethods() { |
| if (redirect != null) |
| return redirect.getMethods(); |
| lazyClassInit(); |
| return methodsList; |
| } |
| |
| /** |
| * @return the abstract methods associated with this {@code ClassNode} |
| */ |
| public List<MethodNode> getAbstractMethods() { |
| return getDeclaredMethodsMap().values().stream() |
| .filter(MethodNode::isAbstract).collect(toList()); |
| } |
| |
| public List<MethodNode> getAllDeclaredMethods() { |
| return new ArrayList<>(getDeclaredMethodsMap().values()); |
| } |
| |
| public Set<ClassNode> getAllInterfaces() { |
| Set<ClassNode> result = new LinkedHashSet<>(); |
| if (isInterface()) result.add(this); |
| getAllInterfaces(result); |
| return result; |
| } |
| |
| private void getAllInterfaces(Set<ClassNode> set) { |
| for (ClassNode face : getInterfaces()) { |
| if (set.add(face)) // GROOVY-11036 |
| face.getAllInterfaces(set); |
| } |
| } |
| |
| public Map<String, MethodNode> getDeclaredMethodsMap() { |
| Map<String, MethodNode> result = ClassNodeUtils.getDeclaredMethodsFromSuper(this); |
| ClassNodeUtils.addDeclaredMethodsFromInterfaces(this, result); |
| // add in the methods implemented in this class |
| for (MethodNode method : getMethods()) { |
| result.put(method.getTypeDescriptor(), method); |
| } |
| return result; |
| } |
| |
| public String getName() { |
| return redirect().name; |
| } |
| |
| public String getUnresolvedName() { |
| return name; |
| } |
| |
| public String setName(String name) { |
| return redirect().name = name; |
| } |
| |
| public int getModifiers() { |
| return redirect().modifiers; |
| } |
| |
| public void setModifiers(int modifiers) { |
| redirect().modifiers = modifiers; |
| } |
| |
| public List<PropertyNode> getProperties() { |
| if (redirect != null) |
| return redirect.getProperties(); |
| if (properties == null) |
| properties = new ArrayList<>(); |
| return properties; |
| } |
| |
| public List<ConstructorNode> getDeclaredConstructors() { |
| if (redirect != null) |
| return redirect.getDeclaredConstructors(); |
| lazyClassInit(); |
| if (constructors == null) |
| constructors = new ArrayList<>(); |
| return constructors; |
| } |
| |
| /** |
| * @return the constructor matching the given parameters or {@code null} |
| */ |
| public ConstructorNode getDeclaredConstructor(Parameter[] parameters) { |
| for (ConstructorNode constructor : getDeclaredConstructors()) { |
| if (parametersEqual(constructor.getParameters(), parameters)) { |
| return constructor; |
| } |
| } |
| return null; |
| } |
| |
| public void removeConstructor(ConstructorNode node) { |
| getDeclaredConstructors().remove(node); |
| } |
| |
| public ModuleNode getModule() { |
| return redirect().module; |
| } |
| |
| public PackageNode getPackage() { |
| return Optional.ofNullable(getModule()).map(ModuleNode::getPackage).orElse(null); |
| } |
| |
| public void setModule(ModuleNode module) { |
| redirect().module = module; |
| if (module != null) { |
| redirect().compileUnit = module.getUnit(); |
| } |
| } |
| |
| public void addField(FieldNode node) { |
| addField(node, false); |
| } |
| |
| public void addFieldFirst(FieldNode node) { |
| addField(node, true); |
| } |
| |
| private void addField(FieldNode node, boolean isFirst) { |
| ClassNode r = redirect(); |
| node.setDeclaringClass(r); |
| node.setOwner(r); |
| if (r.fields == null) |
| r.fields = new ArrayList<>(); |
| if (r.fieldIndex == null) |
| r.fieldIndex = new LinkedHashMap<>(); |
| |
| if (isFirst) { |
| r.fields.add(0, node); |
| } else { |
| r.fields.add(node); |
| } |
| r.fieldIndex.put(node.getName(), node); |
| } |
| |
| public Map<String, FieldNode> getFieldIndex() { |
| return fieldIndex; |
| } |
| |
| public void addProperty(PropertyNode node) { |
| node.setDeclaringClass(redirect()); |
| addField(node.getField()); |
| getProperties().add(node); |
| } |
| |
| public PropertyNode addProperty(String name, |
| int modifiers, |
| ClassNode type, |
| Expression initialValueExpression, |
| Statement getterBlock, |
| Statement setterBlock) { |
| for (PropertyNode pn : getProperties()) { |
| if (pn.getName().equals(name)) { |
| if (pn.getInitialExpression() == null && initialValueExpression != null) |
| pn.getField().setInitialValueExpression(initialValueExpression); |
| |
| if (pn.getGetterBlock() == null && getterBlock != null) |
| pn.setGetterBlock(getterBlock); |
| |
| if (pn.getSetterBlock() == null && setterBlock != null) |
| pn.setSetterBlock(setterBlock); |
| |
| return pn; |
| } |
| } |
| PropertyNode node = |
| new PropertyNode(name, modifiers, type, redirect(), initialValueExpression, getterBlock, setterBlock); |
| addProperty(node); |
| return node; |
| } |
| |
| public boolean hasProperty(String name) { |
| return getProperties().stream().map(PropertyNode::getName).anyMatch(name::equals); |
| } |
| |
| public PropertyNode getProperty(String name) { |
| return getProperties().stream().filter(pn -> pn.getName().equals(name)).findFirst().orElse(null); |
| } |
| |
| public void addConstructor(ConstructorNode node) { |
| ClassNode r = redirect(); |
| node.setDeclaringClass(r); |
| if (r.constructors == null) |
| r.constructors = new ArrayList<>(); |
| r.constructors.add(node); |
| } |
| |
| public ConstructorNode addConstructor(int modifiers, Parameter[] parameters, ClassNode[] exceptions, Statement code) { |
| ConstructorNode node = new ConstructorNode(modifiers, parameters, exceptions, code); |
| addConstructor(node); |
| return node; |
| } |
| |
| public void addMethod(MethodNode node) { |
| ClassNode r = redirect(); |
| node.setDeclaringClass(r); |
| if (r.methodsList.isEmpty()) { |
| r.methodsList = new ArrayList<>(); |
| } |
| r.methodsList.add(node); |
| r.methods.put(node.getName(), node); |
| } |
| |
| public void removeMethod(MethodNode node) { |
| ClassNode r = redirect(); |
| if (!r.methodsList.isEmpty()) { |
| r.methodsList.remove(node); |
| } |
| r.methods.remove(node.getName(), node); |
| } |
| |
| /** |
| * If a method with the given name and parameters is already defined then it is returned |
| * otherwise the given method is added to this node. This method is useful for |
| * default method adding like getProperty() or invokeMethod() where there may already |
| * be a method defined in a class and so the default implementations should not be added |
| * if already present. |
| */ |
| public MethodNode addMethod(String name, |
| int modifiers, |
| ClassNode returnType, |
| Parameter[] parameters, |
| ClassNode[] exceptions, |
| Statement code) { |
| MethodNode other = getDeclaredMethod(name, parameters); |
| // don't add duplicate methods |
| if (other != null) { |
| return other; |
| } |
| MethodNode node = new MethodNode(name, modifiers, returnType, parameters, exceptions, code); |
| addMethod(node); |
| return node; |
| } |
| |
| /** |
| * @see #getDeclaredMethod(String, Parameter[]) |
| */ |
| public boolean hasDeclaredMethod(String name, Parameter[] parameters) { |
| return (getDeclaredMethod(name, parameters) != null); |
| } |
| |
| /** |
| * @see #getMethod(String, Parameter[]) |
| */ |
| public boolean hasMethod(String name, Parameter[] parameters) { |
| return (getMethod(name, parameters) != null); |
| } |
| |
| /** |
| * Adds a synthetic method as part of the compilation process. |
| */ |
| public MethodNode addSyntheticMethod(String name, int modifiers, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code) { |
| MethodNode node = addMethod(name, modifiers | ACC_SYNTHETIC, returnType, parameters, exceptions, code); |
| node.setSynthetic(true); |
| return node; |
| } |
| |
| public FieldNode addField(String name, int modifiers, ClassNode type, Expression initialValue) { |
| FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue); |
| addField(node); |
| return node; |
| } |
| |
| public FieldNode addFieldFirst(String name, int modifiers, ClassNode type, Expression initialValue) { |
| FieldNode node = new FieldNode(name, modifiers, type, redirect(), initialValue); |
| addFieldFirst(node); |
| return node; |
| } |
| |
| public void addInterface(final ClassNode type) { |
| ClassNode[] interfaces = getInterfaces(); |
| for (ClassNode face : interfaces) { |
| if (face.equals(type)) return; |
| } |
| final int n = interfaces.length; |
| |
| System.arraycopy(interfaces, 0, interfaces = new ClassNode[n + 1], 0, n); |
| interfaces[n] = type; // append interface |
| setInterfaces(interfaces); |
| } |
| |
| @Override |
| public boolean equals(Object that) { |
| if (that == this) return true; |
| if (!(that instanceof ClassNode)) return false; |
| if (redirect != null) return redirect.equals(that); |
| if (componentType != null) return componentType.equals(((ClassNode) that).componentType); |
| return ((ClassNode) that).getText().equals(getText()); // arrays could be "T[]" or "[LT;" |
| } |
| |
| @Override |
| public int hashCode() { |
| return (redirect != null ? redirect.hashCode() : getText().hashCode()); |
| } |
| |
| public void addMixin(MixinNode mixin) { |
| // let's check if it already uses a mixin |
| MixinNode[] mixins = getMixins(); |
| boolean skip = false; |
| for (MixinNode existing : mixins) { |
| if (mixin.equals(existing)) { |
| skip = true; |
| break; |
| } |
| } |
| if (!skip) { |
| MixinNode[] newMixins = new MixinNode[mixins.length + 1]; |
| System.arraycopy(mixins, 0, newMixins, 0, mixins.length); |
| newMixins[mixins.length] = mixin; |
| redirect().mixins = newMixins; |
| } |
| } |
| |
| /** |
| * Finds a field matching the given name in this class. |
| * |
| * @param name the name of the field of interest |
| * @return the method matching the given name and parameters or null |
| */ |
| public FieldNode getDeclaredField(String name) { |
| if (redirect != null) |
| return redirect.getDeclaredField(name); |
| lazyClassInit(); |
| return fieldIndex == null ? null : fieldIndex.get(name); |
| } |
| |
| /** |
| * Finds a field matching the given name in this class or a parent class. |
| * |
| * @param name the name of the field of interest |
| * @return the method matching the given name and parameters or null |
| */ |
| public FieldNode getField(String name) { |
| ClassNode node = this; |
| while (node != null) { |
| FieldNode fn = node.getDeclaredField(name); |
| if (fn != null) return fn; |
| node = node.getSuperClass(); |
| } |
| return null; |
| } |
| |
| /** |
| * @return the field on the outer class or {@code null} if this is not an inner class |
| */ |
| public FieldNode getOuterField(String name) { |
| if (redirect != null) { |
| return redirect.getOuterField(name); |
| } |
| return null; |
| } |
| |
| public ClassNode getOuterClass() { |
| if (redirect != null) { |
| return redirect.getOuterClass(); |
| } |
| return null; |
| } |
| |
| public List<ClassNode> getOuterClasses() { |
| ClassNode outer = getOuterClass(); |
| if (outer == null) { |
| return Collections.emptyList(); |
| } |
| List<ClassNode> result = new LinkedList<>(); |
| do { |
| result.add(outer); |
| } while ((outer = outer.getOuterClass()) != null); |
| |
| return result; |
| } |
| |
| /** |
| * Adds a statement to the object initializer. |
| * |
| * @param statements the statement to be added |
| */ |
| public void addObjectInitializerStatements(Statement statements) { |
| getObjectInitializerStatements().add(statements); |
| } |
| |
| public List<Statement> getObjectInitializerStatements() { |
| if (objectInitializers == null) |
| objectInitializers = new LinkedList<>(); |
| return objectInitializers; |
| } |
| |
| private MethodNode getOrAddStaticConstructorNode() { |
| MethodNode method; |
| final String classInitializer = "<clinit>"; |
| List<MethodNode> declaredMethods = getDeclaredMethods(classInitializer); |
| if (declaredMethods.isEmpty()) { |
| method = addMethod(classInitializer, ACC_STATIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement()); |
| method.setSynthetic(true); |
| } else { |
| method = declaredMethods.get(0); |
| } |
| return method; |
| } |
| |
| public void addStaticInitializerStatements(List<Statement> staticStatements, boolean fieldInit) { |
| MethodNode method = getOrAddStaticConstructorNode(); |
| BlockStatement block = getCodeAsBlock(method); |
| |
| // while anything inside a static initializer block is appended |
| // we don't want to append in the case we have an initialization |
| // expression of a static field. In that case we want to add |
| // before the other statements |
| if (!fieldInit) { |
| block.addStatements(staticStatements); |
| } else { |
| List<Statement> blockStatements = block.getStatements(); |
| staticStatements.addAll(blockStatements); |
| blockStatements.clear(); |
| blockStatements.addAll(staticStatements); |
| } |
| } |
| |
| public void positionStmtsAfterEnumInitStmts(List<Statement> staticFieldStatements) { |
| MethodNode constructor = getOrAddStaticConstructorNode(); |
| Statement statement = constructor.getCode(); |
| if (statement instanceof BlockStatement) { |
| BlockStatement block = (BlockStatement) statement; |
| // add given statements for explicitly declared static fields just after enum-special fields |
| // are found - the $VALUES binary expression marks the end of such fields. |
| List<Statement> blockStatements = block.getStatements(); |
| ListIterator<Statement> litr = blockStatements.listIterator(); |
| while (litr.hasNext()) { |
| Statement stmt = litr.next(); |
| if (stmt instanceof ExpressionStatement && |
| ((ExpressionStatement) stmt).getExpression() instanceof BinaryExpression) { |
| BinaryExpression bExp = (BinaryExpression) ((ExpressionStatement) stmt).getExpression(); |
| if (bExp.getLeftExpression() instanceof FieldExpression) { |
| FieldExpression fExp = (FieldExpression) bExp.getLeftExpression(); |
| if (fExp.getFieldName().equals("$VALUES")) { |
| for (Statement tmpStmt : staticFieldStatements) { |
| litr.add(tmpStmt); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method returns a list of all methods of the given name |
| * defined in the current class |
| * @return the method list |
| * @see #getMethods(String) |
| */ |
| public List<MethodNode> getDeclaredMethods(String name) { |
| if (redirect != null) |
| return redirect.getDeclaredMethods(name); |
| lazyClassInit(); |
| return methods.get(name); |
| } |
| |
| /** |
| * This method creates a list of all methods with this name of the |
| * current class and of all super classes |
| * @return the methods list |
| * @see #getDeclaredMethods(String) |
| */ |
| public List<MethodNode> getMethods(String name) { |
| List<MethodNode> result = new ArrayList<>(); |
| ClassNode node = this; |
| while (node != null) { |
| result.addAll(node.getDeclaredMethods(name)); |
| node = node.getSuperClass(); |
| } |
| return result; |
| } |
| |
| /** |
| * Finds a method matching the given name and parameters in this class. |
| * |
| * @return the method matching the given name and parameters or null |
| */ |
| public MethodNode getDeclaredMethod(String name, Parameter[] parameters) { |
| for (MethodNode method : getDeclaredMethods(name)) { |
| if (parametersEqual(method.getParameters(), parameters)) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Finds a method matching the given name and parameters in this class |
| * or any parent class. |
| * |
| * @return the method matching the given name and parameters or null |
| */ |
| public MethodNode getMethod(String name, Parameter[] parameters) { |
| for (MethodNode method : getMethods(name)) { |
| if (parametersEqual(method.getParameters(), parameters)) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param type the ClassNode of interest |
| * @return true if this node is derived from the given ClassNode |
| */ |
| public boolean isDerivedFrom(ClassNode type) { |
| if (isPrimitiveVoid(this)) { |
| return isPrimitiveVoid(type); |
| } |
| if (isObjectType(type)) { |
| return true; |
| } |
| ClassNode node = this; |
| while (node != null) { |
| if (type.equals(node)) { |
| return true; |
| } |
| node = node.getSuperClass(); |
| } |
| return false; |
| } |
| |
| /** |
| * @return {@code true} if this type implements {@code GroovyObject} |
| */ |
| public boolean isDerivedFromGroovyObject() { |
| return implementsInterface(ClassHelper.GROOVY_OBJECT_TYPE); |
| } |
| |
| /** |
| * @param classNodes the class nodes for the interfaces |
| * @return {@code true} if this type implements any of the given interfaces |
| */ |
| public boolean implementsAnyInterfaces(ClassNode... classNodes) { |
| for (ClassNode classNode : classNodes) { |
| if (implementsInterface(classNode)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @param classNode the class node for the interface |
| * @return {@code true} if this type implements the given interface |
| */ |
| public boolean implementsInterface(ClassNode classNode) { |
| ClassNode node = redirect(); |
| do { |
| if (node.declaresInterface(classNode)) { |
| return true; |
| } |
| node = node.getSuperClass(); |
| } |
| while (node != null); |
| |
| return false; |
| } |
| |
| /** |
| * |
| * @param classNodes the class nodes for the interfaces |
| * @return {@code true} if this type declares that it implements any of the |
| * given interfaces or if one of its interfaces extends directly/indirectly |
| * any of the given interfaces |
| */ |
| public boolean declaresAnyInterfaces(ClassNode... classNodes) { |
| for (ClassNode classNode : classNodes) { |
| if (declaresInterface(classNode)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @param classNode the class node for the interface |
| * @return {@code true} if this class declares that it implements the given |
| * interface or if one of its interfaces extends directly/indirectly the interface |
| * |
| * NOTE: Doesn't consider an interface to implement itself. |
| * I think this is intended to be called on ClassNodes representing |
| * classes, not interfaces. |
| * |
| * @see org.codehaus.groovy.ast.tools.GeneralUtils#isOrImplements |
| */ |
| public boolean declaresInterface(ClassNode classNode) { |
| ClassNode[] interfaces = getInterfaces(); |
| for (ClassNode face : interfaces) { |
| if (face.equals(classNode)) { |
| return true; |
| } |
| } |
| for (ClassNode face : interfaces) { |
| if (face.declaresInterface(classNode)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return the {@code ClassNode} of the super class of this type |
| */ |
| public ClassNode getSuperClass() { |
| if (!lazyInitDone && !isResolved()) { |
| throw new GroovyBugError("ClassNode#getSuperClass for " + getName() + " called before class resolving"); |
| } |
| ClassNode sn = redirect().getUnresolvedSuperClass(); |
| if (sn != null) sn = sn.redirect(); |
| return sn; |
| } |
| |
| public ClassNode getUnresolvedSuperClass() { |
| return getUnresolvedSuperClass(true); |
| } |
| |
| public ClassNode getUnresolvedSuperClass(boolean useRedirect) { |
| if (!useRedirect) |
| return superClass; |
| if (redirect != null) |
| return redirect.getUnresolvedSuperClass(true); |
| lazyClassInit(); |
| return superClass; |
| } |
| |
| public void setUnresolvedSuperClass(ClassNode superClass) { |
| this.superClass = superClass; |
| } |
| |
| public ClassNode[] getUnresolvedInterfaces() { |
| return getUnresolvedInterfaces(true); |
| } |
| |
| public ClassNode[] getUnresolvedInterfaces(boolean useRedirect) { |
| if (!useRedirect) |
| return interfaces; |
| if (redirect != null) |
| return redirect.getUnresolvedInterfaces(true); |
| lazyClassInit(); |
| return interfaces; |
| } |
| |
| public CompileUnit getCompileUnit() { |
| if (redirect != null) |
| return redirect.getCompileUnit(); |
| if (compileUnit == null && module != null) { |
| compileUnit = module.getUnit(); |
| } |
| return compileUnit; |
| } |
| |
| protected void setCompileUnit(CompileUnit cu) { |
| if (redirect != null) |
| redirect.setCompileUnit(cu); |
| if (compileUnit != null) compileUnit = cu; |
| } |
| |
| @Deprecated(forRemoval = true, since = "4.0.0") |
| protected boolean parametersEqual(Parameter[] a, Parameter[] b) { |
| return ParameterUtils.parametersEqual(a, b); |
| } |
| |
| public String getPackageName() { |
| int idx = getName().lastIndexOf('.'); |
| if (idx > 0) { |
| return getName().substring(0, idx); |
| } |
| return null; |
| } |
| |
| public String getNameWithoutPackage() { |
| int idx = getName().lastIndexOf('.'); |
| if (idx > 0) { |
| return getName().substring(idx + 1); |
| } |
| return getName(); |
| } |
| |
| public void visitContents(GroovyClassVisitor visitor) { |
| // now let's visit the contents of the class |
| for (PropertyNode pn : getProperties()) { |
| visitor.visitProperty(pn); |
| } |
| |
| for (FieldNode fn : getFields()) { |
| visitor.visitField(fn); |
| } |
| |
| for (ConstructorNode cn : getDeclaredConstructors()) { |
| visitor.visitConstructor(cn); |
| } |
| |
| visitMethods(visitor); |
| } |
| |
| private void visitMethods(GroovyClassVisitor visitor) { |
| // create snapshot of the method list to avoid ConcurrentModificationException |
| List<MethodNode> methodList = new ArrayList<>(getMethods()); |
| for (MethodNode mn : methodList) { |
| visitor.visitMethod(mn); |
| } |
| |
| // visit the method nodes added while iterating, |
| // e.g. synthetic method for constructor reference |
| final List<MethodNode> newMethodList = getMethods(); |
| if (newMethodList.size() > methodList.size()) { // if the newly added method nodes found, visit them |
| List<MethodNode> changedMethodList = new ArrayList<>(newMethodList); |
| boolean changed = changedMethodList.removeAll(methodList); |
| if (changed) { |
| for (MethodNode mn : changedMethodList) { |
| visitor.visitMethod(mn); |
| } |
| } |
| } |
| } |
| |
| public MethodNode getGetterMethod(String getterName) { |
| return getGetterMethod(getterName, true); |
| } |
| |
| public MethodNode getGetterMethod(String getterName, boolean searchSuperClasses) { |
| MethodNode getterMethod = null; |
| boolean booleanReturnOnly = getterName.startsWith("is"); |
| for (MethodNode method : getDeclaredMethods(getterName)) { |
| if (method.getName().equals(getterName) && method.getParameters().length == 0 |
| && (booleanReturnOnly ? isPrimitiveBoolean(method.getReturnType()) : !method.isVoidMethod())) { |
| // GROOVY-7363: There can be multiple matches for a getter returning a generic parameter type, due to |
| // the generation of a bridge method. The real getter is really the non-bridge, non-synthetic one as it |
| // has the most specific and exact return type of the two. Picking the bridge method results in loss of |
| // type information, as it down-casts the return type to the lower bound of the generic parameter. |
| if (getterMethod == null || getterMethod.isSynthetic()) { |
| getterMethod = method; |
| } |
| } |
| } |
| if (getterMethod != null) { |
| return getterMethod; |
| } |
| if (searchSuperClasses) { |
| ClassNode parent = getSuperClass(); |
| if (parent != null) { |
| return parent.getGetterMethod(getterName); |
| } |
| } |
| return null; |
| } |
| |
| public MethodNode getSetterMethod(String setterName) { |
| return getSetterMethod(setterName, true); |
| } |
| |
| public MethodNode getSetterMethod(String setterName, boolean voidOnly) { |
| for (MethodNode method : getDeclaredMethods(setterName)) { |
| if (setterName.equals(method.getName()) |
| && method.getParameters().length == 1 |
| && (!voidOnly || method.isVoidMethod())) { |
| return method; |
| } |
| } |
| ClassNode parent = getSuperClass(); |
| if (parent != null) { |
| return parent.getSetterMethod(setterName, voidOnly); |
| } |
| return null; |
| } |
| |
| /** |
| * Is this class declared in a static method (such as a closure / inner class declared in a static method) |
| */ |
| public boolean isStaticClass() { |
| return redirect().staticClass; |
| } |
| |
| public void setStaticClass(boolean staticClass) { |
| redirect().staticClass = staticClass; |
| } |
| |
| /** |
| * @return {@code true} if this inner class or closure was declared inside a script body |
| */ |
| public boolean isScriptBody() { |
| return redirect().scriptBody; |
| } |
| |
| public void setScriptBody(boolean scriptBody) { |
| redirect().scriptBody = scriptBody; |
| } |
| |
| public boolean isScript() { |
| return redirect().script || isDerivedFrom(ClassHelper.SCRIPT_TYPE); |
| } |
| |
| public void setScript(boolean script) { |
| redirect().script = script; |
| } |
| |
| @Override |
| public String toString() { |
| return toString(true); |
| } |
| |
| public String toString(final boolean showRedirect) { |
| if (isArray()) { |
| return getComponentType().toString(showRedirect) + "[]"; |
| } |
| boolean placeholder = isGenericsPlaceHolder(); |
| StringBuilder ret = new StringBuilder(!placeholder ? getName() : getUnresolvedName()); |
| GenericsType[] genericsTypes = getGenericsTypes(); |
| if (!placeholder && genericsTypes != null) { |
| ret.append('<'); |
| for (int i = 0, n = genericsTypes.length; i < n; i += 1) { |
| if (i != 0) ret.append(", "); |
| ret.append(genericsTypes[i]); |
| } |
| ret.append('>'); |
| } |
| if (showRedirect && redirect != null) { |
| ret.append(" -> ").append(redirect); |
| } |
| return ret.toString(); |
| } |
| |
| /** |
| * Determines if the type has a possibly-matching instance method with the given name and arguments. |
| * |
| * @param name the name of the method of interest |
| * @param arguments the arguments to match against |
| * @return true if a matching method was found |
| */ |
| public boolean hasPossibleMethod(final String name, final Expression arguments) { |
| int count; |
| if (arguments instanceof TupleExpression) { |
| // TODO: this won't strictly be true when using list expansion in argument calls |
| count = ((TupleExpression) arguments).getExpressions().size(); |
| } else { |
| count = 0; |
| } |
| |
| for (ClassNode cn = this; cn != null; cn = cn.getSuperClass()) { |
| for (MethodNode mn : cn.getDeclaredMethods(name)) { |
| if (!mn.isStatic() && hasCompatibleNumberOfArgs(mn, count)) { |
| return true; |
| } |
| } |
| for (ClassNode in : cn.getAllInterfaces()) { |
| for (MethodNode mn : in.getDeclaredMethods(name)) { |
| if (mn.isDefault() && hasCompatibleNumberOfArgs(mn, count)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| public MethodNode tryFindPossibleMethod(final String name, final Expression arguments) { |
| if (!(arguments instanceof TupleExpression)) { |
| return null; |
| } |
| |
| // TODO: this won't strictly be true when using list expansion in argument calls |
| TupleExpression args = (TupleExpression) arguments; |
| int nArgs = args.getExpressions().size(); |
| MethodNode method = null; |
| |
| for (ClassNode cn = this; cn != null; cn = cn.getSuperClass()) { |
| for (MethodNode mn : cn.getDeclaredMethods(name)) { |
| if (hasCompatibleNumberOfArgs(mn, nArgs)) { |
| boolean match = true; |
| for (int i = 0; i < nArgs; i += 1) { |
| if (!hasCompatibleType(args, mn, i)) { |
| match = false; |
| break; |
| } |
| } |
| if (match) { |
| if (method == null) { |
| method = mn; |
| } else if (cn.equals(this) |
| || method.getParameters().length != nArgs) { |
| return null; |
| } else { |
| for (int i = 0; i < nArgs; i += 1) { |
| // prefer super method if it matches better |
| if (!hasExactMatchingCompatibleType(method, mn, i)) { |
| return null; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return method; |
| } |
| |
| private static boolean hasExactMatchingCompatibleType(final MethodNode match, final MethodNode maybe, final int i) { |
| int lastParamIndex = maybe.getParameters().length - 1; |
| return (i <= lastParamIndex && match.getParameters()[i].getType().equals(maybe.getParameters()[i].getType())) |
| || (i >= lastParamIndex && isPotentialVarArg(maybe, lastParamIndex) && match.getParameters()[i].getType().equals(maybe.getParameters()[lastParamIndex].getType().getComponentType())); |
| } |
| |
| private static boolean hasCompatibleType(final TupleExpression args, final MethodNode method, final int i) { |
| int lastParamIndex = method.getParameters().length - 1; |
| return (i <= lastParamIndex && args.getExpression(i).getType().isDerivedFrom(method.getParameters()[i].getType())) |
| || (i >= lastParamIndex && isPotentialVarArg(method, lastParamIndex) && args.getExpression(i).getType().isDerivedFrom(method.getParameters()[lastParamIndex].getType().getComponentType())); |
| } |
| |
| private static boolean hasCompatibleNumberOfArgs(final MethodNode method, final int nArgs) { |
| int lastParamIndex = method.getParameters().length - 1; |
| return nArgs == method.getParameters().length || (nArgs >= lastParamIndex && isPotentialVarArg(method, lastParamIndex)); |
| } |
| |
| private static boolean isPotentialVarArg(final MethodNode method, final int lastParamIndex) { |
| return lastParamIndex >= 0 && method.getParameters()[lastParamIndex].getType().isArray(); |
| } |
| |
| /** |
| * Checks if the given method has a possibly matching static method with the |
| * given name and arguments. |
| * |
| * @param name the name of the method of interest |
| * @param arguments the arguments to match against |
| * @return {@code true} if a matching method was found |
| */ |
| public boolean hasPossibleStaticMethod(final String name, final Expression arguments) { |
| return ClassNodeUtils.hasPossibleStaticMethod(this, name, arguments, false); |
| } |
| |
| public boolean isInterface() { |
| return (getModifiers() & ACC_INTERFACE) != 0; |
| } |
| |
| /** |
| * Checks if the {@link ClassNode} instance represents a native {@code record}. |
| * Check instead for the {@code RecordBase} annotation if looking for records and |
| * record-like classes currently being compiled. |
| * |
| * @return {@code true} if the instance represents a native {@code record} |
| * @since 4.0.0 |
| */ |
| @Incubating |
| public boolean isRecord() { |
| return recordNative(this); |
| } |
| |
| /** |
| * Gets the record components of record type. |
| * |
| * @return {@code RecordComponentNode} instances |
| * @since 4.0.0 |
| */ |
| @Incubating |
| public List<RecordComponentNode> getRecordComponents() { |
| if (redirect != null) |
| return redirect.getRecordComponents(); |
| lazyClassInit(); |
| return recordComponents; |
| } |
| |
| /** |
| * Sets the record components for record type. |
| * |
| * @since 4.0.0 |
| */ |
| @Incubating |
| public void setRecordComponents(List<RecordComponentNode> recordComponents) { |
| if (redirect != null) { |
| redirect.setRecordComponents(recordComponents); |
| } else { |
| this.recordComponents = recordComponents; |
| } |
| } |
| |
| public boolean isAbstract() { |
| return (getModifiers() & ACC_ABSTRACT) != 0; |
| } |
| |
| /** |
| * @return {@code true} for native and emulated (annotation based) sealed classes |
| * @since 4.0.0 |
| */ |
| @Incubating |
| public boolean isSealed() { |
| if (redirect != null) return redirect.isSealed(); |
| lazyClassInit(); |
| return !getAnnotations(SEALED_TYPE).isEmpty() || !getPermittedSubclasses().isEmpty(); |
| } |
| |
| public boolean isResolved() { |
| if (clazz != null) return true; |
| if (redirect != null) return redirect.isResolved(); |
| return (componentType != null && componentType.isResolved()); |
| } |
| |
| public boolean isArray() { |
| return (componentType != null); |
| } |
| |
| public ClassNode getComponentType() { |
| return componentType; |
| } |
| |
| /** |
| * Returns the concrete class this classnode relates to. However, this method |
| * is inherently unsafe as it may return null depending on the compile phase you are |
| * using. AST transformations should never use this method directly, but rather obtain |
| * a new class node using {@link #getPlainNodeReference()}. |
| * @return the class this classnode relates to. May return null. |
| */ |
| public Class getTypeClass() { |
| if (clazz != null) return clazz; |
| if (redirect != null) return redirect.getTypeClass(); |
| |
| ClassNode component = redirect().componentType; |
| if (component != null && component.isResolved()) { |
| return Array.newInstance(component.getTypeClass(), 0).getClass(); |
| } |
| throw new GroovyBugError("ClassNode#getTypeClass for " + getName() + " called before the type class is set"); |
| } |
| |
| public boolean hasPackageName() { |
| return (redirect().name.indexOf('.') > 0); |
| } |
| |
| /** |
| * Marks if the current class uses annotations or not. |
| */ |
| public void setAnnotated(boolean annotated) { |
| this.annotated = annotated; |
| } |
| |
| public boolean isAnnotated() { |
| return this.annotated; |
| } |
| |
| public GenericsType asGenericsType() { |
| if (!isGenericsPlaceHolder()) { |
| return new GenericsType(this); |
| } else if (genericsTypes != null |
| && genericsTypes[0].getUpperBounds() != null) { |
| return genericsTypes[0]; |
| } else { |
| ClassNode upper = (redirect != null ? redirect : this); |
| return new GenericsType(this, new ClassNode[]{upper}, null); |
| } |
| } |
| |
| public GenericsType[] getGenericsTypes() { |
| return genericsTypes; |
| } |
| |
| public void setGenericsTypes(GenericsType[] genericsTypes) { |
| usesGenerics = usesGenerics || genericsTypes != null; |
| this.genericsTypes = genericsTypes; |
| } |
| |
| public void setGenericsPlaceHolder(boolean placeholder) { |
| usesGenerics = usesGenerics || placeholder; |
| this.placeholder = placeholder; |
| } |
| |
| public boolean isGenericsPlaceHolder() { |
| return placeholder; |
| } |
| |
| public boolean isUsingGenerics() { |
| return usesGenerics; |
| } |
| |
| public void setUsingGenerics(boolean usesGenerics) { |
| this.usesGenerics = usesGenerics; |
| } |
| |
| public ClassNode getPlainNodeReference(boolean skipPrimitives) { |
| if (skipPrimitives && ClassHelper.isPrimitiveType(this)) return this; |
| ClassNode n = new ClassNode(name, modifiers, superClass, null, null); |
| n.isPrimaryNode = false; |
| n.setRedirect(redirect()); |
| if (isArray()) { |
| n.componentType = redirect().getComponentType(); |
| } |
| return n; |
| } |
| |
| public ClassNode getPlainNodeReference() { |
| return getPlainNodeReference(true); |
| } |
| |
| public boolean isAnnotationDefinition() { |
| return isInterface() && (getModifiers() & ACC_ANNOTATION) != 0; |
| } |
| |
| @Override |
| public List<AnnotationNode> getAnnotations() { |
| if (redirect != null) |
| return redirect.getAnnotations(); |
| lazyClassInit(); |
| return super.getAnnotations(); |
| } |
| |
| @Override |
| public List<AnnotationNode> getAnnotations(ClassNode type) { |
| if (redirect != null) |
| return redirect.getAnnotations(type); |
| lazyClassInit(); |
| return super.getAnnotations(type); |
| } |
| |
| public void addTransform(Class<? extends ASTTransformation> transform, ASTNode node) { |
| GroovyASTTransformation annotation = transform.getAnnotation(GroovyASTTransformation.class); |
| if (annotation != null) { |
| Map<Class<? extends ASTTransformation>, Set<ASTNode>> transforms = getTransforms(annotation.phase()); |
| Set<ASTNode> nodes = transforms.computeIfAbsent(transform, k -> new LinkedHashSet<>()); |
| nodes.add(node); |
| } |
| } |
| |
| public Map<Class <? extends ASTTransformation>, Set<ASTNode>> getTransforms(CompilePhase phase) { |
| return getTransformInstances().get(phase); |
| } |
| |
| public void renameField(String oldName, String newName) { |
| ClassNode r = redirect(); |
| if (r.fieldIndex == null) |
| r.fieldIndex = new LinkedHashMap<>(); |
| Map<String, FieldNode> index = r.fieldIndex; |
| index.put(newName, index.remove(oldName)); |
| } |
| |
| public void removeField(String oldName) { |
| ClassNode r = redirect(); |
| if (r.fieldIndex == null) |
| r.fieldIndex = new LinkedHashMap<>(); |
| Map<String, FieldNode> index = r.fieldIndex; |
| r.fields.remove(index.get(oldName)); |
| index.remove(oldName); |
| } |
| |
| public boolean isEnum() { |
| return (getModifiers() & ACC_ENUM) != 0; |
| } |
| |
| /** |
| * @return iterator of inner classes defined inside this one |
| */ |
| public Iterator<InnerClassNode> getInnerClasses() { |
| return (innerClasses == null ? Collections.<InnerClassNode>emptyList() : innerClasses).iterator(); |
| } |
| |
| private Map<CompilePhase, Map<Class<? extends ASTTransformation>, Set<ASTNode>>> getTransformInstances() { |
| if (transformInstances == null) { |
| transformInstances = new EnumMap<>(CompilePhase.class); |
| for (CompilePhase phase : CompilePhase.values()) { |
| transformInstances.put(phase, new LinkedHashMap<>()); |
| } |
| } |
| return transformInstances; |
| } |
| |
| @Override |
| public String getText() { |
| return getName(); |
| } |
| |
| public List<AnnotationNode> getTypeAnnotations() { |
| return new ArrayList<>(typeAnnotations); |
| } |
| |
| public List<AnnotationNode> getTypeAnnotations(final ClassNode type) { |
| List<AnnotationNode> annotations = new ArrayList<>(); |
| for (AnnotationNode node : typeAnnotations) { |
| if (type.equals(node.getClassNode())) { |
| annotations.add(node); |
| } |
| } |
| return annotations; |
| } |
| |
| public void addTypeAnnotation(final AnnotationNode annotation) { |
| if (!isPrimaryClassNode() && !isRedirectNode() && isResolved()) { |
| throw new GroovyBugError("Adding type annotation @" + annotation.getClassNode().getNameWithoutPackage() + " to non-primary, non-redirect node: " + getName()); |
| } |
| if (typeAnnotations == Collections.EMPTY_LIST) { |
| typeAnnotations = new ArrayList<>(3); |
| } |
| typeAnnotations.add(requireNonNull(annotation)); |
| setAnnotated(true); |
| } |
| |
| public void addTypeAnnotations(final List<AnnotationNode> annotations) { |
| for (AnnotationNode annotation : annotations) { |
| addTypeAnnotation(annotation); |
| } |
| } |
| } |