Search in sources :

Example 6 with ProcessorException

use of org.realityforge.proton.ProcessorException in project react4j by react4j.

the class React4jProcessor method determineInputValidatesMethods.

private void determineInputValidatesMethods(@Nonnull final ViewDescriptor descriptor) {
    final List<ExecutableElement> methods = getMethods(descriptor.getElement()).stream().filter(m -> AnnotationsUtil.hasAnnotationOfType(m, Constants.INPUT_VALIDATE_CLASSNAME)).collect(Collectors.toList());
    for (final ExecutableElement method : methods) {
        final String name = deriveInputValidateName(method);
        final InputDescriptor input = descriptor.findInputNamed(name);
        if (null == input) {
            throw new ProcessorException("@InputValidate target for input named '" + name + "' has no corresponding " + "@Input annotated method.", method);
        }
        if (1 != method.getParameters().size()) {
            throw new ProcessorException("@InputValidate target must have exactly 1 parameter", method);
        }
        final ExecutableType methodType = resolveMethodType(descriptor, method);
        if (!processingEnv.getTypeUtils().isAssignable(methodType.getParameterTypes().get(0), input.getMethodType().getReturnType())) {
            throw new ProcessorException("@InputValidate target has a parameter type that is not assignable to the " + "return type of the associated @Input annotated method.", method);
        }
        MemberChecks.mustBeSubclassCallable(descriptor.getElement(), Constants.VIEW_CLASSNAME, Constants.INPUT_VALIDATE_CLASSNAME, method);
        MemberChecks.mustNotThrowAnyExceptions(Constants.INPUT_VALIDATE_CLASSNAME, method);
        MemberChecks.mustNotReturnAnyValue(Constants.INPUT_VALIDATE_CLASSNAME, method);
        final VariableElement param = method.getParameters().get(0);
        final boolean mismatchedNullability = (AnnotationsUtil.hasNonnullAnnotation(param) && AnnotationsUtil.hasNullableAnnotation(input.getMethod())) || (AnnotationsUtil.hasNullableAnnotation(param) && input.isNonNull());
        if (mismatchedNullability) {
            throw new ProcessorException("@InputValidate target has a parameter that has a nullability annotation " + "incompatible with the associated @Input method named " + input.getMethod().getSimpleName(), method);
        }
        input.setValidateMethod(method);
    }
}
Also used : Arrays(java.util.Arrays) SupportedOptions(javax.annotation.processing.SupportedOptions) Modifier(javax.lang.model.element.Modifier) VariableElement(javax.lang.model.element.VariableElement) DeferredElementSet(org.realityforge.proton.DeferredElementSet) HashMap(java.util.HashMap) TypeElement(javax.lang.model.element.TypeElement) SupportedAnnotationTypes(javax.annotation.processing.SupportedAnnotationTypes) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) ElementsUtil(org.realityforge.proton.ElementsUtil) SupportedSourceVersion(javax.annotation.processing.SupportedSourceVersion) Matcher(java.util.regex.Matcher) Diagnostic(javax.tools.Diagnostic) Map(java.util.Map) DeclaredType(javax.lang.model.type.DeclaredType) ProcessorException(org.realityforge.proton.ProcessorException) Nonnull(javax.annotation.Nonnull) Nullable(javax.annotation.Nullable) ArrayType(javax.lang.model.type.ArrayType) ElementKind(javax.lang.model.element.ElementKind) ExecutableType(javax.lang.model.type.ExecutableType) Collection(java.util.Collection) ExecutableElement(javax.lang.model.element.ExecutableElement) Set(java.util.Set) IOException(java.io.IOException) Element(javax.lang.model.element.Element) Types(javax.lang.model.util.Types) AnnotationsUtil(org.realityforge.proton.AnnotationsUtil) Collectors(java.util.stream.Collectors) AnnotationMirror(javax.lang.model.element.AnnotationMirror) TypeParameterElement(javax.lang.model.element.TypeParameterElement) TypeKind(javax.lang.model.type.TypeKind) AbstractStandardProcessor(org.realityforge.proton.AbstractStandardProcessor) SourceVersion(javax.lang.model.SourceVersion) List(java.util.List) TypeMirror(javax.lang.model.type.TypeMirror) MemberChecks(org.realityforge.proton.MemberChecks) RoundEnvironment(javax.annotation.processing.RoundEnvironment) TypeName(com.squareup.javapoet.TypeName) TypeVariable(javax.lang.model.type.TypeVariable) AnnotationValue(javax.lang.model.element.AnnotationValue) Pattern(java.util.regex.Pattern) Collections(java.util.Collections) ExecutableType(javax.lang.model.type.ExecutableType) ProcessorException(org.realityforge.proton.ProcessorException) ExecutableElement(javax.lang.model.element.ExecutableElement) VariableElement(javax.lang.model.element.VariableElement)

