private void implementSetter(
      TreeLogger logger,
      MethodBuffer mb,
      JMethod method,
      ModelMagic manifest,
      HasModelFields models,
      Annotation[] annos) {

    JPrimitiveType primitive = method.getReturnType().isPrimitive();
    boolean isVoid = primitive == JPrimitiveType.VOID;
    boolean isFluent = ModelGeneratorGwt.isFluent(method);
    String name = ModelGeneratorGwt.fieldName(method, manifest);
    ModelField field = models.getOrMakeField(name);
    field.addSetter(
                X_Source.binaryToSource(method.getReturnType().getQualifiedBinaryName()),
                name,
                ModelGeneratorGwt.toTypes(method.getParameterTypes()))
            .fluent =
        isFluent;
    if (!isFluent && !isVoid) {
      mb.println("var value = getProperty(\"" + name + "\");");
    }
    JParameter[] params = method.getParameters();
    if (params.length != 1)
      throw new NotConfiguredCorrectly(
          "A setter method, " + method.getJsniSignature() + " must have exactly one parameter");
    mb.println("setProperty(\"" + name + "\", A);");
    if (!isVoid) mb.println("return " + (isFluent ? "this;" : "value;"));
  }
  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("}");
  }
 /**
  * @param srcWriter
  * @param type
  * @param parentVariable
  * @param added
  * @param iocContainerVariable
  * @param configurations
  */
 private static void injectMethods(
     SourcePrinter srcWriter,
     JClassType type,
     String parentVariable,
     Set<String> added,
     String iocContainerVariable,
     Map<String, IocConfig<?>> configurations) {
   for (JMethod method : type.getMethods()) {
     Inject inject = method.getAnnotation(Inject.class);
     if (inject != null && !method.isStatic()) {
       String methodName = method.getName();
       if (!added.contains(methodName + "()")) {
         added.add(methodName + "()");
         JParameter[] parameters = method.getParameters();
         List<String> params = new ArrayList<String>();
         for (JParameter parameter : parameters) {
           JType parameterType = parameter.getType();
           if ((parameterType.isPrimitive() != null)) {
             throw new IoCException(
                 "IoC Error Method ["
                     + methodName
                     + "] from class ["
                     + type.getQualifiedSourceName()
                     + "] declares an invalid parameter. Primitive types are not allowed here");
           }
           String variableName = "parameter_" + methodName + "_" + parameter.getName();
           params.add(variableName);
           String injectionExpression =
               getParameterInjectionExpression(parameter, iocContainerVariable, configurations);
           srcWriter.println(
               parameterType.getQualifiedSourceName()
                   + " "
                   + variableName
                   + " = "
                   + injectionExpression
                   + ";");
         }
         srcWriter.print(parentVariable + "." + methodName + "(");
         boolean first = true;
         for (String param : params) {
           if (!first) {
             srcWriter.print(", ");
           }
           first = false;
           srcWriter.print(param);
         }
         srcWriter.println(");");
       }
     }
   }
 }
  /** Examine a RequestContext method to see if it returns a transportable type. */
  private boolean validateContextMethodAndSetDataType(
      RequestMethod.Builder methodBuilder, JMethod method, boolean allowSetters)
      throws UnableToCompleteException {
    JClassType requestReturnType = method.getReturnType().isInterface();
    JClassType invocationReturnType;
    if (requestReturnType == null) {
      // Primitive return type
      poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
      return false;
    }

    if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
      // Instance method invocation
      JClassType[] params =
          ModelUtils.findParameterizationOf(instanceRequestInterface, requestReturnType);
      methodBuilder.setInstanceType(getEntityProxyType(params[0]));
      invocationReturnType = params[1];
    } else if (requestInterface.isAssignableFrom(requestReturnType)) {
      // Static method invocation
      JClassType[] params = ModelUtils.findParameterizationOf(requestInterface, requestReturnType);
      invocationReturnType = params[0];

    } else {
      // Unhandled return type, must be something random
      poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
      return false;
    }

    // Validate the parameters
    boolean paramsOk = true;
    JParameter[] params = method.getParameters();
    for (int i = 0; i < params.length; ++i) {
      JParameter param = params[i];
      paramsOk =
          validateTransportableType(new RequestMethod.Builder(), param.getType(), false)
              && paramsOk;
    }

    // Validate any extra properties on the request type
    for (JMethod maybeSetter : requestReturnType.getInheritableMethods()) {
      if (JBeanMethod.SET.matches(maybeSetter) || JBeanMethod.SET_BUILDER.matches(maybeSetter)) {
        if (allowSetters) {
          methodBuilder.addExtraSetter(maybeSetter);
        } else {
          poison(noSettersAllowed(maybeSetter));
        }
      }
    }
    return validateTransportableType(methodBuilder, invocationReturnType, true);
  }
    private void emitBody(SourceWriter w) throws NotFoundException {
      JClassType baseClass =
          context_.getTypeOracle().getType("org.rstudio.core.client.js.JsObjectInjector");
      JClassType c = baseType_.asParameterizationOf(baseClass.isGenericType());
      JType typeToInject = c.isParameterized().getTypeArgs()[0];

      w.print("public native final void injectObject(");
      w.print(typeToInject.getQualifiedSourceName());
      w.println(" value) /*-{");
      w.indent();

      w.println(baseExpression_ + " = {");
      w.indent();

      JMethod[] methods = typeToInject.isClassOrInterface().getMethods();
      for (int i = 0; i < methods.length; i++) {
        JMethod method = methods[i];
        final JParameter[] jParameters = method.getParameters();

        StringBuilder argString = new StringBuilder();
        for (int j = 0; j < jParameters.length; j++) {
          argString.append("_").append(j);
          if (j < jParameters.length - 1) argString.append(", ");
        }

        w.println(method.getName() + ": function(" + argString + ") {");
        w.indent();

        if (!method.getReturnType().getQualifiedSourceName().equals("void")) w.print("return ");
        w.print("value.@");
        w.print(typeToInject.getQualifiedSourceName());
        w.print("::");
        w.print(method.getName());
        w.print("(");
        for (JParameter param : jParameters) w.print(param.getType().getJNISignature());
        w.print(")(");
        w.print(argString.toString());
        w.println(");");

        w.outdent();
        w.print("}");
        w.println((i < methods.length - 1) ? "," : "");
      }

      w.outdent();
      w.println("};");

      w.outdent();
      w.println("}-*/;");
    }
  public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
      throws UnableToCompleteException {
    this.logger = logger;
    this.factoryType = factoryType;
    this.oracle = factoryType.getOracle();
    collectionInterface = oracle.findType(Collection.class.getCanonicalName());
    entityProxyInterface = oracle.findType(EntityProxy.class.getCanonicalName());
    instanceRequestInterface = oracle.findType(InstanceRequest.class.getCanonicalName());
    listInterface = oracle.findType(List.class.getCanonicalName());
    mapInterface = oracle.findType(Map.class.getCanonicalName());
    requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
    requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
    requestInterface = oracle.findType(Request.class.getCanonicalName());
    setInterface = oracle.findType(Set.class.getCanonicalName());
    splittableType = oracle.findType(Splittable.class.getCanonicalName());
    valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());

    extraTypes = checkExtraTypes(factoryType, false);
    for (JMethod method : factoryType.getOverridableMethods()) {
      if (method.getEnclosingType().equals(requestFactoryInterface)) {
        // Ignore methods defined an RequestFactory itself
        continue;
      }

      if (method.getParameters().length > 0) {
        poison("Unexpected parameter on method %s", method.getName());
        continue;
      }

      JClassType contextType = method.getReturnType().isInterface();
      if (contextType == null || !requestContextInterface.isAssignableFrom(contextType)) {
        poison(
            "Unexpected return type %s on method %s is not" + " an interface assignable to %s",
            method.getReturnType().getQualifiedSourceName(),
            method.getName(),
            requestContextInterface.getSimpleSourceName());
        continue;
      }

      ContextMethod.Builder builder = new ContextMethod.Builder();
      builder.setDeclaredMethod(method);
      buildContextMethod(builder, contextType);
      contextMethods.add(builder.build());
    }

    if (poisoned) {
      die(poisonedMessage());
    }
  }
  /** Simply prints a JSNI reference to the exported function. */
  private static void writeIdentityInvocation(FragmentGeneratorContext context, JMethod m)
      throws UnableToCompleteException {
    SourceWriter sw = context.sw;
    JParameter[] parameters = m.getParameters();

    sw.print("@");
    sw.print(m.getEnclosingType().getQualifiedSourceName());
    sw.print("::");
    sw.print(m.getName());
    sw.print("(");

    // Argument list for the Java invocation
    for (int i = 0; i < parameters.length; i++) {
      sw.print(parameters[i].getType().getJNISignature());
    }

    sw.print(")");
  }
  public static JMethod getInstantiationMethod(JClassType serializer, JClassType serializee) {
    JMethod[] overloads = serializer.getOverloads("instantiate");
    for (JMethod overload : overloads) {
      JParameter[] parameters = overload.getParameters();

      if (parameters.length != 1) {
        // Different overload
        continue;
      }

      if (!parameters[0]
          .getType()
          .getQualifiedSourceName()
          .equals(SerializationStreamReader.class.getName())) {
        // First param is not a stream class
        continue;
      }

      if (!isValidCustomFieldSerializerMethod(overload)) {
        continue;
      }

      JType type = overload.getReturnType();
      if (type.isPrimitive() != null) {
        // Primitives are auto serialized so this can't be the right method
        continue;
      }

      // TODO: if isArray answered yes to isClass this cast would not be
      // necessary
      JClassType clazz = (JClassType) type;
      if (clazz.isAssignableFrom(serializee)) {
        return overload;
      }
    }

    return null;
  }
  @Override
  protected void generateWrapperMethod(JMethod method, SourcePrinter srcWriter) {
    if (mustDelegateToController(method)) {
      JType returnType = method.getReturnType().getErasedType();

      srcWriter.println(method.getReadableDeclaration(false, false, false, false, true) + "{");
      if (returnType != JPrimitiveType.VOID) {
        srcWriter.print("return ");
      }
      srcWriter.print("this._controller." + method.getName() + "(");
      boolean needsComma = false;
      for (JParameter parameter : method.getParameters()) {
        if (needsComma) {
          srcWriter.print(", ");
        }
        needsComma = true;
        srcWriter.print(parameter.getName());
      }
      srcWriter.println(");");

      srcWriter.println("}");
    }
  }
  private static JMethod getMethod(
      String methodName, String streamClassName, JClassType serializer, JClassType serializee) {
    JMethod[] overloads = serializer.getOverloads(methodName);
    for (JMethod overload : overloads) {
      JParameter[] parameters = overload.getParameters();

      if (parameters.length != 2) {
        // Different overload
        continue;
      }

      if (!parameters[0].getType().getQualifiedSourceName().equals(streamClassName)) {
        // First param is not a stream class
        continue;
      }

      JParameter serializeeParam = parameters[1];
      JType type = serializeeParam.getType();
      if (type.isPrimitive() != null) {
        // Primitives are auto serialized so this can't be the right method
        continue;
      }

      // TODO: if isArray answered yes to isClass this cast would not be
      // necessary
      JClassType clazz = (JClassType) type;
      if (clazz.isAssignableFrom(serializee)) {
        if (isValidCustomFieldSerializerMethod(overload)
            && overload.getReturnType() == JPrimitiveType.VOID) {
          return overload;
        }
      }
    }

    return null;
  }
  /** Determines if the exported method can be used as-is. */
  private static boolean isIdentityFunction(FragmentGeneratorContext context, JMethod m)
      throws UnableToCompleteException {
    TreeLogger logger =
        context.parentLogger.branch(
            TreeLogger.DEBUG, "Determining identity status of " + m.getName(), null);
    FragmentGeneratorOracle fgo = context.fragmentGeneratorOracle;

    boolean identityOnly = m.isStatic();
    JParameter[] parameters = m.getParameters();

    identityOnly &=
        context
            .fragmentGeneratorOracle
            .findFragmentGenerator(logger, context.typeOracle, m.getReturnType())
            .isIdentity();

    for (int i = 0; i < parameters.length && identityOnly; i++) {
      FragmentGenerator fragmentGenerator =
          fgo.findFragmentGenerator(logger, context.typeOracle, parameters[i].getType());
      identityOnly &= fragmentGenerator.isIdentity();
    }

    return identityOnly;
  }
  /** Writes a linkage function object that will invoke the exported function. */
  private static void writeLinkageInvocation(FragmentGeneratorContext context, JMethod m)
      throws UnableToCompleteException {
    TreeLogger logger =
        context.parentLogger.branch(
            TreeLogger.DEBUG, "Writing function() for " + m.getName(), null);

    SourceWriter sw = context.sw;
    JParameter[] parameters = m.getParameters();
    FragmentGeneratorOracle fgo = context.fragmentGeneratorOracle;
    FragmentGenerator returnFragmentGenerator =
        fgo.findFragmentGenerator(logger, context.typeOracle, m.getReturnType());

    sw.print("function(");
    for (int i = 0; i < parameters.length; i++) {
      sw.print("arg");
      sw.print(String.valueOf(i));
      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.println(") {");
    sw.indent();

    if (returnFragmentGenerator.isIdentity()) {
      sw.print("return ");
    } else {
      sw.print("var javaReturn = ");
    }

    // Don't need to reference the instance on a static method
    if (!m.isStatic()) {
      sw.print(context.parameterName);
      sw.print(".");
    }

    sw.print("@");
    sw.print(m.getEnclosingType().getQualifiedSourceName());
    sw.print("::");
    sw.print(m.getName());
    sw.print("(");

    // Argument list for the Java invocation
    for (int i = 0; i < parameters.length; i++) {
      sw.print(parameters[i].getType().getJNISignature());
    }

    sw.println(")(");
    // Indent the parameters, each on its own like to improve readability
    sw.indent();
    sw.indent();

    for (int i = 0; i < parameters.length; i++) {
      // Create a sub-context to generate the wrap/unwrap logic
      JType returnType = parameters[i].getType();
      FragmentGeneratorContext subParams = new FragmentGeneratorContext(context);
      subParams.returnType = returnType;
      subParams.parameterName = "arg" + i;

      FragmentGenerator fragmentGenerator =
          fgo.findFragmentGenerator(logger, context.typeOracle, returnType);
      if (fragmentGenerator == null) {
        logger.log(
            TreeLogger.ERROR,
            "No fragment generator for " + returnType.getQualifiedSourceName(),
            null);
        throw new UnableToCompleteException();
      }

      fragmentGenerator.fromJS(subParams);

      if (i < parameters.length - 1) {
        sw.println(", ");
      }
    }

    sw.outdent();
    sw.outdent();
    sw.println(");");

    if (!returnFragmentGenerator.isIdentity()) {
      FragmentGeneratorContext returnContext = new FragmentGeneratorContext(context);
      returnContext.parameterName = "javaReturn";
      returnContext.returnType = m.getReturnType();
      sw.print("return ");
      returnFragmentGenerator.toJS(returnContext);
      sw.println(";");
    }

    sw.outdent();
    sw.print("}");
  }
  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("}");
  }
