boolean addListener(
     int id, ListenerClass listener, ListenerMethod method, ListenerBinding binding) {
   ViewInjection viewInjection = getOrCreateViewInjection(id);
   if (viewInjection.hasListenerBinding(listener, method)) {
     return false;
   }
   viewInjection.addListenerBinding(listener, method, binding);
   return true;
 }
 private void emitReset(StringBuilder builder) {
   builder.append("  public static void reset(").append(targetClass).append(" target) {\n");
   if (parentInjector != null) {
     builder.append("    ").append(parentInjector).append(".reset(target);\n\n");
   }
   for (ViewInjection injection : viewIdMap.values()) {
     for (ViewBinding viewBinding : injection.getViewBindings()) {
       builder.append("    target.").append(viewBinding.getName()).append(" = null;\n");
     }
   }
   for (CollectionBinding collectionBinding : collectionBindings.keySet()) {
     builder.append("    target.").append(collectionBinding.getName()).append(" = null;\n");
   }
   builder.append("  }\n");
 }
  private void emitViewInjection(StringBuilder builder, ViewInjection injection) {
    builder.append("    view = ");

    List<Binding> requiredBindings = injection.getRequiredBindings();
    if (requiredBindings.isEmpty()) {
      builder.append("finder.findOptionalView(source, ").append(injection.getId()).append(");\n");
    } else {
      builder.append("finder.findRequiredView(source, ").append(injection.getId()).append(", \"");
      emitHumanDescription(builder, requiredBindings);
      builder.append("\");\n");
    }

    emitViewBindings(builder, injection);
    emitListenerBindings(builder, injection);
  }
  private void emitViewBindings(StringBuilder builder, ViewInjection injection) {
    Collection<ViewBinding> viewBindings = injection.getViewBindings();
    if (viewBindings.isEmpty()) {
      return;
    }

    for (ViewBinding viewBinding : viewBindings) {
      builder.append("    target.").append(viewBinding.getName()).append(" = ");
      emitCastIfNeeded(builder, viewBinding.getType());
      builder.append("view;\n");
    }
  }
  private void parseInjectView(
      Element element,
      Map<TypeElement, ViewInjector> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(
          element,
          "@InjectView fields must extend from View or be an interface. (%s.%s)",
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify common generated code restrictions.
    hasError |= isInaccessibleViaGeneratedCode(InjectView.class, "fields", element);
    hasError |= isBindingInWrongPackage(InjectView.class, element);

    // Check for the other field annotation.
    if (element.getAnnotation(InjectViews.class) != null) {
      error(
          element,
          "Only one of @InjectView and @InjectViews is allowed. (%s.%s)",
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    // Assemble information on the injection point.
    int id = element.getAnnotation(InjectView.class).value();

    ViewInjector injector = targetClassMap.get(enclosingElement);
    if (injector != null) {
      ViewInjection viewInjection = injector.getViewInjection(id);
      if (viewInjection != null) {
        Iterator<ViewBinding> iterator = viewInjection.getViewBindings().iterator();
        if (iterator.hasNext()) {
          ViewBinding existingBinding = iterator.next();
          error(
              element,
              "Attempt to use @InjectView for an already injected ID %d on '%s'. (%s.%s)",
              id,
              existingBinding.getName(),
              enclosingElement.getQualifiedName(),
              element.getSimpleName());
          return;
        }
      }
    }

    String name = element.getSimpleName().toString();
    String type = elementType.toString();
    boolean required = element.getAnnotation(Optional.class) == null;

    ViewInjector viewInjector = getOrCreateTargetClass(targetClassMap, enclosingElement);
    ViewBinding binding = new ViewBinding(name, type, required);
    viewInjector.addView(id, binding);

    // Add the type-erased version to the valid injection targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
  private void emitListenerBindings(StringBuilder builder, ViewInjection injection) {
    Map<ListenerClass, Map<ListenerMethod, ListenerBinding>> bindings =
        injection.getListenerBindings();
    if (bindings.isEmpty()) {
      return;
    }

    String extraIndent = "";

    // We only need to emit the null check if there are zero required bindings.
    boolean needsNullChecked = injection.getRequiredBindings().isEmpty();
    if (needsNullChecked) {
      builder.append("    if (view != null) {\n");
      extraIndent = "  ";
    }

    for (Map.Entry<ListenerClass, Map<ListenerMethod, ListenerBinding>> e : bindings.entrySet()) {
      ListenerClass listener = e.getKey();
      Map<ListenerMethod, ListenerBinding> methodBindings = e.getValue();

      // Emit: ((OWNER_TYPE) view).SETTER_NAME(
      boolean needsCast = !VIEW_TYPE.equals(listener.targetType());
      builder.append(extraIndent).append("    ");
      if (needsCast) {
        builder.append("((").append(listener.targetType());
        if (listener.genericArguments() > 0) {
          builder.append('<');
          for (int i = 0; i < listener.genericArguments(); i++) {
            if (i > 0) {
              builder.append(", ");
            }
            builder.append('?');
          }
          builder.append('>');
        }
        builder.append(") ");
      }
      builder.append("view");
      if (needsCast) {
        builder.append(')');
      }
      builder.append('.').append(listener.setter()).append("(\n");

      // Emit: new TYPE() {
      builder.append(extraIndent).append("      new ").append(listener.type()).append("() {\n");

      for (ListenerMethod method : getListenerMethods(listener)) {
        // Emit: @Override public RETURN_TYPE METHOD_NAME(
        builder
            .append(extraIndent)
            .append("        @Override public ")
            .append(method.returnType())
            .append(' ')
            .append(method.name())
            .append("(\n");

        // Emit listener method arguments, each on their own line.
        String[] parameterTypes = method.parameters();
        for (int i = 0, count = parameterTypes.length; i < count; i++) {
          builder
              .append(extraIndent)
              .append("          ")
              .append(parameterTypes[i])
              .append(" p")
              .append(i);
          if (i < count - 1) {
            builder.append(',');
          }
          builder.append('\n');
        }

        // Emit end of parameters, start of body.
        builder.append(extraIndent).append("        ) {\n");

        // Set up the return statement, if needed.
        builder.append(extraIndent).append("          ");
        boolean hasReturnType = !"void".equals(method.returnType());
        if (hasReturnType) {
          builder.append("return ");
        }

        if (methodBindings.containsKey(method)) {
          ListenerBinding binding = methodBindings.get(method);
          builder.append("target.").append(binding.getName()).append('(');
          List<Parameter> parameters = binding.getParameters();
          String[] listenerParameters = method.parameters();
          for (int i = 0, count = parameters.size(); i < count; i++) {
            Parameter parameter = parameters.get(i);
            int listenerPosition = parameter.getListenerPosition();
            emitCastIfNeeded(builder, listenerParameters[listenerPosition], parameter.getType());
            builder.append('p').append(listenerPosition);
            if (i < count - 1) {
              builder.append(", ");
            }
          }
          builder.append(");");
        } else if (hasReturnType) {
          builder.append(method.defaultReturn()).append(';');
        }
        builder.append('\n');

        // Emit end of listener method.
        builder.append(extraIndent).append("        }\n");
      }

      // Emit end of listener class body and close the setter method call.
      builder.append(extraIndent).append("      });\n");
    }

    if (needsNullChecked) {
      builder.append("    }\n");
    }
  }