private <T extends Annotation> T findAnnotationOnMethodOrEnclosingType(
     final JMethod method, final Class<T> annotationType) {
   T annotation = method.getAnnotation(annotationType);
   if (annotation == null) {
     annotation = method.getEnclosingType().getAnnotation(annotationType);
   }
   return annotation;
 }
 private String getRestMethod(JMethod method) throws UnableToCompleteException {
   String restMethod = null;
   if (method.getAnnotation(DELETE.class) != null) {
     restMethod = METHOD_DELETE;
   } else if (method.getAnnotation(GET.class) != null) {
     restMethod = METHOD_GET;
   } else if (method.getAnnotation(HEAD.class) != null) {
     restMethod = METHOD_HEAD;
   } else if (method.getAnnotation(OPTIONS.class) != null) {
     restMethod = METHOD_OPTIONS;
   } else if (method.getAnnotation(POST.class) != null) {
     restMethod = METHOD_POST;
   } else if (method.getAnnotation(PUT.class) != null) {
     restMethod = METHOD_PUT;
   } else if (method.getAnnotation(JSONP.class) != null) {
     restMethod = METHOD_JSONP;
   } else {
     restMethod = method.getName();
     if (!REST_METHODS.contains(restMethod)) {
       getLogger()
           .log(
               ERROR,
               "Invalid rest method. It must either have a lower case rest method name or have a javax rs method annotation: "
                   + method.getReadableDeclaration());
       throw new UnableToCompleteException();
     }
   }
   return restMethod;
 }
  private void writeSubresourceLocatorImpl(JMethod method) throws UnableToCompleteException {
    JClassType iface = method.getReturnType().isInterface();
    if (iface == null || !REST_SERVICE_TYPE.isAssignableFrom(iface)) {
      getLogger()
          .log(
              ERROR,
              "Invalid subresource locator method. Method must have return type of an interface that extends RestService: "
                  + method.getReadableDeclaration());
      throw new UnableToCompleteException();
    }

    Path pathAnnotation = method.getAnnotation(Path.class);
    if (pathAnnotation == null) {
      getLogger()
          .log(
              ERROR,
              "Invalid subresource locator method. Method must have @Path annotation: "
                  + method.getReadableDeclaration());
      throw new UnableToCompleteException();
    }
    String pathExpression = wrap(pathAnnotation.value());

    for (JParameter arg : method.getParameters()) {
      PathParam paramPath = arg.getAnnotation(PathParam.class);
      if (paramPath != null) {
        pathExpression = pathExpression(pathExpression, arg, paramPath);
      }
    }

    p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
    {
      JType type = method.getReturnType();
      String name;
      if (type instanceof JClassType) {
        JClassType restService = (JClassType) type;
        RestServiceClassCreator generator =
            new RestServiceClassCreator(getLogger(), context, restService);
        name = generator.create();
      } else {
        throw new UnsupportedOperationException("Subresource method may not return: " + type);
      }
      p(method.getReturnType().getQualifiedSourceName() + " __subresource = new " + name + "();");
      p(
          "(("
              + RestServiceProxy.class.getName()
              + ")__subresource).setResource(getResource().resolve("
              + pathExpression
              + "));");
      p("return __subresource;");
    }
    i(-1).p("}");
  }
  private void writeMethodImpl(JMethod method) throws UnableToCompleteException {
    boolean returnRequest = false;
    if (method.getReturnType() != JPrimitiveType.VOID) {
      if (!method.getReturnType().getQualifiedSourceName().equals(Request.class.getName())
          && !method
              .getReturnType()
              .getQualifiedSourceName()
              .equals(JsonpRequest.class.getName())) {
        getLogger()
            .log(
                ERROR,
                "Invalid rest method. Method must have void, Request or JsonpRequest return types: "
                    + method.getReadableDeclaration());
        throw new UnableToCompleteException();
      } else {
        returnRequest = true;
      }
    }

    Json jsonAnnotation = source.getAnnotation(Json.class);
    final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;

    Options classOptions = source.getAnnotation(Options.class);
    Options options = method.getAnnotation(Options.class);

    p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
    {
      String restMethod = getRestMethod(method);
      LinkedList<JParameter> args =
          new LinkedList<JParameter>(Arrays.asList(method.getParameters()));

      // the last arg should be the callback.
      if (args.isEmpty()) {
        getLogger()
            .log(
                ERROR,
                "Invalid rest method. Method must declare at least a callback argument: "
                    + method.getReadableDeclaration());
        throw new UnableToCompleteException();
      }
      JParameter callbackArg = args.removeLast();
      JClassType callbackType = callbackArg.getType().isClassOrInterface();
      JClassType methodCallbackType = METHOD_CALLBACK_TYPE;
      if (callbackType == null || !callbackType.isAssignableTo(methodCallbackType)) {
        getLogger()
            .log(
                ERROR,
                "Invalid rest method. Last argument must be a "
                    + methodCallbackType.getName()
                    + " type: "
                    + method.getReadableDeclaration());
        throw new UnableToCompleteException();
      }
      JClassType resultType = getCallbackTypeGenericClass(callbackType);

      String pathExpression = null;
      Path pathAnnotation = method.getAnnotation(Path.class);
      if (pathAnnotation != null) {
        pathExpression = wrap(pathAnnotation.value());
      }

      JParameter contentArg = null;
      HashMap<String, JParameter> queryParams = new HashMap<String, JParameter>();
      HashMap<String, JParameter> formParams = new HashMap<String, JParameter>();
      HashMap<String, JParameter> headerParams = new HashMap<String, JParameter>();

      for (JParameter arg : args) {
        PathParam paramPath = arg.getAnnotation(PathParam.class);
        if (paramPath != null) {
          if (pathExpression == null) {
            getLogger()
                .log(
                    ERROR,
                    "Invalid rest method.  Invalid @PathParam annotation. Method is missing the @Path annotation: "
                        + method.getReadableDeclaration());
            throw new UnableToCompleteException();
          }
          pathExpression = pathExpression(pathExpression, arg, paramPath);
          // .replaceAll(Pattern.quote("{" + paramPath.value() + "}"),
          // "\"+com.google.gwt.http.client.URL.encodePathSegment(" + toStringExpression(arg) +
          // ")+\"");
          if (arg.getAnnotation(Attribute.class) != null) {
            // allow part of the arg-object participate in as PathParam and the object goes over the
            // wire
            contentArg = arg;
          }
          continue;
        }

        QueryParam queryParam = arg.getAnnotation(QueryParam.class);
        if (queryParam != null) {
          queryParams.put(queryParam.value(), arg);
          continue;
        }

        FormParam formParam = arg.getAnnotation(FormParam.class);
        if (formParam != null) {
          formParams.put(formParam.value(), arg);
          continue;
        }

        HeaderParam headerParam = arg.getAnnotation(HeaderParam.class);
        if (headerParam != null) {
          headerParams.put(headerParam.value(), arg);
          continue;
        }

        if (!formParams.isEmpty()) {
          getLogger()
              .log(
                  ERROR,
                  "You can not have both @FormParam parameters and a content parameter: "
                      + method.getReadableDeclaration());
          throw new UnableToCompleteException();
        }

        if (contentArg != null) {
          getLogger()
              .log(
                  ERROR,
                  "Invalid rest method. Only one content parameter is supported: "
                      + method.getReadableDeclaration());
          throw new UnableToCompleteException();
        }
        contentArg = arg;
      }

      String acceptTypeBuiltIn = null;
      if (callbackType.equals(TEXT_CALLBACK_TYPE)) {
        acceptTypeBuiltIn = "CONTENT_TYPE_TEXT";
      } else if (callbackType.equals(JSON_CALLBACK_TYPE)) {
        acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
      } else if (callbackType.isAssignableTo(OVERLAY_CALLBACK_TYPE)) {
        acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
      } else if (callbackType.equals(XML_CALLBACK_TYPE)) {
        acceptTypeBuiltIn = "CONTENT_TYPE_XML";
      }

      p("final " + METHOD_CLASS + " __method =");

      p("getResource()");
      if (pathExpression != null) {
        p(".resolve(" + pathExpression + ")");
      }
      for (Map.Entry<String, JParameter> entry : queryParams.entrySet()) {
        String expr = entry.getValue().getName();
        JClassType type = entry.getValue().getType().isClassOrInterface();
        if (type != null && isQueryParamListType(type)) {
          p(
              ".addQueryParams("
                  + wrap(entry.getKey())
                  + ", "
                  + toIteratedStringExpression(entry.getValue())
                  + ")");
        } else {
          p(
              ".addQueryParam("
                  + wrap(entry.getKey())
                  + ", "
                  + toStringExpression(entry.getValue().getType(), expr)
                  + ")");
        }
      }
      // example: .get()
      p("." + restMethod + "();");

      // Handle JSONP specific configuration...
      JSONP jsonpAnnotation = method.getAnnotation(JSONP.class);

      final boolean isJsonp = restMethod.equals(METHOD_JSONP) && jsonpAnnotation != null;
      if (isJsonp) {
        if (returnRequest
            && !method
                .getReturnType()
                .getQualifiedSourceName()
                .equals(JsonpRequest.class.getName())) {
          getLogger()
              .log(
                  ERROR,
                  "Invalid rest method. JSONP method must have void or JsonpRequest return types: "
                      + method.getReadableDeclaration());
          throw new UnableToCompleteException();
        }
        if (jsonpAnnotation.callbackParam().length() > 0) {
          p(
              "(("
                  + JSONP_METHOD_CLASS
                  + ")__method).callbackParam("
                  + wrap(jsonpAnnotation.callbackParam())
                  + ");");
        }
        if (jsonpAnnotation.failureCallbackParam().length() > 0) {
          p(
              "(("
                  + JSONP_METHOD_CLASS
                  + ")__method).failureCallbackParam("
                  + wrap(jsonpAnnotation.failureCallbackParam())
                  + ");");
        }
      } else {
        if (returnRequest
            && !method.getReturnType().getQualifiedSourceName().equals(Request.class.getName())) {
          getLogger()
              .log(
                  ERROR,
                  "Invalid rest method. Non JSONP method must have void or Request return types: "
                      + method.getReadableDeclaration());
          throw new UnableToCompleteException();
        }
      }

      // configure the dispatcher
      if (options != null && options.dispatcher() != Dispatcher.class) {
        // use the dispatcher configured for the method.
        p("__method.setDispatcher(" + options.dispatcher().getName() + ".INSTANCE);");
      } else {
        // use the default dispatcher configured for the service..
        p("__method.setDispatcher(this.dispatcher);");
      }

      // configure the expected statuses..
      if (options != null && options.expect().length != 0) {
        // Using method level defined expected status
        p("__method.expect(" + join(options.expect(), ", ") + ");");
      } else if (classOptions != null && classOptions.expect().length != 0) {
        // Using class level defined expected status
        p("__method.expect(" + join(classOptions.expect(), ", ") + ");");
      }

      // configure the timeout
      if (options != null && options.timeout() >= 0) {
        // Using method level defined value
        p("__method.timeout(" + options.timeout() + ");");
      } else if (classOptions != null && classOptions.timeout() >= 0) {
        // Using class level defined value
        p("__method.timeout(" + classOptions.timeout() + ");");
      }

      if (jsonpAnnotation == null) {
        Produces producesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Produces.class);
        if (producesAnnotation != null) {
          p(
              "__method.header("
                  + RESOURCE_CLASS
                  + ".HEADER_ACCEPT, "
                  + wrap(producesAnnotation.value()[0])
                  + ");");
        } else {
          // set the default accept header....
          if (acceptTypeBuiltIn != null) {
            p(
                "__method.header("
                    + RESOURCE_CLASS
                    + ".HEADER_ACCEPT, "
                    + RESOURCE_CLASS
                    + "."
                    + acceptTypeBuiltIn
                    + ");");
          } else {
            p(
                "__method.header("
                    + RESOURCE_CLASS
                    + ".HEADER_ACCEPT, "
                    + RESOURCE_CLASS
                    + ".CONTENT_TYPE_JSON);");
          }
        }

        Consumes consumesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Consumes.class);
        if (consumesAnnotation != null) {
          p(
              "__method.header("
                  + RESOURCE_CLASS
                  + ".HEADER_CONTENT_TYPE, "
                  + wrap(consumesAnnotation.value()[0])
                  + ");");
        }

        // and set the explicit headers now (could override the accept header)
        for (Map.Entry<String, JParameter> entry : headerParams.entrySet()) {
          String expr = entry.getValue().getName();
          p(
              "__method.header("
                  + wrap(entry.getKey())
                  + ", "
                  + toStringExpression(entry.getValue().getType(), expr)
                  + ");");
        }
      }

      if (!formParams.isEmpty()) {
        p(FORM_POST_CONTENT_CLASS + " __formPostContent = new " + FORM_POST_CONTENT_CLASS + "();");

        for (Map.Entry<String, JParameter> entry : formParams.entrySet()) {
          p(
              "__formPostContent.addParameter("
                  + wrap(entry.getKey())
                  + ", "
                  + toFormStringExpression(entry.getValue(), classStyle)
                  + ");");
        }

        p("__method.form(__formPostContent.getTextContent());");
      }

      if (contentArg != null) {
        if (contentArg.getType() == STRING_TYPE) {
          p("__method.text(" + contentArg.getName() + ");");
        } else if (contentArg.getType() == JSON_VALUE_TYPE) {
          p("__method.json(" + contentArg.getName() + ");");
        } else if (contentArg.getType().isClass() != null
            && isOverlayArrayType(contentArg.getType().isClass())) {
          p("__method.json(new " + JSON_ARRAY_CLASS + "(" + contentArg.getName() + "));");
        } else if (contentArg.getType().isClass() != null
            && contentArg.getType().isClass().isAssignableTo(OVERLAY_VALUE_TYPE)) {
          p("__method.json(new " + JSON_OBJECT_CLASS + "(" + contentArg.getName() + "));");
        } else if (contentArg.getType() == DOCUMENT_TYPE) {
          p("__method.xml(" + contentArg.getName() + ");");
        } else {
          JClassType contentClass = contentArg.getType().isClass();
          if (contentClass == null) {
            contentClass = contentArg.getType().isClassOrInterface();
            if (!locator.isCollectionType(contentClass)) {
              getLogger().log(ERROR, "Content argument must be a class.");
              throw new UnableToCompleteException();
            }
          }

          jsonAnnotation = contentArg.getAnnotation(Json.class);
          Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;

          // example:
          // .json(Listings$_Generated_JsonEncoder_$.INSTANCE.encode(arg0)
          // )
          p(
              "__method.json("
                  + locator.encodeExpression(contentClass, contentArg.getName(), style)
                  + ");");
        }
      }

      List<AnnotationResolver> annotationResolvers = getAnnotationResolvers(context, getLogger());
      getLogger()
          .log(
              TreeLogger.DEBUG,
              "found " + annotationResolvers.size() + " additional AnnotationResolvers");

      for (AnnotationResolver a : annotationResolvers) {
        getLogger()
            .log(
                TreeLogger.DEBUG,
                "("
                    + a.getClass().getName()
                    + ") resolve `"
                    + source.getName()
                    + "#"
                    + method.getName()
                    + "´ ...");
        final Map<String, String[]> addDataParams =
            a.resolveAnnotation(getLogger(), source, method, restMethod);

        if (addDataParams != null) {
          for (String s : addDataParams.keySet()) {
            final StringBuilder sb = new StringBuilder();
            final List<String> classList = Arrays.asList(addDataParams.get(s));

            sb.append("[");
            for (int i = 0; i < classList.size(); ++i) {
              sb.append("\\\"").append(classList.get(i)).append("\\\"");

              if ((i + 1) < classList.size()) {
                sb.append(",");
              }
            }
            sb.append("]");

            getLogger()
                .log(TreeLogger.DEBUG, "add call with (\"" + s + "\", \"" + sb.toString() + "\")");
            p("__method.addData(\"" + s + "\", \"" + sb.toString() + "\");");
          }
        }
      }

      if (acceptTypeBuiltIn != null) {
        // TODO: shouldn't we also have a cach in here?
        p(returnRequest(returnRequest, isJsonp) + "__method.send(" + callbackArg.getName() + ");");
      } else if (isJsonp) {
        p(returnRequest(returnRequest, isJsonp)
                + "(("
                + JSONP_METHOD_CLASS
                + ")__method).send(new "
                + ABSTRACT_ASYNC_CALLBACK_CLASS
                + "<"
                + resultType.getParameterizedQualifiedSourceName()
                + ">(("
                + JSONP_METHOD_CLASS
                + ")__method, "
                + callbackArg.getName()
                + ") {")
            .i(1);
        {
          p("protected "
                  + resultType.getParameterizedQualifiedSourceName()
                  + " parseResult("
                  + JSON_VALUE_CLASS
                  + " result) throws Exception {")
              .i(1);
          {
            if (resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
              p("return (java.lang.Void) null;");
            } else {
              p("try {").i(1);
              {
                if (resultType.isAssignableTo(locator.LIST_TYPE)) {
                  p("result = new " + JSON_ARRAY_CLASS + "(result.getJavaScriptObject());");
                }
                jsonAnnotation = method.getAnnotation(Json.class);
                Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                p("return " + locator.decodeExpression(resultType, "result", style) + ";");
              }
              i(-1).p("} catch (Throwable __e) {").i(1);
              {
                p(
                    "throw new "
                        + RESPONSE_FORMAT_EXCEPTION_CLASS
                        + "(\"Response was NOT a valid JSON document\", __e);");
              }
              i(-1).p("}");
            }
          }
          i(-1).p("}");
        }
        i(-1).p("});");
      } else {
        p("try {").i(1);
        {
          p(returnRequest(returnRequest, isJsonp)
                  + "__method.send(new "
                  + ABSTRACT_REQUEST_CALLBACK_CLASS
                  + "<"
                  + resultType.getParameterizedQualifiedSourceName()
                  + ">(__method, "
                  + callbackArg.getName()
                  + ") {")
              .i(1);
          {
            p("protected "
                    + resultType.getParameterizedQualifiedSourceName()
                    + " parseResult() throws Exception {")
                .i(1);
            {
              if (resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
                p("return (java.lang.Void) null;");
              } else {
                p("try {").i(1);
                {
                  jsonAnnotation = method.getAnnotation(Json.class);
                  Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
                  p(
                      "return "
                          + locator.decodeExpression(
                              resultType,
                              JSON_PARSER_CLASS + ".parse(__method.getResponse().getText())",
                              style)
                          + ";");
                }
                i(-1).p("} catch (Throwable __e) {").i(1);
                {
                  p(
                      "throw new "
                          + RESPONSE_FORMAT_EXCEPTION_CLASS
                          + "(\"Response was NOT a valid JSON document\", __e);");
                }
                i(-1).p("}");
              }
            }
            i(-1).p("}");
          }
          i(-1).p("});");
        }
        i(-1).p("} catch (" + REQUEST_EXCEPTION_CLASS + " __e) {").i(1);
        {
          p(callbackArg.getName() + ".onFailure(__method,__e);");
          if (returnRequest) {
            p("return null;");
          }
        }
        i(-1).p("}");
      }
    }
    i(-1).p("}");
  }
  @Override
  protected void generate() throws UnableToCompleteException {

    if (source.isInterface() == null) {
      getLogger().log(ERROR, "Type is not an interface.");
      throw new UnableToCompleteException();
    }

    locator = new JsonEncoderDecoderInstanceLocator(context, getLogger());

    this.XML_CALLBACK_TYPE = find(XmlCallback.class, getLogger(), context);
    this.METHOD_CALLBACK_TYPE = find(MethodCallback.class, getLogger(), context);
    this.TEXT_CALLBACK_TYPE = find(TextCallback.class, getLogger(), context);
    this.JSON_CALLBACK_TYPE = find(JsonCallback.class, getLogger(), context);
    this.OVERLAY_CALLBACK_TYPE = find(OverlayCallback.class, getLogger(), context);
    this.DOCUMENT_TYPE = find(Document.class, getLogger(), context);
    this.METHOD_TYPE = find(Method.class, getLogger(), context);
    this.STRING_TYPE = find(String.class, getLogger(), context);
    this.JSON_VALUE_TYPE = find(JSONValue.class, getLogger(), context);
    this.OVERLAY_VALUE_TYPE = find(JavaScriptObject.class, getLogger(), context);
    this.OVERLAY_ARRAY_TYPES = new HashSet<JClassType>();
    this.OVERLAY_ARRAY_TYPES.add(find(JsArray.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayBoolean.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayInteger.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayNumber.class, getLogger(), context));
    this.OVERLAY_ARRAY_TYPES.add(find(JsArrayString.class, getLogger(), context));
    this.QUERY_PARAM_LIST_TYPES = new HashSet<JClassType>();
    this.QUERY_PARAM_LIST_TYPES.add(find(List.class, getLogger(), context));
    this.QUERY_PARAM_LIST_TYPES.add(find(Set.class, getLogger(), context));
    this.REST_SERVICE_TYPE = find(RestService.class, getLogger(), context);

    String path = null;
    Path pathAnnotation = source.getAnnotation(Path.class);
    if (pathAnnotation != null) {
      path = pathAnnotation.value();
    }

    RemoteServiceRelativePath relativePath = source.getAnnotation(RemoteServiceRelativePath.class);
    if (relativePath != null) {
      path = relativePath.value();
    }

    p("private " + RESOURCE_CLASS + " resource = null;");
    p();

    p("public void setResource(" + RESOURCE_CLASS + " resource) {").i(1);
    {
      p("this.resource = resource;");
    }
    i(-1).p("}");

    p("public " + RESOURCE_CLASS + " getResource() {").i(1);
    {
      p("if (this.resource == null) {").i(1);
      if (path == null) {
        p("this.resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot());");
      } else {
        p(
            "this.resource = new "
                + RESOURCE_CLASS
                + "("
                + DEFAULTS_CLASS
                + ".getServiceRoot()).resolve("
                + quote(path)
                + ");");
      }
      i(-1).p("}");
      p("return this.resource;");
    }
    i(-1).p("}");

    Options options = source.getAnnotation(Options.class);
    if (options != null && options.dispatcher() != Dispatcher.class) {
      p(
          "private "
              + DISPATCHER_CLASS
              + " dispatcher = "
              + options.dispatcher().getName()
              + ".INSTANCE;");
    } else {
      p("private " + DISPATCHER_CLASS + " dispatcher = null;");
    }

    p();
    p("public void setDispatcher(" + DISPATCHER_CLASS + " dispatcher) {").i(1);
    {
      p("this.dispatcher = dispatcher;");
    }
    i(-1).p("}");

    p();
    p("public " + DISPATCHER_CLASS + " getDispatcher() {").i(1);
    {
      p("return this.dispatcher;");
    }
    i(-1).p("}");

    for (JMethod method : source.getInheritableMethods()) {
      JClassType iface = method.getReturnType().isInterface();
      if (iface != null && REST_SERVICE_TYPE.isAssignableFrom(iface))
        writeSubresourceLocatorImpl(method);
      else writeMethodImpl(method);
    }
  }
  private InjectionNode innerGenerateProxyCode(InjectionNode injectionNode) {
    AOPProxyAspect aopProxyAspect = injectionNode.getAspect(AOPProxyAspect.class);
    JDefinedClass definedClass;
    String proxyClassName = injectionNode.getClassName() + "_AOPProxy";
    ASTInjectionAspect injectionAspect = injectionNode.getAspect(ASTInjectionAspect.class);
    ConstructorInjectionPoint constructorInjectionPoint =
        injectionAspect.getConstructorInjectionPoint();
    ConstructorInjectionPoint proxyConstructorInjectionPoint =
        new ConstructorInjectionPoint(ASTAccessModifier.PUBLIC);

    try {

      definedClass = codeModel._class(JMod.PUBLIC, proxyClassName, ClassType.CLASS);

      annotateGeneratedClass(definedClass);

      // extending injectionNode
      definedClass._extends(codeModel.ref(injectionNode.getClassName()));

      // copy constructor elements and add aop interceptors
      JMethod constructor = definedClass.constructor(JMod.PUBLIC);

      // converting exceptions into runtime exceptions
      proxyConstructorInjectionPoint.addThrows(constructorInjectionPoint.getThrowsTypes());
      for (ASTType throwType : constructorInjectionPoint.getThrowsTypes()) {
        constructor._throws(codeModel.ref(throwType.getName()));
      }

      JBlock constructorBody = constructor.body();

      List<JVar> superArguments = new ArrayList<JVar>();
      for (InjectionNode node : constructorInjectionPoint.getInjectionNodes()) {
        String paramName = namer.generateName(node);
        JVar param = constructor.param(codeModel.ref(node.getClassName()), paramName);
        superArguments.add(param);
        proxyConstructorInjectionPoint.addInjectionNode(node);
      }

      // super construction
      JInvocation constructorInvocation = constructorBody.invoke(SUPER_REF);
      for (JVar paramArgument : superArguments) {
        constructorInvocation.arg(paramArgument);
      }

      // method interceptors
      Map<ASTMethod, Map<InjectionNode, JFieldVar>> interceptorFields =
          new HashMap<ASTMethod, Map<InjectionNode, JFieldVar>>();
      for (Map.Entry<ASTMethod, Set<InjectionNode>> methodInterceptorEntry :
          aopProxyAspect.getMethodInterceptors().entrySet()) {

        buildMethodInterceptor(
            definedClass,
            proxyConstructorInjectionPoint,
            constructor,
            constructorBody,
            interceptorFields,
            methodInterceptorEntry);
      }

    } catch (JClassAlreadyExistsException e) {
      logger.error("JClassAlreadyExistsException while building AOP Proxy", e);
    } catch (ClassNotFoundException e) {
      logger.error("ClassNotFoundException while building AOP Proxy", e);
    }

    return buildProxyInjectionNode(
        injectionNode, proxyClassName, injectionAspect, proxyConstructorInjectionPoint);
  }
  private JExpression buildInterceptorChain(
      JDefinedClass definedClass,
      ASTMethod method,
      Map<ASTParameter, JVar> parameterMap,
      Set<InjectionNode> interceptors,
      Map<InjectionNode, JFieldVar> interceptorNameMap) {

    try {
      JDefinedClass methodExecutionClass =
          definedClass._class(
              JMod.PRIVATE | JMod.FINAL,
              namer.generateClassName(MethodInterceptorChain.MethodExecution.class));
      methodExecutionClass._implements(MethodInterceptorChain.MethodExecution.class);

      // setup constructor with needed parameters
      JMethod constructor = methodExecutionClass.constructor(JMod.PUBLIC);
      JBlock constructorbody = constructor.body();
      List<JExpression> methodParameters = new ArrayList<JExpression>();
      for (ASTParameter parameter : method.getParameters()) {
        JType parameterType = parameterMap.get(parameter).type();
        JVar param = constructor.param(parameterType, namer.generateName(parameterType));
        JFieldVar field =
            methodExecutionClass.field(
                JMod.PRIVATE, parameterType, namer.generateName(parameterType));
        constructorbody.assign(field, param);
        methodParameters.add(field);
      }

      // getMethod()
      JMethod getMethod =
          methodExecutionClass.method(
              JMod.PUBLIC, Method.class, MethodInterceptorChain.MethodExecution.GET_METHOD);

      JInvocation getMethodInvocation =
          definedClass.dotclass().invoke(CLASS_GET_METHOD).arg(method.getName());
      getMethod.body()._return(getMethodInvocation);
      getMethod._throws(NoSuchMethodException.class);

      for (ASTParameter astParameter : method.getParameters()) {
        getMethodInvocation.arg(codeModel.ref(astParameter.getASTType().getName()).dotclass());
      }

      // invoke()
      JMethod invokeMethod =
          methodExecutionClass.method(
              JMod.PUBLIC, Object.class, MethodInterceptorChain.MethodExecution.INVOKE);

      // add all throws of contained method
      for (ASTType throwable : method.getThrowsTypes()) {
        invokeMethod._throws(codeModel.ref(throwable.getName()));
      }

      JInvocation superCall = definedClass.staticRef(SUPER_REF).invoke(method.getName());

      for (JExpression methodParam : methodParameters) {
        superCall.arg(methodParam);
      }

      if (method.getReturnType().equals(ASTVoidType.VOID)) {
        invokeMethod.body().add(superCall);
        invokeMethod.body()._return(JExpr._null());
      } else {
        invokeMethod.body()._return(superCall);
      }

      JInvocation methodExecutionInvocation = JExpr._new(methodExecutionClass);

      for (ASTParameter astParameter : method.getParameters()) {
        methodExecutionInvocation.arg(parameterMap.get(astParameter));
      }

      JInvocation newInterceptorInvocation =
          JExpr._new(codeModel.ref(MethodInterceptorChain.class))
              .arg(methodExecutionInvocation)
              .arg(JExpr._this());

      for (InjectionNode interceptor : interceptors) {
        newInterceptorInvocation.arg(interceptorNameMap.get(interceptor));
      }

      return newInterceptorInvocation;
    } catch (JClassAlreadyExistsException e) {
      throw new TransfuseAnalysisException("Class already defined while generating inner class", e);
    }
  }
  private void buildMethodInterceptor(
      JDefinedClass definedClass,
      ConstructorInjectionPoint proxyConstructorInjectionPoint,
      JMethod constructor,
      JBlock constructorBody,
      Map<ASTMethod, Map<InjectionNode, JFieldVar>> interceptorFields,
      Map.Entry<ASTMethod, Set<InjectionNode>> methodInterceptorEntry)
      throws ClassNotFoundException {
    ASTMethod method = methodInterceptorEntry.getKey();

    if (method.getAccessModifier().equals(ASTAccessModifier.PRIVATE)) {
      throw new TransfuseAnalysisException("Unable to provide AOP on private methods");
    }

    if (!interceptorFields.containsKey(methodInterceptorEntry.getKey())) {
      interceptorFields.put(
          methodInterceptorEntry.getKey(), new HashMap<InjectionNode, JFieldVar>());
    }
    Map<InjectionNode, JFieldVar> injectionNodeInstanceNameMap =
        interceptorFields.get(methodInterceptorEntry.getKey());

    // setup interceptor fields
    for (InjectionNode interceptorInjectionNode : methodInterceptorEntry.getValue()) {
      String interceptorInstanceName = namer.generateName(interceptorInjectionNode);

      JFieldVar interceptorField =
          definedClass.field(
              JMod.PRIVATE,
              codeModel.ref(interceptorInjectionNode.getClassName()),
              interceptorInstanceName);

      injectionNodeInstanceNameMap.put(interceptorInjectionNode, interceptorField);

      JVar interceptorParam =
          constructor.param(
              codeModel.ref(interceptorInjectionNode.getClassName()),
              namer.generateName(interceptorInjectionNode));

      constructorBody.assign(interceptorField, interceptorParam);

      proxyConstructorInjectionPoint.addInjectionNode(interceptorInjectionNode);
    }

    JType returnType = codeModel.parseType(method.getReturnType().getName());

    JMethod methodDeclaration =
        definedClass.method(
            method.getAccessModifier().getCodeModelJMod(), returnType, method.getName());
    JBlock body = methodDeclaration.body();

    // define method parameter
    Map<ASTParameter, JVar> parameterMap = new HashMap<ASTParameter, JVar>();
    for (ASTParameter parameter : method.getParameters()) {
      parameterMap.put(
          parameter,
          methodDeclaration.param(
              JMod.FINAL,
              codeModel.ref(parameter.getASTType().getName()),
              namer.generateName(parameter.getASTType())));
    }

    // aop interceptor
    Map<InjectionNode, JFieldVar> interceptorNameMap =
        interceptorFields.get(methodInterceptorEntry.getKey());

    JArray paramArray = JExpr.newArray(codeModel.ref(Object.class));

    for (ASTParameter astParameter : method.getParameters()) {
      paramArray.add(parameterMap.get(astParameter));
    }

    JInvocation interceptorInvocation =
        buildInterceptorChain(
                definedClass,
                method,
                parameterMap,
                methodInterceptorEntry.getValue(),
                interceptorNameMap)
            .invoke("invoke");
    interceptorInvocation.arg(paramArray);

    if (method.getReturnType().equals(ASTVoidType.VOID)) {
      body.add(interceptorInvocation);
    } else {
      body._return(JExpr.cast(returnType.boxify(), interceptorInvocation));
    }
  }