Exemple #14
0
  /** Adds a root type for each type that appears in the RemoteService interface methods. */
  private static void addRemoteServiceRootTypes(
      TreeLogger logger,
      TypeOracle typeOracle,
      SerializableTypeOracleBuilder typesSentFromBrowser,
      SerializableTypeOracleBuilder typesSentToBrowser,
      JClassType remoteService)
      throws NotFoundException, UnableToCompleteException {
    logger =
        logger.branch(
            TreeLogger.DEBUG,
            "Analyzing '"
                + remoteService.getParameterizedQualifiedSourceName()
                + "' for serializable types",
            null);

    JMethod[] methods = remoteService.getOverridableMethods();

    JClassType exceptionClass = typeOracle.getType(Exception.class.getName());

    JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
    JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
    RpcTokenImplementation tokenClassToUse =
        remoteService.findAnnotationInTypeHierarchy(RpcTokenImplementation.class);
    if (tokenClassToUse != null) {
      // only include serializer for the specified class literal
      JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
      if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
        typesSentFromBrowser.addRootType(logger, rpcTokenType);
        typesSentToBrowser.addRootType(logger, rteType);
      } else {
        logger.branch(
            TreeLogger.ERROR,
            "RPC token class "
                + tokenClassToUse.value()
                + " must implement "
                + RpcToken.class.getName(),
            null);
        throw new UnableToCompleteException();
      }
    } else {
      JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
      for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
        typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
      }
      if (rpcTokenSubclasses.length > 0) {
        typesSentToBrowser.addRootType(logger, rteType);
      }
    }

    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG, "Analyzing methods:", null);
    for (JMethod method : methods) {
      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG, method.toString(), null);
      JType returnType = method.getReturnType();
      if (returnType != JPrimitiveType.VOID) {
        TreeLogger returnTypeLogger =
            methodLogger.branch(
                TreeLogger.DEBUG,
                "Return type: " + returnType.getParameterizedQualifiedSourceName(),
                null);
        typesSentToBrowser.addRootType(returnTypeLogger, returnType);
      }

      JParameter[] params = method.getParameters();
      for (JParameter param : params) {
        TreeLogger paramLogger =
            methodLogger.branch(TreeLogger.DEBUG, "Parameter: " + param.toString(), null);
        JType paramType = param.getType();
        typesSentFromBrowser.addRootType(paramLogger, paramType);
      }

      JType[] exs = method.getThrows();
      if (exs.length > 0) {
        TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG, "Throws:", null);
        for (JType ex : exs) {
          if (!exceptionClass.isAssignableFrom(ex.isClass())) {
            throwsLogger =
                throwsLogger.branch(
                    TreeLogger.WARN,
                    "'"
                        + ex.getQualifiedSourceName()
                        + "' is not a checked exception; only checked exceptions may be used",
                    null);
          }

          typesSentToBrowser.addRootType(throwsLogger, ex);
        }
      }
    }
  }
  private void writeMethodImpl(JMethod method) throws UnableToCompleteException {
    if (method.getReturnType().isPrimitive() != JPrimitiveType.VOID) {
      error(
          "Invalid rest method. Method must have void return type: "
              + method.getReadableDeclaration());
    }

    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()) {
        error(
            "Invalid rest method. Method must declare at least a callback argument: "
                + method.getReadableDeclaration());
      }
      JParameter callbackArg = args.removeLast();
      JClassType callbackType = callbackArg.getType().isClassOrInterface();
      JClassType methodCallbackType = METHOD_CALLBACK_TYPE;
      if (callbackType == null || !callbackType.isAssignableTo(methodCallbackType)) {
        error(
            "Invalid rest method. Last argument must be a "
                + methodCallbackType.getName()
                + " type: "
                + method.getReadableDeclaration());
      }
      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> headerParams = new HashMap<String, JParameter>();

      for (JParameter arg : args) {
        PathParam paramPath = arg.getAnnotation(PathParam.class);
        if (paramPath != null) {
          if (pathExpression == null) {
            error(
                "Invalid rest method.  Invalid @PathParam annotation. Method is missing the @Path annotation: "
                    + method.getReadableDeclaration());
          }
          pathExpression =
              pathExpression.replaceAll(
                  Pattern.quote("{" + paramPath.value() + "}"),
                  "\"+" + toStringExpression(arg) + "+\"");
          continue;
        }

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

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

        if (contentArg != null) {
          error(
              "Invalid rest method. Only one content parameter is supported: "
                  + method.getReadableDeclaration());
        }
        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("this.resource");
      if (pathExpression != null) {
        p(".resolve(" + pathExpression + ")");
      }
      for (Map.Entry<String, JParameter> entry : queryParams.entrySet()) {
        String expr = entry.getValue().getName();
        p(
            ".addQueryParam("
                + wrap(entry.getKey())
                + ", "
                + toStringExpression(entry.getValue().getType(), expr)
                + ")");
      }
      // example: .get()
      p("." + restMethod + "();");

      // Handle JSONP specific configuration...
      JSONP jsonpAnnotation = method.getAnnotation(JSONP.class);
      if (restMethod.equals(METHOD_JSONP) && jsonpAnnotation != null) {
        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())
                  + ");");
        }
      }

      // 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() + ");");
      }

      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 (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) {
            error("Content argument must be a class.");
          }

          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)
                  + ");");
        }
      }

      if (acceptTypeBuiltIn != null) {
        p("__method.send(" + callbackArg.getName() + ");");
      } else {
        p("try {").i(1);
        {
          p("__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) new java.lang.Object();");
              } 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);");
        }
        i(-1).p("}");
      }
    }
    i(-1).p("}");
  }
