예제 #1
0
 private void parseOnChanged(
     Element element,
     Map<TypeElement, BindingClass> targetClassMap,
     Set<String> erasedTargetNames,
     Class annotationClass) {
   TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
   BindingClass bindingClass =
       getOrCreateTargetClass(targetClassMap, enclosingElement, false, false);
   TypeMirror mirror = element.asType();
   if (!(mirror.getKind() == TypeKind.EXECUTABLE)) return;
   String method = element.toString().trim();
   String methodName = method.substring(0, method.indexOf("("));
   Matcher m = Pattern.compile("\\(([^)]+)\\)").matcher(method);
   if (m.find()) {
     String[] methodTypes = m.group(1).split(",");
     String key = null;
     if (annotationClass.equals(OnKStringChanged.class)) {
       KStringBinding binding = new KStringBinding(methodName, methodTypes);
       key = element.getAnnotation(OnKStringChanged.class).value();
       bindingClass.putGeneric(key, binding);
     } else if (annotationClass.equals(OnKBooleanChanged.class)) {
       KBooleanBinding binding = new KBooleanBinding(methodName, methodTypes);
       key = element.getAnnotation(OnKBooleanChanged.class).value();
       bindingClass.putGeneric(key, binding);
     } else {
       error(element, "unknow annotation class type @%s", annotationClass.getSimpleName());
     }
   }
   erasedTargetNames.add(enclosingElement.toString());
 }
  @Override
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      try {
        JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
        Writer writer = jfo.openWriter();
        writer.write(bindingClass.brewJava());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        error(
            typeElement,
            "Unable to write view binder for type %s: %s",
            typeElement,
            e.getMessage());
      }
    }

    return true;
  }
예제 #3
0
 private void parseLBindLayout(
     Element element,
     Map<TypeElement, BindingClass> targetClassMap,
     Set<String> erasedTargetNames) {
   String id = element.getAnnotation(LBindLayout.class).value();
   BindingClass bindingClass =
       getOrCreateTargetClass(targetClassMap, (TypeElement) element, true, false);
   bindingClass.setLayoutId(id);
 }
예제 #4
0
 private void parseBind(
     Element element,
     Map<TypeElement, BindingClass> targetClassMap,
     Set<String> erasedTargetNames) {
   String data = element.getAnnotation(Bind.class).data();
   int id = element.getAnnotation(Bind.class).id();
   BindingClass bindingClass =
       getOrCreateTargetClass(targetClassMap, (TypeElement) element, false, false);
   bindingClass.addDoubleBinding(id, data);
 }
