From 7e202ef2d47e0d5a1d0f2ce9f6c973d82bc50ebd Mon Sep 17 00:00:00 2001 From: Peter Donald Date: Fri, 31 May 2024 14:54:30 +1000 Subject: [PATCH] Publish website --- .../react4j/processor/React4jProcessor.html | 2857 +++++++++-------- docs/project_setup.html | 4 +- 2 files changed, 1436 insertions(+), 1425 deletions(-) diff --git a/api/src-html/react4j/processor/React4jProcessor.html b/api/src-html/react4j/processor/React4jProcessor.html index 1b6f0515..184b4737 100644 --- a/api/src-html/react4j/processor/React4jProcessor.html +++ b/api/src-html/react4j/processor/React4jProcessor.html @@ -141,1456 +141,1467 @@ 128 /** 129 * Return true if there is any method annotated with @PostConstruct. 130 */ -131 private boolean hasPostConstruct( @Nonnull final TypeElement typeElement ) +131 private boolean hasPostConstruct( @Nonnull final List<ExecutableElement> methods ) 132 { -133 return getMethods( typeElement ) -134 .stream() -135 .anyMatch( e -> AnnotationsUtil.hasAnnotationOfType( e, Constants.POST_CONSTRUCT_CLASSNAME ) ); -136 } -137 -138 @Nonnull -139 private ViewDescriptor parse( @Nonnull final TypeElement typeElement ) -140 { -141 final String name = deriveViewName( typeElement ); -142 final ViewType type = extractViewType( typeElement ); -143 final boolean hasPostConstruct = hasPostConstruct( typeElement ); -144 final boolean shouldSetDefaultPriority = shouldSetDefaultPriority( typeElement ); -145 -146 MemberChecks.mustNotBeFinal( Constants.VIEW_CLASSNAME, typeElement ); -147 MemberChecks.mustBeAbstract( Constants.VIEW_CLASSNAME, typeElement ); -148 if ( ElementKind.CLASS != typeElement.getKind() ) -149 { -150 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, "be a class" ), -151 typeElement ); -152 } -153 else if ( ElementsUtil.isNonStaticNestedClass( typeElement ) ) -154 { -155 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + -156 " target must not be a non-static nested class", -157 typeElement ); -158 } -159 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( typeElement ); -160 if ( 1 != constructors.size() || !isConstructorValid( constructors.get( 0 ) ) ) -161 { -162 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, -163 "have a single, package-access constructor or the default constructor" ), -164 typeElement ); -165 } -166 final ExecutableElement constructor = constructors.get( 0 ); -167 -168 final boolean sting = deriveSting( typeElement, constructor ); -169 final boolean notSyntheticConstructor = -170 Elements.Origin.EXPLICIT == processingEnv.getElementUtils().getOrigin( constructor ); -171 -172 final List<? extends VariableElement> parameters = constructor.getParameters(); -173 if ( sting ) -174 { -175 if ( parameters.isEmpty() ) -176 { -177 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, -178 "have specified sting=ENABLED if the constructor has no parameters" ), -179 typeElement ); -180 } -181 } -182 else -183 { -184 final boolean hasNamedAnnotation = -185 parameters.stream().anyMatch( p -> AnnotationsUtil.hasAnnotationOfType( p, Constants.STING_NAMED_CLASSNAME ) ); -186 if ( hasNamedAnnotation ) -187 { -188 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, -189 "have specified sting=DISABLED and have a constructor parameter annotated with the " + -190 Constants.STING_NAMED_CLASSNAME + " annotation" ), -191 constructor ); -192 } -193 } -194 -195 final ViewDescriptor descriptor = -196 new ViewDescriptor( name, -197 typeElement, -198 constructor, -199 type, -200 sting, -201 notSyntheticConstructor, -202 hasPostConstruct, -203 shouldSetDefaultPriority ); -204 -205 for ( final Element element : descriptor.getElement().getEnclosedElements() ) -206 { -207 if ( ElementKind.METHOD == element.getKind() ) -208 { -209 final ExecutableElement method = (ExecutableElement) element; -210 if ( method.getModifiers().contains( Modifier.PUBLIC ) && -211 MemberChecks.doesMethodNotOverrideInterfaceMethod( processingEnv, typeElement, method ) && -212 ElementsUtil.isWarningNotSuppressed( method, -213 Constants.WARNING_PUBLIC_METHOD, -214 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) -215 { -216 final String message = -217 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, -218 "declare a public method. " + -219 MemberChecks.suppressedBy( Constants.WARNING_PUBLIC_METHOD, -220 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); -221 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); -222 } -223 if ( method.getModifiers().contains( Modifier.FINAL ) && -224 ElementsUtil.isWarningNotSuppressed( method, -225 Constants.WARNING_FINAL_METHOD, -226 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) -227 { -228 final String message = -229 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, -230 "declare a final method. " + -231 MemberChecks.suppressedBy( Constants.WARNING_FINAL_METHOD, -232 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); -233 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); -234 } -235 if ( method.getModifiers().contains( Modifier.PROTECTED ) && -236 ElementsUtil.isWarningNotSuppressed( method, -237 Constants.WARNING_PROTECTED_METHOD, -238 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) && -239 !isMethodAProtectedOverride( typeElement, method ) ) -240 { -241 final String message = -242 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, -243 "declare a protected method. " + -244 MemberChecks.suppressedBy( Constants.WARNING_PROTECTED_METHOD, -245 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); -246 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); -247 } -248 } -249 } -250 -251 determineViewCapabilities( descriptor, typeElement ); -252 determineInputs( descriptor ); -253 determineInputValidatesMethods( descriptor ); -254 determineOnInputChangeMethods( descriptor ); -255 determineDefaultInputsMethods( descriptor ); -256 determineDefaultInputsFields( descriptor ); -257 determinePreUpdateMethod( typeElement, descriptor ); -258 determinePostMountOrUpdateMethod( typeElement, descriptor ); -259 determinePostUpdateMethod( typeElement, descriptor ); -260 determinePostMountMethod( typeElement, descriptor ); -261 determineOnErrorMethod( typeElement, descriptor ); -262 determineScheduleRenderMethods( typeElement, descriptor ); -263 determinePublishMethods( typeElement, descriptor ); -264 determinePreRenderMethods( typeElement, descriptor ); -265 determinePostRenderMethods( typeElement, descriptor ); -266 determineRenderMethod( typeElement, descriptor ); -267 -268 for ( final InputDescriptor input : descriptor.getInputs() ) -269 { -270 if ( !isInputRequired( input ) ) -271 { -272 input.markAsOptional(); -273 } -274 else -275 { -276 if ( input.isContextSource() ) -277 { -278 throw new ProcessorException( MemberChecks.mustNot( Constants.INPUT_CLASSNAME, -279 "specify require=ENABLE parameter when the for source=CONTEXT parameter is specified" ), -280 input.getMethod() ); -281 } -282 } -283 } -284 -285 /* -286 * Sorting must occur after @InputDefault has been processed to ensure the sorting -287 * correctly sorts optional inputs after required inputs. -288 */ -289 descriptor.sortInputs(); -290 -291 verifyInputsNotAnnotatedWithArezAnnotations( descriptor ); -292 verifyInputsNotCollectionOfArezComponents( descriptor ); -293 -294 return descriptor; -295 } -296 -297 private boolean isMethodAProtectedOverride( @Nonnull final TypeElement typeElement, -298 @Nonnull final ExecutableElement method ) -299 { -300 final ExecutableElement overriddenMethod = ElementsUtil.getOverriddenMethod( processingEnv, typeElement, method ); -301 return null != overriddenMethod && overriddenMethod.getModifiers().contains( Modifier.PROTECTED ); -302 } -303 -304 private boolean deriveSting( @Nonnull final TypeElement typeElement, final @Nonnull ExecutableElement constructor ) -305 { -306 final String inject = -307 AnnotationsUtil.getEnumAnnotationParameter( typeElement, -308 Constants.VIEW_CLASSNAME, -309 "sting" ); -310 if ( "ENABLE".equals( inject ) ) -311 { -312 return true; -313 } -314 else if ( "DISABLE".equals( inject ) ) -315 { -316 return false; -317 } -318 else -319 { -320 return !constructor.getParameters().isEmpty() && -321 null != processingEnv.getElementUtils().getTypeElement( Constants.STING_INJECTABLE_CLASSNAME ); -322 } -323 } -324 -325 private boolean isConstructorValid( @Nonnull final ExecutableElement ctor ) -326 { -327 if ( Elements.Origin.EXPLICIT != processingEnv.getElementUtils().getOrigin( ctor ) ) -328 { -329 return true; -330 } -331 else -332 { -333 final Set<Modifier> modifiers = ctor.getModifiers(); -334 return -335 !modifiers.contains( Modifier.PRIVATE ) && -336 !modifiers.contains( Modifier.PUBLIC ) && -337 !modifiers.contains( Modifier.PROTECTED ); -338 } -339 } -340 -341 private void verifyInputsNotCollectionOfArezComponents( @Nonnull final ViewDescriptor descriptor ) -342 { -343 for ( final InputDescriptor input : descriptor.getInputs() ) -344 { -345 final ExecutableElement method = input.getMethod(); -346 final TypeMirror returnType = method.getReturnType(); -347 if ( TypeKind.DECLARED == returnType.getKind() ) -348 { -349 final DeclaredType declaredType = (DeclaredType) returnType; -350 final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); -351 if ( isCollection( declaredType ) ) -352 { -353 if ( 1 == typeArguments.size() && isArezComponent( typeArguments.get( 0 ) ) ) -354 { -355 throw new ProcessorException( "@Input target is a collection that contains Arez components. " + -356 "This is not a safe pattern when the arez components can be disposed.", -357 method ); -358 } -359 } -360 else if ( isMap( declaredType ) ) -361 { -362 if ( 2 == typeArguments.size() && -363 ( isArezComponent( typeArguments.get( 0 ) ) || -364 isArezComponent( typeArguments.get( 1 ) ) ) ) -365 { -366 throw new ProcessorException( "@Input target is a collection that contains Arez components. " + -367 "This is not a safe pattern when the arez components can be disposed.", -368 method ); -369 } -370 } -371 } -372 else if ( TypeKind.ARRAY == returnType.getKind() ) -373 { -374 final ArrayType arrayType = (ArrayType) returnType; -375 if ( isArezComponent( arrayType.getComponentType() ) ) -376 { -377 throw new ProcessorException( "@Input target is an array that contains Arez components. " + -378 "This is not a safe pattern when the arez components can be disposed.", -379 method ); -380 } -381 } -382 } -383 } -384 -385 private boolean isCollection( @Nonnull final DeclaredType declaredType ) -386 { -387 final TypeElement returnType = (TypeElement) processingEnv.getTypeUtils().asElement( declaredType ); -388 final String classname = returnType.getQualifiedName().toString(); -389 /* -390 * For the time being lets just list out a bunch of collections. We can ge more specific when/if -391 * it is ever required -392 */ -393 return Collection.class.getName().equals( classname ) || -394 Set.class.getName().equals( classname ) || -395 List.class.getName().equals( classname ) || -396 HashSet.class.getName().equals( classname ) || -397 ArrayList.class.getName().equals( classname ); -398 } -399 -400 private boolean isMap( @Nonnull final DeclaredType declaredType ) -401 { -402 final TypeElement returnType = (TypeElement) processingEnv.getTypeUtils().asElement( declaredType ); -403 final String classname = returnType.getQualifiedName().toString(); -404 /* -405 * For the time being lets just list out a bunch of collections. We can ge more specific when/if -406 * it is ever required -407 */ -408 return Map.class.getName().equals( classname ) || HashMap.class.getName().equals( classname ); -409 } -410 -411 private boolean isArezComponent( @Nonnull final TypeMirror typeMirror ) -412 { -413 return typeMirror instanceof DeclaredType && -414 processingEnv.getTypeUtils() -415 .asElement( typeMirror ) -416 .getAnnotationMirrors() -417 .stream() -418 .anyMatch( a -> a.getAnnotationType().toString().equals( Constants.AREZ_COMPONENT_CLASSNAME ) ); -419 } -420 -421 private void verifyInputsNotAnnotatedWithArezAnnotations( @Nonnull final ViewDescriptor descriptor ) -422 { -423 for ( final InputDescriptor input : descriptor.getInputs() ) -424 { -425 final ExecutableElement method = input.getMethod(); -426 for ( final AnnotationMirror mirror : method.getAnnotationMirrors() ) -427 { -428 final String classname = mirror.getAnnotationType().toString(); -429 if ( classname.startsWith( "arez.annotations." ) ) -430 { -431 throw new ProcessorException( "@Input target must not be annotated with any arez annotations but " + -432 "is annotated by '" + classname + "'.", method ); -433 } -434 } -435 } -436 } -437 -438 private void determineOnInputChangeMethods( @Nonnull final ViewDescriptor descriptor ) -439 { -440 final List<ExecutableElement> methods = -441 getMethods( descriptor.getElement() ).stream() -442 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.ON_INPUT_CHANGE_CLASSNAME ) ) -443 .collect( Collectors.toList() ); -444 -445 final ArrayList<OnInputChangeDescriptor> onInputChangeDescriptors = new ArrayList<>(); -446 for ( final ExecutableElement method : methods ) -447 { -448 final VariableElement phase = (VariableElement) -449 AnnotationsUtil.getAnnotationValue( method, Constants.ON_INPUT_CHANGE_CLASSNAME, "phase" ).getValue(); -450 final boolean preUpdate = phase.getSimpleName().toString().equals( "PRE" ); -451 -452 final List<? extends VariableElement> parameters = method.getParameters(); -453 final ExecutableType methodType = resolveMethodType( descriptor, method ); -454 final List<? extends TypeMirror> parameterTypes = methodType.getParameterTypes(); +133 return +134 methods.stream().anyMatch( e -> AnnotationsUtil.hasAnnotationOfType( e, Constants.POST_CONSTRUCT_CLASSNAME ) ); +135 } +136 +137 @Nonnull +138 private ViewDescriptor parse( @Nonnull final TypeElement typeElement ) +139 { +140 final String name = deriveViewName( typeElement ); +141 final ViewType type = extractViewType( typeElement ); +142 final List<ExecutableElement> methods = +143 ElementsUtil.getMethods( typeElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); +144 +145 final boolean hasPostConstruct = hasPostConstruct( methods ); +146 final boolean shouldSetDefaultPriority = shouldSetDefaultPriority( methods ); +147 +148 MemberChecks.mustNotBeFinal( Constants.VIEW_CLASSNAME, typeElement ); +149 MemberChecks.mustBeAbstract( Constants.VIEW_CLASSNAME, typeElement ); +150 if ( ElementKind.CLASS != typeElement.getKind() ) +151 { +152 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, "be a class" ), +153 typeElement ); +154 } +155 else if ( ElementsUtil.isNonStaticNestedClass( typeElement ) ) +156 { +157 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + +158 " target must not be a non-static nested class", +159 typeElement ); +160 } +161 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( typeElement ); +162 if ( 1 != constructors.size() || !isConstructorValid( constructors.get( 0 ) ) ) +163 { +164 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, +165 "have a single, package-access constructor or the default constructor" ), +166 typeElement ); +167 } +168 final ExecutableElement constructor = constructors.get( 0 ); +169 +170 final boolean sting = deriveSting( typeElement, constructor ); +171 final boolean notSyntheticConstructor = +172 Elements.Origin.EXPLICIT == processingEnv.getElementUtils().getOrigin( constructor ); +173 +174 final List<? extends VariableElement> parameters = constructor.getParameters(); +175 if ( sting ) +176 { +177 if ( parameters.isEmpty() ) +178 { +179 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, +180 "have specified sting=ENABLED if the constructor has no parameters" ), +181 typeElement ); +182 } +183 } +184 else +185 { +186 final boolean hasNamedAnnotation = +187 parameters.stream().anyMatch( p -> AnnotationsUtil.hasAnnotationOfType( p, Constants.STING_NAMED_CLASSNAME ) ); +188 if ( hasNamedAnnotation ) +189 { +190 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, +191 "have specified sting=DISABLED and have a constructor parameter annotated with the " + +192 Constants.STING_NAMED_CLASSNAME + " annotation" ), +193 constructor ); +194 } +195 } +196 +197 final ViewDescriptor descriptor = +198 new ViewDescriptor( name, +199 typeElement, +200 constructor, +201 type, +202 sting, +203 notSyntheticConstructor, +204 hasPostConstruct, +205 shouldSetDefaultPriority ); +206 +207 for ( final Element element : descriptor.getElement().getEnclosedElements() ) +208 { +209 if ( ElementKind.METHOD == element.getKind() ) +210 { +211 final ExecutableElement method = (ExecutableElement) element; +212 if ( method.getModifiers().contains( Modifier.PUBLIC ) && +213 MemberChecks.doesMethodNotOverrideInterfaceMethod( processingEnv, typeElement, method ) && +214 ElementsUtil.isWarningNotSuppressed( method, +215 Constants.WARNING_PUBLIC_METHOD, +216 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) +217 { +218 final String message = +219 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, +220 "declare a public method. " + +221 MemberChecks.suppressedBy( Constants.WARNING_PUBLIC_METHOD, +222 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); +223 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); +224 } +225 if ( method.getModifiers().contains( Modifier.FINAL ) && +226 ElementsUtil.isWarningNotSuppressed( method, +227 Constants.WARNING_FINAL_METHOD, +228 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) +229 { +230 final String message = +231 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, +232 "declare a final method. " + +233 MemberChecks.suppressedBy( Constants.WARNING_FINAL_METHOD, +234 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); +235 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); +236 } +237 if ( method.getModifiers().contains( Modifier.PROTECTED ) && +238 ElementsUtil.isWarningNotSuppressed( method, +239 Constants.WARNING_PROTECTED_METHOD, +240 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) && +241 !isMethodAProtectedOverride( typeElement, method ) ) +242 { +243 final String message = +244 MemberChecks.shouldNot( Constants.VIEW_CLASSNAME, +245 "declare a protected method. " + +246 MemberChecks.suppressedBy( Constants.WARNING_PROTECTED_METHOD, +247 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ); +248 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); +249 } +250 } +251 } +252 +253 determineViewCapabilities( descriptor, typeElement ); +254 determineInputs( descriptor, methods ); +255 determineInputValidatesMethods( descriptor, methods ); +256 determineOnInputChangeMethods( descriptor, methods ); +257 determineDefaultInputsMethods( descriptor, methods ); +258 determineDefaultInputsFields( descriptor ); +259 determinePreUpdateMethod( typeElement, descriptor, methods ); +260 determinePostMountOrUpdateMethod( typeElement, descriptor, methods ); +261 determinePostUpdateMethod( typeElement, descriptor, methods ); +262 determinePostMountMethod( typeElement, descriptor, methods ); +263 determineOnErrorMethod( typeElement, descriptor, methods ); +264 determineScheduleRenderMethods( typeElement, descriptor, methods ); +265 determinePublishMethods( typeElement, descriptor, methods ); +266 determinePreRenderMethods( typeElement, descriptor, methods ); +267 determinePostRenderMethods( typeElement, descriptor, methods ); +268 determineRenderMethod( typeElement, descriptor, methods ); +269 +270 for ( final InputDescriptor input : descriptor.getInputs() ) +271 { +272 if ( !isInputRequired( input ) ) +273 { +274 input.markAsOptional(); +275 } +276 else +277 { +278 if ( input.isContextSource() ) +279 { +280 throw new ProcessorException( MemberChecks.mustNot( Constants.INPUT_CLASSNAME, +281 "specify require=ENABLE parameter when the for source=CONTEXT parameter is specified" ), +282 input.getMethod() ); +283 } +284 } +285 } +286 +287 /* +288 * Sorting must occur after @InputDefault has been processed to ensure the sorting +289 * correctly sorts optional inputs after required inputs. +290 */ +291 descriptor.sortInputs(); +292 +293 verifyInputsNotAnnotatedWithArezAnnotations( descriptor ); +294 verifyInputsNotCollectionOfArezComponents( descriptor ); +295 +296 return descriptor; +297 } +298 +299 private boolean isMethodAProtectedOverride( @Nonnull final TypeElement typeElement, +300 @Nonnull final ExecutableElement method ) +301 { +302 final ExecutableElement overriddenMethod = ElementsUtil.getOverriddenMethod( processingEnv, typeElement, method ); +303 return null != overriddenMethod && overriddenMethod.getModifiers().contains( Modifier.PROTECTED ); +304 } +305 +306 private boolean deriveSting( @Nonnull final TypeElement typeElement, final @Nonnull ExecutableElement constructor ) +307 { +308 final String inject = +309 AnnotationsUtil.getEnumAnnotationParameter( typeElement, +310 Constants.VIEW_CLASSNAME, +311 "sting" ); +312 if ( "ENABLE".equals( inject ) ) +313 { +314 return true; +315 } +316 else if ( "DISABLE".equals( inject ) ) +317 { +318 return false; +319 } +320 else +321 { +322 return !constructor.getParameters().isEmpty() && +323 null != processingEnv.getElementUtils().getTypeElement( Constants.STING_INJECTABLE_CLASSNAME ); +324 } +325 } +326 +327 private boolean isConstructorValid( @Nonnull final ExecutableElement ctor ) +328 { +329 if ( Elements.Origin.EXPLICIT != processingEnv.getElementUtils().getOrigin( ctor ) ) +330 { +331 return true; +332 } +333 else +334 { +335 final Set<Modifier> modifiers = ctor.getModifiers(); +336 return +337 !modifiers.contains( Modifier.PRIVATE ) && +338 !modifiers.contains( Modifier.PUBLIC ) && +339 !modifiers.contains( Modifier.PROTECTED ); +340 } +341 } +342 +343 private void verifyInputsNotCollectionOfArezComponents( @Nonnull final ViewDescriptor descriptor ) +344 { +345 for ( final InputDescriptor input : descriptor.getInputs() ) +346 { +347 final ExecutableElement method = input.getMethod(); +348 final TypeMirror returnType = method.getReturnType(); +349 if ( TypeKind.DECLARED == returnType.getKind() ) +350 { +351 final DeclaredType declaredType = (DeclaredType) returnType; +352 final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); +353 if ( isCollection( declaredType ) ) +354 { +355 if ( 1 == typeArguments.size() && isArezComponent( typeArguments.get( 0 ) ) ) +356 { +357 throw new ProcessorException( "@Input target is a collection that contains Arez components. " + +358 "This is not a safe pattern when the arez components can be disposed.", +359 method ); +360 } +361 } +362 else if ( isMap( declaredType ) ) +363 { +364 if ( 2 == typeArguments.size() && +365 ( isArezComponent( typeArguments.get( 0 ) ) || +366 isArezComponent( typeArguments.get( 1 ) ) ) ) +367 { +368 throw new ProcessorException( "@Input target is a collection that contains Arez components. " + +369 "This is not a safe pattern when the arez components can be disposed.", +370 method ); +371 } +372 } +373 } +374 else if ( TypeKind.ARRAY == returnType.getKind() ) +375 { +376 final ArrayType arrayType = (ArrayType) returnType; +377 if ( isArezComponent( arrayType.getComponentType() ) ) +378 { +379 throw new ProcessorException( "@Input target is an array that contains Arez components. " + +380 "This is not a safe pattern when the arez components can be disposed.", +381 method ); +382 } +383 } +384 } +385 } +386 +387 private boolean isCollection( @Nonnull final DeclaredType declaredType ) +388 { +389 final TypeElement returnType = (TypeElement) processingEnv.getTypeUtils().asElement( declaredType ); +390 final String classname = returnType.getQualifiedName().toString(); +391 /* +392 * For the time being lets just list out a bunch of collections. We can ge more specific when/if +393 * it is ever required +394 */ +395 return Collection.class.getName().equals( classname ) || +396 Set.class.getName().equals( classname ) || +397 List.class.getName().equals( classname ) || +398 HashSet.class.getName().equals( classname ) || +399 ArrayList.class.getName().equals( classname ); +400 } +401 +402 private boolean isMap( @Nonnull final DeclaredType declaredType ) +403 { +404 final TypeElement returnType = (TypeElement) processingEnv.getTypeUtils().asElement( declaredType ); +405 final String classname = returnType.getQualifiedName().toString(); +406 /* +407 * For the time being lets just list out a bunch of collections. We can ge more specific when/if +408 * it is ever required +409 */ +410 return Map.class.getName().equals( classname ) || HashMap.class.getName().equals( classname ); +411 } +412 +413 private boolean isArezComponent( @Nonnull final TypeMirror typeMirror ) +414 { +415 return typeMirror instanceof DeclaredType && +416 processingEnv.getTypeUtils() +417 .asElement( typeMirror ) +418 .getAnnotationMirrors() +419 .stream() +420 .anyMatch( a -> a.getAnnotationType().toString().equals( Constants.AREZ_COMPONENT_CLASSNAME ) ); +421 } +422 +423 private void verifyInputsNotAnnotatedWithArezAnnotations( @Nonnull final ViewDescriptor descriptor ) +424 { +425 for ( final InputDescriptor input : descriptor.getInputs() ) +426 { +427 final ExecutableElement method = input.getMethod(); +428 for ( final AnnotationMirror mirror : method.getAnnotationMirrors() ) +429 { +430 final String classname = mirror.getAnnotationType().toString(); +431 if ( classname.startsWith( "arez.annotations." ) ) +432 { +433 throw new ProcessorException( "@Input target must not be annotated with any arez annotations but " + +434 "is annotated by '" + classname + "'.", method ); +435 } +436 } +437 } +438 } +439 +440 private void determineOnInputChangeMethods( @Nonnull final ViewDescriptor descriptor, +441 @Nonnull final List<ExecutableElement> methods ) +442 { +443 final List<ExecutableElement> onInputChangeMethods = +444 methods +445 .stream() +446 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.ON_INPUT_CHANGE_CLASSNAME ) ) +447 .toList(); +448 +449 final ArrayList<OnInputChangeDescriptor> onInputChangeDescriptors = new ArrayList<>(); +450 for ( final ExecutableElement method : onInputChangeMethods ) +451 { +452 final VariableElement phase = (VariableElement) +453 AnnotationsUtil.getAnnotationValue( method, Constants.ON_INPUT_CHANGE_CLASSNAME, "phase" ).getValue(); +454 final boolean preUpdate = phase.getSimpleName().toString().equals( "PRE" ); 455 -456 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), -457 Constants.VIEW_CLASSNAME, -458 Constants.ON_INPUT_CHANGE_CLASSNAME, -459 method ); -460 MemberChecks.mustNotThrowAnyExceptions( Constants.ON_INPUT_CHANGE_CLASSNAME, method ); -461 MemberChecks.mustNotReturnAnyValue( Constants.ON_INPUT_CHANGE_CLASSNAME, method ); -462 -463 final int parameterCount = parameters.size(); -464 if ( 0 == parameterCount ) -465 { -466 throw new ProcessorException( "@OnInputChange target must have at least 1 parameter.", method ); -467 } -468 final List<InputDescriptor> inputDescriptors = new ArrayList<>( parameterCount ); -469 for ( int i = 0; i < parameterCount; i++ ) -470 { -471 final VariableElement parameter = parameters.get( i ); -472 final String name = deriveOnInputChangeName( parameter ); -473 final InputDescriptor input = descriptor.findInputNamed( name ); -474 if ( null == input ) -475 { -476 throw new ProcessorException( "@OnInputChange target has a parameter named '" + -477 parameter.getSimpleName() + "' and the parameter is associated with a " + -478 "@Input named '" + name + "' but there is no corresponding @Input " + -479 "annotated method.", parameter ); -480 } -481 final Types typeUtils = processingEnv.getTypeUtils(); -482 if ( !typeUtils.isAssignable( parameterTypes.get( i ), input.getMethodType().getReturnType() ) ) -483 { -484 throw new ProcessorException( "@OnInputChange target has a parameter named '" + -485 parameter.getSimpleName() + "' and the parameter type is not " + -486 "assignable to the return type of the associated @Input annotated method.", -487 method ); -488 } -489 final boolean mismatchedNullability = -490 ( -491 AnnotationsUtil.hasNonnullAnnotation( parameter ) && -492 AnnotationsUtil.hasNullableAnnotation( input.getMethod() ) -493 ) || +456 final List<? extends VariableElement> parameters = method.getParameters(); +457 final ExecutableType methodType = resolveMethodType( descriptor, method ); +458 final List<? extends TypeMirror> parameterTypes = methodType.getParameterTypes(); +459 +460 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), +461 Constants.VIEW_CLASSNAME, +462 Constants.ON_INPUT_CHANGE_CLASSNAME, +463 method ); +464 MemberChecks.mustNotThrowAnyExceptions( Constants.ON_INPUT_CHANGE_CLASSNAME, method ); +465 MemberChecks.mustNotReturnAnyValue( Constants.ON_INPUT_CHANGE_CLASSNAME, method ); +466 +467 final int parameterCount = parameters.size(); +468 if ( 0 == parameterCount ) +469 { +470 throw new ProcessorException( "@OnInputChange target must have at least 1 parameter.", method ); +471 } +472 final List<InputDescriptor> inputDescriptors = new ArrayList<>( parameterCount ); +473 for ( int i = 0; i < parameterCount; i++ ) +474 { +475 final VariableElement parameter = parameters.get( i ); +476 final String name = deriveOnInputChangeName( parameter ); +477 final InputDescriptor input = descriptor.findInputNamed( name ); +478 if ( null == input ) +479 { +480 throw new ProcessorException( "@OnInputChange target has a parameter named '" + +481 parameter.getSimpleName() + "' and the parameter is associated with a " + +482 "@Input named '" + name + "' but there is no corresponding @Input " + +483 "annotated method.", parameter ); +484 } +485 final Types typeUtils = processingEnv.getTypeUtils(); +486 if ( !typeUtils.isAssignable( parameterTypes.get( i ), input.getMethodType().getReturnType() ) ) +487 { +488 throw new ProcessorException( "@OnInputChange target has a parameter named '" + +489 parameter.getSimpleName() + "' and the parameter type is not " + +490 "assignable to the return type of the associated @Input annotated method.", +491 method ); +492 } +493 final boolean mismatchedNullability = 494 ( -495 AnnotationsUtil.hasNullableAnnotation( parameter ) && -496 input.isNonNull() ); -497 -498 if ( mismatchedNullability ) -499 { -500 throw new ProcessorException( "@OnInputChange target has a parameter named '" + -501 parameter.getSimpleName() + "' that has a nullability annotation " + -502 "incompatible with the associated @Input method named " + -503 method.getSimpleName(), method ); -504 } -505 if ( input.isImmutable() ) -506 { -507 throw new ProcessorException( "@OnInputChange target has a parameter named '" + -508 parameter.getSimpleName() + "' that is associated with a @Input " + -509 "annotated method and the input is specified as immutable.", method ); -510 } -511 inputDescriptors.add( input ); -512 } -513 onInputChangeDescriptors.add( new OnInputChangeDescriptor( method, inputDescriptors, preUpdate ) ); -514 } -515 descriptor.setOnInputChangeDescriptors( onInputChangeDescriptors ); -516 } -517 -518 @Nonnull -519 private String deriveOnInputChangeName( @Nonnull final VariableElement parameter ) -520 { -521 final AnnotationValue value = -522 AnnotationsUtil.findAnnotationValue( parameter, Constants.INPUT_REF_CLASSNAME, "value" ); -523 -524 if ( null != value ) -525 { -526 return (String) value.getValue(); -527 } -528 else +495 AnnotationsUtil.hasNonnullAnnotation( parameter ) && +496 AnnotationsUtil.hasNullableAnnotation( input.getMethod() ) +497 ) || +498 ( +499 AnnotationsUtil.hasNullableAnnotation( parameter ) && +500 input.isNonNull() ); +501 +502 if ( mismatchedNullability ) +503 { +504 throw new ProcessorException( "@OnInputChange target has a parameter named '" + +505 parameter.getSimpleName() + "' that has a nullability annotation " + +506 "incompatible with the associated @Input method named " + +507 method.getSimpleName(), method ); +508 } +509 if ( input.isImmutable() ) +510 { +511 throw new ProcessorException( "@OnInputChange target has a parameter named '" + +512 parameter.getSimpleName() + "' that is associated with a @Input " + +513 "annotated method and the input is specified as immutable.", method ); +514 } +515 inputDescriptors.add( input ); +516 } +517 onInputChangeDescriptors.add( new OnInputChangeDescriptor( method, inputDescriptors, preUpdate ) ); +518 } +519 descriptor.setOnInputChangeDescriptors( onInputChangeDescriptors ); +520 } +521 +522 @Nonnull +523 private String deriveOnInputChangeName( @Nonnull final VariableElement parameter ) +524 { +525 final AnnotationValue value = +526 AnnotationsUtil.findAnnotationValue( parameter, Constants.INPUT_REF_CLASSNAME, "value" ); +527 +528 if ( null != value ) 529 { -530 final String parameterName = parameter.getSimpleName().toString(); -531 if ( LAST_INPUT_PATTERN.matcher( parameterName ).matches() || -532 PREV_INPUT_PATTERN.matcher( parameterName ).matches() ) -533 { -534 return Character.toLowerCase( parameterName.charAt( 4 ) ) + parameterName.substring( 5 ); -535 } -536 else if ( INPUT_PATTERN.matcher( parameterName ).matches() ) +530 return (String) value.getValue(); +531 } +532 else +533 { +534 final String parameterName = parameter.getSimpleName().toString(); +535 if ( LAST_INPUT_PATTERN.matcher( parameterName ).matches() || +536 PREV_INPUT_PATTERN.matcher( parameterName ).matches() ) 537 { -538 return parameterName; +538 return Character.toLowerCase( parameterName.charAt( 4 ) ) + parameterName.substring( 5 ); 539 } -540 else +540 else if ( INPUT_PATTERN.matcher( parameterName ).matches() ) 541 { -542 throw new ProcessorException( "@OnInputChange target has a parameter named '" + parameterName + -543 "' is not explicitly associated with a input using @InputRef nor does it " + -544 "follow required naming conventions 'prev[MyInput]', 'last[MyInput]' or " + -545 "'[myInput]'.", parameter ); -546 } -547 } -548 } -549 -550 private void determineInputValidatesMethods( @Nonnull final ViewDescriptor descriptor ) -551 { -552 final List<ExecutableElement> methods = -553 getMethods( descriptor.getElement() ).stream() -554 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_VALIDATE_CLASSNAME ) ) -555 .collect( Collectors.toList() ); -556 -557 for ( final ExecutableElement method : methods ) -558 { -559 final String name = deriveInputValidateName( method ); -560 final InputDescriptor input = descriptor.findInputNamed( name ); -561 if ( null == input ) -562 { -563 throw new ProcessorException( "@InputValidate target for input named '" + name + "' has no corresponding " + -564 "@Input annotated method.", method ); -565 } -566 if ( 1 != method.getParameters().size() ) -567 { -568 throw new ProcessorException( "@InputValidate target must have exactly 1 parameter", method ); -569 } -570 final ExecutableType methodType = resolveMethodType( descriptor, method ); -571 if ( !processingEnv.getTypeUtils().isAssignable( methodType.getParameterTypes().get( 0 ), -572 input.getMethodType().getReturnType() ) ) +542 return parameterName; +543 } +544 else +545 { +546 throw new ProcessorException( "@OnInputChange target has a parameter named '" + parameterName + +547 "' is not explicitly associated with a input using @InputRef nor does it " + +548 "follow required naming conventions 'prev[MyInput]', 'last[MyInput]' or " + +549 "'[myInput]'.", parameter ); +550 } +551 } +552 } +553 +554 private void determineInputValidatesMethods( @Nonnull final ViewDescriptor descriptor, +555 @Nonnull final List<ExecutableElement> methods ) +556 { +557 final List<ExecutableElement> inputValidateMethods = +558 methods +559 .stream() +560 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_VALIDATE_CLASSNAME ) ) +561 .toList(); +562 +563 for ( final ExecutableElement method : inputValidateMethods ) +564 { +565 final String name = deriveInputValidateName( method ); +566 final InputDescriptor input = descriptor.findInputNamed( name ); +567 if ( null == input ) +568 { +569 throw new ProcessorException( "@InputValidate target for input named '" + name + "' has no corresponding " + +570 "@Input annotated method.", method ); +571 } +572 if ( 1 != method.getParameters().size() ) 573 { -574 throw new ProcessorException( "@InputValidate target has a parameter type that is not assignable to the " + -575 "return type of the associated @Input annotated method.", method ); -576 } -577 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), -578 Constants.VIEW_CLASSNAME, -579 Constants.INPUT_VALIDATE_CLASSNAME, -580 method ); -581 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_VALIDATE_CLASSNAME, method ); -582 MemberChecks.mustNotReturnAnyValue( Constants.INPUT_VALIDATE_CLASSNAME, method ); -583 -584 final VariableElement param = method.getParameters().get( 0 ); -585 final boolean mismatchedNullability = -586 ( -587 AnnotationsUtil.hasNonnullAnnotation( param ) && -588 AnnotationsUtil.hasNullableAnnotation( input.getMethod() ) -589 ) || -590 ( -591 AnnotationsUtil.hasNullableAnnotation( param ) && -592 input.isNonNull() ); -593 -594 if ( mismatchedNullability ) -595 { -596 throw new ProcessorException( "@InputValidate target has a parameter that has a nullability annotation " + -597 "incompatible with the associated @Input method named " + -598 input.getMethod().getSimpleName(), method ); -599 } -600 input.setValidateMethod( method ); -601 } -602 } -603 -604 @Nonnull -605 private String deriveInputValidateName( @Nonnull final Element element ) -606 throws ProcessorException -607 { -608 final String name = -609 (String) AnnotationsUtil.getAnnotationValue( element, Constants.INPUT_VALIDATE_CLASSNAME, "name" ) -610 .getValue(); -611 -612 if ( isSentinelName( name ) ) -613 { -614 final String deriveName = deriveName( element, VALIDATE_INPUT_PATTERN, name ); -615 if ( null == deriveName ) -616 { -617 throw new ProcessorException( "@InputValidate target has not specified name nor is it named according " + -618 "to the convention 'validate[Name]Input'.", element ); -619 } -620 return deriveName; -621 } -622 else -623 { -624 if ( !SourceVersion.isIdentifier( name ) ) -625 { -626 throw new ProcessorException( "@InputValidate target specified an invalid name '" + name + "'. The " + -627 "name must be a valid java identifier.", element ); -628 } -629 else if ( SourceVersion.isKeyword( name ) ) -630 { -631 throw new ProcessorException( "@InputValidate target specified an invalid name '" + name + "'. The " + -632 "name must not be a java keyword.", element ); -633 } -634 return name; -635 } -636 } -637 -638 private void determineDefaultInputsMethods( @Nonnull final ViewDescriptor descriptor ) -639 { -640 final List<ExecutableElement> defaultInputsMethods = -641 getMethods( descriptor.getElement() ).stream() -642 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_DEFAULT_CLASSNAME ) ) -643 .collect( Collectors.toList() ); -644 -645 for ( final ExecutableElement method : defaultInputsMethods ) -646 { -647 final String name = deriveInputDefaultName( method ); -648 final InputDescriptor input = descriptor.findInputNamed( name ); -649 if ( null == input ) -650 { -651 throw new ProcessorException( "@InputDefault target for input named '" + name + "' has no corresponding " + -652 "@Input annotated method.", method ); -653 } -654 final ExecutableType methodType = resolveMethodType( descriptor, method ); -655 if ( !processingEnv.getTypeUtils().isAssignable( methodType.getReturnType(), -656 input.getMethodType().getReturnType() ) ) -657 { -658 throw new ProcessorException( "@InputDefault target has a return type that is not assignable to the " + -659 "return type of the associated @Input annotated method.", method ); -660 } -661 MemberChecks.mustBeStaticallySubclassCallable( descriptor.getElement(), -662 Constants.VIEW_CLASSNAME, -663 Constants.INPUT_DEFAULT_CLASSNAME, -664 method ); -665 MemberChecks.mustNotHaveAnyParameters( Constants.INPUT_DEFAULT_CLASSNAME, method ); -666 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_DEFAULT_CLASSNAME, method ); -667 MemberChecks.mustReturnAValue( Constants.INPUT_DEFAULT_CLASSNAME, method ); -668 -669 input.setDefaultMethod( method ); -670 } -671 } -672 -673 private void determineDefaultInputsFields( @Nonnull final ViewDescriptor descriptor ) -674 { -675 final List<VariableElement> defaultInputsFields = -676 ElementsUtil.getFields( descriptor.getElement() ).stream() -677 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_DEFAULT_CLASSNAME ) ) -678 .collect( Collectors.toList() ); -679 -680 for ( final VariableElement field : defaultInputsFields ) -681 { -682 final String name = deriveInputDefaultName( field ); -683 final InputDescriptor input = descriptor.findInputNamed( name ); -684 if ( null == input ) -685 { -686 throw new ProcessorException( "@InputDefault target for input named '" + name + "' has no corresponding " + -687 "@Input annotated method.", field ); -688 } -689 if ( !processingEnv.getTypeUtils().isAssignable( field.asType(), input.getMethodType().getReturnType() ) ) -690 { -691 throw new ProcessorException( "@InputDefault target has a type that is not assignable to the " + -692 "return type of the associated @Input annotated method.", field ); -693 } -694 MemberChecks.mustBeStaticallySubclassCallable( descriptor.getElement(), -695 Constants.VIEW_CLASSNAME, -696 Constants.INPUT_DEFAULT_CLASSNAME, -697 field ); -698 MemberChecks.mustBeFinal( Constants.INPUT_DEFAULT_CLASSNAME, field ); -699 input.setDefaultField( field ); -700 } -701 } -702 -703 @Nonnull -704 private String deriveInputDefaultName( @Nonnull final Element element ) -705 throws ProcessorException -706 { -707 final String name = -708 (String) AnnotationsUtil.getAnnotationValue( element, Constants.INPUT_DEFAULT_CLASSNAME, "name" ) -709 .getValue(); +574 throw new ProcessorException( "@InputValidate target must have exactly 1 parameter", method ); +575 } +576 final ExecutableType methodType = resolveMethodType( descriptor, method ); +577 if ( !processingEnv.getTypeUtils().isAssignable( methodType.getParameterTypes().get( 0 ), +578 input.getMethodType().getReturnType() ) ) +579 { +580 throw new ProcessorException( "@InputValidate target has a parameter type that is not assignable to the " + +581 "return type of the associated @Input annotated method.", method ); +582 } +583 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), +584 Constants.VIEW_CLASSNAME, +585 Constants.INPUT_VALIDATE_CLASSNAME, +586 method ); +587 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_VALIDATE_CLASSNAME, method ); +588 MemberChecks.mustNotReturnAnyValue( Constants.INPUT_VALIDATE_CLASSNAME, method ); +589 +590 final VariableElement param = method.getParameters().get( 0 ); +591 final boolean mismatchedNullability = +592 ( +593 AnnotationsUtil.hasNonnullAnnotation( param ) && +594 AnnotationsUtil.hasNullableAnnotation( input.getMethod() ) +595 ) || +596 ( +597 AnnotationsUtil.hasNullableAnnotation( param ) && +598 input.isNonNull() ); +599 +600 if ( mismatchedNullability ) +601 { +602 throw new ProcessorException( "@InputValidate target has a parameter that has a nullability annotation " + +603 "incompatible with the associated @Input method named " + +604 input.getMethod().getSimpleName(), method ); +605 } +606 input.setValidateMethod( method ); +607 } +608 } +609 +610 @Nonnull +611 private String deriveInputValidateName( @Nonnull final Element element ) +612 throws ProcessorException +613 { +614 final String name = +615 (String) AnnotationsUtil.getAnnotationValue( element, Constants.INPUT_VALIDATE_CLASSNAME, "name" ) +616 .getValue(); +617 +618 if ( isSentinelName( name ) ) +619 { +620 final String deriveName = deriveName( element, VALIDATE_INPUT_PATTERN, name ); +621 if ( null == deriveName ) +622 { +623 throw new ProcessorException( "@InputValidate target has not specified name nor is it named according " + +624 "to the convention 'validate[Name]Input'.", element ); +625 } +626 return deriveName; +627 } +628 else +629 { +630 if ( !SourceVersion.isIdentifier( name ) ) +631 { +632 throw new ProcessorException( "@InputValidate target specified an invalid name '" + name + "'. The " + +633 "name must be a valid java identifier.", element ); +634 } +635 else if ( SourceVersion.isKeyword( name ) ) +636 { +637 throw new ProcessorException( "@InputValidate target specified an invalid name '" + name + "'. The " + +638 "name must not be a java keyword.", element ); +639 } +640 return name; +641 } +642 } +643 +644 private void determineDefaultInputsMethods( @Nonnull final ViewDescriptor descriptor, +645 @Nonnull final List<ExecutableElement> methods ) +646 { +647 final List<ExecutableElement> defaultInputsMethods = +648 methods +649 .stream() +650 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_DEFAULT_CLASSNAME ) ) +651 .toList(); +652 +653 for ( final ExecutableElement method : defaultInputsMethods ) +654 { +655 final String name = deriveInputDefaultName( method ); +656 final InputDescriptor input = descriptor.findInputNamed( name ); +657 if ( null == input ) +658 { +659 throw new ProcessorException( "@InputDefault target for input named '" + name + "' has no corresponding " + +660 "@Input annotated method.", method ); +661 } +662 final ExecutableType methodType = resolveMethodType( descriptor, method ); +663 if ( !processingEnv.getTypeUtils().isAssignable( methodType.getReturnType(), +664 input.getMethodType().getReturnType() ) ) +665 { +666 throw new ProcessorException( "@InputDefault target has a return type that is not assignable to the " + +667 "return type of the associated @Input annotated method.", method ); +668 } +669 MemberChecks.mustBeStaticallySubclassCallable( descriptor.getElement(), +670 Constants.VIEW_CLASSNAME, +671 Constants.INPUT_DEFAULT_CLASSNAME, +672 method ); +673 MemberChecks.mustNotHaveAnyParameters( Constants.INPUT_DEFAULT_CLASSNAME, method ); +674 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_DEFAULT_CLASSNAME, method ); +675 MemberChecks.mustReturnAValue( Constants.INPUT_DEFAULT_CLASSNAME, method ); +676 +677 input.setDefaultMethod( method ); +678 } +679 } +680 +681 private void determineDefaultInputsFields( @Nonnull final ViewDescriptor descriptor ) +682 { +683 final List<VariableElement> defaultInputsFields = +684 ElementsUtil.getFields( descriptor.getElement() ).stream() +685 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_DEFAULT_CLASSNAME ) ) +686 .toList(); +687 +688 for ( final VariableElement field : defaultInputsFields ) +689 { +690 final String name = deriveInputDefaultName( field ); +691 final InputDescriptor input = descriptor.findInputNamed( name ); +692 if ( null == input ) +693 { +694 throw new ProcessorException( "@InputDefault target for input named '" + name + "' has no corresponding " + +695 "@Input annotated method.", field ); +696 } +697 if ( !processingEnv.getTypeUtils().isAssignable( field.asType(), input.getMethodType().getReturnType() ) ) +698 { +699 throw new ProcessorException( "@InputDefault target has a type that is not assignable to the " + +700 "return type of the associated @Input annotated method.", field ); +701 } +702 MemberChecks.mustBeStaticallySubclassCallable( descriptor.getElement(), +703 Constants.VIEW_CLASSNAME, +704 Constants.INPUT_DEFAULT_CLASSNAME, +705 field ); +706 MemberChecks.mustBeFinal( Constants.INPUT_DEFAULT_CLASSNAME, field ); +707 input.setDefaultField( field ); +708 } +709 } 710 -711 if ( isSentinelName( name ) ) -712 { -713 if ( element instanceof ExecutableElement ) -714 { -715 final String deriveName = deriveName( element, DEFAULT_GETTER_PATTERN, name ); -716 if ( null == deriveName ) -717 { -718 throw new ProcessorException( "@InputDefault target has not specified name nor is it named according " + -719 "to the convention 'get[Name]Default'.", element ); -720 } -721 return deriveName; -722 } -723 else -724 { -725 final String fieldName = element.getSimpleName().toString(); -726 boolean matched = true; -727 final int lengthPrefix = "DEFAULT_".length(); -728 final int length = fieldName.length(); -729 if ( fieldName.startsWith( "DEFAULT_" ) && length > lengthPrefix ) -730 { -731 for ( int i = lengthPrefix; i < length; i++ ) -732 { -733 final char ch = fieldName.charAt( i ); -734 if ( Character.isLowerCase( ch ) || -735 ( -736 ( i != lengthPrefix || !Character.isJavaIdentifierStart( ch ) ) && -737 ( i == lengthPrefix || !Character.isJavaIdentifierPart( ch ) ) -738 ) ) -739 { -740 matched = false; -741 break; -742 } -743 } -744 } -745 else -746 { -747 matched = false; -748 } -749 if ( matched ) -750 { -751 return uppercaseConstantToPascalCase( fieldName.substring( lengthPrefix ) ); +711 @Nonnull +712 private String deriveInputDefaultName( @Nonnull final Element element ) +713 throws ProcessorException +714 { +715 final String name = +716 (String) AnnotationsUtil.getAnnotationValue( element, Constants.INPUT_DEFAULT_CLASSNAME, "name" ) +717 .getValue(); +718 +719 if ( isSentinelName( name ) ) +720 { +721 if ( element instanceof ExecutableElement ) +722 { +723 final String deriveName = deriveName( element, DEFAULT_GETTER_PATTERN, name ); +724 if ( null == deriveName ) +725 { +726 throw new ProcessorException( "@InputDefault target has not specified name nor is it named according " + +727 "to the convention 'get[Name]Default'.", element ); +728 } +729 return deriveName; +730 } +731 else +732 { +733 final String fieldName = element.getSimpleName().toString(); +734 boolean matched = true; +735 final int lengthPrefix = "DEFAULT_".length(); +736 final int length = fieldName.length(); +737 if ( fieldName.startsWith( "DEFAULT_" ) && length > lengthPrefix ) +738 { +739 for ( int i = lengthPrefix; i < length; i++ ) +740 { +741 final char ch = fieldName.charAt( i ); +742 if ( Character.isLowerCase( ch ) || +743 ( +744 ( i != lengthPrefix || !Character.isJavaIdentifierStart( ch ) ) && +745 ( i == lengthPrefix || !Character.isJavaIdentifierPart( ch ) ) +746 ) ) +747 { +748 matched = false; +749 break; +750 } +751 } 752 } 753 else 754 { -755 throw new ProcessorException( "@InputDefault target has not specified name nor is it named according " + -756 "to the convention 'DEFAULT_[NAME]'.", element ); -757 } -758 } -759 } -760 else -761 { -762 if ( !SourceVersion.isIdentifier( name ) ) -763 { -764 throw new ProcessorException( "@InputDefault target specified an invalid name '" + name + "'. The " + -765 "name must be a valid java identifier.", element ); +755 matched = false; +756 } +757 if ( matched ) +758 { +759 return uppercaseConstantToPascalCase( fieldName.substring( lengthPrefix ) ); +760 } +761 else +762 { +763 throw new ProcessorException( "@InputDefault target has not specified name nor is it named according " + +764 "to the convention 'DEFAULT_[NAME]'.", element ); +765 } 766 } -767 else if ( SourceVersion.isKeyword( name ) ) -768 { -769 throw new ProcessorException( "@InputDefault target specified an invalid name '" + name + "'. The " + -770 "name must not be a java keyword.", element ); -771 } -772 return name; -773 } -774 } -775 -776 @Nonnull -777 private String uppercaseConstantToPascalCase( @Nonnull final String candidate ) -778 { -779 final String s = candidate.toLowerCase(); -780 final StringBuilder sb = new StringBuilder(); -781 boolean uppercase = false; -782 for ( int i = 0; i < s.length(); i++ ) -783 { -784 final char ch = s.charAt( i ); -785 if ( '_' == ch ) -786 { -787 uppercase = true; -788 } -789 else if ( uppercase ) -790 { -791 sb.append( Character.toUpperCase( ch ) ); -792 uppercase = false; -793 } -794 else -795 { -796 sb.append( ch ); -797 } -798 } -799 return sb.toString(); -800 } -801 -802 private void determineInputs( @Nonnull final ViewDescriptor descriptor ) -803 { -804 final List<InputDescriptor> inputs = -805 getMethods( descriptor.getElement() ).stream() -806 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_CLASSNAME ) ) -807 .map( m -> createInputDescriptor( descriptor, m ) ) -808 .collect( Collectors.toList() ); +767 } +768 else +769 { +770 if ( !SourceVersion.isIdentifier( name ) ) +771 { +772 throw new ProcessorException( "@InputDefault target specified an invalid name '" + name + "'. The " + +773 "name must be a valid java identifier.", element ); +774 } +775 else if ( SourceVersion.isKeyword( name ) ) +776 { +777 throw new ProcessorException( "@InputDefault target specified an invalid name '" + name + "'. The " + +778 "name must not be a java keyword.", element ); +779 } +780 return name; +781 } +782 } +783 +784 @Nonnull +785 private String uppercaseConstantToPascalCase( @Nonnull final String candidate ) +786 { +787 final String s = candidate.toLowerCase(); +788 final StringBuilder sb = new StringBuilder(); +789 boolean uppercase = false; +790 for ( int i = 0; i < s.length(); i++ ) +791 { +792 final char ch = s.charAt( i ); +793 if ( '_' == ch ) +794 { +795 uppercase = true; +796 } +797 else if ( uppercase ) +798 { +799 sb.append( Character.toUpperCase( ch ) ); +800 uppercase = false; +801 } +802 else +803 { +804 sb.append( ch ); +805 } +806 } +807 return sb.toString(); +808 } 809 -810 final InputDescriptor childrenInput = -811 inputs.stream().filter( p -> p.getName().equals( "children" ) ).findAny().orElse( null ); -812 final InputDescriptor childInput = -813 inputs.stream().filter( p -> p.getName().equals( "child" ) ).findAny().orElse( null ); -814 if ( null != childrenInput && null != childInput ) -815 { -816 throw new ProcessorException( "Multiple candidate children @Input annotated methods: " + -817 childrenInput.getMethod().getSimpleName() + " and " + -818 childInput.getMethod().getSimpleName(), -819 childrenInput.getMethod() ); -820 } -821 -822 descriptor.setInputs( inputs ); -823 } -824 -825 private boolean isInputRequired( @Nonnull final InputDescriptor input ) -826 { -827 final String requiredValue = input.getRequiredValue(); -828 if ( "ENABLE".equals( requiredValue ) ) -829 { -830 return true; -831 } -832 else if ( "DISABLE".equals( requiredValue ) ) -833 { -834 return false; -835 } -836 else if ( input.isContextSource() ) -837 { -838 return false; -839 } -840 else -841 { -842 return !input.hasDefaultMethod() && -843 !input.hasDefaultField() && -844 !AnnotationsUtil.hasNullableAnnotation( input.getMethod() ); +810 private void determineInputs( @Nonnull final ViewDescriptor descriptor, +811 @Nonnull final List<ExecutableElement> methods ) +812 { +813 final List<InputDescriptor> inputs = +814 methods +815 .stream() +816 .filter( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.INPUT_CLASSNAME ) ) +817 .map( m -> createInputDescriptor( descriptor, methods, m ) ) +818 .collect( Collectors.toList() ); +819 +820 final InputDescriptor childrenInput = +821 inputs.stream().filter( p -> p.getName().equals( "children" ) ).findAny().orElse( null ); +822 final InputDescriptor childInput = +823 inputs.stream().filter( p -> p.getName().equals( "child" ) ).findAny().orElse( null ); +824 if ( null != childrenInput && null != childInput ) +825 { +826 throw new ProcessorException( "Multiple candidate children @Input annotated methods: " + +827 childrenInput.getMethod().getSimpleName() + " and " + +828 childInput.getMethod().getSimpleName(), +829 childrenInput.getMethod() ); +830 } +831 +832 descriptor.setInputs( inputs ); +833 } +834 +835 private boolean isInputRequired( @Nonnull final InputDescriptor input ) +836 { +837 final String requiredValue = input.getRequiredValue(); +838 if ( "ENABLE".equals( requiredValue ) ) +839 { +840 return true; +841 } +842 else if ( "DISABLE".equals( requiredValue ) ) +843 { +844 return false; 845 } -846 } -847 -848 @Nonnull -849 private InputDescriptor createInputDescriptor( @Nonnull final ViewDescriptor descriptor, -850 @Nonnull final ExecutableElement method ) -851 { -852 final String name = deriveInputName( method ); -853 final ExecutableType methodType = resolveMethodType( descriptor, method ); -854 -855 verifyNoDuplicateAnnotations( method ); -856 MemberChecks.mustBeAbstract( Constants.INPUT_CLASSNAME, method ); -857 MemberChecks.mustNotHaveAnyParameters( Constants.INPUT_CLASSNAME, method ); -858 MemberChecks.mustReturnAValue( Constants.INPUT_CLASSNAME, method ); -859 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_CLASSNAME, method ); -860 MemberChecks.mustNotBePackageAccessInDifferentPackage( descriptor.getElement(), -861 Constants.VIEW_CLASSNAME, -862 Constants.INPUT_CLASSNAME, -863 method ); -864 final TypeMirror returnType = method.getReturnType(); -865 if ( "build".equals( name ) ) -866 { -867 throw new ProcessorException( "@Input named 'build' is invalid as it conflicts with the method named " + -868 "build() that is used in the generated Builder classes", -869 method ); -870 } -871 else if ( "child".equals( name ) && -872 ( returnType.getKind() != TypeKind.DECLARED && !"react4j.ReactNode".equals( returnType.toString() ) ) ) -873 { -874 throw new ProcessorException( "@Input named 'child' should be of type react4j.ReactNode", method ); -875 } -876 else if ( "children".equals( name ) && -877 ( returnType.getKind() != TypeKind.DECLARED && !"react4j.ReactNode[]".equals( returnType.toString() ) ) ) -878 { -879 throw new ProcessorException( "@Input named 'children' should be of type react4j.ReactNode[]", method ); -880 } -881 -882 if ( returnType instanceof TypeVariable ) -883 { -884 final TypeVariable typeVariable = (TypeVariable) returnType; -885 final String typeVariableName = typeVariable.asElement().getSimpleName().toString(); -886 List<? extends TypeParameterElement> typeParameters = method.getTypeParameters(); -887 if ( typeParameters.stream().anyMatch( p -> p.getSimpleName().toString().equals( typeVariableName ) ) ) -888 { -889 throw new ProcessorException( "@Input named '" + name + "' is has a type variable as a return type " + -890 "that is declared on the method.", method ); -891 } -892 } -893 final String qualifier = (String) AnnotationsUtil -894 .getAnnotationValue( method, Constants.INPUT_CLASSNAME, "qualifier" ).getValue(); -895 final boolean contextInput = isContextInput( method ); -896 final Element inputType = processingEnv.getTypeUtils().asElement( returnType ); -897 final boolean immutable = isInputImmutable( method ); -898 final boolean observable = isInputObservable( descriptor, method, immutable ); -899 final boolean disposable = null != inputType && isInputDisposable( method, inputType ); -900 final TypeName typeName = TypeName.get( returnType ); -901 if ( typeName.isBoxedPrimitive() && AnnotationsUtil.hasNonnullAnnotation( method ) ) -902 { -903 throw new ProcessorException( "@Input named '" + name + "' is a boxed primitive annotated with a " + -904 "@Nonnull annotation. The return type should be the primitive type.", -905 method ); -906 } -907 final ImmutableInputKeyStrategy strategy = immutable ? getImmutableInputKeyStrategy( typeName, inputType ) : null; -908 if ( !"".equals( qualifier ) && !contextInput ) -909 { -910 throw new ProcessorException( MemberChecks.mustNot( Constants.INPUT_CLASSNAME, -911 "specify qualifier parameter unless source=CONTEXT is also specified" ), -912 method ); -913 } -914 final String requiredValue = -915 ( (VariableElement) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "require" ) -916 .getValue() ) -917 .getSimpleName().toString(); -918 -919 final boolean dependency = isInputDependency( method, immutable, disposable ); -920 -921 final InputDescriptor inputDescriptor = -922 new InputDescriptor( descriptor, -923 name, -924 qualifier, -925 method, -926 methodType, -927 inputType, -928 contextInput, -929 !immutable, -930 observable, -931 disposable, -932 dependency, -933 strategy, -934 requiredValue ); -935 if ( inputDescriptor.mayNeedMutableInputAccessedInPostConstructInvariant() ) -936 { -937 if ( ElementsUtil.isWarningSuppressed( method, -938 Constants.WARNING_MUTABLE_INPUT_ACCESSED_IN_POST_CONSTRUCT, -939 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) -940 { -941 inputDescriptor.suppressMutableInputAccessedInPostConstruct(); -942 } -943 } -944 return inputDescriptor; -945 } -946 -947 @Nonnull -948 private ImmutableInputKeyStrategy getImmutableInputKeyStrategy( @Nonnull final TypeName typeName, -949 @Nullable final Element element ) -950 { -951 if ( typeName.toString().equals( "java.lang.String" ) ) -952 { -953 return ImmutableInputKeyStrategy.IS_STRING; -954 } -955 else if ( typeName.isBoxedPrimitive() || typeName.isPrimitive() ) -956 { -957 return ImmutableInputKeyStrategy.TO_STRING; -958 } -959 else if ( null != element ) -960 { -961 if ( ( ElementKind.CLASS == element.getKind() || ElementKind.INTERFACE == element.getKind() ) && -962 isAssignableToKeyed( element ) ) -963 { -964 return ImmutableInputKeyStrategy.KEYED; -965 } -966 else if ( ( ElementKind.CLASS == element.getKind() || ElementKind.INTERFACE == element.getKind() ) && -967 ( -968 isAssignableToIdentifiable( element ) || -969 AnnotationsUtil.hasAnnotationOfType( element, Constants.ACT_AS_COMPONENT_CLASSNAME ) || -970 ( AnnotationsUtil.hasAnnotationOfType( element, Constants.AREZ_COMPONENT_CLASSNAME ) && -971 isIdRequired( (TypeElement) element ) ) -972 ) ) +846 else if ( input.isContextSource() ) +847 { +848 return false; +849 } +850 else +851 { +852 return !input.hasDefaultMethod() && +853 !input.hasDefaultField() && +854 !AnnotationsUtil.hasNullableAnnotation( input.getMethod() ); +855 } +856 } +857 +858 @Nonnull +859 private InputDescriptor createInputDescriptor( @Nonnull final ViewDescriptor descriptor, +860 @Nonnull final List<ExecutableElement> methods, +861 @Nonnull final ExecutableElement method ) +862 { +863 final String name = deriveInputName( method ); +864 final ExecutableType methodType = resolveMethodType( descriptor, method ); +865 +866 verifyNoDuplicateAnnotations( method ); +867 MemberChecks.mustBeAbstract( Constants.INPUT_CLASSNAME, method ); +868 MemberChecks.mustNotHaveAnyParameters( Constants.INPUT_CLASSNAME, method ); +869 MemberChecks.mustReturnAValue( Constants.INPUT_CLASSNAME, method ); +870 MemberChecks.mustNotThrowAnyExceptions( Constants.INPUT_CLASSNAME, method ); +871 MemberChecks.mustNotBePackageAccessInDifferentPackage( descriptor.getElement(), +872 Constants.VIEW_CLASSNAME, +873 Constants.INPUT_CLASSNAME, +874 method ); +875 final TypeMirror returnType = method.getReturnType(); +876 if ( "build".equals( name ) ) +877 { +878 throw new ProcessorException( "@Input named 'build' is invalid as it conflicts with the method named " + +879 "build() that is used in the generated Builder classes", +880 method ); +881 } +882 else if ( "child".equals( name ) && +883 ( returnType.getKind() != TypeKind.DECLARED && !"react4j.ReactNode".equals( returnType.toString() ) ) ) +884 { +885 throw new ProcessorException( "@Input named 'child' should be of type react4j.ReactNode", method ); +886 } +887 else if ( "children".equals( name ) && +888 ( returnType.getKind() != TypeKind.DECLARED && !"react4j.ReactNode[]".equals( returnType.toString() ) ) ) +889 { +890 throw new ProcessorException( "@Input named 'children' should be of type react4j.ReactNode[]", method ); +891 } +892 +893 if ( returnType instanceof final TypeVariable typeVariable ) +894 { +895 final String typeVariableName = typeVariable.asElement().getSimpleName().toString(); +896 List<? extends TypeParameterElement> typeParameters = method.getTypeParameters(); +897 if ( typeParameters.stream().anyMatch( p -> p.getSimpleName().toString().equals( typeVariableName ) ) ) +898 { +899 throw new ProcessorException( "@Input named '" + name + "' is has a type variable as a return type " + +900 "that is declared on the method.", method ); +901 } +902 } +903 final String qualifier = (String) AnnotationsUtil +904 .getAnnotationValue( method, Constants.INPUT_CLASSNAME, "qualifier" ).getValue(); +905 final boolean contextInput = isContextInput( method ); +906 final Element inputType = processingEnv.getTypeUtils().asElement( returnType ); +907 final boolean immutable = isInputImmutable( method ); +908 final boolean observable = isInputObservable( methods, method, immutable ); +909 final boolean disposable = null != inputType && isInputDisposable( method, inputType ); +910 final TypeName typeName = TypeName.get( returnType ); +911 if ( typeName.isBoxedPrimitive() && AnnotationsUtil.hasNonnullAnnotation( method ) ) +912 { +913 throw new ProcessorException( "@Input named '" + name + "' is a boxed primitive annotated with a " + +914 "@Nonnull annotation. The return type should be the primitive type.", +915 method ); +916 } +917 final ImmutableInputKeyStrategy strategy = immutable ? getImmutableInputKeyStrategy( typeName, inputType ) : null; +918 if ( !"".equals( qualifier ) && !contextInput ) +919 { +920 throw new ProcessorException( MemberChecks.mustNot( Constants.INPUT_CLASSNAME, +921 "specify qualifier parameter unless source=CONTEXT is also specified" ), +922 method ); +923 } +924 final String requiredValue = +925 ( (VariableElement) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "require" ) +926 .getValue() ) +927 .getSimpleName().toString(); +928 +929 final boolean dependency = isInputDependency( method, immutable, disposable ); +930 +931 final InputDescriptor inputDescriptor = +932 new InputDescriptor( descriptor, +933 name, +934 qualifier, +935 method, +936 methodType, +937 inputType, +938 contextInput, +939 !immutable, +940 observable, +941 disposable, +942 dependency, +943 strategy, +944 requiredValue ); +945 if ( inputDescriptor.mayNeedMutableInputAccessedInPostConstructInvariant() ) +946 { +947 if ( ElementsUtil.isWarningSuppressed( method, +948 Constants.WARNING_MUTABLE_INPUT_ACCESSED_IN_POST_CONSTRUCT, +949 Constants.SUPPRESS_REACT4J_WARNINGS_CLASSNAME ) ) +950 { +951 inputDescriptor.suppressMutableInputAccessedInPostConstruct(); +952 } +953 } +954 return inputDescriptor; +955 } +956 +957 @Nonnull +958 private ImmutableInputKeyStrategy getImmutableInputKeyStrategy( @Nonnull final TypeName typeName, +959 @Nullable final Element element ) +960 { +961 if ( typeName.toString().equals( "java.lang.String" ) ) +962 { +963 return ImmutableInputKeyStrategy.IS_STRING; +964 } +965 else if ( typeName.isBoxedPrimitive() || typeName.isPrimitive() ) +966 { +967 return ImmutableInputKeyStrategy.TO_STRING; +968 } +969 else if ( null != element ) +970 { +971 if ( ( ElementKind.CLASS == element.getKind() || ElementKind.INTERFACE == element.getKind() ) && +972 isAssignableToKeyed( element ) ) 973 { -974 return ImmutableInputKeyStrategy.AREZ_IDENTIFIABLE; +974 return ImmutableInputKeyStrategy.KEYED; 975 } -976 else if ( ElementKind.ENUM == element.getKind() ) -977 { -978 return ImmutableInputKeyStrategy.ENUM; -979 } -980 } -981 return ImmutableInputKeyStrategy.DYNAMIC; -982 } -983 -984 private boolean isAssignableToKeyed( @Nonnull final Element element ) -985 { -986 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( Constants.KEYED_CLASSNAME ); -987 return processingEnv.getTypeUtils().isAssignable( element.asType(), typeElement.asType() ); -988 } -989 -990 private boolean isAssignableToIdentifiable( @Nonnull final Element element ) -991 { -992 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( Constants.IDENTIFIABLE_CLASSNAME ); -993 final TypeMirror identifiableErasure = processingEnv.getTypeUtils().erasure( typeElement.asType() ); -994 return processingEnv.getTypeUtils().isAssignable( element.asType(), identifiableErasure ); -995 } -996 -997 /** -998 * The logic from this method has been cloned from Arez. -999 * One day we should consider improving Arez so that this is not required somehow? -1000 */ -1001 private boolean isIdRequired( @Nonnull final TypeElement element ) -1002 { -1003 final VariableElement requireIdParameter = (VariableElement) -1004 AnnotationsUtil.getAnnotationValue( element, Constants.AREZ_COMPONENT_CLASSNAME, "requireId" ) -1005 .getValue(); -1006 return !"DISABLE".equals( requireIdParameter.getSimpleName().toString() ); -1007 } -1008 -1009 @Nonnull -1010 private String deriveInputName( @Nonnull final ExecutableElement method ) -1011 throws ProcessorException +976 else if ( ( ElementKind.CLASS == element.getKind() || ElementKind.INTERFACE == element.getKind() ) && +977 ( +978 isAssignableToIdentifiable( element ) || +979 AnnotationsUtil.hasAnnotationOfType( element, Constants.ACT_AS_COMPONENT_CLASSNAME ) || +980 ( AnnotationsUtil.hasAnnotationOfType( element, Constants.AREZ_COMPONENT_CLASSNAME ) && +981 isIdRequired( (TypeElement) element ) ) +982 ) ) +983 { +984 return ImmutableInputKeyStrategy.AREZ_IDENTIFIABLE; +985 } +986 else if ( ElementKind.ENUM == element.getKind() ) +987 { +988 return ImmutableInputKeyStrategy.ENUM; +989 } +990 } +991 return ImmutableInputKeyStrategy.DYNAMIC; +992 } +993 +994 private boolean isAssignableToKeyed( @Nonnull final Element element ) +995 { +996 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( Constants.KEYED_CLASSNAME ); +997 return processingEnv.getTypeUtils().isAssignable( element.asType(), typeElement.asType() ); +998 } +999 +1000 private boolean isAssignableToIdentifiable( @Nonnull final Element element ) +1001 { +1002 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( Constants.IDENTIFIABLE_CLASSNAME ); +1003 final TypeMirror identifiableErasure = processingEnv.getTypeUtils().erasure( typeElement.asType() ); +1004 return processingEnv.getTypeUtils().isAssignable( element.asType(), identifiableErasure ); +1005 } +1006 +1007 /** +1008 * The logic from this method has been cloned from Arez. +1009 * One day we should consider improving Arez so that this is not required somehow? +1010 */ +1011 private boolean isIdRequired( @Nonnull final TypeElement element ) 1012 { -1013 final String specifiedName = -1014 (String) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "name" ).getValue(); -1015 -1016 final String name = getPropertyAccessorName( method, specifiedName ); -1017 if ( !SourceVersion.isIdentifier( name ) ) -1018 { -1019 throw new ProcessorException( "@Input target specified an invalid name '" + specifiedName + "'. The " + -1020 "name must be a valid java identifier.", method ); -1021 } -1022 else if ( SourceVersion.isKeyword( name ) ) -1023 { -1024 throw new ProcessorException( "@Input target specified an invalid name '" + specifiedName + "'. The " + -1025 "name must not be a java keyword.", method ); -1026 } -1027 else +1013 final VariableElement requireIdParameter = (VariableElement) +1014 AnnotationsUtil.getAnnotationValue( element, Constants.AREZ_COMPONENT_CLASSNAME, "requireId" ) +1015 .getValue(); +1016 return !"DISABLE".equals( requireIdParameter.getSimpleName().toString() ); +1017 } +1018 +1019 @Nonnull +1020 private String deriveInputName( @Nonnull final ExecutableElement method ) +1021 throws ProcessorException +1022 { +1023 final String specifiedName = +1024 (String) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "name" ).getValue(); +1025 +1026 final String name = getPropertyAccessorName( method, specifiedName ); +1027 if ( !SourceVersion.isIdentifier( name ) ) 1028 { -1029 return name; -1030 } -1031 } -1032 -1033 private void determineOnErrorMethod( @Nonnull final TypeElement typeElement, -1034 @Nonnull final ViewDescriptor descriptor ) -1035 { -1036 for ( final ExecutableElement method : getMethods( typeElement ) ) -1037 { -1038 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.ON_ERROR_CLASSNAME ) ) -1039 { -1040 MemberChecks.mustNotBeAbstract( Constants.ON_ERROR_CLASSNAME, method ); -1041 MemberChecks.mustBeSubclassCallable( typeElement, -1042 Constants.VIEW_CLASSNAME, -1043 Constants.ON_ERROR_CLASSNAME, -1044 method ); -1045 MemberChecks.mustNotReturnAnyValue( Constants.ON_ERROR_CLASSNAME, method ); -1046 MemberChecks.mustNotThrowAnyExceptions( Constants.ON_ERROR_CLASSNAME, method ); -1047 -1048 boolean infoFound = false; -1049 boolean errorFound = false; -1050 for ( final VariableElement parameter : method.getParameters() ) -1051 { -1052 final TypeName typeName = TypeName.get( parameter.asType() ); -1053 if ( typeName.toString().equals( Constants.ERROR_INFO_CLASSNAME ) ) -1054 { -1055 if ( infoFound ) -1056 { -1057 throw new ProcessorException( "@OnError target has multiple parameters of type " + -1058 Constants.ERROR_INFO_CLASSNAME, -1059 method ); -1060 } -1061 infoFound = true; -1062 } -1063 else if ( typeName.toString().equals( Constants.JS_ERROR_CLASSNAME ) ) -1064 { -1065 if ( errorFound ) -1066 { -1067 throw new ProcessorException( "@OnError target has multiple parameters of type " + -1068 Constants.JS_ERROR_CLASSNAME, -1069 method ); -1070 } -1071 errorFound = true; -1072 } -1073 else -1074 { -1075 throw new ProcessorException( "@OnError target has parameter of invalid type named " + -1076 parameter.getSimpleName().toString(), -1077 parameter ); -1078 } -1079 } -1080 descriptor.setOnError( method ); -1081 } -1082 } -1083 } -1084 -1085 private void determineScheduleRenderMethods( @Nonnull final TypeElement typeElement, -1086 @Nonnull final ViewDescriptor descriptor ) -1087 { -1088 final List<ScheduleRenderDescriptor> scheduleRenderDescriptors = new ArrayList<>(); -1089 for ( final ExecutableElement method : getMethods( typeElement ) ) -1090 { -1091 final AnnotationMirror annotation = -1092 AnnotationsUtil.findAnnotationByType( method, Constants.SCHEDULE_RENDER_CLASSNAME ); -1093 if ( null != annotation ) -1094 { -1095 MemberChecks.mustBeAbstract( Constants.SCHEDULE_RENDER_CLASSNAME, method ); -1096 MemberChecks.mustBeSubclassCallable( typeElement, -1097 Constants.VIEW_CLASSNAME, -1098 Constants.SCHEDULE_RENDER_CLASSNAME, -1099 method ); -1100 MemberChecks.mustNotReturnAnyValue( Constants.SCHEDULE_RENDER_CLASSNAME, method ); -1101 MemberChecks.mustNotThrowAnyExceptions( Constants.SCHEDULE_RENDER_CLASSNAME, method ); -1102 -1103 final ViewType viewType = descriptor.getType(); -1104 if ( ViewType.STATEFUL != viewType ) -1105 { -1106 final String message = -1107 MemberChecks.mustNot( Constants.SCHEDULE_RENDER_CLASSNAME, -1108 "be enclosed in a type if it is annotated by @View(type=" + viewType + -1109 "). The type must be STATEFUL" ); -1110 throw new ProcessorException( message, method ); -1111 } -1112 -1113 final boolean skipShouldViewUpdate = -1114 AnnotationsUtil.getAnnotationValueValue( annotation, "skipShouldViewUpdate" ); -1115 -1116 scheduleRenderDescriptors.add( new ScheduleRenderDescriptor( method, skipShouldViewUpdate ) ); -1117 } -1118 } -1119 descriptor.setScheduleRenderDescriptors( scheduleRenderDescriptors ); -1120 } -1121 -1122 private void determinePublishMethods( @Nonnull final TypeElement typeElement, -1123 @Nonnull final ViewDescriptor descriptor ) -1124 { -1125 final List<PublishDescriptor> descriptors = new ArrayList<>(); -1126 for ( final ExecutableElement method : getMethods( typeElement ) ) -1127 { -1128 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( method, Constants.PUBLISH_CLASSNAME ); -1129 if ( null != annotation ) -1130 { -1131 MemberChecks.mustBeSubclassCallable( typeElement, -1132 Constants.VIEW_CLASSNAME, -1133 Constants.PUBLISH_CLASSNAME, -1134 method ); -1135 MemberChecks.mustNotHaveAnyParameters( Constants.PUBLISH_CLASSNAME, method ); -1136 MemberChecks.mustNotHaveAnyTypeParameters( Constants.PUBLISH_CLASSNAME, method ); -1137 MemberChecks.mustReturnAValue( Constants.PUBLISH_CLASSNAME, method ); -1138 MemberChecks.mustNotThrowAnyExceptions( Constants.PUBLISH_CLASSNAME, method ); -1139 -1140 final String qualifier = AnnotationsUtil.getAnnotationValueValue( annotation, "qualifier" ); -1141 final ExecutableType methodType = resolveMethodType( descriptor, method ); -1142 -1143 if ( TypeKind.TYPEVAR == methodType.getReturnType().getKind() ) -1144 { -1145 throw new ProcessorException( MemberChecks.mustNot( Constants.PUBLISH_CLASSNAME, "return a type variable" ), -1146 method ); -1147 } -1148 -1149 descriptors.add( new PublishDescriptor( qualifier, method, methodType ) ); -1150 } -1151 } -1152 descriptor.setPublishDescriptors( descriptors ); -1153 } -1154 -1155 private void determinePreRenderMethods( @Nonnull final TypeElement typeElement, -1156 @Nonnull final ViewDescriptor descriptor ) -1157 { -1158 final List<RenderHookDescriptor> descriptors = new ArrayList<>(); -1159 for ( final ExecutableElement method : getMethods( typeElement ) ) -1160 { -1161 final AnnotationMirror annotation = -1162 AnnotationsUtil.findAnnotationByType( method, Constants.PRE_RENDER_CLASSNAME ); -1163 if ( null != annotation ) -1164 { -1165 MemberChecks.mustBeSubclassCallable( typeElement, -1166 Constants.VIEW_CLASSNAME, -1167 Constants.PRE_RENDER_CLASSNAME, -1168 method ); -1169 MemberChecks.mustNotBeAbstract( Constants.PRE_RENDER_CLASSNAME, method ); -1170 MemberChecks.mustNotHaveAnyParameters( Constants.PRE_RENDER_CLASSNAME, method ); -1171 MemberChecks.mustNotHaveAnyTypeParameters( Constants.PRE_RENDER_CLASSNAME, method ); -1172 MemberChecks.mustNotReturnAnyValue( Constants.PRE_RENDER_CLASSNAME, method ); -1173 MemberChecks.mustNotThrowAnyExceptions( Constants.PRE_RENDER_CLASSNAME, method ); -1174 -1175 final int sortOrder = AnnotationsUtil.getAnnotationValueValue( annotation, "sortOrder" ); -1176 final ExecutableType methodType = resolveMethodType( descriptor, method ); -1177 -1178 descriptors.add( new RenderHookDescriptor( sortOrder, method, methodType ) ); -1179 } -1180 } -1181 descriptors.sort( Comparator.comparingInt( RenderHookDescriptor::getSortOrder ) ); -1182 descriptor.setPreRenderDescriptors( descriptors ); -1183 } -1184 -1185 private void determinePostRenderMethods( @Nonnull final TypeElement typeElement, -1186 @Nonnull final ViewDescriptor descriptor ) -1187 { -1188 final List<RenderHookDescriptor> descriptors = new ArrayList<>(); -1189 for ( final ExecutableElement method : getMethods( typeElement ) ) -1190 { -1191 final AnnotationMirror annotation = -1192 AnnotationsUtil.findAnnotationByType( method, Constants.POST_RENDER_CLASSNAME ); -1193 if ( null != annotation ) -1194 { -1195 MemberChecks.mustBeSubclassCallable( typeElement, -1196 Constants.VIEW_CLASSNAME, -1197 Constants.POST_RENDER_CLASSNAME, -1198 method ); -1199 MemberChecks.mustNotBeAbstract( Constants.POST_RENDER_CLASSNAME, method ); -1200 MemberChecks.mustNotHaveAnyParameters( Constants.POST_RENDER_CLASSNAME, method ); -1201 MemberChecks.mustNotHaveAnyTypeParameters( Constants.POST_RENDER_CLASSNAME, method ); -1202 MemberChecks.mustNotReturnAnyValue( Constants.POST_RENDER_CLASSNAME, method ); -1203 MemberChecks.mustNotThrowAnyExceptions( Constants.POST_RENDER_CLASSNAME, method ); -1204 -1205 final int sortOrder = AnnotationsUtil.getAnnotationValueValue( annotation, "sortOrder" ); -1206 final ExecutableType methodType = resolveMethodType( descriptor, method ); -1207 -1208 descriptors.add( new RenderHookDescriptor( sortOrder, method, methodType ) ); -1209 } -1210 } -1211 descriptors.sort( Comparator.comparingInt( RenderHookDescriptor::getSortOrder ) ); -1212 descriptor.setPostRenderDescriptors( descriptors ); -1213 } -1214 -1215 private void determineRenderMethod( @Nonnull final TypeElement typeElement, -1216 @Nonnull final ViewDescriptor descriptor ) -1217 { -1218 boolean foundRender = false; -1219 for ( final ExecutableElement method : getMethods( typeElement ) ) -1220 { -1221 final AnnotationMirror annotation = -1222 AnnotationsUtil.findAnnotationByType( method, Constants.RENDER_CLASSNAME ); -1223 if ( null != annotation ) -1224 { -1225 MemberChecks.mustNotBeAbstract( Constants.RENDER_CLASSNAME, method ); -1226 MemberChecks.mustBeSubclassCallable( typeElement, -1227 Constants.VIEW_CLASSNAME, -1228 Constants.RENDER_CLASSNAME, -1229 method ); -1230 MemberChecks.mustNotHaveAnyParameters( Constants.RENDER_CLASSNAME, method ); -1231 MemberChecks.mustReturnAnInstanceOf( processingEnv, -1232 method, -1233 Constants.RENDER_CLASSNAME, -1234 Constants.VNODE_CLASSNAME ); -1235 MemberChecks.mustNotThrowAnyExceptions( Constants.RENDER_CLASSNAME, method ); -1236 MemberChecks.mustNotHaveAnyTypeParameters( Constants.RENDER_CLASSNAME, method ); -1237 -1238 descriptor.setRender( method ); -1239 foundRender = true; -1240 } -1241 } -1242 final boolean requireRender = descriptor.requireRender(); -1243 if ( requireRender && !foundRender ) -1244 { -1245 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, -1246 "contain a method annotated with the " + -1247 MemberChecks.toSimpleName( Constants.RENDER_CLASSNAME ) + -1248 " annotation or must specify type=NO_RENDER" ), -1249 typeElement ); -1250 } -1251 else if ( !requireRender ) -1252 { -1253 if ( foundRender ) -1254 { -1255 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, -1256 "contain a method annotated with the " + -1257 MemberChecks.toSimpleName( Constants.RENDER_CLASSNAME ) + -1258 " annotation or must not specify type=NO_RENDER" ), -1259 typeElement ); -1260 } -1261 else if ( !descriptor.hasConstructor() && -1262 !descriptor.hasPostConstruct() && -1263 null == descriptor.getPostMount() && -1264 null == descriptor.getPostRender() && -1265 null == descriptor.getPreUpdate() && -1266 null == descriptor.getPostUpdate() && -1267 descriptor.getPreRenderDescriptors().isEmpty() && -1268 descriptor.getPostRenderDescriptors().isEmpty() && -1269 !descriptor.hasPreUpdateOnInputChange() && -1270 !descriptor.hasPostUpdateOnInputChange() ) -1271 { -1272 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, -1273 "contain lifecycle methods if the the @View(type=NO_RENDER) parameter is specified" ), -1274 typeElement ); -1275 } -1276 } -1277 } -1278 -1279 private void determinePostMountMethod( @Nonnull final TypeElement typeElement, -1280 @Nonnull final ViewDescriptor descriptor ) -1281 { -1282 for ( final ExecutableElement method : getMethods( typeElement ) ) -1283 { -1284 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_MOUNT_CLASSNAME ) ) -1285 { -1286 MemberChecks.mustBeLifecycleHook( typeElement, -1287 Constants.VIEW_CLASSNAME, -1288 Constants.POST_MOUNT_CLASSNAME, -1289 method ); -1290 descriptor.setPostMount( method ); +1029 throw new ProcessorException( "@Input target specified an invalid name '" + specifiedName + "'. The " + +1030 "name must be a valid java identifier.", method ); +1031 } +1032 else if ( SourceVersion.isKeyword( name ) ) +1033 { +1034 throw new ProcessorException( "@Input target specified an invalid name '" + specifiedName + "'. The " + +1035 "name must not be a java keyword.", method ); +1036 } +1037 else +1038 { +1039 return name; +1040 } +1041 } +1042 +1043 private void determineOnErrorMethod( @Nonnull final TypeElement typeElement, +1044 @Nonnull final ViewDescriptor descriptor, +1045 @Nonnull final List<ExecutableElement> methods ) +1046 { +1047 for ( final ExecutableElement method : methods ) +1048 { +1049 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.ON_ERROR_CLASSNAME ) ) +1050 { +1051 MemberChecks.mustNotBeAbstract( Constants.ON_ERROR_CLASSNAME, method ); +1052 MemberChecks.mustBeSubclassCallable( typeElement, +1053 Constants.VIEW_CLASSNAME, +1054 Constants.ON_ERROR_CLASSNAME, +1055 method ); +1056 MemberChecks.mustNotReturnAnyValue( Constants.ON_ERROR_CLASSNAME, method ); +1057 MemberChecks.mustNotThrowAnyExceptions( Constants.ON_ERROR_CLASSNAME, method ); +1058 +1059 boolean infoFound = false; +1060 boolean errorFound = false; +1061 for ( final VariableElement parameter : method.getParameters() ) +1062 { +1063 final TypeName typeName = TypeName.get( parameter.asType() ); +1064 if ( typeName.toString().equals( Constants.ERROR_INFO_CLASSNAME ) ) +1065 { +1066 if ( infoFound ) +1067 { +1068 throw new ProcessorException( "@OnError target has multiple parameters of type " + +1069 Constants.ERROR_INFO_CLASSNAME, +1070 method ); +1071 } +1072 infoFound = true; +1073 } +1074 else if ( typeName.toString().equals( Constants.JS_ERROR_CLASSNAME ) ) +1075 { +1076 if ( errorFound ) +1077 { +1078 throw new ProcessorException( "@OnError target has multiple parameters of type " + +1079 Constants.JS_ERROR_CLASSNAME, +1080 method ); +1081 } +1082 errorFound = true; +1083 } +1084 else +1085 { +1086 throw new ProcessorException( "@OnError target has parameter of invalid type named " + +1087 parameter.getSimpleName().toString(), +1088 parameter ); +1089 } +1090 } +1091 descriptor.setOnError( method ); +1092 } +1093 } +1094 } +1095 +1096 private void determineScheduleRenderMethods( @Nonnull final TypeElement typeElement, +1097 @Nonnull final ViewDescriptor descriptor, +1098 @Nonnull final List<ExecutableElement> methods ) +1099 { +1100 final List<ScheduleRenderDescriptor> scheduleRenderDescriptors = new ArrayList<>(); +1101 for ( final ExecutableElement method : methods ) +1102 { +1103 final AnnotationMirror annotation = +1104 AnnotationsUtil.findAnnotationByType( method, Constants.SCHEDULE_RENDER_CLASSNAME ); +1105 if ( null != annotation ) +1106 { +1107 MemberChecks.mustBeAbstract( Constants.SCHEDULE_RENDER_CLASSNAME, method ); +1108 MemberChecks.mustBeSubclassCallable( typeElement, +1109 Constants.VIEW_CLASSNAME, +1110 Constants.SCHEDULE_RENDER_CLASSNAME, +1111 method ); +1112 MemberChecks.mustNotReturnAnyValue( Constants.SCHEDULE_RENDER_CLASSNAME, method ); +1113 MemberChecks.mustNotThrowAnyExceptions( Constants.SCHEDULE_RENDER_CLASSNAME, method ); +1114 +1115 final ViewType viewType = descriptor.getType(); +1116 if ( ViewType.STATEFUL != viewType ) +1117 { +1118 final String message = +1119 MemberChecks.mustNot( Constants.SCHEDULE_RENDER_CLASSNAME, +1120 "be enclosed in a type if it is annotated by @View(type=" + viewType + +1121 "). The type must be STATEFUL" ); +1122 throw new ProcessorException( message, method ); +1123 } +1124 +1125 final boolean skipShouldViewUpdate = +1126 AnnotationsUtil.getAnnotationValueValue( annotation, "skipShouldViewUpdate" ); +1127 +1128 scheduleRenderDescriptors.add( new ScheduleRenderDescriptor( method, skipShouldViewUpdate ) ); +1129 } +1130 } +1131 descriptor.setScheduleRenderDescriptors( scheduleRenderDescriptors ); +1132 } +1133 +1134 private void determinePublishMethods( @Nonnull final TypeElement typeElement, +1135 @Nonnull final ViewDescriptor descriptor, +1136 @Nonnull final List<ExecutableElement> methods ) +1137 { +1138 final List<PublishDescriptor> descriptors = new ArrayList<>(); +1139 for ( final ExecutableElement method : methods ) +1140 { +1141 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( method, Constants.PUBLISH_CLASSNAME ); +1142 if ( null != annotation ) +1143 { +1144 MemberChecks.mustBeSubclassCallable( typeElement, +1145 Constants.VIEW_CLASSNAME, +1146 Constants.PUBLISH_CLASSNAME, +1147 method ); +1148 MemberChecks.mustNotHaveAnyParameters( Constants.PUBLISH_CLASSNAME, method ); +1149 MemberChecks.mustNotHaveAnyTypeParameters( Constants.PUBLISH_CLASSNAME, method ); +1150 MemberChecks.mustReturnAValue( Constants.PUBLISH_CLASSNAME, method ); +1151 MemberChecks.mustNotThrowAnyExceptions( Constants.PUBLISH_CLASSNAME, method ); +1152 +1153 final String qualifier = AnnotationsUtil.getAnnotationValueValue( annotation, "qualifier" ); +1154 final ExecutableType methodType = resolveMethodType( descriptor, method ); +1155 +1156 if ( TypeKind.TYPEVAR == methodType.getReturnType().getKind() ) +1157 { +1158 throw new ProcessorException( MemberChecks.mustNot( Constants.PUBLISH_CLASSNAME, "return a type variable" ), +1159 method ); +1160 } +1161 +1162 descriptors.add( new PublishDescriptor( qualifier, method, methodType ) ); +1163 } +1164 } +1165 descriptor.setPublishDescriptors( descriptors ); +1166 } +1167 +1168 private void determinePreRenderMethods( @Nonnull final TypeElement typeElement, +1169 @Nonnull final ViewDescriptor descriptor, +1170 @Nonnull final List<ExecutableElement> methods ) +1171 { +1172 final List<RenderHookDescriptor> descriptors = new ArrayList<>(); +1173 for ( final ExecutableElement method : methods ) +1174 { +1175 final AnnotationMirror annotation = +1176 AnnotationsUtil.findAnnotationByType( method, Constants.PRE_RENDER_CLASSNAME ); +1177 if ( null != annotation ) +1178 { +1179 MemberChecks.mustBeSubclassCallable( typeElement, +1180 Constants.VIEW_CLASSNAME, +1181 Constants.PRE_RENDER_CLASSNAME, +1182 method ); +1183 MemberChecks.mustNotBeAbstract( Constants.PRE_RENDER_CLASSNAME, method ); +1184 MemberChecks.mustNotHaveAnyParameters( Constants.PRE_RENDER_CLASSNAME, method ); +1185 MemberChecks.mustNotHaveAnyTypeParameters( Constants.PRE_RENDER_CLASSNAME, method ); +1186 MemberChecks.mustNotReturnAnyValue( Constants.PRE_RENDER_CLASSNAME, method ); +1187 MemberChecks.mustNotThrowAnyExceptions( Constants.PRE_RENDER_CLASSNAME, method ); +1188 +1189 final int sortOrder = AnnotationsUtil.getAnnotationValueValue( annotation, "sortOrder" ); +1190 final ExecutableType methodType = resolveMethodType( descriptor, method ); +1191 +1192 descriptors.add( new RenderHookDescriptor( sortOrder, method, methodType ) ); +1193 } +1194 } +1195 descriptors.sort( Comparator.comparingInt( RenderHookDescriptor::getSortOrder ) ); +1196 descriptor.setPreRenderDescriptors( descriptors ); +1197 } +1198 +1199 private void determinePostRenderMethods( @Nonnull final TypeElement typeElement, +1200 @Nonnull final ViewDescriptor descriptor, +1201 @Nonnull final List<ExecutableElement> methods ) +1202 { +1203 final List<RenderHookDescriptor> descriptors = new ArrayList<>(); +1204 for ( final ExecutableElement method : methods ) +1205 { +1206 final AnnotationMirror annotation = +1207 AnnotationsUtil.findAnnotationByType( method, Constants.POST_RENDER_CLASSNAME ); +1208 if ( null != annotation ) +1209 { +1210 MemberChecks.mustBeSubclassCallable( typeElement, +1211 Constants.VIEW_CLASSNAME, +1212 Constants.POST_RENDER_CLASSNAME, +1213 method ); +1214 MemberChecks.mustNotBeAbstract( Constants.POST_RENDER_CLASSNAME, method ); +1215 MemberChecks.mustNotHaveAnyParameters( Constants.POST_RENDER_CLASSNAME, method ); +1216 MemberChecks.mustNotHaveAnyTypeParameters( Constants.POST_RENDER_CLASSNAME, method ); +1217 MemberChecks.mustNotReturnAnyValue( Constants.POST_RENDER_CLASSNAME, method ); +1218 MemberChecks.mustNotThrowAnyExceptions( Constants.POST_RENDER_CLASSNAME, method ); +1219 +1220 final int sortOrder = AnnotationsUtil.getAnnotationValueValue( annotation, "sortOrder" ); +1221 final ExecutableType methodType = resolveMethodType( descriptor, method ); +1222 +1223 descriptors.add( new RenderHookDescriptor( sortOrder, method, methodType ) ); +1224 } +1225 } +1226 descriptors.sort( Comparator.comparingInt( RenderHookDescriptor::getSortOrder ) ); +1227 descriptor.setPostRenderDescriptors( descriptors ); +1228 } +1229 +1230 private void determineRenderMethod( @Nonnull final TypeElement typeElement, +1231 @Nonnull final ViewDescriptor descriptor, +1232 @Nonnull final List<ExecutableElement> methods ) +1233 { +1234 boolean foundRender = false; +1235 for ( final ExecutableElement method : methods ) +1236 { +1237 final AnnotationMirror annotation = +1238 AnnotationsUtil.findAnnotationByType( method, Constants.RENDER_CLASSNAME ); +1239 if ( null != annotation ) +1240 { +1241 MemberChecks.mustNotBeAbstract( Constants.RENDER_CLASSNAME, method ); +1242 MemberChecks.mustBeSubclassCallable( typeElement, +1243 Constants.VIEW_CLASSNAME, +1244 Constants.RENDER_CLASSNAME, +1245 method ); +1246 MemberChecks.mustNotHaveAnyParameters( Constants.RENDER_CLASSNAME, method ); +1247 MemberChecks.mustReturnAnInstanceOf( processingEnv, +1248 method, +1249 Constants.RENDER_CLASSNAME, +1250 Constants.VNODE_CLASSNAME ); +1251 MemberChecks.mustNotThrowAnyExceptions( Constants.RENDER_CLASSNAME, method ); +1252 MemberChecks.mustNotHaveAnyTypeParameters( Constants.RENDER_CLASSNAME, method ); +1253 +1254 descriptor.setRender( method ); +1255 foundRender = true; +1256 } +1257 } +1258 final boolean requireRender = descriptor.requireRender(); +1259 if ( requireRender && !foundRender ) +1260 { +1261 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, +1262 "contain a method annotated with the " + +1263 MemberChecks.toSimpleName( Constants.RENDER_CLASSNAME ) + +1264 " annotation or must specify type=NO_RENDER" ), +1265 typeElement ); +1266 } +1267 else if ( !requireRender ) +1268 { +1269 if ( foundRender ) +1270 { +1271 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, +1272 "contain a method annotated with the " + +1273 MemberChecks.toSimpleName( Constants.RENDER_CLASSNAME ) + +1274 " annotation or must not specify type=NO_RENDER" ), +1275 typeElement ); +1276 } +1277 else if ( !descriptor.hasConstructor() && +1278 !descriptor.hasPostConstruct() && +1279 null == descriptor.getPostMount() && +1280 null == descriptor.getPostRender() && +1281 null == descriptor.getPreUpdate() && +1282 null == descriptor.getPostUpdate() && +1283 descriptor.getPreRenderDescriptors().isEmpty() && +1284 descriptor.getPostRenderDescriptors().isEmpty() && +1285 !descriptor.hasPreUpdateOnInputChange() && +1286 !descriptor.hasPostUpdateOnInputChange() ) +1287 { +1288 throw new ProcessorException( MemberChecks.must( Constants.VIEW_CLASSNAME, +1289 "contain lifecycle methods if the the @View(type=NO_RENDER) parameter is specified" ), +1290 typeElement ); 1291 } 1292 } 1293 } 1294 -1295 private void determinePostMountOrUpdateMethod( @Nonnull final TypeElement typeElement, -1296 @Nonnull final ViewDescriptor descriptor ) -1297 { -1298 for ( final ExecutableElement method : getMethods( typeElement ) ) -1299 { -1300 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_MOUNT_OR_UPDATE_CLASSNAME ) ) -1301 { -1302 MemberChecks.mustBeLifecycleHook( typeElement, -1303 Constants.VIEW_CLASSNAME, -1304 Constants.POST_MOUNT_OR_UPDATE_CLASSNAME, -1305 method ); -1306 descriptor.setPostRender( method ); -1307 } -1308 } -1309 } -1310 -1311 private void determinePostUpdateMethod( @Nonnull final TypeElement typeElement, -1312 @Nonnull final ViewDescriptor descriptor ) -1313 { -1314 for ( final ExecutableElement method : getMethods( typeElement ) ) -1315 { -1316 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_UPDATE_CLASSNAME ) ) -1317 { -1318 MemberChecks.mustBeLifecycleHook( typeElement, -1319 Constants.VIEW_CLASSNAME, -1320 Constants.POST_UPDATE_CLASSNAME, -1321 method ); -1322 descriptor.setPostUpdate( method ); -1323 } -1324 } -1325 } -1326 -1327 private void determinePreUpdateMethod( @Nonnull final TypeElement typeElement, -1328 @Nonnull final ViewDescriptor descriptor ) -1329 { -1330 for ( final ExecutableElement method : getMethods( typeElement ) ) -1331 { -1332 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.PRE_UPDATE_CLASSNAME ) ) -1333 { -1334 MemberChecks.mustBeLifecycleHook( typeElement, -1335 Constants.VIEW_CLASSNAME, -1336 Constants.PRE_UPDATE_CLASSNAME, -1337 method ); -1338 descriptor.setPreUpdate( method ); -1339 } -1340 } -1341 } -1342 -1343 private ExecutableType resolveMethodType( @Nonnull final ViewDescriptor descriptor, -1344 @Nonnull final ExecutableElement method ) -1345 { -1346 return (ExecutableType) processingEnv.getTypeUtils().asMemberOf( descriptor.getDeclaredType(), method ); -1347 } -1348 -1349 @Nonnull -1350 private String deriveViewName( @Nonnull final TypeElement typeElement ) -1351 { -1352 final String name = -1353 (String) AnnotationsUtil.getAnnotationValue( typeElement, Constants.VIEW_CLASSNAME, "name" ) -1354 .getValue(); -1355 -1356 if ( isSentinelName( name ) ) -1357 { -1358 return typeElement.getSimpleName().toString(); -1359 } -1360 else -1361 { -1362 if ( !SourceVersion.isIdentifier( name ) ) -1363 { -1364 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + -1365 " target specified an invalid name '" + name + "'. The " + -1366 "name must be a valid java identifier.", typeElement ); -1367 } -1368 else if ( SourceVersion.isKeyword( name ) ) -1369 { -1370 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + -1371 " target specified an invalid name '" + name + "'. The " + -1372 "name must not be a java keyword.", typeElement ); -1373 } -1374 return name; -1375 } -1376 } -1377 -1378 private void determineViewCapabilities( @Nonnull final ViewDescriptor descriptor, -1379 @Nonnull final TypeElement typeElement ) -1380 { -1381 if ( AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.AREZ_COMPONENT_CLASSNAME ) ) -1382 { -1383 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, -1384 "be annotated with the " + -1385 MemberChecks.toSimpleName( Constants.AREZ_COMPONENT_CLASSNAME ) + -1386 " as React4j will add the annotation." ), -1387 typeElement ); -1388 } -1389 -1390 if ( descriptor.needsInjection() && !descriptor.getDeclaredType().getTypeArguments().isEmpty() ) -1391 { -1392 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + -1393 " target has enabled injection integration but the class " + -1394 "has type arguments which is incompatible with injection integration.", -1395 typeElement ); -1396 } -1397 } -1398 -1399 @Nonnull -1400 private ViewType extractViewType( @Nonnull final TypeElement typeElement ) -1401 { -1402 final VariableElement declaredTypeEnum = (VariableElement) -1403 AnnotationsUtil -1404 .getAnnotationValue( typeElement, Constants.VIEW_CLASSNAME, "type" ) -1405 .getValue(); -1406 return ViewType.valueOf( declaredTypeEnum.getSimpleName().toString() ); -1407 } -1408 -1409 private boolean isInputObservable( @Nonnull final ViewDescriptor descriptor, -1410 @Nonnull final ExecutableElement method, -1411 final boolean immutable ) -1412 { -1413 final VariableElement parameter = (VariableElement) -1414 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "observable" ).getValue(); -1415 switch ( parameter.getSimpleName().toString() ) -1416 { -1417 case "ENABLE": -1418 if ( immutable ) -1419 { -1420 throw new ProcessorException( "@Input target has specified both immutable=true and " + -1421 "observable=ENABLE which is an invalid combination.", -1422 method ); -1423 } -1424 return true; -1425 case "DISABLE": -1426 return false; -1427 default: -1428 return hasAnyArezObserverMethods( descriptor.getElement() ); -1429 } -1430 } -1431 -1432 private boolean hasAnyArezObserverMethods( @Nonnull final TypeElement typeElement ) -1433 { -1434 return getMethods( typeElement ) -1435 .stream() -1436 .anyMatch( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.MEMOIZE_CLASSNAME ) || -1437 ( AnnotationsUtil.hasAnnotationOfType( m, Constants.OBSERVE_CLASSNAME ) && -1438 ( !m.getParameters().isEmpty() || !m.getSimpleName().toString().equals( "trackRender" ) ) ) ); -1439 } -1440 -1441 private boolean isInputImmutable( @Nonnull final ExecutableElement method ) -1442 { -1443 return (Boolean) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "immutable" ) -1444 .getValue(); -1445 } -1446 -1447 private boolean isInputDependency( @Nonnull final ExecutableElement method, -1448 final boolean immutable, -1449 final boolean disposable ) -1450 { -1451 final VariableElement parameter = (VariableElement) -1452 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "dependency" ).getValue(); -1453 switch ( parameter.getSimpleName().toString() ) -1454 { -1455 case "ENABLE": -1456 if ( !immutable ) -1457 { -1458 throw new ProcessorException( "@Input target must be immutable if dependency=ENABLE is specified", -1459 method ); -1460 } -1461 else if ( !disposable ) -1462 { -1463 throw new ProcessorException( "@Input target must be disposable if dependency=ENABLE is specified", -1464 method ); -1465 } -1466 return true; -1467 case "DISABLE": -1468 return false; -1469 default: -1470 return immutable && disposable; -1471 } -1472 } -1473 -1474 private boolean isInputDisposable( @Nonnull final ExecutableElement method, @Nonnull final Element inputType ) -1475 { -1476 final VariableElement parameter = (VariableElement) -1477 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "disposable" ).getValue(); -1478 switch ( parameter.getSimpleName().toString() ) -1479 { -1480 case "ENABLE": -1481 return true; -1482 case "DISABLE": -1483 return false; -1484 default: -1485 return -1486 ( -1487 ElementKind.CLASS == inputType.getKind() && -1488 AnnotationsUtil.hasAnnotationOfType( inputType, Constants.AREZ_COMPONENT_CLASSNAME ) -1489 ) || -1490 ( -1491 ( ElementKind.CLASS == inputType.getKind() || ElementKind.INTERFACE == inputType.getKind() ) && -1492 AnnotationsUtil.hasAnnotationOfType( inputType, Constants.ACT_AS_COMPONENT_CLASSNAME ) -1493 ); -1494 } -1495 } -1496 -1497 private boolean isContextInput( @Nonnull final ExecutableElement method ) -1498 { -1499 final VariableElement parameter = (VariableElement) -1500 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "source" ).getValue(); -1501 return "CONTEXT".equals( parameter.getSimpleName().toString() ); -1502 } -1503 -1504 private boolean shouldSetDefaultPriority( @Nonnull final TypeElement typeElement ) -1505 { -1506 final List<ExecutableElement> methods = getMethods( typeElement ); -1507 return methods -1508 .stream() -1509 .filter( method -> !method.getModifiers().contains( Modifier.PRIVATE ) ) -1510 .anyMatch( method -> AnnotationsUtil.hasAnnotationOfType( method, Constants.MEMOIZE_CLASSNAME ) || -1511 AnnotationsUtil.hasAnnotationOfType( method, Constants.OBSERVE_CLASSNAME ) ); +1295 private void determinePostMountMethod( @Nonnull final TypeElement typeElement, +1296 @Nonnull final ViewDescriptor descriptor, +1297 @Nonnull final List<ExecutableElement> methods ) +1298 { +1299 for ( final ExecutableElement method : methods ) +1300 { +1301 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_MOUNT_CLASSNAME ) ) +1302 { +1303 MemberChecks.mustBeLifecycleHook( typeElement, +1304 Constants.VIEW_CLASSNAME, +1305 Constants.POST_MOUNT_CLASSNAME, +1306 method ); +1307 descriptor.setPostMount( method ); +1308 } +1309 } +1310 } +1311 +1312 private void determinePostMountOrUpdateMethod( @Nonnull final TypeElement typeElement, +1313 @Nonnull final ViewDescriptor descriptor, +1314 @Nonnull final List<ExecutableElement> methods ) +1315 { +1316 for ( final ExecutableElement method : methods ) +1317 { +1318 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_MOUNT_OR_UPDATE_CLASSNAME ) ) +1319 { +1320 MemberChecks.mustBeLifecycleHook( typeElement, +1321 Constants.VIEW_CLASSNAME, +1322 Constants.POST_MOUNT_OR_UPDATE_CLASSNAME, +1323 method ); +1324 descriptor.setPostRender( method ); +1325 } +1326 } +1327 } +1328 +1329 private void determinePostUpdateMethod( @Nonnull final TypeElement typeElement, +1330 @Nonnull final ViewDescriptor descriptor, +1331 @Nonnull final List<ExecutableElement> methods ) +1332 { +1333 for ( final ExecutableElement method : methods ) +1334 { +1335 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.POST_UPDATE_CLASSNAME ) ) +1336 { +1337 MemberChecks.mustBeLifecycleHook( typeElement, +1338 Constants.VIEW_CLASSNAME, +1339 Constants.POST_UPDATE_CLASSNAME, +1340 method ); +1341 descriptor.setPostUpdate( method ); +1342 } +1343 } +1344 } +1345 +1346 private void determinePreUpdateMethod( @Nonnull final TypeElement typeElement, +1347 @Nonnull final ViewDescriptor descriptor, +1348 @Nonnull final List<ExecutableElement> methods ) +1349 { +1350 for ( final ExecutableElement method : methods ) +1351 { +1352 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.PRE_UPDATE_CLASSNAME ) ) +1353 { +1354 MemberChecks.mustBeLifecycleHook( typeElement, +1355 Constants.VIEW_CLASSNAME, +1356 Constants.PRE_UPDATE_CLASSNAME, +1357 method ); +1358 descriptor.setPreUpdate( method ); +1359 } +1360 } +1361 } +1362 +1363 private ExecutableType resolveMethodType( @Nonnull final ViewDescriptor descriptor, +1364 @Nonnull final ExecutableElement method ) +1365 { +1366 return (ExecutableType) processingEnv.getTypeUtils().asMemberOf( descriptor.getDeclaredType(), method ); +1367 } +1368 +1369 @Nonnull +1370 private String deriveViewName( @Nonnull final TypeElement typeElement ) +1371 { +1372 final String name = +1373 (String) AnnotationsUtil.getAnnotationValue( typeElement, Constants.VIEW_CLASSNAME, "name" ) +1374 .getValue(); +1375 +1376 if ( isSentinelName( name ) ) +1377 { +1378 return typeElement.getSimpleName().toString(); +1379 } +1380 else +1381 { +1382 if ( !SourceVersion.isIdentifier( name ) ) +1383 { +1384 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + +1385 " target specified an invalid name '" + name + "'. The " + +1386 "name must be a valid java identifier.", typeElement ); +1387 } +1388 else if ( SourceVersion.isKeyword( name ) ) +1389 { +1390 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + +1391 " target specified an invalid name '" + name + "'. The " + +1392 "name must not be a java keyword.", typeElement ); +1393 } +1394 return name; +1395 } +1396 } +1397 +1398 private void determineViewCapabilities( @Nonnull final ViewDescriptor descriptor, +1399 @Nonnull final TypeElement typeElement ) +1400 { +1401 if ( AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.AREZ_COMPONENT_CLASSNAME ) ) +1402 { +1403 throw new ProcessorException( MemberChecks.mustNot( Constants.VIEW_CLASSNAME, +1404 "be annotated with the " + +1405 MemberChecks.toSimpleName( Constants.AREZ_COMPONENT_CLASSNAME ) + +1406 " as React4j will add the annotation." ), +1407 typeElement ); +1408 } +1409 +1410 if ( descriptor.needsInjection() && !descriptor.getDeclaredType().getTypeArguments().isEmpty() ) +1411 { +1412 throw new ProcessorException( MemberChecks.toSimpleName( Constants.VIEW_CLASSNAME ) + +1413 " target has enabled injection integration but the class " + +1414 "has type arguments which is incompatible with injection integration.", +1415 typeElement ); +1416 } +1417 } +1418 +1419 @Nonnull +1420 private ViewType extractViewType( @Nonnull final TypeElement typeElement ) +1421 { +1422 final VariableElement declaredTypeEnum = (VariableElement) +1423 AnnotationsUtil +1424 .getAnnotationValue( typeElement, Constants.VIEW_CLASSNAME, "type" ) +1425 .getValue(); +1426 return ViewType.valueOf( declaredTypeEnum.getSimpleName().toString() ); +1427 } +1428 +1429 private boolean isInputObservable( @Nonnull final List<ExecutableElement> methods, +1430 @Nonnull final ExecutableElement method, +1431 final boolean immutable ) +1432 { +1433 final VariableElement parameter = (VariableElement) +1434 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "observable" ).getValue(); +1435 return switch ( parameter.getSimpleName().toString() ) +1436 { +1437 case "ENABLE" -> +1438 { +1439 if ( immutable ) +1440 { +1441 throw new ProcessorException( "@Input target has specified both immutable=true and " + +1442 "observable=ENABLE which is an invalid combination.", +1443 method ); +1444 } +1445 yield true; +1446 } +1447 case "DISABLE" -> false; +1448 default -> hasAnyArezObserverMethods( methods ); +1449 }; +1450 } +1451 +1452 private boolean hasAnyArezObserverMethods( @Nonnull final List<ExecutableElement> methods ) +1453 { +1454 return +1455 methods +1456 .stream() +1457 .anyMatch( m -> AnnotationsUtil.hasAnnotationOfType( m, Constants.MEMOIZE_CLASSNAME ) || +1458 ( AnnotationsUtil.hasAnnotationOfType( m, Constants.OBSERVE_CLASSNAME ) && +1459 ( !m.getParameters().isEmpty() || !m.getSimpleName().toString().equals( "trackRender" ) ) ) ); +1460 } +1461 +1462 private boolean isInputImmutable( @Nonnull final ExecutableElement method ) +1463 { +1464 return (Boolean) AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "immutable" ) +1465 .getValue(); +1466 } +1467 +1468 private boolean isInputDependency( @Nonnull final ExecutableElement method, +1469 final boolean immutable, +1470 final boolean disposable ) +1471 { +1472 final VariableElement parameter = (VariableElement) +1473 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "dependency" ).getValue(); +1474 return switch ( parameter.getSimpleName().toString() ) +1475 { +1476 case "ENABLE" -> +1477 { +1478 if ( !immutable ) +1479 { +1480 throw new ProcessorException( "@Input target must be immutable if dependency=ENABLE is specified", +1481 method ); +1482 } +1483 else if ( !disposable ) +1484 { +1485 throw new ProcessorException( "@Input target must be disposable if dependency=ENABLE is specified", +1486 method ); +1487 } +1488 yield true; +1489 } +1490 case "DISABLE" -> false; +1491 default -> immutable && disposable; +1492 }; +1493 } +1494 +1495 private boolean isInputDisposable( @Nonnull final ExecutableElement method, @Nonnull final Element inputType ) +1496 { +1497 final VariableElement parameter = (VariableElement) +1498 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "disposable" ).getValue(); +1499 return switch ( parameter.getSimpleName().toString() ) +1500 { +1501 case "ENABLE" -> true; +1502 case "DISABLE" -> false; +1503 default -> ( +1504 ElementKind.CLASS == inputType.getKind() && +1505 AnnotationsUtil.hasAnnotationOfType( inputType, Constants.AREZ_COMPONENT_CLASSNAME ) +1506 ) || +1507 ( +1508 ( ElementKind.CLASS == inputType.getKind() || ElementKind.INTERFACE == inputType.getKind() ) && +1509 AnnotationsUtil.hasAnnotationOfType( inputType, Constants.ACT_AS_COMPONENT_CLASSNAME ) +1510 ); +1511 }; 1512 } 1513 -1514 private void verifyNoDuplicateAnnotations( @Nonnull final ExecutableElement method ) -1515 throws ProcessorException -1516 { -1517 final List<String> annotations = -1518 Arrays.asList( Constants.INPUT_DEFAULT_CLASSNAME, -1519 Constants.INPUT_VALIDATE_CLASSNAME, -1520 Constants.ON_INPUT_CHANGE_CLASSNAME, -1521 Constants.INPUT_CLASSNAME ); -1522 MemberChecks.verifyNoOverlappingAnnotations( method, annotations, Collections.emptyMap() ); -1523 } -1524 -1525 @Nonnull -1526 private List<ExecutableElement> getMethods( @Nonnull final TypeElement typeElement ) -1527 { -1528 return ElementsUtil.getMethods( typeElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); +1514 private boolean isContextInput( @Nonnull final ExecutableElement method ) +1515 { +1516 final VariableElement parameter = (VariableElement) +1517 AnnotationsUtil.getAnnotationValue( method, Constants.INPUT_CLASSNAME, "source" ).getValue(); +1518 return "CONTEXT".equals( parameter.getSimpleName().toString() ); +1519 } +1520 +1521 private boolean shouldSetDefaultPriority( @Nonnull final List<ExecutableElement> methods ) +1522 { +1523 return +1524 methods +1525 .stream() +1526 .filter( method -> !method.getModifiers().contains( Modifier.PRIVATE ) ) +1527 .anyMatch( method -> AnnotationsUtil.hasAnnotationOfType( method, Constants.MEMOIZE_CLASSNAME ) || +1528 AnnotationsUtil.hasAnnotationOfType( method, Constants.OBSERVE_CLASSNAME ) ); 1529 } 1530 -1531 private boolean isSentinelName( @Nonnull final String name ) -1532 { -1533 return SENTINEL_NAME.equals( name ); -1534 } -1535 -1536 @Nonnull -1537 private String getPropertyAccessorName( @Nonnull final ExecutableElement method, -1538 @Nonnull final String specifiedName ) -1539 throws ProcessorException -1540 { -1541 String name = deriveName( method, GETTER_PATTERN, specifiedName ); -1542 if ( null != name ) -1543 { -1544 return name; -1545 } -1546 else if ( method.getReturnType().getKind() == TypeKind.BOOLEAN ) -1547 { -1548 name = deriveName( method, ISSER_PATTERN, specifiedName ); -1549 if ( null != name ) -1550 { -1551 return name; -1552 } -1553 } -1554 return method.getSimpleName().toString(); -1555 } -1556 -1557 @Nullable -1558 private String deriveName( @Nonnull final Element method, @Nonnull final Pattern pattern, @Nonnull final String name ) -1559 throws ProcessorException -1560 { -1561 if ( isSentinelName( name ) ) -1562 { -1563 final String methodName = method.getSimpleName().toString(); -1564 final Matcher matcher = pattern.matcher( methodName ); -1565 if ( matcher.find() ) -1566 { -1567 final String candidate = matcher.group( 1 ); -1568 return Character.toLowerCase( candidate.charAt( 0 ) ) + candidate.substring( 1 ); -1569 } -1570 else -1571 { -1572 return null; -1573 } -1574 } -1575 else -1576 { -1577 return name; -1578 } -1579 } -1580} +1531 private void verifyNoDuplicateAnnotations( @Nonnull final ExecutableElement method ) +1532 throws ProcessorException +1533 { +1534 final List<String> annotations = +1535 Arrays.asList( Constants.INPUT_DEFAULT_CLASSNAME, +1536 Constants.INPUT_VALIDATE_CLASSNAME, +1537 Constants.ON_INPUT_CHANGE_CLASSNAME, +1538 Constants.INPUT_CLASSNAME ); +1539 MemberChecks.verifyNoOverlappingAnnotations( method, annotations, Collections.emptyMap() ); +1540 } +1541 +1542 private boolean isSentinelName( @Nonnull final String name ) +1543 { +1544 return SENTINEL_NAME.equals( name ); +1545 } +1546 +1547 @Nonnull +1548 private String getPropertyAccessorName( @Nonnull final ExecutableElement method, +1549 @Nonnull final String specifiedName ) +1550 throws ProcessorException +1551 { +1552 String name = deriveName( method, GETTER_PATTERN, specifiedName ); +1553 if ( null != name ) +1554 { +1555 return name; +1556 } +1557 else if ( method.getReturnType().getKind() == TypeKind.BOOLEAN ) +1558 { +1559 name = deriveName( method, ISSER_PATTERN, specifiedName ); +1560 if ( null != name ) +1561 { +1562 return name; +1563 } +1564 } +1565 return method.getSimpleName().toString(); +1566 } +1567 +1568 @Nullable +1569 private String deriveName( @Nonnull final Element method, @Nonnull final Pattern pattern, @Nonnull final String name ) +1570 throws ProcessorException +1571 { +1572 if ( isSentinelName( name ) ) +1573 { +1574 final String methodName = method.getSimpleName().toString(); +1575 final Matcher matcher = pattern.matcher( methodName ); +1576 if ( matcher.find() ) +1577 { +1578 final String candidate = matcher.group( 1 ); +1579 return Character.toLowerCase( candidate.charAt( 0 ) ) + candidate.substring( 1 ); +1580 } +1581 else +1582 { +1583 return null; +1584 } +1585 } +1586 else +1587 { +1588 return name; +1589 } +1590 } +1591} diff --git a/docs/project_setup.html b/docs/project_setup.html index 46c965f0..f247cac9 100644 --- a/docs/project_setup.html +++ b/docs/project_setup.html @@ -21,7 +21,7 @@

<dependency> <groupId>org.realityforge.react4j</groupId> <artifactId>react4j-dom</artifactId> - <version>0.195</version> + <version>0.196</version> </dependency> ... </dependencies> @@ -45,7 +45,7 @@

<path> <groupId>org.realityforge.react4j</groupId> <artifactId>react4j-processor</artifactId> - <version>0.195</version> + <version>0.196</version> </path> </annotationProcessorPaths> </configuration>