fix NPE in DOMAspect.aj

fix NPE in DOMAspect.aj

/*
 * Copyright 2005-2010 LAMP/EPFL
 */
// $Id$

package scala.tools.eclipse.contribution.weaving.jdt.core;

import java.util.Locale;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.ISourceType;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.SourceAnnotationMethodInfo;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.internal.core.SourceMethodElementInfo;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;

import scala.tools.eclipse.contribution.weaving.jdt.IScalaCompilationUnit;

@SuppressWarnings("restriction")
public privileged aspect DOMAspect {
  pointcut internalCreateAST(ASTParser parser, IProgressMonitor monitor) :
    execution(ASTNode ASTParser.internalCreateAST(IProgressMonitor)) &&
    args(monitor) &&
    target(parser);

  pointcut convert(SourceTypeConverter stc, SourceMethod methodHandle, SourceMethodElementInfo methodInfo, CompilationResult compilationResult) :
    execution(AbstractMethodDeclaration SourceTypeConverter.convert(SourceMethod, SourceMethodElementInfo, CompilationResult)) &&
    args(methodHandle, methodInfo, compilationResult) &&
    target(stc);

  ASTNode around(ASTParser parser, IProgressMonitor monitor) :
    internalCreateAST(parser, monitor) {
    try {
      if (!(parser.typeRoot instanceof IScalaCompilationUnit))
        return proceed(parser, monitor);

      ICompilationUnit cu = (ICompilationUnit)parser.typeRoot;
      org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit)cu;

      IType[] topLevelTypes = cu.getTypes();
      int length = topLevelTypes.length;
      if (length == 0)
        throw new IllegalStateException();

      SourceTypeElementInfo[] topLevelInfos = new SourceTypeElementInfo[length];
      for (int i = 0; i < length; i++) {
        topLevelInfos[i] = (SourceTypeElementInfo) ((SourceType)topLevelTypes[i]).getElementInfo();
      }
      char[] fileName = ((ISourceType)topLevelInfos[0]).getFileName();

      CompilationResult result = new CompilationResult(fileName, 1, 1, 100);

      JavaProject javaProject = (JavaProject)cu.getJavaProject();
      Map compilerOptions0 = javaProject.getOptions(true);
      CompilerOptions compilerOptions = new CompilerOptions(compilerOptions0);
      compilerOptions.storeAnnotations = true;

      ProblemReporter problemReporter =
        new ProblemReporter(
          DefaultErrorHandlingPolicies.proceedWithAllProblems(),
          compilerOptions,
          new DefaultProblemFactory(Locale.getDefault()));

      int flags = SourceTypeConverter.FIELD_AND_METHOD | SourceTypeConverter.MEMBER_TYPE;

      CompilationUnitDeclaration unit =
        SourceTypeConverter.buildCompilationUnit(topLevelInfos, flags, problemReporter, result);

      INameEnvironment ne = javaProject.newSearchableNameEnvironment(DefaultWorkingCopyOwner.PRIMARY);
      final LookupEnvironment le = new LookupEnvironment(null, compilerOptions, problemReporter, ne);
      ITypeRequestor typeRequestor = new ITypeRequestor() {
        public void accept(IBinaryType binaryType, PackageBinding packageBinding, AccessRestriction accessRestriction) {
          le.createBinaryTypeFrom(binaryType, packageBinding, accessRestriction);
        }
        public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit, AccessRestriction accessRestriction) {}
        public void accept(ISourceType[] sourceType, PackageBinding packageBinding, AccessRestriction accessRestriction) {}
      };
      le.typeRequestor = typeRequestor;

      le.buildTypeBindings(unit, null);
      le.completeTypeBindings();
      fixTypes(unit.types);

      AST ast = AST.newAST(AST.JLS3);
      ast.setFlag(AST.RESOLVED_BINDINGS);
      ast.setDefaultNodeFlag(ASTNode.ORIGINAL);

      org.eclipse.jdt.core.dom.ASTConverter converter =
        new org.eclipse.jdt.core.dom.ASTConverter(compilerOptions0, true, monitor);

      org.eclipse.jdt.core.dom.BindingResolver resolver =
        new org.eclipse.jdt.core.dom.DefaultBindingResolver(
          unit.scope,
          DefaultWorkingCopyOwner.PRIMARY,
          new org.eclipse.jdt.core.dom.DefaultBindingResolver.BindingTables(),
          true);
      ast.setBindingResolver(resolver);
      converter.setAST(ast);

      ASTNode node = converter.convert(unit, sourceUnit.getContents());
      return node;
    } catch (JavaModelException ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  private void fixTypes(TypeDeclaration[] types) {
    for(int i = 0, iLimit = types.length; i < iLimit ; ++i) {
      TypeDeclaration tpe = types[i];
      if (tpe != null) {
        if (tpe.binding != null) {
          tpe.binding.getAnnotationTagBits();
        }
        if (tpe.scope != null) {
          tpe.scope.buildFields();
          tpe.scope.buildMethods();
        }
        fixFields(tpe.fields);
        fixMethods(tpe.methods);
        fixTypes(tpe.memberTypes);
      }
    }
  }

  private void fixMethods(AbstractMethodDeclaration[] methods) {
    if (methods == null)
      return;

    for(int i = 0, iLimit = methods.length; i < iLimit; ++i) {
      AbstractMethodDeclaration m = methods[i];
      m.bodyStart = m.declarationSourceStart;
      m.bodyEnd = m.declarationSourceEnd;
      if (m.binding != null) {
        m.binding.getAnnotationTagBits();
      }
    }
  }

  private void fixFields(AbstractVariableDeclaration[] fields) {
    if (fields == null)
      return;

    for(int i = 0, iLimit = fields.length; i < iLimit; ++i) {
      AbstractVariableDeclaration f = fields[i];
      f.declarationEnd = f.declarationSourceEnd;
    }
  }

  AbstractMethodDeclaration around(SourceTypeConverter stc, SourceMethod methodHandle, SourceMethodElementInfo methodInfo, CompilationResult compilationResult) throws JavaModelException :
    convert(stc, methodHandle, methodInfo, compilationResult) {
    AbstractMethodDeclaration method;

    /* only source positions available */
    int start = methodInfo.getNameSourceStart();
    int end = methodInfo.getNameSourceEnd();

    // convert 1.5 specific constructs only if compliance is 1.5 or above
    TypeParameter[] typeParams = null;
    if (stc.has1_5Compliance) {
      /* convert type parameters */
      char[][] typeParameterNames = methodInfo.getTypeParameterNames();
      if (typeParameterNames != null) {
        int parameterCount = typeParameterNames.length;
        if (parameterCount > 0) { // method's type parameters must be null if no type parameter
          char[][][] typeParameterBounds = methodInfo.getTypeParameterBounds();
          typeParams = new TypeParameter[parameterCount];
          for (int i = 0; i < parameterCount; i++) {
            typeParams[i] = stc.createTypeParameter(typeParameterNames[i], typeParameterBounds[i], start, end);
          }
        }
      }
    }

    int modifiers = methodInfo.getModifiers();
    if (methodInfo.isConstructor()) {
      ConstructorDeclaration decl = new ConstructorDeclaration(compilationResult);
      decl.bits &= ~org.eclipse.jdt.internal.compiler.ast.ASTNode.IsDefaultConstructor;
      method = decl;
      decl.typeParameters = typeParams;
    } else {
      MethodDeclaration decl;
      if (methodInfo.isAnnotationMethod()) {
        AnnotationMethodDeclaration annotationMethodDeclaration = new AnnotationMethodDeclaration(compilationResult);

        /* conversion of default value */
        SourceAnnotationMethodInfo annotationMethodInfo = (SourceAnnotationMethodInfo) methodInfo;
        boolean hasDefaultValue = annotationMethodInfo.defaultValueStart != -1 || annotationMethodInfo.defaultValueEnd != -1;
        if ((stc.flags & SourceTypeConverter.FIELD_INITIALIZATION) != 0) {
          if (hasDefaultValue) {
            char[] defaultValueSource = CharOperation.subarray(stc.getSource(), annotationMethodInfo.defaultValueStart, annotationMethodInfo.defaultValueEnd+1);
            if (defaultValueSource != null) {
                Expression expression =  stc.parseMemberValue(defaultValueSource);
                if (expression != null) {
                  annotationMethodDeclaration.defaultValue = expression;
                }
            } else {
              // could not retrieve the default value
              hasDefaultValue = false;
            }
          }
        }
        if (hasDefaultValue)
          modifiers |= ClassFileConstants.AccAnnotationDefault;
        decl = annotationMethodDeclaration;
      } else {
        decl = new MethodDeclaration(compilationResult);
      }

      // convert return type
      decl.returnType = stc.createTypeReference(methodInfo.getReturnTypeName(), start, end);

      // type parameters
      decl.typeParameters = typeParams;

      method = decl;
    }
    method.selector = methodHandle.getElementName().toCharArray();
    boolean isVarargs = (modifiers & ClassFileConstants.AccVarargs) != 0;
    method.modifiers = modifiers & ~ClassFileConstants.AccVarargs;
    method.sourceStart = start;
    method.sourceEnd = end;
    method.declarationSourceStart = methodInfo.getDeclarationSourceStart();
    method.declarationSourceEnd = methodInfo.getDeclarationSourceEnd();

    // convert 1.5 specific constructs only if compliance is 1.5 or above
    if (stc.has1_5Compliance) {
      /* convert annotations */
      method.annotations = stc.convertAnnotations(methodHandle);
    }

    /* convert arguments */
    String[] argumentTypeSignatures = methodHandle.getParameterTypes();
    char[][] argumentNames = methodInfo.getArgumentNames();
    int argumentCount = argumentTypeSignatures == null ? 0 : argumentTypeSignatures.length;
    if (argumentCount > 0) {
      long position = ((long) start << 32) + end;
      method.arguments = new Argument[argumentCount];
      for (int i = 0; i < argumentCount; i++) {
        TypeReference typeReference = stc.createTypeReference(argumentTypeSignatures[i], start, end);
        if (isVarargs && i == argumentCount-1) {
          typeReference.bits |= org.eclipse.jdt.internal.compiler.ast.ASTNode.IsVarArgs;
        }
        method.arguments[i] =
          new Argument(
            argumentNames[i],
            position,
            typeReference,
            ClassFileConstants.AccDefault);
        // do not care whether was final or not
      }
    }

    /* convert thrown exceptions */
    char[][] exceptionTypeNames = methodInfo.getExceptionTypeNames();
    int exceptionCount = exceptionTypeNames == null ? 0 : exceptionTypeNames.length;
    if (exceptionCount > 0) {
      method.thrownExceptions = new TypeReference[exceptionCount];
      for (int i = 0; i < exceptionCount; i++) {
        method.thrownExceptions[i] =
          stc.createTypeReference(exceptionTypeNames[i], start, end);
      }
    }

    /* convert local and anonymous types */
    if ((stc.flags & SourceTypeConverter.LOCAL_TYPE) != 0) {
      IJavaElement[] children = methodInfo.getChildren();
      int typesLength = 0;
      int childrenLength = children.length;
      for (int i = 0; i < childrenLength; ++i)
        if (children[i] instanceof SourceType)
          ++typesLength;

      if (typesLength != 0) {
        Statement[] statements = new Statement[typesLength];
        int typeIndex = 0;
        for (int i = 0; i < childrenLength; i++) {
          if (children[i] instanceof SourceType) {
            SourceType type = (SourceType) children[i];
            TypeDeclaration localType = stc.convert(type, compilationResult);
            if ((localType.bits & org.eclipse.jdt.internal.compiler.ast.ASTNode.IsAnonymousType) != 0) {
              QualifiedAllocationExpression expression = new QualifiedAllocationExpression(localType);
              expression.type = localType.superclass;
              localType.superclass = null;
              localType.superInterfaces = null;
              localType.allocation = expression;
              statements[typeIndex] = expression;
            } else {
              statements[typeIndex] = localType;
            }
            ++typeIndex;
          }
        }
        method.statements = statements;
      }
    }

    return method;
  }
}

