http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java b/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java new file mode 100644 index 0000000..c493299 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/AnnotationNode.java @@ -0,0 +1,189 @@ +/* + * 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.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.expr.Expression; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Represents an annotation which can be attached to interfaces, classes, methods and fields. + */ +public class AnnotationNode extends ASTNode { + public static final int CONSTRUCTOR_TARGET = 1 << 1; + public static final int METHOD_TARGET = 1 << 2; + public static final int FIELD_TARGET = 1 << 3; + public static final int PARAMETER_TARGET = 1 << 4; + public static final int LOCAL_VARIABLE_TARGET = 1 << 5; + public static final int ANNOTATION_TARGET = 1 << 6; + public static final int PACKAGE_TARGET = 1 << 7; + public static final int TYPE_PARAMETER_TARGET = 1 << 8; + public static final int TYPE_USE_TARGET = 1 << 9; + public static final int TYPE_TARGET = 1 + ANNOTATION_TARGET; //GROOVY-7151 + private static final int ALL_TARGETS = TYPE_TARGET | CONSTRUCTOR_TARGET | METHOD_TARGET + | FIELD_TARGET | PARAMETER_TARGET | LOCAL_VARIABLE_TARGET | ANNOTATION_TARGET + | PACKAGE_TARGET | TYPE_PARAMETER_TARGET | TYPE_USE_TARGET; + + private final ClassNode classNode; + private Map<String, Expression> members; + private boolean runtimeRetention= false, sourceRetention= false, classRetention = false; + private int allowedTargets = ALL_TARGETS; + + public AnnotationNode(ClassNode classNode) { + this.classNode = classNode; + } + + public ClassNode getClassNode() { + return classNode; + } + + public Map<String, Expression> getMembers() { + if (members == null) { + return Collections.emptyMap(); + } + return members; + } + + public Expression getMember(String name) { + if (members == null) { + return null; + } + return members.get(name); + } + + private void assertMembers() { + if (members == null) { + members = new LinkedHashMap<String, Expression>(); + } + } + + public void addMember(String name, Expression value) { + assertMembers(); + Expression oldValue = members.get(name); + if (oldValue == null) { + members.put(name, value); + } + else { + throw new GroovyBugError(String.format("Annotation member %s has already been added", name)); + } + } + + public void setMember(String name, Expression value) { + assertMembers(); + members.put(name, value); + } + + public boolean isBuiltIn(){ + return false; + } + + /** + * Flag corresponding to <code>RetentionPolicy</code>. + * @return <tt>true</tt> if the annotation should be visible at runtime, + * <tt>false</tt> otherwise + */ + public boolean hasRuntimeRetention() { + return this.runtimeRetention; + } + + /** + * Sets the internal flag of this annotation runtime retention policy. + * If the current annotation has + * <code>RetentionPolicy.RUNTIME</code> or if <tt>false</tt> + * if the <code>RetentionPolicy.CLASS</code>. + * @param flag if <tt>true</tt> then current annotation is marked as having + * <code>RetentionPolicy.RUNTIME</code>. If <tt>false</tt> then + * the annotation has <code>RetentionPolicy.CLASS</code>. + */ + public void setRuntimeRetention(boolean flag) { + this.runtimeRetention = flag; + } + + /** + * Flag corresponding to <code>RetentionPolicy.SOURCE</code>. + * @return <tt>true</tt> if the annotation is only allowed in sources + * <tt>false</tt> otherwise + */ + public boolean hasSourceRetention() { + if (!runtimeRetention && !classRetention) return true; + return this.sourceRetention; + } + + /** Sets the internal flag if the current annotation has + * <code>RetentionPolicy.SOURCE</code>. + */ + public void setSourceRetention(boolean flag) { + this.sourceRetention = flag; + } + + /** + * Flag corresponding to <code>RetentionPolicy.CLASS</code>. + * @return <tt>true</tt> if the annotation is recorded by the compiler, + * but not visible at runtime * + * <tt>false</tt> otherwise + */ + public boolean hasClassRetention() { + return this.classRetention; + } + + /** Sets the internal flag if the current annotation has + * <code>RetentionPolicy.CLASS</code>. + */ + public void setClassRetention(boolean flag) { + this.classRetention = flag; + } + + public void setAllowedTargets(int bitmap) { + this.allowedTargets = bitmap; + } + + public boolean isTargetAllowed(int target) { + return (this.allowedTargets & target) == target; + } + + public static String targetToName(int target) { + switch(target) { + case TYPE_TARGET: + return "TYPE"; + case CONSTRUCTOR_TARGET: + return "CONSTRUCTOR"; + case METHOD_TARGET: + return "METHOD"; + case FIELD_TARGET: + return "FIELD"; + case PARAMETER_TARGET: + return "PARAMETER"; + case LOCAL_VARIABLE_TARGET: + return "LOCAL_VARIABLE"; + case ANNOTATION_TARGET: + return "ANNOTATION"; + case PACKAGE_TARGET: + return "PACKAGE"; + case TYPE_PARAMETER_TARGET: + return "TYPE_PARAMETER"; + case TYPE_USE_TARGET: + return "TYPE_USE"; + default: + return "unknown target"; + } + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/AstToTextHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/AstToTextHelper.java b/src/main/java/org/codehaus/groovy/ast/AstToTextHelper.java new file mode 100644 index 0000000..3599fbf --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/AstToTextHelper.java @@ -0,0 +1,111 @@ +/* + * 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 java.lang.reflect.Modifier; + +/** + * Helper class for converting AST into text. + * @author Hamlet D'Arcy + */ +public class AstToTextHelper { + + public static String getClassText(ClassNode node) { + if (node == null) return "<unknown>"; + if (node.getName() == null) return "<unknown>"; + return node.getName(); + } + + public static String getParameterText(Parameter node) { + if (node == null) return "<unknown>"; + + String name = node.getName() == null ? "<unknown>" : node.getName(); + String type = getClassText(node.getType()); + if (node.getInitialExpression() != null) { + return type + " " + name + " = " + node.getInitialExpression().getText(); + } + return type + " " + name; + } + + public static String getParametersText(Parameter[] parameters) { + if (parameters == null) return ""; + if (parameters.length == 0) return ""; + StringBuilder result = new StringBuilder(); + int max = parameters.length; + for (int x = 0; x < max; x++) { + result.append(getParameterText(parameters[x])); + if (x < (max - 1)) { + result.append(", "); + } + } + return result.toString(); + } + + public static String getThrowsClauseText(ClassNode[] exceptions) { + if (exceptions == null) return ""; + if (exceptions.length == 0) return ""; + StringBuilder result = new StringBuilder("throws "); + int max = exceptions.length; + for (int x = 0; x < max; x++) { + result.append(getClassText(exceptions[x])); + if (x < (max - 1)) { + result.append(", "); + } + } + return result.toString(); + } + + public static String getModifiersText(int modifiers) { + StringBuilder result = new StringBuilder(); + if (Modifier.isPrivate(modifiers)) { + result.append("private "); + } + if (Modifier.isProtected(modifiers)) { + result.append("protected "); + } + if (Modifier.isPublic(modifiers)) { + result.append("public "); + } + if (Modifier.isStatic(modifiers)) { + result.append("static "); + } + if (Modifier.isAbstract(modifiers)) { + result.append("abstract "); + } + if (Modifier.isFinal(modifiers)) { + result.append("final "); + } + if (Modifier.isInterface(modifiers)) { + result.append("interface "); + } + if (Modifier.isNative(modifiers)) { + result.append("native "); + } + if (Modifier.isSynchronized(modifiers)) { + result.append("synchronized "); + } + if (Modifier.isTransient(modifiers)) { + result.append("transient "); + } + if (Modifier.isVolatile(modifiers)) { + result.append("volatile "); + } + return result.toString().trim(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/ClassCodeExpressionTransformer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassCodeExpressionTransformer.java b/src/main/java/org/codehaus/groovy/ast/ClassCodeExpressionTransformer.java new file mode 100644 index 0000000..06ef44d --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ClassCodeExpressionTransformer.java @@ -0,0 +1,146 @@ +/* + * 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.codehaus.groovy.ast.expr.BooleanExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ExpressionTransformer; +import org.codehaus.groovy.ast.stmt.AssertStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; + +import java.util.List; +import java.util.Map; + +/** + * Visitor to transform expressions in a whole class. + * Transformed Expressions are usually not visited. + * + * @author Jochen Theodorou + */ +public abstract class ClassCodeExpressionTransformer extends ClassCodeVisitorSupport implements ExpressionTransformer { + + protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + for (Parameter p : node.getParameters()) { + if (p.hasInitialExpression()) { + Expression init = p.getInitialExpression(); + p.setInitialExpression(transform(init)); + } + } + super.visitConstructorOrMethod(node, isConstructor); + } + + public void visitSwitch(SwitchStatement statement) { + Expression exp = statement.getExpression(); + statement.setExpression(transform(exp)); + for (CaseStatement caseStatement : statement.getCaseStatements()) { + caseStatement.visit(this); + } + statement.getDefaultStatement().visit(this); + } + + public void visitField(FieldNode node) { + visitAnnotations(node); + Expression init = node.getInitialExpression(); + node.setInitialValueExpression(transform(init)); + } + + public void visitProperty(PropertyNode node) { + visitAnnotations(node); + Statement statement = node.getGetterBlock(); + visitClassCodeContainer(statement); + + statement = node.getSetterBlock(); + visitClassCodeContainer(statement); + } + + public void visitIfElse(IfStatement ifElse) { + ifElse.setBooleanExpression((BooleanExpression) (transform(ifElse.getBooleanExpression()))); + ifElse.getIfBlock().visit(this); + ifElse.getElseBlock().visit(this); + } + + public Expression transform(Expression exp) { + if (exp == null) return null; + return exp.transformExpression(this); + } + + public void visitAnnotations(AnnotatedNode node) { + List<AnnotationNode> annotations = node.getAnnotations(); + if (annotations.isEmpty()) return; + for (AnnotationNode an : annotations) { + // skip built-in properties + if (an.isBuiltIn()) continue; + for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) { + member.setValue(transform(member.getValue())); + } + } + } + + public void visitReturnStatement(ReturnStatement statement) { + statement.setExpression(transform(statement.getExpression())); + } + + public void visitAssertStatement(AssertStatement as) { + as.setBooleanExpression((BooleanExpression) (transform(as.getBooleanExpression()))); + as.setMessageExpression(transform(as.getMessageExpression())); + } + + public void visitCaseStatement(CaseStatement statement) { + statement.setExpression(transform(statement.getExpression())); + statement.getCode().visit(this); + } + + public void visitDoWhileLoop(DoWhileStatement loop) { + loop.setBooleanExpression((BooleanExpression) (transform(loop.getBooleanExpression()))); + super.visitDoWhileLoop(loop); + } + + public void visitForLoop(ForStatement forLoop) { + forLoop.setCollectionExpression(transform(forLoop.getCollectionExpression())); + super.visitForLoop(forLoop); + } + + public void visitSynchronizedStatement(SynchronizedStatement sync) { + sync.setExpression(transform(sync.getExpression())); + super.visitSynchronizedStatement(sync); + } + + public void visitThrowStatement(ThrowStatement ts) { + ts.setExpression(transform(ts.getExpression())); + } + + public void visitWhileLoop(WhileStatement loop) { + loop.setBooleanExpression((BooleanExpression) transform(loop.getBooleanExpression())); + super.visitWhileLoop(loop); + } + + public void visitExpressionStatement(ExpressionStatement es) { + es.setExpression(transform(es.getExpression())); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/ClassCodeVisitorSupport.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassCodeVisitorSupport.java b/src/main/java/org/codehaus/groovy/ast/ClassCodeVisitorSupport.java new file mode 100644 index 0000000..378d2be --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ClassCodeVisitorSupport.java @@ -0,0 +1,239 @@ +/* + * 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.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.AssertStatement; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.BreakStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.ContinueStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; +import org.codehaus.groovy.transform.ErrorCollecting; + +import java.util.List; +import java.util.Map; + +public abstract class ClassCodeVisitorSupport extends CodeVisitorSupport implements ErrorCollecting, GroovyClassVisitor { + + public void visitClass(ClassNode node) { + visitAnnotations(node); + visitPackage(node.getPackage()); + visitImports(node.getModule()); + node.visitContents(this); + visitObjectInitializerStatements(node); + } + + protected void visitObjectInitializerStatements(ClassNode node) { + for (Statement element : node.getObjectInitializerStatements()) { + element.visit(this); + } + } + + public void visitPackage(PackageNode node) { + if (node != null) { + visitAnnotations(node); + node.visit(this); + } + } + + public void visitImports(ModuleNode node) { + if (node != null) { + for (ImportNode importNode : node.getImports()) { + visitAnnotations(importNode); + importNode.visit(this); + } + for (ImportNode importStarNode : node.getStarImports()) { + visitAnnotations(importStarNode); + importStarNode.visit(this); + } + for (ImportNode importStaticNode : node.getStaticImports().values()) { + visitAnnotations(importStaticNode); + importStaticNode.visit(this); + } + for (ImportNode importStaticStarNode : node.getStaticStarImports().values()) { + visitAnnotations(importStaticStarNode); + importStaticStarNode.visit(this); + } + } + } + + public void visitAnnotations(AnnotatedNode node) { + List<AnnotationNode> annotations = node.getAnnotations(); + if (annotations.isEmpty()) return; + for (AnnotationNode an : annotations) { + // skip built-in properties + if (an.isBuiltIn()) continue; + for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) { + member.getValue().visit(this); + } + } + } + + public void visitBlockStatement(BlockStatement block) { + visitStatement(block); + super.visitBlockStatement(block); + } + + protected void visitClassCodeContainer(Statement code) { + if (code != null) code.visit(this); + } + + @Override + public void visitDeclarationExpression(DeclarationExpression expression) { + visitAnnotations(expression); + super.visitDeclarationExpression(expression); + } + + protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + visitAnnotations(node); + visitClassCodeContainer(node.getCode()); + for (Parameter param : node.getParameters()) { + visitAnnotations(param); + } + } + + public void visitConstructor(ConstructorNode node) { + visitConstructorOrMethod(node, true); + } + + public void visitMethod(MethodNode node) { + visitConstructorOrMethod(node, false); + } + + public void visitField(FieldNode node) { + visitAnnotations(node); + Expression init = node.getInitialExpression(); + if (init != null) init.visit(this); + } + + public void visitProperty(PropertyNode node) { + visitAnnotations(node); + Statement statement = node.getGetterBlock(); + visitClassCodeContainer(statement); + + statement = node.getSetterBlock(); + visitClassCodeContainer(statement); + + Expression init = node.getInitialExpression(); + if (init != null) init.visit(this); + } + + public void addError(String msg, ASTNode expr) { + SourceUnit source = getSourceUnit(); + source.getErrorCollector().addErrorAndContinue( + new SyntaxErrorMessage(new SyntaxException(msg + '\n', expr.getLineNumber(), expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), source) + ); + } + + protected abstract SourceUnit getSourceUnit(); + + protected void visitStatement(Statement statement) { + } + + public void visitAssertStatement(AssertStatement statement) { + visitStatement(statement); + super.visitAssertStatement(statement); + } + + public void visitBreakStatement(BreakStatement statement) { + visitStatement(statement); + super.visitBreakStatement(statement); + } + + public void visitCaseStatement(CaseStatement statement) { + visitStatement(statement); + super.visitCaseStatement(statement); + } + + public void visitCatchStatement(CatchStatement statement) { + visitStatement(statement); + super.visitCatchStatement(statement); + } + + public void visitContinueStatement(ContinueStatement statement) { + visitStatement(statement); + super.visitContinueStatement(statement); + } + + public void visitDoWhileLoop(DoWhileStatement loop) { + visitStatement(loop); + super.visitDoWhileLoop(loop); + } + + public void visitExpressionStatement(ExpressionStatement statement) { + visitStatement(statement); + super.visitExpressionStatement(statement); + } + + public void visitForLoop(ForStatement forLoop) { + visitStatement(forLoop); + super.visitForLoop(forLoop); + } + + public void visitIfElse(IfStatement ifElse) { + visitStatement(ifElse); + super.visitIfElse(ifElse); + } + + public void visitReturnStatement(ReturnStatement statement) { + visitStatement(statement); + super.visitReturnStatement(statement); + } + + public void visitSwitch(SwitchStatement statement) { + visitStatement(statement); + super.visitSwitch(statement); + } + + public void visitSynchronizedStatement(SynchronizedStatement statement) { + visitStatement(statement); + super.visitSynchronizedStatement(statement); + } + + public void visitThrowStatement(ThrowStatement statement) { + visitStatement(statement); + super.visitThrowStatement(statement); + } + + public void visitTryCatchFinally(TryCatchStatement statement) { + visitStatement(statement); + super.visitTryCatchFinally(statement); + } + + public void visitWhileLoop(WhileStatement loop) { + visitStatement(loop); + super.visitWhileLoop(loop); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/ast/ClassHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java new file mode 100644 index 0000000..60eaa47 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -0,0 +1,481 @@ +/* + * 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 groovy.lang.Binding; +import groovy.lang.Closure; +import groovy.lang.GString; +import groovy.lang.GroovyInterceptable; +import groovy.lang.GroovyObject; +import groovy.lang.GroovyObjectSupport; +import groovy.lang.MetaClass; +import groovy.lang.Range; +import groovy.lang.Reference; +import groovy.lang.Script; +import org.codehaus.groovy.runtime.GeneratedClosure; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; +import org.codehaus.groovy.transform.trait.Traits; +import org.codehaus.groovy.util.ManagedConcurrentMap; +import org.codehaus.groovy.util.ReferenceBundle; +import org.codehaus.groovy.vmplugin.VMPluginFactory; +import org.objectweb.asm.Opcodes; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.ref.SoftReference; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * This class is a Helper for ClassNode and classes handling ClassNodes. + * It does contain a set of predefined ClassNodes for the most used + * types and some code for cached ClassNode creation and basic + * ClassNode handling + */ +public class ClassHelper { + + private static final Class[] classes = new Class[]{ + Object.class, Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, + Integer.TYPE, Long.TYPE, Double.TYPE, Float.TYPE, Void.TYPE, + Closure.class, GString.class, List.class, Map.class, Range.class, + Pattern.class, Script.class, String.class, Boolean.class, + Character.class, Byte.class, Short.class, Integer.class, Long.class, + Double.class, Float.class, BigDecimal.class, BigInteger.class, + Number.class, Void.class, Reference.class, Class.class, MetaClass.class, + Iterator.class, GeneratedClosure.class, GroovyObjectSupport.class + }; + + private static final String[] primitiveClassNames = new String[]{ + "", "boolean", "char", "byte", "short", + "int", "long", "double", "float", "void" + }; + + public static final ClassNode + DYNAMIC_TYPE = makeCached(Object.class), OBJECT_TYPE = DYNAMIC_TYPE, + VOID_TYPE = makeCached(Void.TYPE), CLOSURE_TYPE = makeCached(Closure.class), + GSTRING_TYPE = makeCached(GString.class), LIST_TYPE = makeWithoutCaching(List.class), + MAP_TYPE = makeWithoutCaching(Map.class), RANGE_TYPE = makeCached(Range.class), + PATTERN_TYPE = makeCached(Pattern.class), STRING_TYPE = makeCached(String.class), + SCRIPT_TYPE = makeCached(Script.class), REFERENCE_TYPE = makeWithoutCaching(Reference.class), + BINDING_TYPE = makeCached(Binding.class), + + boolean_TYPE = makeCached(boolean.class), char_TYPE = makeCached(char.class), + byte_TYPE = makeCached(byte.class), int_TYPE = makeCached(int.class), + long_TYPE = makeCached(long.class), short_TYPE = makeCached(short.class), + double_TYPE = makeCached(double.class), float_TYPE = makeCached(float.class), + Byte_TYPE = makeCached(Byte.class), Short_TYPE = makeCached(Short.class), + Integer_TYPE = makeCached(Integer.class), Long_TYPE = makeCached(Long.class), + Character_TYPE = makeCached(Character.class), Float_TYPE = makeCached(Float.class), + Double_TYPE = makeCached(Double.class), Boolean_TYPE = makeCached(Boolean.class), + BigInteger_TYPE = makeCached(java.math.BigInteger.class), + BigDecimal_TYPE = makeCached(java.math.BigDecimal.class), + Number_TYPE = makeCached(Number.class), + + void_WRAPPER_TYPE = makeCached(Void.class), METACLASS_TYPE = makeCached(MetaClass.class), + Iterator_TYPE = makeCached(Iterator.class), + + Enum_Type = makeWithoutCaching(Enum.class), + Annotation_TYPE = makeCached(Annotation.class), + ELEMENT_TYPE_TYPE = makeCached(ElementType.class), + + // uncached constants. + CLASS_Type = makeWithoutCaching(Class.class), COMPARABLE_TYPE = makeWithoutCaching(Comparable.class), + GENERATED_CLOSURE_Type = makeWithoutCaching(GeneratedClosure.class), + GROOVY_OBJECT_SUPPORT_TYPE = makeWithoutCaching(GroovyObjectSupport.class), + GROOVY_OBJECT_TYPE = makeWithoutCaching(GroovyObject.class), + GROOVY_INTERCEPTABLE_TYPE = makeWithoutCaching(GroovyInterceptable.class); + + private static final ClassNode[] types = new ClassNode[]{ + OBJECT_TYPE, + boolean_TYPE, char_TYPE, byte_TYPE, short_TYPE, + int_TYPE, long_TYPE, double_TYPE, float_TYPE, + VOID_TYPE, CLOSURE_TYPE, GSTRING_TYPE, + LIST_TYPE, MAP_TYPE, RANGE_TYPE, PATTERN_TYPE, + SCRIPT_TYPE, STRING_TYPE, Boolean_TYPE, Character_TYPE, + Byte_TYPE, Short_TYPE, Integer_TYPE, Long_TYPE, + Double_TYPE, Float_TYPE, BigDecimal_TYPE, BigInteger_TYPE, + Number_TYPE, + void_WRAPPER_TYPE, REFERENCE_TYPE, CLASS_Type, METACLASS_TYPE, + Iterator_TYPE, GENERATED_CLOSURE_Type, GROOVY_OBJECT_SUPPORT_TYPE, + GROOVY_OBJECT_TYPE, GROOVY_INTERCEPTABLE_TYPE, Enum_Type, Annotation_TYPE + }; + + private static final int ABSTRACT_STATIC_PRIVATE = + Modifier.ABSTRACT | Modifier.PRIVATE | Modifier.STATIC; + private static final int VISIBILITY = 5; // public|protected + + protected static final ClassNode[] EMPTY_TYPE_ARRAY = {}; + + public static final String OBJECT = "java.lang.Object"; + + public static ClassNode makeCached(Class c) { + final SoftReference<ClassNode> classNodeSoftReference = ClassHelperCache.classCache.get(c); + ClassNode classNode; + if (classNodeSoftReference == null || (classNode = classNodeSoftReference.get()) == null) { + classNode = new ClassNode(c); + ClassHelperCache.classCache.put(c, new SoftReference<ClassNode>(classNode)); + + VMPluginFactory.getPlugin().setAdditionalClassInformation(classNode); + } + + return classNode; + } + + /** + * Creates an array of ClassNodes using an array of classes. + * For each of the given classes a new ClassNode will be + * created + * + * @param classes an array of classes used to create the ClassNodes + * @return an array of ClassNodes + * @see #make(Class) + */ + public static ClassNode[] make(Class[] classes) { + ClassNode[] cns = new ClassNode[classes.length]; + for (int i = 0; i < cns.length; i++) { + cns[i] = make(classes[i]); + } + + return cns; + } + + /** + * Creates a ClassNode using a given class. + * A new ClassNode object is only created if the class + * is not one of the predefined ones + * + * @param c class used to created the ClassNode + * @return ClassNode instance created from the given class + */ + public static ClassNode make(Class c) { + return make(c, true); + } + + public static ClassNode make(Class c, boolean includeGenerics) { + for (int i = 0; i < classes.length; i++) { + if (c == classes[i]) return types[i]; + } + if (c.isArray()) { + ClassNode cn = make(c.getComponentType(), includeGenerics); + return cn.makeArray(); + } + return makeWithoutCaching(c, includeGenerics); + } + + public static ClassNode makeWithoutCaching(Class c) { + return makeWithoutCaching(c, true); + } + + public static ClassNode makeWithoutCaching(Class c, boolean includeGenerics) { + if (c.isArray()) { + ClassNode cn = makeWithoutCaching(c.getComponentType(), includeGenerics); + return cn.makeArray(); + } + + final ClassNode cached = makeCached(c); + if (includeGenerics) { + return cached; + } else { + ClassNode t = makeWithoutCaching(c.getName()); + t.setRedirect(cached); + return t; + } + } + + + /** + * Creates a ClassNode using a given class. + * Unlike make(String) this method will not use the cache + * to create the ClassNode. This means the ClassNode created + * from this method using the same name will have a different + * reference + * + * @param name of the class the ClassNode is representing + * @see #make(String) + */ + public static ClassNode makeWithoutCaching(String name) { + ClassNode cn = new ClassNode(name, Opcodes.ACC_PUBLIC, OBJECT_TYPE); + cn.isPrimaryNode = false; + return cn; + } + + /** + * Creates a ClassNode using a given class. + * If the name is one of the predefined ClassNodes then the + * corresponding ClassNode instance will be returned. If the + * name is null or of length 0 the dynamic type is returned + * + * @param name of the class the ClassNode is representing + */ + public static ClassNode make(String name) { + if (name == null || name.length() == 0) return DYNAMIC_TYPE; + + for (int i = 0; i < primitiveClassNames.length; i++) { + if (primitiveClassNames[i].equals(name)) return types[i]; + } + + for (int i = 0; i < classes.length; i++) { + String cname = classes[i].getName(); + if (name.equals(cname)) return types[i]; + } + return makeWithoutCaching(name); + } + + /** + * Creates a ClassNode containing the wrapper of a ClassNode + * of primitive type. Any ClassNode representing a primitive + * type should be created using the predefined types used in + * class. The method will check the parameter for known + * references of ClassNode representing a primitive type. If + * Reference is found, then a ClassNode will be contained that + * represents the wrapper class. For example for boolean, the + * wrapper class is java.lang.Boolean. + * <p> + * If the parameter is no primitive type, the redirected + * ClassNode will be returned + * + * @param cn the ClassNode containing a possible primitive type + * @see #make(Class) + * @see #make(String) + */ + public static ClassNode getWrapper(ClassNode cn) { + cn = cn.redirect(); + if (!isPrimitiveType(cn)) return cn; + if (cn == boolean_TYPE) { + return Boolean_TYPE; + } else if (cn == byte_TYPE) { + return Byte_TYPE; + } else if (cn == char_TYPE) { + return Character_TYPE; + } else if (cn == short_TYPE) { + return Short_TYPE; + } else if (cn == int_TYPE) { + return Integer_TYPE; + } else if (cn == long_TYPE) { + return Long_TYPE; + } else if (cn == float_TYPE) { + return Float_TYPE; + } else if (cn == double_TYPE) { + return Double_TYPE; + } else if (cn == VOID_TYPE) { + return void_WRAPPER_TYPE; + } else { + return cn; + } + } + + public static ClassNode getUnwrapper(ClassNode cn) { + cn = cn.redirect(); + if (isPrimitiveType(cn)) return cn; + if (cn == Boolean_TYPE) { + return boolean_TYPE; + } else if (cn == Byte_TYPE) { + return byte_TYPE; + } else if (cn == Character_TYPE) { + return char_TYPE; + } else if (cn == Short_TYPE) { + return short_TYPE; + } else if (cn == Integer_TYPE) { + return int_TYPE; + } else if (cn == Long_TYPE) { + return long_TYPE; + } else if (cn == Float_TYPE) { + return float_TYPE; + } else if (cn == Double_TYPE) { + return double_TYPE; + } else { + return cn; + } + } + + + /** + * Test to determine if a ClassNode is a primitive type. + * Note: this only works for ClassNodes created using a + * predefined ClassNode + * + * @param cn the ClassNode containing a possible primitive type + * @return true if the ClassNode is a primitive type + * @see #make(Class) + * @see #make(String) + */ + public static boolean isPrimitiveType(ClassNode cn) { + return cn == boolean_TYPE || + cn == char_TYPE || + cn == byte_TYPE || + cn == short_TYPE || + cn == int_TYPE || + cn == long_TYPE || + cn == float_TYPE || + cn == double_TYPE || + cn == VOID_TYPE; + } + + /** + * Test to determine if a ClassNode is a type belongs to the list of types which + * are allowed to initialize constants directly in bytecode instead of using <cinit> + * <p> + * Note: this only works for ClassNodes created using a + * predefined ClassNode + * + * @param cn the ClassNode to be tested + * @return true if the ClassNode is of int, float, long, double or String type + * @see #make(Class) + * @see #make(String) + */ + public static boolean isStaticConstantInitializerType(ClassNode cn) { + return cn == int_TYPE || + cn == float_TYPE || + cn == long_TYPE || + cn == double_TYPE || + cn == STRING_TYPE || + // the next items require conversion to int when initializing + cn == byte_TYPE || + cn == char_TYPE || + cn == short_TYPE; + } + + public static boolean isNumberType(ClassNode cn) { + return cn == Byte_TYPE || + cn == Short_TYPE || + cn == Integer_TYPE || + cn == Long_TYPE || + cn == Float_TYPE || + cn == Double_TYPE || + cn == byte_TYPE || + cn == short_TYPE || + cn == int_TYPE || + cn == long_TYPE || + cn == float_TYPE || + cn == double_TYPE; + } + + public static ClassNode makeReference() { + return REFERENCE_TYPE.getPlainNodeReference(); + } + + public static boolean isCachedType(ClassNode type) { + for (ClassNode cachedType : types) { + if (cachedType == type) return true; + } + return false; + } + + static class ClassHelperCache { + static ManagedConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle()); + } + + public static boolean isSAMType(ClassNode type) { + return findSAM(type) != null; + } + + /** + * Returns the single abstract method of a class node, if it is a SAM type, or null otherwise. + * + * @param type a type for which to search for a single abstract method + * @return the method node if type is a SAM type, null otherwise + */ + public static MethodNode findSAM(ClassNode type) { + if (!Modifier.isAbstract(type.getModifiers())) return null; + if (type.isInterface()) { + List<MethodNode> methods = type.getMethods(); + MethodNode found = null; + for (MethodNode mi : methods) { + // ignore methods, that are not abstract and from Object + if (!Modifier.isAbstract(mi.getModifiers())) continue; + // ignore trait methods which have a default implementation + if (Traits.hasDefaultImplementation(mi)) continue; + if (mi.getDeclaringClass().equals(OBJECT_TYPE)) continue; + if (OBJECT_TYPE.getDeclaredMethod(mi.getName(), mi.getParameters()) != null) continue; + + // we have two methods, so no SAM + if (found != null) return null; + found = mi; + } + return found; + + } else { + + List<MethodNode> methods = type.getAbstractMethods(); + MethodNode found = null; + if (methods != null) { + for (MethodNode mi : methods) { + if (!hasUsableImplementation(type, mi)) { + if (found != null) return null; + found = mi; + } + } + } + return found; + } + } + + private static boolean hasUsableImplementation(ClassNode c, MethodNode m) { + if (c == m.getDeclaringClass()) return false; + MethodNode found = c.getDeclaredMethod(m.getName(), m.getParameters()); + if (found == null) return false; + int asp = found.getModifiers() & ABSTRACT_STATIC_PRIVATE; + int visible = found.getModifiers() & VISIBILITY; + if (visible != 0 && asp == 0) return true; + if (c.equals(OBJECT_TYPE)) return false; + return hasUsableImplementation(c.getSuperClass(), m); + } + + /** + * Returns a super class or interface for a given class depending on a given target. + * If the target is no super class or interface, then null will be returned. + * For a non-primitive array type, returns an array of the componentType's super class + * or interface if the target is also an array. + * + * @param clazz the start class + * @param goalClazz the goal class + * @return the next super class or interface + */ + public static ClassNode getNextSuperClass(ClassNode clazz, ClassNode goalClazz) { + if (clazz.isArray()) { + if (!goalClazz.isArray()) return null; + ClassNode cn = getNextSuperClass(clazz.getComponentType(), goalClazz.getComponentType()); + if (cn != null) cn = cn.makeArray(); + return cn; + } + + if (!goalClazz.isInterface()) { + if (clazz.isInterface()) { + if (OBJECT_TYPE.equals(clazz)) return null; + return OBJECT_TYPE; + } else { + return clazz.getUnresolvedSuperClass(); + } + } + + ClassNode[] interfaces = clazz.getUnresolvedInterfaces(); + for (ClassNode anInterface : interfaces) { + if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(anInterface, goalClazz)) { + return anInterface; + } + } + //none of the interfaces here match, so continue with super class + return clazz.getUnresolvedSuperClass(); + } +}
