Esempio n. 1
0
  /**
   * Establishes the type bound:
   *
   * <ol>
   *   <li>{@code<? extends Number>}, returns Number
   *   <li>{@code<? super Number>}, returns Number
   *   <li>{@code<?>}, returns Object
   *   <li>{@code<T extends Number>, returns Number}
   * </ol>
   *
   * @param typeMirror the type to return the bound for
   * @return the bound for this parameter
   */
  public TypeMirror getTypeBound(TypeMirror typeMirror) {
    if (typeMirror.getKind() == TypeKind.WILDCARD) {
      WildcardType wildCardType = (WildcardType) typeMirror;
      if (wildCardType.getExtendsBound() != null) {
        return wildCardType.getExtendsBound();
      }

      if (wildCardType.getSuperBound() != null) {
        return wildCardType.getSuperBound();
      }

      String wildCardName = wildCardType.toString();
      if ("?".equals(wildCardName)) {
        return elementUtils.getTypeElement(Object.class.getCanonicalName()).asType();
      }
    } else if (typeMirror.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariableType = (TypeVariable) typeMirror;
      if (typeVariableType.getUpperBound() != null) {
        return typeVariableType.getUpperBound();
      }
      // Lowerbounds intentionally left out: Type variables otherwise have a lower bound of
      // NullType.
    }

    return typeMirror;
  }
Esempio n. 2
0
 @Override
 public TypeElement visitTypeVariable(TypeVariable t, Void p) {
   if (t.getUpperBound() != null) {
     return visit(t.getUpperBound());
   } else {
     return visit(t.getLowerBound());
   }
 }
  private void parseBindExtra(
      Element element,
      Map<TypeElement, IntentBindingAdapterGenerator> targetClassMap,
      Set<String> erasedTargetNames)
      throws InvalidTypeException {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify that the target has all the appropriate information for type
    TypeMirror type = element.asType();
    if (type instanceof TypeVariable) {
      TypeVariable typeVariable = (TypeVariable) type;
      type = typeVariable.getUpperBound();
    }

    validateNotRequiredArguments(element);
    validateForCodeGeneration(BindExtra.class, element);
    validateBindingPackage(BindExtra.class, element);

    // Assemble information on the bind point
    String name = element.getSimpleName().toString();
    String intentType = typeUtil.getIntentType(type);
    KeySpec key = getKey(element);
    boolean required = element.getAnnotation(NotRequired.class) == null;
    boolean hasDefault = typeUtil.isPrimitive(type);
    boolean needsToBeCast = typeUtil.needToCastIntentType(type);

    IntentBindingAdapterGenerator intentBindingAdapterGenerator =
        getOrCreateTargetClass(targetClassMap, enclosingElement);
    IntentFieldBinding binding =
        new IntentFieldBinding(name, type, intentType, key, needsToBeCast, hasDefault, required);
    intentBindingAdapterGenerator.addField(binding);

    // Add the type-erased version to the valid targets set.
    erasedTargetNames.add(enclosingElement.toString());
  }
Esempio n. 4
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());
  }
  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());
  }
  private void parseInjectViews(
      Element element,
      Map<TypeElement, ViewInjector> 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;
    CollectionBinding.Kind kind = null;
    if (elementType.getKind() == TypeKind.ARRAY) {
      ArrayType arrayType = (ArrayType) elementType;
      viewType = arrayType.getComponentType();
      kind = CollectionBinding.Kind.ARRAY;
    } else if (LIST_TYPE.equals(erasedType)) {
      DeclaredType declaredType = (DeclaredType) elementType;
      List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
      if (typeArguments.size() != 1) {
        error(
            element,
            "@InjectViews List must have a generic component. (%s.%s)",
            enclosingElement.getQualifiedName(),
            element.getSimpleName());
        hasError = true;
      } else {
        viewType = typeArguments.get(0);
      }
      kind = CollectionBinding.Kind.LIST;
    } else {
      error(
          element,
          "@InjectViews must be a List or array. (%s.%s)",
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }
    if (viewType instanceof TypeVariable) {
      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,
          "@InjectViews type must extend from View or be an interface. (%s.%s)",
          enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

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

    if (hasError) {
      return;
    }

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

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

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

    ViewInjector viewInjector = getOrCreateTargetClass(targetClassMap, enclosingElement);
    CollectionBinding binding = new CollectionBinding(name, type, kind, required);
    viewInjector.addCollection(ids, binding);

    erasedTargetNames.add(enclosingElement.toString());
  }
  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());
  }