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