@Override
  public void writeFieldReadStatement(
      VariableElement field,
      Collection<ExecutableElement> postCreateChildMethods,
      JavaWriter writer)
      throws IOException {
    DeclaredType type = (DeclaredType) field.asType();
    TypeMirror itemType = type.getTypeArguments().get(0);
    TypeMirror itemTypeErasure = processingEnv.getTypeUtils().erasure(itemType);

    String collectionInitializer;
    try {
      collectionInitializer = initializers.findCollectionInitializer(type);
    } catch (InvalidTypeException e) {
      processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), field);
      collectionInitializer = "null";
    }
    writer.beginControlFlow("if (bundle.containsKey(\"%s\"))", field.getSimpleName());
    writer.emitStatement("object.%s = %s", field.getSimpleName(), collectionInitializer);
    writer.emitStatement(
        "%1$s.readCollectionFromBundle(object.%2$s, bundle, %3$s.class, \"%2$s\")",
        CollectionBundler.class.getCanonicalName(), field.getSimpleName(), itemTypeErasure);

    writePostCreateChildMethodCalls(field, itemType, postCreateChildMethods, writer);
    writer.endControlFlow();
  }
  @Override
  public void writeFieldWriteStatement(VariableElement field, JavaWriter writer)
      throws IOException {
    DeclaredType type = (DeclaredType) field.asType();
    TypeMirror itemType = type.getTypeArguments().get(0);
    TypeMirror itemTypeErasure = processingEnv.getTypeUtils().erasure(itemType);

    writer.beginControlFlow("if (object.%s != null)", field.getSimpleName());
    writer.emitStatement(
        "%1$s.writeCollectionToBundle(object.%2$s, bundle, %3$s.class, \"%2$s\")",
        CollectionBundler.class.getCanonicalName(), field.getSimpleName(), itemTypeErasure);
    writer.endControlFlow();
  }
  private void writePostCreateChildMethodCalls(
      VariableElement field,
      TypeMirror itemType,
      Collection<ExecutableElement> postCreateChildMethods,
      JavaWriter writer)
      throws IOException {

    if (!postCreateChildMethods.isEmpty() && metaTypes.isSubtype(itemType, Names.PARCELABLE)) {
      writer.beginControlFlow("for (Object child : object.%s)", field.getSimpleName());
      for (ExecutableElement method : postCreateChildMethods) {
        writer.emitStatement("object.%s(child)", method.getSimpleName());
      }
      writer.endControlFlow();
    }
  }
 public static void emitFillRealmObjectFromStream(
     String setter,
     String fieldName,
     String fieldTypeCanonicalName,
     String proxyClass,
     JavaWriter writer)
     throws IOException {
   writer
       .beginControlFlow("if (reader.peek() == JsonToken.NULL)")
       .emitStatement("reader.skipValue()")
       .emitStatement("obj.%s(null)", setter)
       .nextControlFlow("else")
       .emitStatement(
           "%s %sObj = %s.createUsingJsonStream(realm, reader)",
           fieldTypeCanonicalName, fieldName, proxyClass)
       .emitStatement("obj.%s(%sObj)", setter, fieldName)
       .endControlFlow();
 }
 public static void emitFillRealmObjectWithJsonValue(
     String setter,
     String fieldName,
     String qualifiedFieldType,
     String proxyClass,
     JavaWriter writer)
     throws IOException {
   writer
       .beginControlFlow("if (json.has(\"%s\"))", fieldName)
       .beginControlFlow("if (json.isNull(\"%s\"))", fieldName)
       .emitStatement("obj.%s(null)", setter)
       .nextControlFlow("else")
       .emitStatement(
           "%s %sObj = %s.createOrUpdateUsingJsonObject(realm, json.getJSONObject(\"%s\"), update)",
           qualifiedFieldType, fieldName, proxyClass, fieldName)
       .emitStatement("obj.%s(%sObj)", setter, fieldName)
       .endControlFlow()
       .endControlFlow();
 }
 @Override
 public void emitStreamTypeConversion(
     String setter, String fieldName, String fieldType, JavaWriter writer) throws IOException {
   String statementSetNullOrThrow;
   if (Utils.isPrimitiveType(fieldType)) {
     // Only throw exception for primitive types. For boxed types and String, exception will be
     // thrown in
     // the setter.
     statementSetNullOrThrow =
         String.format(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName);
   } else {
     statementSetNullOrThrow = String.format("obj.%s(null)", setter);
   }
   writer
       .beginControlFlow("if (reader.peek() == JsonToken.NULL)")
       .emitStatement("reader.skipValue()")
       .emitStatement(statementSetNullOrThrow)
       .nextControlFlow("else")
       .emitStatement("obj.%s((%s) reader.next%s())", setter, castType, jsonType)
       .endControlFlow();
 }
 public static void emitFillRealmListFromStream(
     String getter,
     String setter,
     String fieldTypeCanonicalName,
     String proxyClass,
     JavaWriter writer)
     throws IOException {
   writer
       .beginControlFlow("if (reader.peek() == JsonToken.NULL)")
       .emitStatement("reader.skipValue()")
       .emitStatement("obj.%s(null)", setter)
       .nextControlFlow("else")
       .emitStatement("reader.beginArray()")
       .beginControlFlow("while (reader.hasNext())")
       .emitStatement(
           "%s item = %s.createUsingJsonStream(realm, reader)", fieldTypeCanonicalName, proxyClass)
       .emitStatement("obj.%s().add(item)", getter)
       .endControlFlow()
       .emitStatement("reader.endArray()")
       .endControlFlow();
 }
 public static void emitFillRealmListWithJsonValue(
     String getter,
     String setter,
     String fieldName,
     String fieldTypeCanonicalName,
     String proxyClass,
     JavaWriter writer)
     throws IOException {
   writer
       .beginControlFlow("if (json.has(\"%s\"))", fieldName)
       .beginControlFlow("if (json.isNull(\"%s\"))", fieldName)
       .emitStatement("obj.%s(null)", setter)
       .nextControlFlow("else")
       .emitStatement("obj.%s().clear()", getter)
       .emitStatement("JSONArray array = json.getJSONArray(\"%s\")", fieldName)
       .beginControlFlow("for (int i = 0; i < array.length(); i++)")
       .emitStatement(
           "%s item = %s.createOrUpdateUsingJsonObject(realm, array.getJSONObject(i), update)",
           fieldTypeCanonicalName, proxyClass, fieldTypeCanonicalName)
       .emitStatement("obj.%s().add(item)", getter)
       .endControlFlow()
       .endControlFlow()
       .endControlFlow();
 }
  @SuppressWarnings({"unchecked", "ConstantConditions"})
  private void manageType(TypeElement enclosingElement, Logger logger) {

    // Make sure we don't process twice the same type
    String simpleName = enclosingElement.getSimpleName().toString();
    String qualifiedName = enclosingElement.getQualifiedName().toString();
    String packageName = Utils.getElementPackageName(enclosingElement);
    if (managedTypes.contains(qualifiedName)) return;
    managedTypes.add(qualifiedName);

    // Prepare the output file
    try {
      JavaFileObject classFile =
          processingEnv.getFiler().createSourceFile(qualifiedName + INJECTOR_SUFFIX);
      logger.note("Writing " + classFile.toUri().getRawPath());
      Writer out = classFile.openWriter();
      JavaWriter writer = new JavaWriter(out);

      writer.emitPackage(packageName);

      // Initial imports
      writer
          .emitImports(AsyncService.class, Injector.class, Message.class, Set.class, HashSet.class)
          .emitImports(
              "com.joanzapata.android.asyncservice.api.annotation.OnMessage.Priority",
              "android.os.Handler",
              "android.os.Looper")
          .emitStaticImports(
              "com.joanzapata.android.asyncservice.api.annotation.OnMessage.Priority.*");

      // Generates "public final class XXXInjector extends Injector<XXX>"
      writer
          .emitEmptyLine()
          .beginType(
              simpleName + INJECTOR_SUFFIX,
              "class",
              of(PUBLIC, FINAL),
              "Injector<" + simpleName + ">");

      // Generate a handler to execute runnables on the UI Thread
      writer
          .emitEmptyLine()
          .emitField(
              "Handler", "__handler", of(PRIVATE, FINAL), "new Handler(Looper.getMainLooper())");

      // Keep trace of when a method has received data which is not from cache
      writer
          .emitEmptyLine()
          .emitField(
              "Set<String>",
              "__receivedFinalResponses",
              of(PRIVATE, FINAL),
              "new HashSet<String>()");

      // Generates "protected void inject(XXX target) { ..."
      writer
          .emitEmptyLine()
          .emitAnnotation(Override.class)
          .beginMethod("void", "inject", of(PROTECTED), simpleName, "target");

      // Here, inject all services
      List<Element> elementsAnnotatedWith =
          findElementsAnnotatedWith(enclosingElement, InjectService.class);
      for (Element element : elementsAnnotatedWith) {
        if (isPublicOrPackagePrivate(element)) {
          writer.emitStatement(
              "target.%s = new %s(target)",
              element.getSimpleName(),
              element.asType().toString() + AsyncServiceAP.GENERATED_CLASS_SUFFIX);
        }
      }

      // End of inject()
      writer.endMethod().emitEmptyLine();

      // Generates "protected void dispatch(XXX target, Message event)"
      writer
          .emitAnnotation(Override.class)
          .beginMethod(
              "void",
              "dispatch",
              of(PROTECTED),
              "final " + simpleName,
              "target",
              "final " + Message.class.getSimpleName(),
              "event",
              Priority.class.getSimpleName(),
              "priority");

      // Once the user has received a "remote" result, make sure no cache is sent anymore
      writer
          .emitField(
              "boolean",
              "__hasBeenReceivedAlready",
              of(FINAL),
              "event.getQuery() != null && __receivedFinalResponses.contains(event.getQuery())")
          .emitStatement("if (event.isCached() && __hasBeenReceivedAlready) return")
          .emitStatement(
              "if (!__hasBeenReceivedAlready && !event.isCached() && priority == LAST) __receivedFinalResponses.add(event.getQuery())");

      // Here, dispatch events to methods
      List<Element> responseReceivers =
          findElementsAnnotatedWith(enclosingElement, OnMessage.class);
      for (Element responseReceiver : responseReceivers) {
        ExecutableElement annotatedMethod = (ExecutableElement) responseReceiver;
        AnnotationMirror annotationMirror = getAnnotation(annotatedMethod, OnMessage.class);
        List<? extends VariableElement> parameters = annotatedMethod.getParameters();

        if (parameters.size() > 1)
          logger.error(
              responseReceiver, "@OnMessage annotated methods can't have more than 1 argument");

        // Define event type given parameter or @InjectResponse value
        List<String> eventTypes;
        boolean hasArg = parameters.size() == 1;
        if (hasArg) {
          TypeMirror typeMirror = parameters.get(0).asType();
          eventTypes = asList(typeMirror.toString());
          if (hasTypeParameters(processingEnv, typeMirror))
            logger.error(
                parameters.get(0),
                "You can't receive typed parameters in @OnMessage annotated methods");

        } else {
          List<AnnotationValue> parameterTypeClasses =
              getAnnotationValue(annotationMirror, "value");

          // Validate each parameter type given in the annotation
          eventTypes = new ArrayList<String>();
          for (AnnotationValue value : parameterTypeClasses) {
            DeclaredType parameterTypeClass = (DeclaredType) value.getValue();
            if (parameterTypeClass == null)
              logger.error(
                  annotatedMethod, "Either declare an argument or give @OnMessage a value.");
            if (hasTypeParameters(processingEnv, parameterTypeClass))
              logger.error(
                  annotatedMethod,
                  annotationMirror,
                  "value",
                  "You can't receive typed parameters in @OnMessage method");
            eventTypes.add(parameterTypeClass.toString());
          }
        }

        // Define whether we should check emitter or not dependeing on the annotation value
        VariableElement from = getAnnotationValue(annotationMirror, "from");
        boolean checkEmitter = !ALL.toString().equals("" + from);

        // Check the priority of the method
        VariableElement priorityValue = getAnnotationValue(annotationMirror, "priority");
        Priority priority = !LAST.toString().equals("" + priorityValue) ? FIRST : LAST;
        writer.beginControlFlow("if (priority == %s)", priority);

        // Write the code to call the user method
        if (checkEmitter) writer.beginControlFlow("if (event.getEmitter() == getTarget())");

        // Create a new inner class for the Runnable to run on UI thread
        StringWriter buffer = new StringWriter();
        JavaWriter inner = new JavaWriter(buffer);
        inner
            .emitPackage("")
            .beginType("Runnable()", "new")
            .emitAnnotation("Override")
            .beginMethod("void", "run", of(PUBLIC));
        if (hasArg)
          inner.emitStatement(
              "target.%s((%s) event.getPayload())",
              annotatedMethod.getSimpleName(), eventTypes.get(0));
        else inner.emitStatement("target.%s()", annotatedMethod.getSimpleName());
        inner.endMethod().endType();

        // For each type (can be multiple)
        for (int i = 0; i < eventTypes.size(); i++) {
          String eventType = eventTypes.get(i);
          writer
              .beginControlFlow(
                  "%sif (event.getPayload() instanceof %s)", i != 0 ? "else " : "", eventType)
              .emitStatement("__handler.post(%s)", buffer.toString())
              .endControlFlow();
        }

        if (checkEmitter) writer.endControlFlow();
        writer.endControlFlow();
      }

      // End of inject();
      writer.endMethod().emitEmptyLine();

      // End of file
      writer.endType();
      out.flush();
      out.close();
    } catch (IOException e) {
      throw new IllegalStateException("Error while create the injector for " + qualifiedName, e);
    }
  }