Search in sources :

Example 1 with ListenerMethod

use of butterknife.internal.ListenerMethod in project butterknife by JakeWharton.

the class BindingSet method addMethodBindings.

private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding) {
    Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> classMethodBindings = binding.getMethodBindings();
    if (classMethodBindings.isEmpty()) {
        return;
    }
    // We only need to emit the null check if there are zero required bindings.
    boolean needsNullChecked = binding.getRequiredBindings().isEmpty();
    if (needsNullChecked) {
        result.beginControlFlow("if (view != null)");
    }
    // Add the view reference to the binding.
    String fieldName = "viewSource";
    String bindName = "source";
    if (!binding.isBoundToRoot()) {
        fieldName = "view" + binding.getId().value;
        bindName = "view";
    }
    result.addStatement("$L = $N", fieldName, bindName);
    for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> e : classMethodBindings.entrySet()) {
        ListenerClass listener = e.getKey();
        Map<ListenerMethod, Set<MethodViewBinding>> methodBindings = e.getValue();
        TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("").superclass(ClassName.bestGuess(listener.type()));
        for (ListenerMethod method : getListenerMethods(listener)) {
            MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()).addAnnotation(Override.class).addModifiers(PUBLIC).returns(bestGuess(method.returnType()));
            String[] parameterTypes = method.parameters();
            for (int i = 0, count = parameterTypes.length; i < count; i++) {
                callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i);
            }
            boolean hasReturnType = !"void".equals(method.returnType());
            CodeBlock.Builder builder = CodeBlock.builder();
            if (hasReturnType) {
                builder.add("return ");
            }
            if (methodBindings.containsKey(method)) {
                for (MethodViewBinding methodBinding : methodBindings.get(method)) {
                    builder.add("target.$L(", methodBinding.getName());
                    List<Parameter> parameters = methodBinding.getParameters();
                    String[] listenerParameters = method.parameters();
                    for (int i = 0, count = parameters.size(); i < count; i++) {
                        if (i > 0) {
                            builder.add(", ");
                        }
                        Parameter parameter = parameters.get(i);
                        int listenerPosition = parameter.getListenerPosition();
                        if (parameter.requiresCast(listenerParameters[listenerPosition])) {
                            builder.add("$T.<$T>castParam(p$L, $S, $L, $S, $L)", UTILS, parameter.getType(), listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i);
                        } else {
                            builder.add("p$L", listenerPosition);
                        }
                    }
                    builder.add(");\n");
                }
            } else if (hasReturnType) {
                builder.add("$L;\n", method.defaultReturn());
            }
            callbackMethod.addCode(builder.build());
            callback.addMethod(callbackMethod.build());
        }
        boolean requiresRemoval = listener.remover().length() != 0;
        String listenerField = null;
        if (requiresRemoval) {
            TypeName listenerClassName = bestGuess(listener.type());
            listenerField = fieldName + ((ClassName) listenerClassName).simpleName();
            result.addStatement("$L = $L", listenerField, callback.build());
        }
        if (!VIEW_TYPE.equals(listener.targetType())) {
            result.addStatement("(($T) $N).$L($L)", bestGuess(listener.targetType()), bindName, listener.setter(), requiresRemoval ? listenerField : callback.build());
        } else {
            result.addStatement("$N.$L($L)", bindName, listener.setter(), requiresRemoval ? listenerField : callback.build());
        }
    }
    if (needsNullChecked) {
        result.endControlFlow();
    }
}
Also used : WildcardTypeName(com.squareup.javapoet.WildcardTypeName) ParameterizedTypeName(com.squareup.javapoet.ParameterizedTypeName) TypeName(com.squareup.javapoet.TypeName) ListenerClass(butterknife.internal.ListenerClass) Set(java.util.Set) MethodSpec(com.squareup.javapoet.MethodSpec) CodeBlock(com.squareup.javapoet.CodeBlock) ClassName(com.squareup.javapoet.ClassName) ListenerMethod(butterknife.internal.ListenerMethod) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map) TypeSpec(com.squareup.javapoet.TypeSpec)

