| /* |
| * 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 groovy.transform.PackageScope; |
| import groovy.transform.PackageScopeTarget; |
| 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.ConstructorNode; |
| 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.PropertyExpression; |
| import org.codehaus.groovy.control.CompilePhase; |
| import org.codehaus.groovy.control.SourceUnit; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.ArrayList; |
| |
| /** |
| * Handles transformation for the @PackageScope annotation. |
| * <p> |
| * Both the deprecated groovy.lang.PackageScope and groovy.transform.PackageScope |
| * annotations are supported. The former will be removed in a future version of Groovy. |
| * |
| * @author Paul King |
| */ |
| @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) |
| public class PackageScopeASTTransformation extends AbstractASTTransformation { |
| |
| private static final Class MY_CLASS = PackageScope.class; |
| private static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS); |
| private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); |
| private static final String LEGACY_TYPE_NAME = "groovy.lang.PackageScope"; |
| private static final Class TARGET_CLASS = groovy.transform.PackageScopeTarget.class; |
| private static final String TARGET_CLASS_NAME = ClassHelper.make(TARGET_CLASS).getNameWithoutPackage(); |
| |
| public void visit(ASTNode[] nodes, SourceUnit source) { |
| init(nodes, source); |
| AnnotatedNode parent = (AnnotatedNode) nodes[1]; |
| AnnotationNode node = (AnnotationNode) nodes[0]; |
| boolean legacyMode = LEGACY_TYPE_NAME.equals(node.getClassNode().getName()); |
| if (!MY_TYPE.equals(node.getClassNode()) && !legacyMode) return; |
| |
| Expression value = node.getMember("value"); |
| if (parent instanceof ClassNode) { |
| List<groovy.transform.PackageScopeTarget> targets; |
| if (value == null) targets = Collections.singletonList(legacyMode ? PackageScopeTarget.FIELDS : PackageScopeTarget.CLASS); |
| else targets = determineTargets(value); |
| visitClassNode((ClassNode) parent, targets); |
| parent.getAnnotations(); |
| } else { |
| if (value != null) { |
| addError("Error during " + MY_TYPE_NAME |
| + " processing: " + TARGET_CLASS_NAME + " only allowed at class level.", parent); |
| return; |
| } |
| if (parent instanceof MethodNode) { |
| visitMethodNode((MethodNode) parent); |
| } else if (parent instanceof FieldNode) { |
| visitFieldNode((FieldNode) parent); |
| } |
| } |
| } |
| |
| private void visitMethodNode(MethodNode methodNode) { |
| if (methodNode.isSyntheticPublic()) revertVisibility(methodNode); |
| else addError("Can't use " + MY_TYPE_NAME + " for method '" + methodNode.getName() + "' which has explicit visibility.", methodNode); |
| } |
| |
| private void visitClassNode(ClassNode cNode, List<PackageScopeTarget> value) { |
| String cName = cNode.getName(); |
| if (cNode.isInterface() && value.size() != 1 && value.get(0) != PackageScopeTarget.CLASS) { |
| addError("Error processing interface '" + cName + "'. " + MY_TYPE_NAME + " not allowed for interfaces except when targeting Class level.", cNode); |
| } |
| if (value.contains(groovy.transform.PackageScopeTarget.CLASS)) { |
| if (cNode.isSyntheticPublic()) revertVisibility(cNode); |
| else addError("Can't use " + MY_TYPE_NAME + " for class '" + cNode.getName() + "' which has explicit visibility.", cNode); |
| } |
| if (value.contains(groovy.transform.PackageScopeTarget.METHODS)) { |
| final List<MethodNode> mList = cNode.getMethods(); |
| for (MethodNode mNode : mList) { |
| if (mNode.isSyntheticPublic()) revertVisibility(mNode); |
| } |
| } |
| if (value.contains(groovy.transform.PackageScopeTarget.CONSTRUCTORS)) { |
| final List<ConstructorNode> cList = cNode.getDeclaredConstructors(); |
| for (MethodNode mNode : cList) { |
| if (mNode.isSyntheticPublic()) revertVisibility(mNode); |
| } |
| } |
| if (value.contains(PackageScopeTarget.FIELDS)) { |
| final List<PropertyNode> pList = cNode.getProperties(); |
| List<PropertyNode> foundProps = new ArrayList<PropertyNode>(); |
| List<String> foundNames = new ArrayList<String>(); |
| for (PropertyNode pNode : pList) { |
| foundProps.add(pNode); |
| foundNames.add(pNode.getName()); |
| } |
| for (PropertyNode pNode : foundProps) { |
| pList.remove(pNode); |
| } |
| final List<FieldNode> fList = cNode.getFields(); |
| for (FieldNode fNode : fList) { |
| if (foundNames.contains(fNode.getName())) { |
| revertVisibility(fNode); |
| } |
| } |
| } |
| } |
| |
| private static void visitFieldNode(FieldNode fNode) { |
| final ClassNode cNode = fNode.getDeclaringClass(); |
| final List<PropertyNode> pList = cNode.getProperties(); |
| PropertyNode foundProp = null; |
| for (PropertyNode pNode : pList) { |
| if (pNode.getName().equals(fNode.getName())) { |
| foundProp = pNode; |
| break; |
| } |
| } |
| if (foundProp != null) { |
| revertVisibility(fNode); |
| pList.remove(foundProp); |
| } |
| } |
| |
| private static void revertVisibility(FieldNode fNode) { |
| fNode.setModifiers(fNode.getModifiers() & ~ACC_PRIVATE); |
| } |
| |
| private static void revertVisibility(MethodNode mNode) { |
| mNode.setModifiers(mNode.getModifiers() & ~ACC_PUBLIC); |
| } |
| |
| private static void revertVisibility(ClassNode cNode) { |
| cNode.setModifiers(cNode.getModifiers() & ~ACC_PUBLIC); |
| } |
| |
| private static List<groovy.transform.PackageScopeTarget> determineTargets(Expression expr) { |
| List<groovy.transform.PackageScopeTarget> list = new ArrayList<groovy.transform.PackageScopeTarget>(); |
| if (expr instanceof PropertyExpression) { |
| list.add(extractTarget((PropertyExpression) expr)); |
| } else if (expr instanceof ListExpression) { |
| final ListExpression expressionList = (ListExpression) expr; |
| final List<Expression> expressions = expressionList.getExpressions(); |
| for (Expression ex : expressions) { |
| if (ex instanceof PropertyExpression) { |
| list.add(extractTarget((PropertyExpression) ex)); |
| } |
| } |
| } |
| return list; |
| } |
| |
| private static groovy.transform.PackageScopeTarget extractTarget(PropertyExpression expr) { |
| Expression oe = expr.getObjectExpression(); |
| if (oe instanceof ClassExpression) { |
| ClassExpression ce = (ClassExpression) oe; |
| if (ce.getType().getName().equals("groovy.transform.PackageScopeTarget")) { |
| Expression prop = expr.getProperty(); |
| if (prop instanceof ConstantExpression) { |
| String propName = (String) ((ConstantExpression) prop).getValue(); |
| try { |
| return PackageScopeTarget.valueOf(propName); |
| } catch(IllegalArgumentException iae) { |
| /* ignore */ |
| } |
| } |
| } |
| } |
| throw new GroovyBugError("Internal error during " + MY_TYPE_NAME |
| + " processing. Annotation parameters must be of type: " + TARGET_CLASS_NAME + "."); |
| } |
| |
| } |