/*
 * Copyright 2005-2010 LAMP/EPFL
 */
// $Id$

package scala.tools.eclipse.contribution.weaving.jdt.core;

import java.util.Locale;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AnnotationMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.ISourceType;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import org.eclipse.jdt.internal.compiler.parser.SourceTypeConverter;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.SourceAnnotationMethodInfo;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.internal.core.SourceMethodElementInfo;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;

import scala.tools.eclipse.contribution.weaving.jdt.IScalaCompilationUnit;

@SuppressWarnings("restriction")
public privileged aspect DOMAspect {
  pointcut internalCreateAST(ASTParser parser, IProgressMonitor monitor) :
    execution(ASTNode ASTParser.internalCreateAST(IProgressMonitor)) &&
    args(monitor) &&
    target(parser);

  pointcut convert(SourceTypeConverter stc, SourceMethod methodHandle, SourceMethodElementInfo methodInfo, CompilationResult compilationResult) :
    execution(AbstractMethodDeclaration SourceTypeConverter.convert(SourceMethod, SourceMethodElementInfo, CompilationResult)) &&
    args(methodHandle, methodInfo, compilationResult) &&
    target(stc);

  ASTNode around(ASTParser parser, IProgressMonitor monitor) :
    internalCreateAST(parser, monitor) {
    try {
      if (!(parser.typeRoot instanceof IScalaCompilationUnit))
        return proceed(parser, monitor);

      ICompilationUnit cu = (ICompilationUnit)parser.typeRoot;
      org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit)cu;

      IType[] topLevelTypes = cu.getTypes();
      int length = topLevelTypes.length;
      if (length == 0)
        throw new IllegalStateException();

      SourceTypeElementInfo[] topLevelInfos = new SourceTypeElementInfo[length];
      for (int i = 0; i < length; i++) {
        topLevelInfos[i] = (SourceTypeElementInfo) ((SourceType)topLevelTypes[i]).getElementInfo();
      }
      char[] fileName = ((ISourceType)topLevelInfos[0]).getFileName();

      CompilationResult result = new CompilationResult(fileName, 1, 1, 100);

      JavaProject javaProject = (JavaProject)cu.getJavaProject();
      Map compilerOptions0 = javaProject.getOptions(true);
      CompilerOptions compilerOptions = new CompilerOptions(compilerOptions0);
      compilerOptions.storeAnnotations = true;

      ProblemReporter problemReporter =
        new ProblemReporter(
          DefaultErrorHandlingPolicies.proceedWithAllProblems(),
          compilerOptions,
          new DefaultProblemFactory(Locale.getDefault()));

      int flags = SourceTypeConverter.FIELD_AND_METHOD | SourceTypeConverter.MEMBER_TYPE;

      CompilationUnitDeclaration unit =
        SourceTypeConverter.buildCompilationUnit(topLevelInfos, flags, problemReporter, result);

      INameEnvironment ne = javaProject.newSearchableNameEnvironment(DefaultWorkingCopyOwner.PRIMARY);
      final LookupEnvironment le = new LookupEnvironment(null, compilerOptions, problemReporter, ne);
      ITypeRequestor typeRequestor = new ITypeRequestor() {
        public void accept(IBinaryType binaryType, PackageBinding packageBinding, AccessRestriction accessRestriction) {
          le.createBinaryTypeFrom(binaryType, packageBinding, accessRestriction);
        }
        public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit, AccessRestriction accessRestriction) {}
        public void accept(ISourceType[] sourceType, PackageBinding packageBinding, AccessRestriction accessRestriction) {}
      };
      le.typeRequestor = typeRequestor;

      le.buildTypeBindings(unit, null);
      le.completeTypeBindings();
      fixTypes(unit.types);

      AST ast = AST.newAST(AST.JLS3);
      ast.setFlag(AST.RESOLVED_BINDINGS);
      ast.setDefaultNodeFlag(ASTNode.ORIGINAL);

      org.eclipse.jdt.core.dom.ASTConverter converter =
        new org.eclipse.jdt.core.dom.ASTConverter(compilerOptions0, true, monitor);

      org.eclipse.jdt.core.dom.BindingResolver resolver =
        new org.eclipse.jdt.core.dom.DefaultBindingResolver(
          unit.scope,
          DefaultWorkingCopyOwner.PRIMARY,
          new org.eclipse.jdt.core.dom.DefaultBindingResolver.BindingTables(),
          true);
      ast.setBindingResolver(resolver);
      converter.setAST(ast);

      ASTNode node = converter.convert(unit, sourceUnit.getContents());
      return node;
    } catch (JavaModelException ex) {
      throw new IllegalArgumentException(ex);
    }
  }

  private void fixTypes(TypeDeclaration[] types) {
    for(int i = 0, iLimit = types.length; i < iLimit ; ++i) {
      TypeDeclaration tpe = types[i];
      tpe.binding.getAnnotationTagBits();
      tpe.scope.buildFields();
      tpe.scope.buildMethods();
      fixFields(tpe.fields);
      fixMethods(tpe.methods);
      fixTypes(tpe.memberTypes);
    }
  }

  private void fixMethods(AbstractMethodDeclaration[] methods) {
    if (methods == null)
      return;

    for(int i = 0, iLimit = methods.length; i < iLimit; ++i) {
      AbstractMethodDeclaration m = methods[i];
      m.bodyStart = m.declarationSourceStart;
      m.bodyEnd = m.declarationSourceEnd;
      m.binding.getAnnotationTagBits();
    }
  }

  private void fixFields(AbstractVariableDeclaration[] fields) {
    if (fields == null)
      return;

    for(int i = 0, iLimit = fields.length; i < iLimit; ++i) {
      AbstractVariableDeclaration f = fields[i];
      f.declarationEnd = f.declarationSourceEnd;
    }
  }

  AbstractMethodDeclaration around(SourceTypeConverter stc, SourceMethod methodHandle, SourceMethodElementInfo methodInfo, CompilationResult compilationResult) throws JavaModelException :
    convert(stc, methodHandle, methodInfo, compilationResult) {
    AbstractMethodDeclaration method;

    /* only source positions available */
    int start = methodInfo.getNameSourceStart();
    int end = methodInfo.getNameSourceEnd();

    // convert 1.5 specific constructs only if compliance is 1.5 or above
    TypeParameter[] typeParams = null;
    if (stc.has1_5Compliance) {
      /* convert type parameters */
      char[][] typeParameterNames = methodInfo.getTypeParameterNames();
      if (typeParameterNames != null) {
        int parameterCount = typeParameterNames.length;
        if (parameterCount > 0) { // method's type parameters must be null if no type parameter
          char[][][] typeParameterBounds = methodInfo.getTypeParameterBounds();
          typeParams = new TypeParameter[parameterCount];
          for (int i = 0; i < parameterCount; i++) {
            typeParams[i] = stc.createTypeParameter(typeParameterNames[i], typeParameterBounds[i], start, end);
          }
        }
      }
    }

    int modifiers = methodInfo.getModifiers();
    if (methodInfo.isConstructor()) {
      ConstructorDeclaration decl = new ConstructorDeclaration(compilationResult);
      decl.bits &= ~org.eclipse.jdt.internal.compiler.ast.ASTNode.IsDefaultConstructor;
      method = decl;
      decl.typeParameters = typeParams;
    } else {
      MethodDeclaration decl;
      if (methodInfo.isAnnotationMethod()) {
        AnnotationMethodDeclaration annotationMethodDeclaration = new AnnotationMethodDeclaration(compilationResult);

        /* conversion of default value */
        SourceAnnotationMethodInfo annotationMethodInfo = (SourceAnnotationMethodInfo) methodInfo;
        boolean hasDefaultValue = annotationMethodInfo.defaultValueStart != -1 || annotationMethodInfo.defaultValueEnd != -1;
        if ((stc.flags & SourceTypeConverter.FIELD_INITIALIZATION) != 0) {
          if (hasDefaultValue) {
            char[] defaultValueSource = CharOperation.subarray(stc.getSource(), annotationMethodInfo.defaultValueStart, annotationMethodInfo.defaultValueEnd+1);
            if (defaultValueSource != null) {
                Expression expression =  stc.parseMemberValue(defaultValueSource);
                if (expression != null) {
                  annotationMethodDeclaration.defaultValue = expression;
                }
            } else {
              // could not retrieve the default value
              hasDefaultValue = false;
            }
          }
        }
        if (hasDefaultValue)
          modifiers |= ClassFileConstants.AccAnnotationDefault;
        decl = annotationMethodDeclaration;
      } else {
        decl = new MethodDeclaration(compilationResult);
      }

      // convert return type
      decl.returnType = stc.createTypeReference(methodInfo.getReturnTypeName(), start, end);

      // type parameters
      decl.typeParameters = typeParams;

      method = decl;
    }
    method.selector = methodHandle.getElementName().toCharArray();
    boolean isVarargs = (modifiers & ClassFileConstants.AccVarargs) != 0;
    method.modifiers = modifiers & ~ClassFileConstants.AccVarargs;
    method.sourceStart = start;
    method.sourceEnd = end;
    method.declarationSourceStart = methodInfo.getDeclarationSourceStart();
    method.declarationSourceEnd = methodInfo.getDeclarationSourceEnd();

    // convert 1.5 specific constructs only if compliance is 1.5 or above
    if (stc.has1_5Compliance) {
      /* convert annotations */
      method.annotations = stc.convertAnnotations(methodHandle);
    }

    /* convert arguments */
    String[] argumentTypeSignatures = methodHandle.getParameterTypes();
    char[][] argumentNames = methodInfo.getArgumentNames();
    int argumentCount = argumentTypeSignatures == null ? 0 : argumentTypeSignatures.length;
    if (argumentCount > 0) {
      long position = ((long) start << 32) + end;
      method.arguments = new Argument[argumentCount];
      for (int i = 0; i < argumentCount; i++) {
        TypeReference typeReference = stc.createTypeReference(argumentTypeSignatures[i], start, end);
        if (isVarargs && i == argumentCount-1) {
          typeReference.bits |= org.eclipse.jdt.internal.compiler.ast.ASTNode.IsVarArgs;
        }
        method.arguments[i] =
          new Argument(
            argumentNames[i],
            position,
            typeReference,
            ClassFileConstants.AccDefault);
        // do not care whether was final or not
      }
    }

    /* convert thrown exceptions */
    char[][] exceptionTypeNames = methodInfo.getExceptionTypeNames();
    int exceptionCount = exceptionTypeNames == null ? 0 : exceptionTypeNames.length;
    if (exceptionCount > 0) {
      method.thrownExceptions = new TypeReference[exceptionCount];
      for (int i = 0; i < exceptionCount; i++) {
        method.thrownExceptions[i] =
          stc.createTypeReference(exceptionTypeNames[i], start, end);
      }
    }

    /* convert local and anonymous types */
    if ((stc.flags & SourceTypeConverter.LOCAL_TYPE) != 0) {
      IJavaElement[] children = methodInfo.getChildren();
      int typesLength = 0;
      int childrenLength = children.length;
      for (int i = 0; i < childrenLength; ++i)
        if (children[i] instanceof SourceType)
          ++typesLength;

      if (typesLength != 0) {
        Statement[] statements = new Statement[typesLength];
        int typeIndex = 0;
        for (int i = 0; i < childrenLength; i++) {
          if (children[i] instanceof SourceType) {
            SourceType type = (SourceType) children[i];
            TypeDeclaration localType = stc.convert(type, compilationResult);
            if ((localType.bits & org.eclipse.jdt.internal.compiler.ast.ASTNode.IsAnonymousType) != 0) {
              QualifiedAllocationExpression expression = new QualifiedAllocationExpression(localType);
              expression.type = localType.superclass;
              localType.superclass = null;
              localType.superInterfaces = null;
              localType.allocation = expression;
              statements[typeIndex] = expression;
            } else {
              statements[typeIndex] = localType;
            }
            ++typeIndex;
          }
        }
        method.statements = statements;
      }
    }

    return method;
  }
}