Example 7 with ProcessorException

use of org.realityforge.proton.ProcessorException in project react4j by react4j.

the class React4jProcessor method determineOnInputChangeMethods.

private void determineOnInputChangeMethods(@Nonnull final ViewDescriptor descriptor) {
    final List<ExecutableElement> methods = getMethods(descriptor.getElement()).stream().filter(m -> AnnotationsUtil.hasAnnotationOfType(m, Constants.ON_INPUT_CHANGE_CLASSNAME)).collect(Collectors.toList());
    final ArrayList<OnInputChangeDescriptor> onInputChangeDescriptors = new ArrayList<>();
    for (final ExecutableElement method : methods) {
        final VariableElement phase = (VariableElement) AnnotationsUtil.getAnnotationValue(method, Constants.ON_INPUT_CHANGE_CLASSNAME, "phase").getValue();
        final boolean preUpdate = phase.getSimpleName().toString().equals("PRE");
        final List<? extends VariableElement> parameters = method.getParameters();
        final ExecutableType methodType = resolveMethodType(descriptor, method);
        final List<? extends TypeMirror> parameterTypes = methodType.getParameterTypes();
        MemberChecks.mustBeSubclassCallable(descriptor.getElement(), Constants.VIEW_CLASSNAME, Constants.ON_INPUT_CHANGE_CLASSNAME, method);
        MemberChecks.mustNotThrowAnyExceptions(Constants.ON_INPUT_CHANGE_CLASSNAME, method);
        MemberChecks.mustNotReturnAnyValue(Constants.ON_INPUT_CHANGE_CLASSNAME, method);
        final int parameterCount = parameters.size();
        if (0 == parameterCount) {
            throw new ProcessorException("@OnInputChange target must have at least 1 parameter.", method);
        }
        final List<InputDescriptor> inputDescriptors = new ArrayList<>(parameterCount);
        for (int i = 0; i < parameterCount; i++) {
            final VariableElement parameter = parameters.get(i);
            final String name = deriveOnInputChangeName(parameter);
            final InputDescriptor input = descriptor.findInputNamed(name);
            if (null == input) {
                throw new ProcessorException("@OnInputChange target has a parameter named '" + parameter.getSimpleName() + "' and the parameter is associated with a " + "@Input named '" + name + "' but there is no corresponding @Input " + "annotated method.", parameter);
            }
            final Types typeUtils = processingEnv.getTypeUtils();
            if (!typeUtils.isAssignable(parameterTypes.get(i), input.getMethodType().getReturnType())) {
                throw new ProcessorException("@OnInputChange target has a parameter named '" + parameter.getSimpleName() + "' and the parameter type is not " + "assignable to the return type of the associated @Input annotated method.", method);
            }
            final boolean mismatchedNullability = (AnnotationsUtil.hasNonnullAnnotation(parameter) && AnnotationsUtil.hasNullableAnnotation(input.getMethod())) || (AnnotationsUtil.hasNullableAnnotation(parameter) && input.isNonNull());
            if (mismatchedNullability) {
                throw new ProcessorException("@OnInputChange target has a parameter named '" + parameter.getSimpleName() + "' that has a nullability annotation " + "incompatible with the associated @Input method named " + method.getSimpleName(), method);
            }
            if (input.isImmutable()) {
                throw new ProcessorException("@OnInputChange target has a parameter named '" + parameter.getSimpleName() + "' that is associated with a @Input " + "annotated method and the input is specified as immutable.", method);
            }
            inputDescriptors.add(input);
        }
        onInputChangeDescriptors.add(new OnInputChangeDescriptor(method, inputDescriptors, preUpdate));
    }
    descriptor.setOnInputChangeDescriptors(onInputChangeDescriptors);
}
Also used : Arrays(java.util.Arrays) SupportedOptions(javax.annotation.processing.SupportedOptions) Modifier(javax.lang.model.element.Modifier) VariableElement(javax.lang.model.element.VariableElement) DeferredElementSet(org.realityforge.proton.DeferredElementSet) HashMap(java.util.HashMap) TypeElement(javax.lang.model.element.TypeElement) SupportedAnnotationTypes(javax.annotation.processing.SupportedAnnotationTypes) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) ElementsUtil(org.realityforge.proton.ElementsUtil) SupportedSourceVersion(javax.annotation.processing.SupportedSourceVersion) Matcher(java.util.regex.Matcher) Diagnostic(javax.tools.Diagnostic) Map(java.util.Map) DeclaredType(javax.lang.model.type.DeclaredType) ProcessorException(org.realityforge.proton.ProcessorException) Nonnull(javax.annotation.Nonnull) Nullable(javax.annotation.Nullable) ArrayType(javax.lang.model.type.ArrayType) ElementKind(javax.lang.model.element.ElementKind) ExecutableType(javax.lang.model.type.ExecutableType) Collection(java.util.Collection) ExecutableElement(javax.lang.model.element.ExecutableElement) Set(java.util.Set) IOException(java.io.IOException) Element(javax.lang.model.element.Element) Types(javax.lang.model.util.Types) AnnotationsUtil(org.realityforge.proton.AnnotationsUtil) Collectors(java.util.stream.Collectors) AnnotationMirror(javax.lang.model.element.AnnotationMirror) TypeParameterElement(javax.lang.model.element.TypeParameterElement) TypeKind(javax.lang.model.type.TypeKind) AbstractStandardProcessor(org.realityforge.proton.AbstractStandardProcessor) SourceVersion(javax.lang.model.SourceVersion) List(java.util.List) TypeMirror(javax.lang.model.type.TypeMirror) MemberChecks(org.realityforge.proton.MemberChecks) RoundEnvironment(javax.annotation.processing.RoundEnvironment) TypeName(com.squareup.javapoet.TypeName) TypeVariable(javax.lang.model.type.TypeVariable) AnnotationValue(javax.lang.model.element.AnnotationValue) Pattern(java.util.regex.Pattern) Collections(java.util.Collections) ExecutableType(javax.lang.model.type.ExecutableType) SupportedAnnotationTypes(javax.annotation.processing.SupportedAnnotationTypes) Types(javax.lang.model.util.Types) ProcessorException(org.realityforge.proton.ProcessorException) ExecutableElement(javax.lang.model.element.ExecutableElement) ArrayList(java.util.ArrayList) VariableElement(javax.lang.model.element.VariableElement)

