| /* |
| * Copyright 2003-2014 the original author or authors. |
| * |
| * Licensed 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.lang.GroovyClassLoader; |
| import groovy.lang.GroovyRuntimeException; |
| import groovy.transform.CompilationUnitAware; |
| 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.ClassCodeExpressionTransformer; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.DynamicVariable; |
| import org.codehaus.groovy.ast.FieldNode; |
| import org.codehaus.groovy.ast.InnerClassNode; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.MethodCallExpression; |
| import org.codehaus.groovy.ast.expr.TupleExpression; |
| import org.codehaus.groovy.ast.expr.VariableExpression; |
| import org.codehaus.groovy.classgen.VariableScopeVisitor; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.CompilePhase; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.runtime.DefaultGroovyMethods; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Iterator; |
| |
| /** |
| * This class provides an AST Transformation to add a log field to a class. |
| * |
| * @author Guillaume Laforge |
| * @author Jochen Theodorou |
| * @author Dinko Srkoc |
| * @author Hamlet D'Arcy |
| * @author Raffaele Cigni |
| * @author Alberto Vilches Raton |
| * @author Tomasz Bujok |
| * @author Martin Ghados |
| * @author Matthias Cullmann |
| */ |
| @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) |
| public class LogASTTransformation extends AbstractASTTransformation implements CompilationUnitAware { |
| |
| /** |
| * This is just a dummy value used because String annotations values can not be null. |
| * It will be replaced by the fully qualified class name of the annotated class. |
| */ |
| public static final String DEFAULT_CATEGORY_NAME = "##default-category-name##"; |
| |
| private CompilationUnit compilationUnit; |
| |
| public void visit(ASTNode[] nodes, final SourceUnit source) { |
| init(nodes, source); |
| AnnotatedNode targetClass = (AnnotatedNode) nodes[1]; |
| AnnotationNode logAnnotation = (AnnotationNode) nodes[0]; |
| |
| final GroovyClassLoader classLoader = compilationUnit != null ? compilationUnit.getTransformLoader() : source.getClassLoader(); |
| final LoggingStrategy loggingStrategy = createLoggingStrategy(logAnnotation, classLoader); |
| if (loggingStrategy == null) return; |
| |
| final String logFieldName = lookupLogFieldName(logAnnotation); |
| |
| final String categoryName = lookupCategoryName(logAnnotation); |
| |
| if (!(targetClass instanceof ClassNode)) |
| throw new GroovyBugError("Class annotation " + logAnnotation.getClassNode().getName() + " annotated no Class, this must not happen."); |
| |
| final ClassNode classNode = (ClassNode) targetClass; |
| |
| ClassCodeExpressionTransformer transformer = new ClassCodeExpressionTransformer() { |
| private FieldNode logNode; |
| |
| @Override |
| protected SourceUnit getSourceUnit() { |
| return source; |
| } |
| |
| public Expression transform(Expression exp) { |
| if (exp == null) return null; |
| if (exp instanceof MethodCallExpression) { |
| return transformMethodCallExpression(exp); |
| } |
| return super.transform(exp); |
| } |
| |
| @Override |
| public void visitClass(ClassNode node) { |
| FieldNode logField = node.getField(logFieldName); |
| if (logField != null && logField.getOwner().equals(node)) { |
| addError("Class annotated with Log annotation cannot have log field declared", logField); |
| } else if (logField != null && !Modifier.isPrivate(logField.getModifiers())) { |
| addError("Class annotated with Log annotation cannot have log field declared because the field exists in the parent class: " + logField.getOwner().getName(), logField); |
| } else { |
| logNode = loggingStrategy.addLoggerFieldToClass(node, logFieldName, categoryName); |
| } |
| super.visitClass(node); |
| } |
| |
| private Expression transformMethodCallExpression(Expression exp) { |
| MethodCallExpression mce = (MethodCallExpression) exp; |
| if (!(mce.getObjectExpression() instanceof VariableExpression)) { |
| return exp; |
| } |
| VariableExpression variableExpression = (VariableExpression) mce.getObjectExpression(); |
| if (!variableExpression.getName().equals(logFieldName) |
| || !(variableExpression.getAccessedVariable() instanceof DynamicVariable)) { |
| return exp; |
| } |
| String methodName = mce.getMethodAsString(); |
| if (methodName == null) return exp; |
| if (usesSimpleMethodArgumentsOnly(mce)) return exp; |
| |
| variableExpression.setAccessedVariable(logNode); |
| |
| if (!loggingStrategy.isLoggingMethod(methodName)) return exp; |
| |
| return loggingStrategy.wrapLoggingMethodCall(variableExpression, methodName, exp); |
| } |
| |
| private boolean usesSimpleMethodArgumentsOnly(MethodCallExpression mce) { |
| Expression arguments = mce.getArguments(); |
| if (arguments instanceof TupleExpression) { |
| TupleExpression tuple = (TupleExpression) arguments; |
| for (Expression exp : tuple.getExpressions()) { |
| if (!isSimpleExpression(exp)) return false; |
| } |
| return true; |
| } |
| return !isSimpleExpression(arguments); |
| } |
| |
| private boolean isSimpleExpression(Expression exp) { |
| if (exp instanceof ConstantExpression) return true; |
| if (exp instanceof VariableExpression) return true; |
| return false; |
| } |
| |
| }; |
| transformer.visitClass(classNode); |
| |
| // GROOVY-6373: references to 'log' field are normally already FieldNodes by now, so revisit scoping |
| new VariableScopeVisitor(sourceUnit, true).visitClass(classNode); |
| } |
| |
| private String lookupLogFieldName(AnnotationNode logAnnotation) { |
| Expression member = logAnnotation.getMember("value"); |
| if (member != null && member.getText() != null) { |
| return member.getText(); |
| } else { |
| return "log"; |
| } |
| } |
| |
| private String lookupCategoryName(AnnotationNode logAnnotation) { |
| Expression member = logAnnotation.getMember("category"); |
| if (member != null && member.getText() != null) { |
| return member.getText(); |
| } |
| return DEFAULT_CATEGORY_NAME; |
| } |
| |
| private LoggingStrategy createLoggingStrategy(AnnotationNode logAnnotation, GroovyClassLoader loader) { |
| |
| String annotationName = logAnnotation.getClassNode().getName(); |
| |
| Class annotationClass; |
| try { |
| annotationClass = Class.forName(annotationName, false, loader); |
| } catch (Throwable e) { |
| throw new RuntimeException("Could not resolve class named " + annotationName); |
| } |
| |
| Method annotationMethod; |
| try { |
| annotationMethod = annotationClass.getDeclaredMethod("loggingStrategy", (Class[]) null); |
| } catch (Throwable e) { |
| throw new RuntimeException("Could not find method named loggingStrategy on class named " + annotationName); |
| } |
| |
| Object defaultValue; |
| try { |
| defaultValue = annotationMethod.getDefaultValue(); |
| } catch (Throwable e) { |
| throw new RuntimeException("Could not find default value of method named loggingStrategy on class named " + annotationName); |
| } |
| |
| if (!LoggingStrategy.class.isAssignableFrom((Class) defaultValue)) { |
| throw new RuntimeException("Default loggingStrategy value on class named " + annotationName + " is not a LoggingStrategy"); |
| } |
| |
| try { |
| Class<? extends LoggingStrategy> strategyClass = (Class<? extends LoggingStrategy>) defaultValue; |
| if (AbstractLoggingStrategy.class.isAssignableFrom(strategyClass)) { |
| return DefaultGroovyMethods.newInstance(strategyClass, new Object[]{loader}); |
| } else { |
| return strategyClass.newInstance(); |
| } |
| } catch (Exception e) { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * A LoggingStrategy defines how to wire a new logger instance into an existing class. |
| * It is meant to be used with the @Log family of annotations to allow you to |
| * write your own Log annotation provider. |
| */ |
| public interface LoggingStrategy { |
| /** |
| * In this method, you are given a ClassNode, a field name and a category name, and you must add a new Field |
| * onto the class. Return the result of the ClassNode.addField operations. |
| * |
| * @param classNode the class that was originally annotated with the Log transformation. |
| * @param fieldName the name of the logger field |
| * @param categoryName the name of the logging category |
| * @return the FieldNode instance that was created and added to the class |
| */ |
| FieldNode addLoggerFieldToClass(ClassNode classNode, String fieldName, String categoryName); |
| |
| boolean isLoggingMethod(String methodName); |
| |
| String getCategoryName(ClassNode classNode, String categoryName); |
| |
| Expression wrapLoggingMethodCall(Expression logVariable, String methodName, Expression originalExpression); |
| } |
| |
| public static abstract class AbstractLoggingStrategy implements LoggingStrategy { |
| protected final GroovyClassLoader loader; |
| |
| protected AbstractLoggingStrategy(final GroovyClassLoader loader) { |
| this.loader = loader; |
| } |
| |
| protected AbstractLoggingStrategy() { |
| this(null); |
| } |
| |
| public String getCategoryName(ClassNode classNode, String categoryName) { |
| if (categoryName.equals(DEFAULT_CATEGORY_NAME)) { |
| return classNode.getName(); |
| } |
| return categoryName; |
| } |
| |
| protected ClassNode classNode(String name) { |
| ClassLoader cl = loader == null ? this.getClass().getClassLoader() : loader; |
| try { |
| return ClassHelper.make(Class.forName(name, false, cl)); |
| } catch (ClassNotFoundException e) { |
| throw new GroovyRuntimeException("Unable to load logging class", e); |
| } |
| } |
| } |
| |
| public void setCompilationUnit(final CompilationUnit unit) { |
| this.compilationUnit = unit; |
| } |
| } |