/** * 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; }
@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()); }
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()); }