fixed imports

fixed imports

/**
 * palava - a java-php-bridge
 * Copyright (C) 2007-2010  CosmoCode GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package de.cosmocode.palava.jpa.aspectj;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

import de.cosmocode.palava.core.aop.AbstractPalavaAspect;
import de.cosmocode.palava.jpa.Transactional;

public final aspect TransactionAspect extends AbstractPalavaAspect issingleton() {

    private static final Logger LOG = LoggerFactory.getLogger(TransactionAspect.class);

    private Provider<EntityManager> currentManager;

    @Inject
    public void setProvider(Provider<EntityManager> manager) {
        this.currentManager = Preconditions.checkNotNull(manager, "Manager");
    }

    pointcut transactional(): execution(@Transactional * *.*(..));

    @SuppressAjWarnings("adviceDidNotMatch")
    Object around(): transactional() {
        final EntityManager manager = currentManager.get();
        LOG.trace("Retrieved entitymanager {}", manager);
        final EntityTransaction tx = manager.getTransaction();
        LOG.trace("Using transaction {}", tx);
        final boolean localTx = !tx.isActive();
        if (localTx) {
            LOG.trace("Beginning automatic transaction");
            tx.begin();
        } else {
            LOG.trace("Transaction already active");
        }

        final Object returnValue;

        try {
            returnValue = proceed();
        } catch (RuntimeException e) {
            LOG.error("Exception inside automatic transaction context", e);
            tx.rollback();
            throw e;
        }

        try {
            if (localTx && tx.isActive()) {
                tx.commit();
                LOG.trace("Committed automatic transaction");
            }
            return returnValue;
        } catch (PersistenceException e) {
            LOG.error("Commit in automatic transaction context failed", e);
            tx.rollback();
            throw e;
        }
    }

}

/**
 * palava - a java-php-bridge
 * Copyright (C) 2007-2010  CosmoCode GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package de.cosmocode.palava.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

import de.cosmocode.palava.core.aop.AbstractPalavaAspect;

public final aspect TransactionAspect extends AbstractPalavaAspect issingleton() {

    private static final Logger LOG = LoggerFactory.getLogger(TransactionAspect.class);

    private Provider<EntityManager> currentManager;

    @Inject
    public void setProvider(Provider<EntityManager> manager) {
        this.currentManager = Preconditions.checkNotNull(manager, "Manager");
    }

    pointcut transactional(): execution(@Transactional * *.*(..));

    @SuppressAjWarnings("adviceDidNotMatch")
    Object around(): transactional() {
        final EntityManager manager = currentManager.get();
        LOG.trace("Retrieved entitymanager {}", manager);
        final EntityTransaction tx = manager.getTransaction();
        LOG.trace("Using transaction {}", tx);
        final boolean localTx = !tx.isActive();
        if (localTx) {
            LOG.trace("Beginning automatic transaction");
            tx.begin();
        } else {
            LOG.trace("Transaction already active");
        }

        final Object returnValue;

        try {
            returnValue = proceed();
        } catch (RuntimeException e) {
            LOG.error("Exception inside automatic transaction context", e);
            tx.rollback();
            throw e;
        }

        try {
            if (localTx && tx.isActive()) {
                tx.commit();
                LOG.trace("Committed automatic transaction");
            }
            return returnValue;
        } catch (PersistenceException e) {
            LOG.error("Commit in automatic transaction context failed", e);
            tx.rollback();
            throw e;
        }
    }

}


More fixes to RetryTransactionAspect exception categorization: if the exception we catch is already translated into a Spring DAO exception, don't just accept it if it's an UncategorizedDataAccessException, because that means Spring didn't know how to categorize it.

/*
 * Copyright (C) 2011 Archie L. Cobbs and other authors. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.spring;

import java.lang.reflect.Method;
import java.util.Random;

import org.aspectj.lang.reflect.MethodSignature;
import org.dellroad.stuff.java.ThreadLocalHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.dao.UncategorizedDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * An aspect that automatically retries failed but "retryable" transactions.
 *
 * @see RetryTransaction
 */
