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);
}
}
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);
}
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;
}
}
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;
}
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);
}
}
}
}
Aggregations