Exemple #16
0
  /**
   * Generates the client's asynchronous proxy method.
   *
   * @param serializableTypeOracle the type oracle
   */
  protected void generateProxyMethod(
      SourceWriter w,
      SerializableTypeOracle serializableTypeOracle,
      TypeOracle typeOracle,
      JMethod syncMethod,
      JMethod asyncMethod) {

    w.println();

    // Write the method signature
    JType asyncReturnType = asyncMethod.getReturnType().getErasedType();
    w.print("public ");
    w.print(asyncReturnType.getQualifiedSourceName());
    w.print(" ");
    w.print(asyncMethod.getName() + "(");

    boolean needsComma = false;
    NameFactory nameFactory = new NameFactory();
    JParameter[] asyncParams = asyncMethod.getParameters();
    for (int i = 0; i < asyncParams.length; ++i) {
      JParameter param = asyncParams[i];

      if (needsComma) {
        w.print(", ");
      } else {
        needsComma = true;
      }

      /*
       * Ignoring the AsyncCallback parameter, if any method requires a call to
       * SerializationStreamWriter.writeObject we need a try catch block
       */
      JType paramType = param.getType();
      paramType = paramType.getErasedType();

      w.print(paramType.getQualifiedSourceName());
      w.print(" ");

      String paramName = param.getName();
      nameFactory.addName(paramName);
      w.print(paramName);
    }

    w.println(") {");
    w.indent();

    String helperName = nameFactory.createName("helper");
    String helperClassName = RemoteServiceProxy.ServiceHelper.class.getCanonicalName();
    w.println(
        "%s %s = new %s(\"%s\", \"%s\");",
        helperClassName, helperName, helperClassName, getProxySimpleName(), syncMethod.getName());

    w.println("try {");
    w.indent();

    // Write the parameter count followed by the parameter values
    JParameter[] syncParams = syncMethod.getParameters();

    String streamWriterName = nameFactory.createName("streamWriter");
    w.println(
        "%s %s = %s.start(REMOTE_SERVICE_INTERFACE_NAME, %s);",
        SerializationStreamWriter.class.getSimpleName(),
        streamWriterName,
        helperName,
        syncParams.length);

    for (JParameter param : syncParams) {
      JType paramType = param.getType().getErasedType();
      String typeNameExpression = computeTypeNameExpression(paramType);
      assert typeNameExpression != null
          : "Could not compute a type name for " + paramType.getQualifiedSourceName();
      w.println(streamWriterName + ".writeString(" + typeNameExpression + ");");
    }

    // Encode all of the arguments to the asynchronous method, but exclude the
    // last argument which is the callback instance.
    //
    for (int i = 0; i < asyncParams.length - 1; ++i) {
      JParameter asyncParam = asyncParams[i];
      w.print(streamWriterName + ".");
      w.print(Shared.getStreamWriteMethodNameFor(asyncParam.getType()));
      w.println("(" + asyncParam.getName() + ");");
    }

    /*
     * Depending on the return type for the async method, return a
     * RequestBuilder, a Request, or nothing at all.
     */
    JParameter callbackParam = asyncParams[asyncParams.length - 1];
    JType returnType = syncMethod.getReturnType();
    String callbackName = callbackParam.getName();

    if (asyncReturnType == JPrimitiveType.VOID) {
      w.println(
          "%s.finish(%s, ResponseReader.%s);",
          helperName, callbackName, getResponseReaderFor(returnType).name());
    } else if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
      w.println(
          "return %s.finishForRequestBuilder(%s, ResponseReader.%s);",
          helperName, callbackName, getResponseReaderFor(returnType).name());
    } else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
      w.println(
          "return %s.finish(%s, ResponseReader.%s);",
          helperName, callbackName, getResponseReaderFor(returnType).name());
    } else {
      // This method should have been caught by RemoteServiceAsyncValidator
      throw new RuntimeException(
          "Unhandled return type " + asyncReturnType.getQualifiedSourceName());
    }

    w.outdent();
    w.print("} catch (SerializationException ");
    String exceptionName = nameFactory.createName("ex");
    w.println(exceptionName + ") {");
    w.indent();
    if (!asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
      /*
       * If the method returns void or Request, signal the serialization error
       * immediately. If the method returns RequestBuilder, the error will be
       * signaled whenever RequestBuilder.send() is invoked.
       */
      w.println(callbackName + ".onFailure(" + exceptionName + ");");
    }
    if (asyncReturnType.getQualifiedSourceName().equals(RequestBuilder.class.getName())) {
      w.println(
          "return new "
              + FailingRequestBuilder.class.getName()
              + "("
              + exceptionName
              + ", "
              + callbackName
              + ");");
    } else if (asyncReturnType.getQualifiedSourceName().equals(Request.class.getName())) {
      w.println("return new " + FailedRequest.class.getName() + "();");
    } else {
      assert asyncReturnType == JPrimitiveType.VOID;
    }
    w.outdent();
    w.println("}");

    w.outdent();
    w.println("}");
  }
  private void createFindMethod(SourceWriter sourceWriter) {

    // We create more than one findMethod as the body of one method would be too large. This is the
    // int that we
    // increment to add to the name
    // i.e. findMethod0()
    int methodNum = 0;

    // This int keeps track of how many methods are generated. When it gets to 200 we create a new
    // findMethodX()
    // and chain it to the previous.
    int methodCount = 0;

    sourceWriter.println(
        "private GwtBindingMethod findOrCreateMethod(String obj, String methodName){ ");
    sourceWriter.indent();

    sourceWriter.println("GwtBindingMethod newMethod;");

    // dummy first condition, rest are "else if". Keeps us from having conditional logic.
    sourceWriter.println("if(false){ }");

    for (JClassType type : implementingTypes) {

      // close last method, chain it to a new one.
      if (methodCount > 200) {
        sourceWriter.println("return findOrCreateMethod" + (methodNum) + "(obj, methodName);");
        sourceWriter.println("}");

        sourceWriter.println(
            "private GwtBindingMethod findOrCreateMethod"
                + (methodNum++)
                + "(String obj, String methodName){ ");
        sourceWriter.println("GwtBindingMethod newMethod;");
        // dummy first condition, rest are "else if". Keeps us from having conditional logic.
        sourceWriter.println("if(false){ }");

        methodCount = 0;
      }

      String keyRoot = generateTypeKey(type);

      // if(type.isAbstract()){
      // System.out.println("abstract");
      // continue;
      // }

      // determine if there are any methods that are bindable before writing out conditional for
      // class

      JClassType loopType = type;
      boolean hasBindableMethods = false;
      JClassType eventSourceType = null;
      try {
        eventSourceType = typeOracle.getType("org.pentaho.ui.xul.XulEventSource");
      } catch (NotFoundException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }
      // CHECKSTYLE IGNORE Indentation FOR NEXT 1 LINES
      outer:
      while (loopType.getSuperclass() != null
          && loopType.getSimpleSourceName().equals("Object") == false
          && loopType.isAssignableTo(eventSourceType)) {

        for (JMethod m : loopType.getMethods()) {
          if (m.isPublic() && m.getAnnotation(Bindable.class) != null) {
            hasBindableMethods = true;
            break outer;
          }
        }
        loopType = loopType.getSuperclass();
      }
      if (hasBindableMethods == false) {
        continue;
      }
      sourceWriter.println("else if(obj.equals(\"" + type.getQualifiedSourceName() + "\")){ ");
      try {

        loopType = type;
        sourceWriter.indent();

        // Loop over class heirarchy and generate methods for every object that is declared a
        // XulEventSource
        while (loopType.getSuperclass() != null
            && loopType.getSimpleSourceName().equals("Object") == false
            && loopType.isAssignableTo(eventSourceType)) {
          String superName = generateTypeKey(loopType);

          boolean first = true;
          for (JMethod m : loopType.getMethods()) {
            methodCount++;
            if (!m.isPublic() || m.getAnnotation(Bindable.class) == null) {
              continue;
            }

            sourceWriter.println(
                (first ? "" : "else ") + "if(methodName.equals(\"" + m.getName() + "\")){ ");
            if (first) {
              first = false;
            }
            sourceWriter.indent();

            String methodName = m.getName();

            // check to see if we've already processed this classes' method. Point to that class
            // instead.
            if (generatedMethods.contains((superName + "_" + methodName).toLowerCase())
                && type != loopType) {

              sourceWriter.println("return findOrCreateMethod(\"" + superName + "\", methodName);");

            } else {
              // See if it's already been created and cached. If so, return that.
              String keyName = (keyRoot + "_" + methodName).toLowerCase();

              sourceWriter.println(
                  "GwtBindingMethod found = wrappedTypes.get(\"" + keyName + "\");");
              sourceWriter.println("if(found != null){");
              sourceWriter.indent();
              sourceWriter.println("return found;");
              sourceWriter.outdent();
              sourceWriter.println("} else {");
              sourceWriter.indent();

              // Not cached, create a new instance and put it in the cache.
              sourceWriter.println("newMethod = new GwtBindingMethod(){");

              sourceWriter.println(
                  "public Object invoke(Object obj, Object[] args) throws XulException { ");
              sourceWriter.indent();
              sourceWriter.println("try{");
              sourceWriter.println(
                  loopType.getQualifiedSourceName()
                      + " target = ("
                      + loopType.getQualifiedSourceName()
                      + ") obj;");

              JParameter[] params = m.getParameters();
              String argList = "";
              int pos = 0;
              for (JParameter param : params) {
                if (pos > 0) {
                  argList += ", ";
                }
                argList += "(" + getTypeName(param.getType()) + ") args[" + pos + "]";
                pos++;
              }

              if (isVoidReturn(m.getReturnType())) {
                sourceWriter.println("target." + methodName + "(" + argList + ");");
                sourceWriter.println("return null;");
              } else {
                sourceWriter.println(
                    "return " + boxReturnType(m) + " target." + methodName + "(" + argList + ");");
              }

              sourceWriter.println(
                  "}catch(Exception e){ e.printStackTrace(); throw new XulException(\"error with "
                      + type.getQualifiedSourceName()
                      + "\"+e.getMessage());}");
              sourceWriter.println("}");

              sourceWriter.outdent();
              sourceWriter.println("};");

              // Add it to the HashMap cache as type and decendant type if available.
              sourceWriter.println("wrappedTypes.put((\"" + keyName + "\"), newMethod);");
              if (keyRoot.equals(superName) == false) {
                sourceWriter.println("wrappedTypes.put((\"" + keyName + "\"), newMethod);");
              }
              generatedMethods.add((keyRoot + "_" + methodName).toLowerCase());
              generatedMethods.add((superName + "_" + methodName).toLowerCase());

              sourceWriter.println("return newMethod;");

              sourceWriter.outdent();
              sourceWriter.println("}");
            }
            sourceWriter.outdent();

            sourceWriter.println("}");
          }

          // go up a level in the heirarchy and check again.
          loopType = loopType.getSuperclass();
        }
        sourceWriter.outdent();
        sourceWriter.println("}");

      } catch (Exception e) {

        // record to logger that Map generation threw an exception
        logger.log(TreeLogger.ERROR, "PropertyMap ERROR!!!", e);
      }
    }

    sourceWriter.outdent();

    // This is the end of the line, if not found return null.
    sourceWriter.println("return null;");
    sourceWriter.println("}");
  }
  private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
      throws UnableToCompleteException {
    entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
    EntityProxyModel toReturn = peers.get(entityProxyType);
    if (toReturn == null) {
      EntityProxyModel.Builder inProgress = peerBuilders.get(entityProxyType);
      if (inProgress != null) {
        toReturn = inProgress.peek();
      }
    }
    if (toReturn == null) {
      EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
      peerBuilders.put(entityProxyType, builder);

      // Validate possible super-proxy types first
      for (JClassType supertype : entityProxyType.getFlattenedSupertypeHierarchy()) {
        List<EntityProxyModel> superTypes = new ArrayList<EntityProxyModel>();
        if (supertype != entityProxyType && shouldAttemptProxyValidation(supertype)) {
          superTypes.add(getEntityProxyType(supertype));
        }
        builder.setSuperProxyTypes(superTypes);
      }

      builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
      builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
      if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
        builder.setType(Type.ENTITY);
      } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
        builder.setType(Type.VALUE);
      } else {
        poison(
            "The type %s is not assignable to either %s or %s",
            entityProxyInterface.getQualifiedSourceName(),
            valueProxyInterface.getQualifiedSourceName());
        // Cannot continue, since knowing the behavior is crucial
        die(poisonedMessage());
      }

      // Get the server domain object type
      ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
      ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
      JsonRpcProxy jsonRpcProxy = entityProxyType.getAnnotation(JsonRpcProxy.class);
      if (proxyFor == null && proxyForName == null && jsonRpcProxy == null) {
        poison(
            "The %s type does not have a @%s, @%s, or @%s annotation",
            entityProxyType.getQualifiedSourceName(),
            ProxyFor.class.getSimpleName(),
            ProxyForName.class.getSimpleName(),
            JsonRpcProxy.class.getSimpleName());
      }

      // Look at the methods declared on the EntityProxy
      List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
      Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>();
      for (JMethod method : entityProxyType.getInheritableMethods()) {
        if (method.getEnclosingType().equals(entityProxyInterface)) {
          // Ignore methods on EntityProxy
          continue;
        }
        RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
        methodBuilder.setDeclarationMethod(entityProxyType, method);

        JType transportedType;
        String name = method.getName();
        if (JBeanMethod.GET.matches(method)) {
          transportedType = method.getReturnType();
          String propertyName = JBeanMethod.GET.inferName(method);
          JMethod previouslySeen = duplicatePropertyGetters.get(propertyName);
          if (previouslySeen == null) {
            duplicatePropertyGetters.put(propertyName, method);
          } else {
            poison(
                "Duplicate accessors for property %s: %s() and %s()",
                propertyName, previouslySeen.getName(), method.getName());
          }

        } else if (JBeanMethod.SET.matches(method) || JBeanMethod.SET_BUILDER.matches(method)) {
          transportedType = method.getParameters()[0].getType();

        } else if (name.equals("stableId") && method.getParameters().length == 0) {
          // Ignore any overload of stableId
          continue;
        } else {
          poison("The method %s is neither a getter nor a setter", method.getReadableDeclaration());
          continue;
        }
        validateTransportableType(methodBuilder, transportedType, false);
        RequestMethod requestMethod = methodBuilder.build();
        requestMethods.add(requestMethod);
      }
      builder
          .setExtraTypes(checkExtraTypes(entityProxyType, false))
          .setRequestMethods(requestMethods);

      toReturn = builder.build();
      peers.put(entityProxyType, toReturn);
      peerBuilders.remove(entityProxyType);
    }
    return toReturn;
  }
  @Override
  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
      throws UnableToCompleteException {
    // make sure it is an interface
    TypeOracle oracle = context.getTypeOracle();

    propertyAccessInterface = oracle.findType(Name.getSourceNameForClass(PropertyAccess.class));
    modelKeyProviderInterface = oracle.findType(Name.getSourceNameForClass(ModelKeyProvider.class));
    valueProviderInterface = oracle.findType(Name.getSourceNameForClass(ValueProvider.class));
    labelProviderInterface = oracle.findType(Name.getSourceNameForClass(LabelProvider.class));
    JClassType toGenerate = oracle.findType(typeName).isInterface();
    if (toGenerate == null) {
      logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
      throw new UnableToCompleteException();
    }
    if (!toGenerate.isAssignableTo(propertyAccessInterface)) {
      logger.log(Type.ERROR, "This isn't a PropertyAccess subtype...");
      throw new UnableToCompleteException();
    }

    // Get the name of the new type
    String packageName = toGenerate.getPackage().getName();
    String simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
    PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
    if (pw == null) {
      return packageName + "." + simpleSourceName;
    }

    // start making the class, with basic imports
    ClassSourceFileComposerFactory factory =
        new ClassSourceFileComposerFactory(packageName, simpleSourceName);
    factory.addImplementedInterface(typeName);
    SourceWriter sw = factory.createSourceWriter(context, pw);

    // for each method,
    for (JMethod m : toGenerate.getOverridableMethods()) {
      TreeLogger l = logger.branch(Type.DEBUG, "Building method: " + m.getReadableDeclaration());

      // no support for params at this time
      if (m.getParameters().length != 0) {
        l.log(Type.ERROR, "Method " + m.toString() + " must not have parameters.");
        throw new UnableToCompleteException();
      }

      // ask for the types that provide the property data
      JClassType ret = m.getReturnType().isClassOrInterface();
      final AbstractCreator c;
      if (ret.isAssignableTo(valueProviderInterface)) {
        c = new ValueProviderCreator(context, l, m);
      } else if (ret.isAssignableTo(modelKeyProviderInterface)) {
        c = new ModelKeyProviderCreator(context, l, m);
      } else if (ret.isAssignableTo(labelProviderInterface)) {
        c = new LabelProviderCreator(context, l, m);
      } else {
        logger.log(Type.ERROR, "Method uses a return type that cannot be generated");
        throw new UnableToCompleteException();
      }
      c.create();
      // build the method
      // public ValueProvider<T, V> name() { return NameValueProvider.instance;
      // }
      sw.println("public %1$s %2$s() {", m.getReturnType().getQualifiedSourceName(), m.getName());
      sw.indentln("return %1$s;", c.getInstanceExpression());
      sw.println("}");
    }

    sw.commit(logger);

    return factory.getCreatedClassName();
  }
  /**
   * Write an entry in the methodMapNative for one type.
   *
   * @param type type to generate entry for
   */
  private void writeTypeMethodsNative(JType type) {
    srcWriter.indent();
    String serializerName = SerializationUtils.getFieldSerializerName(typeOracle, type);
    JClassType customSerializer =
        SerializableTypeOracleBuilder.findCustomFieldSerializer(typeOracle, type);

    // First the initialization method
    if (deserializationOracle.maybeInstantiated(type)) {
      srcWriter.print("@");
      if (customSerializer != null) {
        if (hasInstantiateMethod(customSerializer, type)) {
          srcWriter.print(serializerName);
        } else {
          srcWriter.print(SerializationUtils.getStandardSerializerName((JClassType) type));
        }
      } else {
        srcWriter.print(serializerName);
      }
      srcWriter.print("::instantiate");
      srcWriter.print("(L" + SerializationStreamReader.class.getName().replace('.', '/') + ";)");
    }
    srcWriter.println(",");

    // Now the deserialization method
    if (deserializationOracle.isSerializable(type)) {
      // Assume param type is the concrete type of the serialized type.
      JType paramType = type;
      if (customSerializer != null) {
        // But a custom serializer may specify a looser type.
        JMethod deserializationMethod =
            CustomFieldSerializerValidator.getDeserializationMethod(
                customSerializer, (JClassType) type);
        paramType = deserializationMethod.getParameters()[1].getType();
      }
      srcWriter.print("@" + serializerName);
      srcWriter.print(
          "::deserialize(L"
              + SerializationStreamReader.class.getName().replace('.', '/')
              + ";"
              + paramType.getJNISignature()
              + ")");
    }
    srcWriter.println(",");

    // Now the serialization method
    if (serializationOracle.isSerializable(type)) {
      // Assume param type is the concrete type of the serialized type.
      JType paramType = type;
      if (customSerializer != null) {
        // But a custom serializer may specify a looser type.
        JMethod serializationMethod =
            CustomFieldSerializerValidator.getSerializationMethod(
                customSerializer, (JClassType) type);
        paramType = serializationMethod.getParameters()[1].getType();
      }
      srcWriter.print("@" + serializerName);
      srcWriter.print(
          "::serialize(L"
              + SerializationStreamWriter.class.getName().replace('.', '/')
              + ";"
              + paramType.getJNISignature()
              + ")");
      srcWriter.println();
    }
    srcWriter.outdent();
  }