Example 8 with ProcessorException

use of org.realityforge.proton.ProcessorException in project react4j by react4j.

the class React4jProcessor method deriveInputDefaultName.

@Nonnull
private String deriveInputDefaultName(@Nonnull final Element element) throws ProcessorException {
    final String name = (String) AnnotationsUtil.getAnnotationValue(element, Constants.INPUT_DEFAULT_CLASSNAME, "name").getValue();
    if (isSentinelName(name)) {
        if (element instanceof ExecutableElement) {
            final String deriveName = deriveName(element, DEFAULT_GETTER_PATTERN, name);
            if (null == deriveName) {
                throw new ProcessorException("@InputDefault target has not specified name nor is it named according " + "to the convention 'get[Name]Default'.", element);
            }
            return deriveName;
        } else {
            final String fieldName = element.getSimpleName().toString();
            boolean matched = true;
            final int lengthPrefix = "DEFAULT_".length();
            final int length = fieldName.length();
            if (fieldName.startsWith("DEFAULT_") && length > lengthPrefix) {
                for (int i = lengthPrefix; i < length; i++) {
                    final char ch = fieldName.charAt(i);
                    if (Character.isLowerCase(ch) || ((i != lengthPrefix || !Character.isJavaIdentifierStart(ch)) && (i == lengthPrefix || !Character.isJavaIdentifierPart(ch)))) {
                        matched = false;
                        break;
                    }
                }
            } else {
                matched = false;
            }
            if (matched) {
                return uppercaseConstantToPascalCase(fieldName.substring(lengthPrefix));
            } else {
                throw new ProcessorException("@InputDefault target has not specified name nor is it named according " + "to the convention 'DEFAULT_[NAME]'.", element);
            }
        }
    } else {
        if (!SourceVersion.isIdentifier(name)) {
            throw new ProcessorException("@InputDefault target specified an invalid name '" + name + "'. The " + "name must be a valid java identifier.", element);
        } else if (SourceVersion.isKeyword(name)) {
            throw new ProcessorException("@InputDefault target specified an invalid name '" + name + "'. The " + "name must not be a java keyword.", element);
        }
        return name;
    }
}
Also used : ProcessorException(org.realityforge.proton.ProcessorException) ExecutableElement(javax.lang.model.element.ExecutableElement) Nonnull(javax.annotation.Nonnull)

