001package sting.processor; 002 003import com.palantir.javapoet.CodeBlock; 004import java.io.IOException; 005import java.lang.annotation.Retention; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.Collections; 010import java.util.EnumMap; 011import java.util.HashMap; 012import java.util.HashSet; 013import java.util.LinkedHashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.Objects; 017import java.util.ServiceLoader; 018import java.util.Set; 019import java.util.Stack; 020import java.util.concurrent.atomic.AtomicBoolean; 021import java.util.function.Predicate; 022import java.util.stream.Collectors; 023import javax.annotation.Nonnull; 024import javax.annotation.Nullable; 025import javax.annotation.processing.ProcessingEnvironment; 026import javax.annotation.processing.RoundEnvironment; 027import javax.annotation.processing.SupportedAnnotationTypes; 028import javax.annotation.processing.SupportedOptions; 029import javax.annotation.processing.SupportedSourceVersion; 030import javax.lang.model.AnnotatedConstruct; 031import javax.lang.model.SourceVersion; 032import javax.lang.model.element.AnnotationMirror; 033import javax.lang.model.element.AnnotationValue; 034import javax.lang.model.element.Element; 035import javax.lang.model.element.ElementKind; 036import javax.lang.model.element.ExecutableElement; 037import javax.lang.model.element.Modifier; 038import javax.lang.model.element.TypeElement; 039import javax.lang.model.element.VariableElement; 040import javax.lang.model.type.DeclaredType; 041import javax.lang.model.type.ExecutableType; 042import javax.lang.model.type.TypeKind; 043import javax.lang.model.type.TypeMirror; 044import javax.lang.model.util.Elements; 045import javax.tools.Diagnostic; 046import org.realityforge.proton.AbstractStandardProcessor; 047import org.realityforge.proton.AnnotationsUtil; 048import org.realityforge.proton.DeferredElementSet; 049import org.realityforge.proton.ElementsUtil; 050import org.realityforge.proton.GeneratorUtil; 051import org.realityforge.proton.JsonUtil; 052import org.realityforge.proton.MemberChecks; 053import org.realityforge.proton.ProcessorException; 054import org.realityforge.proton.ResourceUtil; 055import org.realityforge.proton.StopWatch; 056import org.realityforge.proton.SuperficialValidation; 057import org.realityforge.proton.TypesUtil; 058 059/** 060 * The annotation processor that analyzes Sting annotated source code and generates an injector and supporting artifacts. 061 */ 062@SuppressWarnings( "DuplicatedCode" ) 063@SupportedAnnotationTypes( { Constants.INJECTOR_CLASSNAME, 064 Constants.INJECTOR_FRAGMENT_CLASSNAME, 065 Constants.FACTORY_CLASSNAME, 066 Constants.INJECTABLE_CLASSNAME, 067 Constants.FRAGMENT_CLASSNAME, 068 Constants.EAGER_CLASSNAME, 069 Constants.TYPED_CLASSNAME, 070 Constants.NAMED_CLASSNAME, 071 Constants.STING_PROVIDER_CLASSNAME, 072 Constants.ACT_AS_STING_CONSUMER_CLASSNAME, 073 Constants.ACT_AS_STING_PROVIDER_CLASSNAME, 074 Constants.ACT_AS_STING_COMPONENT_CLASSNAME } ) 075@SupportedSourceVersion( SourceVersion.RELEASE_17 ) 076@SupportedOptions( { "sting.defer.unresolved", 077 "sting.defer.errors", 078 "sting.debug", 079 "sting.format_generated_source", 080 "sting.profile", 081 "sting.emit_json_descriptors", 082 "sting.emit_dot_reports", 083 "sting.verbose_out_of_round.errors", 084 "sting.warnings_as_errors" } ) 085public final class StingProcessor 086 extends AbstractStandardProcessor 087{ 088 private enum AnnotationUsageKind 089 { 090 PROCESSED, 091 SILENT_INTEGRATION_EXCEPTION, 092 INVALID 093 } 094 095 /** 096 * Extension for json descriptors. 097 */ 098 static final String JSON_SUFFIX = ".sting.json"; 099 /** 100 * Extension for graphviz .dot reports. 101 */ 102 static final String DOT_SUFFIX = ".gv"; 103 // Binary descriptor persistence removed 104 /** 105 * Extension for the computed graph descriptor. 106 */ 107 static final String GRAPH_SUFFIX = "__ObjectGraph" + JSON_SUFFIX; 108 /** 109 * A local cache of bindings that is cleared on error or when processing is complete. 110 * This will probably be loaded from json cache files in the future but now we require 111 * in memory processing. 112 */ 113 @Nonnull 114 private final Registry _registry = new Registry(); 115 @Nonnull 116 private final DeferredElementSet _deferredInjectableTypes = new DeferredElementSet(); 117 @Nonnull 118 private final DeferredElementSet _deferredFragmentTypes = new DeferredElementSet(); 119 @Nonnull 120 private final DeferredElementSet _deferredFactoryTypes = new DeferredElementSet(); 121 @Nonnull 122 private final DeferredElementSet _deferredInjectorTypes = new DeferredElementSet(); 123 @Nonnull 124 private final DeferredElementSet _deferredInjectorFragmentTypes = new DeferredElementSet(); 125 @Nonnull 126 private final StopWatch _analyzeInjectableStopWatch = new StopWatch( "Analyze Injectable" ); 127 @Nonnull 128 private final StopWatch _analyzeFragmentStopWatch = new StopWatch( "Analyze Fragment" ); 129 @Nonnull 130 private final StopWatch _analyzeFactoryStopWatch = new StopWatch( "Analyze Factory" ); 131 @Nonnull 132 private final StopWatch _analyzeInjectorFragmentStopWatch = new StopWatch( "Analyze Injector Fragment" ); 133 @Nonnull 134 private final StopWatch _analyzeInjectorStopWatch = new StopWatch( "Analyze Injector" ); 135 private final StopWatch _generateInjectableStubStopWatch = new StopWatch( "Generate Injectable Stub" ); 136 @Nonnull 137 private final StopWatch _generateFragmentStubStopWatch = new StopWatch( "Generate Fragment Stub" ); 138 @Nonnull 139 private final StopWatch _generateFactoryImplStopWatch = new StopWatch( "Generate Factory Impl" ); 140 @Nonnull 141 private final StopWatch _generateInjectorImplStopWatch = new StopWatch( "Generate Injector Impl" ); 142 @Nonnull 143 private final StopWatch _isInjectorResolvedStopWatch = new StopWatch( "Is Injector Resolved" ); 144 @Nonnull 145 private final StopWatch _buildAndEmitObjectGraphStopWatch = new StopWatch( "Build and Emit Object Graph" ); 146 /** 147 * Flag controlling whether json descriptors are emitted. 148 * Json descriptors are primarily used during debugging and probably should not be enabled in production code. 149 */ 150 private boolean _emitJsonDescriptors; 151 /** 152 * Flag controlling whether .dot formatted report is emitted. 153 * The .dot report is typically used by end users who want to explore the graph. 154 */ 155 private boolean _emitDotReports; 156 /** 157 * Cache of derived fragment descriptors during a processing session. 158 */ 159 @Nonnull 160 private final Map<String, FragmentDescriptor> _derivedFragmentCache = new HashMap<>(); 161 /** 162 * Cache of derived injectable descriptors during a processing session. 163 */ 164 @Nonnull 165 private final Map<String, InjectableDescriptor> _derivedInjectableCache = new HashMap<>(); 166 /** 167 * Track fragments that are currently being resolved to break include cycles. 168 */ 169 @Nonnull 170 private final Set<String> _resolvingFragmentTypes = new HashSet<>(); 171 @Nullable 172 private final List<InterceptorCodeGenerator> _suppliedInterceptorCodeGenerators; 173 @Nonnull 174 private List<InterceptorCodeGenerator> _interceptorCodeGenerators = Collections.emptyList(); 175 176 /** 177 * Create the annotation processor. 178 */ 179 public StingProcessor() 180 { 181 this( null ); 182 } 183 184 StingProcessor( @Nullable final List<InterceptorCodeGenerator> interceptorCodeGenerators ) 185 { 186 _suppliedInterceptorCodeGenerators = null == interceptorCodeGenerators ? 187 null : 188 List.copyOf( interceptorCodeGenerators ); 189 } 190 191 @Nonnull 192 @Override 193 protected String getIssueTrackerURL() 194 { 195 return "https://github.com/sting-ioc/sting/issues"; 196 } 197 198 @Nonnull 199 @Override 200 protected String getOptionPrefix() 201 { 202 return "sting"; 203 } 204 205 @Override 206 public synchronized void init( @Nonnull final ProcessingEnvironment processingEnv ) 207 { 208 super.init( processingEnv ); 209 _emitJsonDescriptors = readBooleanOption( "emit_json_descriptors", false ); 210 _emitDotReports = readBooleanOption( "emit_dot_reports", false ); 211 _interceptorCodeGenerators = null != _suppliedInterceptorCodeGenerators ? 212 _suppliedInterceptorCodeGenerators : 213 ServiceLoader 214 .load( InterceptorCodeGenerator.class, getClass().getClassLoader() ) 215 .stream() 216 .map( ServiceLoader.Provider::get ) 217 .toList(); 218 } 219 220 @Override 221 protected void collectStopWatches( @Nonnull final Collection<StopWatch> stopWatches ) 222 { 223 stopWatches.add( _analyzeInjectableStopWatch ); 224 stopWatches.add( _analyzeFragmentStopWatch ); 225 stopWatches.add( _analyzeFactoryStopWatch ); 226 stopWatches.add( _analyzeInjectorFragmentStopWatch ); 227 stopWatches.add( _analyzeInjectorStopWatch ); 228 stopWatches.add( _generateInjectableStubStopWatch ); 229 stopWatches.add( _generateFragmentStubStopWatch ); 230 stopWatches.add( _generateFactoryImplStopWatch ); 231 stopWatches.add( _generateInjectorImplStopWatch ); 232 stopWatches.add( _isInjectorResolvedStopWatch ); 233 stopWatches.add( _buildAndEmitObjectGraphStopWatch ); 234 } 235 236 @Override 237 public boolean process( @Nonnull final Set<? extends TypeElement> annotations, @Nonnull final RoundEnvironment env ) 238 { 239 debugAnnotationProcessingRootElements( env ); 240 collectRootTypeNames( env ); 241 242 processTypeElements( annotations, 243 env, 244 Constants.FACTORY_CLASSNAME, 245 _deferredFactoryTypes, 246 "Analyze Factory", 247 this::processFactory, 248 _analyzeFactoryStopWatch ); 249 250 processTypeElements( annotations, 251 env, 252 Constants.INJECTABLE_CLASSNAME, 253 _deferredInjectableTypes, 254 "Analyze Injectable", 255 this::processInjectable, 256 _analyzeInjectableStopWatch ); 257 258 processTypeElements( annotations, 259 env, 260 Constants.FRAGMENT_CLASSNAME, 261 _deferredFragmentTypes, 262 "Analyze Fragment", 263 this::processFragment, 264 _analyzeFragmentStopWatch ); 265 266 annotations 267 .stream() 268 .filter( a -> a.getQualifiedName().toString().equals( Constants.NAMED_CLASSNAME ) ) 269 .findAny() 270 .ifPresent( a -> verifyNamedElements( env, env.getElementsAnnotatedWith( a ) ) ); 271 272 annotations 273 .stream() 274 .filter( a -> a.getQualifiedName().toString().equals( Constants.TYPED_CLASSNAME ) ) 275 .findAny() 276 .ifPresent( a -> verifyTypedElements( env, env.getElementsAnnotatedWith( a ) ) ); 277 278 annotations.stream() 279 .filter( a -> a.getQualifiedName().toString().equals( Constants.EAGER_CLASSNAME ) ) 280 .findAny() 281 .ifPresent( a -> verifyEagerElements( env, env.getElementsAnnotatedWith( a ) ) ); 282 283 processTypeElements( annotations, 284 env, 285 Constants.INJECTOR_FRAGMENT_CLASSNAME, 286 _deferredInjectorFragmentTypes, 287 "Analyze Injector Fragment", 288 this::processInjectorFragment, 289 _analyzeInjectorFragmentStopWatch ); 290 291 processTypeElements( annotations, 292 env, 293 Constants.INJECTOR_CLASSNAME, 294 _deferredInjectorTypes, 295 "Analyze Injector", 296 this::processInjector, 297 _analyzeInjectorStopWatch ); 298 299 processResolvedFactories( env ); 300 processResolvedInjectables( env ); 301 processResolvedFragments( env ); 302 processResolvedInjectors( env ); 303 304 errorIfProcessingOverAndInvalidTypesDetected( env ); 305 errorIfProcessingOverAndUnprocessedInjectorDetected( env ); 306 if ( env.processingOver() || env.errorRaised() ) 307 { 308 _registry.clear(); 309 _derivedFragmentCache.clear(); 310 _derivedInjectableCache.clear(); 311 } 312 clearRootTypeNamesIfProcessingOver( env ); 313 reportProfilerTimings(); 314 return true; 315 } 316 317 private void errorIfProcessingOverAndUnprocessedInjectorDetected( @Nonnull final RoundEnvironment env ) 318 { 319 if ( env.processingOver() && !env.errorRaised() ) 320 { 321 final List<InjectorDescriptor> injectors = _registry.getInjectors(); 322 if ( !injectors.isEmpty() ) 323 { 324 processingEnv 325 .getMessager() 326 .printMessage( Diagnostic.Kind.ERROR, 327 getClass().getSimpleName() + " failed to process " + injectors.size() + " injectors " + 328 "as not all of their dependencies could be resolved. Ensure that included types are " + 329 "present on the classpath and are valid Sting components. If the problem is not " + 330 "obvious, consider passing the annotation option sting.debug=true" ); 331 for ( final InjectorDescriptor injector : injectors ) 332 { 333 processingEnv 334 .getMessager() 335 .printMessage( Diagnostic.Kind.ERROR, 336 "Failed to process the " + injector.getElement().getQualifiedName() + " injector." ); 337 } 338 } 339 } 340 } 341 342 private void processResolvedInjectables( @Nonnull final RoundEnvironment env ) 343 { 344 for ( final InjectableDescriptor injectable : new ArrayList<>( _registry.getInjectables() ) ) 345 { 346 if ( !injectable.isJavaStubGenerated() ) 347 { 348 performAction( env, "Generate Injectable Stub", e -> { 349 injectable.markJavaStubAsGenerated(); 350 emitInjectableJsonDescriptor( injectable ); 351 emitInjectableStub( injectable ); 352 }, injectable.getElement(), _generateInjectableStubStopWatch ); 353 } 354 } 355 } 356 357 private void processResolvedFactories( @Nonnull final RoundEnvironment env ) 358 { 359 for ( final FactoryDescriptor factory : new ArrayList<>( _registry.getFactories() ) ) 360 { 361 if ( !factory.isGenerated() ) 362 { 363 performAction( env, "Generate Factory Impl", e -> { 364 factory.markGenerated(); 365 emitFactoryImpl( factory ); 366 }, factory.getElement(), _generateFactoryImplStopWatch ); 367 } 368 } 369 } 370 371 private void emitFactoryImpl( @Nonnull final FactoryDescriptor factory ) 372 throws IOException 373 { 374 debug( () -> "Emitting factory implementation for the factory " + factory.getElement().getQualifiedName() ); 375 final String packageName = GeneratorUtil.getQualifiedPackageName( factory.getElement() ); 376 emitTypeSpec( packageName, FactoryGenerator.buildType( processingEnv, factory ) ); 377 } 378 379 private void emitInjectableStub( @Nonnull final InjectableDescriptor injectable ) 380 throws IOException 381 { 382 debug( () -> "Emitting injectable stub for the injectable " + injectable.getElement().getQualifiedName() ); 383 final String packageName = GeneratorUtil.getQualifiedPackageName( injectable.getElement() ); 384 emitTypeSpec( packageName, InjectableGenerator.buildType( processingEnv, injectable ) ); 385 } 386 387 private void processResolvedFragments( @Nonnull final RoundEnvironment env ) 388 { 389 final List<FragmentDescriptor> deferred = new ArrayList<>(); 390 final List<FragmentDescriptor> current = new ArrayList<>( _registry.getFragments() ); 391 final AtomicBoolean resolvedType = new AtomicBoolean(); 392 393 while ( !current.isEmpty() ) 394 { 395 for ( final FragmentDescriptor fragment : current ) 396 { 397 if ( !fragment.isJavaStubGenerated() && !fragment.containsError() ) 398 { 399 performAction( env, "Generate Fragment Stub", e -> { 400 final ResolveType resolveType = isFragmentResolved( env, fragment ); 401 if ( ResolveType.RESOLVED == resolveType ) 402 { 403 fragment.markJavaStubAsGenerated(); 404 emitFragmentJsonDescriptor( fragment ); 405 emitFragmentStub( fragment ); 406 } 407 else if ( ResolveType.MAYBE_UNRESOLVED == resolveType ) 408 { 409 debug( () -> "The fragment " + fragment.getElement().getQualifiedName() + 410 " has resolved java types but unresolved descriptors. Deferring processing " + 411 "until later in the round" ); 412 deferred.add( fragment ); 413 } 414 else 415 { 416 debug( () -> "Defer generation for the fragment " + fragment.getElement().getQualifiedName() + 417 " as it is not yet resolved" ); 418 } 419 }, fragment.getElement(), _generateFragmentStubStopWatch ); 420 } 421 } 422 current.clear(); 423 if ( resolvedType.get() ) 424 { 425 current.addAll( deferred ); 426 deferred.clear(); 427 } 428 else 429 { 430 break; 431 } 432 } 433 } 434 435 @Nonnull 436 private ResolveType isFragmentReady( @Nonnull final RoundEnvironment env, 437 @Nonnull final FragmentDescriptor fragment ) 438 { 439 if ( fragment.containsError() ) 440 { 441 return ResolveType.UNRESOLVED; 442 } 443 else 444 { 445 return isFragmentResolved( env, fragment ); 446 } 447 } 448 449 private void emitFragmentStub( @Nonnull final FragmentDescriptor fragment ) 450 throws IOException 451 { 452 debug( () -> "Emitting fragment stub for the fragment " + fragment.getElement().getQualifiedName() ); 453 final String packageName = GeneratorUtil.getQualifiedPackageName( fragment.getElement() ); 454 emitTypeSpec( packageName, FragmentGenerator.buildType( processingEnv, fragment ) ); 455 } 456 457 private void processResolvedInjectors( @Nonnull final RoundEnvironment env ) 458 { 459 final boolean profileEnabled = isProfileEnabled(); 460 461 final List<InjectorDescriptor> deferred = new ArrayList<>(); 462 final List<InjectorDescriptor> current = new ArrayList<>( _registry.getInjectors() ); 463 final AtomicBoolean resolvedType = new AtomicBoolean(); 464 465 while ( !current.isEmpty() ) 466 { 467 for ( final InjectorDescriptor injector : current ) 468 { 469 if ( !injector.containsError() ) 470 { 471 performAction( env, "Generate Injector Impl", e -> { 472 if ( profileEnabled ) 473 { 474 _isInjectorResolvedStopWatch.start(); 475 } 476 final ResolveType resolveType = isInjectorResolved( env, injector ); 477 if ( profileEnabled ) 478 { 479 _isInjectorResolvedStopWatch.stop(); 480 } 481 if ( ResolveType.RESOLVED == resolveType ) 482 { 483 _registry.deregisterInjector( injector ); 484 if ( profileEnabled ) 485 { 486 _buildAndEmitObjectGraphStopWatch.start(); 487 } 488 try 489 { 490 buildAndEmitObjectGraph( injector ); 491 } 492 finally 493 { 494 if ( profileEnabled ) 495 { 496 _buildAndEmitObjectGraphStopWatch.stop(); 497 } 498 } 499 resolvedType.set( true ); 500 } 501 else if ( ResolveType.MAYBE_UNRESOLVED == resolveType ) 502 { 503 debug( () -> "The injector " + injector.getElement().getQualifiedName() + 504 " has resolved java types but unresolved descriptors. Deferring processing " + 505 "until later in the round" ); 506 deferred.add( injector ); 507 } 508 else 509 { 510 debug( () -> "Defer generation for the injector " + injector.getElement().getQualifiedName() + 511 " as it is not yet resolved" ); 512 } 513 }, injector.getElement(), _generateInjectorImplStopWatch ); 514 } 515 } 516 current.clear(); 517 if ( resolvedType.get() ) 518 { 519 resolvedType.set( false ); 520 current.addAll( deferred ); 521 deferred.clear(); 522 } 523 else 524 { 525 break; 526 } 527 } 528 } 529 530 private void buildAndEmitObjectGraph( @Nonnull final InjectorDescriptor injector ) 531 throws Exception 532 { 533 debug( () -> "Preparing to build component graph for the injector " + injector.getElement().getQualifiedName() ); 534 final ComponentGraph graph = new ComponentGraph( this, injector, _registry ); 535 registerIncludesComponents( graph ); 536 537 registerInputs( graph ); 538 539 debug( () -> "Building component graph for the injector " + injector.getElement().getQualifiedName() ); 540 541 buildObjectGraphNodes( graph ); 542 543 if ( graph.getNodes().isEmpty() && graph.getRootNode().getDependsOn().isEmpty() ) 544 { 545 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " target " + 546 "produced an empty object graph. This means that there are no eager nodes " + 547 "in the includes and there are no dependencies or only unsatisfied optional " + 548 "dependencies defined by the injector", 549 graph.getInjector().getElement() ); 550 } 551 552 debug( () -> "Propagating eager-ness for the injector " + injector.getElement().getQualifiedName() ); 553 554 propagateEagerFlagUpstream( graph ); 555 556 debug( () -> "Verifying no circular dependencies for the injector " + injector.getElement().getQualifiedName() ); 557 558 CircularDependencyChecker.verifyNoCircularDependencyLoops( graph ); 559 560 final Set<Binding> actualBindings = 561 graph.getNodes() 562 .stream() 563 .filter( Node::isBinding ) 564 .map( Node::getBinding ) 565 .collect( Collectors.toSet() ); 566 567 for ( final Map.Entry<IncludeDescriptor, Set<Binding>> entry : graph.getIncludeRootToBindingMap().entrySet() ) 568 { 569 if ( entry.getValue().stream().noneMatch( actualBindings::contains ) ) 570 { 571 final IncludeDescriptor includeRoot = entry.getKey(); 572 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " must not " + 573 "include type " + includeRoot.includedType() + 574 " when the type is not used within the graph", 575 graph.getInjector().getElement() ); 576 } 577 } 578 579 emitObjectGraphJsonDescriptor( graph ); 580 emitInterceptorProxies( graph ); 581 582 final String packageName = GeneratorUtil.getQualifiedPackageName( graph.getInjector().getElement() ); 583 584 debug( () -> "Emitting injector implementation for the injector " + 585 graph.getInjector().getElement().getQualifiedName() ); 586 emitTypeSpec( packageName, InjectorGenerator.buildType( processingEnv, graph ) ); 587 588 if ( injector.isInjectable() ) 589 { 590 debug( () -> "Emitting injector provider for the injector " + 591 graph.getInjector().getElement().getQualifiedName() ); 592 emitTypeSpec( packageName, InjectorProviderGenerator.buildType( processingEnv, graph ) ); 593 } 594 emitDotReport( graph ); 595 } 596 597 private void emitInterceptorProxies( @Nonnull final ComponentGraph graph ) 598 throws IOException 599 { 600 for ( final Node node : graph.getNodes() ) 601 { 602 if ( node.isProxy() ) 603 { 604 final InterceptorProxyDescriptor proxy = node.getProxy(); 605 if ( !proxy.isGenerated() ) 606 { 607 proxy.markGenerated(); 608 emitTypeSpec( proxy.getClassName().packageName(), 609 InterceptorProxyGenerator.buildType( processingEnv, proxy, _interceptorCodeGenerators ) ); 610 } 611 } 612 } 613 } 614 615 private void emitDotReport( @Nonnull final ComponentGraph graph ) 616 throws IOException 617 { 618 if ( _emitDotReports ) 619 { 620 final TypeElement element = graph.getInjector().getElement(); 621 final String filename = toFilename( element ) + DOT_SUFFIX; 622 debug( () -> "Emitting .dot report for the injector " + graph.getInjector().getElement().getQualifiedName() ); 623 624 final String report = InjectorDotReportGenerator.buildDotReport( processingEnv, graph ); 625 ResourceUtil.writeResource( processingEnv, filename, report, element ); 626 } 627 } 628 629 private void registerInputs( @Nonnull final ComponentGraph graph ) 630 { 631 for ( final InputDescriptor input : graph.getInjector().getInputs() ) 632 { 633 graph.registerInput( input ); 634 } 635 } 636 637 private void propagateEagerFlagUpstream( @Nonnull final ComponentGraph graph ) 638 { 639 // Propagate Eager flag to all dependencies of eager nodes breaking the propagation at Supplier nodes 640 // They may not be configured as eager but they are effectively eager given that they will be created 641 // at startup, they may as well be marked as eager objects as that results in smaller code-size. 642 graph.getNodes().stream().filter( Node::isDeclaredEager ).forEach( Node::markNodeAndUpstreamAsEager ); 643 } 644 645 private void registerIncludesComponents( @Nonnull final ComponentGraph graph ) 646 { 647 registerIncludes( graph, null, graph.getInjector().getIncludes() ); 648 } 649 650 private void registerIncludes( @Nonnull final ComponentGraph graph, 651 @Nullable final IncludeDescriptor includeRoot, 652 @Nonnull final Collection<IncludeDescriptor> includes ) 653 { 654 for ( final IncludeDescriptor include : includes ) 655 { 656 final String classname = include.actualTypeName(); 657 if ( isDebugEnabled() && include.isProvider() ) 658 { 659 debug( () -> "Registering include " + classname + " via provider " + include.includedType() + 660 " into graph " + graph.getInjector().getElement().getQualifiedName() ); 661 } 662 else 663 { 664 debug( () -> "Registering include " + classname + " into graph " + 665 graph.getInjector().getElement().getQualifiedName() ); 666 } 667 final IncludeDescriptor root = null == includeRoot ? include : includeRoot; 668 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 669 assert null != element; 670 final String qualifiedName = element.getQualifiedName().toString(); 671 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 672 { 673 final FragmentDescriptor fragment = _registry.getFragmentByClassName( qualifiedName ); 674 registerIncludes( graph, root, fragment.getIncludes() ); 675 graph.registerFragment( root, fragment ); 676 } 677 else 678 { 679 assert AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ); 680 final InjectableDescriptor injectable = _registry.getInjectableByClassName( qualifiedName ); 681 graph.registerInjectable( root, injectable ); 682 } 683 } 684 } 685 686 private void buildObjectGraphNodes( @Nonnull final ComponentGraph graph ) 687 { 688 final Set<Node> completed = new HashSet<>(); 689 final Stack<WorkEntry> workList = new Stack<>(); 690 // At this stage the "rootNode" contains dependencies for all the output methods declared on the injector 691 // and all the eager services declared in includes have already been added to the nodes list. 692 // 693 // We start at the rootNode and expand all of the dependencies. And then we take any of the eager dependencies 694 // that have yet to be added to be processed and add them to to worklist and expand all dependencies until there 695 // are no eager nodes left to process 696 final Node rootNode = graph.getRootNode(); 697 rootNode.setDepth( 0 ); 698 addDependsOnToWorkList( workList, rootNode, null ); 699 processWorkList( graph, completed, workList ); 700 List<Node> eagerNodes = graph.getRawNodeCollection().stream().filter( Node::isDeclaredEager ).toList(); 701 while ( !eagerNodes.isEmpty() ) 702 { 703 for ( final Node node : eagerNodes ) 704 { 705 if ( node.isDepthNotSet() ) 706 { 707 node.setDepth( 0 ); 708 addDependsOnToWorkList( workList, node, null ); 709 processWorkList( graph, completed, workList ); 710 } 711 } 712 eagerNodes = 713 graph.getRawNodeCollection().stream().filter( n -> n.isDeclaredEager() && n.isDepthNotSet() ).toList(); 714 } 715 graph.complete(); 716 } 717 718 private void processWorkList( @Nonnull final ComponentGraph graph, 719 @Nonnull final Set<Node> completed, 720 @Nonnull final Stack<WorkEntry> workList ) 721 { 722 while ( !workList.isEmpty() ) 723 { 724 final WorkEntry workEntry = workList.pop(); 725 final Edge edge = workEntry.getEntry().edge(); 726 assert null != edge; 727 if ( edge.isSatisfied() ) 728 { 729 for ( final Node node : edge.getSatisfiedBy() ) 730 { 731 if ( !completed.contains( node ) ) 732 { 733 completed.add( node ); 734 addDependsOnToWorkList( workList, node, workEntry ); 735 } 736 } 737 continue; 738 } 739 final ServiceRequest serviceRequest = edge.getServiceRequest(); 740 final Coordinate coordinate = serviceRequest.getService().getCoordinate(); 741 final List<Binding> bindings = new ArrayList<>( graph.findAllBindingsByCoordinate( coordinate ) ); 742 743 if ( bindings.isEmpty() && coordinate.qualifier().isEmpty() ) 744 { 745 final String typename = coordinate.type().toString(); 746 final InjectableDescriptor injectable = _registry.findInjectableByClassName( typename ); 747 if ( null != injectable && injectable.isAutoDiscoverable() ) 748 { 749 bindings.add( injectable.getBinding() ); 750 } 751 if ( bindings.isEmpty() ) 752 { 753 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( typename ); 754 if ( null != typeElement ) 755 { 756 final InjectableDescriptor candidate = deriveInjectableDescriptor( typeElement ); 757 if ( null != candidate && candidate.isAutoDiscoverable() ) 758 { 759 assert coordinate.equals( candidate.getBinding().getPublishedServices().get( 0 ).getCoordinate() ); 760 _registry.registerInjectable( candidate ); 761 bindings.add( candidate.getBinding() ); 762 } 763 if ( bindings.isEmpty() ) 764 { 765 bindings.addAll( autoDiscoverProviderBindings( graph, workEntry, typeElement ) ); 766 } 767 } 768 } 769 } 770 771 final List<Binding> nullableProviders = bindings.stream() 772 .filter( b -> b.getPublishedServices().stream().anyMatch( ServiceSpec::isOptional ) ) 773 .collect( Collectors.toList() ); 774 if ( !serviceRequest.canConsumeOptionalBindings() && !nullableProviders.isEmpty() ) 775 { 776 final String message = 777 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 778 "contain an optional provider method or optional injector input and " + 779 "a non-optional service request for the coordinate " + coordinate + "\n" + 780 "Dependency Path:\n" + workEntry.describePathFromRoot() + "\n" + 781 "Binding" + ( nullableProviders.size() > 1 ? "s" : "" ) + ":\n" + 782 bindingsToString( nullableProviders ) ); 783 throw new ProcessorException( message, serviceRequest.getElement() ); 784 } 785 if ( bindings.isEmpty() ) 786 { 787 if ( serviceRequest.canBeAbsent() ) 788 { 789 edge.setSatisfiedBy( Collections.emptyList() ); 790 } 791 else 792 { 793 final String message = 794 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 795 "contain a non-optional dependency " + coordinate + 796 " that can not be satisfied.\n" + 797 "Dependency Path:\n" + workEntry.describePathFromRoot() ); 798 throw new ProcessorException( message, serviceRequest.getElement() ); 799 } 800 } 801 else 802 { 803 final ServiceRequest.Kind kind = serviceRequest.getKind(); 804 if ( 1 == bindings.size() || kind.isCollection() ) 805 { 806 final List<Node> nodes = new ArrayList<>(); 807 for ( final Binding binding : bindings ) 808 { 809 final Node node = graph.findOrCreateProviderNode( binding, coordinate ); 810 nodes.add( node ); 811 } 812 edge.setSatisfiedBy( nodes ); 813 for ( final Node node : nodes ) 814 { 815 if ( node.isProxy() ) 816 { 817 graph.attachProxyDependencies( node ); 818 } 819 if ( !completed.contains( node ) ) 820 { 821 completed.add( node ); 822 addDependsOnToWorkList( workList, node, workEntry ); 823 } 824 } 825 } 826 else 827 { 828 //noinspection ConstantConditions 829 assert bindings.size() > 1 && !kind.isCollection(); 830 final String message = 831 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 832 "contain a non-collection dependency " + coordinate + 833 " that can be satisfied by multiple nodes.\n" + 834 "Dependency Path:\n" + workEntry.describePathFromRoot() + "\n" + 835 "Candidate Nodes:\n" + bindingsToString( bindings ) ); 836 throw new ProcessorException( message, serviceRequest.getElement() ); 837 } 838 } 839 } 840 } 841 842 @Nonnull 843 private String bindingsToString( @Nonnull final List<Binding> bindings ) 844 { 845 return bindings 846 .stream() 847 .map( b -> " " + b.getTypeLabel() + " " + b.describe() ) 848 .collect( Collectors.joining( "\n" ) ); 849 } 850 851 private void addDependsOnToWorkList( @Nonnull final Stack<WorkEntry> workList, 852 @Nonnull final Node node, 853 @Nullable final WorkEntry parent ) 854 { 855 for ( final Edge e : node.getDependsOn() ) 856 { 857 final Stack<PathEntry> stack = new Stack<>(); 858 if ( null != parent ) 859 { 860 stack.addAll( parent.getStack() ); 861 } 862 final PathEntry entry = new PathEntry( node, e ); 863 stack.add( entry ); 864 workList.add( new WorkEntry( entry, stack ) ); 865 } 866 } 867 868 private void emitObjectGraphJsonDescriptor( @Nonnull final ComponentGraph graph ) 869 throws IOException 870 { 871 if ( _emitJsonDescriptors ) 872 { 873 final TypeElement element = graph.getInjector().getElement(); 874 final String filename = toFilename( element ) + GRAPH_SUFFIX; 875 debug( () -> "Emitting json descriptor for the injector " + graph.getInjector().getElement().getQualifiedName() ); 876 JsonUtil.writeJsonResource( processingEnv, element, filename, graph::write ); 877 } 878 } 879 880 @Nonnull 881 private ResolveType isFragmentResolved( @Nonnull final RoundEnvironment env, 882 @Nonnull final FragmentDescriptor fragment ) 883 { 884 if ( fragment.isResolved() ) 885 { 886 return ResolveType.RESOLVED; 887 } 888 else 889 { 890 final String fragmentName = fragment.getElement().getQualifiedName().toString(); 891 if ( _resolvingFragmentTypes.contains( fragmentName ) ) 892 { 893 return ResolveType.RESOLVED; 894 } 895 _resolvingFragmentTypes.add( fragmentName ); 896 try 897 { 898 final ResolveType resolveType = isResolved( env, fragment, fragment.getElement(), fragment.getIncludes() ); 899 if ( resolveType == ResolveType.RESOLVED ) 900 { 901 fragment.markAsResolved(); 902 // Check for redundant explicit @Injectable includes that are also transitively included via fragments 903 maybeWarnOnRedundantDirectInjectableInclude( fragment.getElement(), 904 Constants.FRAGMENT_CLASSNAME, 905 fragment.getIncludes() ); 906 // Check for include cycles between fragments 907 maybeWarnOnFragmentIncludeCycle( fragment.getElement(), fragment.getIncludes() ); 908 } 909 return resolveType; 910 } 911 finally 912 { 913 _resolvingFragmentTypes.remove( fragmentName ); 914 } 915 } 916 } 917 918 @Nonnull 919 private ResolveType isInjectorResolved( @Nonnull final RoundEnvironment env, 920 @Nonnull final InjectorDescriptor injector ) 921 { 922 final ResolveType resolveType = isResolved( env, injector, injector.getElement(), injector.getIncludes() ); 923 if ( ResolveType.RESOLVED == resolveType ) 924 { 925 // Check for redundant explicit @Injectable includes that are also transitively included via included fragments 926 maybeWarnOnRedundantDirectInjectableInclude( injector.getElement(), 927 Constants.INJECTOR_CLASSNAME, 928 injector.getIncludes() ); 929 } 930 return resolveType; 931 } 932 933 @Nonnull 934 private <T> ResolveType isResolved( @Nonnull final RoundEnvironment env, 935 @Nonnull final T descriptor, 936 @Nonnull final TypeElement originator, 937 @Nonnull final Collection<IncludeDescriptor> includes ) 938 { 939 // By the time we get here we can guarantee that the java types for includes are correctly resolved 940 // but they may not have passed through annotation processor and thus the descriptors may be absent 941 // so we go through a few iterations and as long as one include is resolved in each iteration we should 942 // keep going as we may be able to resolve all includes. Also if one of the includes is a provider 943 // we need to check the associated provider type is resolved. 944 for ( final IncludeDescriptor include : includes ) 945 { 946 final ResolveType resolveType = isIncludeResolved( env, descriptor, originator, include ); 947 if ( ResolveType.RESOLVED != resolveType ) 948 { 949 return resolveType; 950 } 951 } 952 953 return ResolveType.RESOLVED; 954 } 955 956 @Nonnull 957 private ResolveType isIncludeResolved( @Nonnull final RoundEnvironment env, 958 @Nonnull final Object descriptor, 959 @Nonnull final TypeElement originator, 960 @Nonnull final IncludeDescriptor include ) 961 { 962 AnnotationMirror annotation = 963 AnnotationsUtil.findAnnotationByType( originator, Constants.INJECTOR_CLASSNAME ); 964 965 final String annotationClassname = 966 null != annotation ? Constants.INJECTOR_CLASSNAME : Constants.FRAGMENT_CLASSNAME; 967 if ( null == annotation ) 968 { 969 annotation = AnnotationsUtil.getAnnotationByType( originator, Constants.FRAGMENT_CLASSNAME ); 970 } 971 972 final String classname = include.actualTypeName(); 973 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 974 if ( null == element ) 975 { 976 assert include.isProvider(); 977 if ( env.processingOver() ) 978 { 979 if ( descriptor instanceof FragmentDescriptor ) 980 { 981 ( (FragmentDescriptor) descriptor ).markAsContainsError(); 982 } 983 else 984 { 985 ( (InjectorDescriptor) descriptor ).markAsContainsError(); 986 } 987 988 final String message = 989 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 990 "parameter named 'includes' containing the value " + include.includedType() + 991 " and that type is annotated by the " + 992 MemberChecks.toSimpleName( Constants.STING_PROVIDER_CLASSNAME ) + 993 " annotation. The provider annotation expects a " + 994 "provider class named " + include.actualTypeName() + " but no such class exists. The " + 995 "type needs to be removed from the includes or the provider class needs to be present."; 996 reportError( env, message, originator, annotation, null ); 997 } 998 return ResolveType.UNRESOLVED; 999 } 1000 final boolean isInjectable = AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ); 1001 final boolean isFragment = 1002 !isInjectable && AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ); 1003 if ( include.isProvider() && !isInjectable && !isFragment ) 1004 { 1005 if ( descriptor instanceof FragmentDescriptor ) 1006 { 1007 ( (FragmentDescriptor) descriptor ).markAsContainsError(); 1008 } 1009 else 1010 { 1011 ( (InjectorDescriptor) descriptor ).markAsContainsError(); 1012 } 1013 1014 final String message = 1015 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 1016 "parameter named 'includes' containing the value " + include.includedType() + 1017 " and that type is annotated by the " + 1018 MemberChecks.toSimpleName( Constants.STING_PROVIDER_CLASSNAME ) + 1019 " annotation. The provider annotation expects a " + 1020 "provider class named " + include.actualTypeName() + " but that class is not annotated with either " + 1021 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + ", " + 1022 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + " or " + 1023 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ); 1024 throw new ProcessorException( message, originator, annotation ); 1025 } 1026 if ( descriptor instanceof final FragmentDescriptor fragmentDescriptor ) 1027 { 1028 if ( fragmentDescriptor.isLocalOnly() && !isInSamePackage( originator, element ) ) 1029 { 1030 fragmentDescriptor.markAsContainsError(); 1031 final String message = 1032 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + " target has an includes parameter " + 1033 "containing the value " + include.includedType() + " that is in the package " + 1034 GeneratorUtil.getQualifiedPackageName( element ) + " when the fragment is in the package " + 1035 GeneratorUtil.getQualifiedPackageName( originator ) + " and localOnly is true"; 1036 throw new ProcessorException( message, originator, annotation ); 1037 } 1038 } 1039 else if ( !include.auto() && ( (InjectorDescriptor) descriptor ).isFragmentOnly() && !isFragment ) 1040 { 1041 final InjectorDescriptor injectorDescriptor = (InjectorDescriptor) descriptor; 1042 injectorDescriptor.markAsContainsError(); 1043 final String message = 1044 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " target has an includes parameter containing " + 1045 "the value " + include.includedType() + " that resolves to " + element.getQualifiedName() + 1046 " which is not annotated by " + MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1047 " when fragmentOnly is true"; 1048 throw new ProcessorException( message, originator, annotation ); 1049 } 1050 if ( isFragment ) 1051 { 1052 FragmentDescriptor fragment = _registry.findFragmentByClassName( classname ); 1053 if ( null == fragment ) 1054 { 1055 fragment = deriveFragmentDescriptor( element ); 1056 if ( null == fragment ) 1057 { 1058 debug( () -> "Unable to derive descriptor for fragment " + classname + 1059 ". Marking " + originator.getQualifiedName() + " as unresolved" ); 1060 return ResolveType.MAYBE_UNRESOLVED; 1061 } 1062 _registry.registerFragment( fragment ); 1063 } 1064 if ( ResolveType.RESOLVED != isFragmentReady( env, fragment ) ) 1065 { 1066 debug( () -> "Fragment include " + classname + " is present but not yet resolved. " + 1067 "Marking " + originator.getQualifiedName() + " as unresolved" ); 1068 return ResolveType.UNRESOLVED; 1069 } 1070 } 1071 else 1072 { 1073 InjectableDescriptor injectable = _registry.findInjectableByClassName( classname ); 1074 if ( null == injectable ) 1075 { 1076 injectable = deriveInjectableDescriptor( element ); 1077 if ( null == injectable ) 1078 { 1079 debug( () -> "Unable to derive descriptor for injectable " + classname + 1080 ". Marking " + originator.getQualifiedName() + " as unresolved" ); 1081 return ResolveType.MAYBE_UNRESOLVED; 1082 } 1083 _registry.registerInjectable( injectable ); 1084 } 1085 if ( !SuperficialValidation.validateElement( processingEnv, injectable.getElement() ) ) 1086 { 1087 debug( () -> "Injectable include " + classname + " is not yet resolved. " + 1088 "Marking " + originator.getQualifiedName() + " as unresolved" ); 1089 return ResolveType.UNRESOLVED; 1090 } 1091 1092 if ( !include.auto() && 1093 !injectable.getBinding().isEager() && 1094 injectable.isAutoDiscoverable() && 1095 ElementsUtil.isWarningNotSuppressed( originator, Constants.WARNING_AUTO_DISCOVERABLE_INCLUDED ) ) 1096 { 1097 final String message = 1098 MemberChecks.shouldNot( annotationClassname, 1099 "include an auto-discoverable type " + classname + ". " + 1100 MemberChecks.suppressedBy( Constants.WARNING_AUTO_DISCOVERABLE_INCLUDED ) ); 1101 warning( message, originator ); 1102 } 1103 } 1104 return ResolveType.RESOLVED; 1105 } 1106 1107 private void processInjectorFragment( @Nonnull final TypeElement element ) 1108 { 1109 debug( () -> "Processing Injector Fragment: " + element ); 1110 final ElementKind kind = element.getKind(); 1111 if ( ElementKind.INTERFACE != kind ) 1112 { 1113 throw new ProcessorException( MemberChecks.must( Constants.INJECTOR_FRAGMENT_CLASSNAME, "be an interface" ), 1114 element ); 1115 } 1116 final List<ExecutableElement> methods = 1117 ElementsUtil.getMethods( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 1118 for ( final ExecutableElement method : methods ) 1119 { 1120 if ( method.getModifiers().contains( Modifier.DEFAULT ) ) 1121 { 1122 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) + 1123 " target must not include default methods", 1124 method ); 1125 } 1126 else if ( method.getModifiers().contains( Modifier.STATIC ) ) 1127 { 1128 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) + 1129 " target must not include static methods", 1130 method ); 1131 } 1132 } 1133 } 1134 1135 private void processInjector( @Nonnull final TypeElement element ) 1136 throws Exception 1137 { 1138 debug( () -> "Processing Injector: " + element ); 1139 final ElementKind kind = element.getKind(); 1140 if ( ElementKind.INTERFACE != kind ) 1141 { 1142 throw new ProcessorException( MemberChecks.must( Constants.INJECTOR_CLASSNAME, "be an interface" ), 1143 element ); 1144 } 1145 if ( !element.getTypeParameters().isEmpty() ) 1146 { 1147 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, "have type parameters" ), 1148 element ); 1149 } 1150 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 1151 if ( !scopedAnnotations.isEmpty() ) 1152 { 1153 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1154 "be annotated with an annotation that is " + 1155 "annotated with the " + Constants.JSR_330_SCOPE_CLASSNAME + 1156 " annotation such as " + scopedAnnotations ), 1157 element ); 1158 } 1159 1160 final boolean gwt = isGwtEnabled( element ); 1161 final boolean injectable = 1162 (boolean) AnnotationsUtil.getAnnotationValue( element, Constants.INJECTOR_CLASSNAME, "injectable" ).getValue(); 1163 final boolean fragmentOnly = extractInjectorFragmentOnly( element ); 1164 final List<IncludeDescriptor> includes = extractIncludes( element, Constants.INJECTOR_CLASSNAME, fragmentOnly ); 1165 final List<InputDescriptor> inputs = extractInputs( element ); 1166 1167 final List<ServiceRequest> outputs = new ArrayList<>(); 1168 final List<ExecutableElement> methods = 1169 ElementsUtil.getMethods( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 1170 for ( final ExecutableElement method : methods ) 1171 { 1172 if ( method.getModifiers().contains( Modifier.ABSTRACT ) ) 1173 { 1174 processInjectorOutputMethod( outputs, method ); 1175 } 1176 else if ( method.getModifiers().contains( Modifier.DEFAULT ) ) 1177 { 1178 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1179 " target must not include default methods", 1180 method ); 1181 } 1182 } 1183 for ( final Element enclosedElement : element.getEnclosedElements() ) 1184 { 1185 final ElementKind enclosedElementKind = enclosedElement.getKind(); 1186 if ( ElementKind.INTERFACE == enclosedElementKind && 1187 AnnotationsUtil.hasAnnotationOfType( enclosedElement, Constants.FRAGMENT_CLASSNAME ) ) 1188 { 1189 final DeclaredType type = (DeclaredType) enclosedElement.asType(); 1190 if ( includes.stream().noneMatch( d -> Objects.equals( d.includedType(), type ) ) ) 1191 { 1192 includes.add( new IncludeDescriptor( type, type.toString(), true ) ); 1193 } 1194 else 1195 { 1196 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1197 " target must not include a " + 1198 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + " annotated " + 1199 "type that is auto-included as it is enclosed within the injector type", 1200 element ); 1201 } 1202 } 1203 else if ( ElementKind.CLASS == enclosedElementKind && 1204 AnnotationsUtil.hasAnnotationOfType( enclosedElement, Constants.INJECTABLE_CLASSNAME ) ) 1205 { 1206 final DeclaredType type = (DeclaredType) enclosedElement.asType(); 1207 if ( includes.stream().noneMatch( d -> Objects.equals( d.includedType(), type ) ) ) 1208 { 1209 includes.add( new IncludeDescriptor( type, type.toString(), true ) ); 1210 } 1211 else 1212 { 1213 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1214 " target must not include an " + 1215 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + " annotated " + 1216 "type that is auto-included as it is enclosed within the injector type", 1217 element ); 1218 } 1219 } 1220 else if ( enclosedElementKind.isClass() || enclosedElementKind.isInterface() ) 1221 { 1222 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1223 " target must not contain a type that is not annotated " + 1224 "by either " + MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1225 " or " + MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ), 1226 element ); 1227 } 1228 } 1229 final InjectorDescriptor injector = 1230 new InjectorDescriptor( element, gwt, injectable, fragmentOnly, includes, inputs, outputs ); 1231 _registry.registerInjector( injector ); 1232 emitInjectorJsonDescriptor( injector ); 1233 } 1234 1235 private boolean extractInjectorFragmentOnly( @Nonnull final TypeElement element ) 1236 { 1237 return (boolean) AnnotationsUtil.getAnnotationValue( element, Constants.INJECTOR_CLASSNAME, "fragmentOnly" ) 1238 .getValue(); 1239 } 1240 1241 private boolean isGwtEnabled( @Nonnull final TypeElement element ) 1242 { 1243 final String value = AnnotationsUtil.getEnumAnnotationParameter( element, Constants.INJECTOR_CLASSNAME, "gwt" ); 1244 return "ENABLE".equals( value ) || 1245 ( "AUTODETECT".equals( value ) && 1246 null != processingEnv.getElementUtils().getTypeElement( "javaemul.internal.annotations.DoNotInline" ) ); 1247 } 1248 1249 private void emitInjectorJsonDescriptor( @Nonnull final InjectorDescriptor injector ) 1250 throws IOException 1251 { 1252 if ( _emitJsonDescriptors ) 1253 { 1254 final TypeElement element = injector.getElement(); 1255 final String filename = toFilename( element ) + JSON_SUFFIX; 1256 JsonUtil.writeJsonResource( processingEnv, element, filename, injector::write ); 1257 } 1258 } 1259 1260 private void processInjectorOutputMethod( @Nonnull final List<ServiceRequest> outputs, 1261 @Nonnull final ExecutableElement method ) 1262 { 1263 assert method.getModifiers().contains( Modifier.ABSTRACT ); 1264 if ( !findInterceptorBindingAnnotations( method ).isEmpty() ) 1265 { 1266 throw new ProcessorException( "Interceptor bindings on non-fragment methods are not supported", 1267 method ); 1268 } 1269 if ( TypeKind.VOID == method.getReturnType().getKind() ) 1270 { 1271 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1272 "contain a method that has a void return value" ), 1273 method ); 1274 } 1275 else if ( !method.getParameters().isEmpty() ) 1276 { 1277 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1278 "contain a method that has parameters" ), 1279 method ); 1280 } 1281 else if ( !method.getTypeParameters().isEmpty() ) 1282 { 1283 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1284 "contain a method that has any type parameters" ), 1285 method ); 1286 } 1287 else if ( !method.getThrownTypes().isEmpty() ) 1288 { 1289 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1290 "contain a method that throws any exceptions" ), 1291 method ); 1292 } 1293 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( method ); 1294 if ( !scopedAnnotations.isEmpty() ) 1295 { 1296 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1297 "contain a method that is annotated with an " + 1298 "annotation that is annotated with the " + 1299 Constants.JSR_330_SCOPE_CLASSNAME + 1300 " annotation such as " + scopedAnnotations ), 1301 method ); 1302 } 1303 outputs.add( processOutputMethod( method ) ); 1304 } 1305 1306 @Nonnull 1307 private ServiceRequest processOutputMethod( @Nonnull final ExecutableElement method ) 1308 { 1309 final TypeMirror type = method.getReturnType(); 1310 if ( TypesUtil.containsArrayType( type ) ) 1311 { 1312 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1313 "contain a method with a return type that contains an array type" ), 1314 method ); 1315 } 1316 else if ( TypesUtil.containsWildcard( type ) ) 1317 { 1318 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1319 "contain a method with a return type that contains a wildcard type parameter" ), 1320 method ); 1321 } 1322 else if ( TypesUtil.containsRawType( type ) ) 1323 { 1324 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1325 "contain a method with a return type that contains a raw type" ), 1326 method ); 1327 } 1328 else 1329 { 1330 TypeMirror dependencyType = null; 1331 ServiceRequest.Kind kind = null; 1332 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 1333 { 1334 dependencyType = candidate.extractType( type ); 1335 if ( null != dependencyType ) 1336 { 1337 kind = candidate; 1338 break; 1339 } 1340 } 1341 if ( null == kind ) 1342 { 1343 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1344 "contain a method with a return type that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 1345 method ); 1346 } 1347 else 1348 { 1349 final boolean optional = AnnotationsUtil.hasNullableAnnotation( method ); 1350 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 1351 { 1352 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1353 "contain a method annotated with " + 1354 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 1355 " that is not an instance dependency kind" ), 1356 method ); 1357 } 1358 final String qualifier = getQualifier( method ); 1359 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.JSR_330_NAMED_CLASSNAME ) ) 1360 { 1361 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1362 "contain a method annotated with the " + 1363 Constants.JSR_330_NAMED_CLASSNAME + 1364 " annotation. Use the " + Constants.NAMED_CLASSNAME + 1365 " annotation instead" ), 1366 method ); 1367 } 1368 1369 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 1370 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 1371 return new ServiceRequest( kind, service, method ); 1372 } 1373 } 1374 } 1375 1376 private void verifyNamedElements( @Nonnull final RoundEnvironment env, 1377 @Nonnull final Set<? extends Element> elements ) 1378 { 1379 for ( final Element element : elements ) 1380 { 1381 final AnnotationUsageKind usageKind = classifyNamedElement( element ); 1382 if ( AnnotationUsageKind.INVALID == usageKind ) 1383 { 1384 if ( ElementKind.PARAMETER == element.getKind() ) 1385 { 1386 if ( ElementKind.CONSTRUCTOR == element.getEnclosingElement().getKind() ) 1387 { 1388 reportError( env, 1389 MemberChecks.must( Constants.NAMED_CLASSNAME, 1390 "only be present on a constructor parameter if the constructor " + 1391 "is enclosed in a type annotated with " + 1392 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1393 " or the type is annotated with an annotation annotated by " + 1394 MemberChecks.toSimpleName( Constants.ACT_AS_STING_CONSUMER_CLASSNAME ) + 1395 " or " + 1396 MemberChecks.toSimpleName( Constants.ACT_AS_STING_COMPONENT_CLASSNAME ) ), 1397 element ); 1398 } 1399 else 1400 { 1401 reportError( env, 1402 MemberChecks.must( Constants.NAMED_CLASSNAME, 1403 "only be present on a method parameter if the method is enclosed in a type annotated with " + 1404 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) ), 1405 element ); 1406 } 1407 } 1408 else if ( ElementKind.CLASS == element.getKind() ) 1409 { 1410 reportError( env, 1411 MemberChecks.must( Constants.NAMED_CLASSNAME, 1412 "only be present on a type if the type is annotated with " + 1413 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1414 " or the type is annotated with an annotation annotated by " + 1415 MemberChecks.toSimpleName( Constants.ACT_AS_STING_PROVIDER_CLASSNAME ) + 1416 " or " + 1417 MemberChecks.toSimpleName( Constants.ACT_AS_STING_COMPONENT_CLASSNAME ) ), 1418 element ); 1419 } 1420 else if ( ElementKind.METHOD == element.getKind() ) 1421 { 1422 reportError( env, 1423 MemberChecks.mustNot( Constants.NAMED_CLASSNAME, 1424 "be a method unless the method is enclosed in a type annotated with " + 1425 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + ", " + 1426 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " or " + 1427 MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) ), 1428 element ); 1429 } 1430 else 1431 { 1432 reportError( env, 1433 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + " target is not valid", 1434 element ); 1435 } 1436 } 1437 else if ( AnnotationUsageKind.PROCESSED != usageKind && 1438 AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION != usageKind ) 1439 { 1440 reportError( env, 1441 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + " target is not valid", 1442 element ); 1443 } 1444 } 1445 } 1446 1447 @Nonnull 1448 private AnnotationUsageKind classifyNamedElement( @Nonnull final Element element ) 1449 { 1450 if ( ElementKind.PARAMETER == element.getKind() ) 1451 { 1452 final Element executableElement = element.getEnclosingElement(); 1453 final Element enclosingType = executableElement.getEnclosingElement(); 1454 if ( ElementKind.CONSTRUCTOR == executableElement.getKind() ) 1455 { 1456 if ( AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.INJECTABLE_CLASSNAME ) ) 1457 { 1458 return AnnotationUsageKind.PROCESSED; 1459 } 1460 else if ( hasActAsStingConsumer( enclosingType ) ) 1461 { 1462 return AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION; 1463 } 1464 else 1465 { 1466 return AnnotationUsageKind.INVALID; 1467 } 1468 } 1469 else if ( ElementKind.METHOD == executableElement.getKind() && 1470 AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.FRAGMENT_CLASSNAME ) ) 1471 { 1472 return AnnotationUsageKind.PROCESSED; 1473 } 1474 else 1475 { 1476 return AnnotationUsageKind.INVALID; 1477 } 1478 } 1479 else if ( ElementKind.CLASS == element.getKind() ) 1480 { 1481 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 1482 { 1483 return AnnotationUsageKind.PROCESSED; 1484 } 1485 else if ( hasActAsStingProvider( element ) ) 1486 { 1487 return AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION; 1488 } 1489 else 1490 { 1491 return AnnotationUsageKind.INVALID; 1492 } 1493 } 1494 else if ( ElementKind.METHOD == element.getKind() ) 1495 { 1496 final Element enclosingType = element.getEnclosingElement(); 1497 if ( AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.FRAGMENT_CLASSNAME ) || 1498 AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.INJECTOR_CLASSNAME ) ) 1499 { 1500 return AnnotationUsageKind.PROCESSED; 1501 } 1502 else if ( AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.INJECTOR_FRAGMENT_CLASSNAME ) ) 1503 { 1504 return AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION; 1505 } 1506 else 1507 { 1508 return AnnotationUsageKind.INVALID; 1509 } 1510 } 1511 else 1512 { 1513 return AnnotationUsageKind.INVALID; 1514 } 1515 } 1516 1517 private boolean hasActAsStingConsumer( @Nonnull final Element element ) 1518 { 1519 return hasValidationRole( element, 1520 Constants.ACT_AS_STING_CONSUMER_SIMPLE_NAME, 1521 Constants.ACT_AS_STING_COMPONENT_SIMPLE_NAME ); 1522 } 1523 1524 private boolean hasActAsStingProvider( @Nonnull final Element element ) 1525 { 1526 return hasValidationRole( element, 1527 Constants.ACT_AS_STING_PROVIDER_SIMPLE_NAME, 1528 Constants.ACT_AS_STING_COMPONENT_SIMPLE_NAME ); 1529 } 1530 1531 private boolean hasValidationRole( @Nonnull final Element element, @Nonnull final String... annotationNames ) 1532 { 1533 return hasAnnotationWithAnnotationMatching( element, ca -> matchesValidationRole( ca, annotationNames ) ); 1534 } 1535 1536 private boolean matchesValidationRole( @Nonnull final AnnotationMirror annotation, 1537 @Nonnull final String... annotationNames ) 1538 { 1539 final Element element = annotation.getAnnotationType().asElement(); 1540 if ( ElementKind.ANNOTATION_TYPE != element.getKind() || 1541 element.getEnclosedElements().stream().anyMatch( e -> ElementKind.METHOD == e.getKind() ) ) 1542 { 1543 return false; 1544 } 1545 final String simpleName = element.getSimpleName().toString(); 1546 return Arrays.asList( annotationNames ).contains( simpleName ); 1547 } 1548 1549 private boolean hasAnnotationWithAnnotationMatching( @Nonnull final AnnotatedConstruct element, 1550 @Nonnull final Predicate<? super AnnotationMirror> predicate ) 1551 { 1552 return element 1553 .getAnnotationMirrors() 1554 .stream() 1555 .anyMatch( a -> a.getAnnotationType() 1556 .asElement() 1557 .getAnnotationMirrors() 1558 .stream() 1559 .anyMatch( predicate ) ); 1560 } 1561 1562 private void verifyTypedElements( @Nonnull final RoundEnvironment env, 1563 @Nonnull final Set<? extends Element> elements ) 1564 { 1565 for ( final Element element : elements ) 1566 { 1567 final AnnotationUsageKind usageKind = classifyTypedElement( element ); 1568 if ( AnnotationUsageKind.PROCESSED != usageKind && 1569 AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION != usageKind ) 1570 { 1571 if ( ElementKind.CLASS == element.getKind() ) 1572 { 1573 reportError( env, 1574 MemberChecks.must( Constants.TYPED_CLASSNAME, 1575 "only be present on a type if the type is annotated with " + 1576 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1577 " or the type is annotated with an annotation annotated by " + 1578 MemberChecks.toSimpleName( Constants.ACT_AS_STING_PROVIDER_CLASSNAME ) + 1579 " or " + 1580 MemberChecks.toSimpleName( Constants.ACT_AS_STING_COMPONENT_CLASSNAME ) ), 1581 element ); 1582 } 1583 else if ( ElementKind.METHOD == element.getKind() ) 1584 { 1585 reportError( env, 1586 MemberChecks.mustNot( Constants.TYPED_CLASSNAME, 1587 "be a method unless the method is enclosed in a type annotated with " + 1588 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) ), 1589 element ); 1590 } 1591 else 1592 { 1593 reportError( env, 1594 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + " target is not valid", 1595 element ); 1596 } 1597 } 1598 } 1599 } 1600 1601 @Nonnull 1602 private AnnotationUsageKind classifyTypedElement( @Nonnull final Element element ) 1603 { 1604 if ( ElementKind.CLASS == element.getKind() ) 1605 { 1606 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 1607 { 1608 return AnnotationUsageKind.PROCESSED; 1609 } 1610 else if ( hasActAsStingProvider( element ) ) 1611 { 1612 return AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION; 1613 } 1614 else 1615 { 1616 return AnnotationUsageKind.INVALID; 1617 } 1618 } 1619 else if ( ElementKind.METHOD == element.getKind() ) 1620 { 1621 final Element enclosingType = element.getEnclosingElement(); 1622 if ( AnnotationsUtil.hasAnnotationOfType( enclosingType, Constants.FRAGMENT_CLASSNAME ) ) 1623 { 1624 return AnnotationUsageKind.PROCESSED; 1625 } 1626 else 1627 { 1628 return AnnotationUsageKind.INVALID; 1629 } 1630 } 1631 else 1632 { 1633 return AnnotationUsageKind.INVALID; 1634 } 1635 } 1636 1637 private void verifyEagerElements( @Nonnull final RoundEnvironment env, 1638 @Nonnull final Set<? extends Element> elements ) 1639 { 1640 for ( final Element element : elements ) 1641 { 1642 final AnnotationUsageKind usageKind = classifyEagerElement( element ); 1643 if ( AnnotationUsageKind.PROCESSED != usageKind && 1644 AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION != usageKind ) 1645 { 1646 if ( ElementKind.CLASS == element.getKind() ) 1647 { 1648 reportError( env, 1649 MemberChecks.must( Constants.EAGER_CLASSNAME, 1650 "only be present on a type if the type is annotated with " + 1651 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1652 " or the type is annotated with an annotation annotated by " + 1653 MemberChecks.toSimpleName( Constants.ACT_AS_STING_PROVIDER_CLASSNAME ) + 1654 " or " + 1655 MemberChecks.toSimpleName( Constants.ACT_AS_STING_COMPONENT_CLASSNAME ) ), 1656 element ); 1657 } 1658 else if ( ElementKind.METHOD == element.getKind() ) 1659 { 1660 reportError( env, 1661 MemberChecks.must( Constants.EAGER_CLASSNAME, 1662 "only be present on a method if the method is enclosed in a type annotated with " + 1663 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) ), 1664 element ); 1665 } 1666 else 1667 { 1668 reportError( env, 1669 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + " target is not valid", 1670 element ); 1671 } 1672 } 1673 } 1674 } 1675 1676 @Nonnull 1677 private AnnotationUsageKind classifyEagerElement( @Nonnull final Element element ) 1678 { 1679 if ( ElementKind.CLASS == element.getKind() ) 1680 { 1681 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 1682 { 1683 return AnnotationUsageKind.PROCESSED; 1684 } 1685 else if ( hasActAsStingProvider( element ) ) 1686 { 1687 return AnnotationUsageKind.SILENT_INTEGRATION_EXCEPTION; 1688 } 1689 else 1690 { 1691 return AnnotationUsageKind.INVALID; 1692 } 1693 } 1694 else if ( ElementKind.METHOD == element.getKind() && 1695 AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.FRAGMENT_CLASSNAME ) ) 1696 { 1697 return AnnotationUsageKind.PROCESSED; 1698 } 1699 else 1700 { 1701 return AnnotationUsageKind.INVALID; 1702 } 1703 } 1704 1705 private void processFragment( @Nonnull final TypeElement element ) 1706 { 1707 debug( () -> "Processing Fragment: " + element ); 1708 if ( ElementKind.INTERFACE != element.getKind() ) 1709 { 1710 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, "be an interface" ), 1711 element ); 1712 } 1713 else if ( !element.getTypeParameters().isEmpty() ) 1714 { 1715 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, "have type parameters" ), 1716 element ); 1717 } 1718 else if ( !element.getInterfaces().isEmpty() ) 1719 { 1720 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, "extend any interfaces" ), 1721 element ); 1722 } 1723 final boolean localOnly = extractFragmentLocalOnly( element ); 1724 final List<IncludeDescriptor> includes = extractIncludes( element, Constants.FRAGMENT_CLASSNAME, false ); 1725 final Map<ExecutableElement, Binding> bindings = new LinkedHashMap<>(); 1726 for ( final Element enclosedElement : element.getEnclosedElements() ) 1727 { 1728 final ElementKind enclosedElementKind = enclosedElement.getKind(); 1729 if ( ElementKind.METHOD == enclosedElementKind ) 1730 { 1731 processProvidesMethod( element, bindings, (ExecutableElement) enclosedElement ); 1732 } 1733 if ( enclosedElementKind.isClass() || enclosedElementKind.isInterface() ) 1734 { 1735 throw new ProcessorException( MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1736 " target must not contain any types", 1737 element ); 1738 } 1739 } 1740 if ( bindings.isEmpty() && includes.isEmpty() ) 1741 { 1742 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 1743 "contain one or more methods or one or more includes" ), 1744 element ); 1745 } 1746 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 1747 if ( !scopedAnnotations.isEmpty() ) 1748 { 1749 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1750 "be annotated with an annotation that is " + 1751 "annotated with the " + Constants.JSR_330_SCOPE_CLASSNAME + 1752 " annotation such as " + scopedAnnotations ), 1753 element ); 1754 } 1755 _registry.registerFragment( new FragmentDescriptor( element, includes, localOnly, bindings.values() ) ); 1756 } 1757 1758 private void processFactory( @Nonnull final TypeElement element ) 1759 { 1760 debug( () -> "Processing Factory: " + element ); 1761 if ( ElementKind.INTERFACE != element.getKind() ) 1762 { 1763 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, "be an interface" ), 1764 element ); 1765 } 1766 else if ( !element.getTypeParameters().isEmpty() ) 1767 { 1768 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, "have type parameters" ), 1769 element ); 1770 } 1771 else if ( !element.getInterfaces().isEmpty() ) 1772 { 1773 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, "extend any interfaces" ), 1774 element ); 1775 } 1776 1777 final List<FactoryMethodDescriptor> methods = new ArrayList<>(); 1778 final List<FactoryDependencyDescriptor> dependencies = new ArrayList<>(); 1779 final Set<String> usedFieldNames = new HashSet<>(); 1780 for ( final Element enclosedElement : element.getEnclosedElements() ) 1781 { 1782 if ( ElementKind.METHOD == enclosedElement.getKind() ) 1783 { 1784 final ExecutableElement method = (ExecutableElement) enclosedElement; 1785 if ( !method.getModifiers().contains( Modifier.DEFAULT ) && 1786 !method.getModifiers().contains( Modifier.STATIC ) && 1787 !method.getModifiers().contains( Modifier.PRIVATE ) ) 1788 { 1789 methods.add( processFactoryMethod( element, method, dependencies, usedFieldNames ) ); 1790 } 1791 } 1792 } 1793 if ( methods.isEmpty() ) 1794 { 1795 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 1796 "contain at least one abstract method that returns a value" ), 1797 element ); 1798 } 1799 1800 _registry.registerFactory( new FactoryDescriptor( element, methods, dependencies ) ); 1801 } 1802 1803 @SuppressWarnings( "unchecked" ) 1804 @Nonnull 1805 private List<InputDescriptor> extractInputs( @Nonnull final TypeElement element ) 1806 { 1807 final List<InputDescriptor> results = new ArrayList<>(); 1808 final AnnotationMirror annotation = AnnotationsUtil.getAnnotationByType( element, Constants.INJECTOR_CLASSNAME ); 1809 final AnnotationValue inputsAnnotationValue = AnnotationsUtil.findAnnotationValue( annotation, "inputs" ); 1810 assert null != inputsAnnotationValue; 1811 final List<AnnotationMirror> inputs = (List<AnnotationMirror>) inputsAnnotationValue.getValue(); 1812 1813 final int size = inputs.size(); 1814 for ( int i = 0; i < size; i++ ) 1815 { 1816 final AnnotationMirror input = inputs.get( i ); 1817 final String qualifier = AnnotationsUtil.getAnnotationValueValue( input, "qualifier" ); 1818 final AnnotationValue typeAnnotationValue = AnnotationsUtil.getAnnotationValue( input, "type" ); 1819 final TypeMirror type = (TypeMirror) typeAnnotationValue.getValue(); 1820 if ( TypeKind.ARRAY == type.getKind() ) 1821 { 1822 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1823 " must not specify an array type for the type parameter", 1824 element, 1825 input, 1826 typeAnnotationValue ); 1827 } 1828 else if ( TypeKind.VOID == type.getKind() ) 1829 { 1830 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1831 " must specify a non-void type for the type parameter", 1832 element, 1833 input, 1834 typeAnnotationValue ); 1835 } 1836 else if ( TypeKind.DECLARED == type.getKind() && 1837 !( (TypeElement) ( (DeclaredType) type ).asElement() ).getTypeParameters().isEmpty() ) 1838 { 1839 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1840 " must not specify a parameterized type for the type parameter", 1841 element, 1842 input, 1843 typeAnnotationValue ); 1844 } 1845 final Coordinate coordinate = new Coordinate( qualifier, type ); 1846 final boolean optional = AnnotationsUtil.getAnnotationValueValue( input, "optional" ); 1847 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 1848 final Binding binding = 1849 new Binding( Binding.Kind.INPUT, 1850 element.getQualifiedName() + "#" + i, 1851 Collections.singletonList( service ), 1852 true, 1853 element, 1854 new ServiceRequest[ 0 ] ); 1855 binding.setInterceptorBindingSource( element, findInterceptorBindingAnnotationValues( element ) ); 1856 results.add( new InputDescriptor( service, binding, "input" + ( i + 1 ) ) ); 1857 } 1858 return results; 1859 } 1860 1861 @Nonnull 1862 private List<IncludeDescriptor> extractIncludes( @Nonnull final TypeElement element, 1863 @Nonnull final String annotationClassname, 1864 final boolean fragmentOnly ) 1865 { 1866 final List<IncludeDescriptor> results = new ArrayList<>(); 1867 final List<TypeMirror> includes = 1868 AnnotationsUtil.getTypeMirrorsAnnotationParameter( element, annotationClassname, "includes" ); 1869 final Set<String> included = new HashSet<>(); 1870 for ( final TypeMirror include : includes ) 1871 { 1872 if ( processingEnv.getTypeUtils().isSameType( include, element.asType() ) ) 1873 { 1874 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1875 " target must not include self", 1876 element ); 1877 } 1878 if ( include.getKind().isPrimitive() ) 1879 { 1880 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1881 " target must not include a primitive in the includes parameter", 1882 element ); 1883 } 1884 final Element includeElement = processingEnv.getTypeUtils().asElement( include ); 1885 if ( AnnotationsUtil.hasAnnotationOfType( includeElement, Constants.FRAGMENT_CLASSNAME ) || 1886 AnnotationsUtil.hasAnnotationOfType( includeElement, Constants.INJECTABLE_CLASSNAME ) ) 1887 { 1888 if ( fragmentOnly && 1889 AnnotationsUtil.hasAnnotationOfType( includeElement, Constants.INJECTABLE_CLASSNAME ) ) 1890 { 1891 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1892 " target has an includes parameter containing the value " + include + 1893 " that is not annotated by " + 1894 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1895 " when fragmentOnly is true", 1896 element ); 1897 } 1898 results.add( new IncludeDescriptor( (DeclaredType) include, include.toString(), false ) ); 1899 } 1900 else 1901 { 1902 final ElementKind kind = includeElement.getKind(); 1903 if ( ElementKind.CLASS == kind || ElementKind.INTERFACE == kind ) 1904 { 1905 final ProviderEntry provider = 1906 resolveSingleStingProvider( element, 1907 MemberChecks.toSimpleName( annotationClassname ) + 1908 " target has an 'includes' parameter containing the value " + 1909 includeElement.asType(), 1910 (TypeElement) includeElement ); 1911 if ( null != provider ) 1912 { 1913 final String targetQualifiedName = 1914 deriveProviderQualifiedName( (TypeElement) includeElement, provider.provider() ); 1915 results.add( new IncludeDescriptor( (DeclaredType) include, targetQualifiedName, false ) ); 1916 } 1917 else 1918 { 1919 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1920 " target has an includes parameter containing the value " + include + 1921 " that is not a type annotated by either " + 1922 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1923 " or " + 1924 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1925 " and the type does not declare a provider", 1926 element ); 1927 } 1928 } 1929 } 1930 final String includedType = include.toString(); 1931 if ( included.contains( includedType ) ) 1932 { 1933 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1934 " target has an includes parameter containing duplicate " + 1935 "includes with the type " + includedType, 1936 element ); 1937 } 1938 else 1939 { 1940 included.add( includedType ); 1941 } 1942 } 1943 return results; 1944 } 1945 1946 @Nullable 1947 private ProviderEntry resolveSingleStingProvider( @Nonnull final TypeElement element, 1948 @Nonnull final String targetDescription, 1949 @Nonnull final TypeElement annotatedType ) 1950 { 1951 final List<ProviderEntry> providers = 1952 annotatedType.getAnnotationMirrors() 1953 .stream() 1954 .map( a -> { 1955 final AnnotationMirror provider = getStingProvider( element, targetDescription, a ); 1956 return null != provider ? new ProviderEntry( a, provider ) : null; 1957 } ) 1958 .filter( Objects::nonNull ) 1959 .toList(); 1960 if ( providers.size() > 1 ) 1961 { 1962 final String message = 1963 targetDescription + " that is annotated by multiple " + 1964 MemberChecks.toSimpleName( Constants.STING_PROVIDER_CLASSNAME ) + 1965 " annotations. Matching annotations:\n" + 1966 providers 1967 .stream() 1968 .map( a -> ( (TypeElement) a.annotation().getAnnotationType().asElement() ).getQualifiedName() ) 1969 .map( a -> " " + a ) 1970 .collect( Collectors.joining( "\n" ) ); 1971 throw new ProcessorException( message, element ); 1972 } 1973 return providers.isEmpty() ? null : providers.get( 0 ); 1974 } 1975 1976 @Nullable 1977 private AnnotationMirror getStingProvider( @Nonnull final TypeElement element, 1978 @Nonnull final String targetDescription, 1979 @Nonnull final AnnotationMirror annotation ) 1980 { 1981 return annotation.getAnnotationType() 1982 .asElement() 1983 .getAnnotationMirrors() 1984 .stream() 1985 .filter( ca -> isStingProvider( element, targetDescription, ca ) ) 1986 .findAny() 1987 .orElse( null ); 1988 } 1989 1990 @Nonnull 1991 private String deriveProviderQualifiedName( @Nonnull final TypeElement annotatedType, 1992 @Nonnull final AnnotationMirror providerAnnotation ) 1993 { 1994 final String namePattern = AnnotationsUtil.getAnnotationValueValue( providerAnnotation, "value" ); 1995 final String targetCompoundType = 1996 namePattern 1997 .replace( "[SimpleName]", annotatedType.getSimpleName().toString() ) 1998 .replace( "[CompoundName]", getComponentName( annotatedType ) ) 1999 .replace( "[EnclosingName]", getEnclosingName( annotatedType ) ) 2000 .replace( "[FlatEnclosingName]", getEnclosingName( annotatedType ).replace( '.', '_' ) ); 2001 return ElementsUtil.getPackageElement( annotatedType ).getQualifiedName() + "." + targetCompoundType; 2002 } 2003 2004 @Nonnull 2005 private String getComponentName( @Nonnull final TypeElement element ) 2006 { 2007 return getEnclosingName( element ) + element.getSimpleName(); 2008 } 2009 2010 @Nonnull 2011 private String getEnclosingName( @Nonnull final TypeElement element ) 2012 { 2013 Element enclosingElement = element.getEnclosingElement(); 2014 final List<String> nameParts = new ArrayList<>(); 2015 while ( ElementKind.PACKAGE != enclosingElement.getKind() ) 2016 { 2017 nameParts.add( enclosingElement.getSimpleName().toString() ); 2018 enclosingElement = enclosingElement.getEnclosingElement(); 2019 } 2020 if ( nameParts.isEmpty() ) 2021 { 2022 return ""; 2023 } 2024 else 2025 { 2026 Collections.reverse( nameParts ); 2027 return String.join( ".", nameParts ) + "."; 2028 } 2029 } 2030 2031 private boolean isStingProvider( @Nonnull final TypeElement element, 2032 @Nonnull final String targetDescription, 2033 @Nonnull final AnnotationMirror annotation ) 2034 { 2035 if ( !annotation.getAnnotationType().asElement().getSimpleName().contentEquals( "StingProvider" ) ) 2036 { 2037 return false; 2038 } 2039 else 2040 { 2041 final boolean nameMatched = annotation.getElementValues() 2042 .entrySet() 2043 .stream() 2044 .anyMatch( e -> e.getKey().getSimpleName().contentEquals( "value" ) && 2045 e.getValue().getValue() instanceof String ); 2046 if ( nameMatched ) 2047 { 2048 return true; 2049 } 2050 else 2051 { 2052 final String message = targetDescription + 2053 " that is annotated by " + annotation + 2054 " that is annotated by an invalid " + 2055 MemberChecks.toSimpleName( Constants.STING_PROVIDER_CLASSNAME ) + 2056 " annotation missing a 'value' parameter of type string."; 2057 throw new ProcessorException( message, element ); 2058 } 2059 } 2060 } 2061 2062 @Nonnull 2063 private List<Binding> autoDiscoverProviderBindings( @Nonnull final ComponentGraph graph, 2064 @Nonnull final WorkEntry workEntry, 2065 @Nonnull final TypeElement frameworkType ) 2066 { 2067 final ProviderEntry provider = 2068 resolveSingleStingProvider( graph.getInjector().getElement(), 2069 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 2070 " target is attempting to auto-discover the type " + frameworkType.asType(), 2071 frameworkType ); 2072 if ( null == provider ) 2073 { 2074 return Collections.emptyList(); 2075 } 2076 2077 final String providerTypeName = deriveProviderQualifiedName( frameworkType, provider.provider() ); 2078 final TypeElement providerType = processingEnv.getElementUtils().getTypeElement( providerTypeName ); 2079 final Coordinate coordinate = new Coordinate( "", frameworkType.asType() ); 2080 if ( null == providerType ) 2081 { 2082 throw new ProcessorException( buildAutoDiscoverProviderError( coordinate, 2083 workEntry, 2084 "the framework type " + 2085 frameworkType.getQualifiedName() + 2086 " expects a provider class named " + 2087 providerTypeName + " but no such class exists" ), 2088 workEntry.getEntry().edge().getServiceRequest().getElement() ); 2089 } 2090 2091 if ( AnnotationsUtil.hasAnnotationOfType( providerType, Constants.FRAGMENT_CLASSNAME ) ) 2092 { 2093 FragmentDescriptor fragment = _registry.findFragmentByClassName( providerTypeName ); 2094 if ( null == fragment ) 2095 { 2096 fragment = deriveFragmentDescriptor( providerType ); 2097 if ( null != fragment ) 2098 { 2099 _registry.registerFragment( fragment ); 2100 } 2101 } 2102 if ( null == fragment ) 2103 { 2104 return Collections.emptyList(); 2105 } 2106 verifyAutoDiscoverProviderPublishesType( coordinate, providerType, fragment.getBindings(), workEntry ); 2107 registerAutoDiscoveredFragment( graph, fragment ); 2108 return graph.findAllBindingsByCoordinate( coordinate ); 2109 } 2110 else if ( AnnotationsUtil.hasAnnotationOfType( providerType, Constants.INJECTABLE_CLASSNAME ) ) 2111 { 2112 InjectableDescriptor injectable = _registry.findInjectableByClassName( providerTypeName ); 2113 if ( null == injectable ) 2114 { 2115 injectable = deriveInjectableDescriptor( providerType ); 2116 if ( null != injectable ) 2117 { 2118 _registry.registerInjectable( injectable ); 2119 } 2120 } 2121 if ( null == injectable ) 2122 { 2123 return Collections.emptyList(); 2124 } 2125 verifyAutoDiscoverProviderPublishesType( coordinate, 2126 providerType, 2127 Collections.singletonList( injectable.getBinding() ), 2128 workEntry ); 2129 graph.registerInjectable( injectable ); 2130 return Collections.singletonList( injectable.getBinding() ); 2131 } 2132 else 2133 { 2134 throw new ProcessorException( buildAutoDiscoverProviderError( coordinate, 2135 workEntry, 2136 "the framework type " + 2137 frameworkType.getQualifiedName() + 2138 " expects a provider class named " + 2139 providerTypeName + 2140 " but that class is not annotated with either " + 2141 MemberChecks.toSimpleName( 2142 Constants.FRAGMENT_CLASSNAME ) + 2143 " or " + 2144 MemberChecks.toSimpleName( 2145 Constants.INJECTABLE_CLASSNAME ) ), 2146 workEntry.getEntry().edge().getServiceRequest().getElement() ); 2147 } 2148 } 2149 2150 private void registerAutoDiscoveredFragment( @Nonnull final ComponentGraph graph, 2151 @Nonnull final FragmentDescriptor fragment ) 2152 { 2153 registerAutoDiscoveredIncludes( graph, fragment.getIncludes() ); 2154 graph.registerFragment( fragment ); 2155 } 2156 2157 private void registerAutoDiscoveredIncludes( @Nonnull final ComponentGraph graph, 2158 @Nonnull final Collection<IncludeDescriptor> includes ) 2159 { 2160 for ( final IncludeDescriptor include : includes ) 2161 { 2162 final String classname = include.actualTypeName(); 2163 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 2164 assert null != element; 2165 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 2166 { 2167 FragmentDescriptor fragment = _registry.findFragmentByClassName( classname ); 2168 if ( null == fragment ) 2169 { 2170 fragment = deriveFragmentDescriptor( element ); 2171 if ( null != fragment ) 2172 { 2173 _registry.registerFragment( fragment ); 2174 } 2175 } 2176 assert null != fragment; 2177 registerAutoDiscoveredFragment( graph, fragment ); 2178 } 2179 else 2180 { 2181 assert AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ); 2182 InjectableDescriptor injectable = _registry.findInjectableByClassName( classname ); 2183 if ( null == injectable ) 2184 { 2185 injectable = deriveInjectableDescriptor( element ); 2186 if ( null != injectable ) 2187 { 2188 _registry.registerInjectable( injectable ); 2189 } 2190 } 2191 assert null != injectable; 2192 graph.registerInjectable( injectable ); 2193 } 2194 } 2195 } 2196 2197 private void verifyAutoDiscoverProviderPublishesType( @Nonnull final Coordinate coordinate, 2198 @Nonnull final TypeElement providerType, 2199 @Nonnull final Collection<Binding> bindings, 2200 @Nonnull final WorkEntry workEntry ) 2201 { 2202 final boolean matches = 2203 bindings.stream().flatMap( b -> b.getPublishedServices().stream() ).anyMatch( s -> coordinate.equals( 2204 s.getCoordinate() ) ); 2205 if ( !matches ) 2206 { 2207 throw new ProcessorException( buildAutoDiscoverProviderError( coordinate, 2208 workEntry, 2209 "the provider class " + 2210 providerType.getQualifiedName() + 2211 " does not publish the service " + 2212 coordinate + 2213 " for the framework type " + 2214 coordinate.type() + 2215 ". The provider must publish the framework type with the default qualifier" ), 2216 workEntry.getEntry().edge().getServiceRequest().getElement() ); 2217 } 2218 } 2219 2220 @Nonnull 2221 private String buildAutoDiscoverProviderError( @Nonnull final Coordinate coordinate, 2222 @Nonnull final WorkEntry workEntry, 2223 @Nonnull final String detail ) 2224 { 2225 return MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 2226 "contain a non-optional dependency " + coordinate + 2227 " that can not be auto-discovered via " + 2228 MemberChecks.toSimpleName( Constants.STING_PROVIDER_CLASSNAME ) + 2229 " because " + detail + ".\nDependency Path:\n" + workEntry.describePathFromRoot() ); 2230 } 2231 2232 private void emitFragmentJsonDescriptor( @Nonnull final FragmentDescriptor fragment ) 2233 throws IOException 2234 { 2235 if ( _emitJsonDescriptors ) 2236 { 2237 final TypeElement element = fragment.getElement(); 2238 final String filename = toFilename( element ) + JSON_SUFFIX; 2239 JsonUtil.writeJsonResource( processingEnv, element, filename, fragment::write ); 2240 } 2241 } 2242 2243 private void processProvidesMethod( @Nonnull final TypeElement element, 2244 @Nonnull final Map<ExecutableElement, Binding> bindings, 2245 @Nonnull final ExecutableElement method ) 2246 { 2247 if ( TypeKind.VOID == method.getReturnType().getKind() ) 2248 { 2249 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 2250 "only contain methods that return a value" ), 2251 method ); 2252 } 2253 if ( !method.getTypeParameters().isEmpty() ) 2254 { 2255 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2256 "contain methods with a type parameter" ), 2257 method ); 2258 } 2259 if ( !method.getModifiers().contains( Modifier.DEFAULT ) ) 2260 { 2261 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 2262 "only contain methods with a default modifier" ), 2263 method ); 2264 } 2265 final boolean nullablePresent = AnnotationsUtil.hasNullableAnnotation( method ); 2266 if ( nullablePresent && method.getReturnType().getKind().isPrimitive() ) 2267 { 2268 throw new ProcessorException( MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 2269 " contains a method that is incorrectly annotated with " + 2270 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 2271 " as the return type is a primitive value", 2272 method ); 2273 } 2274 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( method ); 2275 if ( !scopedAnnotations.isEmpty() ) 2276 { 2277 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2278 "contain a method that is annotated with an " + 2279 "annotation that is annotated with the " + 2280 Constants.JSR_330_SCOPE_CLASSNAME + 2281 " annotation such as " + scopedAnnotations ), 2282 method ); 2283 } 2284 2285 final boolean eager = AnnotationsUtil.hasAnnotationOfType( method, Constants.EAGER_CLASSNAME ); 2286 2287 final List<ServiceRequest> dependencies = new ArrayList<>(); 2288 int index = 0; 2289 final List<? extends TypeMirror> parameterTypes = ( (ExecutableType) method.asType() ).getParameterTypes(); 2290 for ( final VariableElement parameter : method.getParameters() ) 2291 { 2292 dependencies.add( processFragmentServiceParameter( parameter, parameterTypes.get( index ) ) ); 2293 index++; 2294 } 2295 2296 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( method, Constants.TYPED_CLASSNAME ); 2297 final AnnotationValue value = 2298 null != annotation ? AnnotationsUtil.findAnnotationValue( annotation, "value" ) : null; 2299 2300 final String qualifier = getQualifier( method ); 2301 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.JSR_330_NAMED_CLASSNAME ) ) 2302 { 2303 final String message = 2304 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2305 "contain a method annotated with the " + Constants.JSR_330_NAMED_CLASSNAME + 2306 " annotation. Use the " + Constants.NAMED_CLASSNAME + " annotation instead" ); 2307 throw new ProcessorException( message, method ); 2308 } 2309 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.CDI_TYPED_CLASSNAME ) ) 2310 { 2311 final String message = 2312 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2313 "contain a method annotated with the " + Constants.CDI_TYPED_CLASSNAME + 2314 " annotation. Use the " + Constants.TYPED_CLASSNAME + " annotation instead" ); 2315 throw new ProcessorException( message, method ); 2316 } 2317 2318 @SuppressWarnings( "unchecked" ) 2319 final List<TypeMirror> types = 2320 null == value ? 2321 Collections.singletonList( method.getReturnType() ) : 2322 ( (List<AnnotationValue>) value.getValue() ) 2323 .stream() 2324 .map( v -> (TypeMirror) v.getValue() ) 2325 .toList(); 2326 2327 final ServiceSpec[] specs = new ServiceSpec[ types.size() ]; 2328 for ( int i = 0; i < specs.length; i++ ) 2329 { 2330 final TypeMirror type = types.get( i ); 2331 if ( !processingEnv.getTypeUtils().isAssignable( method.getReturnType(), type ) ) 2332 { 2333 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2334 " specified a type that is not assignable to the return type of the method", 2335 element, 2336 annotation, 2337 value ); 2338 } 2339 else if ( TypeKind.DECLARED == type.getKind() && isParameterized( (DeclaredType) type ) ) 2340 { 2341 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2342 " specified a type that is a a parameterized type", 2343 element, 2344 annotation, 2345 value ); 2346 } 2347 specs[ i ] = new ServiceSpec( new Coordinate( qualifier, type ), nullablePresent ); 2348 } 2349 2350 if ( 0 == specs.length && !eager ) 2351 { 2352 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2353 "contain methods that specify zero types with the " + 2354 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2355 " annotation and are not annotated with the " + 2356 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + 2357 " annotation otherwise the component can not be created by " + 2358 "the injector" ), 2359 element ); 2360 } 2361 if ( 0 == specs.length && !qualifier.isEmpty() ) 2362 { 2363 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2364 "contain methods that specify zero types with the " + 2365 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2366 " annotation and specify a qualifier with the " + 2367 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + 2368 " annotation as the qualifier is meaningless" ), 2369 element ); 2370 } 2371 2372 final Binding binding = 2373 new Binding( Binding.Kind.PROVIDES, 2374 element.getQualifiedName() + "#" + method.getSimpleName(), 2375 Arrays.asList( specs ), 2376 eager, 2377 method, 2378 dependencies.toArray( new ServiceRequest[ 0 ] ) ); 2379 binding.setInterceptorBindingSource( method, findInterceptorBindingAnnotationValues( method ) ); 2380 bindings.put( method, binding ); 2381 } 2382 2383 void processInterceptorBindings( @Nonnull final Binding binding ) 2384 { 2385 if ( !binding.isInterceptorBindingsProcessed() ) 2386 { 2387 final var bindingSource = binding.getInterceptorBindingSourceOrNull(); 2388 if ( null != bindingSource ) 2389 { 2390 for ( final var service : binding.getPublishedServices() ) 2391 { 2392 final var serviceBindings = new ArrayList<InterceptorBindingDescriptor>(); 2393 final var serviceType = service.getCoordinate().type(); 2394 TypeElement serviceElement = null; 2395 if ( TypeKind.DECLARED == serviceType.getKind() ) 2396 { 2397 serviceElement = (TypeElement) ( (DeclaredType) serviceType ).asElement(); 2398 for ( final var annotation : findInterceptorBindingAnnotations( serviceElement ) ) 2399 { 2400 final var values = extractBindingValues( annotation ); 2401 serviceBindings.add( createInterceptorBindingDescriptor( service, annotation, serviceElement, values ) ); 2402 } 2403 } 2404 if ( Binding.Kind.INPUT != binding.getKind() ) 2405 { 2406 for ( final var entry : binding.getInterceptorBindingSourceAnnotations().entrySet() ) 2407 { 2408 serviceBindings.add( createInterceptorBindingDescriptor( service, 2409 entry.getKey(), 2410 bindingSource, 2411 entry.getValue() ) ); 2412 } 2413 } 2414 validateNoMethodLevelInterceptorBindings( binding, serviceElement, bindingSource ); 2415 if ( !serviceBindings.isEmpty() ) 2416 { 2417 validateInterceptedService( binding, service, serviceElement, serviceBindings, bindingSource ); 2418 resolvePluginClaims( serviceBindings ); 2419 resolveGenericInterceptors( serviceBindings ); 2420 binding.addInterceptedService( new InterceptedServiceDescriptor( binding, service, serviceBindings ) ); 2421 } 2422 } 2423 } 2424 binding.markInterceptorBindingsProcessed(); 2425 } 2426 } 2427 2428 @Nonnull 2429 private List<AnnotationMirror> findInterceptorBindingAnnotations( @Nonnull final Element element ) 2430 { 2431 final var annotations = new ArrayList<AnnotationMirror>(); 2432 for ( final var annotation : element.getAnnotationMirrors() ) 2433 { 2434 if ( null != findInterceptorBindingMetaAnnotation( annotation ) ) 2435 { 2436 annotations.add( annotation ); 2437 } 2438 } 2439 return annotations; 2440 } 2441 2442 @Nonnull 2443 private Map<AnnotationMirror, Map<String, BindingValueModelImpl>> findInterceptorBindingAnnotationValues( 2444 @Nonnull final Element element ) 2445 { 2446 final var values = new LinkedHashMap<AnnotationMirror, Map<String, BindingValueModelImpl>>(); 2447 for ( final var annotation : findInterceptorBindingAnnotations( element ) ) 2448 { 2449 values.put( annotation, extractBindingValues( annotation ) ); 2450 } 2451 return values; 2452 } 2453 2454 @Nullable 2455 private AnnotationMirror findInterceptorBindingMetaAnnotation( @Nonnull final AnnotationMirror annotation ) 2456 { 2457 return 2458 annotation 2459 .getAnnotationType() 2460 .asElement() 2461 .getAnnotationMirrors() 2462 .stream() 2463 .filter( a -> Constants.INTERCEPTOR_BINDING_SIMPLE_NAME.contentEquals( a.getAnnotationType() 2464 .asElement() 2465 .getSimpleName() ) ) 2466 .findAny() 2467 .orElse( null ); 2468 } 2469 2470 @Nonnull 2471 private InterceptorBindingDescriptor createInterceptorBindingDescriptor( @Nonnull final ServiceSpec service, 2472 @Nonnull final AnnotationMirror annotation, 2473 @Nonnull final Element usageElement, 2474 @Nonnull final Map<String, BindingValueModelImpl> 2475 values ) 2476 { 2477 final var metaAnnotation = Objects.requireNonNull( findInterceptorBindingMetaAnnotation( annotation ) ); 2478 validateInterceptorBindingAnnotationType( annotation, metaAnnotation, usageElement ); 2479 final var annotationType = (TypeElement) annotation.getAnnotationType().asElement(); 2480 final var metaValues = processingEnv.getElementUtils().getElementValuesWithDefaults( metaAnnotation ); 2481 final var priorityValue = findAnnotationValueByName( metaValues, "priority" ); 2482 final var implementedByValue = findAnnotationValueByName( metaValues, "implementedBy" ); 2483 final int priority = (Integer) Objects.requireNonNull( priorityValue ).getValue(); 2484 final var implementedBy = null == implementedByValue ? "" : (String) implementedByValue.getValue(); 2485 return new InterceptorBindingDescriptor( service, 2486 annotation, 2487 annotationType, 2488 usageElement, 2489 priority, 2490 implementedBy, 2491 values ); 2492 } 2493 2494 private void validateInterceptorBindingAnnotationType( @Nonnull final AnnotationMirror annotation, 2495 @Nonnull final AnnotationMirror metaAnnotation, 2496 @Nonnull final Element usageElement ) 2497 { 2498 final var annotationType = (TypeElement) annotation.getAnnotationType().asElement(); 2499 if ( hasSourceRetention( annotationType ) ) 2500 { 2501 throw new ProcessorException( "Interceptor binding annotation " + annotationType.getQualifiedName() + 2502 " must not use @Retention(SOURCE)", 2503 usageElement, 2504 annotation ); 2505 } 2506 else 2507 { 2508 final var metaValues = processingEnv.getElementUtils().getElementValuesWithDefaults( metaAnnotation ); 2509 final var priorityValue = findAnnotationValueByName( metaValues, "priority" ); 2510 if ( null == priorityValue || !( priorityValue.getValue() instanceof Integer ) ) 2511 { 2512 throw new ProcessorException( "Interceptor binding meta-annotation on " + annotationType.getQualifiedName() + 2513 " must declare an int priority member", 2514 usageElement, 2515 annotation ); 2516 } 2517 else 2518 { 2519 final var implementedByValue = findAnnotationValueByName( metaValues, "implementedBy" ); 2520 if ( null != implementedByValue && !( implementedByValue.getValue() instanceof String ) ) 2521 { 2522 throw new ProcessorException( "Interceptor binding meta-annotation on " + annotationType.getQualifiedName() + 2523 " must declare a String implementedBy member when present", 2524 usageElement, 2525 annotation ); 2526 } 2527 else 2528 { 2529 final var implementedBy = null == implementedByValue ? "" : (String) implementedByValue.getValue(); 2530 if ( !implementedBy.isEmpty() ) 2531 { 2532 validateImplementedByName( implementedBy, usageElement, annotation ); 2533 } 2534 } 2535 } 2536 } 2537 } 2538 2539 private boolean hasSourceRetention( @Nonnull final TypeElement annotationType ) 2540 { 2541 final var retention = AnnotationsUtil.findAnnotationByType( annotationType, Retention.class.getName() ); 2542 if ( null != retention ) 2543 { 2544 final var value = AnnotationsUtil.findAnnotationValue( retention, "value" ); 2545 return null != value && "SOURCE".equals( value.getValue().toString() ); 2546 } 2547 else 2548 { 2549 return false; 2550 } 2551 } 2552 2553 @Nullable 2554 private AnnotationValue findAnnotationValueByName( 2555 @Nonnull final Map<? extends ExecutableElement, ? extends AnnotationValue> values, 2556 @Nonnull final String name ) 2557 { 2558 return 2559 values 2560 .entrySet() 2561 .stream() 2562 .filter( e -> e.getKey().getSimpleName().contentEquals( name ) ) 2563 .map( Map.Entry::getValue ) 2564 .findAny() 2565 .orElse( null ); 2566 } 2567 2568 @Nonnull 2569 private Map<String, BindingValueModelImpl> extractBindingValues( @Nonnull final AnnotationMirror annotation ) 2570 { 2571 final var values = new LinkedHashMap<String, BindingValueModelImpl>(); 2572 for ( final var entry : processingEnv.getElementUtils().getElementValuesWithDefaults( annotation ).entrySet() ) 2573 { 2574 final var name = entry.getKey().getSimpleName().toString(); 2575 values.put( name, toBindingValueModel( name, entry.getValue().getValue() ) ); 2576 } 2577 return values; 2578 } 2579 2580 @Nonnull 2581 private BindingValueModelImpl toBindingValueModel( @Nonnull final String name, @Nullable final Object value ) 2582 { 2583 if ( value instanceof String ) 2584 { 2585 return new BindingValueModelImpl( name, 2586 BindingValueKind.STRING, 2587 value, 2588 null, 2589 null, 2590 null, 2591 CodeBlock.of( "$S", value ).toString() ); 2592 } 2593 else if ( value instanceof Boolean ) 2594 { 2595 return new BindingValueModelImpl( name, 2596 BindingValueKind.BOOLEAN, 2597 value, 2598 null, 2599 null, 2600 null, 2601 value.toString() ); 2602 } 2603 else if ( value instanceof Byte ) 2604 { 2605 return new BindingValueModelImpl( name, 2606 BindingValueKind.BYTE, 2607 value, 2608 null, 2609 null, 2610 null, 2611 "(byte) " + value ); 2612 } 2613 else if ( value instanceof Short ) 2614 { 2615 return new BindingValueModelImpl( name, 2616 BindingValueKind.SHORT, 2617 value, 2618 null, 2619 null, 2620 null, 2621 "(short) " + value ); 2622 } 2623 else if ( value instanceof Integer ) 2624 { 2625 return new BindingValueModelImpl( name, BindingValueKind.INT, value, null, null, null, value.toString() ); 2626 } 2627 else if ( value instanceof Long ) 2628 { 2629 return new BindingValueModelImpl( name, BindingValueKind.LONG, value, null, null, null, value + "L" ); 2630 } 2631 else if ( value instanceof Float ) 2632 { 2633 return new BindingValueModelImpl( name, BindingValueKind.FLOAT, value, null, null, null, value + "F" ); 2634 } 2635 else if ( value instanceof Double ) 2636 { 2637 return new BindingValueModelImpl( name, BindingValueKind.DOUBLE, value, null, null, null, value.toString() ); 2638 } 2639 else if ( value instanceof Character ) 2640 { 2641 final char c = (Character) value; 2642 return new BindingValueModelImpl( name, 2643 BindingValueKind.CHAR, 2644 value, 2645 null, 2646 null, 2647 null, 2648 charLiteral( c ) ); 2649 } 2650 else if ( value instanceof TypeMirror ) 2651 { 2652 final String className = value.toString(); 2653 return new BindingValueModelImpl( name, 2654 BindingValueKind.CLASS, 2655 null, 2656 className, 2657 null, 2658 null, 2659 CodeBlock.of( "$S", className ).toString() ); 2660 } 2661 else if ( value instanceof final VariableElement enumValue ) 2662 { 2663 final var enumTypeName = ( (TypeElement) enumValue.getEnclosingElement() ).getQualifiedName().toString(); 2664 final var constantName = enumValue.getSimpleName().toString(); 2665 return new BindingValueModelImpl( name, 2666 BindingValueKind.ENUM, 2667 null, 2668 null, 2669 enumTypeName, 2670 constantName, 2671 CodeBlock.of( "$S", constantName ).toString() ); 2672 } 2673 else 2674 { 2675 return new BindingValueModelImpl( name, BindingValueKind.UNSUPPORTED, null, null, null, null, "<unsupported>" ); 2676 } 2677 } 2678 2679 @Nonnull 2680 private String charLiteral( final char c ) 2681 { 2682 return switch ( c ) 2683 { 2684 case '\b' -> "'\\b'"; 2685 case '\t' -> "'\\t'"; 2686 case '\n' -> "'\\n'"; 2687 case '\f' -> "'\\f'"; 2688 case '\r' -> "'\\r'"; 2689 case '"' -> "'\"'"; 2690 case '\'' -> "'\\''"; 2691 case '\\' -> "'\\\\'"; 2692 default -> Character.isISOControl( c ) ? 2693 String.format( "'\\u%04x'", (int) c ) : 2694 "'" + c + "'"; 2695 }; 2696 } 2697 2698 private void validateInterceptedService( @Nonnull final Binding binding, 2699 @Nonnull final ServiceSpec service, 2700 @Nullable final TypeElement serviceElement, 2701 @Nonnull final List<InterceptorBindingDescriptor> interceptors, 2702 @Nonnull final Element bindingSource ) 2703 { 2704 if ( Binding.Kind.INPUT == binding.getKind() ) 2705 { 2706 throw new ProcessorException( "Interceptor bindings on injector input services are not supported", 2707 binding.getElement() ); 2708 } 2709 else if ( service.isOptional() || binding.isOptional() ) 2710 { 2711 throw new ProcessorException( "Interceptor bindings on nullable or optional provider bindings are not supported", 2712 bindingSource ); 2713 } 2714 else if ( null == serviceElement ) 2715 { 2716 throw new ProcessorException( "Intercepted bindings must publish service interfaces only", bindingSource ); 2717 } 2718 else if ( Object.class.getName().equals( serviceElement.getQualifiedName().toString() ) ) 2719 { 2720 throw new ProcessorException( "Intercepted bindings must not publish java.lang.Object", bindingSource ); 2721 } 2722 else if ( ElementKind.INTERFACE != serviceElement.getKind() ) 2723 { 2724 throw new ProcessorException( "Intercepted bindings must publish service interfaces only", bindingSource ); 2725 } 2726 else if ( !serviceElement.getTypeParameters().isEmpty() ) 2727 { 2728 throw new ProcessorException( "Intercepted service interfaces must not declare type parameters", serviceElement ); 2729 } 2730 else 2731 { 2732 final var methods = 2733 ElementsUtil.getMethods( serviceElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 2734 for ( final var method : methods ) 2735 { 2736 if ( !method.getTypeParameters().isEmpty() ) 2737 { 2738 throw new ProcessorException( "Intercepted service methods must not declare type parameters", method ); 2739 } 2740 } 2741 final var types = new HashSet<String>(); 2742 final var priorities = new HashSet<Integer>(); 2743 for ( final var interceptor : interceptors ) 2744 { 2745 if ( !types.add( interceptor.annotationTypeName() ) ) 2746 { 2747 throw new ProcessorException( "Duplicate interceptor binding annotation type " + 2748 interceptor.annotationTypeName() + " for service " + 2749 service.getCoordinate(), 2750 interceptor.getUsageElement(), 2751 interceptor.getAnnotation() ); 2752 } 2753 else if ( !priorities.add( interceptor.priority() ) ) 2754 { 2755 throw new ProcessorException( "Duplicate interceptor priority " + interceptor.priority() + 2756 " for service " + service.getCoordinate(), 2757 interceptor.getUsageElement(), 2758 interceptor.getAnnotation() ); 2759 } 2760 } 2761 } 2762 } 2763 2764 private void validateNoMethodLevelInterceptorBindings( @Nonnull final Binding binding, 2765 @Nullable final TypeElement serviceElement, 2766 @Nonnull final Element bindingSource ) 2767 { 2768 if ( null != serviceElement ) 2769 { 2770 final var methods = 2771 ElementsUtil.getMethods( serviceElement, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 2772 for ( final var method : methods ) 2773 { 2774 if ( !findInterceptorBindingAnnotations( method ).isEmpty() ) 2775 { 2776 throw new ProcessorException( "Interceptor bindings on service interface methods are not supported", method ); 2777 } 2778 } 2779 } 2780 if ( Binding.Kind.INJECTABLE == binding.getKind() ) 2781 { 2782 for ( final var element : bindingSource.getEnclosedElements() ) 2783 { 2784 if ( ElementKind.METHOD == element.getKind() && !findInterceptorBindingAnnotations( element ).isEmpty() ) 2785 { 2786 throw new ProcessorException( "Interceptor bindings on implementation methods are not supported", element ); 2787 } 2788 } 2789 } 2790 } 2791 2792 private void resolvePluginClaims( @Nonnull final List<InterceptorBindingDescriptor> interceptors ) 2793 { 2794 for ( final var interceptor : interceptors ) 2795 { 2796 final var pluginIds = new ArrayList<String>(); 2797 for ( final var generator : _interceptorCodeGenerators ) 2798 { 2799 if ( generator.supports( interceptor ) ) 2800 { 2801 pluginIds.add( pluginId( generator ) ); 2802 } 2803 } 2804 final var pluginCount = pluginIds.size(); 2805 if ( pluginCount > 1 ) 2806 { 2807 interceptor.setConflict( pluginIds ); 2808 throw new ProcessorException( "Multiple interceptor plugins claim " + interceptor.annotationTypeName() + 2809 " for service " + interceptor.getService().getCoordinate() + ": " + 2810 String.join( ", ", interceptor.getPluginIds() ), 2811 interceptor.getUsageElement(), 2812 interceptor.getAnnotation() ); 2813 } 2814 else if ( 1 == pluginCount ) 2815 { 2816 interceptor.setClaimedBy( pluginIds.get( 0 ) ); 2817 } 2818 } 2819 } 2820 2821 @Nonnull 2822 private String pluginId( @Nonnull final InterceptorCodeGenerator generator ) 2823 { 2824 final var canonicalName = generator.getClass().getCanonicalName(); 2825 return null == canonicalName ? generator.getClass().getName() : canonicalName; 2826 } 2827 2828 private void resolveGenericInterceptors( @Nonnull final List<InterceptorBindingDescriptor> interceptors ) 2829 { 2830 for ( final var interceptor : interceptors ) 2831 { 2832 if ( InterceptorBindingDescriptor.ClaimState.CLAIMED != interceptor.getClaimState() ) 2833 { 2834 if ( interceptor.getImplementedBy().isEmpty() ) 2835 { 2836 throw new ProcessorException( "Interceptor binding " + interceptor.annotationTypeName() + 2837 " must specify implementedBy or be claimed by exactly one plugin", 2838 interceptor.getUsageElement(), 2839 interceptor.getAnnotation() ); 2840 } 2841 else 2842 { 2843 interceptor.setInterceptor( resolveGenericInterceptor( interceptor ) ); 2844 } 2845 } 2846 } 2847 } 2848 2849 @Nonnull 2850 private InterceptorDescriptor resolveGenericInterceptor( @Nonnull final InterceptorBindingDescriptor interceptor ) 2851 { 2852 final var classname = interceptor.getImplementedBy(); 2853 final var existing = _registry.findInterceptorByClassName( classname ); 2854 if ( null == existing ) 2855 { 2856 final var element = processingEnv.getElementUtils().getTypeElement( classname ); 2857 if ( null == element ) 2858 { 2859 throw new ProcessorException( "Interceptor implementation " + classname + " does not exist", 2860 interceptor.getUsageElement(), 2861 interceptor.getAnnotation() ); 2862 } 2863 else if ( !ElementsUtil.isEffectivelyPublic( element ) ) 2864 { 2865 throw new ProcessorException( "Interceptor implementation " + classname + " must be effectively public", 2866 element ); 2867 } 2868 else if ( !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 2869 { 2870 throw new ProcessorException( "Interceptor implementation " + classname + " must be annotated with " + 2871 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ), 2872 element ); 2873 } 2874 else 2875 { 2876 var injectable = _registry.findInjectableByClassName( classname ); 2877 if ( null == injectable ) 2878 { 2879 injectable = deriveInjectableDescriptor( element ); 2880 if ( null == injectable ) 2881 { 2882 throw new ProcessorException( "Unable to derive interceptor implementation " + classname, element ); 2883 } 2884 _registry.registerInjectable( injectable ); 2885 } 2886 final var descriptor = new InterceptorDescriptor( element, 2887 new EnumMap<>( validateLifecycleMethods( element, 2888 interceptor ) ), 2889 injectable.getBinding() ); 2890 _registry.registerInterceptor( descriptor ); 2891 return descriptor; 2892 } 2893 } 2894 else 2895 { 2896 return existing; 2897 } 2898 } 2899 2900 @Nonnull 2901 private Map<InterceptorPhase, InterceptorMethodDescriptor> validateLifecycleMethods( 2902 @Nonnull final TypeElement element, 2903 @Nonnull final InterceptorBindingDescriptor interceptor ) 2904 { 2905 final var methods = new EnumMap<InterceptorPhase, InterceptorMethodDescriptor>( InterceptorPhase.class ); 2906 for ( final var enclosedElement : element.getEnclosedElements() ) 2907 { 2908 if ( ElementKind.METHOD == enclosedElement.getKind() ) 2909 { 2910 final var method = (ExecutableElement) enclosedElement; 2911 final var phases = lifecyclePhases( method ); 2912 final var phaseCount = phases.size(); 2913 if ( phaseCount > 1 ) 2914 { 2915 throw new ProcessorException( "Interceptor lifecycle method must not have multiple lifecycle annotations", 2916 method ); 2917 } 2918 else if ( 1 == phaseCount ) 2919 { 2920 final var phase = phases.get( 0 ); 2921 validateLifecycleMethodShape( method ); 2922 final var descriptor = 2923 new InterceptorMethodDescriptor( phase, 2924 method, 2925 List.copyOf( validateLifecycleParameters( method, phase, interceptor ) ) ); 2926 if ( null != methods.put( phase, descriptor ) ) 2927 { 2928 throw new ProcessorException( "Interceptor implementation " + element.getQualifiedName() + 2929 " must declare at most one " + phase + " lifecycle method", 2930 method ); 2931 } 2932 } 2933 } 2934 } 2935 if ( methods.isEmpty() ) 2936 { 2937 throw new ProcessorException( "Interceptor implementation " + element.getQualifiedName() + 2938 " must declare at least one lifecycle method", 2939 element ); 2940 } 2941 validateNoInheritedLifecycleMethods( element ); 2942 return methods; 2943 } 2944 2945 @Nonnull 2946 private List<InterceptorPhase> lifecyclePhases( @Nonnull final ExecutableElement method ) 2947 { 2948 final var phases = new ArrayList<InterceptorPhase>(); 2949 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.INTERCEPTOR_BEFORE_CLASSNAME ) ) 2950 { 2951 phases.add( InterceptorPhase.BEFORE ); 2952 } 2953 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.INTERCEPTOR_AFTER_CLASSNAME ) ) 2954 { 2955 phases.add( InterceptorPhase.AFTER ); 2956 } 2957 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.INTERCEPTOR_AFTER_EXCEPTION_CLASSNAME ) ) 2958 { 2959 phases.add( InterceptorPhase.AFTER_EXCEPTION ); 2960 } 2961 return phases; 2962 } 2963 2964 private void validateLifecycleMethodShape( @Nonnull final ExecutableElement method ) 2965 { 2966 final var modifiers = method.getModifiers(); 2967 if ( !modifiers.contains( Modifier.PUBLIC ) || 2968 modifiers.contains( Modifier.STATIC ) || 2969 modifiers.contains( Modifier.PRIVATE ) || 2970 modifiers.contains( Modifier.PROTECTED ) ) 2971 { 2972 throw new ProcessorException( "Interceptor lifecycle methods must be public instance methods", method ); 2973 } 2974 else if ( TypeKind.VOID != method.getReturnType().getKind() ) 2975 { 2976 throw new ProcessorException( "Interceptor lifecycle methods must return void", method ); 2977 } 2978 else if ( !method.getTypeParameters().isEmpty() ) 2979 { 2980 throw new ProcessorException( "Interceptor lifecycle methods must not declare type parameters", method ); 2981 } 2982 else 2983 { 2984 for ( final var thrownType : method.getThrownTypes() ) 2985 { 2986 if ( !isUncheckedThrowable( thrownType ) ) 2987 { 2988 throw new ProcessorException( "Interceptor lifecycle methods must not declare checked exceptions", method ); 2989 } 2990 } 2991 } 2992 } 2993 2994 private boolean isUncheckedThrowable( @Nonnull final TypeMirror type ) 2995 { 2996 final var elementUtils = processingEnv.getElementUtils(); 2997 final var runtimeException = elementUtils.getTypeElement( RuntimeException.class.getName() ); 2998 final var error = elementUtils.getTypeElement( Error.class.getName() ); 2999 final var typeUtils = processingEnv.getTypeUtils(); 3000 return typeUtils.isAssignable( type, runtimeException.asType() ) || 3001 typeUtils.isAssignable( type, error.asType() ); 3002 } 3003 3004 private void validateNoInheritedLifecycleMethods( @Nonnull final TypeElement element ) 3005 { 3006 validateNoInheritedLifecycleMethods( element, element ); 3007 } 3008 3009 private void validateNoInheritedLifecycleMethods( @Nonnull final TypeElement element, 3010 @Nonnull final TypeElement type ) 3011 { 3012 for ( final var supertype : processingEnv.getTypeUtils().directSupertypes( type.asType() ) ) 3013 { 3014 if ( TypeKind.DECLARED == supertype.getKind() ) 3015 { 3016 final var superElement = (TypeElement) ( (DeclaredType) supertype ).asElement(); 3017 if ( Object.class.getName().equals( superElement.getQualifiedName().toString() ) ) 3018 { 3019 continue; 3020 } 3021 for ( final var member : superElement.getEnclosedElements() ) 3022 { 3023 if ( ElementKind.METHOD == member.getKind() ) 3024 { 3025 final var method = (ExecutableElement) member; 3026 if ( !lifecyclePhases( method ).isEmpty() ) 3027 { 3028 final var override = findDeclaredOverride( element, method ); 3029 if ( null == override || lifecyclePhases( override ).isEmpty() ) 3030 { 3031 throw new ProcessorException( "Inherited interceptor lifecycle annotations are not supported", 3032 element ); 3033 } 3034 } 3035 } 3036 } 3037 validateNoInheritedLifecycleMethods( element, superElement ); 3038 } 3039 } 3040 } 3041 3042 @Nullable 3043 private ExecutableElement findDeclaredOverride( @Nonnull final TypeElement element, 3044 @Nonnull final ExecutableElement inheritedMethod ) 3045 { 3046 for ( final var enclosedElement : element.getEnclosedElements() ) 3047 { 3048 if ( ElementKind.METHOD == enclosedElement.getKind() ) 3049 { 3050 final var method = (ExecutableElement) enclosedElement; 3051 if ( processingEnv.getElementUtils().overrides( method, inheritedMethod, element ) ) 3052 { 3053 return method; 3054 } 3055 } 3056 } 3057 return null; 3058 } 3059 3060 @Nonnull 3061 private List<LifecycleParameterDescriptor> validateLifecycleParameters( @Nonnull final ExecutableElement method, 3062 @Nonnull final InterceptorPhase phase, 3063 @Nonnull final InterceptorBindingDescriptor interceptor ) 3064 { 3065 final var parameters = new ArrayList<LifecycleParameterDescriptor>(); 3066 for ( final var parameter : method.getParameters() ) 3067 { 3068 parameters.add( validateLifecycleParameter( parameter, phase, interceptor ) ); 3069 } 3070 return parameters; 3071 } 3072 3073 @Nonnull 3074 private LifecycleParameterDescriptor validateLifecycleParameter( @Nonnull final VariableElement parameter, 3075 @Nonnull final InterceptorPhase phase, 3076 @Nonnull final InterceptorBindingDescriptor interceptor ) 3077 { 3078 final var markers = new ArrayList<AnnotationMirror>(); 3079 for ( final var annotation : parameter.getAnnotationMirrors() ) 3080 { 3081 if ( isLifecycleMarker( annotation ) ) 3082 { 3083 markers.add( annotation ); 3084 } 3085 } 3086 if ( markers.isEmpty() ) 3087 { 3088 throw new ProcessorException( "Interceptor lifecycle parameters must have exactly one marker annotation", 3089 parameter ); 3090 } 3091 else if ( markers.size() > 1 ) 3092 { 3093 throw new ProcessorException( "Interceptor lifecycle parameters must not have multiple marker annotations", 3094 parameter ); 3095 } 3096 else 3097 { 3098 final var marker = markers.get( 0 ); 3099 final var markerType = ( (TypeElement) marker.getAnnotationType().asElement() ).getQualifiedName().toString(); 3100 final var type = parameter.asType(); 3101 if ( Constants.INTERCEPTOR_SERVICE_TYPE_CLASSNAME.equals( markerType ) ) 3102 { 3103 requireType( parameter, type, String.class.getName(), "@ServiceType" ); 3104 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.SERVICE_TYPE, "" ); 3105 } 3106 else if ( Constants.INTERCEPTOR_METHOD_NAME_CLASSNAME.equals( markerType ) ) 3107 { 3108 requireType( parameter, type, String.class.getName(), "@MethodName" ); 3109 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.METHOD_NAME, "" ); 3110 } 3111 else if ( Constants.INTERCEPTOR_ARGUMENTS_CLASSNAME.equals( markerType ) ) 3112 { 3113 if ( TypeKind.ARRAY != type.getKind() || !"java.lang.Object[]".equals( type.toString() ) ) 3114 { 3115 throw new ProcessorException( "@Arguments lifecycle parameter must have type Object[]", parameter ); 3116 } 3117 else 3118 { 3119 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.ARGUMENTS, "" ); 3120 } 3121 } 3122 else if ( Constants.INTERCEPTOR_RESULT_CLASSNAME.equals( markerType ) ) 3123 { 3124 if ( InterceptorPhase.AFTER != phase ) 3125 { 3126 throw new ProcessorException( "@Result lifecycle parameter is only valid on @After methods", parameter ); 3127 } 3128 else 3129 { 3130 requireType( parameter, type, Object.class.getName(), "@Result" ); 3131 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.RESULT, "" ); 3132 } 3133 } 3134 else if ( Constants.INTERCEPTOR_THROWN_CLASSNAME.equals( markerType ) ) 3135 { 3136 if ( InterceptorPhase.AFTER_EXCEPTION != phase ) 3137 { 3138 throw new ProcessorException( "@Thrown lifecycle parameter is only valid on @AfterException methods", 3139 parameter ); 3140 } 3141 else 3142 { 3143 requireType( parameter, type, Throwable.class.getName(), "@Thrown" ); 3144 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.THROWN, "" ); 3145 } 3146 } 3147 else 3148 { 3149 assert Constants.INTERCEPTOR_BINDING_VALUE_CLASSNAME.equals( markerType ); 3150 final String name = AnnotationsUtil.getAnnotationValueValue( marker, "value" ); 3151 validateBindingValueParameter( parameter, type, interceptor, name ); 3152 return new LifecycleParameterDescriptor( LifecycleParameterDescriptor.Kind.BINDING_VALUE, name ); 3153 } 3154 } 3155 } 3156 3157 private boolean isLifecycleMarker( @Nonnull final AnnotationMirror annotation ) 3158 { 3159 final var classname = ( (TypeElement) annotation.getAnnotationType().asElement() ).getQualifiedName().toString(); 3160 return Constants.INTERCEPTOR_SERVICE_TYPE_CLASSNAME.equals( classname ) || 3161 Constants.INTERCEPTOR_METHOD_NAME_CLASSNAME.equals( classname ) || 3162 Constants.INTERCEPTOR_BINDING_VALUE_CLASSNAME.equals( classname ) || 3163 Constants.INTERCEPTOR_ARGUMENTS_CLASSNAME.equals( classname ) || 3164 Constants.INTERCEPTOR_RESULT_CLASSNAME.equals( classname ) || 3165 Constants.INTERCEPTOR_THROWN_CLASSNAME.equals( classname ); 3166 } 3167 3168 private void requireType( @Nonnull final Element element, 3169 @Nonnull final TypeMirror actualType, 3170 @Nonnull final String expectedTypeName, 3171 @Nonnull final String markerName ) 3172 { 3173 final var expected = processingEnv.getElementUtils().getTypeElement( expectedTypeName ); 3174 if ( null == expected || !processingEnv.getTypeUtils().isSameType( actualType, expected.asType() ) ) 3175 { 3176 throw new ProcessorException( markerName + " lifecycle parameter must have type " + expectedTypeName, element ); 3177 } 3178 } 3179 3180 private void validateBindingValueParameter( @Nonnull final VariableElement parameter, 3181 @Nonnull final TypeMirror type, 3182 @Nonnull final InterceptorBindingDescriptor interceptor, 3183 @Nonnull final String name ) 3184 { 3185 final var value = interceptor.values().get( name ); 3186 if ( null == value ) 3187 { 3188 throw new ProcessorException( "@BindingValue references unknown interceptor binding member " + name, parameter ); 3189 } 3190 else if ( BindingValueKind.UNSUPPORTED == value.kind() ) 3191 { 3192 throw new ProcessorException( "@BindingValue member " + name + " has an unsupported v1 value type", parameter ); 3193 } 3194 else if ( BindingValueKind.STRING == value.kind() || 3195 BindingValueKind.ENUM == value.kind() || 3196 BindingValueKind.CLASS == value.kind() ) 3197 { 3198 requireType( parameter, type, String.class.getName(), "@BindingValue" ); 3199 } 3200 else if ( !isPrimitiveOrBoxedMatch( type, value.kind() ) ) 3201 { 3202 throw new ProcessorException( "@BindingValue member " + name + 3203 " is not compatible with lifecycle parameter type " + type, 3204 parameter ); 3205 } 3206 } 3207 3208 private boolean isPrimitiveOrBoxedMatch( @Nonnull final TypeMirror type, @Nonnull final BindingValueKind kind ) 3209 { 3210 final var name = type.toString(); 3211 return switch ( kind ) 3212 { 3213 case BOOLEAN -> "boolean".equals( name ) || Boolean.class.getName().equals( name ); 3214 case BYTE -> "byte".equals( name ) || Byte.class.getName().equals( name ); 3215 case SHORT -> "short".equals( name ) || Short.class.getName().equals( name ); 3216 case INT -> "int".equals( name ) || Integer.class.getName().equals( name ); 3217 case LONG -> "long".equals( name ) || Long.class.getName().equals( name ); 3218 case FLOAT -> "float".equals( name ) || Float.class.getName().equals( name ); 3219 case DOUBLE -> "double".equals( name ) || Double.class.getName().equals( name ); 3220 case CHAR -> "char".equals( name ) || Character.class.getName().equals( name ); 3221 default -> false; 3222 }; 3223 } 3224 3225 private void validateImplementedByName( @Nonnull final String name, 3226 @Nonnull final Element element, 3227 @Nonnull final AnnotationMirror annotation ) 3228 { 3229 if ( name.contains( "$" ) || !isDottedJavaName( name ) ) 3230 { 3231 throw new ProcessorException( "implementedBy must be a canonical dotted qualified Java name", 3232 element, 3233 annotation ); 3234 } 3235 } 3236 3237 private boolean isDottedJavaName( @Nonnull final String name ) 3238 { 3239 if ( name.isEmpty() || name.startsWith( "." ) || name.endsWith( "." ) || name.contains( ".." ) ) 3240 { 3241 return false; 3242 } 3243 else 3244 { 3245 for ( final var part : name.split( "\\." ) ) 3246 { 3247 if ( part.isEmpty() || !SourceVersion.isIdentifier( part ) || SourceVersion.isKeyword( part ) ) 3248 { 3249 return false; 3250 } 3251 } 3252 return true; 3253 } 3254 } 3255 3256 @Nonnull 3257 private ServiceRequest processFragmentServiceParameter( @Nonnull final VariableElement parameter, 3258 @Nonnull final TypeMirror type ) 3259 { 3260 if ( TypesUtil.containsArrayType( type ) ) 3261 { 3262 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3263 "contain a method with a parameter that contains an array type" ), 3264 parameter ); 3265 } 3266 else if ( TypesUtil.containsWildcard( type ) ) 3267 { 3268 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3269 "contain a method with a parameter that contains a wildcard type parameter" ), 3270 parameter ); 3271 } 3272 else if ( TypesUtil.containsRawType( type ) ) 3273 { 3274 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3275 "contain a method with a parameter that contains a raw type" ), 3276 parameter ); 3277 } 3278 else 3279 { 3280 TypeMirror dependencyType = null; 3281 ServiceRequest.Kind kind = null; 3282 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 3283 { 3284 dependencyType = candidate.extractType( type ); 3285 if ( null != dependencyType ) 3286 { 3287 kind = candidate; 3288 break; 3289 } 3290 } 3291 if ( null == kind ) 3292 { 3293 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3294 "contain a method with a parameter that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 3295 parameter ); 3296 } 3297 else 3298 { 3299 final boolean optional = AnnotationsUtil.hasNullableAnnotation( parameter ); 3300 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 3301 { 3302 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3303 "contain a method with a parameter annotated with the " + 3304 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 3305 " annotation that is not an instance dependency kind" ), 3306 parameter ); 3307 } 3308 final String qualifier = getQualifier( parameter ); 3309 if ( AnnotationsUtil.hasAnnotationOfType( parameter, Constants.JSR_330_NAMED_CLASSNAME ) ) 3310 { 3311 final String message = 3312 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 3313 "contain a method with a parameter annotated with the " + 3314 Constants.JSR_330_NAMED_CLASSNAME + " annotation. Use the " + 3315 Constants.NAMED_CLASSNAME + " annotation instead" ); 3316 throw new ProcessorException( message, parameter ); 3317 } 3318 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 3319 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 3320 return new ServiceRequest( kind, service, parameter ); 3321 } 3322 } 3323 } 3324 3325 private void processInjectable( @Nonnull final TypeElement element ) 3326 { 3327 debug( () -> "Processing Injectable: " + element ); 3328 if ( ElementKind.CLASS != element.getKind() ) 3329 { 3330 throw new ProcessorException( MemberChecks.must( Constants.INJECTABLE_CLASSNAME, "be a class" ), 3331 element ); 3332 } 3333 else if ( element.getModifiers().contains( Modifier.ABSTRACT ) ) 3334 { 3335 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, "be abstract" ), 3336 element ); 3337 } 3338 else if ( ElementsUtil.isNonStaticNestedClass( element ) ) 3339 { 3340 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3341 "be a non-static nested class" ), 3342 element ); 3343 } 3344 else if ( !element.getTypeParameters().isEmpty() ) 3345 { 3346 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, "have type parameters" ), 3347 element ); 3348 } 3349 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( element ); 3350 final ExecutableElement constructor = constructors.get( 0 ); 3351 if ( constructors.size() > 1 ) 3352 { 3353 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3354 "have multiple constructors" ), 3355 element ); 3356 } 3357 injectableConstructorShouldNotBeProtected( constructor ); 3358 injectableConstructorShouldNotBePublic( constructor ); 3359 injectableShouldNotHaveScopedAnnotation( element ); 3360 3361 final boolean eager = AnnotationsUtil.hasAnnotationOfType( element, Constants.EAGER_CLASSNAME ); 3362 3363 final List<ServiceRequest> dependencies = new ArrayList<>(); 3364 int index = 0; 3365 final List<? extends TypeMirror> parameterTypes = ( (ExecutableType) constructor.asType() ).getParameterTypes(); 3366 for ( final VariableElement parameter : constructor.getParameters() ) 3367 { 3368 dependencies.add( handleConstructorParameter( parameter, parameterTypes.get( index ) ) ); 3369 index++; 3370 } 3371 3372 final AnnotationMirror annotation = 3373 AnnotationsUtil.findAnnotationByType( element, Constants.TYPED_CLASSNAME ); 3374 final AnnotationValue value = 3375 null != annotation ? AnnotationsUtil.findAnnotationValue( annotation, "value" ) : null; 3376 3377 final String qualifier = getQualifier( element ); 3378 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.JSR_330_NAMED_CLASSNAME ) && 3379 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_JSR_330_NAMED ) ) 3380 { 3381 final String message = 3382 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3383 "be annotated with the " + Constants.JSR_330_NAMED_CLASSNAME + " annotation. " + 3384 "Use the " + Constants.NAMED_CLASSNAME + " annotation instead. " + 3385 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_NAMED ) ); 3386 warning( message, element ); 3387 } 3388 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.CDI_TYPED_CLASSNAME ) && 3389 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_CDI_TYPED ) ) 3390 { 3391 final String message = 3392 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3393 "be annotated with the " + Constants.CDI_TYPED_CLASSNAME + " annotation. " + 3394 "Use the " + Constants.TYPED_CLASSNAME + " annotation instead. " + 3395 MemberChecks.suppressedBy( Constants.WARNING_CDI_TYPED ) ); 3396 warning( message, element ); 3397 } 3398 if ( AnnotationsUtil.hasAnnotationOfType( constructor, Constants.JSR_330_INJECT_CLASSNAME ) && 3399 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_JSR_330_INJECT ) ) 3400 { 3401 final String message = 3402 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3403 "be annotated with the " + Constants.JSR_330_INJECT_CLASSNAME + " annotation. " + 3404 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_INJECT ) ); 3405 warning( message, constructor ); 3406 } 3407 @SuppressWarnings( "unchecked" ) 3408 final List<TypeMirror> types = 3409 null == value ? 3410 Collections.singletonList( element.asType() ) : 3411 ( (List<AnnotationValue>) value.getValue() ) 3412 .stream() 3413 .map( v -> (TypeMirror) v.getValue() ) 3414 .toList(); 3415 3416 final ServiceSpec[] specs = new ServiceSpec[ types.size() ]; 3417 for ( int i = 0; i < specs.length; i++ ) 3418 { 3419 final TypeMirror type = types.get( i ); 3420 if ( !processingEnv.getTypeUtils().isAssignable( element.asType(), type ) ) 3421 { 3422 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3423 " specified a type that is not assignable to the declaring type", 3424 element, 3425 annotation, 3426 value ); 3427 } 3428 else if ( TypeKind.DECLARED == type.getKind() && isParameterized( (DeclaredType) type ) ) 3429 { 3430 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3431 " specified a type that is a a parameterized type", 3432 element, 3433 annotation, 3434 value ); 3435 } 3436 specs[ i ] = new ServiceSpec( new Coordinate( qualifier, type ), false ); 3437 } 3438 3439 if ( 0 == specs.length && !eager ) 3440 { 3441 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3442 "specify zero types with the " + 3443 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3444 " annotation or must be annotated with the " + 3445 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + 3446 " annotation otherwise the component can not be created by the injector" ), 3447 element ); 3448 } 3449 if ( 0 == specs.length && !qualifier.isEmpty() ) 3450 { 3451 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3452 "specify zero types with the " + 3453 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3454 " annotation and specify a qualifier with the " + 3455 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + 3456 " annotation as the qualifier is meaningless" ), 3457 element ); 3458 } 3459 3460 final Binding binding = 3461 new Binding( Binding.Kind.INJECTABLE, 3462 element.getQualifiedName().toString(), 3463 Arrays.asList( specs ), 3464 eager, 3465 constructor, 3466 dependencies.toArray( new ServiceRequest[ 0 ] ) ); 3467 binding.setInterceptorBindingSource( element, findInterceptorBindingAnnotationValues( element ) ); 3468 final InjectableDescriptor injectable = new InjectableDescriptor( binding ); 3469 _registry.registerInjectable( injectable ); 3470 } 3471 3472 @Nonnull 3473 private FactoryMethodDescriptor processFactoryMethod( @Nonnull final TypeElement factory, 3474 @Nonnull final ExecutableElement method, 3475 @Nonnull final List<FactoryDependencyDescriptor> dependencies, 3476 @Nonnull final Set<String> usedFieldNames ) 3477 { 3478 if ( !findInterceptorBindingAnnotations( method ).isEmpty() ) 3479 { 3480 throw new ProcessorException( "Interceptor bindings on non-fragment methods are not supported", 3481 method ); 3482 } 3483 if ( TypeKind.VOID == method.getReturnType().getKind() ) 3484 { 3485 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3486 "only contain abstract methods that return a value" ), 3487 method ); 3488 } 3489 else if ( !method.getTypeParameters().isEmpty() ) 3490 { 3491 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3492 "contain abstract methods with a type parameter" ), 3493 method ); 3494 } 3495 else if ( !method.getThrownTypes().isEmpty() ) 3496 { 3497 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3498 "contain abstract methods that throw exceptions" ), 3499 method ); 3500 } 3501 else if ( TypeKind.DECLARED != method.getReturnType().getKind() ) 3502 { 3503 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3504 "contain abstract methods that return a class type" ), 3505 method ); 3506 } 3507 3508 final DeclaredType returnType = (DeclaredType) method.getReturnType(); 3509 if ( !returnType.getTypeArguments().isEmpty() ) 3510 { 3511 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3512 "contain abstract methods that return a parameterized type" ), 3513 method ); 3514 } 3515 3516 final TypeElement producedType = (TypeElement) returnType.asElement(); 3517 if ( ElementKind.CLASS != producedType.getKind() ) 3518 { 3519 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3520 "contain abstract methods that return a class type" ), 3521 method ); 3522 } 3523 else if ( producedType.getModifiers().contains( Modifier.ABSTRACT ) ) 3524 { 3525 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3526 "contain abstract methods that return an abstract class" ), 3527 method ); 3528 } 3529 else if ( ElementsUtil.isNonStaticNestedClass( producedType ) ) 3530 { 3531 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3532 "contain abstract methods that return a non-static nested class" ), 3533 method ); 3534 } 3535 else if ( !producedType.getTypeParameters().isEmpty() ) 3536 { 3537 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3538 "contain abstract methods that return a class with type parameters" ), 3539 method ); 3540 } 3541 3542 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( producedType ); 3543 if ( 1 != constructors.size() ) 3544 { 3545 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3546 "create types with a single accessible constructor" ), 3547 method ); 3548 } 3549 final ExecutableElement constructor = constructors.get( 0 ); 3550 if ( !isFactoryAccessible( factory, constructor ) ) 3551 { 3552 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3553 "create types with a constructor accessible from the factory interface" ), 3554 method ); 3555 } 3556 3557 final List<? extends VariableElement> constructorParameters = constructor.getParameters(); 3558 final List<? extends VariableElement> methodParameters = method.getParameters(); 3559 final Map<Integer, VariableElement> methodParametersByConstructorIndex = new LinkedHashMap<>(); 3560 final Map<Integer, FactoryDependencyDescriptor> dependenciesByConstructorIndex = new LinkedHashMap<>(); 3561 final List<? extends TypeMirror> constructorParameterTypes = 3562 ( (ExecutableType) constructor.asType() ).getParameterTypes(); 3563 3564 for ( final VariableElement parameter : methodParameters ) 3565 { 3566 boolean matched = false; 3567 for ( int i = 0; i < constructorParameters.size(); i++ ) 3568 { 3569 final VariableElement constructorParameter = constructorParameters.get( i ); 3570 if ( constructorParameter.getSimpleName().contentEquals( parameter.getSimpleName() ) ) 3571 { 3572 matched = true; 3573 if ( methodParametersByConstructorIndex.containsKey( i ) ) 3574 { 3575 throw new ProcessorException( MemberChecks.mustNot( Constants.FACTORY_CLASSNAME, 3576 "contain duplicate abstract method parameters that " + 3577 "match the same constructor parameter" ), 3578 parameter ); 3579 } 3580 if ( !processingEnv.getTypeUtils().isSameType( parameter.asType(), constructorParameterTypes.get( i ) ) ) 3581 { 3582 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3583 "contain abstract method parameters whose name and type " + 3584 "match the created type constructor parameter" ), 3585 parameter ); 3586 } 3587 methodParametersByConstructorIndex.put( i, parameter ); 3588 break; 3589 } 3590 } 3591 if ( !matched ) 3592 { 3593 throw new ProcessorException( MemberChecks.must( Constants.FACTORY_CLASSNAME, 3594 "contain abstract method parameters whose name and type " + 3595 "match the created type constructor parameter" ), 3596 parameter ); 3597 } 3598 } 3599 3600 for ( int i = 0; i < constructorParameters.size(); i++ ) 3601 { 3602 if ( !methodParametersByConstructorIndex.containsKey( i ) ) 3603 { 3604 final VariableElement constructorParameter = constructorParameters.get( i ); 3605 final ServiceRequest request = 3606 handleConstructorParameter( constructorParameter, constructorParameterTypes.get( i ) ); 3607 final FactoryDependencyDescriptor dependency = 3608 findOrCreateFactoryDependency( request, dependencies, usedFieldNames ); 3609 dependenciesByConstructorIndex.put( i, dependency ); 3610 } 3611 } 3612 3613 return new FactoryMethodDescriptor( method, 3614 producedType.asType(), 3615 constructor, 3616 constructorParameters, 3617 methodParametersByConstructorIndex, 3618 dependenciesByConstructorIndex ); 3619 } 3620 3621 @Nonnull 3622 private FactoryDependencyDescriptor findOrCreateFactoryDependency( @Nonnull final ServiceRequest request, 3623 @Nonnull final List<FactoryDependencyDescriptor> dependencies, 3624 @Nonnull final Set<String> usedFieldNames ) 3625 { 3626 for ( final FactoryDependencyDescriptor existing : dependencies ) 3627 { 3628 if ( existing.matches( request ) ) 3629 { 3630 return existing; 3631 } 3632 } 3633 3634 final VariableElement parameter = (VariableElement) request.getElement(); 3635 final String parameterName = parameter.getSimpleName().toString(); 3636 final String fieldName = uniqueFactoryFieldName( parameterName, usedFieldNames ); 3637 final FactoryDependencyDescriptor dependency = 3638 new FactoryDependencyDescriptor( request, parameterName, fieldName ); 3639 dependencies.add( dependency ); 3640 return dependency; 3641 } 3642 3643 @Nonnull 3644 private String uniqueFactoryFieldName( @Nonnull final String parameterName, 3645 @Nonnull final Set<String> usedFieldNames ) 3646 { 3647 final String baseName = StingGeneratorUtil.FRAMEWORK_PREFIX + parameterName; 3648 String candidate = baseName; 3649 int index = 2; 3650 while ( !usedFieldNames.add( candidate ) ) 3651 { 3652 candidate = baseName + index; 3653 index++; 3654 } 3655 return candidate; 3656 } 3657 3658 private boolean isFactoryAccessible( @Nonnull final TypeElement factory, 3659 @Nonnull final ExecutableElement constructor ) 3660 { 3661 final Set<Modifier> modifiers = constructor.getModifiers(); 3662 if ( modifiers.contains( Modifier.PRIVATE ) ) 3663 { 3664 return false; 3665 } 3666 else if ( modifiers.contains( Modifier.PUBLIC ) ) 3667 { 3668 return true; 3669 } 3670 else 3671 { 3672 final String factoryPackage = GeneratorUtil.getQualifiedPackageName( factory ); 3673 final String targetPackage = 3674 GeneratorUtil.getQualifiedPackageName( (TypeElement) constructor.getEnclosingElement() ); 3675 return factoryPackage.equals( targetPackage ); 3676 } 3677 } 3678 3679 // Binary descriptor writing and verification removed 3680 3681 private void emitInjectableJsonDescriptor( @Nonnull final InjectableDescriptor injectable ) 3682 throws IOException 3683 { 3684 if ( _emitJsonDescriptors ) 3685 { 3686 final TypeElement element = injectable.getElement(); 3687 final String filename = toFilename( element ) + JSON_SUFFIX; 3688 JsonUtil.writeJsonResource( processingEnv, element, filename, injectable::write ); 3689 } 3690 } 3691 3692 @Nonnull 3693 private String toFilename( @Nonnull final TypeElement typeElement ) 3694 { 3695 return GeneratorUtil.getGeneratedClassName( typeElement, "", "" ).toString().replace( ".", "/" ); 3696 } 3697 3698 @Nonnull 3699 private ServiceRequest handleConstructorParameter( @Nonnull final VariableElement parameter, 3700 @Nonnull final TypeMirror type ) 3701 { 3702 if ( TypesUtil.containsArrayType( type ) ) 3703 { 3704 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3705 "contain a constructor with a parameter that contains an array type" ), 3706 parameter ); 3707 } 3708 else if ( TypesUtil.containsWildcard( type ) ) 3709 { 3710 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3711 "contain a constructor with a parameter that contains a wildcard type parameter" ), 3712 parameter ); 3713 } 3714 else if ( TypesUtil.containsRawType( type ) ) 3715 { 3716 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3717 "contain a constructor with a parameter that contains a raw type" ), 3718 parameter ); 3719 } 3720 else 3721 { 3722 TypeMirror dependencyType = null; 3723 ServiceRequest.Kind kind = null; 3724 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 3725 { 3726 dependencyType = candidate.extractType( type ); 3727 if ( null != dependencyType ) 3728 { 3729 kind = candidate; 3730 break; 3731 } 3732 } 3733 if ( null == kind ) 3734 { 3735 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3736 "contain a constructor with a parameter that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 3737 parameter ); 3738 } 3739 else 3740 { 3741 final boolean optional = AnnotationsUtil.hasNullableAnnotation( parameter ); 3742 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 3743 { 3744 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3745 "contain a constructor with a parameter annotated with " + 3746 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 3747 " that is not an instance dependency kind" ), 3748 parameter ); 3749 } 3750 final String qualifier = getQualifier( parameter ); 3751 if ( AnnotationsUtil.hasAnnotationOfType( parameter, Constants.JSR_330_NAMED_CLASSNAME ) && 3752 ElementsUtil.isWarningNotSuppressed( parameter, Constants.WARNING_JSR_330_NAMED ) ) 3753 { 3754 final String message = 3755 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3756 "contain a constructor with a parameter annotated with the " + 3757 Constants.JSR_330_NAMED_CLASSNAME + " annotation. Use the " + 3758 Constants.NAMED_CLASSNAME + " annotation instead. " + 3759 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_NAMED ) ); 3760 warning( message, parameter ); 3761 } 3762 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 3763 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 3764 return new ServiceRequest( kind, service, parameter ); 3765 } 3766 } 3767 } 3768 3769 @Nonnull 3770 private String getQualifier( @Nonnull final Element element ) 3771 { 3772 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( element, Constants.NAMED_CLASSNAME ); 3773 return null == annotation ? "" : AnnotationsUtil.getAnnotationValueValue( annotation, "value" ); 3774 } 3775 3776 private void injectableShouldNotHaveScopedAnnotation( @Nonnull final TypeElement element ) 3777 { 3778 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 3779 if ( !scopedAnnotations.isEmpty() && 3780 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_JSR_330_SCOPED ) ) 3781 { 3782 final String message = 3783 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 3784 "be annotated with an annotation that is annotated with the " + 3785 Constants.JSR_330_SCOPE_CLASSNAME + " annotation such as " + scopedAnnotations + ". " + 3786 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_SCOPED ) ); 3787 warning( message, element ); 3788 } 3789 } 3790 3791 private void injectableConstructorShouldNotBePublic( @Nonnull final ExecutableElement constructor ) 3792 { 3793 if ( Elements.Origin.EXPLICIT == processingEnv.getElementUtils().getOrigin( constructor ) && 3794 constructor.getModifiers().contains( Modifier.PUBLIC ) && 3795 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_PUBLIC_CONSTRUCTOR ) ) 3796 { 3797 final String message = 3798 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 3799 "have a public constructor. The type is instantiated by the injector " + 3800 "and should have a package-access constructor. " + 3801 MemberChecks.suppressedBy( Constants.WARNING_PUBLIC_CONSTRUCTOR ) ); 3802 warning( message, constructor ); 3803 } 3804 } 3805 3806 private void injectableConstructorShouldNotBeProtected( @Nonnull final ExecutableElement constructor ) 3807 { 3808 if ( constructor.getModifiers().contains( Modifier.PROTECTED ) && 3809 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_PROTECTED_CONSTRUCTOR ) ) 3810 { 3811 final String message = 3812 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 3813 "have a protected constructor. The type is instantiated by the " + 3814 "injector and should have a package-access constructor. " + 3815 MemberChecks.suppressedBy( Constants.WARNING_PROTECTED_CONSTRUCTOR ) ); 3816 warning( message, constructor ); 3817 } 3818 } 3819 3820 private boolean isParameterized( @Nonnull final DeclaredType nestedParameterType ) 3821 { 3822 return !( (TypeElement) nestedParameterType.asElement() ).getTypeParameters().isEmpty(); 3823 } 3824 3825 @Nonnull 3826 private List<? extends AnnotationMirror> getScopedAnnotations( @Nonnull final Element element ) 3827 { 3828 return element 3829 .getAnnotationMirrors() 3830 .stream() 3831 .filter( a -> AnnotationsUtil.hasAnnotationOfType( a.getAnnotationType().asElement(), 3832 Constants.JSR_330_SCOPE_CLASSNAME ) ) 3833 .collect( Collectors.toList() ); 3834 } 3835 3836 @Nullable 3837 private FragmentDescriptor deriveFragmentDescriptor( @Nonnull final TypeElement element ) 3838 { 3839 final String classname = element.getQualifiedName().toString(); 3840 final FragmentDescriptor cached = _derivedFragmentCache.get( classname ); 3841 if ( null != cached ) 3842 { 3843 return cached; 3844 } 3845 // Only derive for proper @Fragment types 3846 else if ( ElementKind.INTERFACE != element.getKind() || 3847 !AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 3848 { 3849 return null; 3850 } 3851 else if ( !SuperficialValidation.validateElement( processingEnv, element ) ) 3852 { 3853 return null; 3854 } 3855 else 3856 { 3857 final boolean localOnly = extractFragmentLocalOnly( element ); 3858 final List<IncludeDescriptor> includes = extractIncludes( element, Constants.FRAGMENT_CLASSNAME, false ); 3859 final Map<ExecutableElement, Binding> bindings = new LinkedHashMap<>(); 3860 for ( final Element enclosedElement : element.getEnclosedElements() ) 3861 { 3862 if ( ElementKind.METHOD == enclosedElement.getKind() ) 3863 { 3864 processProvidesMethod( element, bindings, (ExecutableElement) enclosedElement ); 3865 } 3866 } 3867 final FragmentDescriptor fragment = new FragmentDescriptor( element, includes, localOnly, bindings.values() ); 3868 fragment.markJavaStubAsGenerated(); 3869 _derivedFragmentCache.put( classname, fragment ); 3870 return fragment; 3871 } 3872 } 3873 3874 private boolean extractFragmentLocalOnly( @Nonnull final TypeElement element ) 3875 { 3876 return (boolean) AnnotationsUtil.getAnnotationValue( element, Constants.FRAGMENT_CLASSNAME, "localOnly" ) 3877 .getValue(); 3878 } 3879 3880 private boolean isInSamePackage( @Nonnull final TypeElement type1, @Nonnull final TypeElement type2 ) 3881 { 3882 return GeneratorUtil.getQualifiedPackageName( type1 ).equals( GeneratorUtil.getQualifiedPackageName( type2 ) ); 3883 } 3884 3885 @Nullable 3886 private InjectableDescriptor deriveInjectableDescriptor( @Nonnull final TypeElement element ) 3887 { 3888 final String classname = element.getQualifiedName().toString(); 3889 final InjectableDescriptor cached = _derivedInjectableCache.get( classname ); 3890 if ( null != cached ) 3891 { 3892 return cached; 3893 } 3894 // Only derive for proper @Injectable types 3895 if ( ElementKind.CLASS != element.getKind() || 3896 !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 3897 { 3898 return null; 3899 } 3900 3901 if ( element.getModifiers().contains( Modifier.ABSTRACT ) || 3902 ElementsUtil.isNonStaticNestedClass( element ) || 3903 !element.getTypeParameters().isEmpty() ) 3904 { 3905 // Invalid injectable; let normal processing flag this if encountered as source. 3906 return null; 3907 } 3908 3909 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( element ); 3910 if ( constructors.isEmpty() ) 3911 { 3912 return null; 3913 } 3914 final ExecutableElement constructor = constructors.get( 0 ); 3915 if ( constructors.size() > 1 ) 3916 { 3917 return null; 3918 } 3919 3920 final boolean eager = AnnotationsUtil.hasAnnotationOfType( element, Constants.EAGER_CLASSNAME ); 3921 3922 final List<ServiceRequest> dependencies = new ArrayList<>(); 3923 int index = 0; 3924 final List<? extends TypeMirror> parameterTypes = ( (ExecutableType) constructor.asType() ).getParameterTypes(); 3925 for ( final VariableElement parameter : constructor.getParameters() ) 3926 { 3927 dependencies.add( handleConstructorParameter( parameter, parameterTypes.get( index ) ) ); 3928 index++; 3929 } 3930 3931 final AnnotationMirror annotation = 3932 AnnotationsUtil.findAnnotationByType( element, Constants.TYPED_CLASSNAME ); 3933 final AnnotationValue value = 3934 null != annotation ? AnnotationsUtil.findAnnotationValue( annotation, "value" ) : null; 3935 3936 final String qualifier = getQualifier( element ); 3937 3938 @SuppressWarnings( "unchecked" ) 3939 final List<TypeMirror> types = 3940 null == value ? 3941 Collections.singletonList( element.asType() ) : 3942 ( (List<AnnotationValue>) value.getValue() ) 3943 .stream() 3944 .map( v -> (TypeMirror) v.getValue() ) 3945 .toList(); 3946 3947 final ServiceSpec[] specs = new ServiceSpec[ types.size() ]; 3948 for ( int i = 0; i < specs.length; i++ ) 3949 { 3950 final TypeMirror type = types.get( i ); 3951 if ( !processingEnv.getTypeUtils().isAssignable( element.asType(), type ) ) 3952 { 3953 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3954 " specified a type that is not assignable to the declaring type", 3955 element, 3956 annotation, 3957 value ); 3958 } 3959 else if ( TypeKind.DECLARED == type.getKind() && isParameterized( (DeclaredType) type ) ) 3960 { 3961 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3962 " specified a type that is a a parameterized type", 3963 element, 3964 annotation, 3965 value ); 3966 } 3967 specs[ i ] = new ServiceSpec( new Coordinate( qualifier, type ), false ); 3968 } 3969 3970 if ( 0 == specs.length && !eager ) 3971 { 3972 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3973 "specify zero types with the " + 3974 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3975 " annotation or must be annotated with the " + 3976 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + 3977 " annotation otherwise the component can not be created by the injector" ), 3978 element ); 3979 } 3980 if ( 0 == specs.length && !qualifier.isEmpty() ) 3981 { 3982 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 3983 "specify zero types with the " + 3984 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 3985 " annotation and specify a qualifier with the " + 3986 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + 3987 " annotation as the qualifier is meaningless" ), 3988 element ); 3989 } 3990 3991 final Binding binding = 3992 new Binding( Binding.Kind.INJECTABLE, 3993 element.getQualifiedName().toString(), 3994 Arrays.asList( specs ), 3995 eager, 3996 constructor, 3997 dependencies.toArray( new ServiceRequest[ 0 ] ) ); 3998 final InjectableDescriptor injectable = new InjectableDescriptor( binding ); 3999 injectable.markJavaStubAsGenerated(); 4000 _derivedInjectableCache.put( classname, injectable ); 4001 return injectable; 4002 } 4003 4004 private void maybeWarnOnRedundantDirectInjectableInclude( @Nonnull final TypeElement originator, 4005 @Nonnull final String annotationClassname, 4006 @Nonnull final Collection<IncludeDescriptor> includes ) 4007 { 4008 if ( !ElementsUtil.isWarningNotSuppressed( originator, 4009 Constants.WARNING_REDUNDANT_DIRECT_INJECTABLE_INCLUDE ) ) 4010 { 4011 return; 4012 } 4013 4014 final Set<String> directInjectables = new HashSet<>(); 4015 final Set<String> transitiveInjectables = new HashSet<>(); 4016 4017 for ( final IncludeDescriptor include : includes ) 4018 { 4019 final String classname = include.actualTypeName(); 4020 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 4021 if ( null == element ) 4022 { 4023 continue; 4024 } 4025 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 4026 { 4027 // Only consider explicit direct includes as candidates (for Injector, auto-includes are not considered direct) 4028 if ( !include.auto() ) 4029 { 4030 directInjectables.add( classname ); 4031 } 4032 } 4033 else if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 4034 { 4035 collectTransitiveInjectablesFromFragment( classname, transitiveInjectables, new HashSet<>() ); 4036 } 4037 } 4038 4039 directInjectables.retainAll( transitiveInjectables ); 4040 if ( !directInjectables.isEmpty() ) 4041 { 4042 for ( final String classname : directInjectables ) 4043 { 4044 final String message = 4045 MemberChecks.shouldNot( annotationClassname, 4046 "include type " + classname + 4047 " as it is already transitively included via included fragments. " + 4048 MemberChecks.suppressedBy( Constants.WARNING_REDUNDANT_DIRECT_INJECTABLE_INCLUDE ) ); 4049 warning( message, originator ); 4050 } 4051 } 4052 } 4053 4054 private void collectTransitiveInjectablesFromFragment( @Nonnull final String fragmentClassname, 4055 @Nonnull final Set<String> collector, 4056 @Nonnull final Set<String> visitedFragments ) 4057 { 4058 if ( !visitedFragments.add( fragmentClassname ) ) 4059 { 4060 return; 4061 } 4062 final FragmentDescriptor fragment = _registry.findFragmentByClassName( fragmentClassname ); 4063 if ( null == fragment ) 4064 { 4065 return; 4066 } 4067 for ( final IncludeDescriptor include : fragment.getIncludes() ) 4068 { 4069 final String classname = include.actualTypeName(); 4070 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 4071 if ( null == element ) 4072 { 4073 continue; 4074 } 4075 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) ) 4076 { 4077 collector.add( classname ); 4078 } 4079 else if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 4080 { 4081 collectTransitiveInjectablesFromFragment( classname, collector, visitedFragments ); 4082 } 4083 } 4084 } 4085 4086 private void maybeWarnOnFragmentIncludeCycle( @Nonnull final TypeElement originator, 4087 @Nonnull final Collection<IncludeDescriptor> includes ) 4088 { 4089 if ( !ElementsUtil.isWarningNotSuppressed( originator, Constants.WARNING_FRAGMENT_INCLUDE_CYCLE ) ) 4090 { 4091 return; 4092 } 4093 final String originClassname = originator.getQualifiedName().toString(); 4094 for ( final IncludeDescriptor include : includes ) 4095 { 4096 final String classname = include.actualTypeName(); 4097 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 4098 if ( null == element ) 4099 { 4100 continue; 4101 } 4102 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 4103 { 4104 if ( fragmentTransitivelyIncludes( classname, originClassname, new HashSet<>() ) ) 4105 { 4106 final String message = 4107 MemberChecks.shouldNot( Constants.FRAGMENT_CLASSNAME, 4108 "include a fragment " + classname + 4109 " that transitively includes " + originClassname + ". " + 4110 MemberChecks.suppressedBy( Constants.WARNING_FRAGMENT_INCLUDE_CYCLE ) ); 4111 warning( message, originator ); 4112 return; 4113 } 4114 } 4115 } 4116 } 4117 4118 private boolean fragmentTransitivelyIncludes( @Nonnull final String fragmentClassname, 4119 @Nonnull final String targetFragmentClassname, 4120 @Nonnull final Set<String> visited ) 4121 { 4122 if ( !visited.add( fragmentClassname ) ) 4123 { 4124 return false; 4125 } 4126 if ( fragmentClassname.equals( targetFragmentClassname ) ) 4127 { 4128 return true; 4129 } 4130 final FragmentDescriptor fragment = _registry.findFragmentByClassName( fragmentClassname ); 4131 if ( null == fragment ) 4132 { 4133 return false; 4134 } 4135 for ( final IncludeDescriptor include : fragment.getIncludes() ) 4136 { 4137 final String classname = include.actualTypeName(); 4138 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 4139 if ( null != element && AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 4140 { 4141 if ( fragmentTransitivelyIncludes( classname, targetFragmentClassname, visited ) ) 4142 { 4143 return true; 4144 } 4145 } 4146 } 4147 return false; 4148 } 4149}