001package arez.processor; 002 003import com.squareup.javapoet.ParameterizedTypeName; 004import com.squareup.javapoet.TypeName; 005import java.io.IOException; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.List; 013import java.util.Map; 014import java.util.Objects; 015import java.util.Set; 016import java.util.function.Function; 017import java.util.regex.Matcher; 018import java.util.regex.Pattern; 019import java.util.regex.PatternSyntaxException; 020import javax.annotation.Nonnull; 021import javax.annotation.Nullable; 022import javax.annotation.processing.ProcessingEnvironment; 023import javax.annotation.processing.RoundEnvironment; 024import javax.annotation.processing.SupportedAnnotationTypes; 025import javax.annotation.processing.SupportedOptions; 026import javax.annotation.processing.SupportedSourceVersion; 027import javax.lang.model.SourceVersion; 028import javax.lang.model.element.AnnotationMirror; 029import javax.lang.model.element.AnnotationValue; 030import javax.lang.model.element.Element; 031import javax.lang.model.element.ElementKind; 032import javax.lang.model.element.ExecutableElement; 033import javax.lang.model.element.Modifier; 034import javax.lang.model.element.TypeElement; 035import javax.lang.model.element.VariableElement; 036import javax.lang.model.type.DeclaredType; 037import javax.lang.model.type.ExecutableType; 038import javax.lang.model.type.TypeKind; 039import javax.lang.model.type.TypeMirror; 040import javax.lang.model.util.Elements; 041import javax.lang.model.util.Types; 042import javax.tools.Diagnostic; 043import org.realityforge.proton.AbstractStandardProcessor; 044import org.realityforge.proton.AnnotationsUtil; 045import org.realityforge.proton.DeferredElementSet; 046import org.realityforge.proton.ElementsUtil; 047import org.realityforge.proton.MemberChecks; 048import org.realityforge.proton.ProcessorException; 049import org.realityforge.proton.StopWatch; 050import org.realityforge.proton.SuperficialValidation; 051import org.realityforge.proton.TypesUtil; 052import static javax.tools.Diagnostic.Kind.*; 053 054/** 055 * Annotation processor that analyzes Arez annotated source and generates models from the annotations. 056 */ 057@SupportedAnnotationTypes( Constants.COMPONENT_CLASSNAME ) 058@SupportedSourceVersion( SourceVersion.RELEASE_17 ) 059@SupportedOptions( { "arez.defer.unresolved", 060 "arez.defer.errors", 061 "arez.debug", 062 "arez.profile", 063 "arez.verbose_out_of_round.errors" } ) 064public final class ArezProcessor 065 extends AbstractStandardProcessor 066{ 067 @Nonnull 068 static final Pattern GETTER_PATTERN = Pattern.compile( "^get([A-Z].*)$" ); 069 @Nonnull 070 private static final Pattern ON_ACTIVATE_PATTERN = Pattern.compile( "^on([A-Z].*)Activate$" ); 071 @Nonnull 072 private static final Pattern ON_DEACTIVATE_PATTERN = Pattern.compile( "^on([A-Z].*)Deactivate$" ); 073 @Nonnull 074 private static final Pattern SETTER_PATTERN = Pattern.compile( "^set([A-Z].*)$" ); 075 @Nonnull 076 private static final Pattern ISSER_PATTERN = Pattern.compile( "^is([A-Z].*)$" ); 077 @Nonnull 078 private static final List<String> OBJECT_METHODS = 079 Arrays.asList( "hashCode", "equals", "clone", "toString", "finalize", "getClass", "wait", "notifyAll", "notify" ); 080 @Nonnull 081 private static final List<String> AREZ_SPECIAL_METHODS = 082 Arrays.asList( "observe", "dispose", "isDisposed", "getArezId" ); 083 @Nonnull 084 private static final Pattern ID_GETTER_PATTERN = Pattern.compile( "^get([A-Z].*)Id$" ); 085 @Nonnull 086 private static final Pattern RAW_ID_GETTER_PATTERN = Pattern.compile( "^(.*)Id$" ); 087 @Nonnull 088 private static final Pattern OBSERVABLE_REF_PATTERN = Pattern.compile( "^get([A-Z].*)ObservableValue$" ); 089 @Nonnull 090 private static final Pattern COMPUTABLE_VALUE_REF_PATTERN = Pattern.compile( "^get([A-Z].*)ComputableValue$" ); 091 @Nonnull 092 private static final Pattern OBSERVER_REF_PATTERN = Pattern.compile( "^get([A-Z].*)Observer$" ); 093 @Nonnull 094 private static final Pattern ON_DEPS_CHANGE_PATTERN = Pattern.compile( "^on([A-Z].*)DepsChange" ); 095 @Nonnull 096 private static final Pattern PRE_INVERSE_REMOVE_PATTERN = Pattern.compile( "^pre([A-Z].*)Remove" ); 097 @Nonnull 098 private static final Pattern POST_INVERSE_ADD_PATTERN = Pattern.compile( "^post([A-Z].*)Add" ); 099 @Nonnull 100 private static final Pattern CAPTURE_PATTERN = Pattern.compile( "^capture([A-Z].*)" ); 101 @Nonnull 102 private static final Pattern POP_PATTERN = Pattern.compile( "^pop([A-Z].*)" ); 103 @Nonnull 104 private static final Pattern PUSH_PATTERN = Pattern.compile( "^push([A-Z].*)" ); 105 @Nonnull 106 private final DeferredElementSet _deferredTypes = new DeferredElementSet(); 107 @Nonnull 108 private final StopWatch _analyzeComponentStopWatch = new StopWatch( "Analyze Component" ); 109 110 @Override 111 @Nonnull 112 protected String getIssueTrackerURL() 113 { 114 return "https://github.com/arez/arez/issues"; 115 } 116 117 @Nonnull 118 @Override 119 protected String getOptionPrefix() 120 { 121 return "arez"; 122 } 123 124 @Override 125 protected void collectStopWatches( @Nonnull final Collection<StopWatch> stopWatches ) 126 { 127 stopWatches.add( _analyzeComponentStopWatch ); 128 } 129 130 @Override 131 public boolean process( @Nonnull final Set<? extends TypeElement> annotations, @Nonnull final RoundEnvironment env ) 132 { 133 debugAnnotationProcessingRootElements( env ); 134 collectRootTypeNames( env ); 135 processTypeElements( annotations, 136 env, 137 Constants.COMPONENT_CLASSNAME, 138 _deferredTypes, 139 _analyzeComponentStopWatch.getName(), 140 this::process, 141 _analyzeComponentStopWatch ); 142 errorIfProcessingOverAndInvalidTypesDetected( env ); 143 clearRootTypeNamesIfProcessingOver( env ); 144 return true; 145 } 146 147 private void process( @Nonnull final TypeElement element ) 148 throws IOException, ProcessorException 149 { 150 final ComponentDescriptor descriptor = parse( element ); 151 emitTypeSpec( descriptor.getPackageName(), ComponentGenerator.buildType( processingEnv, descriptor ) ); 152 } 153 154 @Nonnull 155 private ObservableDescriptor addObservable( @Nonnull final ComponentDescriptor component, 156 @Nonnull final AnnotationMirror annotation, 157 @Nonnull final ExecutableElement method, 158 @Nonnull final ExecutableType methodType ) 159 throws ProcessorException 160 { 161 MemberChecks.mustBeOverridable( component.getElement(), 162 Constants.COMPONENT_CLASSNAME, 163 Constants.OBSERVABLE_CLASSNAME, 164 method ); 165 166 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 167 final boolean expectSetter = AnnotationsUtil.getAnnotationValueValue( annotation, "expectSetter" ); 168 final VariableElement readOutsideTransaction = 169 AnnotationsUtil.getAnnotationValueValue( annotation, "readOutsideTransaction" ); 170 final VariableElement writeOutsideTransaction = 171 AnnotationsUtil.getAnnotationValueValue( annotation, "writeOutsideTransaction" ); 172 final boolean setterAlwaysMutates = AnnotationsUtil.getAnnotationValueValue( annotation, "setterAlwaysMutates" ); 173 final Boolean requireInitializer = isInitializerRequired( method ); 174 175 final TypeMirror returnType = method.getReturnType(); 176 final String methodName = method.getSimpleName().toString(); 177 String name; 178 final boolean setter; 179 if ( TypeKind.VOID == returnType.getKind() ) 180 { 181 setter = true; 182 //Should be a setter 183 if ( 1 != method.getParameters().size() ) 184 { 185 throw new ProcessorException( "@Observable target should be a setter or getter", method ); 186 } 187 188 name = deriveName( method, SETTER_PATTERN, declaredName ); 189 if ( null == name ) 190 { 191 name = methodName; 192 } 193 } 194 else 195 { 196 setter = false; 197 //Must be a getter 198 if ( !method.getParameters().isEmpty() ) 199 { 200 throw new ProcessorException( "@Observable target should be a setter or getter", method ); 201 } 202 name = getPropertyAccessorName( method, declaredName ); 203 } 204 // Override name if supplied by user 205 if ( !Constants.SENTINEL.equals( declaredName ) ) 206 { 207 name = declaredName; 208 if ( !SourceVersion.isIdentifier( name ) ) 209 { 210 throw new ProcessorException( "@Observable target specified an invalid name '" + name + "'. The " + 211 "name must be a valid java identifier.", method ); 212 } 213 else if ( SourceVersion.isKeyword( name ) ) 214 { 215 throw new ProcessorException( "@Observable target specified an invalid name '" + name + "'. The " + 216 "name must not be a java keyword.", method ); 217 } 218 } 219 checkNameUnique( component, name, method, Constants.OBSERVABLE_CLASSNAME ); 220 221 if ( setter && !expectSetter ) 222 { 223 throw new ProcessorException( "Method annotated with @Observable is a setter but defines " + 224 "expectSetter = false for observable named " + name, method ); 225 } 226 227 final ObservableDescriptor observable = component.findOrCreateObservable( name ); 228 observable.setReadOutsideTransaction( readOutsideTransaction.getSimpleName().toString() ); 229 observable.setWriteOutsideTransaction( writeOutsideTransaction.getSimpleName().toString() ); 230 if ( !setterAlwaysMutates ) 231 { 232 observable.setSetterAlwaysMutates( false ); 233 } 234 if ( !expectSetter ) 235 { 236 observable.setExpectSetter( false ); 237 } 238 if ( !observable.expectSetter() ) 239 { 240 if ( observable.hasSetter() ) 241 { 242 throw new ProcessorException( "Method annotated with @Observable defines expectSetter = false but a " + 243 "setter exists named " + observable.getSetter().getSimpleName() + 244 "for observable named " + name, method ); 245 } 246 } 247 if ( setter ) 248 { 249 if ( observable.hasSetter() ) 250 { 251 throw new ProcessorException( "Method annotated with @Observable defines duplicate setter for " + 252 "observable named " + name, method ); 253 } 254 if ( !observable.expectSetter() ) 255 { 256 throw new ProcessorException( "Method annotated with @Observable defines expectSetter = false but a " + 257 "setter exists for observable named " + name, method ); 258 } 259 observable.setSetter( method, methodType ); 260 } 261 else 262 { 263 if ( observable.hasGetter() ) 264 { 265 throw new ProcessorException( "Method annotated with @Observable defines duplicate getter for " + 266 "observable named " + name, method ); 267 } 268 observable.setGetter( method, methodType ); 269 } 270 if ( null != requireInitializer ) 271 { 272 if ( !method.getModifiers().contains( Modifier.ABSTRACT ) ) 273 { 274 throw new ProcessorException( "@Observable target set initializer parameter to ENABLED but " + 275 "method is not abstract.", method ); 276 } 277 final Boolean existing = observable.getInitializer(); 278 if ( null == existing ) 279 { 280 observable.setInitializer( requireInitializer ); 281 } 282 else if ( existing != requireInitializer ) 283 { 284 throw new ProcessorException( "@Observable target set initializer parameter to value that differs from " + 285 "the paired observable method.", method ); 286 } 287 } 288 return observable; 289 } 290 291 private void addObservableValueRef( @Nonnull final ComponentDescriptor component, 292 @Nonnull final AnnotationMirror annotation, 293 @Nonnull final ExecutableElement method, 294 @Nonnull final ExecutableType methodType ) 295 throws ProcessorException 296 { 297 mustBeStandardRefMethod( processingEnv, 298 component, 299 method, 300 Constants.OBSERVABLE_VALUE_REF_CLASSNAME ); 301 302 final TypeMirror returnType = methodType.getReturnType(); 303 if ( TypeKind.DECLARED != returnType.getKind() || 304 !ElementsUtil.toRawType( returnType ).toString().equals( "arez.ObservableValue" ) ) 305 { 306 throw new ProcessorException( "Method annotated with @ObservableValueRef must return an instance of " + 307 "arez.ObservableValue", method ); 308 } 309 310 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 311 final String name; 312 if ( Constants.SENTINEL.equals( declaredName ) ) 313 { 314 name = deriveName( method, OBSERVABLE_REF_PATTERN, declaredName ); 315 if ( null == name ) 316 { 317 throw new ProcessorException( "Method annotated with @ObservableValueRef should specify name or be " + 318 "named according to the convention get[Name]ObservableValue", method ); 319 } 320 } 321 else 322 { 323 name = declaredName; 324 if ( !SourceVersion.isIdentifier( name ) ) 325 { 326 throw new ProcessorException( "@ObservableValueRef target specified an invalid name '" + name + "'. The " + 327 "name must be a valid java identifier.", method ); 328 } 329 else if ( SourceVersion.isKeyword( name ) ) 330 { 331 throw new ProcessorException( "@ObservableValueRef target specified an invalid name '" + name + "'. The " + 332 "name must not be a java keyword.", method ); 333 } 334 } 335 336 component.findOrCreateObservable( name ).addRefMethod( method, methodType ); 337 } 338 339 private void addComputableValueRef( @Nonnull final ComponentDescriptor component, 340 @Nonnull final AnnotationMirror annotation, 341 @Nonnull final ExecutableElement method, 342 @Nonnull final ExecutableType methodType ) 343 throws ProcessorException 344 { 345 mustBeRefMethod( component, method, Constants.COMPUTABLE_VALUE_REF_CLASSNAME ); 346 shouldBeInternalRefMethod( processingEnv, 347 component, 348 method, 349 Constants.COMPUTABLE_VALUE_REF_CLASSNAME ); 350 351 final TypeMirror returnType = methodType.getReturnType(); 352 if ( TypeKind.DECLARED != returnType.getKind() || 353 !ElementsUtil.toRawType( returnType ).toString().equals( "arez.ComputableValue" ) ) 354 { 355 throw new ProcessorException( "Method annotated with @ComputableValueRef must return an instance of " + 356 "arez.ComputableValue", method ); 357 } 358 359 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 360 final String name; 361 if ( Constants.SENTINEL.equals( declaredName ) ) 362 { 363 name = deriveName( method, COMPUTABLE_VALUE_REF_PATTERN, declaredName ); 364 if ( null == name ) 365 { 366 throw new ProcessorException( "Method annotated with @ComputableValueRef should specify name or be " + 367 "named according to the convention get[Name]ComputableValue", method ); 368 } 369 } 370 else 371 { 372 name = declaredName; 373 if ( !SourceVersion.isIdentifier( name ) ) 374 { 375 throw new ProcessorException( "@ComputableValueRef target specified an invalid name '" + name + "'. The " + 376 "name must be a valid java identifier.", method ); 377 } 378 else if ( SourceVersion.isKeyword( name ) ) 379 { 380 throw new ProcessorException( "@ComputableValueRef target specified an invalid name '" + name + "'. The " + 381 "name must not be a java keyword.", method ); 382 } 383 } 384 385 MemberChecks.mustBeSubclassCallable( component.getElement(), 386 Constants.COMPONENT_CLASSNAME, 387 Constants.COMPUTABLE_VALUE_REF_CLASSNAME, 388 method ); 389 MemberChecks.mustNotThrowAnyExceptions( Constants.COMPUTABLE_VALUE_REF_CLASSNAME, method ); 390 component.findOrCreateMemoize( name ).addRefMethod( method, methodType ); 391 } 392 393 @Nonnull 394 private String deriveMemoizeName( @Nonnull final ExecutableElement method, 395 @Nonnull final AnnotationMirror annotation ) 396 throws ProcessorException 397 { 398 final String name = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 399 if ( Constants.SENTINEL.equals( name ) ) 400 { 401 return getPropertyAccessorName( method, name ); 402 } 403 else 404 { 405 if ( !SourceVersion.isIdentifier( name ) ) 406 { 407 throw new ProcessorException( "@Memoize target specified an invalid name '" + name + "'. The " + 408 "name must be a valid java identifier.", method ); 409 } 410 else if ( SourceVersion.isKeyword( name ) ) 411 { 412 throw new ProcessorException( "@Memoize target specified an invalid name '" + name + "'. The " + 413 "name must not be a java keyword.", method ); 414 } 415 return name; 416 } 417 } 418 419 private void addOnActivate( @Nonnull final ComponentDescriptor component, 420 @Nonnull final AnnotationMirror annotation, 421 @Nonnull final ExecutableElement method ) 422 throws ProcessorException 423 { 424 final String name = 425 deriveHookName( component, 426 method, 427 ON_ACTIVATE_PATTERN, 428 "Activate", 429 AnnotationsUtil.getAnnotationValueValue( annotation, "name" ) ); 430 MemberChecks.mustBeLifecycleHook( component.getElement(), 431 Constants.COMPONENT_CLASSNAME, 432 Constants.ON_ACTIVATE_CLASSNAME, 433 method ); 434 shouldBeInternalHookMethod( processingEnv, 435 component, 436 method, 437 Constants.ON_ACTIVATE_CLASSNAME ); 438 439 component.findOrCreateMemoize( name ).setOnActivate( method ); 440 } 441 442 private void addOnDeactivate( @Nonnull final ComponentDescriptor component, 443 @Nonnull final AnnotationMirror annotation, 444 @Nonnull final ExecutableElement method ) 445 throws ProcessorException 446 { 447 final String name = 448 deriveHookName( component, 449 method, 450 ON_DEACTIVATE_PATTERN, 451 "Deactivate", 452 AnnotationsUtil.getAnnotationValueValue( annotation, "name" ) ); 453 MemberChecks.mustBeLifecycleHook( component.getElement(), 454 Constants.COMPONENT_CLASSNAME, 455 Constants.ON_DEACTIVATE_CLASSNAME, 456 method ); 457 shouldBeInternalHookMethod( processingEnv, 458 component, 459 method, 460 Constants.ON_DEACTIVATE_CLASSNAME ); 461 component.findOrCreateMemoize( name ).setOnDeactivate( method ); 462 } 463 464 @Nonnull 465 private String deriveHookName( @Nonnull final ComponentDescriptor component, 466 @Nonnull final ExecutableElement method, 467 @Nonnull final Pattern pattern, 468 @Nonnull final String type, 469 @Nonnull final String name ) 470 throws ProcessorException 471 { 472 final String value = deriveName( method, pattern, name ); 473 if ( null == value ) 474 { 475 throw new ProcessorException( "Unable to derive name for @On" + type + " as does not match " + 476 "on[Name]" + type + " pattern. Please specify name.", method ); 477 } 478 else if ( !SourceVersion.isIdentifier( value ) ) 479 { 480 throw new ProcessorException( "@On" + type + " target specified an invalid name '" + value + "'. The " + 481 "name must be a valid java identifier.", component.getElement() ); 482 } 483 else if ( SourceVersion.isKeyword( value ) ) 484 { 485 throw new ProcessorException( "@On" + type + " target specified an invalid name '" + value + "'. The " + 486 "name must not be a java keyword.", component.getElement() ); 487 } 488 else 489 { 490 return value; 491 } 492 } 493 494 private void addComponentStateRef( @Nonnull final ComponentDescriptor component, 495 @Nonnull final AnnotationMirror annotation, 496 @Nonnull final ExecutableElement method ) 497 throws ProcessorException 498 { 499 mustBeStandardRefMethod( processingEnv, 500 component, 501 method, 502 Constants.COMPONENT_STATE_REF_CLASSNAME ); 503 504 final TypeMirror returnType = method.getReturnType(); 505 if ( TypeKind.BOOLEAN != returnType.getKind() ) 506 { 507 throw new ProcessorException( "@ComponentStateRef target must return a boolean", method ); 508 } 509 final VariableElement variableElement = AnnotationsUtil.getAnnotationValueValue( annotation, "value" ); 510 final ComponentStateRefDescriptor.State state = 511 ComponentStateRefDescriptor.State.valueOf( variableElement.getSimpleName().toString() ); 512 513 component.getComponentStateRefs().add( new ComponentStateRefDescriptor( method, state ) ); 514 } 515 516 private void addContextRef( @Nonnull final ComponentDescriptor component, @Nonnull final ExecutableElement method ) 517 throws ProcessorException 518 { 519 mustBeStandardRefMethod( processingEnv, 520 component, 521 method, 522 Constants.CONTEXT_REF_CLASSNAME ); 523 MemberChecks.mustReturnAnInstanceOf( processingEnv, 524 method, 525 Constants.OBSERVER_REF_CLASSNAME, 526 "arez.ArezContext" ); 527 component.getContextRefs().add( method ); 528 } 529 530 private void addComponentIdRef( @Nonnull final ComponentDescriptor component, 531 @Nonnull final ExecutableElement method ) 532 { 533 mustBeRefMethod( component, method, Constants.COMPONENT_ID_REF_CLASSNAME ); 534 MemberChecks.mustNotHaveAnyParameters( Constants.COMPONENT_ID_REF_CLASSNAME, method ); 535 component.getComponentIdRefs().add( method ); 536 } 537 538 private void addComponentRef( @Nonnull final ComponentDescriptor component, @Nonnull final ExecutableElement method ) 539 throws ProcessorException 540 { 541 mustBeStandardRefMethod( processingEnv, 542 component, 543 method, 544 Constants.COMPONENT_REF_CLASSNAME ); 545 MemberChecks.mustReturnAnInstanceOf( processingEnv, 546 method, 547 Constants.COMPONENT_REF_CLASSNAME, 548 "arez.Component" ); 549 component.getComponentRefs().add( method ); 550 } 551 552 private void setComponentId( @Nonnull final ComponentDescriptor component, 553 @Nonnull final ExecutableElement componentId, 554 @Nonnull final ExecutableType componentIdMethodType ) 555 throws ProcessorException 556 { 557 MemberChecks.mustNotBeAbstract( Constants.COMPONENT_ID_CLASSNAME, componentId ); 558 MemberChecks.mustBeSubclassCallable( component.getElement(), 559 Constants.COMPONENT_CLASSNAME, 560 Constants.COMPONENT_ID_CLASSNAME, 561 componentId ); 562 MemberChecks.mustNotHaveAnyParameters( Constants.COMPONENT_ID_CLASSNAME, componentId ); 563 MemberChecks.mustReturnAValue( Constants.COMPONENT_ID_CLASSNAME, componentId ); 564 MemberChecks.mustNotThrowAnyExceptions( Constants.COMPONENT_ID_CLASSNAME, componentId ); 565 566 if ( null != component.getComponentId() ) 567 { 568 throw new ProcessorException( "@ComponentId target duplicates existing method named " + 569 component.getComponentId().getSimpleName(), componentId ); 570 } 571 else 572 { 573 component.setComponentId( Objects.requireNonNull( componentId ) ); 574 component.setComponentIdMethodType( componentIdMethodType ); 575 } 576 } 577 578 private void setComponentTypeNameRef( @Nonnull final ComponentDescriptor component, 579 @Nonnull final ExecutableElement method ) 580 throws ProcessorException 581 { 582 mustBeStandardRefMethod( processingEnv, 583 component, 584 method, 585 Constants.COMPONENT_TYPE_NAME_REF_CLASSNAME ); 586 MemberChecks.mustReturnAnInstanceOf( processingEnv, 587 method, 588 Constants.COMPONENT_TYPE_NAME_REF_CLASSNAME, 589 String.class.getName() ); 590 component.getComponentTypeNameRefs().add( method ); 591 } 592 593 private void addComponentNameRef( @Nonnull final ComponentDescriptor component, 594 @Nonnull final ExecutableElement method ) 595 throws ProcessorException 596 { 597 mustBeStandardRefMethod( processingEnv, 598 component, 599 method, 600 Constants.COMPONENT_NAME_REF_CLASSNAME ); 601 MemberChecks.mustReturnAnInstanceOf( processingEnv, 602 method, 603 Constants.COMPONENT_NAME_REF_CLASSNAME, 604 String.class.getName() ); 605 component.getComponentNameRefs().add( method ); 606 } 607 608 private void addPostConstruct( @Nonnull final ComponentDescriptor component, @Nonnull final ExecutableElement method ) 609 throws ProcessorException 610 { 611 MemberChecks.mustBeLifecycleHook( component.getElement(), 612 Constants.COMPONENT_CLASSNAME, 613 Constants.POST_CONSTRUCT_CLASSNAME, 614 method ); 615 shouldBeInternalLifecycleMethod( processingEnv, 616 component, 617 method, 618 Constants.POST_CONSTRUCT_CLASSNAME ); 619 component.getPostConstructs().add( method ); 620 } 621 622 private void addPreDispose( @Nonnull final ComponentDescriptor component, @Nonnull final ExecutableElement method ) 623 throws ProcessorException 624 { 625 MemberChecks.mustBeLifecycleHook( component.getElement(), 626 Constants.COMPONENT_CLASSNAME, 627 Constants.PRE_DISPOSE_CLASSNAME, 628 method ); 629 shouldBeInternalLifecycleMethod( processingEnv, 630 component, 631 method, 632 Constants.PRE_DISPOSE_CLASSNAME ); 633 component.getPreDisposes().add( method ); 634 } 635 636 private void addPostDispose( @Nonnull final ComponentDescriptor component, @Nonnull final ExecutableElement method ) 637 throws ProcessorException 638 { 639 MemberChecks.mustBeLifecycleHook( component.getElement(), 640 Constants.COMPONENT_CLASSNAME, 641 Constants.POST_DISPOSE_CLASSNAME, 642 method ); 643 shouldBeInternalLifecycleMethod( processingEnv, 644 component, 645 method, 646 Constants.POST_DISPOSE_CLASSNAME ); 647 component.getPostDisposes().add( method ); 648 } 649 650 private void linkUnAnnotatedObservables( @Nonnull final ComponentDescriptor component, 651 @Nonnull final Map<String, CandidateMethod> getters, 652 @Nonnull final Map<String, CandidateMethod> setters ) 653 throws ProcessorException 654 { 655 for ( final ObservableDescriptor observable : component.getObservables().values() ) 656 { 657 if ( !observable.hasSetter() && !observable.hasGetter() ) 658 { 659 throw new ProcessorException( "@ObservableValueRef target unable to be associated with an " + 660 "Observable property", observable.getRefMethods().get( 0 ).getMethod() ); 661 } 662 else if ( !observable.hasSetter() && observable.expectSetter() ) 663 { 664 final CandidateMethod candidate = setters.remove( observable.getName() ); 665 if ( null != candidate ) 666 { 667 MemberChecks.mustBeOverridable( component.getElement(), 668 Constants.COMPONENT_CLASSNAME, 669 Constants.OBSERVABLE_CLASSNAME, 670 candidate.getMethod() ); 671 observable.setSetter( candidate.getMethod(), candidate.getMethodType() ); 672 } 673 else if ( observable.hasGetter() ) 674 { 675 throw new ProcessorException( "@Observable target defined getter but no setter was defined and no " + 676 "setter could be automatically determined", observable.getGetter() ); 677 } 678 } 679 else if ( !observable.hasGetter() ) 680 { 681 final CandidateMethod candidate = getters.remove( observable.getName() ); 682 if ( null != candidate ) 683 { 684 MemberChecks.mustBeOverridable( component.getElement(), 685 Constants.COMPONENT_CLASSNAME, 686 Constants.OBSERVABLE_CLASSNAME, 687 candidate.getMethod() ); 688 observable.setGetter( candidate.getMethod(), candidate.getMethodType() ); 689 } 690 else 691 { 692 throw new ProcessorException( "@Observable target defined setter but no getter was defined and no " + 693 "getter could be automatically determined", observable.getSetter() ); 694 } 695 } 696 } 697 698 // Find pairs of un-annotated abstract setter/getter pairs and treat them as if they 699 // are annotated with @Observable 700 for ( final Map.Entry<String, CandidateMethod> entry : new ArrayList<>( getters.entrySet() ) ) 701 { 702 final CandidateMethod getter = entry.getValue(); 703 if ( getter.getMethod().getModifiers().contains( Modifier.ABSTRACT ) ) 704 { 705 final String name = entry.getKey(); 706 final CandidateMethod setter = setters.remove( name ); 707 if ( null != setter && setter.getMethod().getModifiers().contains( Modifier.ABSTRACT ) ) 708 { 709 final ObservableDescriptor observable = component.findOrCreateObservable( name ); 710 observable.setGetter( getter.getMethod(), getter.getMethodType() ); 711 observable.setSetter( setter.getMethod(), setter.getMethodType() ); 712 getters.remove( name ); 713 } 714 } 715 } 716 } 717 718 private void linkUnAnnotatedObserves( @Nonnull final ComponentDescriptor component, 719 @Nonnull final Map<String, CandidateMethod> observes, 720 @Nonnull final Map<String, CandidateMethod> onDepsChanges ) 721 throws ProcessorException 722 { 723 for ( final ObserveDescriptor observe : component.getObserves().values() ) 724 { 725 if ( !observe.hasObserve() ) 726 { 727 final CandidateMethod candidate = observes.remove( observe.getName() ); 728 if ( null != candidate ) 729 { 730 observe.setObserveMethod( false, 731 Priority.NORMAL, 732 true, 733 true, 734 true, 735 "AREZ", 736 false, 737 false, 738 candidate.getMethod(), 739 candidate.getMethodType() ); 740 } 741 else 742 { 743 throw new ProcessorException( "@OnDepsChange target has no corresponding @Observe that could " + 744 "be automatically determined", observe.getOnDepsChange() ); 745 } 746 } 747 else if ( !observe.hasOnDepsChange() ) 748 { 749 final CandidateMethod candidate = onDepsChanges.remove( observe.getName() ); 750 if ( null != candidate ) 751 { 752 setOnDepsChange( component, observe, candidate.getMethod() ); 753 } 754 } 755 } 756 } 757 758 private void setOnDepsChange( @Nonnull final ComponentDescriptor component, 759 @Nonnull final ObserveDescriptor observe, 760 @Nonnull final ExecutableElement method ) 761 { 762 MemberChecks.mustNotBeAbstract( Constants.ON_DEPS_CHANGE_CLASSNAME, method ); 763 MemberChecks.mustBeSubclassCallable( component.getElement(), 764 Constants.COMPONENT_CLASSNAME, 765 Constants.ON_DEPS_CHANGE_CLASSNAME, 766 method ); 767 final List<? extends VariableElement> parameters = method.getParameters(); 768 if ( 769 !( 770 parameters.isEmpty() || 771 ( 1 == parameters.size() && Constants.OBSERVER_CLASSNAME.equals( parameters.get( 0 ).asType().toString() ) ) 772 ) 773 ) 774 { 775 throw new ProcessorException( "@OnDepsChange target must not have any parameters or must have a single " + 776 "parameter of type arez.Observer", method ); 777 } 778 779 MemberChecks.mustNotReturnAnyValue( Constants.ON_DEPS_CHANGE_CLASSNAME, method ); 780 MemberChecks.mustNotThrowAnyExceptions( Constants.ON_DEPS_CHANGE_CLASSNAME, method ); 781 shouldBeInternalHookMethod( processingEnv, 782 component, 783 method, 784 Constants.ON_DEPS_CHANGE_CLASSNAME ); 785 observe.setOnDepsChange( method ); 786 } 787 788 private void verifyNoDuplicateAnnotations( @Nonnull final ExecutableElement method ) 789 throws ProcessorException 790 { 791 final List<String> annotations = 792 Arrays.asList( Constants.ACTION_CLASSNAME, 793 Constants.OBSERVE_CLASSNAME, 794 Constants.ON_DEPS_CHANGE_CLASSNAME, 795 Constants.OBSERVER_REF_CLASSNAME, 796 Constants.OBSERVABLE_CLASSNAME, 797 Constants.OBSERVABLE_VALUE_REF_CLASSNAME, 798 Constants.MEMOIZE_CLASSNAME, 799 Constants.MEMOIZE_CONTEXT_PARAMETER_CLASSNAME, 800 Constants.COMPUTABLE_VALUE_REF_CLASSNAME, 801 Constants.COMPONENT_REF_CLASSNAME, 802 Constants.COMPONENT_ID_CLASSNAME, 803 Constants.COMPONENT_NAME_REF_CLASSNAME, 804 Constants.COMPONENT_TYPE_NAME_REF_CLASSNAME, 805 Constants.CASCADE_DISPOSE_CLASSNAME, 806 Constants.CONTEXT_REF_CLASSNAME, 807 Constants.POST_CONSTRUCT_CLASSNAME, 808 Constants.PRE_DISPOSE_CLASSNAME, 809 Constants.POST_DISPOSE_CLASSNAME, 810 Constants.REFERENCE_CLASSNAME, 811 Constants.REFERENCE_ID_CLASSNAME, 812 Constants.ON_ACTIVATE_CLASSNAME, 813 Constants.ON_DEACTIVATE_CLASSNAME, 814 Constants.COMPONENT_DEPENDENCY_CLASSNAME ); 815 final Map<String, Collection<String>> exceptions = new HashMap<>(); 816 exceptions.put( Constants.OBSERVABLE_CLASSNAME, 817 Arrays.asList( Constants.COMPONENT_DEPENDENCY_CLASSNAME, 818 Constants.CASCADE_DISPOSE_CLASSNAME, 819 Constants.REFERENCE_ID_CLASSNAME ) ); 820 exceptions.put( Constants.REFERENCE_CLASSNAME, 821 Collections.singletonList( Constants.CASCADE_DISPOSE_CLASSNAME ) ); 822 exceptions.put( Constants.POST_CONSTRUCT_CLASSNAME, 823 Collections.singletonList( Constants.ACTION_CLASSNAME ) ); 824 825 MemberChecks.verifyNoOverlappingAnnotations( method, annotations, exceptions ); 826 } 827 828 private void verifyNoDuplicateAnnotations( @Nonnull final VariableElement field ) 829 throws ProcessorException 830 { 831 MemberChecks.verifyNoOverlappingAnnotations( field, 832 Arrays.asList( Constants.COMPONENT_DEPENDENCY_CLASSNAME, 833 Constants.CASCADE_DISPOSE_CLASSNAME ), 834 Collections.emptyMap() ); 835 } 836 837 @Nonnull 838 private String getPropertyAccessorName( @Nonnull final ExecutableElement method, @Nonnull final String specifiedName ) 839 throws ProcessorException 840 { 841 String name = deriveName( method, GETTER_PATTERN, specifiedName ); 842 if ( null != name ) 843 { 844 return name; 845 } 846 if ( method.getReturnType().getKind() == TypeKind.BOOLEAN ) 847 { 848 name = deriveName( method, ISSER_PATTERN, specifiedName ); 849 if ( null != name ) 850 { 851 return name; 852 } 853 } 854 return method.getSimpleName().toString(); 855 } 856 857 private void validate( final boolean allowEmpty, @Nonnull final ComponentDescriptor component ) 858 throws ProcessorException 859 { 860 component.getCascadeDisposes().values().forEach( CascadeDisposeDescriptor::validate ); 861 component.getObservables().values().forEach( ObservableDescriptor::validate ); 862 component.getMemoizes().values().forEach( e -> e.validate( processingEnv ) ); 863 component.getMemoizeContextParameters().values().forEach( p -> p.validate( processingEnv ) ); 864 component.getObserves().values().forEach( ObserveDescriptor::validate ); 865 component.getDependencies().values().forEach( DependencyDescriptor::validate ); 866 component.getReferences().values().forEach( ReferenceDescriptor::validate ); 867 component.getInverses().values().forEach( e -> e.validate( processingEnv ) ); 868 869 final boolean hasZeroReactiveElements = 870 component.getObservables().isEmpty() && 871 component.getActions().isEmpty() && 872 component.getMemoizes().isEmpty() && 873 component.getDependencies().isEmpty() && 874 component.getCascadeDisposes().isEmpty() && 875 component.getReferences().isEmpty() && 876 component.getInverses().isEmpty() && 877 component.getObserves().isEmpty(); 878 879 final TypeElement element = component.getElement(); 880 if ( null != component.getDefaultPriority() && 881 component.getMemoizes().isEmpty() && 882 component.getObserves().isEmpty() && 883 isWarningNotSuppressed( element, Constants.WARNING_UNNECESSARY_DEFAULT_PRIORITY ) ) 884 { 885 final String message = 886 MemberChecks.toSimpleName( Constants.COMPONENT_CLASSNAME ) + " target should not specify " + 887 "the defaultPriority parameter unless it contains methods annotated with either the " + 888 MemberChecks.toSimpleName( Constants.MEMOIZE_CLASSNAME ) + " annotation or the " + 889 MemberChecks.toSimpleName( Constants.OBSERVE_CLASSNAME ) + " annotation. " + 890 suppressedBy( Constants.WARNING_UNNECESSARY_DEFAULT_PRIORITY ); 891 processingEnv.getMessager().printMessage( WARNING, message, element ); 892 } 893 if ( !allowEmpty && hasZeroReactiveElements ) 894 { 895 throw new ProcessorException( "@ArezComponent target has no methods annotated with @Action, " + 896 "@CascadeDispose, @Memoize, @Observable, @Inverse, " + 897 "@Reference, @ComponentDependency or @Observe", element ); 898 } 899 else if ( allowEmpty && 900 !hasZeroReactiveElements && 901 isWarningNotSuppressed( element, Constants.WARNING_UNNECESSARY_ALLOW_EMPTY ) ) 902 { 903 final String message = 904 "@ArezComponent target has specified allowEmpty = true but has methods " + 905 "annotated with @Action, @CascadeDispose, @Memoize, @Observable, @Inverse, " + 906 "@Reference, @ComponentDependency or @Observe. " + 907 suppressedBy( Constants.WARNING_UNNECESSARY_ALLOW_EMPTY ); 908 processingEnv.getMessager().printMessage( WARNING, message, element ); 909 } 910 911 for ( final ExecutableElement componentIdRef : component.getComponentIdRefs() ) 912 { 913 if ( null != component.getComponentId() && 914 !processingEnv.getTypeUtils() 915 .isSameType( component.getComponentId().getReturnType(), componentIdRef.getReturnType() ) ) 916 { 917 throw new ProcessorException( "@ComponentIdRef target has a return type " + componentIdRef.getReturnType() + 918 " and a @ComponentId annotated method with a return type " + 919 componentIdRef.getReturnType() + ". The types must match.", 920 element ); 921 } 922 else if ( null == component.getComponentId() && 923 !processingEnv.getTypeUtils() 924 .isSameType( processingEnv.getTypeUtils().getPrimitiveType( TypeKind.INT ), 925 componentIdRef.getReturnType() ) ) 926 { 927 throw new ProcessorException( "@ComponentIdRef target has a return type " + componentIdRef.getReturnType() + 928 " but no @ComponentId annotated method. The type is expected to be of " + 929 "type int.", element ); 930 } 931 } 932 for ( final ExecutableElement constructor : ElementsUtil.getConstructors( element ) ) 933 { 934 if ( Elements.Origin.EXPLICIT == processingEnv.getElementUtils().getOrigin( constructor ) && 935 constructor.getModifiers().contains( Modifier.PUBLIC ) && 936 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_PUBLIC_CONSTRUCTOR ) ) 937 { 938 final String instruction = 939 component.isStingEnabled() ? 940 "The type is instantiated by the sting injection framework and should have a package-access constructor. " : 941 "It is recommended that a static create method be added to the component that is responsible " + 942 "for instantiating the arez implementation class. "; 943 944 final String message = 945 MemberChecks.shouldNot( Constants.COMPONENT_CLASSNAME, 946 "have a public constructor. " + instruction + 947 MemberChecks.suppressedBy( Constants.WARNING_PUBLIC_CONSTRUCTOR, 948 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ) ); 949 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, constructor ); 950 } 951 } 952 if ( null != component.getDeclaredDefaultReadOutsideTransaction() && 953 component.getObservables().isEmpty() && 954 component.getMemoizes().isEmpty() && 955 isWarningNotSuppressed( element, Constants.WARNING_UNNECESSARY_DEFAULT ) ) 956 { 957 final String message = 958 "@ArezComponent target has specified a value for the defaultReadOutsideTransaction parameter but does not " + 959 "contain any methods annotated with either @Memoize or @Observable. " + 960 suppressedBy( Constants.WARNING_UNNECESSARY_DEFAULT ); 961 processingEnv.getMessager().printMessage( WARNING, message, element ); 962 } 963 if ( null != component.getDeclaredDefaultWriteOutsideTransaction() && 964 component.getObservables().isEmpty() && 965 isWarningNotSuppressed( element, Constants.WARNING_UNNECESSARY_DEFAULT ) ) 966 { 967 final String message = 968 "@ArezComponent target has specified a value for the defaultWriteOutsideTransaction parameter but does not " + 969 "contain any methods annotated with @Observable. " + 970 suppressedBy( Constants.WARNING_UNNECESSARY_DEFAULT ); 971 processingEnv.getMessager().printMessage( WARNING, message, element ); 972 } 973 } 974 975 private void processCascadeDisposeFields( @Nonnull final ComponentDescriptor component ) 976 { 977 ElementsUtil.getFields( component.getElement() ) 978 .stream() 979 .filter( f -> AnnotationsUtil.hasAnnotationOfType( f, Constants.CASCADE_DISPOSE_CLASSNAME ) ) 980 .forEach( field -> processCascadeDisposeField( component, field ) ); 981 } 982 983 private void processCascadeDisposeField( @Nonnull final ComponentDescriptor component, 984 @Nonnull final VariableElement field ) 985 { 986 verifyNoDuplicateAnnotations( field ); 987 MemberChecks.mustBeSubclassCallable( component.getElement(), 988 Constants.COMPONENT_CLASSNAME, 989 Constants.CASCADE_DISPOSE_CLASSNAME, 990 field ); 991 mustBeCascadeDisposeTypeCompatible( field ); 992 component.addCascadeDispose( new CascadeDisposeDescriptor( field ) ); 993 } 994 995 @Nonnull 996 private String suppressedBy( @Nonnull final String warning ) 997 { 998 return MemberChecks.suppressedBy( warning, Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 999 } 1000 1001 private boolean isWarningNotSuppressed( @Nonnull final Element element, @Nonnull final String warning ) 1002 { 1003 return !ElementsUtil.isWarningSuppressed( element, 1004 warning, 1005 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 1006 } 1007 1008 @SuppressWarnings( "SameParameterValue" ) 1009 @Nonnull 1010 private String extractName( @Nonnull final ExecutableElement method, 1011 @Nonnull final Function<ExecutableElement, String> function, 1012 @Nonnull final String annotationClassname ) 1013 { 1014 return AnnotationsUtil.extractName( method, function, annotationClassname, "name", Constants.SENTINEL ); 1015 } 1016 1017 private void mustBeCascadeDisposeTypeCompatible( @Nonnull final VariableElement field ) 1018 { 1019 final TypeMirror typeMirror = field.asType(); 1020 if ( !isAssignable( typeMirror, getDisposableTypeElement() ) ) 1021 { 1022 final TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement( typeMirror ); 1023 final AnnotationMirror value = 1024 null != typeElement ? 1025 AnnotationsUtil.findAnnotationByType( typeElement, Constants.COMPONENT_CLASSNAME ) : 1026 null; 1027 if ( null == value || !isDisposableTrackableRequired( typeElement ) ) 1028 { 1029 throw new ProcessorException( "@CascadeDispose target must be assignable to " + 1030 Constants.DISPOSABLE_CLASSNAME + " or a type annotated with the @ArezComponent " + 1031 "annotation where the disposeNotifier does not resolve to DISABLE", 1032 field ); 1033 } 1034 } 1035 } 1036 1037 private void addCascadeDisposeMethod( @Nonnull final ComponentDescriptor component, 1038 @Nonnull final ExecutableElement method, 1039 @Nullable final ObservableDescriptor observable ) 1040 { 1041 MemberChecks.mustNotHaveAnyParameters( Constants.CASCADE_DISPOSE_CLASSNAME, method ); 1042 MemberChecks.mustNotThrowAnyExceptions( Constants.CASCADE_DISPOSE_CLASSNAME, method ); 1043 MemberChecks.mustBeSubclassCallable( component.getElement(), 1044 Constants.COMPONENT_CLASSNAME, 1045 Constants.CASCADE_DISPOSE_CLASSNAME, 1046 method ); 1047 mustBeCascadeDisposeTypeCompatible( method ); 1048 component.addCascadeDispose( new CascadeDisposeDescriptor( method, observable ) ); 1049 } 1050 1051 private void mustBeCascadeDisposeTypeCompatible( @Nonnull final ExecutableElement method ) 1052 { 1053 final TypeMirror typeMirror = method.getReturnType(); 1054 if ( !isAssignable( typeMirror, getDisposableTypeElement() ) ) 1055 { 1056 final TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement( typeMirror ); 1057 final AnnotationMirror value = 1058 null != typeElement ? 1059 AnnotationsUtil.findAnnotationByType( typeElement, Constants.COMPONENT_CLASSNAME ) : 1060 null; 1061 if ( null == value || !isDisposableTrackableRequired( typeElement ) ) 1062 { 1063 //The type of the field must implement {@link arez.Disposable} or must be annotated by {@link ArezComponent} 1064 throw new ProcessorException( "@CascadeDispose target must return a type assignable to " + 1065 Constants.DISPOSABLE_CLASSNAME + " or a type annotated with @ArezComponent", 1066 method ); 1067 } 1068 } 1069 } 1070 1071 private void addOrUpdateDependency( @Nonnull final ComponentDescriptor component, 1072 @Nonnull final ExecutableElement method, 1073 @Nonnull final ObservableDescriptor observable ) 1074 { 1075 final DependencyDescriptor dependencyDescriptor = 1076 component.getDependencies().computeIfAbsent( method, m -> createMethodDependencyDescriptor( component, method ) ); 1077 dependencyDescriptor.setObservable( observable ); 1078 } 1079 1080 private void addAction( @Nonnull final ComponentDescriptor component, 1081 @Nonnull final AnnotationMirror annotation, 1082 @Nonnull final ExecutableElement method, 1083 @Nonnull final ExecutableType methodType ) 1084 throws ProcessorException 1085 { 1086 MemberChecks.mustBeWrappable( component.getElement(), 1087 Constants.COMPONENT_CLASSNAME, 1088 Constants.ACTION_CLASSNAME, 1089 method ); 1090 1091 final String name = 1092 extractName( method, m -> m.getSimpleName().toString(), Constants.ACTION_CLASSNAME ); 1093 checkNameUnique( component, name, method, Constants.ACTION_CLASSNAME ); 1094 final boolean mutation = AnnotationsUtil.getAnnotationValueValue( annotation, "mutation" ); 1095 final boolean requireNewTransaction = 1096 AnnotationsUtil.getAnnotationValueValue( annotation, "requireNewTransaction" ); 1097 final boolean reportParameters = AnnotationsUtil.getAnnotationValueValue( annotation, "reportParameters" ); 1098 final boolean reportResult = AnnotationsUtil.getAnnotationValueValue( annotation, "reportResult" ); 1099 final boolean verifyRequired = AnnotationsUtil.getAnnotationValueValue( annotation, "verifyRequired" ); 1100 final ActionDescriptor action = 1101 new ActionDescriptor( component, 1102 name, 1103 requireNewTransaction, 1104 mutation, 1105 verifyRequired, 1106 reportParameters, 1107 reportResult, 1108 method, 1109 methodType ); 1110 component.getActions().put( action.getName(), action ); 1111 } 1112 1113 private void addObserve( @Nonnull final ComponentDescriptor component, 1114 @Nonnull final AnnotationMirror annotation, 1115 @Nonnull final ExecutableElement method, 1116 @Nonnull final ExecutableType methodType ) 1117 throws ProcessorException 1118 { 1119 final String name = deriveObserveName( method, annotation ); 1120 checkNameUnique( component, name, method, Constants.OBSERVE_CLASSNAME ); 1121 final boolean mutation = AnnotationsUtil.getAnnotationValueValue( annotation, "mutation" ); 1122 final boolean observeLowerPriorityDependencies = 1123 AnnotationsUtil.getAnnotationValueValue( annotation, "observeLowerPriorityDependencies" ); 1124 final boolean nestedActionsAllowed = AnnotationsUtil.getAnnotationValueValue( annotation, "nestedActionsAllowed" ); 1125 final VariableElement priority = AnnotationsUtil.getAnnotationValueValue( annotation, "priority" ); 1126 final boolean reportParameters = AnnotationsUtil.getAnnotationValueValue( annotation, "reportParameters" ); 1127 final boolean reportResult = AnnotationsUtil.getAnnotationValueValue( annotation, "reportResult" ); 1128 final VariableElement executor = AnnotationsUtil.getAnnotationValueValue( annotation, "executor" ); 1129 final VariableElement depType = AnnotationsUtil.getAnnotationValueValue( annotation, "depType" ); 1130 1131 component 1132 .findOrCreateObserve( name ) 1133 .setObserveMethod( mutation, 1134 toPriority( component.getDefaultPriority(), priority ), 1135 executor.getSimpleName().toString().equals( "INTERNAL" ), 1136 reportParameters, 1137 reportResult, 1138 depType.getSimpleName().toString(), 1139 observeLowerPriorityDependencies, 1140 nestedActionsAllowed, 1141 method, 1142 methodType ); 1143 } 1144 1145 @Nonnull 1146 private String deriveObserveName( @Nonnull final ExecutableElement method, 1147 @Nonnull final AnnotationMirror annotation ) 1148 throws ProcessorException 1149 { 1150 final String name = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 1151 if ( Constants.SENTINEL.equals( name ) ) 1152 { 1153 return method.getSimpleName().toString(); 1154 } 1155 else 1156 { 1157 if ( !SourceVersion.isIdentifier( name ) ) 1158 { 1159 throw new ProcessorException( "@Observe target specified an invalid name '" + name + "'. The " + 1160 "name must be a valid java identifier.", method ); 1161 } 1162 else if ( SourceVersion.isKeyword( name ) ) 1163 { 1164 throw new ProcessorException( "@Observe target specified an invalid name '" + name + "'. The " + 1165 "name must not be a java keyword.", method ); 1166 } 1167 return name; 1168 } 1169 } 1170 1171 private void addOnDepsChange( @Nonnull final ComponentDescriptor component, 1172 @Nonnull final AnnotationMirror annotation, 1173 @Nonnull final ExecutableElement method ) 1174 throws ProcessorException 1175 { 1176 final String name = 1177 deriveHookName( component, method, 1178 ON_DEPS_CHANGE_PATTERN, 1179 "DepsChange", 1180 AnnotationsUtil.getAnnotationValueValue( annotation, "name" ) ); 1181 setOnDepsChange( component, component.findOrCreateObserve( name ), method ); 1182 } 1183 1184 private void addObserverRef( @Nonnull final ComponentDescriptor component, 1185 @Nonnull final AnnotationMirror annotation, 1186 @Nonnull final ExecutableElement method, 1187 @Nonnull final ExecutableType methodType ) 1188 throws ProcessorException 1189 { 1190 mustBeStandardRefMethod( processingEnv, 1191 component, 1192 method, 1193 Constants.OBSERVER_REF_CLASSNAME ); 1194 MemberChecks.mustReturnAnInstanceOf( processingEnv, 1195 method, 1196 Constants.OBSERVER_REF_CLASSNAME, 1197 Constants.OBSERVER_CLASSNAME ); 1198 1199 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 1200 final String name; 1201 if ( Constants.SENTINEL.equals( declaredName ) ) 1202 { 1203 name = deriveName( method, OBSERVER_REF_PATTERN, declaredName ); 1204 if ( null == name ) 1205 { 1206 throw new ProcessorException( "Method annotated with @ObserverRef should specify name or be " + 1207 "named according to the convention get[Name]Observer", method ); 1208 } 1209 } 1210 else 1211 { 1212 name = declaredName; 1213 if ( !SourceVersion.isIdentifier( name ) ) 1214 { 1215 throw new ProcessorException( "@ObserverRef target specified an invalid name '" + name + "'. The " + 1216 "name must be a valid java identifier.", method ); 1217 } 1218 else if ( SourceVersion.isKeyword( name ) ) 1219 { 1220 throw new ProcessorException( "@ObserverRef target specified an invalid name '" + name + "'. The " + 1221 "name must not be a java keyword.", method ); 1222 } 1223 } 1224 component.getObserverRefs().computeIfAbsent( name, s -> new ArrayList<>() ) 1225 .add( new CandidateMethod( method, methodType ) ); 1226 } 1227 1228 private void addMemoizeContextParameter( @Nonnull final ComponentDescriptor component, 1229 @Nonnull final AnnotationMirror annotation, 1230 @Nonnull final ExecutableElement method, 1231 @Nonnull final ExecutableType methodType ) 1232 throws ProcessorException 1233 { 1234 final String methodName = method.getSimpleName().toString(); 1235 final MemoizeContextParameterMethodType mcpMethodType = 1236 PUSH_PATTERN.matcher( methodName ).matches() ? MemoizeContextParameterMethodType.Push : 1237 POP_PATTERN.matcher( methodName ).matches() ? MemoizeContextParameterMethodType.Pop : 1238 MemoizeContextParameterMethodType.Capture; 1239 final String name = deriveMemoizeContextParameterName( method, annotation, mcpMethodType ); 1240 1241 checkNameUnique( component, name, method, Constants.MEMOIZE_CONTEXT_PARAMETER_CLASSNAME ); 1242 final boolean allowEmpty = AnnotationsUtil.getAnnotationValueValue( annotation, "allowEmpty" ); 1243 final String pattern = AnnotationsUtil.getAnnotationValueValue( annotation, "pattern" ); 1244 final MemoizeContextParameterDescriptor descriptor = component.findOrCreateMemoizeContextParameter( name ); 1245 1246 final Pattern compiledPattern; 1247 try 1248 { 1249 compiledPattern = Pattern.compile( pattern ); 1250 } 1251 catch ( final PatternSyntaxException e ) 1252 { 1253 throw new ProcessorException( "@MemoizeContextParameter target specified a pattern parameter " + 1254 "that is not a valid regular expression.", method ); 1255 } 1256 1257 if ( MemoizeContextParameterMethodType.Capture == mcpMethodType ) 1258 { 1259 descriptor.setCapture( method, methodType, allowEmpty, pattern, compiledPattern ); 1260 } 1261 else if ( MemoizeContextParameterMethodType.Push == mcpMethodType ) 1262 { 1263 descriptor.setPush( method, methodType, allowEmpty, pattern, compiledPattern ); 1264 } 1265 else // MemoizeContextParameterMethodType.Pop == mcpMethodType 1266 { 1267 descriptor.setPop( method, methodType, allowEmpty, pattern, compiledPattern ); 1268 } 1269 } 1270 1271 private String deriveMemoizeContextParameterName( @Nonnull final ExecutableElement method, 1272 @Nonnull final AnnotationMirror annotation, 1273 @Nonnull final MemoizeContextParameterMethodType mcpMethodType ) 1274 throws ProcessorException 1275 { 1276 final String name = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 1277 if ( Constants.SENTINEL.equals( name ) ) 1278 { 1279 final Pattern pattern = 1280 MemoizeContextParameterMethodType.Push == mcpMethodType ? PUSH_PATTERN : 1281 MemoizeContextParameterMethodType.Pop == mcpMethodType ? POP_PATTERN : 1282 CAPTURE_PATTERN; 1283 final String methodName = method.getSimpleName().toString(); 1284 final Matcher matcher = pattern.matcher( methodName ); 1285 if ( matcher.find() ) 1286 { 1287 final String candidate = matcher.group( 1 ); 1288 return firstCharacterToLowerCase( candidate ); 1289 } 1290 else 1291 { 1292 // we get here for a capture method that does not start with capture 1293 return methodName; 1294 } 1295 } 1296 else 1297 { 1298 if ( !SourceVersion.isIdentifier( name ) ) 1299 { 1300 throw new ProcessorException( "@MemoizeContextParameter target specified an invalid name '" + name + 1301 "'. The name must be a valid java identifier.", method ); 1302 } 1303 else if ( SourceVersion.isKeyword( name ) ) 1304 { 1305 throw new ProcessorException( "@MemoizeContextParameter target specified an invalid name '" + name + 1306 "'. The name must not be a java keyword.", method ); 1307 } 1308 return name; 1309 } 1310 } 1311 1312 private void addMemoize( @Nonnull final ComponentDescriptor component, 1313 @Nonnull final AnnotationMirror annotation, 1314 @Nonnull final ExecutableElement method, 1315 @Nonnull final ExecutableType methodType ) 1316 throws ProcessorException 1317 { 1318 final String name = deriveMemoizeName( method, annotation ); 1319 checkNameUnique( component, name, method, Constants.MEMOIZE_CLASSNAME ); 1320 final boolean keepAlive = AnnotationsUtil.getAnnotationValueValue( annotation, "keepAlive" ); 1321 final boolean reportResult = AnnotationsUtil.getAnnotationValueValue( annotation, "reportResult" ); 1322 final boolean observeLowerPriorityDependencies = 1323 AnnotationsUtil.getAnnotationValueValue( annotation, "observeLowerPriorityDependencies" ); 1324 final VariableElement readOutsideTransaction = 1325 AnnotationsUtil.getAnnotationValueValue( annotation, "readOutsideTransaction" ); 1326 final VariableElement priority = AnnotationsUtil.getAnnotationValueValue( annotation, "priority" ); 1327 final VariableElement depType = AnnotationsUtil.getAnnotationValueValue( annotation, "depType" ); 1328 final String depTypeAsString = depType.getSimpleName().toString(); 1329 component.findOrCreateMemoize( name ).setMemoize( method, 1330 methodType, 1331 keepAlive, 1332 toPriority( component.getDefaultPriority(), 1333 priority ), 1334 reportResult, 1335 observeLowerPriorityDependencies, 1336 readOutsideTransaction.getSimpleName().toString(), 1337 depTypeAsString ); 1338 } 1339 1340 @Nonnull 1341 private Priority toPriority( @Nullable final Priority defaultPriority, 1342 @Nonnull final VariableElement priorityElement ) 1343 { 1344 final String priorityName = priorityElement.getSimpleName().toString(); 1345 return "DEFAULT".equals( priorityName ) ? 1346 null != defaultPriority ? defaultPriority : Priority.NORMAL : 1347 Priority.valueOf( priorityName ); 1348 } 1349 1350 private void autodetectObservableInitializers( @Nonnull final ComponentDescriptor component ) 1351 { 1352 for ( final ObservableDescriptor observable : component.getObservables().values() ) 1353 { 1354 if ( null == observable.getInitializer() && observable.hasGetter() ) 1355 { 1356 if ( observable.hasSetter() ) 1357 { 1358 final boolean initializer = 1359 autodetectInitializer( observable.getGetter() ) && autodetectInitializer( observable.getSetter() ); 1360 observable.setInitializer( initializer ); 1361 } 1362 else 1363 { 1364 observable.setInitializer( autodetectInitializer( observable.getGetter() ) ); 1365 } 1366 } 1367 } 1368 } 1369 1370 private boolean hasDependencyAnnotation( @Nonnull final ExecutableElement method ) 1371 { 1372 return AnnotationsUtil.hasAnnotationOfType( method, Constants.COMPONENT_DEPENDENCY_CLASSNAME ); 1373 } 1374 1375 private void ensureTargetTypeAligns( @Nonnull final ComponentDescriptor component, 1376 @Nonnull final InverseDescriptor descriptor, 1377 @Nonnull final TypeMirror target ) 1378 { 1379 if ( !processingEnv.getTypeUtils().isSameType( target, component.getElement().asType() ) ) 1380 { 1381 throw new ProcessorException( "@Inverse target expected to find an associated @Reference annotation with " + 1382 "a target type equal to " + component.getElement().asType() + " but the actual " + 1383 "target type is " + target, descriptor.getObservable().getGetter() ); 1384 } 1385 } 1386 1387 @Nullable 1388 private TypeElement getInverseManyTypeTarget( @Nonnull final ExecutableElement method ) 1389 { 1390 final TypeName typeName = TypeName.get( method.getReturnType() ); 1391 if ( typeName instanceof final ParameterizedTypeName type ) 1392 { 1393 if ( isSupportedInverseCollectionType( type.rawType.toString() ) && !type.typeArguments.isEmpty() ) 1394 { 1395 final TypeElement typeElement = getTypeElement( type.typeArguments.get( 0 ).toString() ); 1396 if ( AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.COMPONENT_CLASSNAME ) ) 1397 { 1398 return typeElement; 1399 } 1400 else 1401 { 1402 throw new ProcessorException( "@Inverse target expected to return a type annotated with " + 1403 Constants.COMPONENT_CLASSNAME, method ); 1404 } 1405 } 1406 } 1407 return null; 1408 } 1409 1410 private boolean isSupportedInverseCollectionType( @Nonnull final String typeClassname ) 1411 { 1412 return Collection.class.getName().equals( typeClassname ) || 1413 Set.class.getName().equals( typeClassname ) || 1414 List.class.getName().equals( typeClassname ); 1415 } 1416 1417 @Nonnull 1418 private String getInverseReferenceNameParameter( @Nonnull final ComponentDescriptor component, 1419 @Nonnull final ExecutableElement method ) 1420 { 1421 final String declaredName = 1422 (String) AnnotationsUtil.getAnnotationValue( method, 1423 Constants.INVERSE_CLASSNAME, 1424 "referenceName" ).getValue(); 1425 final String name; 1426 if ( Constants.SENTINEL.equals( declaredName ) ) 1427 { 1428 name = firstCharacterToLowerCase( component.getElement().getSimpleName().toString() ); 1429 } 1430 else 1431 { 1432 name = declaredName; 1433 if ( !SourceVersion.isIdentifier( name ) ) 1434 { 1435 throw new ProcessorException( "@Inverse target specified an invalid referenceName '" + name + "'. The " + 1436 "name must be a valid java identifier.", method ); 1437 } 1438 else if ( SourceVersion.isKeyword( name ) ) 1439 { 1440 throw new ProcessorException( "@Inverse target specified an invalid referenceName '" + name + "'. The " + 1441 "name must not be a java keyword.", method ); 1442 } 1443 } 1444 return name; 1445 } 1446 1447 private void linkDependencies( @Nonnull final ComponentDescriptor component, 1448 @Nonnull final Collection<CandidateMethod> candidates ) 1449 { 1450 component.getObservables() 1451 .values() 1452 .stream() 1453 .filter( ObservableDescriptor::hasGetter ) 1454 .filter( o -> hasDependencyAnnotation( o.getGetter() ) ) 1455 .forEach( o -> addOrUpdateDependency( component, o.getGetter(), o ) ); 1456 1457 component.getMemoizes() 1458 .values() 1459 .stream() 1460 .filter( MemoizeDescriptor::hasMemoize ) 1461 .map( MemoizeDescriptor::getMethod ) 1462 .filter( this::hasDependencyAnnotation ) 1463 .forEach( method1 -> component.addDependency( createMethodDependencyDescriptor( component, method1 ) ) ); 1464 1465 candidates 1466 .stream() 1467 .map( CandidateMethod::getMethod ) 1468 .filter( this::hasDependencyAnnotation ) 1469 .forEach( method -> component.addDependency( createMethodDependencyDescriptor( component, method ) ) ); 1470 } 1471 1472 private void linkCascadeDisposeObservables( @Nonnull final ComponentDescriptor component ) 1473 { 1474 for ( final ObservableDescriptor observable : component.getObservables().values() ) 1475 { 1476 final CascadeDisposeDescriptor cascadeDisposeDescriptor = observable.getCascadeDisposeDescriptor(); 1477 if ( null == cascadeDisposeDescriptor ) 1478 { 1479 //@CascadeDisposable can only occur on getter so if we don't have it then we look in 1480 // cascadeDisposableDescriptor list to see if we can match getter 1481 final CascadeDisposeDescriptor descriptor = component.getCascadeDisposes().get( observable.getGetter() ); 1482 if ( null != descriptor ) 1483 { 1484 descriptor.setObservable( observable ); 1485 } 1486 } 1487 } 1488 } 1489 1490 private void linkCascadeDisposeReferences( @Nonnull final ComponentDescriptor component ) 1491 { 1492 for ( final ReferenceDescriptor reference : component.getReferences().values() ) 1493 { 1494 final CascadeDisposeDescriptor cascadeDisposeDescriptor = reference.getCascadeDisposeDescriptor(); 1495 if ( null == cascadeDisposeDescriptor && reference.hasMethod() ) 1496 { 1497 final CascadeDisposeDescriptor descriptor = component.getCascadeDisposes().get( reference.getMethod() ); 1498 if ( null != descriptor ) 1499 { 1500 descriptor.setReference( reference ); 1501 } 1502 } 1503 } 1504 } 1505 1506 private void linkObserverRefs( @Nonnull final ComponentDescriptor component ) 1507 { 1508 for ( final Map.Entry<String, List<CandidateMethod>> entry : component.getObserverRefs().entrySet() ) 1509 { 1510 final String key = entry.getKey(); 1511 final List<CandidateMethod> methods = entry.getValue(); 1512 final ObserveDescriptor observed = component.getObserves().get( key ); 1513 if ( null != observed ) 1514 { 1515 methods.stream().map( CandidateMethod::getMethod ).forEach( observed::addRefMethod ); 1516 } 1517 else 1518 { 1519 throw new ProcessorException( "@ObserverRef target defined observer named '" + key + "' but no " + 1520 "@Observe method with that name exists", methods.get( 0 ).getMethod() ); 1521 } 1522 } 1523 } 1524 1525 @Nullable 1526 private Boolean isInitializerRequired( @Nonnull final ExecutableElement element ) 1527 { 1528 final AnnotationMirror annotation = 1529 AnnotationsUtil.findAnnotationByType( element, Constants.OBSERVABLE_CLASSNAME ); 1530 final AnnotationValue v = 1531 null == annotation ? null : AnnotationsUtil.findAnnotationValueNoDefaults( annotation, "initializer" ); 1532 final String value = null == v ? "AUTODETECT" : ( (VariableElement) v.getValue() ).getSimpleName().toString(); 1533 return switch ( value ) 1534 { 1535 case "ENABLE" -> Boolean.TRUE; 1536 case "DISABLE" -> Boolean.FALSE; 1537 default -> null; 1538 }; 1539 } 1540 1541 private boolean autodetectInitializer( @Nonnull final ExecutableElement element ) 1542 { 1543 return element.getModifiers().contains( Modifier.ABSTRACT ) && 1544 ( 1545 ( 1546 // Getter 1547 element.getReturnType().getKind() != TypeKind.VOID && 1548 AnnotationsUtil.hasNonnullAnnotation( element ) && 1549 !AnnotationsUtil.hasAnnotationOfType( element, Constants.INVERSE_CLASSNAME ) 1550 ) || 1551 ( 1552 // Setter 1553 1 == element.getParameters().size() && 1554 AnnotationsUtil.hasNonnullAnnotation( element.getParameters().get( 0 ) ) 1555 ) 1556 ); 1557 } 1558 1559 private void checkNameUnique( @Nonnull final ComponentDescriptor component, @Nonnull final String name, 1560 @Nonnull final ExecutableElement sourceMethod, 1561 @Nonnull final String sourceAnnotationName ) 1562 throws ProcessorException 1563 { 1564 final ActionDescriptor action = component.getActions().get( name ); 1565 if ( null != action ) 1566 { 1567 throw toException( name, 1568 sourceAnnotationName, 1569 sourceMethod, 1570 Constants.ACTION_CLASSNAME, 1571 action.getAction() ); 1572 } 1573 final MemoizeDescriptor memoize = component.getMemoizes().get( name ); 1574 if ( null != memoize && memoize.hasMemoize() ) 1575 { 1576 throw toException( name, 1577 sourceAnnotationName, 1578 sourceMethod, 1579 Constants.MEMOIZE_CLASSNAME, 1580 memoize.getMethod() ); 1581 } 1582 // Observe have pairs so let the caller determine whether a duplicate occurs in that scenario 1583 if ( !sourceAnnotationName.equals( Constants.OBSERVE_CLASSNAME ) ) 1584 { 1585 final ObserveDescriptor observed = component.getObserves().get( name ); 1586 if ( null != observed ) 1587 { 1588 throw toException( name, 1589 sourceAnnotationName, 1590 sourceMethod, 1591 Constants.OBSERVE_CLASSNAME, 1592 observed.getMethod() ); 1593 } 1594 } 1595 // Observables have pairs so let the caller determine whether a duplicate occurs in that scenario 1596 if ( !sourceAnnotationName.equals( Constants.OBSERVABLE_CLASSNAME ) ) 1597 { 1598 final ObservableDescriptor observable = component.getObservables().get( name ); 1599 if ( null != observable ) 1600 { 1601 throw toException( name, 1602 sourceAnnotationName, 1603 sourceMethod, 1604 Constants.OBSERVABLE_CLASSNAME, 1605 observable.getDefiner() ); 1606 } 1607 } 1608 } 1609 1610 @Nonnull 1611 private ProcessorException toException( @Nonnull final String name, 1612 @Nonnull final String sourceAnnotationName, 1613 @Nonnull final ExecutableElement sourceMethod, 1614 @Nonnull final String targetAnnotationName, 1615 @Nonnull final ExecutableElement targetElement ) 1616 { 1617 return new ProcessorException( "Method annotated with " + MemberChecks.toSimpleName( sourceAnnotationName ) + 1618 " specified name " + name + " that duplicates " + 1619 MemberChecks.toSimpleName( targetAnnotationName ) + " defined by method " + 1620 targetElement.getSimpleName(), sourceMethod ); 1621 } 1622 1623 private void processComponentDependencyFields( @Nonnull final ComponentDescriptor component ) 1624 { 1625 ElementsUtil.getFields( component.getElement() ) 1626 .stream() 1627 .filter( f -> AnnotationsUtil.hasAnnotationOfType( f, Constants.COMPONENT_DEPENDENCY_CLASSNAME ) ) 1628 .forEach( field -> processComponentDependencyField( component, field ) ); 1629 } 1630 1631 private void processComponentDependencyField( @Nonnull final ComponentDescriptor component, 1632 @Nonnull final VariableElement field ) 1633 { 1634 verifyNoDuplicateAnnotations( field ); 1635 MemberChecks.mustBeSubclassCallable( component.getElement(), 1636 Constants.COMPONENT_CLASSNAME, 1637 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 1638 field ); 1639 component.addDependency( createFieldDependencyDescriptor( component, field ) ); 1640 } 1641 1642 private void addReference( @Nonnull final ComponentDescriptor component, 1643 @Nonnull final AnnotationMirror annotation, 1644 @Nonnull final ExecutableElement method, 1645 @Nonnull final ExecutableType methodType ) 1646 { 1647 MemberChecks.mustNotHaveAnyParameters( Constants.REFERENCE_CLASSNAME, method ); 1648 MemberChecks.mustBeSubclassCallable( component.getElement(), 1649 Constants.COMPONENT_CLASSNAME, 1650 Constants.REFERENCE_CLASSNAME, 1651 method ); 1652 MemberChecks.mustNotThrowAnyExceptions( Constants.REFERENCE_CLASSNAME, method ); 1653 MemberChecks.mustReturnAValue( Constants.REFERENCE_CLASSNAME, method ); 1654 MemberChecks.mustBeAbstract( Constants.REFERENCE_CLASSNAME, method ); 1655 1656 final String name = getReferenceName( annotation, method ); 1657 final String linkType = getLinkType( method ); 1658 final String inverseName; 1659 final Multiplicity inverseMultiplicity; 1660 if ( hasInverse( annotation ) ) 1661 { 1662 inverseMultiplicity = getReferenceInverseMultiplicity( annotation ); 1663 inverseName = getReferenceInverseName( component, annotation, method, inverseMultiplicity ); 1664 final TypeMirror returnType = method.getReturnType(); 1665 if ( !( returnType instanceof DeclaredType ) || 1666 !AnnotationsUtil.hasAnnotationOfType( ( (DeclaredType) returnType ).asElement(), 1667 Constants.COMPONENT_CLASSNAME ) ) 1668 { 1669 throw new ProcessorException( "@Reference target expected to return a type annotated with " + 1670 MemberChecks.toSimpleName( Constants.COMPONENT_CLASSNAME ) + 1671 " if there is an inverse reference", method ); 1672 } 1673 } 1674 else 1675 { 1676 inverseName = null; 1677 inverseMultiplicity = null; 1678 } 1679 final ReferenceDescriptor descriptor = component.findOrCreateReference( name ); 1680 descriptor.setMethod( method, methodType, linkType, inverseName, inverseMultiplicity ); 1681 verifyMultiplicityOfAssociatedInverseMethod( component, descriptor ); 1682 } 1683 1684 private boolean hasInverse( @Nonnull final AnnotationMirror annotation ) 1685 { 1686 final VariableElement variableElement = AnnotationsUtil.getAnnotationValueValue( annotation, "inverse" ); 1687 return switch ( variableElement.getSimpleName().toString() ) 1688 { 1689 case "ENABLE" -> true; 1690 case "DISABLE" -> false; 1691 default -> null != AnnotationsUtil.findAnnotationValueNoDefaults( annotation, "inverseName" ) || 1692 null != AnnotationsUtil.findAnnotationValueNoDefaults( annotation, "inverseMultiplicity" ); 1693 }; 1694 } 1695 1696 private void verifyMultiplicityOfAssociatedReferenceMethod( @Nonnull final ComponentDescriptor component, 1697 @Nonnull final InverseDescriptor descriptor ) 1698 { 1699 final Multiplicity multiplicity = 1700 ElementsUtil 1701 .getMethods( descriptor.getTargetType(), 1702 processingEnv.getElementUtils(), 1703 processingEnv.getTypeUtils() ) 1704 .stream() 1705 .map( m -> { 1706 final AnnotationMirror a = 1707 AnnotationsUtil.findAnnotationByType( m, Constants.REFERENCE_CLASSNAME ); 1708 if ( null != a && getReferenceName( a, m ).equals( descriptor.getReferenceName() ) ) 1709 { 1710 if ( null == AnnotationsUtil.findAnnotationValueNoDefaults( a, "inverse" ) && 1711 null == AnnotationsUtil.findAnnotationValueNoDefaults( a, "inverseName" ) && 1712 null == AnnotationsUtil.findAnnotationValueNoDefaults( a, "inverseMultiplicity" ) ) 1713 { 1714 throw new ProcessorException( "@Inverse target found an associated @Reference on the method '" + 1715 m.getSimpleName() + "' on type '" + 1716 descriptor.getTargetType().getQualifiedName() + "' but the " + 1717 "annotation has not configured an inverse.", 1718 descriptor.getObservable().getGetter() ); 1719 } 1720 ensureTargetTypeAligns( component, descriptor, m.getReturnType() ); 1721 return getReferenceInverseMultiplicity( a ); 1722 } 1723 else 1724 { 1725 return null; 1726 } 1727 } ) 1728 .filter( Objects::nonNull ) 1729 .findAny() 1730 .orElse( null ); 1731 if ( null == multiplicity ) 1732 { 1733 throw new ProcessorException( "@Inverse target expected to find an associated @Reference annotation with " + 1734 "a name parameter equal to '" + descriptor.getReferenceName() + "' on class " + 1735 descriptor.getTargetType().getQualifiedName() + " but is unable to " + 1736 "locate a matching method.", descriptor.getObservable().getGetter() ); 1737 } 1738 1739 if ( descriptor.getMultiplicity() != multiplicity ) 1740 { 1741 throw new ProcessorException( "@Inverse target has a multiplicity of " + descriptor.getMultiplicity() + 1742 " but that associated @Reference has a multiplicity of " + multiplicity + 1743 ". The multiplicity must align.", descriptor.getObservable().getGetter() ); 1744 } 1745 } 1746 1747 @Nonnull 1748 private String getLinkType( @Nonnull final ExecutableElement method ) 1749 { 1750 return AnnotationsUtil.getEnumAnnotationParameter( method, Constants.REFERENCE_CLASSNAME, "load" ); 1751 } 1752 1753 @Nonnull 1754 private String getReferenceName( @Nonnull final AnnotationMirror annotation, 1755 @Nonnull final ExecutableElement method ) 1756 { 1757 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 1758 final String name; 1759 if ( Constants.SENTINEL.equals( declaredName ) ) 1760 { 1761 final String candidate = deriveName( method, GETTER_PATTERN, declaredName ); 1762 if ( null == candidate ) 1763 { 1764 name = method.getSimpleName().toString(); 1765 } 1766 else 1767 { 1768 name = candidate; 1769 } 1770 } 1771 else 1772 { 1773 name = declaredName; 1774 if ( !SourceVersion.isIdentifier( name ) ) 1775 { 1776 throw new ProcessorException( "@Reference target specified an invalid name '" + name + "'. The " + 1777 "name must be a valid java identifier.", method ); 1778 } 1779 else if ( SourceVersion.isKeyword( name ) ) 1780 { 1781 throw new ProcessorException( "@Reference target specified an invalid name '" + name + "'. The " + 1782 "name must not be a java keyword.", method ); 1783 } 1784 } 1785 return name; 1786 } 1787 1788 @Nonnull 1789 private Multiplicity getReferenceInverseMultiplicity( @Nonnull final AnnotationMirror annotation ) 1790 { 1791 final VariableElement variableElement = 1792 AnnotationsUtil.getAnnotationValueValue( annotation, "inverseMultiplicity" ); 1793 return switch ( variableElement.getSimpleName().toString() ) 1794 { 1795 case "MANY" -> Multiplicity.MANY; 1796 case "ONE" -> Multiplicity.ONE; 1797 default -> Multiplicity.ZERO_OR_ONE; 1798 }; 1799 } 1800 1801 @Nonnull 1802 private String getReferenceInverseName( @Nonnull final ComponentDescriptor component, 1803 @Nonnull final AnnotationMirror annotation, 1804 @Nonnull final ExecutableElement method, 1805 @Nonnull final Multiplicity multiplicity ) 1806 { 1807 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "inverseName" ); 1808 final String name; 1809 if ( Constants.SENTINEL.equals( declaredName ) ) 1810 { 1811 final String baseName = component.getElement().getSimpleName().toString(); 1812 return firstCharacterToLowerCase( baseName ) + ( Multiplicity.MANY == multiplicity ? "s" : "" ); 1813 } 1814 else 1815 { 1816 name = declaredName; 1817 if ( !SourceVersion.isIdentifier( name ) ) 1818 { 1819 throw new ProcessorException( "@Reference target specified an invalid inverseName '" + name + "'. The " + 1820 "inverseName must be a valid java identifier.", method ); 1821 } 1822 else if ( SourceVersion.isKeyword( name ) ) 1823 { 1824 throw new ProcessorException( "@Reference target specified an invalid inverseName '" + name + "'. The " + 1825 "inverseName must not be a java keyword.", method ); 1826 } 1827 } 1828 return name; 1829 } 1830 1831 private void ensureTargetTypeAligns( @Nonnull final ComponentDescriptor component, 1832 @Nonnull final ReferenceDescriptor descriptor, 1833 @Nonnull final TypeMirror target ) 1834 { 1835 if ( !processingEnv.getTypeUtils().isSameType( target, component.getElement().asType() ) ) 1836 { 1837 throw new ProcessorException( "@Reference target expected to find an associated @Inverse annotation with " + 1838 "a target type equal to " + component.getElement().getQualifiedName() + " but " + 1839 "the actual target type is " + target, descriptor.getMethod() ); 1840 } 1841 } 1842 1843 private void verifyMultiplicityOfAssociatedInverseMethod( @Nonnull final ComponentDescriptor component, 1844 @Nonnull final ReferenceDescriptor descriptor ) 1845 { 1846 final TypeElement element = 1847 (TypeElement) processingEnv.getTypeUtils().asElement( descriptor.getMethod().getReturnType() ); 1848 final String defaultInverseName = 1849 descriptor.hasInverse() ? 1850 null : 1851 firstCharacterToLowerCase( component.getElement().getSimpleName().toString() ) + "s"; 1852 final Multiplicity multiplicity = 1853 ElementsUtil 1854 .getMethods( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ) 1855 .stream() 1856 .map( m -> { 1857 final AnnotationMirror a = AnnotationsUtil.findAnnotationByType( m, Constants.INVERSE_CLASSNAME ); 1858 if ( null == a ) 1859 { 1860 return null; 1861 } 1862 final String inverseName = getInverseName( a, m ); 1863 if ( !descriptor.hasInverse() && inverseName.equals( defaultInverseName ) ) 1864 { 1865 throw new ProcessorException( "@Reference target has not configured an inverse but there is an " + 1866 "associated @Inverse annotated method named '" + m.getSimpleName() + 1867 "' on type '" + element.getQualifiedName() + "'.", 1868 descriptor.getMethod() ); 1869 } 1870 if ( descriptor.hasInverse() && inverseName.equals( descriptor.getInverseName() ) ) 1871 { 1872 final TypeElement target = getInverseManyTypeTarget( m ); 1873 if ( null != target ) 1874 { 1875 ensureTargetTypeAligns( component, descriptor, target.asType() ); 1876 return Multiplicity.MANY; 1877 } 1878 else 1879 { 1880 ensureTargetTypeAligns( component, descriptor, m.getReturnType() ); 1881 return AnnotationsUtil.hasNonnullAnnotation( m ) ? Multiplicity.ONE : Multiplicity.ZERO_OR_ONE; 1882 } 1883 } 1884 else 1885 { 1886 return null; 1887 } 1888 } ) 1889 .filter( Objects::nonNull ) 1890 .findAny() 1891 .orElse( null ); 1892 1893 if ( descriptor.hasInverse() ) 1894 { 1895 if ( null == multiplicity ) 1896 { 1897 throw new ProcessorException( "@Reference target expected to find an associated @Inverse annotation " + 1898 "with a name parameter equal to '" + descriptor.getInverseName() + "' on " + 1899 "class " + descriptor.getMethod().getReturnType() + " but is unable to " + 1900 "locate a matching method.", descriptor.getMethod() ); 1901 } 1902 1903 final Multiplicity inverseMultiplicity = descriptor.getInverseMultiplicity(); 1904 if ( inverseMultiplicity != multiplicity ) 1905 { 1906 throw new ProcessorException( "@Reference target has an inverseMultiplicity of " + inverseMultiplicity + 1907 " but that associated @Inverse has a multiplicity of " + multiplicity + 1908 ". The multiplicity must align.", descriptor.getMethod() ); 1909 } 1910 } 1911 } 1912 1913 @Nonnull 1914 private DependencyDescriptor createMethodDependencyDescriptor( @Nonnull final ComponentDescriptor descriptor, 1915 @Nonnull final ExecutableElement method ) 1916 { 1917 MemberChecks.mustNotHaveAnyParameters( Constants.COMPONENT_DEPENDENCY_CLASSNAME, method ); 1918 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 1919 Constants.COMPONENT_CLASSNAME, 1920 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 1921 method ); 1922 MemberChecks.mustNotThrowAnyExceptions( Constants.COMPONENT_DEPENDENCY_CLASSNAME, method ); 1923 MemberChecks.mustReturnAValue( Constants.COMPONENT_DEPENDENCY_CLASSNAME, method ); 1924 1925 final boolean validateTypeAtRuntime = 1926 (Boolean) AnnotationsUtil.getAnnotationValue( method, 1927 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 1928 "validateTypeAtRuntime" ).getValue(); 1929 1930 final TypeMirror type = method.getReturnType(); 1931 if ( TypeKind.DECLARED != type.getKind() ) 1932 { 1933 throw new ProcessorException( "@ComponentDependency target must return a non-primitive value", method ); 1934 } 1935 if ( !validateTypeAtRuntime ) 1936 { 1937 final TypeElement disposeNotifier = getTypeElement( Constants.DISPOSE_NOTIFIER_CLASSNAME ); 1938 if ( !processingEnv.getTypeUtils().isAssignable( type, disposeNotifier.asType() ) ) 1939 { 1940 final TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement( type ); 1941 if ( !isActAsComponentAnnotated( typeElement ) && !isDisposeTrackableComponent( typeElement ) ) 1942 { 1943 throw new ProcessorException( "@ComponentDependency target must return an instance compatible with " + 1944 Constants.DISPOSE_NOTIFIER_CLASSNAME + " or a type annotated " + 1945 "with @ArezComponent(disposeNotifier=ENABLE) or @ActAsComponent", method ); 1946 } 1947 } 1948 } 1949 1950 final boolean cascade = isActionCascade( method ); 1951 return new DependencyDescriptor( descriptor, method, cascade ); 1952 } 1953 1954 @Nonnull 1955 private DependencyDescriptor createFieldDependencyDescriptor( @Nonnull final ComponentDescriptor descriptor, 1956 @Nonnull final VariableElement field ) 1957 { 1958 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 1959 Constants.COMPONENT_CLASSNAME, 1960 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 1961 field ); 1962 MemberChecks.mustBeFinal( Constants.COMPONENT_DEPENDENCY_CLASSNAME, field ); 1963 1964 final boolean validateTypeAtRuntime = 1965 (Boolean) AnnotationsUtil.getAnnotationValue( field, 1966 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 1967 "validateTypeAtRuntime" ).getValue(); 1968 1969 final TypeMirror type = processingEnv.getTypeUtils().asMemberOf( descriptor.asDeclaredType(), field ); 1970 if ( TypeKind.TYPEVAR != type.getKind() && TypeKind.DECLARED != type.getKind() ) 1971 { 1972 throw new ProcessorException( "@ComponentDependency target must be a non-primitive value", field ); 1973 } 1974 if ( !validateTypeAtRuntime ) 1975 { 1976 final TypeElement disposeNotifier = getTypeElement( Constants.DISPOSE_NOTIFIER_CLASSNAME ); 1977 if ( !processingEnv.getTypeUtils().isAssignable( type, disposeNotifier.asType() ) ) 1978 { 1979 final Element element = processingEnv.getTypeUtils().asElement( type ); 1980 if ( !( element instanceof TypeElement ) || 1981 !isActAsComponentAnnotated( (TypeElement) element ) && 1982 !isDisposeTrackableComponent( (TypeElement) element ) ) 1983 { 1984 throw new ProcessorException( "@ComponentDependency target must be an instance compatible with " + 1985 Constants.DISPOSE_NOTIFIER_CLASSNAME + " or a type annotated " + 1986 "with @ArezComponent(disposeNotifier=ENABLE) or @ActAsComponent", field ); 1987 } 1988 } 1989 } 1990 1991 if ( !isActionCascade( field ) ) 1992 { 1993 throw new ProcessorException( "@ComponentDependency target defined an action of 'SET_NULL' but the " + 1994 "dependency is on a final field and can not be set to null.", field ); 1995 1996 } 1997 1998 return new DependencyDescriptor( descriptor, field ); 1999 } 2000 2001 private boolean isActionCascade( @Nonnull final Element method ) 2002 { 2003 final String value = 2004 AnnotationsUtil.getEnumAnnotationParameter( method, 2005 Constants.COMPONENT_DEPENDENCY_CLASSNAME, 2006 "action" ); 2007 return "CASCADE".equals( value ); 2008 } 2009 2010 @SuppressWarnings( "BooleanMethodIsAlwaysInverted" ) 2011 private boolean isActAsComponentAnnotated( @Nonnull final TypeElement typeElement ) 2012 { 2013 return AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.ACT_AS_COMPONENT_CLASSNAME ); 2014 } 2015 2016 @SuppressWarnings( "BooleanMethodIsAlwaysInverted" ) 2017 private boolean isDisposeTrackableComponent( @Nonnull final TypeElement typeElement ) 2018 { 2019 return AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.COMPONENT_CLASSNAME ) && 2020 isDisposableTrackableRequired( typeElement ); 2021 } 2022 2023 @Nonnull 2024 private ComponentDescriptor parse( @Nonnull final TypeElement typeElement ) 2025 throws ProcessorException 2026 { 2027 if ( ElementKind.CLASS != typeElement.getKind() && ElementKind.INTERFACE != typeElement.getKind() ) 2028 { 2029 throw new ProcessorException( "@ArezComponent target must be a class or an interface", typeElement ); 2030 } 2031 else if ( typeElement.getModifiers().contains( Modifier.FINAL ) ) 2032 { 2033 throw new ProcessorException( "@ArezComponent target must not be final", typeElement ); 2034 } 2035 else if ( ElementsUtil.isNonStaticNestedClass( typeElement ) ) 2036 { 2037 throw new ProcessorException( "@ArezComponent target must not be a non-static nested class", typeElement ); 2038 } 2039 final AnnotationMirror arezComponent = 2040 AnnotationsUtil.getAnnotationByType( typeElement, Constants.COMPONENT_CLASSNAME ); 2041 final String declaredName = getAnnotationParameter( arezComponent, "name" ); 2042 final boolean disposeOnDeactivate = getAnnotationParameter( arezComponent, "disposeOnDeactivate" ); 2043 final boolean observableFlag = isComponentObservableRequired( arezComponent, disposeOnDeactivate ); 2044 final boolean service = isService( typeElement ); 2045 final boolean disposeNotifierFlag = isDisposableTrackableRequired( typeElement ); 2046 final boolean allowEmpty = getAnnotationParameter( arezComponent, "allowEmpty" ); 2047 final List<VariableElement> fields = ElementsUtil.getFields( typeElement ); 2048 ensureNoFieldInjections( fields ); 2049 ensureNoMethodInjections( typeElement ); 2050 final boolean sting = isStingIntegrationEnabled( arezComponent, service ); 2051 2052 final AnnotationValue defaultReadOutsideTransaction = 2053 AnnotationsUtil.findAnnotationValueNoDefaults( arezComponent, "defaultReadOutsideTransaction" ); 2054 final AnnotationValue defaultWriteOutsideTransaction = 2055 AnnotationsUtil.findAnnotationValueNoDefaults( arezComponent, "defaultWriteOutsideTransaction" ); 2056 2057 final boolean requireEquals = isEqualsRequired( arezComponent ); 2058 final boolean requireVerify = isVerifyRequired( arezComponent, typeElement ); 2059 2060 if ( !typeElement.getModifiers().contains( Modifier.ABSTRACT ) ) 2061 { 2062 throw new ProcessorException( "@ArezComponent target must be abstract", typeElement ); 2063 } 2064 2065 final String name = 2066 Constants.SENTINEL.equals( declaredName ) ? 2067 typeElement.getQualifiedName().toString().replace( ".", "_" ) : 2068 declaredName; 2069 2070 if ( !SourceVersion.isIdentifier( name ) ) 2071 { 2072 throw new ProcessorException( "@ArezComponent target specified an invalid name '" + name + "'. The " + 2073 "name must be a valid java identifier.", typeElement ); 2074 } 2075 else if ( SourceVersion.isKeyword( name ) ) 2076 { 2077 throw new ProcessorException( "@ArezComponent target specified an invalid name '" + name + "'. The " + 2078 "name must not be a java keyword.", typeElement ); 2079 } 2080 2081 verifyConstructors( typeElement, sting ); 2082 2083 if ( sting && !( (DeclaredType) typeElement.asType() ).getTypeArguments().isEmpty() ) 2084 { 2085 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2086 "enable sting integration and be a parameterized type" ), 2087 typeElement ); 2088 } 2089 if ( !sting && AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_EAGER ) ) 2090 { 2091 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2092 "disable sting integration and be annotated with " + 2093 Constants.STING_EAGER ), 2094 typeElement ); 2095 } 2096 if ( !sting && AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_TYPED ) ) 2097 { 2098 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2099 "disable sting integration and be annotated with " + 2100 Constants.STING_TYPED ), 2101 typeElement ); 2102 } 2103 if ( !sting && AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_NAMED ) ) 2104 { 2105 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2106 "disable sting integration and be annotated with " + 2107 Constants.STING_NAMED ), 2108 typeElement ); 2109 } 2110 if ( !sting && AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_CONTRIBUTE_TO ) ) 2111 { 2112 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2113 "disable sting integration and be annotated with " + 2114 Constants.STING_CONTRIBUTE_TO ), 2115 typeElement ); 2116 } 2117 2118 if ( !observableFlag && disposeOnDeactivate ) 2119 { 2120 throw new ProcessorException( "@ArezComponent target has specified observable = DISABLE and " + 2121 "disposeOnDeactivate = true which is not a valid combination", typeElement ); 2122 } 2123 2124 if ( isWarningNotSuppressed( typeElement, Constants.WARNING_EXTENDS_COMPONENT ) ) 2125 { 2126 TypeMirror parent = typeElement.getSuperclass(); 2127 while ( null != parent ) 2128 { 2129 final Element parentElement = processingEnv.getTypeUtils().asElement( parent ); 2130 final TypeElement parentTypeElement = 2131 null != parentElement && ElementKind.CLASS == parentElement.getKind() ? (TypeElement) parentElement : null; 2132 2133 if ( null != parentTypeElement && 2134 AnnotationsUtil.hasAnnotationOfType( parentTypeElement, Constants.COMPONENT_CLASSNAME ) ) 2135 { 2136 final String message = 2137 MemberChecks.shouldNot( Constants.COMPONENT_CLASSNAME, 2138 "extend a class annotated with the " + Constants.COMPONENT_CLASSNAME + 2139 " annotation. " + suppressedBy( Constants.WARNING_EXTENDS_COMPONENT ) ); 2140 processingEnv.getMessager().printMessage( WARNING, message, typeElement ); 2141 } 2142 parent = null != parentTypeElement ? parentTypeElement.getSuperclass() : null; 2143 } 2144 } 2145 2146 final List<ExecutableElement> methods = 2147 ElementsUtil.getMethods( typeElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils(), true ); 2148 final boolean generateToString = methods.stream(). 2149 noneMatch( m -> m.getSimpleName().toString().equals( "toString" ) && 2150 m.getParameters().isEmpty() && 2151 !( m.getEnclosingElement().getSimpleName().toString().equals( "Object" ) && 2152 "java.lang".equals( processingEnv 2153 .getElementUtils() 2154 .getPackageOf( m.getEnclosingElement() ) 2155 .getQualifiedName() 2156 .toString() ) ) ); 2157 2158 final String priority = getDefaultPriority( arezComponent ); 2159 final Priority defaultPriority = 2160 null == priority ? null : "DEFAULT".equals( priority ) ? Priority.NORMAL : Priority.valueOf( priority ); 2161 2162 final String defaultReadOutsideTransactionValue = 2163 null == defaultReadOutsideTransaction ? 2164 null : 2165 ( (VariableElement) defaultReadOutsideTransaction.getValue() ).getSimpleName().toString(); 2166 final String defaultWriteOutsideTransactionValue = 2167 null == defaultWriteOutsideTransaction ? 2168 null : 2169 ( (VariableElement) defaultWriteOutsideTransaction.getValue() ).getSimpleName().toString(); 2170 2171 final ComponentDescriptor descriptor = 2172 new ComponentDescriptor( name, 2173 defaultPriority, 2174 observableFlag, 2175 disposeNotifierFlag, 2176 disposeOnDeactivate, 2177 sting, 2178 requireEquals, 2179 requireVerify, 2180 generateToString, 2181 typeElement, 2182 defaultReadOutsideTransactionValue, 2183 defaultWriteOutsideTransactionValue ); 2184 2185 analyzeCandidateMethods( descriptor, methods, processingEnv.getTypeUtils() ); 2186 validate( allowEmpty, descriptor ); 2187 2188 for ( final ObservableDescriptor observable : descriptor.getObservables().values() ) 2189 { 2190 if ( observable.expectSetter() ) 2191 { 2192 final TypeMirror returnType = observable.getGetterType().getReturnType(); 2193 final TypeMirror parameterType = observable.getSetterType().getParameterTypes().get( 0 ); 2194 if ( !processingEnv.getTypeUtils().isSameType( parameterType, returnType ) && 2195 !parameterType.toString().equals( returnType.toString() ) ) 2196 { 2197 throw new ProcessorException( "@Observable property defines a setter and getter with different types." + 2198 " Getter type: " + returnType + " Setter type: " + parameterType + ".", 2199 observable.getGetter() ); 2200 } 2201 } 2202 } 2203 2204 final boolean idRequired = isIdRequired( arezComponent ); 2205 descriptor.setIdRequired( idRequired ); 2206 if ( !idRequired ) 2207 { 2208 if ( descriptor.hasComponentIdMethod() ) 2209 { 2210 throw new ProcessorException( "@ArezComponent target has specified the idRequired = DISABLE " + 2211 "annotation parameter but also has annotated a method with @ComponentId " + 2212 "that requires idRequired = ENABLE.", typeElement ); 2213 } 2214 if ( !descriptor.getComponentIdRefs().isEmpty() ) 2215 { 2216 throw new ProcessorException( "@ArezComponent target has specified the idRequired = DISABLE " + 2217 "annotation parameter but also has annotated a method with @ComponentIdRef " + 2218 "that requires idRequired = ENABLE.", typeElement ); 2219 } 2220 if ( !descriptor.getInverses().isEmpty() ) 2221 { 2222 throw new ProcessorException( "@ArezComponent target has specified the idRequired = DISABLE " + 2223 "annotation parameter but also has annotated a method with @Inverse " + 2224 "that requires idRequired = ENABLE.", typeElement ); 2225 } 2226 } 2227 2228 warnOnUnmanagedComponentReferences( descriptor, fields ); 2229 2230 return descriptor; 2231 } 2232 2233 private boolean isStingIntegrationEnabled( @Nonnull final AnnotationMirror arezComponent, final boolean service ) 2234 { 2235 final VariableElement parameter = getAnnotationParameter( arezComponent, "sting" ); 2236 final String value = parameter.getSimpleName().toString(); 2237 return "ENABLE".equals( value ) || 2238 ( "AUTODETECT".equals( value ) && 2239 service && 2240 null != findTypeElement( Constants.STING_INJECTOR ) ); 2241 } 2242 2243 private void verifyConstructors( @Nonnull final TypeElement typeElement, final boolean sting ) 2244 { 2245 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( typeElement ); 2246 if ( constructors.size() > 1 ) 2247 { 2248 if ( sting ) 2249 { 2250 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2251 "enable sting integration and have multiple constructors" ), 2252 typeElement ); 2253 } 2254 } 2255 2256 for ( final ExecutableElement constructor : constructors ) 2257 { 2258 if ( constructor.getModifiers().contains( Modifier.PROTECTED ) && 2259 isWarningNotSuppressed( constructor, Constants.WARNING_PROTECTED_CONSTRUCTOR ) ) 2260 { 2261 final String message = 2262 MemberChecks.should( Constants.COMPONENT_CLASSNAME, 2263 "have a package access constructor. " + 2264 suppressedBy( Constants.WARNING_PROTECTED_CONSTRUCTOR ) ); 2265 processingEnv.getMessager().printMessage( WARNING, message, constructor ); 2266 } 2267 verifyConstructorParameters( constructor, sting ); 2268 } 2269 } 2270 2271 private void verifyConstructorParameters( @Nonnull final ExecutableElement constructor, final boolean sting ) 2272 { 2273 for ( final VariableElement parameter : constructor.getParameters() ) 2274 { 2275 final TypeMirror type = parameter.asType(); 2276 if ( sting && TypesUtil.containsArrayType( type ) ) 2277 { 2278 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2279 "enable sting integration and contain a constructor with a parameter that contains an array type" ), 2280 parameter ); 2281 } 2282 else if ( sting && TypesUtil.containsWildcard( type ) ) 2283 { 2284 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2285 "enable sting integration and contain a constructor with a parameter that contains a wildcard type parameter" ), 2286 parameter ); 2287 } 2288 else if ( sting && TypesUtil.containsRawType( type ) ) 2289 { 2290 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2291 "enable sting integration and contain a constructor with a parameter that contains a raw type" ), 2292 parameter ); 2293 } 2294 else if ( !sting && AnnotationsUtil.hasAnnotationOfType( parameter, Constants.STING_NAMED ) ) 2295 { 2296 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2297 "disable sting integration and contain a constructor with a parameter that is annotated with the " + 2298 Constants.STING_NAMED + " annotation" ), 2299 parameter ); 2300 } 2301 else if ( sting && TypeKind.DECLARED == type.getKind() && !( (DeclaredType) type ).getTypeArguments().isEmpty() ) 2302 { 2303 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2304 "enable sting integration and contain a constructor with a parameter that contains a parameterized type" ), 2305 parameter ); 2306 } 2307 } 2308 } 2309 2310 private void ensureNoFieldInjections( @Nonnull final List<VariableElement> fields ) 2311 { 2312 for ( final VariableElement field : fields ) 2313 { 2314 if ( hasInjectAnnotation( field ) ) 2315 { 2316 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2317 "contain fields annotated by the " + 2318 Constants.INJECT_CLASSNAME + 2319 " annotation. Use constructor injection instead" ), 2320 field ); 2321 } 2322 } 2323 } 2324 2325 private void ensureNoMethodInjections( @Nonnull final TypeElement typeElement ) 2326 { 2327 final List<ExecutableElement> methods = 2328 ElementsUtil.getMethods( typeElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 2329 for ( final ExecutableElement method : methods ) 2330 { 2331 if ( hasInjectAnnotation( method ) ) 2332 { 2333 throw new ProcessorException( MemberChecks.mustNot( Constants.COMPONENT_CLASSNAME, 2334 "contain methods annotated by the " + 2335 Constants.INJECT_CLASSNAME + 2336 " annotation. Use constructor injection instead" ), 2337 method ); 2338 } 2339 } 2340 } 2341 2342 private void analyzeCandidateMethods( @Nonnull final ComponentDescriptor componentDescriptor, 2343 @Nonnull final List<ExecutableElement> methods, 2344 @Nonnull final Types typeUtils ) 2345 throws ProcessorException 2346 { 2347 for ( final ExecutableElement method : methods ) 2348 { 2349 final String methodName = method.getSimpleName().toString(); 2350 if ( AREZ_SPECIAL_METHODS.contains( methodName ) && method.getParameters().isEmpty() ) 2351 { 2352 throw new ProcessorException( "Method defined on a class annotated by @ArezComponent uses a name " + 2353 "reserved by Arez", method ); 2354 } 2355 else if ( methodName.startsWith( ComponentGenerator.FIELD_PREFIX ) || 2356 methodName.startsWith( ComponentGenerator.OBSERVABLE_DATA_FIELD_PREFIX ) || 2357 methodName.startsWith( ComponentGenerator.REFERENCE_FIELD_PREFIX ) || 2358 methodName.startsWith( ComponentGenerator.FRAMEWORK_PREFIX ) ) 2359 { 2360 throw new ProcessorException( "Method defined on a class annotated by @ArezComponent uses a name " + 2361 "with a prefix reserved by Arez", method ); 2362 } 2363 } 2364 final Map<String, CandidateMethod> getters = new HashMap<>(); 2365 final Map<String, CandidateMethod> captures = new HashMap<>(); 2366 final Map<String, CandidateMethod> pushes = new HashMap<>(); 2367 final Map<String, CandidateMethod> pops = new HashMap<>(); 2368 final Map<String, CandidateMethod> setters = new HashMap<>(); 2369 final Map<String, CandidateMethod> observes = new HashMap<>(); 2370 final Map<String, CandidateMethod> onDepsChanges = new HashMap<>(); 2371 for ( final ExecutableElement method : methods ) 2372 { 2373 final ExecutableType methodType = 2374 (ExecutableType) typeUtils.asMemberOf( (DeclaredType) componentDescriptor.getElement().asType(), method ); 2375 if ( !analyzeMethod( componentDescriptor, method, methodType ) ) 2376 { 2377 /* 2378 * If we get here the method was not annotated so we can try to detect if it is a 2379 * candidate arez method in case some arez annotations are implied via naming conventions. 2380 */ 2381 if ( method.getModifiers().contains( Modifier.STATIC ) ) 2382 { 2383 continue; 2384 } 2385 2386 final CandidateMethod candidateMethod = new CandidateMethod( method, methodType ); 2387 final boolean voidReturn = method.getReturnType().getKind() == TypeKind.VOID; 2388 final int parameterCount = method.getParameters().size(); 2389 String name; 2390 2391 name = deriveName( method, PUSH_PATTERN, Constants.SENTINEL ); 2392 if ( voidReturn && 1 == parameterCount && null != name ) 2393 { 2394 pushes.put( name, candidateMethod ); 2395 continue; 2396 } 2397 name = deriveName( method, POP_PATTERN, Constants.SENTINEL ); 2398 if ( voidReturn && 1 == parameterCount && null != name ) 2399 { 2400 pops.put( name, candidateMethod ); 2401 continue; 2402 } 2403 name = deriveName( method, CAPTURE_PATTERN, Constants.SENTINEL ); 2404 if ( !voidReturn && 0 == parameterCount && null != name ) 2405 { 2406 captures.put( name, candidateMethod ); 2407 continue; 2408 } 2409 2410 if ( !method.getModifiers().contains( Modifier.FINAL ) ) 2411 { 2412 name = deriveName( method, SETTER_PATTERN, Constants.SENTINEL ); 2413 if ( voidReturn && 1 == parameterCount && null != name ) 2414 { 2415 setters.put( name, candidateMethod ); 2416 continue; 2417 } 2418 name = deriveName( method, ISSER_PATTERN, Constants.SENTINEL ); 2419 if ( !voidReturn && 0 == parameterCount && null != name ) 2420 { 2421 getters.put( name, candidateMethod ); 2422 continue; 2423 } 2424 name = deriveName( method, GETTER_PATTERN, Constants.SENTINEL ); 2425 if ( !voidReturn && 0 == parameterCount && null != name ) 2426 { 2427 getters.put( name, candidateMethod ); 2428 continue; 2429 } 2430 } 2431 name = deriveName( method, ON_DEPS_CHANGE_PATTERN, Constants.SENTINEL ); 2432 if ( voidReturn && null != name ) 2433 { 2434 if ( 0 == parameterCount || 2435 ( 2436 1 == parameterCount && 2437 Constants.OBSERVER_CLASSNAME.equals( method.getParameters().get( 0 ).asType().toString() ) 2438 ) 2439 ) 2440 { 2441 onDepsChanges.put( name, candidateMethod ); 2442 continue; 2443 } 2444 } 2445 2446 final String methodName = method.getSimpleName().toString(); 2447 if ( !OBJECT_METHODS.contains( methodName ) ) 2448 { 2449 observes.put( methodName, candidateMethod ); 2450 } 2451 } 2452 } 2453 2454 linkUnAnnotatedObservables( componentDescriptor, getters, setters ); 2455 linkUnAnnotatedObserves( componentDescriptor, observes, onDepsChanges ); 2456 linkUnMemoizeContextParameters( componentDescriptor, captures, pushes, pops ); 2457 linkObserverRefs( componentDescriptor ); 2458 linkCascadeDisposeObservables( componentDescriptor ); 2459 linkCascadeDisposeReferences( componentDescriptor ); 2460 2461 // CascadeDispose returned false but it was actually processed so lets remove them from getters set 2462 2463 componentDescriptor.getCascadeDisposes().keySet().forEach( method -> { 2464 for ( final Map.Entry<String, CandidateMethod> entry : new HashMap<>( getters ).entrySet() ) 2465 { 2466 if ( method.equals( entry.getValue().getMethod() ) ) 2467 { 2468 getters.remove( entry.getKey() ); 2469 } 2470 } 2471 } ); 2472 2473 linkMemoizeContextParametersToMemoizes( componentDescriptor ); 2474 2475 linkDependencies( componentDescriptor, getters.values() ); 2476 2477 autodetectObservableInitializers( componentDescriptor ); 2478 2479 /* 2480 * All of the maps will have called remove() for all matching candidates. 2481 * Thus any left are the non-arez methods. 2482 */ 2483 2484 ensureNoAbstractMethods( componentDescriptor, getters.values() ); 2485 ensureNoAbstractMethods( componentDescriptor, setters.values() ); 2486 ensureNoAbstractMethods( componentDescriptor, observes.values() ); 2487 ensureNoAbstractMethods( componentDescriptor, onDepsChanges.values() ); 2488 2489 processCascadeDisposeFields( componentDescriptor ); 2490 processComponentDependencyFields( componentDescriptor ); 2491 } 2492 2493 private static void linkMemoizeContextParametersToMemoizes( final @Nonnull ComponentDescriptor componentDescriptor ) 2494 { 2495 // Link MemoizeContextParameters to associated Memoize descriptors 2496 componentDescriptor 2497 .getMemoizes() 2498 .values() 2499 .forEach( m -> 2500 componentDescriptor 2501 .getMemoizeContextParameters() 2502 .values() 2503 .forEach( p -> p.tryMatchMemoizeDescriptor( m ) ) ); 2504 } 2505 2506 private void linkUnMemoizeContextParameters( @Nonnull final ComponentDescriptor componentDescriptor, 2507 @Nonnull final Map<String, CandidateMethod> captures, 2508 @Nonnull final Map<String, CandidateMethod> pushes, 2509 @Nonnull final Map<String, CandidateMethod> pops ) 2510 { 2511 final var parameters = componentDescriptor.getMemoizeContextParameters().values(); 2512 for ( final var parameter : parameters ) 2513 { 2514 if ( !parameter.hasCapture() ) 2515 { 2516 final CandidateMethod capture = captures.remove( parameter.getName() ); 2517 if ( null != capture ) 2518 { 2519 parameter.linkUnAnnotatedCapture( capture.getMethod(), capture.getMethodType() ); 2520 } 2521 } 2522 if ( !parameter.hasPop() ) 2523 { 2524 final CandidateMethod pop = pops.remove( parameter.getName() ); 2525 if ( null != pop ) 2526 { 2527 parameter.linkUnAnnotatedPop( pop.getMethod(), pop.getMethodType() ); 2528 } 2529 } 2530 if ( !parameter.hasPush() ) 2531 { 2532 final CandidateMethod push = pushes.remove( parameter.getName() ); 2533 if ( null != push ) 2534 { 2535 parameter.linkUnAnnotatedPush( push.getMethod(), push.getMethodType() ); 2536 } 2537 } 2538 } 2539 } 2540 2541 private void ensureNoAbstractMethods( @Nonnull final ComponentDescriptor componentDescriptor, 2542 @Nonnull final Collection<CandidateMethod> candidateMethods ) 2543 { 2544 candidateMethods 2545 .stream() 2546 .map( CandidateMethod::getMethod ) 2547 .filter( m -> m.getModifiers().contains( Modifier.ABSTRACT ) ) 2548 .forEach( m -> { 2549 throw new ProcessorException( "@ArezComponent target has an abstract method not implemented by " + 2550 "framework. The method is named " + m.getSimpleName(), 2551 componentDescriptor.getElement() ); 2552 } ); 2553 } 2554 2555 private boolean analyzeMethod( @Nonnull final ComponentDescriptor descriptor, 2556 @Nonnull final ExecutableElement method, 2557 @Nonnull final ExecutableType methodType ) 2558 throws ProcessorException 2559 { 2560 emitWarningForUnnecessaryProtectedMethod( descriptor, method ); 2561 emitWarningForUnnecessaryFinalMethod( descriptor, method ); 2562 verifyNoDuplicateAnnotations( method ); 2563 2564 final AnnotationMirror action = 2565 AnnotationsUtil.findAnnotationByType( method, Constants.ACTION_CLASSNAME ); 2566 final AnnotationMirror jaxWsAction = 2567 AnnotationsUtil.findAnnotationByType( method, Constants.JAX_WS_ACTION_CLASSNAME ); 2568 final AnnotationMirror observed = 2569 AnnotationsUtil.findAnnotationByType( method, Constants.OBSERVE_CLASSNAME ); 2570 final AnnotationMirror observable = 2571 AnnotationsUtil.findAnnotationByType( method, Constants.OBSERVABLE_CLASSNAME ); 2572 final AnnotationMirror observableValueRef = 2573 AnnotationsUtil.findAnnotationByType( method, Constants.OBSERVABLE_VALUE_REF_CLASSNAME ); 2574 final AnnotationMirror memoize = 2575 AnnotationsUtil.findAnnotationByType( method, Constants.MEMOIZE_CLASSNAME ); 2576 final AnnotationMirror memoizeContextParameter = 2577 AnnotationsUtil.findAnnotationByType( method, Constants.MEMOIZE_CONTEXT_PARAMETER_CLASSNAME ); 2578 final AnnotationMirror computableValueRef = 2579 AnnotationsUtil.findAnnotationByType( method, Constants.COMPUTABLE_VALUE_REF_CLASSNAME ); 2580 final AnnotationMirror contextRef = 2581 AnnotationsUtil.findAnnotationByType( method, Constants.CONTEXT_REF_CLASSNAME ); 2582 final AnnotationMirror stateRef = 2583 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_STATE_REF_CLASSNAME ); 2584 final AnnotationMirror componentRef = 2585 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_REF_CLASSNAME ); 2586 final AnnotationMirror componentId = 2587 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_ID_CLASSNAME ); 2588 final AnnotationMirror componentIdRef = 2589 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_ID_REF_CLASSNAME ); 2590 final AnnotationMirror componentTypeName = 2591 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_TYPE_NAME_REF_CLASSNAME ); 2592 final AnnotationMirror componentNameRef = 2593 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_NAME_REF_CLASSNAME ); 2594 final AnnotationMirror postConstruct = 2595 AnnotationsUtil.findAnnotationByType( method, Constants.POST_CONSTRUCT_CLASSNAME ); 2596 final AnnotationMirror ejbPostConstruct = 2597 AnnotationsUtil.findAnnotationByType( method, Constants.EJB_POST_CONSTRUCT_CLASSNAME ); 2598 final AnnotationMirror preDispose = 2599 AnnotationsUtil.findAnnotationByType( method, Constants.PRE_DISPOSE_CLASSNAME ); 2600 final AnnotationMirror postDispose = 2601 AnnotationsUtil.findAnnotationByType( method, Constants.POST_DISPOSE_CLASSNAME ); 2602 final AnnotationMirror onActivate = 2603 AnnotationsUtil.findAnnotationByType( method, Constants.ON_ACTIVATE_CLASSNAME ); 2604 final AnnotationMirror onDeactivate = 2605 AnnotationsUtil.findAnnotationByType( method, Constants.ON_DEACTIVATE_CLASSNAME ); 2606 final AnnotationMirror onDepsChange = 2607 AnnotationsUtil.findAnnotationByType( method, Constants.ON_DEPS_CHANGE_CLASSNAME ); 2608 final AnnotationMirror observerRef = 2609 AnnotationsUtil.findAnnotationByType( method, Constants.OBSERVER_REF_CLASSNAME ); 2610 final AnnotationMirror dependency = 2611 AnnotationsUtil.findAnnotationByType( method, Constants.COMPONENT_DEPENDENCY_CLASSNAME ); 2612 final AnnotationMirror reference = 2613 AnnotationsUtil.findAnnotationByType( method, Constants.REFERENCE_CLASSNAME ); 2614 final AnnotationMirror referenceId = 2615 AnnotationsUtil.findAnnotationByType( method, Constants.REFERENCE_ID_CLASSNAME ); 2616 final AnnotationMirror inverse = 2617 AnnotationsUtil.findAnnotationByType( method, Constants.INVERSE_CLASSNAME ); 2618 final AnnotationMirror preInverseRemove = 2619 AnnotationsUtil.findAnnotationByType( method, Constants.PRE_INVERSE_REMOVE_CLASSNAME ); 2620 final AnnotationMirror postInverseAdd = 2621 AnnotationsUtil.findAnnotationByType( method, Constants.POST_INVERSE_ADD_CLASSNAME ); 2622 final AnnotationMirror cascadeDispose = 2623 AnnotationsUtil.findAnnotationByType( method, Constants.CASCADE_DISPOSE_CLASSNAME ); 2624 2625 if ( null != observable ) 2626 { 2627 final ObservableDescriptor observableDescriptor = addObservable( descriptor, 2628 observable, method, methodType ); 2629 if ( null != referenceId ) 2630 { 2631 addReferenceId( descriptor, referenceId, observableDescriptor, method ); 2632 } 2633 if ( null != inverse ) 2634 { 2635 addInverse( descriptor, inverse, observableDescriptor, method ); 2636 } 2637 if ( null != cascadeDispose ) 2638 { 2639 addCascadeDisposeMethod( descriptor, method, observableDescriptor ); 2640 } 2641 return true; 2642 } 2643 else if ( null != observableValueRef ) 2644 { 2645 addObservableValueRef( descriptor, observableValueRef, method, methodType ); 2646 return true; 2647 } 2648 else if ( null != action ) 2649 { 2650 if ( null != postConstruct ) 2651 { 2652 addPostConstruct( descriptor, method ); 2653 } 2654 addAction( descriptor, action, method, methodType ); 2655 return true; 2656 } 2657 else if ( null != observed ) 2658 { 2659 addObserve( descriptor, observed, method, methodType ); 2660 return true; 2661 } 2662 else if ( null != onDepsChange ) 2663 { 2664 addOnDepsChange( descriptor, onDepsChange, method ); 2665 return true; 2666 } 2667 else if ( null != observerRef ) 2668 { 2669 addObserverRef( descriptor, observerRef, method, methodType ); 2670 return true; 2671 } 2672 else if ( null != contextRef ) 2673 { 2674 addContextRef( descriptor, method ); 2675 return true; 2676 } 2677 else if ( null != stateRef ) 2678 { 2679 addComponentStateRef( descriptor, stateRef, method ); 2680 return true; 2681 } 2682 else if ( null != memoizeContextParameter ) 2683 { 2684 addMemoizeContextParameter( descriptor, memoizeContextParameter, method, methodType ); 2685 return true; 2686 } 2687 else if ( null != memoize ) 2688 { 2689 addMemoize( descriptor, memoize, method, methodType ); 2690 return true; 2691 } 2692 else if ( null != computableValueRef ) 2693 { 2694 addComputableValueRef( descriptor, computableValueRef, method, methodType ); 2695 return true; 2696 } 2697 else if ( null != reference ) 2698 { 2699 if ( null != cascadeDispose ) 2700 { 2701 addCascadeDisposeMethod( descriptor, method, null ); 2702 } 2703 addReference( descriptor, reference, method, methodType ); 2704 return true; 2705 } 2706 else if ( null != cascadeDispose ) 2707 { 2708 addCascadeDisposeMethod( descriptor, method, null ); 2709 // Return false so that it can be picked as the getter of an @Observable or linked to a @Reference 2710 return false; 2711 } 2712 else if ( null != componentIdRef ) 2713 { 2714 addComponentIdRef( descriptor, method ); 2715 return true; 2716 } 2717 else if ( null != componentRef ) 2718 { 2719 addComponentRef( descriptor, method ); 2720 return true; 2721 } 2722 else if ( null != componentId ) 2723 { 2724 setComponentId( descriptor, method, methodType ); 2725 return true; 2726 } 2727 else if ( null != componentNameRef ) 2728 { 2729 addComponentNameRef( descriptor, method ); 2730 return true; 2731 } 2732 else if ( null != componentTypeName ) 2733 { 2734 setComponentTypeNameRef( descriptor, method ); 2735 return true; 2736 } 2737 else if ( null != jaxWsAction ) 2738 { 2739 throw new ProcessorException( "@" + Constants.JAX_WS_ACTION_CLASSNAME + " annotation " + 2740 "not supported in components annotated with @ArezComponent, use the @" + 2741 Constants.ACTION_CLASSNAME + " annotation instead.", 2742 method ); 2743 } 2744 else if ( null != ejbPostConstruct ) 2745 { 2746 throw new ProcessorException( "@" + Constants.EJB_POST_CONSTRUCT_CLASSNAME + " annotation " + 2747 "not supported in components annotated with @ArezComponent, use the @" + 2748 Constants.POST_CONSTRUCT_CLASSNAME + " annotation instead.", 2749 method ); 2750 } 2751 else if ( null != postConstruct ) 2752 { 2753 addPostConstruct( descriptor, method ); 2754 return true; 2755 } 2756 else if ( null != preDispose ) 2757 { 2758 addPreDispose( descriptor, method ); 2759 return true; 2760 } 2761 else if ( null != postDispose ) 2762 { 2763 addPostDispose( descriptor, method ); 2764 return true; 2765 } 2766 else if ( null != onActivate ) 2767 { 2768 addOnActivate( descriptor, onActivate, method ); 2769 return true; 2770 } 2771 else if ( null != onDeactivate ) 2772 { 2773 addOnDeactivate( descriptor, onDeactivate, method ); 2774 return true; 2775 } 2776 else if ( null != dependency ) 2777 { 2778 descriptor.addDependency( createMethodDependencyDescriptor( descriptor, method ) ); 2779 return false; 2780 } 2781 else if ( null != referenceId ) 2782 { 2783 addReferenceId( descriptor, referenceId, method ); 2784 return true; 2785 } 2786 else if ( null != inverse ) 2787 { 2788 addInverse( descriptor, inverse, method, methodType ); 2789 return true; 2790 } 2791 else if ( null != preInverseRemove ) 2792 { 2793 addPreInverseRemove( descriptor, preInverseRemove, method ); 2794 return true; 2795 } 2796 else if ( null != postInverseAdd ) 2797 { 2798 addPostInverseAdd( descriptor, postInverseAdd, method ); 2799 return true; 2800 } 2801 else 2802 { 2803 return false; 2804 } 2805 } 2806 2807 private void emitWarningForUnnecessaryProtectedMethod( @Nonnull final ComponentDescriptor descriptor, 2808 @Nonnull final ExecutableElement method ) 2809 { 2810 if ( method.getModifiers().contains( Modifier.PROTECTED ) && 2811 Objects.equals( method.getEnclosingElement(), descriptor.getElement() ) && 2812 ElementsUtil.isWarningNotSuppressed( method, 2813 Constants.WARNING_PROTECTED_METHOD, 2814 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ) && 2815 !isMethodAProtectedOverride( descriptor.getElement(), method ) ) 2816 { 2817 final String message = 2818 MemberChecks.shouldNot( Constants.COMPONENT_CLASSNAME, 2819 "declare a protected method. " + 2820 MemberChecks.suppressedBy( Constants.WARNING_PROTECTED_METHOD, 2821 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ) ); 2822 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); 2823 } 2824 } 2825 2826 private void emitWarningForUnnecessaryFinalMethod( @Nonnull final ComponentDescriptor descriptor, 2827 @Nonnull final ExecutableElement method ) 2828 { 2829 if ( method.getModifiers().contains( Modifier.FINAL ) && 2830 Objects.equals( method.getEnclosingElement(), descriptor.getElement() ) && 2831 ElementsUtil.isWarningNotSuppressed( method, 2832 Constants.WARNING_FINAL_METHOD, 2833 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ) ) 2834 { 2835 final String message = 2836 MemberChecks.shouldNot( Constants.COMPONENT_CLASSNAME, 2837 "declare a final method. " + 2838 MemberChecks.suppressedBy( Constants.WARNING_FINAL_METHOD, 2839 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ) ); 2840 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, method ); 2841 } 2842 } 2843 2844 private void addReferenceId( @Nonnull final ComponentDescriptor descriptor, 2845 @Nonnull final AnnotationMirror annotation, 2846 @Nonnull final ObservableDescriptor observable, 2847 @Nonnull final ExecutableElement method ) 2848 { 2849 MemberChecks.mustNotHaveAnyParameters( Constants.REFERENCE_ID_CLASSNAME, method ); 2850 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 2851 Constants.COMPONENT_CLASSNAME, 2852 Constants.REFERENCE_ID_CLASSNAME, 2853 method ); 2854 MemberChecks.mustNotThrowAnyExceptions( Constants.REFERENCE_ID_CLASSNAME, method ); 2855 MemberChecks.mustReturnAValue( Constants.REFERENCE_ID_CLASSNAME, method ); 2856 2857 final String name = getReferenceIdName( annotation, method ); 2858 descriptor.findOrCreateReference( name ).setObservable( observable ); 2859 } 2860 2861 private void addReferenceId( @Nonnull final ComponentDescriptor descriptor, 2862 @Nonnull final AnnotationMirror annotation, 2863 @Nonnull final ExecutableElement method ) 2864 { 2865 MemberChecks.mustNotHaveAnyParameters( Constants.REFERENCE_ID_CLASSNAME, method ); 2866 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 2867 Constants.COMPONENT_CLASSNAME, 2868 Constants.REFERENCE_ID_CLASSNAME, 2869 method ); 2870 MemberChecks.mustNotThrowAnyExceptions( Constants.REFERENCE_ID_CLASSNAME, method ); 2871 MemberChecks.mustReturnAValue( Constants.REFERENCE_ID_CLASSNAME, method ); 2872 2873 final String name = getReferenceIdName( annotation, method ); 2874 descriptor.findOrCreateReference( name ).setIdMethod( method ); 2875 } 2876 2877 @Nonnull 2878 private String getReferenceIdName( @Nonnull final AnnotationMirror annotation, 2879 @Nonnull final ExecutableElement method ) 2880 { 2881 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 2882 final String name; 2883 if ( Constants.SENTINEL.equals( declaredName ) ) 2884 { 2885 final String candidate = deriveName( method, ID_GETTER_PATTERN, declaredName ); 2886 if ( null == candidate ) 2887 { 2888 final String candidate2 = deriveName( method, RAW_ID_GETTER_PATTERN, declaredName ); 2889 if ( null == candidate2 ) 2890 { 2891 throw new ProcessorException( "@ReferenceId target has not specified a name and does not follow " + 2892 "the convention \"get[Name]Id\" or \"[name]Id\"", method ); 2893 } 2894 else 2895 { 2896 name = candidate2; 2897 } 2898 } 2899 else 2900 { 2901 name = candidate; 2902 } 2903 } 2904 else 2905 { 2906 name = declaredName; 2907 if ( !SourceVersion.isIdentifier( name ) ) 2908 { 2909 throw new ProcessorException( "@ReferenceId target specified an invalid name '" + name + "'. The " + 2910 "name must be a valid java identifier.", method ); 2911 } 2912 else if ( SourceVersion.isKeyword( name ) ) 2913 { 2914 throw new ProcessorException( "@ReferenceId target specified an invalid name '" + name + "'. The " + 2915 "name must not be a java keyword.", method ); 2916 } 2917 } 2918 return name; 2919 } 2920 2921 private void addPreInverseRemove( @Nonnull final ComponentDescriptor component, 2922 @Nonnull final AnnotationMirror annotation, 2923 @Nonnull final ExecutableElement method ) 2924 throws ProcessorException 2925 { 2926 mustBeHookHook( component.getElement(), 2927 Constants.PRE_INVERSE_REMOVE_CLASSNAME, 2928 method ); 2929 shouldBeInternalHookMethod( processingEnv, 2930 component, 2931 method, 2932 Constants.PRE_INVERSE_REMOVE_CLASSNAME ); 2933 if ( 1 != method.getParameters().size() ) 2934 { 2935 throw new ProcessorException( MemberChecks.must( Constants.PRE_INVERSE_REMOVE_CLASSNAME, 2936 "have exactly 1 parameter" ), method ); 2937 } 2938 2939 final String name = getPreInverseRemoveName( annotation, method ); 2940 findOrCreateInverseDescriptor( component, name ).addPreInverseRemoveHook( method ); 2941 } 2942 2943 @Nonnull 2944 private String getPreInverseRemoveName( @Nonnull final AnnotationMirror annotation, 2945 @Nonnull final ExecutableElement method ) 2946 { 2947 final String name = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 2948 if ( Constants.SENTINEL.equals( name ) ) 2949 { 2950 final String candidate = deriveName( method, PRE_INVERSE_REMOVE_PATTERN, name ); 2951 if ( null == candidate ) 2952 { 2953 throw new ProcessorException( "@PreInverseRemove target has not specified a name and does not follow " + 2954 "the convention \"pre[Name]Remove\"", method ); 2955 } 2956 else 2957 { 2958 return candidate; 2959 } 2960 } 2961 else 2962 { 2963 if ( !SourceVersion.isIdentifier( name ) ) 2964 { 2965 throw new ProcessorException( "@PreInverseRemove target specified an invalid name '" + name + "'. The " + 2966 "name must be a valid java identifier", method ); 2967 } 2968 else if ( SourceVersion.isKeyword( name ) ) 2969 { 2970 throw new ProcessorException( "@PreInverseRemove target specified an invalid name '" + name + "'. The " + 2971 "name must not be a java keyword", method ); 2972 } 2973 return name; 2974 } 2975 } 2976 2977 private void addPostInverseAdd( @Nonnull final ComponentDescriptor component, 2978 @Nonnull final AnnotationMirror annotation, 2979 @Nonnull final ExecutableElement method ) 2980 throws ProcessorException 2981 { 2982 mustBeHookHook( component.getElement(), 2983 Constants.POST_INVERSE_ADD_CLASSNAME, 2984 method ); 2985 shouldBeInternalHookMethod( processingEnv, 2986 component, 2987 method, 2988 Constants.POST_INVERSE_ADD_CLASSNAME ); 2989 if ( 1 != method.getParameters().size() ) 2990 { 2991 throw new ProcessorException( MemberChecks.must( Constants.POST_INVERSE_ADD_CLASSNAME, 2992 "have exactly 1 parameter" ), method ); 2993 } 2994 final String name = getPostInverseAddName( annotation, method ); 2995 findOrCreateInverseDescriptor( component, name ).addPostInverseAddHook( method ); 2996 } 2997 2998 @Nonnull 2999 private String getPostInverseAddName( @Nonnull final AnnotationMirror annotation, 3000 @Nonnull final ExecutableElement method ) 3001 { 3002 final String name = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 3003 if ( Constants.SENTINEL.equals( name ) ) 3004 { 3005 final String candidate = deriveName( method, POST_INVERSE_ADD_PATTERN, name ); 3006 if ( null == candidate ) 3007 { 3008 throw new ProcessorException( "@PostInverseAdd target has not specified a name and does not follow " + 3009 "the convention \"post[Name]Add\"", method ); 3010 } 3011 else 3012 { 3013 return candidate; 3014 } 3015 } 3016 else 3017 { 3018 if ( !SourceVersion.isIdentifier( name ) ) 3019 { 3020 throw new ProcessorException( "@PostInverseAdd target specified an invalid name '" + name + "'. The " + 3021 "name must be a valid java identifier", method ); 3022 } 3023 else if ( SourceVersion.isKeyword( name ) ) 3024 { 3025 throw new ProcessorException( "@PostInverseAdd target specified an invalid name '" + name + "'. The " + 3026 "name must not be a java keyword", method ); 3027 } 3028 return name; 3029 } 3030 } 3031 3032 private void addInverse( @Nonnull final ComponentDescriptor descriptor, 3033 @Nonnull final AnnotationMirror annotation, 3034 @Nonnull final ExecutableElement method, 3035 @Nonnull final ExecutableType methodType ) 3036 { 3037 MemberChecks.mustNotHaveAnyParameters( Constants.INVERSE_CLASSNAME, method ); 3038 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 3039 Constants.COMPONENT_CLASSNAME, 3040 Constants.INVERSE_CLASSNAME, 3041 method ); 3042 MemberChecks.mustNotThrowAnyExceptions( Constants.INVERSE_CLASSNAME, method ); 3043 MemberChecks.mustReturnAValue( Constants.INVERSE_CLASSNAME, method ); 3044 MemberChecks.mustBeAbstract( Constants.INVERSE_CLASSNAME, method ); 3045 3046 final String name = getInverseName( annotation, method ); 3047 final ObservableDescriptor observable = descriptor.findOrCreateObservable( name ); 3048 observable.setGetter( method, methodType ); 3049 3050 addInverse( descriptor, annotation, observable, method ); 3051 } 3052 3053 private void addInverse( @Nonnull final ComponentDescriptor descriptor, 3054 @Nonnull final AnnotationMirror annotation, 3055 @Nonnull final ObservableDescriptor observable, 3056 @Nonnull final ExecutableElement method ) 3057 { 3058 MemberChecks.mustNotHaveAnyParameters( Constants.INVERSE_CLASSNAME, method ); 3059 MemberChecks.mustBeSubclassCallable( descriptor.getElement(), 3060 Constants.COMPONENT_CLASSNAME, 3061 Constants.INVERSE_CLASSNAME, 3062 method ); 3063 MemberChecks.mustNotThrowAnyExceptions( Constants.INVERSE_CLASSNAME, method ); 3064 MemberChecks.mustReturnAValue( Constants.INVERSE_CLASSNAME, method ); 3065 MemberChecks.mustBeAbstract( Constants.INVERSE_CLASSNAME, method ); 3066 3067 final String name = getInverseName( annotation, method ); 3068 final InverseDescriptor existing = descriptor.getInverses().get( name ); 3069 if ( null != existing && existing.hasObservable() ) 3070 { 3071 throw new ProcessorException( "@Inverse target defines duplicate inverse for name '" + name + 3072 "'. The other inverse is " + existing.getObservable().getGetter(), 3073 method ); 3074 } 3075 else 3076 { 3077 final TypeMirror type = method.getReturnType(); 3078 3079 final Multiplicity multiplicity; 3080 TypeElement targetType = getInverseManyTypeTarget( method ); 3081 if ( null != targetType ) 3082 { 3083 multiplicity = Multiplicity.MANY; 3084 } 3085 else 3086 { 3087 if ( !( type instanceof DeclaredType ) || 3088 !AnnotationsUtil.hasAnnotationOfType( ( (DeclaredType) type ).asElement(), 3089 Constants.COMPONENT_CLASSNAME ) ) 3090 { 3091 throw new ProcessorException( "@Inverse target expected to return a type annotated with " + 3092 Constants.COMPONENT_CLASSNAME, method ); 3093 } 3094 targetType = (TypeElement) ( (DeclaredType) type ).asElement(); 3095 if ( AnnotationsUtil.hasNonnullAnnotation( method ) ) 3096 { 3097 multiplicity = Multiplicity.ONE; 3098 } 3099 else if ( AnnotationsUtil.hasNullableAnnotation( method ) ) 3100 { 3101 multiplicity = Multiplicity.ZERO_OR_ONE; 3102 } 3103 else 3104 { 3105 throw new ProcessorException( "@Inverse target expected to be annotated with either " + 3106 AnnotationsUtil.NULLABLE_CLASSNAME + " or " + 3107 AnnotationsUtil.NONNULL_CLASSNAME, method ); 3108 } 3109 } 3110 final String referenceName = getInverseReferenceNameParameter( descriptor, method ); 3111 3112 final InverseDescriptor inverse = findOrCreateInverseDescriptor( descriptor, name ); 3113 final String otherName = firstCharacterToLowerCase( targetType.getSimpleName().toString() ); 3114 inverse.setInverse( observable, referenceName, multiplicity, targetType, otherName ); 3115 verifyMultiplicityOfAssociatedReferenceMethod( descriptor, inverse ); 3116 } 3117 } 3118 3119 @Nonnull 3120 private InverseDescriptor findOrCreateInverseDescriptor( @Nonnull final ComponentDescriptor descriptor, 3121 @Nonnull final String name ) 3122 { 3123 return descriptor.getInverses().computeIfAbsent( name, n -> new InverseDescriptor( descriptor, name ) ); 3124 } 3125 3126 @Nonnull 3127 private String getInverseName( @Nonnull final AnnotationMirror annotation, 3128 @Nonnull final ExecutableElement method ) 3129 { 3130 final String declaredName = AnnotationsUtil.getAnnotationValueValue( annotation, "name" ); 3131 final String name; 3132 if ( Constants.SENTINEL.equals( declaredName ) ) 3133 { 3134 final String candidate = deriveName( method, GETTER_PATTERN, declaredName ); 3135 name = null == candidate ? method.getSimpleName().toString() : candidate; 3136 } 3137 else 3138 { 3139 name = declaredName; 3140 if ( !SourceVersion.isIdentifier( name ) ) 3141 { 3142 throw new ProcessorException( "@Inverse target specified an invalid name '" + name + "'. The " + 3143 "name must be a valid java identifier.", method ); 3144 } 3145 else if ( SourceVersion.isKeyword( name ) ) 3146 { 3147 throw new ProcessorException( "@Inverse target specified an invalid name '" + name + "'. The " + 3148 "name must not be a java keyword.", method ); 3149 } 3150 } 3151 return name; 3152 } 3153 3154 private void warnOnUnmanagedComponentReferences( @Nonnull final ComponentDescriptor descriptor, 3155 @Nonnull final List<VariableElement> fields ) 3156 { 3157 final TypeElement disposeNotifier = getTypeElement( Constants.DISPOSE_NOTIFIER_CLASSNAME ); 3158 3159 final Set<String> injectedTypes = new HashSet<>(); 3160 if ( descriptor.isStingEnabled() ) 3161 { 3162 for ( final ExecutableElement constructor : ElementsUtil.getConstructors( descriptor.getElement() ) ) 3163 { 3164 final List<? extends VariableElement> parameters = constructor.getParameters(); 3165 for ( final VariableElement parameter : parameters ) 3166 { 3167 final boolean isDisposeNotifier = isAssignable( parameter.asType(), disposeNotifier ); 3168 final boolean isTypeAnnotatedByComponentAnnotation = 3169 !isDisposeNotifier && isTypeAnnotatedByComponentAnnotation( parameter ); 3170 final boolean isTypeAnnotatedActAsComponent = 3171 !isDisposeNotifier && 3172 !isTypeAnnotatedByComponentAnnotation && 3173 isTypeAnnotatedByActAsComponentAnnotation( parameter ); 3174 if ( isDisposeNotifier || isTypeAnnotatedByComponentAnnotation || isTypeAnnotatedActAsComponent ) 3175 { 3176 injectedTypes.add( parameter.asType().toString() ); 3177 } 3178 } 3179 } 3180 } 3181 3182 for ( final VariableElement field : fields ) 3183 { 3184 if ( !field.getModifiers().contains( Modifier.STATIC ) && 3185 SuperficialValidation.validateElement( processingEnv, field ) ) 3186 { 3187 final boolean isDisposeNotifier = isAssignable( field.asType(), disposeNotifier ); 3188 final boolean isTypeAnnotatedByComponentAnnotation = 3189 !isDisposeNotifier && isTypeAnnotatedByComponentAnnotation( field ); 3190 final boolean isTypeAnnotatedActAsComponent = 3191 !isDisposeNotifier && 3192 !isTypeAnnotatedByComponentAnnotation && 3193 isTypeAnnotatedByActAsComponentAnnotation( field ); 3194 if ( isDisposeNotifier || isTypeAnnotatedByComponentAnnotation || isTypeAnnotatedActAsComponent ) 3195 { 3196 if ( !descriptor.isDependencyDefined( field ) && 3197 !descriptor.isCascadeDisposeDefined( field ) && 3198 ( isDisposeNotifier || isTypeAnnotatedActAsComponent || verifyReferencesToComponent( field ) ) && 3199 isUnmanagedComponentReferenceNotSuppressed( field ) && 3200 !injectedTypes.contains( field.asType().toString() ) ) 3201 { 3202 final String label = 3203 isDisposeNotifier ? "an implementation of DisposeNotifier" : 3204 isTypeAnnotatedByComponentAnnotation ? "an Arez component" : 3205 "annotated with @ActAsComponent"; 3206 final String message = 3207 "Field named '" + field.getSimpleName().toString() + "' has a type that is " + label + 3208 " but is not annotated with @" + Constants.CASCADE_DISPOSE_CLASSNAME + " or " + 3209 "@" + Constants.COMPONENT_DEPENDENCY_CLASSNAME + " and was not injected into the " + 3210 "constructor. This scenario can cause errors if the value is disposed. Please " + 3211 "annotate the field as appropriate or suppress the warning by annotating the field with " + 3212 "@SuppressWarnings( \"" + Constants.WARNING_UNMANAGED_COMPONENT_REFERENCE + "\" ) or " + 3213 "@SuppressArezWarnings( \"" + Constants.WARNING_UNMANAGED_COMPONENT_REFERENCE + "\" )"; 3214 processingEnv.getMessager().printMessage( WARNING, message, field ); 3215 } 3216 } 3217 } 3218 } 3219 3220 for ( final ObservableDescriptor observable : descriptor.getObservables().values() ) 3221 { 3222 if ( observable.isAbstract() ) 3223 { 3224 final ExecutableElement getter = observable.getGetter(); 3225 if ( SuperficialValidation.validateElement( processingEnv, getter ) ) 3226 { 3227 final TypeMirror returnType = getter.getReturnType(); 3228 final Element returnElement = processingEnv.getTypeUtils().asElement( returnType ); 3229 final boolean isDisposeNotifier = isAssignable( returnType, disposeNotifier ); 3230 final boolean isTypeAnnotatedByComponentAnnotation = 3231 !isDisposeNotifier && isElementAnnotatedBy( returnElement, Constants.COMPONENT_CLASSNAME ); 3232 final boolean isTypeAnnotatedActAsComponent = 3233 !isDisposeNotifier && 3234 !isTypeAnnotatedByComponentAnnotation && 3235 isElementAnnotatedBy( returnElement, Constants.ACT_AS_COMPONENT_CLASSNAME ); 3236 if ( isDisposeNotifier || isTypeAnnotatedByComponentAnnotation || isTypeAnnotatedActAsComponent ) 3237 { 3238 if ( !descriptor.isDependencyDefined( getter ) && 3239 !descriptor.isCascadeDisposeDefined( getter ) && 3240 ( isDisposeNotifier || 3241 isTypeAnnotatedActAsComponent || 3242 verifyReferencesToComponent( (TypeElement) returnElement ) ) && 3243 isUnmanagedComponentReferenceNotSuppressed( getter ) && 3244 ( observable.hasSetter() && isUnmanagedComponentReferenceNotSuppressed( observable.getSetter() ) ) ) 3245 { 3246 final String label = 3247 isDisposeNotifier ? "an implementation of DisposeNotifier" : 3248 isTypeAnnotatedByComponentAnnotation ? "an Arez component" : 3249 "annotated with @ActAsComponent"; 3250 final String message = 3251 "Method named '" + getter.getSimpleName().toString() + "' has a return type that is " + label + 3252 " but is not annotated with @" + Constants.CASCADE_DISPOSE_CLASSNAME + " or " + 3253 "@" + Constants.COMPONENT_DEPENDENCY_CLASSNAME + ". This scenario can cause errors. " + 3254 "Please annotate the method as appropriate or suppress the warning by annotating the method with " + 3255 "@SuppressWarnings( \"" + Constants.WARNING_UNMANAGED_COMPONENT_REFERENCE + "\" ) or " + 3256 "@SuppressArezWarnings( \"" + Constants.WARNING_UNMANAGED_COMPONENT_REFERENCE + "\" )"; 3257 processingEnv.getMessager().printMessage( WARNING, message, getter ); 3258 } 3259 } 3260 } 3261 } 3262 } 3263 } 3264 3265 private boolean verifyReferencesToComponent( @Nonnull final VariableElement field ) 3266 { 3267 return verifyReferencesToComponent( (TypeElement) processingEnv.getTypeUtils().asElement( field.asType() ) ); 3268 } 3269 3270 private boolean verifyReferencesToComponent( @Nonnull final TypeElement element ) 3271 { 3272 assert SuperficialValidation.validateElement( processingEnv, element ); 3273 3274 final String verifyReferencesToComponent = 3275 AnnotationsUtil.getEnumAnnotationParameter( element, 3276 Constants.COMPONENT_CLASSNAME, 3277 "verifyReferencesToComponent" ); 3278 return switch ( verifyReferencesToComponent ) 3279 { 3280 case "ENABLE" -> true; 3281 case "DISABLE" -> false; 3282 default -> isDisposableTrackableRequired( element ); 3283 }; 3284 } 3285 3286 private boolean isUnmanagedComponentReferenceNotSuppressed( @Nonnull final Element element ) 3287 { 3288 return !ElementsUtil.isWarningSuppressed( element, 3289 Constants.WARNING_UNMANAGED_COMPONENT_REFERENCE, 3290 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 3291 } 3292 3293 private boolean isTypeAnnotatedByActAsComponentAnnotation( @Nonnull final VariableElement field ) 3294 { 3295 final Element element = processingEnv.getTypeUtils().asElement( field.asType() ); 3296 return isElementAnnotatedBy( element, Constants.ACT_AS_COMPONENT_CLASSNAME ); 3297 } 3298 3299 private boolean isTypeAnnotatedByComponentAnnotation( @Nonnull final VariableElement field ) 3300 { 3301 final Element element = processingEnv.getTypeUtils().asElement( field.asType() ); 3302 return isElementAnnotatedBy( element, Constants.COMPONENT_CLASSNAME ); 3303 } 3304 3305 private boolean isElementAnnotatedBy( @Nullable final Element element, @Nonnull final String annotation ) 3306 { 3307 return null != element && 3308 SuperficialValidation.validateElement( processingEnv, element ) && 3309 AnnotationsUtil.hasAnnotationOfType( element, annotation ); 3310 } 3311 3312 private boolean isService( @Nonnull final TypeElement typeElement ) 3313 { 3314 final String service = 3315 AnnotationsUtil.getEnumAnnotationParameter( typeElement, Constants.COMPONENT_CLASSNAME, "service" ); 3316 return switch ( service ) 3317 { 3318 case "ENABLE" -> true; 3319 case "DISABLE" -> false; 3320 default -> AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_CONTRIBUTE_TO ) || 3321 AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_TYPED ) || 3322 AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_NAMED ) || 3323 AnnotationsUtil.hasAnnotationOfType( typeElement, Constants.STING_EAGER ); 3324 }; 3325 } 3326 3327 private boolean isComponentObservableRequired( @Nonnull final AnnotationMirror arezComponent, 3328 final boolean disposeOnDeactivate ) 3329 { 3330 final VariableElement variableElement = getAnnotationParameter( arezComponent, "observable" ); 3331 return switch ( variableElement.getSimpleName().toString() ) 3332 { 3333 case "ENABLE" -> true; 3334 case "DISABLE" -> false; 3335 default -> disposeOnDeactivate; 3336 }; 3337 } 3338 3339 private boolean isVerifyRequired( @Nonnull final AnnotationMirror arezComponent, 3340 @Nonnull final TypeElement typeElement ) 3341 { 3342 final VariableElement parameter = getAnnotationParameter( arezComponent, "verify" ); 3343 return switch ( parameter.getSimpleName().toString() ) 3344 { 3345 case "ENABLE" -> true; 3346 case "DISABLE" -> false; 3347 default -> ElementsUtil.getMethods( typeElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ). 3348 stream().anyMatch( this::hasReferenceAnnotations ); 3349 }; 3350 } 3351 3352 private boolean hasReferenceAnnotations( @Nonnull final Element method ) 3353 { 3354 return AnnotationsUtil.hasAnnotationOfType( method, Constants.REFERENCE_CLASSNAME ) || 3355 AnnotationsUtil.hasAnnotationOfType( method, Constants.REFERENCE_ID_CLASSNAME ) || 3356 AnnotationsUtil.hasAnnotationOfType( method, Constants.INVERSE_CLASSNAME ); 3357 } 3358 3359 private boolean isEqualsRequired( @Nonnull final AnnotationMirror arezComponent ) 3360 { 3361 final VariableElement injectParameter = getAnnotationParameter( arezComponent, "requireEquals" ); 3362 return "ENABLE".equals( injectParameter.getSimpleName().toString() ); 3363 } 3364 3365 @Nullable 3366 private String getDefaultPriority( @Nonnull final AnnotationMirror arezComponent ) 3367 { 3368 final AnnotationValue value = 3369 AnnotationsUtil.findAnnotationValueNoDefaults( arezComponent, "defaultPriority" ); 3370 return null == value ? null : ( (VariableElement) value.getValue() ).getSimpleName().toString(); 3371 } 3372 3373 private boolean isIdRequired( @Nonnull final AnnotationMirror arezComponent ) 3374 { 3375 final VariableElement injectParameter = getAnnotationParameter( arezComponent, "requireId" ); 3376 return !"DISABLE".equals( injectParameter.getSimpleName().toString() ); 3377 } 3378 3379 private boolean hasInjectAnnotation( @Nonnull final Element method ) 3380 { 3381 return AnnotationsUtil.hasAnnotationOfType( method, Constants.INJECT_CLASSNAME ); 3382 } 3383 3384 @Nonnull 3385 private <T> T getAnnotationParameter( @Nonnull final AnnotationMirror annotation, 3386 @Nonnull final String parameterName ) 3387 { 3388 return AnnotationsUtil.getAnnotationValueValue( annotation, parameterName ); 3389 } 3390 3391 @Nonnull 3392 private TypeElement getDisposableTypeElement() 3393 { 3394 return getTypeElement( Constants.DISPOSABLE_CLASSNAME ); 3395 } 3396 3397 private boolean isDisposableTrackableRequired( @Nonnull final TypeElement element ) 3398 { 3399 final String disposeNotifier = 3400 AnnotationsUtil.getEnumAnnotationParameter( element, Constants.COMPONENT_CLASSNAME, "disposeNotifier" ); 3401 return switch ( disposeNotifier ) 3402 { 3403 case "ENABLE" -> true; 3404 case "DISABLE" -> false; 3405 default -> null == AnnotationsUtil.findAnnotationByType( element, Constants.COMPONENT_CLASSNAME ) || 3406 !isService( element ); 3407 }; 3408 } 3409 3410 @Nonnull 3411 private TypeElement getTypeElement( @Nonnull final String classname ) 3412 { 3413 final TypeElement typeElement = findTypeElement( classname ); 3414 assert null != typeElement; 3415 return typeElement; 3416 } 3417 3418 @Nullable 3419 private TypeElement findTypeElement( @Nonnull final String classname ) 3420 { 3421 return processingEnv.getElementUtils().getTypeElement( classname ); 3422 } 3423 3424 private boolean isAssignable( @Nonnull final TypeMirror type, @Nonnull final TypeElement typeElement ) 3425 { 3426 return processingEnv.getTypeUtils().isAssignable( type, typeElement.asType() ); 3427 } 3428 3429 private boolean isMethodAProtectedOverride( @Nonnull final TypeElement typeElement, 3430 @Nonnull final ExecutableElement method ) 3431 { 3432 final ExecutableElement overriddenMethod = ElementsUtil.getOverriddenMethod( processingEnv, typeElement, method ); 3433 return null != overriddenMethod && overriddenMethod.getModifiers().contains( Modifier.PROTECTED ); 3434 } 3435 3436 private void mustBeStandardRefMethod( @Nonnull final ProcessingEnvironment processingEnv, 3437 @Nonnull final ComponentDescriptor descriptor, 3438 @Nonnull final ExecutableElement method, 3439 @Nonnull final String annotationClassname ) 3440 { 3441 mustBeRefMethod( descriptor, method, annotationClassname ); 3442 MemberChecks.mustNotHaveAnyParameters( annotationClassname, method ); 3443 shouldBeInternalRefMethod( processingEnv, descriptor, method, annotationClassname ); 3444 } 3445 3446 private void mustBeRefMethod( @Nonnull final ComponentDescriptor descriptor, 3447 @Nonnull final ExecutableElement method, 3448 @Nonnull final String annotationClassname ) 3449 { 3450 MemberChecks.mustBeAbstract( annotationClassname, method ); 3451 final TypeElement typeElement = descriptor.getElement(); 3452 MemberChecks.mustNotBePackageAccessInDifferentPackage( typeElement, 3453 Constants.COMPONENT_CLASSNAME, 3454 annotationClassname, 3455 method ); 3456 MemberChecks.mustReturnAValue( annotationClassname, method ); 3457 MemberChecks.mustNotThrowAnyExceptions( annotationClassname, method ); 3458 } 3459 3460 private void mustBeHookHook( @Nonnull final TypeElement targetType, 3461 @Nonnull final String annotationName, 3462 @Nonnull final ExecutableElement method ) 3463 throws ProcessorException 3464 { 3465 MemberChecks.mustNotBeAbstract( annotationName, method ); 3466 MemberChecks.mustBeSubclassCallable( targetType, Constants.COMPONENT_CLASSNAME, annotationName, method ); 3467 MemberChecks.mustNotReturnAnyValue( annotationName, method ); 3468 MemberChecks.mustNotThrowAnyExceptions( annotationName, method ); 3469 } 3470 3471 private void shouldBeInternalRefMethod( @Nonnull final ProcessingEnvironment processingEnv, 3472 @Nonnull final ComponentDescriptor descriptor, 3473 @Nonnull final ExecutableElement method, 3474 @Nonnull final String annotationClassname ) 3475 { 3476 if ( MemberChecks.doesMethodNotOverrideInterfaceMethod( processingEnv, descriptor.getElement(), method ) ) 3477 { 3478 MemberChecks.shouldNotBePublic( processingEnv, 3479 method, 3480 annotationClassname, 3481 Constants.WARNING_PUBLIC_REF_METHOD, 3482 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 3483 } 3484 } 3485 3486 private void shouldBeInternalLifecycleMethod( @Nonnull final ProcessingEnvironment processingEnv, 3487 @Nonnull final ComponentDescriptor descriptor, 3488 @Nonnull final ExecutableElement method, 3489 @Nonnull final String annotationClassname ) 3490 { 3491 if ( MemberChecks.doesMethodNotOverrideInterfaceMethod( processingEnv, descriptor.getElement(), method ) ) 3492 { 3493 MemberChecks.shouldNotBePublic( processingEnv, 3494 method, 3495 annotationClassname, 3496 Constants.WARNING_PUBLIC_LIFECYCLE_METHOD, 3497 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 3498 } 3499 } 3500 3501 private void shouldBeInternalHookMethod( @Nonnull final ProcessingEnvironment processingEnv, 3502 @Nonnull final ComponentDescriptor descriptor, 3503 @Nonnull final ExecutableElement method, 3504 @Nonnull final String annotationClassname ) 3505 { 3506 if ( MemberChecks.doesMethodNotOverrideInterfaceMethod( processingEnv, descriptor.getElement(), method ) ) 3507 { 3508 MemberChecks.shouldNotBePublic( processingEnv, 3509 method, 3510 annotationClassname, 3511 Constants.WARNING_PUBLIC_HOOK_METHOD, 3512 Constants.SUPPRESS_AREZ_WARNINGS_CLASSNAME ); 3513 } 3514 } 3515 3516 @Nullable 3517 private String deriveName( @Nonnull final ExecutableElement method, 3518 @Nonnull final Pattern pattern, 3519 @Nonnull final String name ) 3520 throws ProcessorException 3521 { 3522 if ( Constants.SENTINEL.equals( name ) ) 3523 { 3524 final String methodName = method.getSimpleName().toString(); 3525 final Matcher matcher = pattern.matcher( methodName ); 3526 if ( matcher.find() ) 3527 { 3528 final String candidate = matcher.group( 1 ); 3529 return firstCharacterToLowerCase( candidate ); 3530 } 3531 else 3532 { 3533 return null; 3534 } 3535 } 3536 else 3537 { 3538 return name; 3539 } 3540 } 3541 3542 @Nonnull 3543 private String firstCharacterToLowerCase( @Nonnull final String name ) 3544 { 3545 return Character.toLowerCase( name.charAt( 0 ) ) + name.substring( 1 ); 3546 } 3547}