Example 9 with ProcessorException

use of org.realityforge.proton.ProcessorException in project react4j by react4j.

the class React4jProcessor method parse.

@Nonnull
private ViewDescriptor parse(@Nonnull final TypeElement typeElement) {
    final String name = deriveViewName(typeElement);
    final ViewType type = extractViewType(typeElement);
    final boolean hasPostConstruct = hasPostConstruct(typeElement);
    final boolean shouldSetDefaultPriority = shouldSetDefaultPriority(typeElement);
    MemberChecks.mustNotBeFinal(Constants.VIEW_CLASSNAME, typeElement);
    MemberChecks.mustBeAbstract(Constants.VIEW_CLASSNAME, typeElement);
    if (ElementKind.CLASS != typeElement.getKind()) {
        throw new ProcessorException(MemberChecks.must(Constants.VIEW_CLASSNAME, "be a class"), typeElement);
    } else if (ElementsUtil.isNonStaticNestedClass(typeElement)) {
        throw new ProcessorException(MemberChecks.toSimpleName(Constants.VIEW_CLASSNAME) + " target must not be a non-static nested class", typeElement);
    }
    final List<ExecutableElement> constructors = ElementsUtil.getConstructors(typeElement);
    if (1 != constructors.size() || !isConstructorValid(constructors.get(0))) {
        throw new ProcessorException(MemberChecks.must(Constants.VIEW_CLASSNAME, "have a single, package-access constructor or the default constructor"), typeElement);
    }
    final ExecutableElement constructor = constructors.get(0);
    final boolean inject = deriveInject(typeElement, constructor);
    final boolean sting = deriveSting(typeElement, constructor);
    final boolean notSyntheticConstructor = ElementsUtil.isNotSynthetic(constructor);
    final List<? extends VariableElement> parameters = constructor.getParameters();
    if (inject) {
        if (parameters.isEmpty()) {
            throw new ProcessorException(MemberChecks.mustNot(Constants.VIEW_CLASSNAME, "have specified inject=ENABLED if the constructor has no parameters"), typeElement);
        }
    } else {
        final boolean hasNamedAnnotation = parameters.stream().anyMatch(p -> AnnotationsUtil.hasAnnotationOfType(p, Constants.JSR_330_NAMED_CLASSNAME));
        if (hasNamedAnnotation) {
            throw new ProcessorException(MemberChecks.mustNot(Constants.VIEW_CLASSNAME, "have specified inject=DISABLED and have a constructor parameter annotated with the " + Constants.JSR_330_NAMED_CLASSNAME + " annotation"), constructor);
        }
    }
    if (sting) {
        if (parameters.isEmpty()) {
            throw new ProcessorException(MemberChecks.mustNot(Constants.VIEW_CLASSNAME, "have specified sting=ENABLED if the constructor has no parameters"), typeElement);
        }
    } else {
        final boolean hasNamedAnnotation = parameters.stream().anyMatch(p -> AnnotationsUtil.hasAnnotationOfType(p, Constants.STING_NAMED_CLASSNAME));
        if (hasNamedAnnotation) {
            throw new ProcessorException(MemberChecks.mustNot(Constants.VIEW_CLASSNAME, "have specified sting=DISABLED and have a constructor parameter annotated with the " + Constants.STING_NAMED_CLASSNAME + " annotation"), constructor);
        }
    }
    final ViewDescriptor descriptor = new ViewDescriptor(name, typeElement, constructor, type, inject, sting, notSyntheticConstructor, hasPostConstruct, shouldSetDefaultPriority);
    for (final Element element : descriptor.getElement().getEnclosedElements()) {
        if (ElementKind.METHOD == element.getKind()) {
            final ExecutableElement method = (ExecutableElement) element;
            if (method.getModifiers().contains(Modifier.PUBLIC) && MemberChecks.doesMethodNotOverrideInterfaceMethod(processingEnv, typeElement, method) && ElementsUtil.isWarningNotSuppressed(method, Constants.WARNING_PUBLIC_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME)) {
                final String message = MemberChecks.shouldNot(Constants.VIEW_CLASSNAME, "declare a public method. " + MemberChecks.suppressedBy(Constants.WARNING_PUBLIC_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME));
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message, method);
            }
            if (method.getModifiers().contains(Modifier.FINAL) && ElementsUtil.isWarningNotSuppressed(method, Constants.WARNING_FINAL_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME)) {
                final String message = MemberChecks.shouldNot(Constants.VIEW_CLASSNAME, "declare a final method. " + MemberChecks.suppressedBy(Constants.WARNING_FINAL_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME));
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message, method);
            }
            if (method.getModifiers().contains(Modifier.PROTECTED) && ElementsUtil.isWarningNotSuppressed(method, Constants.WARNING_PROTECTED_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME) && !isMethodAProtectedOverride(typeElement, method)) {
                final String message = MemberChecks.shouldNot(Constants.VIEW_CLASSNAME, "declare a protected method. " + MemberChecks.suppressedBy(Constants.WARNING_PROTECTED_METHOD, Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME));
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message, method);
            }
        }
    }
    determineViewCapabilities(descriptor, typeElement);
    determineInputs(descriptor);
    determineInputValidatesMethods(descriptor);
    determineOnInputChangeMethods(descriptor);
    determineDefaultInputsMethods(descriptor);
    determineDefaultInputsFields(descriptor);
    determinePreUpdateMethod(typeElement, descriptor);
    determinePostMountOrUpdateMethod(typeElement, descriptor);
    determinePostUpdateMethod(typeElement, descriptor);
    determinePostMountMethod(typeElement, descriptor);
    determineOnErrorMethod(typeElement, descriptor);
    determineScheduleRenderMethods(typeElement, descriptor);
    determinePublishMethods(typeElement, descriptor);
    determineRenderMethod(typeElement, descriptor);
    for (final InputDescriptor input : descriptor.getInputs()) {
        if (!isInputRequired(input)) {
            input.markAsOptional();
        } else {
            if (input.isContextSource()) {
                throw new ProcessorException(MemberChecks.mustNot(Constants.INPUT_CLASSNAME, "specify require=ENABLE parameter when the for source=CONTEXT parameter is specified"), input.getMethod());
            }
        }
    }
    /*
     * Sorting must occur after @InputDefault has been processed to ensure the sorting
     * correctly sorts optional inputs after required inputs.
     */
    descriptor.sortInputs();
    verifyInputsNotAnnotatedWithArezAnnotations(descriptor);
    verifyInputsNotCollectionOfArezComponents(descriptor);
    return descriptor;
}
Also used : ProcessorException(org.realityforge.proton.ProcessorException) ExecutableElement(javax.lang.model.element.ExecutableElement) VariableElement(javax.lang.model.element.VariableElement) TypeElement(javax.lang.model.element.TypeElement) ExecutableElement(javax.lang.model.element.ExecutableElement) Element(javax.lang.model.element.Element) TypeParameterElement(javax.lang.model.element.TypeParameterElement) Nonnull(javax.annotation.Nonnull)