public aspect RetryTransactionAspect implements RetryTransactionProvider, InitializingBean {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final ThreadLocalHolder<Integer> attemptHolder = new ThreadLocalHolder<Integer>();
    private final Random random = new Random();

    // Default values for retry settings
    private int maxRetriesDefault = RetryTransaction.DEFAULT_MAX_RETRIES;
    private long initialDelayDefault = RetryTransaction.DEFAULT_INITIAL_DELAY;
    private long maximumDelayDefault = RetryTransaction.DEFAULT_MAXIMUM_DELAY;

    // This knows how to read information from @Transactional annotation
    private final AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(false);

    // Tells us what is a "transient" exception; must be explicitly configured
    private PersistenceExceptionTranslator persistenceExceptionTranslator;

// Property accessors and RetryTransactionProvider implementation

    @Override
    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.persistenceExceptionTranslator;
    }
    public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator persistenceExceptionTranslator) {
        this.persistenceExceptionTranslator = persistenceExceptionTranslator;
    }

    @Override
    public int getMaxRetriesDefault() {
        return this.maxRetriesDefault;
    }
    public void setMaxRetriesDefault(int maxRetriesDefault) {
        this.maxRetriesDefault = maxRetriesDefault;
    }

    @Override
    public long getInitialDelayDefault() {
        return this.initialDelayDefault;
    }
    public void setInitialDelayDefault(long initialDelayDefault) {
        this.initialDelayDefault = initialDelayDefault;
    }

    @Override
    public long getMaximumDelayDefault() {
        return this.maximumDelayDefault;
    }
    public void setMaximumDelayDefault(long maximumDelayDefault) {
        this.maximumDelayDefault = maximumDelayDefault;
    }

    @Override
    public int getAttemptNumber() {
        final Integer attempt = this.attemptHolder.get();
        return attempt != null ? attempt : 0;
    }