Example 2 with ListenerMethod

use of butterknife.internal.ListenerMethod in project butterknife by JakeWharton.

the class BindingSet method getListenerMethods.

private static List<ListenerMethod> getListenerMethods(ListenerClass listener) {
    if (listener.method().length == 1) {
        return Arrays.asList(listener.method());
    }
    try {
        List<ListenerMethod> methods = new ArrayList<>();
        Class<? extends Enum<?>> callbacks = listener.callbacks();
        for (Enum<?> callbackMethod : callbacks.getEnumConstants()) {
            Field callbackField = callbacks.getField(callbackMethod.name());
            ListenerMethod method = callbackField.getAnnotation(ListenerMethod.class);
            if (method == null) {
                throw new IllegalStateException(String.format("@%s's %s.%s missing @%s annotation.", callbacks.getEnclosingClass().getSimpleName(), callbacks.getSimpleName(), callbackMethod.name(), ListenerMethod.class.getSimpleName()));
            }
            methods.add(method);
        }
        return methods;
    } catch (NoSuchFieldException e) {
        throw new AssertionError(e);
    }
}
Also used : Field(java.lang.reflect.Field) ArrayList(java.util.ArrayList) ListenerMethod(butterknife.internal.ListenerMethod)

Example 3 with ListenerMethod

use of butterknife.internal.ListenerMethod in project butterknife by JakeWharton.

the class ButterKnifeProcessor method parseListenerAnnotation.

private void parseListenerAnnotation(Class<? extends Annotation> annotationClass, Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> 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 = isListenerRequired(executableElement);
    // 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 == NO_ID.value) {
            if (ids.length == 1) {
                if (!required) {
                    error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)", 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]) && isSubtypeOfType(methodParameterType, VIEW_TYPE)) || isTypeEqual(methodParameterType, parameterTypes[j]) || isInterface(methodParameterType)) {
                    parameters[i] = new Parameter(j, TypeName.get(methodParameterType));
                    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);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    for (int id : ids) {
        QualifiedId qualifiedId = elementToQualifiedId(element, id);
        if (!builder.addMethod(getId(qualifiedId), 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);
}
Also used : ListenerClass(butterknife.internal.ListenerClass) ExecutableElement(javax.lang.model.element.ExecutableElement) BindString(butterknife.BindString) VariableElement(javax.lang.model.element.VariableElement) Field(java.lang.reflect.Field) TypeMirror(javax.lang.model.type.TypeMirror) TypeVariable(javax.lang.model.type.TypeVariable) TypeElement(javax.lang.model.element.TypeElement) BitSet(java.util.BitSet) Method(java.lang.reflect.Method) ListenerMethod(butterknife.internal.ListenerMethod) Annotation(java.lang.annotation.Annotation) ListenerMethod(butterknife.internal.ListenerMethod)

Aggregations

ListenerMethod (butterknife.internal.ListenerMethod)3 ListenerClass (butterknife.internal.ListenerClass)2 Field (java.lang.reflect.Field)2 BindString (butterknife.BindString)1 ClassName (com.squareup.javapoet.ClassName)1 CodeBlock (com.squareup.javapoet.CodeBlock)1 MethodSpec (com.squareup.javapoet.MethodSpec)1 ParameterizedTypeName (com.squareup.javapoet.ParameterizedTypeName)1 TypeName (com.squareup.javapoet.TypeName)1 TypeSpec (com.squareup.javapoet.TypeSpec)1 WildcardTypeName (com.squareup.javapoet.WildcardTypeName)1 Annotation (java.lang.annotation.Annotation)1 Method (java.lang.reflect.Method)1 ArrayList (java.util.ArrayList)1 BitSet (java.util.BitSet)1 LinkedHashMap (java.util.LinkedHashMap)1 Map (java.util.Map)1 Set (java.util.Set)1 ExecutableElement (javax.lang.model.element.ExecutableElement)1 TypeElement (javax.lang.model.element.TypeElement)1