Example 10 with ProcessorException

use of org.realityforge.proton.ProcessorException in project react4j by react4j.

the class React4jProcessor method verifyInputsNotCollectionOfArezComponents.

private void verifyInputsNotCollectionOfArezComponents(@Nonnull final ViewDescriptor descriptor) {
    for (final InputDescriptor input : descriptor.getInputs()) {
        final ExecutableElement method = input.getMethod();
        final TypeMirror returnType = method.getReturnType();
        if (TypeKind.DECLARED == returnType.getKind()) {
            final DeclaredType declaredType = (DeclaredType) returnType;
            final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
            if (isCollection(declaredType)) {
                if (1 == typeArguments.size() && isArezComponent(typeArguments.get(0))) {
                    throw new ProcessorException("@Input target is a collection that contains Arez components. " + "This is not a safe pattern when the arez components can be disposed.", method);
                }
            } else if (isMap(declaredType)) {
                if (2 == typeArguments.size() && (isArezComponent(typeArguments.get(0)) || isArezComponent(typeArguments.get(1)))) {
                    throw new ProcessorException("@Input target is a collection that contains Arez components. " + "This is not a safe pattern when the arez components can be disposed.", method);
                }
            }
        } else if (TypeKind.ARRAY == returnType.getKind()) {
            final ArrayType arrayType = (ArrayType) returnType;
            if (isArezComponent(arrayType.getComponentType())) {
                throw new ProcessorException("@Input target is an array that contains Arez components. " + "This is not a safe pattern when the arez components can be disposed.", method);
            }
        }
    }
}
Also used : ArrayType(javax.lang.model.type.ArrayType) ProcessorException(org.realityforge.proton.ProcessorException) TypeMirror(javax.lang.model.type.TypeMirror) ExecutableElement(javax.lang.model.element.ExecutableElement) DeclaredType(javax.lang.model.type.DeclaredType)

Aggregations

ProcessorException (org.realityforge.proton.ProcessorException)13 ExecutableElement (javax.lang.model.element.ExecutableElement)12 Nonnull (javax.annotation.Nonnull)8 AnnotationMirror (javax.lang.model.element.AnnotationMirror)7 VariableElement (javax.lang.model.element.VariableElement)7 TypeName (com.squareup.javapoet.TypeName)6 ArrayList (java.util.ArrayList)6 Element (javax.lang.model.element.Element)6 TypeElement (javax.lang.model.element.TypeElement)6 TypeParameterElement (javax.lang.model.element.TypeParameterElement)6 ExecutableType (javax.lang.model.type.ExecutableType)6 TypeMirror (javax.lang.model.type.TypeMirror)6 ArrayType (javax.lang.model.type.ArrayType)5 DeclaredType (javax.lang.model.type.DeclaredType)5 TypeVariable (javax.lang.model.type.TypeVariable)5 IOException (java.io.IOException)4 Arrays (java.util.Arrays)4 Collection (java.util.Collection)4 Collections (java.util.Collections)4 HashMap (java.util.HashMap)4