// InitializingBean

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.persistenceExceptionTranslator == null)
            throw new IllegalArgumentException("no PersistenceExceptionTranslator configured");
    }

// Aspect

    // Ensure that this aspect is woven outside of the AnnotationTransactionAspect
    declare precedence : RetryTransactionAspect, org.springframework.transaction.aspectj.*;

    /**
     * Attempt to execute a transactional method. If the method throws any exception which is translated
     * to a subclass of TransientDataAccessException, the method will be executed again, with a delay
     * between attempts.
     */
    Object around(final Object txObject) : retryTransactionalMethodExecution(txObject) {

        // Get method info
        final MethodSignature methodSignature = (MethodSignature)thisJoinPoint.getSignature();
        final Method method = methodSignature.getMethod();

        // Get transaction attribute from @Transactional annotation
        final TransactionAttribute transactionAttribute = this.transactionAttributeSource
          .getTransactionAttribute(method, txObject.getClass());
        if (transactionAttribute == null) {
            throw new RuntimeException("no @Transactional annotation found for method "
              + method + "; required for @RetryTransaction");
        }

        // Do nothing unless this method is about to create a new transaction
        switch (transactionAttribute.getPropagationBehavior()) {
        case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
            break;
        case TransactionDefinition.PROPAGATION_REQUIRED:
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                if (this.log.isTraceEnabled())
                    this.log.trace("skipping retry logic; transaction already open in @Transactional method {}", method);
                return proceed(txObject);
            }
            break;
        default:
            return proceed(txObject);
        }

        // Get @RetryTransaction annotation
        final RetryTransaction retryTransaction = AnnotationUtils.getAnnotation(method, RetryTransaction.class);
        if (retryTransaction == null)
            throw new RuntimeException("internal error: no @RetryTransaction annotation found for method " + method);

        // Get retry parameters, applying defaults if necessary
        final int maxRetries = retryTransaction.maxRetries() != -1 ?
          retryTransaction.maxRetries() : this.maxRetriesDefault;
        final long initialDelay = retryTransaction.initialDelay() != -1 ?
          retryTransaction.initialDelay() : this.initialDelayDefault;
        final long maximumDelay = retryTransaction.maximumDelay() != -1 ?
          retryTransaction.maximumDelay() : this.maximumDelayDefault;

        // Sanity check we are configured
        if (this.persistenceExceptionTranslator == null)
            throw new RuntimeException("@RetryTransaction aspect must be configured before use");

        // Perform attempts
        TransientDataAccessException transientException;
        int attempt = 1;
        do {

            // If this is not the first attempt, sleep for a while
            if (attempt > 1) {
                final long delay = this.calculateDelay(attempt, initialDelay, maximumDelay);
                if (this.log.isDebugEnabled())
                    this.log.debug("pausing {}ms before retrying @Transactional method {}", delay, method);
                this.pause(delay);
                if (this.log.isDebugEnabled())
                    this.log.debug("retrying @Transactional method {} (attempt #{})", method, attempt);
            }

            // Make attempt
            try {

                // Make next attempt
                if (this.log.isTraceEnabled())
                    this.log.trace("starting @Transactional method {} (attempt #{})", method, attempt);
                final Object[] result = new Object[1];
                this.attemptHolder.invoke(attempt, new Runnable() {
                    @Override
                    public void run() {
                        result[0] = proceed(txObject);
                    }
                });
                if (attempt > 1) {
                    if (this.log.isDebugEnabled())
                        this.log.debug("successfully completed @Transactional method {} on re-try attempt #{}", method, attempt);
                } else {
                    if (this.log.isTraceEnabled())
                        this.log.trace("successfully completed @Transactional method {} on first attempt", method);
                }

                // We're done
                return result[0];
            } catch (RuntimeException e) {

                // Translate the exception, if not already translated into something recognizable
                DataAccessException translatedException;
                if (e instanceof UncategorizedDataAccessException && e.getCause() instanceof RuntimeException) {
                    translatedException = this.persistenceExceptionTranslator
                      .translateExceptionIfPossible((RuntimeException)e.getCause());
                } else if (e instanceof DataAccessException)
                    translatedException = (DataAccessException)e;               // exception is already translated
                else
                    translatedException = this.persistenceExceptionTranslator.translateExceptionIfPossible(e);
                if (this.log.isDebugEnabled()) {
                    this.log.debug("exception from @Transactional method {} on attempt #{}: {} (translates to {})",
                      method, attempt, e, translatedException != null ? translatedException.getClass().getSimpleName() : null);
                }

                // If it's not a transient exception, re-throw it
                if (!(translatedException instanceof TransientDataAccessException))
                    throw e;

                // Re-throw the transient exception when we've run out of attempts
                transientException = (TransientDataAccessException)translatedException;
            }
        }
        while (attempt++ <= maxRetries);

        // All attempts failed
        this.log.error("@Transactional method {} failed after {} attempts, giving up!", method, maxRetries);
        throw transientException;
    }

// Static methods

    /**
     * Get the current transaction attempt number.
     *
     * @return transaction attempt number, or zero if this aspect is not active in the current thread
     * @deprecated Use {@link RetryTransactionProvider#getAttemptNumber} instead
     */
    public static int getAttempt() {
        final Integer attempt = RetryTransactionAspect.aspectOf().attemptHolder.get();
        return attempt != null ? attempt : 0;
    }