예제 #5
0
  private void parseBindText(
      Element element,
      Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    // Assemble information on the field.
    int[] ids = element.getAnnotation(BindText.class).value();
    BindingClass bindingClass =
        getOrCreateTargetClass(targetClassMap, enclosingElement, false, false);
    for (int id : ids) {
      if (bindingClass != null) {
        KBindings bindings = bindingClass.getKBindings(String.valueOf(id));
        if (bindings != null) {
          Iterator<FieldViewBinding> iterator = bindings.getFieldBindings().iterator();
          if (iterator.hasNext()) {
            FieldViewBinding existingBinding = iterator.next();
            error(
                element,
                "Attempt to use @%s for an already bound ID %s on '%s'. (%s.%s)",
                BindText.class.getSimpleName(),
                id,
                existingBinding.getName(),
                enclosingElement.getQualifiedName(),
                element.getSimpleName());
            return;
          }
        }
      } else {
        bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement, false, false);
      }
      String name = element.getSimpleName().toString();
      TypeName type = TypeName.get(elementType);
      boolean required = isRequiredBinding(element);

      FieldViewBinding binding = new FieldViewBinding(name, type, required);
      bindingClass.addField(String.valueOf(id), binding);
    }

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
예제 #6
0
 private void parseOnKStringUpdate(
     Element element,
     Map<TypeElement, BindingClass> targetClassMap,
     Set<String> erasedTargetNames) {
   TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
   BindingClass bindingClass =
       getOrCreateTargetClass(targetClassMap, enclosingElement, false, false);
   TypeMirror mirror = element.asType();
   if (!(mirror.getKind() == TypeKind.EXECUTABLE)) return;
   String method = element.toString().trim();
   String methodName = method.substring(0, method.indexOf("("));
   Matcher m = Pattern.compile("\\(([^)]+)\\)").matcher(method);
   if (m.find()) {
     String[] methodTypes = m.group(1).split(",");
     UpdateKStringBinding binding = new UpdateKStringBinding(methodName, methodTypes);
     String kstring = element.getAnnotation(OnKStringChanged.class).value();
     bindingClass.addKStringUpdateBinding(kstring, binding);
   }
   erasedTargetNames.add(enclosingElement.toString());
 }
  private void parseResourceDimen(
      Element element,
      Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type is int or ColorStateList.
    boolean isInt = false;
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.INT) {
      isInt = true;
    } else if (elementType.getKind() != TypeKind.FLOAT) {
      error(
          element,
          "@%s field type must be 'int' or 'float'. (%s.%s)",
          BindDimen.class.getSimpleName(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

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

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    String name = element.getSimpleName().toString();
    int id = element.getAnnotation(BindDimen.class).value();

    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    FieldResourceBinding binding =
        new FieldResourceBinding(id, name, isInt ? "getDimensionPixelSize" : "getDimension");
    bindingClass.addResource(binding);

    erasedTargetNames.add(enclosingElement.toString());
  }
예제 #8
0
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass BindingClass = entry.getValue();

      try {
        BindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(
            typeElement,
            "Unable to write view binder for type %s: %s",
            typeElement,
            e.getMessage());
      }
    }

    return true;
  }
  private void parseResourceString(
      Element element,
      Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target type is String.
    if (!"java.lang.String".equals(element.asType().toString())) {
      error(
          element,
          "@%s field type must be 'String'. (%s.%s)",
          BindString.class.getSimpleName(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

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

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    String name = element.getSimpleName().toString();
    int id = element.getAnnotation(BindString.class).value();

    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    FieldResourceBinding binding = new FieldResourceBinding(id, name, "getString");
    bindingClass.addResource(binding);

    erasedTargetNames.add(enclosingElement.toString());
  }
  private void parseListenerAnnotation(
      Class<? extends Annotation> annotationClass,
      Element element,
      Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames)
      throws Exception {
    // This should be guarded by the annotation's @Target but it's worth a check for safe casting.
    if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) {
      throw new IllegalStateException(
          String.format("@%s annotation must be on a method.", annotationClass.getSimpleName()));
    }

    ExecutableElement executableElement = (ExecutableElement) element;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Assemble information on the method.
    Annotation annotation = element.getAnnotation(annotationClass);
    Method annotationValue = annotationClass.getDeclaredMethod("value");
    if (annotationValue.getReturnType() != int[].class) {
      throw new IllegalStateException(
          String.format("@%s annotation value() type not int[].", annotationClass));
    }

    int[] ids = (int[]) annotationValue.invoke(annotation);
    String name = executableElement.getSimpleName().toString();
    boolean required = isRequiredBinding(element);

    // Verify that the method and its containing class are accessible via generated code.
    boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element);
    hasError |= isBindingInWrongPackage(annotationClass, element);

    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(
          element,
          "@%s annotation for method contains duplicate ID %d. (%s.%s)",
          annotationClass.getSimpleName(),
          duplicateId,
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class);
    if (listener == null) {
      throw new IllegalStateException(
          String.format(
              "No @%s defined on @%s.",
              ListenerClass.class.getSimpleName(), annotationClass.getSimpleName()));
    }

    for (int id : ids) {
      if (id == View.NO_ID) {
        if (ids.length == 1) {
          if (!required) {
            error(
                element,
                "ID-free binding must not be annotated with @Nullable. (%s.%s)",
                enclosingElement.getQualifiedName(),
                element.getSimpleName());
            hasError = true;
          }

          // Verify target type is valid for a binding without an id.
          String targetType = listener.targetType();
          if (!isSubtypeOfType(enclosingElement.asType(), targetType)
              && !isInterface(enclosingElement.asType())) {
            error(
                element,
                "@%s annotation without an ID may only be used with an object of type "
                    + "\"%s\" or an interface. (%s.%s)",
                annotationClass.getSimpleName(),
                targetType,
                enclosingElement.getQualifiedName(),
                element.getSimpleName());
            hasError = true;
          }
        } else {
          error(
              element,
              "@%s annotation contains invalid ID %d. (%s.%s)",
              annotationClass.getSimpleName(),
              id,
              enclosingElement.getQualifiedName(),
              element.getSimpleName());
          hasError = true;
        }
      }
    }

    ListenerMethod method;
    ListenerMethod[] methods = listener.method();
    if (methods.length > 1) {
      throw new IllegalStateException(
          String.format(
              "Multiple listener methods specified on @%s.", annotationClass.getSimpleName()));
    } else if (methods.length == 1) {
      if (listener.callbacks() != ListenerClass.NONE.class) {
        throw new IllegalStateException(
            String.format(
                "Both method() and callback() defined on @%s.", annotationClass.getSimpleName()));
      }
      method = methods[0];
    } else {
      Method annotationCallback = annotationClass.getDeclaredMethod("callback");
      Enum<?> callback = (Enum<?>) annotationCallback.invoke(annotation);
      Field callbackField = callback.getDeclaringClass().getField(callback.name());
      method = callbackField.getAnnotation(ListenerMethod.class);
      if (method == null) {
        throw new IllegalStateException(
            String.format(
                "No @%s defined on @%s's %s.%s.",
                ListenerMethod.class.getSimpleName(),
                annotationClass.getSimpleName(),
                callback.getDeclaringClass().getSimpleName(),
                callback.name()));
      }
    }

    // Verify that the method has equal to or less than the number of parameters as the listener.
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    if (methodParameters.size() > method.parameters().length) {
      error(
          element,
          "@%s methods can have at most %s parameter(s). (%s.%s)",
          annotationClass.getSimpleName(),
          method.parameters().length,
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify method return type matches the listener.
    TypeMirror returnType = executableElement.getReturnType();
    if (returnType instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) returnType;
      returnType = typeVariable.getUpperBound();
    }
    if (!returnType.toString().equals(method.returnType())) {
      error(
          element,
          "@%s methods must have a '%s' return type. (%s.%s)",
          annotationClass.getSimpleName(),
          method.returnType(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    Parameter[] parameters = Parameter.NONE;
    if (!methodParameters.isEmpty()) {
      parameters = new Parameter[methodParameters.size()];
      BitSet methodParameterUsed = new BitSet(methodParameters.size());
      String[] parameterTypes = method.parameters();
      for (int i = 0; i < methodParameters.size(); i++) {
        VariableElement methodParameter = methodParameters.get(i);
        TypeMirror methodParameterType = methodParameter.asType();
        if (methodParameterType instanceof TypeVariable) {
          TypeVariable typeVariable = (TypeVariable) methodParameterType;
          methodParameterType = typeVariable.getUpperBound();
        }

        for (int j = 0; j < parameterTypes.length; j++) {
          if (methodParameterUsed.get(j)) {
            continue;
          }
          if (isSubtypeOfType(methodParameterType, parameterTypes[j])
              || isInterface(methodParameterType)) {
            parameters[i] = new Parameter(j, methodParameterType.toString());
            methodParameterUsed.set(j);
            break;
          }
        }
        if (parameters[i] == null) {
          StringBuilder builder = new StringBuilder();
          builder
              .append("Unable to match @")
              .append(annotationClass.getSimpleName())
              .append(" method arguments. (")
              .append(enclosingElement.getQualifiedName())
              .append('.')
              .append(element.getSimpleName())
              .append(')');
          for (int j = 0; j < parameters.length; j++) {
            Parameter parameter = parameters[j];
            builder
                .append("\n\n  Parameter #")
                .append(j + 1)
                .append(": ")
                .append(methodParameters.get(j).asType().toString())
                .append("\n    ");
            if (parameter == null) {
              builder.append("did not match any listener parameters");
            } else {
              builder
                  .append("matched listener parameter #")
                  .append(parameter.getListenerPosition() + 1)
                  .append(": ")
                  .append(parameter.getType());
            }
          }
          builder
              .append("\n\nMethods may have up to ")
              .append(method.parameters().length)
              .append(" parameter(s):\n");
          for (String parameterType : method.parameters()) {
            builder.append("\n  ").append(parameterType);
          }
          builder.append(
              "\n\nThese may be listed in any order but will be searched for from top to bottom.");
          error(executableElement, builder.toString());
          return;
        }
      }
    }

    MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required);
    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    for (int id : ids) {
      if (!bindingClass.addMethod(id, listener, method, binding)) {
        error(
            element,
            "Multiple listener methods with return value specified for ID %d. (%s.%s)",
            id,
            enclosingElement.getQualifiedName(),
            element.getSimpleName());
        return;
      }
    }

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
  private void parseBindMany(
      Element element,
      Map<TypeElement, BindingClass> targetClassMap,
      Set<String> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the type is a List or an array.
    TypeMirror elementType = element.asType();
    String erasedType = doubleErasure(elementType);
    TypeMirror viewType = null;
    FieldCollectionViewBinding.Kind kind = null;
    if (elementType.getKind() == TypeKind.ARRAY) {
      ArrayType arrayType = (ArrayType) elementType;
      viewType = arrayType.getComponentType();
      kind = FieldCollectionViewBinding.Kind.ARRAY;
    } else if (LIST_TYPE.equals(erasedType)) {
      DeclaredType declaredType = (DeclaredType) elementType;
      List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
      if (typeArguments.size() != 1) {
        error(
            element,
            "@%s List must have a generic component. (%s.%s)",
            Bind.class.getSimpleName(),
            enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      } else {
        viewType = typeArguments.get(0);
      }
      kind = FieldCollectionViewBinding.Kind.LIST;
    } else {
      throw new AssertionError();
    }
    if (viewType != null && viewType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) viewType;
      viewType = typeVariable.getUpperBound();
    }

    // Verify that the target type extends from View.
    if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) {
      error(
          element,
          "@%s List or array type must extend from View or be an interface. (%s.%s)",
          Bind.class.getSimpleName(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    String name = element.getSimpleName().toString();
    int[] ids = element.getAnnotation(Bind.class).value();
    if (ids.length == 0) {
      error(
          element,
          "@%s must specify at least one ID. (%s.%s)",
          Bind.class.getSimpleName(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      return;
    }

    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
      error(
          element,
          "@%s annotation contains duplicate ID %d. (%s.%s)",
          Bind.class.getSimpleName(),
          duplicateId,
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
    }

    assert viewType != null; // Always false as hasError would have been true.
    String type = viewType.toString();
    boolean required = isRequiredBinding(element);

    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    FieldCollectionViewBinding binding = new FieldCollectionViewBinding(name, type, kind, required);
    bindingClass.addFieldCollection(ids, binding);

    erasedTargetNames.add(enclosingElement.toString());
  }
  private void parseBindOne(
      Element element,
      Map<TypeElement, BindingClass> 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.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      error(
          element,
          "@%s fields must extend from View or be an interface. (%s.%s)",
          Bind.class.getSimpleName(),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Assemble information on the field.
    int[] ids = element.getAnnotation(Bind.class).value();
    if (ids.length != 1) {
      error(
          element,
          "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
          Bind.class.getSimpleName(),
          Arrays.toString(ids),
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    int id = ids[0];
    BindingClass bindingClass = targetClassMap.get(enclosingElement);
    if (bindingClass != null) {
      ViewBindings viewBindings = bindingClass.getViewBinding(id);
      if (viewBindings != null) {
        Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
        if (iterator.hasNext()) {
          FieldViewBinding existingBinding = iterator.next();
          error(
              element,
              "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
              Bind.class.getSimpleName(),
              id,
              existingBinding.getName(),
              enclosingElement.getQualifiedName(),
              element.getSimpleName());
          return;
        }
      }
    } else {
      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    }

    String name = element.getSimpleName().toString();
    String type = elementType.toString();
    boolean required = isRequiredBinding(element);

    FieldViewBinding binding = new FieldViewBinding(name, type, required);
    bindingClass.addField(id, binding);

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }