blob: 3e5a404d115aae15b1d6b89576e05bde32befe85 [file] [log] [blame]
/*
* 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.transform;
import org.apache.groovy.ast.tools.AnnotatedNodeUtils;
import org.apache.groovy.ast.tools.MethodNodeUtils;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.tools.BeanUtils;
import org.codehaus.groovy.ast.tools.GeneralUtils;
import org.codehaus.groovy.ast.tools.GenericsUtils;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.runtime.StringGroovyMethods;
import org.codehaus.groovy.syntax.SyntaxException;
import org.objectweb.asm.Opcodes;
import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static groovy.transform.Undefined.isUndefined;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInstanceNonPropertyFieldNames;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSuperNonPropertyFields;
public abstract class AbstractASTTransformation implements Opcodes, ASTTransformation, ErrorCollecting {
public static final ClassNode RETENTION_CLASSNODE = ClassHelper.makeWithoutCaching(Retention.class);
protected SourceUnit sourceUnit;
/**
* Copies all <tt>candidateAnnotations</tt> with retention policy {@link java.lang.annotation.RetentionPolicy#RUNTIME}
* and {@link java.lang.annotation.RetentionPolicy#CLASS}.
* <p>
* Annotations with {@link org.codehaus.groovy.runtime.GeneratedClosure} members are not supported for now.
*/
protected List<AnnotationNode> copyAnnotatedNodeAnnotations(final AnnotatedNode annotatedNode, String myTypeName) {
return copyAnnotatedNodeAnnotations(annotatedNode, myTypeName, true);
}
/**
* Copies all <tt>candidateAnnotations</tt> with retention policy {@link java.lang.annotation.RetentionPolicy#RUNTIME}
* and {@link java.lang.annotation.RetentionPolicy#CLASS}.
* <p>
* Annotations with {@link org.codehaus.groovy.runtime.GeneratedClosure} members are not supported for now.
*/
protected List<AnnotationNode> copyAnnotatedNodeAnnotations(final AnnotatedNode annotatedNode, String myTypeName, boolean includeGenerated) {
final List<AnnotationNode> copiedAnnotations = new ArrayList<>();
final List<AnnotationNode> notCopied = new ArrayList<>();
GeneralUtils.copyAnnotatedNodeAnnotations(annotatedNode, copiedAnnotations, notCopied, includeGenerated);
for (AnnotationNode annotation : notCopied) {
addError(myTypeName + " does not support keeping Closure annotation members.", annotation);
}
return copiedAnnotations;
}
/**
* If the transform is associated with a single annotation, returns a name suitable for displaying in error messages.
*
* @return The simple name of the annotation including the "@" or null if no such name is defined
*/
public String getAnnotationName() {
return null;
}
protected void init(ASTNode[] nodes, SourceUnit sourceUnit) {
if (nodes == null || nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
throw new GroovyBugError("Internal error: expecting [AnnotationNode, AnnotatedNode] but got: " + (nodes == null ? null : Arrays.asList(nodes)));
}
this.sourceUnit = sourceUnit;
}
public boolean memberHasValue(AnnotationNode node, String name, Object value) {
final Expression member = node.getMember(name);
return member instanceof ConstantExpression && ((ConstantExpression) member).getValue().equals(value);
}
public Object getMemberValue(AnnotationNode node, String name) {
final Expression member = node.getMember(name);
if (member instanceof ConstantExpression) return ((ConstantExpression) member).getValue();
return null;
}
public static String getMemberStringValue(AnnotationNode node, String name, String defaultValue) {
final Expression member = node.getMember(name);
if (member instanceof ConstantExpression) {
Object result = ((ConstantExpression) member).getValue();
if (result instanceof String && isUndefined((String) result)) result = null;
if (result != null) return result.toString();
}
return defaultValue;
}
public static String getMemberStringValue(AnnotationNode node, String name) {
return getMemberStringValue(node, name, null);
}
public int getMemberIntValue(AnnotationNode node, String name) {
Object value = getMemberValue(node, name);
if (value instanceof Integer) {
return (Integer) value;
}
return 0;
}
public ClassNode getMemberClassValue(AnnotationNode node, String name) {
return getMemberClassValue(node, name, null);
}
public ClassNode getMemberClassValue(AnnotationNode node, String name, ClassNode defaultValue) {
final Expression member = node.getMember(name);
if (member != null) {
if (member instanceof ClassExpression) {
if (!isUndefined(member.getType())) return member.getType();
} else if (member instanceof VariableExpression) {
addError("Error expecting to find class value for '" + name + "' but found variable: " + member.getText() + ". Missing import?", node);
return null;
} else if (member instanceof ConstantExpression) {
addError("Error expecting to find class value for '" + name + "' but found constant: " + member.getText() + "!", node);
return null;
}
}
return defaultValue;
}
public static List<String> getMemberStringList(AnnotationNode anno, String name) {
Expression expr = anno.getMember(name);
if (expr == null) {
return null;
}
if (expr instanceof ListExpression) {
final ListExpression listExpression = (ListExpression) expr;
if (isUndefinedMarkerList(listExpression)) {
return null;
}
return getValueStringList(listExpression);
}
return tokenize(getMemberStringValue(anno, name));
}
private static boolean isUndefinedMarkerList(ListExpression listExpression) {
if (listExpression.getExpressions().size() != 1) return false;
Expression itemExpr = listExpression.getExpression(0);
if (itemExpr == null) return false;
if (itemExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) itemExpr).getValue();
if (value instanceof String && isUndefined((String)value)) return true;
} else if (itemExpr instanceof ClassExpression && isUndefined(itemExpr.getType())) {
return true;
}
return false;
}
private static List<String> getValueStringList(ListExpression listExpression) {
List<String> list = new ArrayList<>();
for (Expression itemExpr : listExpression.getExpressions()) {
if (itemExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) itemExpr).getValue();
if (value != null) list.add(value.toString());
}
}
return list;
}
public List<ClassNode> getMemberClassList(AnnotationNode anno, String name) {
List<ClassNode> list = new ArrayList<>();
Expression expr = anno.getMember(name);
if (expr == null) {
return null;
}
if (expr instanceof ListExpression) {
final ListExpression listExpression = (ListExpression) expr;
if (isUndefinedMarkerList(listExpression)) {
return null;
}
list = getTypeList(listExpression);
} else if (expr instanceof ClassExpression) {
ClassNode cn = expr.getType();
if (isUndefined(cn)) return null;
if (cn != null) list.add(cn);
}
return list;
}
private static List<ClassNode> getTypeList(ListExpression listExpression) {
List<ClassNode> list = new ArrayList<>();
for (Expression itemExpr : listExpression.getExpressions()) {
if (itemExpr instanceof ClassExpression) {
ClassNode cn = itemExpr.getType();
if (cn != null) list.add(cn);
}
}
return list;
}
public void addError(String msg, ASTNode expr) {
sourceUnit.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(
new SyntaxException(msg + '\n', expr.getLineNumber(), expr.getColumnNumber(),
expr.getLastLineNumber(), expr.getLastColumnNumber()),
sourceUnit)
);
}
protected boolean checkNotInterface(ClassNode cNode, String annotationName) {
if (cNode.isInterface()) {
addError("Error processing interface '" + cNode.getName() + "'. " +
annotationName + " not allowed for interfaces.", cNode);
return false;
}
return true;
}
public boolean hasAnnotation(ClassNode node, ClassNode annotation) {
return AnnotatedNodeUtils.hasAnnotation(node, annotation);
}
public static List<String> tokenize(String rawExcludes) {
return rawExcludes == null ? new ArrayList<>() : StringGroovyMethods.tokenize(rawExcludes, ", ");
}
public static boolean deemedInternalName(String name) {
return name.contains("$");
}
public static boolean shouldSkipUndefinedAware(String name, List<String> excludes, List<String> includes) {
return shouldSkipUndefinedAware(name, excludes, includes, false);
}
public static boolean shouldSkipUndefinedAware(String name, List<String> excludes, List<String> includes, boolean allNames) {
return (excludes != null && excludes.contains(name)) ||
(!allNames && deemedInternalName(name)) ||
(includes != null && !includes.contains(name));
}
public static boolean shouldSkip(String name, List<String> excludes, List<String> includes) {
return shouldSkip(name, excludes, includes, false);
}
public static boolean shouldSkip(String name, List<String> excludes, List<String> includes, boolean allNames) {
return (excludes != null && excludes.contains(name)) ||
(!allNames && deemedInternalName(name)) ||
(includes != null && !includes.isEmpty() && !includes.contains(name));
}
public static boolean shouldSkipOnDescriptorUndefinedAware(boolean checkReturn, Map genericsSpec, MethodNode mNode,
List<ClassNode> excludeTypes, List<ClassNode> includeTypes) {
String descriptor = mNode.getTypeDescriptor();
String descriptorNoReturn = MethodNodeUtils.methodDescriptorWithoutReturnType(mNode);
if (excludeTypes != null) {
for (ClassNode cn : excludeTypes) {
List<ClassNode> remaining = new LinkedList<>();
remaining.add(cn);
Map updatedGenericsSpec = new HashMap(genericsSpec);
while (!remaining.isEmpty()) {
ClassNode next = remaining.remove(0);
if (!next.equals(ClassHelper.OBJECT_TYPE)) {
updatedGenericsSpec = GenericsUtils.createGenericsSpec(next, updatedGenericsSpec);
for (MethodNode mn : next.getMethods()) {
MethodNode correctedMethodNode = GenericsUtils.correctToGenericsSpec(updatedGenericsSpec, mn);
if (checkReturn) {
String md = correctedMethodNode.getTypeDescriptor();
if (md.equals(descriptor)) return true;
} else {
String md = MethodNodeUtils.methodDescriptorWithoutReturnType(correctedMethodNode);
if (md.equals(descriptorNoReturn)) return true;
}
}
remaining.addAll(Arrays.asList(next.getInterfaces()));
}
}
}
}
if (includeTypes == null) return false;
for (ClassNode cn : includeTypes) {
List<ClassNode> remaining = new LinkedList<>();
remaining.add(cn);
Map updatedGenericsSpec = new HashMap(genericsSpec);
while (!remaining.isEmpty()) {
ClassNode next = remaining.remove(0);
if (!next.equals(ClassHelper.OBJECT_TYPE)) {
updatedGenericsSpec = GenericsUtils.createGenericsSpec(next, updatedGenericsSpec);
for (MethodNode mn : next.getMethods()) {
MethodNode correctedMethodNode = GenericsUtils.correctToGenericsSpec(updatedGenericsSpec, mn);
if (checkReturn) {
String md = correctedMethodNode.getTypeDescriptor();
if (md.equals(descriptor)) return false;
} else {
String md = MethodNodeUtils.methodDescriptorWithoutReturnType(correctedMethodNode);
if (md.equals(descriptorNoReturn)) return false;
}
}
remaining.addAll(Arrays.asList(next.getInterfaces()));
}
}
}
return true;
}
protected boolean checkIncludeExcludeUndefinedAware(AnnotationNode node, List<String> excludes, List<String> includes, String typeName) {
if (includes != null && excludes != null && !excludes.isEmpty()) {
addError("Error during " + typeName + " processing: Only one of 'includes' and 'excludes' should be supplied not both.", node);
return false;
}
return true;
}
protected void checkIncludeExcludeUndefinedAware(AnnotationNode node, List<String> excludes, List<String> includes,
List<ClassNode> excludeTypes, List<ClassNode> includeTypes, String typeName) {
int found = 0;
if (includes != null) found++;
if (excludes != null && !excludes.isEmpty()) found++;
if (includeTypes != null) found++;
if (excludeTypes != null && !excludeTypes.isEmpty()) found++;
if (found > 1) {
addError("Error during " + typeName + " processing: Only one of 'includes', 'excludes', 'includeTypes' and 'excludeTypes' should be supplied.", node);
}
}
public boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields) {
return checkPropertyList(cNode, propertyNameList, listName, anno, typeName, includeFields, false, false);
}
public boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties) {
return checkPropertyList(cNode, propertyNameList, listName, anno, typeName, includeFields, includeSuperProperties, allProperties, false, false);
}
public boolean checkPropertyList(ClassNode cNode, List<String> propertyNameList, String listName, AnnotationNode anno, String typeName, boolean includeFields, boolean includeSuperProperties, boolean allProperties, boolean includeSuperFields, boolean includeStatic) {
if (propertyNameList == null || propertyNameList.isEmpty()) {
return true;
}
final List<String> pNames = new ArrayList<>();
for (PropertyNode pNode : BeanUtils.getAllProperties(cNode, includeSuperProperties, includeStatic, allProperties)) {
pNames.add(pNode.getField().getName());
}
boolean result = true;
if (includeFields || includeSuperFields) {
final List<String> fNames = new ArrayList<>();
if (includeFields) {
fNames.addAll(getInstanceNonPropertyFieldNames(cNode));
}
if (includeSuperFields) {
List<FieldNode> superNonPropertyFields = getSuperNonPropertyFields(cNode.getSuperClass());
for (FieldNode fn : superNonPropertyFields) {
fNames.add(fn.getName());
}
}
for (String pName : propertyNameList) {
if (!pNames.contains(pName) && !fNames.contains(pName)) {
addError("Error during " + typeName + " processing: '" + listName + "' property or field '" + pName + "' does not exist.", anno);
result = false;
}
}
} else {
for (String pName : propertyNameList) {
if (!pNames.contains(pName)) {
addError("Error during " + typeName + " processing: '" + listName + "' property '" + pName + "' does not exist.", anno);
result = false;
}
}
}
return result;
}
}