// Helper methods

    /**
     * Calculate how long to sleep.
     *
     * @param attempt attempt number (one-based)
     * @param initialDelay initial delay
     * @param maximumDelay maximum delay
     */
    protected long calculateDelay(int attempt, long initialDelay, long maximumDelay) {

        // Enforce sanity
        initialDelay = Math.max(initialDelay, 1);
        maximumDelay = Math.max(maximumDelay, 1);
        initialDelay = Math.min(initialDelay, maximumDelay);

        // Calculate nominal delay for this attempt, using exponential back-off; be careful to avoid overflow
        long delay = initialDelay;
        while (attempt-- > 1) {
            delay <<= 1;
            if (delay >= maximumDelay) {
                delay = maximumDelay;
                break;
            }
        }

        // Add in +/- 12.5% randomness
        final int randomRange = Math.max(1, (int)(delay >> 3));
        synchronized (this.random) {
            delay += this.random.nextInt(randomRange) - randomRange / 2;
        }

        // Enforce upper bound again
        delay = Math.min(delay, maximumDelay);

        // Done
        return delay;
    }

    /**
     * Sleep for the specified period.
     *
     * @param delay delay in milliseconds
     */
    protected void pause(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /*
     * Matches the execution of any public method in a type with the @Transactional annotation, or any subtype of a type with the
     * @Transactional annotation.
     */
    private pointcut executionOfPublicMethodInTransactionalType() : execution(public * ((@Transactional *)+).*(..));

    /*
     * Matches the execution of any public method in a type with the @RetryTransaction annotation, or any subtype of a type with the
     * @RetryTransaction annotation.
     */
    private pointcut executionOfPublicMethodInRetryTransactionType() : execution(public * ((@RetryTransaction *)+).*(..));

    /*
     * Matches the execution of any method with the @Transactional annotation.
     */
    private pointcut executionOfTransactionalMethod() : execution(@Transactional * *(..));

    /*
     * Matches the execution of any method with the @RetryTransaction annotation.
     */
    private pointcut executionOfRetryTransactionMethod() : execution(@RetryTransaction * *(..));

    /*
     * Main pointcut - matched join points have both @Transactional and @RetryTransaction on method and/or type
     */
    private pointcut retryTransactionalMethodExecution(Object txObject) : this(txObject)
        && (executionOfPublicMethodInTransactionalType() || executionOfTransactionalMethod())
        && (executionOfPublicMethodInRetryTransactionType() || executionOfRetryTransactionMethod());
}

/*
 * Copyright (C) 2011 Archie L. Cobbs and other authors. All rights reserved.
 *
 * $Id$
 */

package org.dellroad.stuff.spring;

import java.lang.reflect.Method;
import java.util.Random;

import org.aspectj.lang.reflect.MethodSignature;
import org.dellroad.stuff.java.ThreadLocalHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.TransientDataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * An aspect that automatically retries failed but "retryable" transactions.
 *
 * @see RetryTransaction
 */
public aspect RetryTransactionAspect implements RetryTransactionProvider, InitializingBean {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final ThreadLocalHolder<Integer> attemptHolder = new ThreadLocalHolder<Integer>();
    private final Random random = new Random();

    // Default values for retry settings
    private int maxRetriesDefault = RetryTransaction.DEFAULT_MAX_RETRIES;
    private long initialDelayDefault = RetryTransaction.DEFAULT_INITIAL_DELAY;
    private long maximumDelayDefault = RetryTransaction.DEFAULT_MAXIMUM_DELAY;

    // This knows how to read information from @Transactional annotation
    private final AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(false);

    // Tells us what is a "transient" exception; must be explicitly configured
    private PersistenceExceptionTranslator persistenceExceptionTranslator;

// Property accessors and RetryTransactionProvider implementation

    @Override
    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.persistenceExceptionTranslator;
    }
    public void setPersistenceExceptionTranslator(PersistenceExceptionTranslator persistenceExceptionTranslator) {
        this.persistenceExceptionTranslator = persistenceExceptionTranslator;
    }

    @Override
    public int getMaxRetriesDefault() {
        return this.maxRetriesDefault;
    }
    public void setMaxRetriesDefault(int maxRetriesDefault) {
        this.maxRetriesDefault = maxRetriesDefault;
    }

    @Override
    public long getInitialDelayDefault() {
        return this.initialDelayDefault;
    }
    public void setInitialDelayDefault(long initialDelayDefault) {
        this.initialDelayDefault = initialDelayDefault;
    }

    @Override
    public long getMaximumDelayDefault() {
        return this.maximumDelayDefault;
    }
    public void setMaximumDelayDefault(long maximumDelayDefault) {
        this.maximumDelayDefault = maximumDelayDefault;
    }

    @Override
    public int getAttemptNumber() {
        final Integer attempt = this.attemptHolder.get();
        return attempt != null ? attempt : 0;
    }

// InitializingBean

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.persistenceExceptionTranslator == null)
            throw new IllegalArgumentException("no PersistenceExceptionTranslator configured");
    }

