@Override
  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
      throws UnableToCompleteException {
    TypeOracle oracle = context.getTypeOracle();

    // JClassType requestQueue = oracle.findType(RequestQueue.class.getName());
    JClassType toGenerate = oracle.findType(typeName);

    if (toGenerate == null) {
      logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
      throw new UnableToCompleteException();
    }

    String packageName = toGenerate.getPackage().getName();
    String simpleSourceName = toGenerate.getName().replace('.', '_') + "_Impl";
    PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
    if (pw == null) {
      return packageName + "." + simpleSourceName;
    }

    ClassSourceFileComposerFactory factory =
        new ClassSourceFileComposerFactory(packageName, simpleSourceName);
    factory.setSuperclass(AbstractRequestQueueImpl.class.getName());
    factory.addImplementedInterface(typeName);
    SourceWriter sw = factory.createSourceWriter(context, pw);

    // Collect async services we need to provide access to, and the rpc calls they'll make
    RequestQueueModel model =
        collectModel(logger.branch(Type.DEBUG, "Collecting service info"), context, toGenerate);

    // Build a pair of RPC interfaces for serialization
    String realRpcInterfaceName =
        buildRpcInterfaces(logger.branch(Type.DEBUG, "Writing RPC interfaces"), context, model);

    // Build the getRealService() method
    String serviceQueueAsync = ServiceQueueBaseAsync.class.getName();
    sw.println("public %1$s getRealService() {", serviceQueueAsync);
    sw.indentln(
        "return %3$s.<%1$s>create(%2$s.class);",
        serviceQueueAsync, realRpcInterfaceName, GWT.class.getName());
    sw.println("}");

    // Build the methods (and maybe types?) that call addRequest()
    for (AsyncServiceModel service : model.getServices()) {
      sw.println(
          "public %1$s %2$s() {",
          service.getAsyncServiceInterfaceName(), service.getDeclaredMethodName());
      sw.indent();

      sw.println("return new %1$s() {", service.getAsyncServiceInterfaceName());
      sw.indent();

      for (AsyncServiceMethodModel method : service.getMethods()) {
        sw.println("public void %1$s(", method.getMethodName());
        StringBuilder argList = new StringBuilder();
        StringBuilder types = new StringBuilder("new String[]{");
        for (int i = 0; i < method.getArgTypes().size(); i++) {
          if (i != 0) {
            sw.print(", ");
            argList.append(", ");
            types.append(", ");
          }
          JType arg = method.getArgTypes().get(i);

          sw.print("%1$s arg%2$d", arg.getParameterizedQualifiedSourceName(), i);
          argList.append("arg").append(i);
          types.append("\"").append(escape(SerializationUtils.getRpcTypeName(arg))).append("\"");
        }
        types.append("}");
        if (method.hasCallback()) {
          if (method.getArgTypes().size() != 0) {
            sw.print(", ");
          }
          sw.print(
              "%1$s<%2$s> callback", AsyncCallback.class.getName(), method.getReturnTypeName());
        }
        sw.println(") {");
        sw.indent();

        sw.println(
            "addRequest(\"%1$s\", \"%2$s\",\n%3$s,\n",
            escape(service.getServiceName()), escape(method.getMethodName()), types.toString());
        if (method.hasCallback()) {
          sw.indentln("callback,");
        } else {
          sw.indentln("null,");
        }
        sw.indentln("new Object[]{%1$s});", argList.toString());

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

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

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

    sw.commit(logger);

    return factory.getCreatedClassName();
  }
  private String buildRpcInterfaces(
      TreeLogger logger, GeneratorContext context, RequestQueueModel model) {
    String rqType = model.getRequestQueueInterfaceName();
    int lastDot = rqType.lastIndexOf('.');
    String packageName = rqType.substring(0, lastDot - 1);
    String serviceSourceName = rqType.substring(lastDot).replace('.', '_') + "_ImplRPC";
    String asyncSourceName = serviceSourceName + "Async";
    PrintWriter pw = context.tryCreate(logger, packageName, asyncSourceName);
    if (pw == null) {
      return packageName + "." + serviceSourceName;
    }

    // Create async class
    ClassSourceFileComposerFactory factory =
        new ClassSourceFileComposerFactory(packageName, asyncSourceName);
    factory.addImplementedInterface(ServiceQueueBaseAsync.class.getName());
    factory.makeInterface();
    SourceWriter asyncSw = factory.createSourceWriter(context, pw);

    // Create service class
    pw = context.tryCreate(logger, packageName, serviceSourceName);
    assert pw != null;
    factory = new ClassSourceFileComposerFactory(packageName, serviceSourceName);
    factory.addImplementedInterface(ServiceQueueBase.class.getName());
    factory.makeInterface();
    SourceWriter serviceSw = factory.createSourceWriter(context, pw);

    // write out service and async
    int i = 0;
    for (AsyncServiceModel service : model.getServices()) {
      for (AsyncServiceMethodModel method : service.getMethods()) {
        String methodName = "a" + ++i;
        asyncSw.println("void %1$s(", methodName);
        serviceSw.println("%2$s %1$s(", methodName, method.getReturnTypeName());
        asyncSw.indent();
        serviceSw.indent();

        boolean firstArgument = true;
        int argIndex = 0;
        for (JType arg : method.getArgTypes()) {
          if (!firstArgument) {
            asyncSw.println(",");
            serviceSw.println(",");
          }
          firstArgument = false;

          if (arg.isPrimitive() != null) {
            JPrimitiveType t = arg.isPrimitive();
            asyncSw.println("%2$s arg%1$d", argIndex, t.getQualifiedBoxedSourceName());
            serviceSw.println("%2$s arg%1$d", argIndex, t.getQualifiedBoxedSourceName());
          } else {
            asyncSw.println("%2$s arg%1$d", argIndex, arg.getParameterizedQualifiedSourceName());
            serviceSw.println("%2$s arg%1$d", argIndex, arg.getParameterizedQualifiedSourceName());
          }

          argIndex++;
        }
        if (!firstArgument) {
          asyncSw.print(", ");
        }
        // TODO return type boxing
        asyncSw.println(
            "%1$s<%2$s>callback);", AsyncCallback.class.getName(), method.getReturnTypeName());
        serviceSw.print(")");
        if (!method.getThrowables().isEmpty()) {
          serviceSw.print(" throws ");
          boolean firstThrowable = true;
          for (JClassType throwable : method.getThrowables()) {
            if (!firstThrowable) {
              serviceSw.print(", ");
            }
            firstThrowable = false;
            serviceSw.print(throwable.getQualifiedSourceName());
          }
        }
        serviceSw.println(";");
        asyncSw.outdent();
        serviceSw.outdent();
      }
    }

    asyncSw.commit(logger);
    serviceSw.commit(logger);

    return factory.getCreatedClassName();
  }