@SuppressWarnings("unchecked")
  private AroundAdvice createAdvice(NamedFunction f, Method mtd) {
    Function inner = f.getFunction();
    if (inner instanceof JavaDefinedFunction) {
      final M2tAroundAdvice adviceAnn = mtd.getAnnotation(M2tAroundAdvice.class);
      final M2tPointcut pointcutAnn = adviceAnn.pointcut();

      List<NamedFunction> adviceBodyJavaFunctions = null;
      if ((adviceBodyJavaFunctions =
              (List<NamedFunction>)
                  _middleEnd
                      .getExecutionContext()
                      .getContributionStateContext()
                      .retrieveState("AdviceBodyJavaDefinedFunctions"))
          == null) {
        adviceBodyJavaFunctions = new ArrayList<NamedFunction>();
        _middleEnd
            .getExecutionContext()
            .getContributionStateContext()
            .storeState("AdviceBodyJavaDefinedFunctions", adviceBodyJavaFunctions);
      }
      adviceBodyJavaFunctions.add(f);
      String[] paramTypeNames = pointcutAnn.paramTypeNames();
      List<Pair<String, AdviceParamType>> paramTypes =
          new ArrayList<Pair<String, AdviceParamType>>();
      for (int i = 0; i < paramTypeNames.length; i++) {
        String paramTypeName = paramTypeNames[i];
        // TODO make subtype inclusion configurable
        if (!paramTypeName.equals("*")) {
          final AdviceParamType paramType =
              new AdviceParamType(_middleEnd.getTypesystem().findType(paramTypeName), true);
          final Pair<String, AdviceParamType> paramTypePair =
              new Pair<String, AdviceParamType>("o" + i + 1, paramType);
          paramTypes.add(paramTypePair);
        }
      }
      AdviceParamType varArgsAdvParamType =
          new AdviceParamType(
              _middleEnd.getTypesystem().findType(ObjectType.INSTANCE.getUniqueRepresentation()),
              true);

      final ExecutionPointcut pointcut =
          new ExecutionPointcut(
              pointcutAnn.namePattern(), paramTypes, pointcutAnn.hasVarArgs(), null);
      ExpressionBase body =
          new MethodInvocationExpression(
              mtd,
              Arrays.asList(new LocalVarEvalExpression(SyntaxConstants.THIS_JOINPOINT, null)),
              false,
              null);

      final AroundAdvice adv = new AroundAdvice(body, pointcut, true);

      return adv;
    } else {
      throw new IllegalArgumentException(
          "Advice definition " + f.getName() + " is not a JavaDefinedFunction");
    }
  }
  public ParsedResource parseResource(String resourceName) {
    final ParsedResource result = new ParsedResource();

    if (classAsResource(Object.class).equals(resourceName)) return result;

    // TODO test imports
    // TODO test advice
    // TODO guards

    final Class<?> cls = getCls(resourceName);

    for (Method mtd : cls.getDeclaredMethods()) {
      // register only public methods
      if (!isPublic(mtd)) continue;

      if (isInfrastructureMethod(mtd, cls)) continue;

      if (mtd.getAnnotation(M2tNoFunction.class) != null) continue;

      final boolean isPublicFunction =
          (mtd.getAnnotation(M2tPrivateFunction.class) == null
              && mtd.getAnnotation(M2tAroundAdvice.class) == null);
      final boolean isAroundAdvice = (mtd.getAnnotation(M2tAroundAdvice.class) != null);
      final QualifiedName functionName =
          (mtd.getAnnotation(M2tQualifiedName.class) != null)
              ? new QualifiedName(
                  cls.getCanonicalName().replaceAll("\\.", SyntaxConstants.NS_DELIM), mtd.getName())
              : new QualifiedName(mtd.getName());
      final NamedFunction f =
          new NamedFunction(
              functionName, new JavaDefinedFunction(mtd, null, _middleEnd.getTypesystem()));

      if (isPublicFunction) {
        result.getPublicFunctions().add(f);
      } else if (isAroundAdvice) {
        result.getAdvice().add(createAdvice(f, mtd));
      } else {
        result.getPrivateFunctions().add(f);
      }
    }

    final M2tImports importsAnn = cls.getAnnotation(M2tImports.class);
    if (importsAnn != null) {
      for (M2tImport imp : importsAnn.imports()) {
        String impResPath = imp.resource().replaceAll("\\.", SyntaxConstants.NS_DELIM);
        impResPath.replaceAll("/", SyntaxConstants.NS_DELIM);
        result.getImports().add(new ImportedResource(impResPath, imp.reexport()));
      }
    }

    return result;
  }