// Aspect

    // Ensure that this aspect is woven outside of the AnnotationTransactionAspect
    declare precedence : RetryTransactionAspect, org.springframework.transaction.aspectj.*;

    /**
     * Attempt to execute a transactional method. If the method throws any exception which is translated
     * to a subclass of TransientDataAccessException, the method will be executed again, with a delay
     * between attempts.
     */
    Object around(final Object txObject) : retryTransactionalMethodExecution(txObject) {

        // Get method info
        final MethodSignature methodSignature = (MethodSignature)thisJoinPoint.getSignature();
        final Method method = methodSignature.getMethod();

        // Get transaction attribute from @Transactional annotation
        final TransactionAttribute transactionAttribute = this.transactionAttributeSource
          .getTransactionAttribute(method, txObject.getClass());
        if (transactionAttribute == null) {
            throw new RuntimeException("no @Transactional annotation found for method "
              + method + "; required for @RetryTransaction");
        }

        // Do nothing unless this method is about to create a new transaction
        switch (transactionAttribute.getPropagationBehavior()) {
        case TransactionDefinition.PROPAGATION_REQUIRES_NEW:
            break;
        case TransactionDefinition.PROPAGATION_REQUIRED:
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                if (this.log.isTraceEnabled())
                    this.log.trace("skipping retry logic; transaction already open in @Transactional method {}", method);
                return proceed(txObject);
            }
            break;
        default:
            return proceed(txObject);
        }

        // Get @RetryTransaction annotation
        final RetryTransaction retryTransaction = AnnotationUtils.getAnnotation(method, RetryTransaction.class);
        if (retryTransaction == null)
            throw new RuntimeException("internal error: no @RetryTransaction annotation found for method " + method);

        // Get retry parameters, applying defaults if necessary
        final int maxRetries = retryTransaction.maxRetries() != -1 ?
          retryTransaction.maxRetries() : this.maxRetriesDefault;
        final long initialDelay = retryTransaction.initialDelay() != -1 ?
          retryTransaction.initialDelay() : this.initialDelayDefault;
        final long maximumDelay = retryTransaction.maximumDelay() != -1 ?
          retryTransaction.maximumDelay() : this.maximumDelayDefault;

        // Sanity check we are configured
        if (this.persistenceExceptionTranslator == null)
            throw new RuntimeException("@RetryTransaction aspect must be configured before use");

        // Perform attempts
        TransientDataAccessException transientException;
        int attempt = 1;
        do {

            // If this is not the first attempt, sleep for a while
            if (attempt > 1) {
                final long delay = this.calculateDelay(attempt, initialDelay, maximumDelay);
                if (this.log.isDebugEnabled())
                    this.log.debug("pausing {}ms before retrying @Transactional method {}", delay, method);
                this.pause(delay);
                if (this.log.isDebugEnabled())
                    this.log.debug("retrying @Transactional method {} (attempt #{})", method, attempt);
            }

            // Make attempt
            try {

                // Make next attempt
                if (this.log.isTraceEnabled())
                    this.log.trace("starting @Transactional method {} (attempt #{})", method, attempt);
                final Object[] result = new Object[1];
                this.attemptHolder.invoke(attempt, new Runnable() {
                    @Override
                    public void run() {
                        result[0] = proceed(txObject);
                    }
                });
                if (attempt > 1) {
                    if (this.log.isDebugEnabled())
                        this.log.debug("successfully completed @Transactional method {} on re-try attempt #{}", method, attempt);
                } else {
                    if (this.log.isTraceEnabled())
                        this.log.trace("successfully completed @Transactional method {} on first attempt", method);
                }

                // We're done
                return result[0];
            } catch (RuntimeException e) {

                // Translate the exception
                final DataAccessException translatedException = e instanceof DataAccessException ?
                  (DataAccessException)e : this.persistenceExceptionTranslator.translateExceptionIfPossible(e);
                if (this.log.isDebugEnabled()) {
                    this.log.debug("exception from @Transactional method {} on attempt #{}: {} (translates to {})",
                      method, attempt, e, translatedException != null ? translatedException.getClass().getSimpleName() : null);
                }

                // If it's not a transient exception, re-throw it
                if (!(translatedException instanceof TransientDataAccessException))
                    throw e;

                // Re-throw the transient exception when we've run out of attempts
                transientException = (TransientDataAccessException)translatedException;
            }
        }
        while (attempt++ <= maxRetries);

        // All attempts failed
        this.log.error("@Transactional method {} failed after {} attempts, giving up!", method, maxRetries);
        throw transientException;
    }

// Static methods

    /**
     * Get the current transaction attempt number.
     *
     * @return transaction attempt number, or zero if this aspect is not active in the current thread
     * @deprecated Use {@link RetryTransactionProvider#getAttemptNumber} instead
     */
    public static int getAttempt() {
        final Integer attempt = RetryTransactionAspect.aspectOf().attemptHolder.get();
        return attempt != null ? attempt : 0;
    }

// Helper methods

    /**
     * Calculate how long to sleep.
     *
     * @param attempt attempt number (one-based)
     * @param initialDelay initial delay
     * @param maximumDelay maximum delay
     */
    protected long calculateDelay(int attempt, long initialDelay, long maximumDelay) {

        // Enforce sanity
        initialDelay = Math.max(initialDelay, 1);
        maximumDelay = Math.max(maximumDelay, 1);
        initialDelay = Math.min(initialDelay, maximumDelay);

        // Calculate nominal delay for this attempt, using exponential back-off; be careful to avoid overflow
        long delay = initialDelay;
        while (attempt-- > 1) {
            delay <<= 1;
            if (delay >= maximumDelay) {
                delay = maximumDelay;
                break;
            }
        }

        // Add in +/- 12.5% randomness
        final int randomRange = Math.max(1, (int)(delay >> 3));
        synchronized (this.random) {
            delay += this.random.nextInt(randomRange) - randomRange / 2;
        }

        // Enforce upper bound again
        delay = Math.min(delay, maximumDelay);

        // Done
        return delay;
    }

    /**
     * Sleep for the specified period.
     *
     * @param delay delay in milliseconds
     */
    protected void pause(long delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /*
     * Matches the execution of any public method in a type with the @Transactional annotation, or any subtype of a type with the
     * @Transactional annotation.
     */
    private pointcut executionOfPublicMethodInTransactionalType() : execution(public * ((@Transactional *)+).*(..));

    /*
     * Matches the execution of any public method in a type with the @RetryTransaction annotation, or any subtype of a type with the
     * @RetryTransaction annotation.
     */
    private pointcut executionOfPublicMethodInRetryTransactionType() : execution(public * ((@RetryTransaction *)+).*(..));

    /*
     * Matches the execution of any method with the @Transactional annotation.
     */
    private pointcut executionOfTransactionalMethod() : execution(@Transactional * *(..));

    /*
     * Matches the execution of any method with the @RetryTransaction annotation.
     */
    private pointcut executionOfRetryTransactionMethod() : execution(@RetryTransaction * *(..));

    /*
     * Main pointcut - matched join points have both @Transactional and @RetryTransaction on method and/or type
     */
    private pointcut retryTransactionalMethodExecution(Object txObject) : this(txObject)
        && (executionOfPublicMethodInTransactionalType() || executionOfTransactionalMethod())
        && (executionOfPublicMethodInRetryTransactionType() || executionOfRetryTransactionMethod());
}


HIGHLY UNTESTED COMMIT: another attempt to sneaky throw checked exception

/**
 * Copyright 2010 CosmoCode GmbH
 *
 * 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 de.cosmocode.palava.jta;

import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

import de.cosmocode.palava.core.Registry;
import de.cosmocode.palava.core.aop.AbstractPalavaAspect;

public abstract aspect AbstractUserTransactionAspect extends AbstractPalavaAspect issingleton() {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractUserTransactionAspect.class);

    private Provider<UserTransaction> provider;

    private TransactionBeginEvent begin;
    private TransactionBeganEvent began;
    private TransactionCommitEvent commit;
    private TransactionCommittedEvent committed;
    private TransactionRollbackEvent rollback;
    private TransactionRolledbackEvent rolledback;

    @Inject
    void setProvider(Provider<UserTransaction> provider) {
        this.provider = Preconditions.checkNotNull(provider, "Provider");
    }

    @Inject
    void setRegistry(Registry registry) {
        Preconditions.checkNotNull(registry, "Registry");
        this.begin = registry.proxy(TransactionBeginEvent.class);
        this.began = registry.proxy(TransactionBeganEvent.class);
        this.commit = registry.proxy(TransactionCommitEvent.class);
        this.committed = registry.proxy(TransactionCommittedEvent.class);
        this.rollback = registry.proxy(TransactionRollbackEvent.class);
        this.rolledback = registry.proxy(TransactionRolledbackEvent.class);
    }

    protected abstract pointcut transactional();

    @SuppressAjWarnings("adviceDidNotMatch")
    Object around(): transactional() {
        Preconditions.checkNotNull(provider, "Provider not injected yet");
        final UserTransaction tx = provider.get();
        LOG.trace("Using transaction {}", tx);

        try {
            LOG.trace("Status of transaction: {}", tx.getStatus());
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        int status;
        final boolean local;
        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        local = status == Status.STATUS_NO_TRANSACTION;

        if (local) {
            LOG.debug("Beginning automatic transaction {}", tx);
            try {
                begin.eventTransactionBegin(tx);
                tx.begin();
                began.eventTransactionBegan(tx);
            } catch (NotSupportedException e) {
                throw new IllegalStateException(e);
            } catch (SystemException e) {
                throw new IllegalStateException(e);
            }
        } else {
            LOG.trace("Transaction {} already active (Status: {})", tx, status);
        }

        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        final Object returnValue;

        try {
            LOG.trace("Status before execution: {}", status);
            returnValue = proceed();
        } catch (Exception e) {
            try {
                if (local &&
                    (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)) {
                    LOG.info("Rolling back local/active transaction {}", tx);
                    rollback(tx);
                } else if (tx.getStatus() != Status.STATUS_ROLLEDBACK || tx.getStatus() != Status.STATUS_ROLLING_BACK) {
                    LOG.debug("Setting transaction {} as rollback only", tx);
                    tx.setRollbackOnly();
                }
            } catch (Exception inner) {
                LOG.error("Rollback failed", inner);
            }
            // FIXME this effectively shadows the real exceptions thus preventing correct exception handling
            //throw new IllegalStateException(e);
            return sneakyThrow(e);
        }

        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        LOG.trace("Status after execution: {}", status);

        if (local) {
            if (status == Status.STATUS_MARKED_ROLLBACK) {
                try {
                    LOG.debug("Rolling back marked transaction {}", tx);
                    rollback(tx);
                } catch (SystemException e) {
                    throw new IllegalStateException(e);
                }
            } else {
                try {
                    commit.eventTransactionCommit(tx);
                    tx.commit();
                    committed.eventTransactionCommitted(tx);
                    LOG.debug("Committed automatic transaction {}", tx);
                } catch (Exception e) {
                    try {
                        LOG.info("Rolling back transaction {}", tx);
                        rollback(tx);
                    } catch (Exception inner) {
                        LOG.error("Rollback failed", inner);
                    }
                    throw new IllegalStateException(e);
                }
            }
        }

        return returnValue;
    }

    private void rollback(UserTransaction tx) throws SystemException {
        rollback.eventTransactionRollback(tx);
        tx.rollback();
        rolledback.eventTransactionRolledback(tx);
    }

    private RuntimeException sneakyThrow(Throwable t) {
        this.<RuntimeException>doSneakyThrow(t);
        return null;
    }

    @SuppressWarnings("unchecked")
    private <T extends Throwable> T doSneakyThrow(Throwable t) throws T {
        throw (T) t;
    }

}

/**
 * Copyright 2010 CosmoCode GmbH
 *
 * 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 de.cosmocode.palava.jta;

import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Provider;

import de.cosmocode.palava.core.Registry;
import de.cosmocode.palava.core.aop.AbstractPalavaAspect;

public abstract aspect AbstractUserTransactionAspect extends AbstractPalavaAspect issingleton() {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractUserTransactionAspect.class);

    private Provider<UserTransaction> provider;

    private TransactionBeginEvent begin;
    private TransactionBeganEvent began;
    private TransactionCommitEvent commit;
    private TransactionCommittedEvent committed;
    private TransactionRollbackEvent rollback;
    private TransactionRolledbackEvent rolledback;

    @Inject
    void setProvider(Provider<UserTransaction> provider) {
        this.provider = Preconditions.checkNotNull(provider, "Provider");
    }

    @Inject
    void setRegistry(Registry registry) {
        Preconditions.checkNotNull(registry, "Registry");
        this.begin = registry.proxy(TransactionBeginEvent.class);
        this.began = registry.proxy(TransactionBeganEvent.class);
        this.commit = registry.proxy(TransactionCommitEvent.class);
        this.committed = registry.proxy(TransactionCommittedEvent.class);
        this.rollback = registry.proxy(TransactionRollbackEvent.class);
        this.rolledback = registry.proxy(TransactionRolledbackEvent.class);
    }

    protected abstract pointcut transactional();

    @SuppressAjWarnings("adviceDidNotMatch")
    Object around(): transactional() {
        Preconditions.checkNotNull(provider, "Provider not injected yet");
        final UserTransaction tx = provider.get();
        LOG.trace("Using transaction {}", tx);

        try {
            LOG.trace("Status of transaction: {}", tx.getStatus());
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        int status;
        final boolean local;
        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        local = status == Status.STATUS_NO_TRANSACTION;

        if (local) {
            LOG.debug("Beginning automatic transaction {}", tx);
            try {
                begin.eventTransactionBegin(tx);
                tx.begin();
                began.eventTransactionBegan(tx);
            } catch (NotSupportedException e) {
                throw new IllegalStateException(e);
            } catch (SystemException e) {
                throw new IllegalStateException(e);
            }
        } else {
            LOG.trace("Transaction {} already active (Status: {})", tx, status);
        }

        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        final Object returnValue;

        try {
            LOG.trace("Status before execution: {}", status);
            returnValue = proceed();
        } catch (Exception e) {
            try {
                if (local &&
                    (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)) {
                    LOG.info("Rolling back local/active transaction {}", tx);
                    rollback(tx);
                } else if (tx.getStatus() != Status.STATUS_ROLLEDBACK || tx.getStatus() != Status.STATUS_ROLLING_BACK) {
                    LOG.debug("Setting transaction {} as rollback only", tx);
                    tx.setRollbackOnly();
                }
            } catch (Exception inner) {
                LOG.error("Rollback failed", inner);
            }
            // FIXME this effectively shadows the real exceptions thus preventing correct exception handling
            throw sneakyThrow(e);
            //throw new IllegalStateException(e);
        }

        try {
            status = tx.getStatus();
        } catch (SystemException e) {
            throw new IllegalStateException(e);
        }

        LOG.trace("Status after execution: {}", status);

        if (local) {
            if (status == Status.STATUS_MARKED_ROLLBACK) {
                try {
                    LOG.debug("Rolling back marked transaction {}", tx);
                    rollback(tx);
                } catch (SystemException e) {
                    throw new IllegalStateException(e);
                }
            } else {
                try {
                    commit.eventTransactionCommit(tx);
                    tx.commit();
                    committed.eventTransactionCommitted(tx);
                    LOG.debug("Committed automatic transaction {}", tx);
                } catch (Exception e) {
                    try {
                        LOG.info("Rolling back transaction {}", tx);
                        rollback(tx);
                    } catch (Exception inner) {
                        LOG.error("Rollback failed", inner);
                    }
                    throw new IllegalStateException(e);
                }
            }
        }

        return returnValue;
    }

    private void rollback(UserTransaction tx) throws SystemException {
        rollback.eventTransactionRollback(tx);
        tx.rollback();
        rolledback.eventTransactionRolledback(tx);
    }

    private RuntimeException sneakyThrow(Throwable t) {
        return this.<RuntimeException>doSneakyThrow(t);
    }

    @SuppressWarnings("unchecked")
    private <T extends Throwable> T doSneakyThrow(Throwable t) throws T {
        return (T)t;
    }

}