001package sting.processor; 002 003import java.io.ByteArrayInputStream; 004import java.io.ByteArrayOutputStream; 005import java.io.DataInputStream; 006import java.io.DataOutputStream; 007import java.io.IOException; 008import java.io.OutputStream; 009import java.util.ArrayList; 010import java.util.Arrays; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.HashSet; 014import java.util.LinkedHashMap; 015import java.util.List; 016import java.util.Map; 017import java.util.Objects; 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.tools.Diagnostic; 045import javax.tools.FileObject; 046import javax.tools.JavaFileManager; 047import javax.tools.StandardLocation; 048import org.realityforge.proton.AbstractStandardProcessor; 049import org.realityforge.proton.AnnotationsUtil; 050import org.realityforge.proton.DeferredElementSet; 051import org.realityforge.proton.ElementsUtil; 052import org.realityforge.proton.GeneratorUtil; 053import org.realityforge.proton.IOUtil; 054import org.realityforge.proton.JsonUtil; 055import org.realityforge.proton.MemberChecks; 056import org.realityforge.proton.ProcessorException; 057import org.realityforge.proton.ResourceUtil; 058import org.realityforge.proton.SuperficialValidation; 059import org.realityforge.proton.TypesUtil; 060 061/** 062 * The annotation processor that analyzes Sting annotated source code and generates an injector and supporting artifacts. 063 */ 064@SuppressWarnings( "DuplicatedCode" ) 065@SupportedAnnotationTypes( { Constants.INJECTOR_CLASSNAME, 066 Constants.INJECTOR_FRAGMENT_CLASSNAME, 067 Constants.INJECTABLE_CLASSNAME, 068 Constants.FRAGMENT_CLASSNAME, 069 Constants.EAGER_CLASSNAME, 070 Constants.TYPED_CLASSNAME, 071 Constants.NAMED_CLASSNAME, 072 Constants.AUTO_FRAGMENT_CLASSNAME, 073 Constants.CONTRIBUTE_TO_CLASSNAME } ) 074@SupportedSourceVersion( SourceVersion.RELEASE_8 ) 075@SupportedOptions( { "sting.defer.unresolved", 076 "sting.defer.errors", 077 "sting.debug", 078 "sting.emit_json_descriptors", 079 "sting.emit_dot_reports", 080 "sting.verbose_out_of_round.errors", 081 "sting.verify_descriptors" } ) 082public final class StingProcessor 083 extends AbstractStandardProcessor 084{ 085 /** 086 * Extension for json descriptors. 087 */ 088 static final String JSON_SUFFIX = ".sting.json"; 089 /** 090 * Extension for graphviz .dot reports. 091 */ 092 static final String DOT_SUFFIX = ".gv"; 093 /** 094 * Extension for sting binary descriptors. 095 */ 096 static final String SUFFIX = ".sbf"; 097 /** 098 * Extension for the computed graph descriptor. 099 */ 100 static final String GRAPH_SUFFIX = "__ObjectGraph" + JSON_SUFFIX; 101 /** 102 * A local cache of bindings that is cleared on error or when processing is complete. 103 * This will probably be loaded from json cache files in the future but now we require 104 * in memory processing. 105 */ 106 @Nonnull 107 private final Registry _registry = new Registry(); 108 @Nonnull 109 private final DeferredElementSet _deferredInjectableTypes = new DeferredElementSet(); 110 @Nonnull 111 private final DeferredElementSet _deferredFragmentTypes = new DeferredElementSet(); 112 @Nonnull 113 private final DeferredElementSet _deferredInjectorTypes = new DeferredElementSet(); 114 @Nonnull 115 private final DeferredElementSet _deferredInjectorFragmentTypes = new DeferredElementSet(); 116 /** 117 * Flag controlling whether json descriptors are emitted. 118 * Json descriptors are primarily used during debugging and probably should not be enabled in production code. 119 */ 120 private boolean _emitJsonDescriptors; 121 /** 122 * Flag controlling whether the binary descriptors are deserialized after serialization to verify 123 * that they produce the expected output. This is only used for debugging and should not be enabled 124 * in production code. 125 */ 126 private boolean _verifyDescriptors; 127 /** 128 * A utility class for reading and writing the binary descriptors. 129 */ 130 private DescriptorIO _descriptorIO; 131 /** 132 * Flag controlling whether .dot formatted report is emitted. 133 * The .dot report is typically used by end users who want to explore the graph. 134 */ 135 private boolean _emitDotReports; 136 137 @Nonnull 138 @Override 139 protected String getIssueTrackerURL() 140 { 141 return "https://github.com/sting-ioc/sting/issues"; 142 } 143 144 @Nonnull 145 @Override 146 protected String getOptionPrefix() 147 { 148 return "sting"; 149 } 150 151 @Override 152 public synchronized void init( final ProcessingEnvironment processingEnv ) 153 { 154 super.init( processingEnv ); 155 _descriptorIO = new DescriptorIO( processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 156 _emitJsonDescriptors = readBooleanOption( "emit_json_descriptors", false ); 157 _emitDotReports = readBooleanOption( "emit_dot_reports", false ); 158 _verifyDescriptors = readBooleanOption( "verify_descriptors", false ); 159 } 160 161 @Override 162 public boolean process( @Nonnull final Set<? extends TypeElement> annotations, @Nonnull final RoundEnvironment env ) 163 { 164 // Reset modified flag for auto-fragment so we can determine 165 // whether we should generate fragment this round 166 _registry.getAutoFragments().forEach( AutoFragmentDescriptor::resetModified ); 167 168 processAutoFragments( annotations, env ); 169 170 processTypeElements( annotations, 171 env, 172 Constants.INJECTABLE_CLASSNAME, 173 _deferredInjectableTypes, 174 this::processInjectable ); 175 176 processTypeElements( annotations, 177 env, 178 Constants.FRAGMENT_CLASSNAME, 179 _deferredFragmentTypes, 180 this::processFragment ); 181 182 processContributeTos( annotations, env ); 183 184 annotations.stream() 185 .filter( a -> a.getQualifiedName().toString().equals( Constants.NAMED_CLASSNAME ) ) 186 .findAny() 187 .ifPresent( a -> verifyNamedElements( env, env.getElementsAnnotatedWith( a ) ) ); 188 189 annotations.stream() 190 .filter( a -> a.getQualifiedName().toString().equals( Constants.TYPED_CLASSNAME ) ) 191 .findAny() 192 .ifPresent( a -> verifyTypedElements( env, env.getElementsAnnotatedWith( a ) ) ); 193 194 annotations.stream() 195 .filter( a -> a.getQualifiedName().toString().equals( Constants.EAGER_CLASSNAME ) ) 196 .findAny() 197 .ifPresent( a -> verifyEagerElements( env, env.getElementsAnnotatedWith( a ) ) ); 198 199 processTypeElements( annotations, 200 env, 201 Constants.INJECTOR_FRAGMENT_CLASSNAME, 202 _deferredInjectorFragmentTypes, 203 this::processInjectorFragment ); 204 205 processTypeElements( annotations, 206 env, 207 Constants.INJECTOR_CLASSNAME, 208 _deferredInjectorTypes, 209 this::processInjector ); 210 211 processUnmodifiedAutoFragment( env ); 212 processResolvedInjectables( env ); 213 processResolvedFragments( env ); 214 processResolvedInjectors( env ); 215 216 errorIfProcessingOverAndInvalidTypesDetected( env ); 217 errorIfProcessingOverAndUnprocessedInjectorDetected( env ); 218 errorIfProcessingOverAndUnprocessedAutoFragmentsDetected( env ); 219 errorIfProcessingOverAndUnprocessedContributeTosDetected( env ); 220 if ( env.processingOver() || env.errorRaised() ) 221 { 222 _registry.clear(); 223 } 224 return true; 225 } 226 227 private void processAutoFragments( @Nonnull final Set<? extends TypeElement> annotations, 228 @Nonnull final RoundEnvironment env ) 229 { 230 final Collection<TypeElement> autoFragments = 231 getNewTypeElementsToProcess( annotations, env, Constants.AUTO_FRAGMENT_CLASSNAME ); 232 for ( final TypeElement element : autoFragments ) 233 { 234 performAction( env, this::processAutoFragment, element ); 235 } 236 } 237 238 private void processContributeTos( @Nonnull final Set<? extends TypeElement> annotations, 239 @Nonnull final RoundEnvironment env ) 240 { 241 final Collection<TypeElement> contributeTos = 242 getNewTypeElementsToProcess( annotations, env, Constants.CONTRIBUTE_TO_CLASSNAME ); 243 for ( final TypeElement element : contributeTos ) 244 { 245 performAction( env, this::processContributeTo, element ); 246 } 247 } 248 249 private void errorIfProcessingOverAndUnprocessedAutoFragmentsDetected( @Nonnull final RoundEnvironment env ) 250 { 251 if ( env.processingOver() && !env.errorRaised() ) 252 { 253 final Collection<AutoFragmentDescriptor> autoFragments = 254 _registry.getAutoFragments().stream().filter( a -> !a.isFragmentGenerated() ).collect( Collectors.toList() ); 255 if ( !autoFragments.isEmpty() ) 256 { 257 processingEnv 258 .getMessager() 259 .printMessage( Diagnostic.Kind.ERROR, 260 getClass().getSimpleName() + " failed to process " + autoFragments.size() + 261 " @AutoFragment annotated types as the fragments either contained no contributors " + 262 "or only had contributors added in the last annotation processor round which is not " + 263 "supported by the @AutoFragment annotation. If the problem is not obvious, consider " + 264 "passing the annotation option sting.debug=true" ); 265 for ( final AutoFragmentDescriptor autoFragment : autoFragments ) 266 { 267 processingEnv 268 .getMessager() 269 .printMessage( Diagnostic.Kind.ERROR, 270 "Failed to process the " + autoFragment.getElement().getQualifiedName() + 271 " @AutoFragment as 0 @ContributeTo annotations reference the @AutoFragment" ); 272 } 273 } 274 } 275 } 276 277 private void errorIfProcessingOverAndUnprocessedContributeTosDetected( @Nonnull final RoundEnvironment env ) 278 { 279 if ( env.processingOver() && !env.errorRaised() ) 280 { 281 final Collection<String> contributorKeys = 282 _registry.getContributorKeys() 283 .stream() 284 .filter( key -> null == _registry.findAutoFragmentByKey( key ) ) 285 .collect( Collectors.toList() ); 286 if ( !contributorKeys.isEmpty() ) 287 { 288 for ( final String contributorKey : contributorKeys ) 289 { 290 processingEnv 291 .getMessager() 292 .printMessage( Diagnostic.Kind.ERROR, 293 "Failed to process the @ContributeTo contributors for key '" + contributorKey + 294 "' as no associated @AutoFragment is on the class path. Impacted contributors included: " + 295 _registry.getContributorsByKey( contributorKey ).stream() 296 .map( c -> c.getElement().getQualifiedName() ) 297 .collect( Collectors.joining( ", " ) ) ); 298 } 299 } 300 } 301 } 302 303 private void errorIfProcessingOverAndUnprocessedInjectorDetected( @Nonnull final RoundEnvironment env ) 304 { 305 if ( env.processingOver() && !env.errorRaised() ) 306 { 307 final List<InjectorDescriptor> injectors = _registry.getInjectors(); 308 if ( !injectors.isEmpty() ) 309 { 310 processingEnv 311 .getMessager() 312 .printMessage( Diagnostic.Kind.ERROR, 313 getClass().getSimpleName() + " failed to process " + injectors.size() + " injectors " + 314 "as not all of their dependencies could be resolved. The java code resolved but the " + 315 "descriptors were missing or in the incorrect format. Ensure that the included " + 316 "typed have been compiled with a compatible version of Sting and that the .sbf " + 317 "descriptors have been packaged with the .class files. If the problem is not " + 318 "obvious, consider passing the annotation option sting.debug=true" ); 319 for ( final InjectorDescriptor injector : injectors ) 320 { 321 processingEnv 322 .getMessager() 323 .printMessage( Diagnostic.Kind.ERROR, 324 "Failed to process the " + injector.getElement().getQualifiedName() + " injector." ); 325 } 326 } 327 } 328 } 329 330 private void processUnmodifiedAutoFragment( @Nonnull final RoundEnvironment env ) 331 { 332 for ( final AutoFragmentDescriptor autoFragment : new ArrayList<>( _registry.getAutoFragments() ) ) 333 { 334 performAction( env, e -> { 335 if ( !autoFragment.isFragmentGenerated() ) 336 { 337 if ( !autoFragment.isModified() && 338 !autoFragment.getContributors().isEmpty() ) 339 { 340 autoFragment.markFragmentGenerated(); 341 342 final boolean autoDiscoverableContributors = autoFragment.getContributors() 343 .stream() 344 .map( ContributorDescriptor::getElement ) 345 .map( c -> _registry.findInjectableByClassName( c.getQualifiedName().toString() ) ) 346 .filter( Objects::nonNull ) 347 .anyMatch( c -> !c.getBinding().isEager() && c.isAutoDiscoverable() ); 348 if ( autoDiscoverableContributors ) 349 { 350 autoFragment.markAsAutoDiscoverableContributors(); 351 } 352 353 final String packageName = GeneratorUtil.getQualifiedPackageName( autoFragment.getElement() ); 354 emitTypeSpec( packageName, AutoFragmentGenerator.buildType( processingEnv, autoFragment ) ); 355 } 356 else 357 { 358 debug( () -> "Defer generation for the auto-fragment " + 359 autoFragment.getElement().getQualifiedName() + 360 " as it " + 361 ( autoFragment.isModified() ? 362 "has been modified in the current round" : 363 "has zero contributors" ) ); 364 } 365 } 366 }, autoFragment.getElement() ); 367 } 368 } 369 370 private void processResolvedInjectables( @Nonnull final RoundEnvironment env ) 371 { 372 for ( final InjectableDescriptor injectable : new ArrayList<>( _registry.getInjectables() ) ) 373 { 374 performAction( env, e -> { 375 if ( !injectable.isJavaStubGenerated() ) 376 { 377 injectable.markJavaStubAsGenerated(); 378 writeBinaryDescriptor( injectable.getElement(), injectable ); 379 emitInjectableJsonDescriptor( injectable ); 380 emitInjectableStub( injectable ); 381 } 382 }, injectable.getElement() ); 383 } 384 } 385 386 private void emitInjectableStub( @Nonnull final InjectableDescriptor injectable ) 387 throws IOException 388 { 389 final String packageName = GeneratorUtil.getQualifiedPackageName( injectable.getElement() ); 390 emitTypeSpec( packageName, InjectableGenerator.buildType( processingEnv, injectable ) ); 391 } 392 393 private void processResolvedFragments( @Nonnull final RoundEnvironment env ) 394 { 395 final List<FragmentDescriptor> deferred = new ArrayList<>(); 396 final List<FragmentDescriptor> current = new ArrayList<>( _registry.getFragments() ); 397 final AtomicBoolean resolvedType = new AtomicBoolean(); 398 399 while ( !current.isEmpty() ) 400 { 401 for ( final FragmentDescriptor fragment : current ) 402 { 403 performAction( env, e -> { 404 if ( !fragment.isJavaStubGenerated() && !fragment.containsError() ) 405 { 406 final ResolveType resolveType = isFragmentResolved( env, fragment ); 407 if ( ResolveType.RESOLVED == resolveType ) 408 { 409 fragment.markJavaStubAsGenerated(); 410 writeBinaryDescriptor( fragment.getElement(), fragment ); 411 emitFragmentJsonDescriptor( fragment ); 412 emitFragmentStub( fragment ); 413 } 414 else if ( ResolveType.MAYBE_UNRESOLVED == resolveType ) 415 { 416 debug( () -> "The fragment " + fragment.getElement().getQualifiedName() + 417 " has resolved java types but unresolved descriptors. Deferring processing " + 418 "until later in the round" ); 419 deferred.add( fragment ); 420 } 421 else 422 { 423 debug( () -> "Defer generation for the fragment " + fragment.getElement().getQualifiedName() + 424 " as it is not yet resolved" ); 425 } 426 } 427 }, fragment.getElement() ); 428 } 429 current.clear(); 430 if ( resolvedType.get() ) 431 { 432 current.addAll( deferred ); 433 deferred.clear(); 434 } 435 else 436 { 437 break; 438 } 439 } 440 } 441 442 @Nonnull 443 private ResolveType isFragmentReady( @Nonnull final RoundEnvironment env, 444 @Nonnull final FragmentDescriptor fragment ) 445 { 446 if ( fragment.containsError() ) 447 { 448 return ResolveType.UNRESOLVED; 449 } 450 else 451 { 452 return isFragmentResolved( env, fragment ); 453 } 454 } 455 456 private void emitFragmentStub( @Nonnull final FragmentDescriptor fragment ) 457 throws IOException 458 { 459 final String packageName = GeneratorUtil.getQualifiedPackageName( fragment.getElement() ); 460 emitTypeSpec( packageName, FragmentGenerator.buildType( processingEnv, fragment ) ); 461 } 462 463 private void processResolvedInjectors( @Nonnull final RoundEnvironment env ) 464 { 465 final List<InjectorDescriptor> deferred = new ArrayList<>(); 466 final List<InjectorDescriptor> current = new ArrayList<>( _registry.getInjectors() ); 467 final AtomicBoolean resolvedType = new AtomicBoolean(); 468 469 while ( !current.isEmpty() ) 470 { 471 for ( final InjectorDescriptor injector : current ) 472 { 473 performAction( env, e -> { 474 if ( !injector.containsError() ) 475 { 476 final ResolveType resolveType = isInjectorResolved( env, injector ); 477 if ( ResolveType.RESOLVED == resolveType ) 478 { 479 _registry.deregisterInjector( injector ); 480 buildAndEmitObjectGraph( injector ); 481 resolvedType.set( true ); 482 } 483 else if ( ResolveType.MAYBE_UNRESOLVED == resolveType ) 484 { 485 debug( () -> "The injector " + injector.getElement().getQualifiedName() + 486 " has resolved java types but unresolved descriptors. Deferring processing " + 487 "until later in the round" ); 488 deferred.add( injector ); 489 } 490 else 491 { 492 debug( () -> "Defer generation for the injector " + injector.getElement().getQualifiedName() + 493 " as it is not yet resolved" ); 494 } 495 } 496 }, injector.getElement() ); 497 } 498 current.clear(); 499 if ( resolvedType.get() ) 500 { 501 resolvedType.set( false ); 502 current.addAll( deferred ); 503 deferred.clear(); 504 } 505 else 506 { 507 break; 508 } 509 } 510 } 511 512 private void buildAndEmitObjectGraph( @Nonnull final InjectorDescriptor injector ) 513 throws Exception 514 { 515 debug( () -> "Preparing to build component graph for the injector " + injector.getElement().getQualifiedName() ); 516 final ComponentGraph graph = new ComponentGraph( injector ); 517 registerIncludesComponents( graph ); 518 519 registerInputs( graph ); 520 521 debug( () -> "Building component graph for the injector " + injector.getElement().getQualifiedName() ); 522 523 buildObjectGraphNodes( graph ); 524 525 if ( graph.getNodes().isEmpty() && graph.getRootNode().getDependsOn().isEmpty() ) 526 { 527 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " target " + 528 "produced an empty object graph. This means that there are no eager nodes " + 529 "in the includes and there are no dependencies or only unsatisfied optional " + 530 "dependencies defined by the injector", 531 graph.getInjector().getElement() ); 532 } 533 534 debug( () -> "Propagating eager-ness for the injector " + injector.getElement().getQualifiedName() ); 535 536 propagateEagerFlagUpstream( graph ); 537 538 debug( () -> "Verifying no circular dependencies for the injector " + injector.getElement().getQualifiedName() ); 539 540 CircularDependencyChecker.verifyNoCircularDependencyLoops( graph ); 541 542 final Set<Binding> actualBindings = 543 graph.getNodes().stream().map( Node::getBinding ).collect( Collectors.toSet() ); 544 545 for ( final Map.Entry<IncludeDescriptor, Set<Binding>> entry : graph.getIncludeRootToBindingMap().entrySet() ) 546 { 547 if ( entry.getValue().stream().noneMatch( actualBindings::contains ) ) 548 { 549 final IncludeDescriptor includeRoot = entry.getKey(); 550 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " must not " + 551 "include type " + includeRoot.getIncludedType() + 552 " when the type is not used within the graph", 553 graph.getInjector().getElement() ); 554 } 555 } 556 557 emitObjectGraphJsonDescriptor( graph ); 558 559 final String packageName = GeneratorUtil.getQualifiedPackageName( graph.getInjector().getElement() ); 560 561 debug( () -> "Emitting injector implementation for the injector " + 562 graph.getInjector().getElement().getQualifiedName() ); 563 emitTypeSpec( packageName, InjectorGenerator.buildType( processingEnv, graph ) ); 564 565 if ( injector.isInjectable() ) 566 { 567 debug( () -> "Emitting injector provider for the injector " + 568 graph.getInjector().getElement().getQualifiedName() ); 569 emitTypeSpec( packageName, InjectorProviderGenerator.buildType( processingEnv, graph ) ); 570 } 571 emitDotReport( graph ); 572 } 573 574 private void emitDotReport( @Nonnull final ComponentGraph graph ) 575 throws IOException 576 { 577 if ( _emitDotReports ) 578 { 579 final TypeElement element = graph.getInjector().getElement(); 580 final String filename = toFilename( element ) + DOT_SUFFIX; 581 debug( () -> "Emitting .dot report for the injector " + graph.getInjector().getElement().getQualifiedName() ); 582 583 final String report = InjectorDotReportGenerator.buildDotReport( processingEnv, graph ); 584 ResourceUtil.writeResource( processingEnv, filename, report, element ); 585 } 586 } 587 588 private void registerInputs( @Nonnull final ComponentGraph graph ) 589 { 590 for ( final InputDescriptor input : graph.getInjector().getInputs() ) 591 { 592 graph.registerInput( input ); 593 } 594 } 595 596 private void propagateEagerFlagUpstream( @Nonnull final ComponentGraph graph ) 597 { 598 // Propagate Eager flag to all dependencies of eager nodes breaking the propagation at Supplier nodes 599 // They may not be configured as eager but they are effectively eager given that they will be created 600 // at startup, they may as well be marked as eager objects as that results in smaller code-size. 601 graph.getNodes().stream().filter( n -> n.getBinding().isEager() ).forEach( Node::markNodeAndUpstreamAsEager ); 602 } 603 604 private void registerIncludesComponents( @Nonnull final ComponentGraph graph ) 605 { 606 registerIncludes( graph, null, graph.getInjector().getIncludes() ); 607 } 608 609 private void registerIncludes( @Nonnull final ComponentGraph graph, 610 @Nullable final IncludeDescriptor includeRoot, 611 @Nonnull final Collection<IncludeDescriptor> includes ) 612 { 613 for ( final IncludeDescriptor include : includes ) 614 { 615 final String classname = include.getActualTypeName(); 616 if ( isDebugEnabled() && include.isProvider() ) 617 { 618 debug( () -> "Registering include " + classname + " via provider " + include.getIncludedType() + 619 " into graph " + graph.getInjector().getElement().getQualifiedName() ); 620 } 621 else 622 { 623 debug( () -> "Registering include " + classname + " into graph " + 624 graph.getInjector().getElement().getQualifiedName() ); 625 } 626 final IncludeDescriptor root = null == includeRoot ? include : includeRoot; 627 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 628 assert null != element; 629 final String qualifiedName = element.getQualifiedName().toString(); 630 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) ) 631 { 632 final FragmentDescriptor fragment = _registry.getFragmentByClassName( qualifiedName ); 633 registerIncludes( graph, root, fragment.getIncludes() ); 634 graph.registerFragment( root, fragment ); 635 } 636 else 637 { 638 assert AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ); 639 final InjectableDescriptor injectable = _registry.getInjectableByClassName( qualifiedName ); 640 graph.registerInjectable( root, injectable ); 641 } 642 } 643 } 644 645 private void buildObjectGraphNodes( @Nonnull final ComponentGraph graph ) 646 { 647 final Set<Node> completed = new HashSet<>(); 648 final Stack<WorkEntry> workList = new Stack<>(); 649 // At this stage the "rootNode" contains dependencies for all the output methods declared on the injector 650 // and all the eager services declared in includes have already been added to the nodes list. 651 // 652 // We start at the rootNode and expand all of the dependencies. And then we take any of the eager dependencies 653 // that have yet to be added to be processed and add them to to worklist and expand all dependencies until there 654 // are no eager nodes left to process 655 final List<Node> eagerNodes = new ArrayList<>( graph.getRawNodeCollection() ); 656 final Node rootNode = graph.getRootNode(); 657 rootNode.setDepth( 0 ); 658 addDependsOnToWorkList( workList, rootNode, null ); 659 processWorkList( graph, completed, workList ); 660 for ( final Node node : eagerNodes ) 661 { 662 if ( node.isDepthNotSet() ) 663 { 664 node.setDepth( 0 ); 665 addDependsOnToWorkList( workList, node, null ); 666 processWorkList( graph, completed, workList ); 667 } 668 } 669 graph.complete(); 670 } 671 672 private void processWorkList( @Nonnull final ComponentGraph graph, 673 @Nonnull final Set<Node> completed, 674 @Nonnull final Stack<WorkEntry> workList ) 675 { 676 final InjectorDescriptor injector = graph.getInjector(); 677 while ( !workList.isEmpty() ) 678 { 679 final WorkEntry workEntry = workList.pop(); 680 final Edge edge = workEntry.getEntry().getEdge(); 681 assert null != edge; 682 final ServiceRequest serviceRequest = edge.getServiceRequest(); 683 final Coordinate coordinate = serviceRequest.getService().getCoordinate(); 684 final List<Binding> bindings = new ArrayList<>( graph.findAllBindingsByCoordinate( coordinate ) ); 685 686 if ( bindings.isEmpty() && coordinate.getQualifier().isEmpty() ) 687 { 688 final String typename = coordinate.getType().toString(); 689 final InjectableDescriptor injectable = _registry.findInjectableByClassName( typename ); 690 if ( null != injectable && injectable.isAutoDiscoverable() ) 691 { 692 bindings.add( injectable.getBinding() ); 693 } 694 if ( bindings.isEmpty() ) 695 { 696 final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement( typename ); 697 final byte[] data = null != typeElement ? tryLoadDescriptorData( typeElement ) : null; 698 if ( null != data ) 699 { 700 final Node node = edge.getNode(); 701 final Object owner = node.hasNoBinding() ? null : node.getBinding().getOwner(); 702 final TypeElement ownerElement = 703 owner instanceof FragmentDescriptor ? ( (FragmentDescriptor) owner ).getElement() : 704 owner instanceof InjectableDescriptor ? ( (InjectableDescriptor) owner ).getElement() : 705 injector.getElement(); 706 707 final Object descriptor = loadDescriptor( ownerElement, typename, data ); 708 if ( descriptor instanceof InjectableDescriptor ) 709 { 710 final InjectableDescriptor candidate = (InjectableDescriptor) descriptor; 711 if ( candidate.isAutoDiscoverable() ) 712 { 713 assert coordinate.equals( candidate.getBinding().getPublishedServices().get( 0 ).getCoordinate() ); 714 _registry.registerInjectable( candidate ); 715 bindings.add( candidate.getBinding() ); 716 } 717 } 718 } 719 } 720 } 721 722 final List<Binding> nullableProviders = bindings.stream() 723 .filter( b -> b.getPublishedServices().stream().anyMatch( ServiceSpec::isOptional ) ) 724 .collect( Collectors.toList() ); 725 if ( !serviceRequest.getService().isOptional() && !nullableProviders.isEmpty() ) 726 { 727 final String message = 728 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 729 "contain an optional provider method or optional injector input and " + 730 "a non-optional service request for the coordinate " + coordinate + "\n" + 731 "Dependency Path:\n" + workEntry.describePathFromRoot() + "\n" + 732 "Binding" + ( nullableProviders.size() > 1 ? "s" : "" ) + ":\n" + 733 bindingsToString( nullableProviders ) ); 734 throw new ProcessorException( message, serviceRequest.getElement() ); 735 } 736 if ( bindings.isEmpty() ) 737 { 738 if ( serviceRequest.getService().isOptional() || serviceRequest.getKind().isCollection() ) 739 { 740 edge.setSatisfiedBy( Collections.emptyList() ); 741 } 742 else 743 { 744 final String message = 745 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 746 "contain a non-optional dependency " + coordinate + 747 " that can not be satisfied.\n" + 748 "Dependency Path:\n" + workEntry.describePathFromRoot() ); 749 throw new ProcessorException( message, serviceRequest.getElement() ); 750 } 751 } 752 else 753 { 754 final ServiceRequest.Kind kind = serviceRequest.getKind(); 755 if ( 1 == bindings.size() || kind.isCollection() ) 756 { 757 final List<Node> nodes = new ArrayList<>(); 758 for ( final Binding binding : bindings ) 759 { 760 final Node node = graph.findOrCreateNode( binding ); 761 nodes.add( node ); 762 if ( !completed.contains( node ) ) 763 { 764 completed.add( node ); 765 addDependsOnToWorkList( workList, node, workEntry ); 766 } 767 } 768 edge.setSatisfiedBy( nodes ); 769 } 770 else 771 { 772 //noinspection ConstantConditions 773 assert bindings.size() > 1 && !kind.isCollection(); 774 final String message = 775 MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 776 "contain a non-collection dependency " + coordinate + 777 " that can be satisfied by multiple nodes.\n" + 778 "Dependency Path:\n" + workEntry.describePathFromRoot() + "\n" + 779 "Candidate Nodes:\n" + bindingsToString( bindings ) ); 780 throw new ProcessorException( message, serviceRequest.getElement() ); 781 } 782 } 783 } 784 } 785 786 @Nonnull 787 private String bindingsToString( @Nonnull final List<Binding> bindings ) 788 { 789 return bindings 790 .stream() 791 .map( b -> " " + b.getTypeLabel() + " " + b.describe() ) 792 .collect( Collectors.joining( "\n" ) ); 793 } 794 795 private void addDependsOnToWorkList( @Nonnull final Stack<WorkEntry> workList, 796 @Nonnull final Node node, 797 @Nullable final WorkEntry parent ) 798 { 799 for ( final Edge e : node.getDependsOn() ) 800 { 801 final Stack<PathEntry> stack = new Stack<>(); 802 if ( null != parent ) 803 { 804 stack.addAll( parent.getStack() ); 805 } 806 final PathEntry entry = new PathEntry( node, e ); 807 stack.add( entry ); 808 workList.add( new WorkEntry( entry, stack ) ); 809 } 810 } 811 812 private void emitObjectGraphJsonDescriptor( @Nonnull final ComponentGraph graph ) 813 throws IOException 814 { 815 if ( _emitJsonDescriptors ) 816 { 817 final TypeElement element = graph.getInjector().getElement(); 818 final String filename = toFilename( element ) + GRAPH_SUFFIX; 819 debug( () -> "Emitting json descriptor for the injector " + graph.getInjector().getElement().getQualifiedName() ); 820 JsonUtil.writeJsonResource( processingEnv, element, filename, graph::write ); 821 } 822 } 823 824 @Nonnull 825 private ResolveType isFragmentResolved( @Nonnull final RoundEnvironment env, 826 @Nonnull final FragmentDescriptor fragment ) 827 { 828 if ( fragment.isResolved() ) 829 { 830 return ResolveType.RESOLVED; 831 } 832 else 833 { 834 final ResolveType resolveType = isResolved( env, fragment, fragment.getElement(), fragment.getIncludes() ); 835 if ( resolveType == ResolveType.RESOLVED ) 836 { 837 fragment.markAsResolved(); 838 } 839 return resolveType; 840 } 841 } 842 843 @Nonnull 844 private ResolveType isInjectorResolved( @Nonnull final RoundEnvironment env, 845 @Nonnull final InjectorDescriptor injector ) 846 { 847 return isResolved( env, injector, injector.getElement(), injector.getIncludes() ); 848 } 849 850 @Nonnull 851 private <T> ResolveType isResolved( @Nonnull final RoundEnvironment env, 852 @Nonnull final T descriptor, 853 @Nonnull final TypeElement originator, 854 @Nonnull final Collection<IncludeDescriptor> includes ) 855 { 856 // By the time we get here we can guarantee that the java types for includes are correctly resolved 857 // but they may not have passed through annotation processor and thus the descriptors may be absent 858 // so we go through a few iterations and as long as one include is resolved in each iteration we should 859 // keep going as we may be able to resolve all includes. Also if one of the includes is a provider 860 // we need to check the associated provider type is resolved. 861 for ( final IncludeDescriptor include : includes ) 862 { 863 final ResolveType resolveType = isIncludeResolved( env, descriptor, originator, include ); 864 if ( ResolveType.RESOLVED != resolveType ) 865 { 866 return resolveType; 867 } 868 } 869 870 return ResolveType.RESOLVED; 871 } 872 873 @Nonnull 874 private ResolveType isIncludeResolved( @Nonnull final RoundEnvironment env, 875 @Nonnull final Object descriptor, 876 @Nonnull final TypeElement originator, 877 @Nonnull final IncludeDescriptor include ) 878 { 879 AnnotationMirror annotation = 880 AnnotationsUtil.findAnnotationByType( originator, Constants.INJECTOR_CLASSNAME ); 881 882 final String annotationClassname = 883 null != annotation ? Constants.INJECTOR_CLASSNAME : Constants.FRAGMENT_CLASSNAME; 884 if ( null == annotation ) 885 { 886 annotation = AnnotationsUtil.getAnnotationByType( originator, Constants.FRAGMENT_CLASSNAME ); 887 } 888 889 final String classname = include.getActualTypeName(); 890 final TypeElement element = processingEnv.getElementUtils().getTypeElement( classname ); 891 if ( null == element ) 892 { 893 assert include.isProvider(); 894 if ( env.processingOver() ) 895 { 896 if ( descriptor instanceof FragmentDescriptor ) 897 { 898 ( (FragmentDescriptor) descriptor ).markAsContainsError(); 899 } 900 else 901 { 902 ( (InjectorDescriptor) descriptor ).markAsContainsError(); 903 } 904 905 final String message = 906 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 907 "parameter named 'includes' containing the value " + include.getIncludedType() + 908 " and that type is annotated by the @StingProvider annotation. The provider annotation expects a " + 909 "provider class named " + include.getActualTypeName() + " but no such class exists. The " + 910 "type needs to be removed from the includes or the provider class needs to be present."; 911 reportError( env, message, originator, annotation, null ); 912 } 913 return ResolveType.UNRESOLVED; 914 } 915 final boolean isInjectable = AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ); 916 final boolean isFragment = 917 !isInjectable && AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ); 918 if ( include.isProvider() && !isInjectable && !isFragment ) 919 { 920 if ( descriptor instanceof FragmentDescriptor ) 921 { 922 ( (FragmentDescriptor) descriptor ).markAsContainsError(); 923 } 924 else 925 { 926 ( (InjectorDescriptor) descriptor ).markAsContainsError(); 927 } 928 929 final String message = 930 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 931 "parameter named 'includes' containing the value " + include.getIncludedType() + 932 " and that type is annotated by the @StingProvider annotation. The provider annotation expects a " + 933 "provider class named " + include.getActualTypeName() + " but that class is not annotated with either " + 934 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " or " + 935 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ); 936 throw new ProcessorException( message, originator, annotation ); 937 } 938 if ( isFragment ) 939 { 940 FragmentDescriptor fragment = _registry.findFragmentByClassName( classname ); 941 if ( null == fragment ) 942 { 943 final byte[] data = tryLoadDescriptorData( element ); 944 if ( null == data ) 945 { 946 debug( () -> "The fragment " + classname + " is compiled to a .class file but no descriptor is present. " + 947 "Marking " + originator.getQualifiedName() + " as unresolved" ); 948 return ResolveType.MAYBE_UNRESOLVED; 949 } 950 final Object loadedDescriptor = loadDescriptor( originator, classname, data ); 951 if ( loadedDescriptor instanceof FragmentDescriptor ) 952 { 953 fragment = (FragmentDescriptor) loadedDescriptor; 954 _registry.registerFragment( fragment ); 955 } 956 else if ( null == loadedDescriptor ) 957 { 958 debug( () -> "The fragment " + classname + " is compiled to a .class file no descriptor is present. " + 959 "Marking " + originator.getQualifiedName() + " as unresolved" ); 960 return ResolveType.MAYBE_UNRESOLVED; 961 } 962 else 963 { 964 debug( () -> "The fragment " + classname + " is compiled to a .class " + 965 "file but an invalid descriptor is present. " + 966 "Marking " + originator.getQualifiedName() + " as unresolved" ); 967 return ResolveType.MAYBE_UNRESOLVED; 968 } 969 } 970 if ( ResolveType.RESOLVED != isFragmentReady( env, fragment ) ) 971 { 972 debug( () -> "Fragment include " + classname + " is present but not yet resolved. " + 973 "Marking " + originator.getQualifiedName() + " as unresolved" ); 974 return ResolveType.UNRESOLVED; 975 } 976 } 977 else 978 { 979 InjectableDescriptor injectable = _registry.findInjectableByClassName( classname ); 980 if ( null == injectable ) 981 { 982 final byte[] data = tryLoadDescriptorData( element ); 983 if ( null == data ) 984 { 985 debug( () -> "The injectable " + classname + " is compiled to a .class file but no descriptor is present." + 986 "Marking " + originator.getQualifiedName() + " as unresolved" ); 987 return ResolveType.MAYBE_UNRESOLVED; 988 } 989 final Object loadedDescriptor = loadDescriptor( originator, classname, data ); 990 if ( loadedDescriptor instanceof InjectableDescriptor ) 991 { 992 injectable = (InjectableDescriptor) loadedDescriptor; 993 _registry.registerInjectable( injectable ); 994 } 995 else 996 { 997 debug( () -> "The injectable " + classname + " is compiled to a .class " + 998 "file but an invalid descriptor is present. " + 999 "Marking " + originator.getQualifiedName() + " as unresolved" ); 1000 return ResolveType.MAYBE_UNRESOLVED; 1001 } 1002 } 1003 if ( !SuperficialValidation.validateElement( processingEnv, injectable.getElement() ) ) 1004 { 1005 debug( () -> "Injectable include " + classname + " is not yet resolved. " + 1006 "Marking " + originator.getQualifiedName() + " as unresolved" ); 1007 return ResolveType.UNRESOLVED; 1008 } 1009 1010 if ( !include.isAuto() && 1011 !injectable.getBinding().isEager() && 1012 injectable.isAutoDiscoverable() && 1013 ElementsUtil.isWarningNotSuppressed( originator, Constants.WARNING_AUTO_DISCOVERABLE_INCLUDED ) ) 1014 { 1015 final String message = 1016 MemberChecks.shouldNot( annotationClassname, 1017 "include an auto-discoverable type " + classname + ". " + 1018 MemberChecks.suppressedBy( Constants.WARNING_AUTO_DISCOVERABLE_INCLUDED ) ); 1019 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, originator ); 1020 } 1021 } 1022 return ResolveType.RESOLVED; 1023 } 1024 1025 private void processInjectorFragment( @Nonnull final TypeElement element ) 1026 throws Exception 1027 { 1028 debug( () -> "Processing Injector Fragment: " + element ); 1029 final ElementKind kind = element.getKind(); 1030 if ( ElementKind.INTERFACE != kind ) 1031 { 1032 throw new ProcessorException( MemberChecks.must( Constants.INJECTOR_FRAGMENT_CLASSNAME, "be an interface" ), 1033 element ); 1034 } 1035 final List<ExecutableElement> methods = 1036 ElementsUtil.getMethods( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 1037 for ( final ExecutableElement method : methods ) 1038 { 1039 if ( method.getModifiers().contains( Modifier.DEFAULT ) ) 1040 { 1041 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) + 1042 " target must not include default methods", 1043 method ); 1044 } 1045 else if ( method.getModifiers().contains( Modifier.STATIC ) ) 1046 { 1047 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) + 1048 " target must not include static methods", 1049 method ); 1050 } 1051 } 1052 } 1053 1054 private void processInjector( @Nonnull final TypeElement element ) 1055 throws Exception 1056 { 1057 debug( () -> "Processing Injector: " + element ); 1058 final ElementKind kind = element.getKind(); 1059 if ( ElementKind.INTERFACE != kind ) 1060 { 1061 throw new ProcessorException( MemberChecks.must( Constants.INJECTOR_CLASSNAME, "be an interface" ), 1062 element ); 1063 } 1064 if ( !element.getTypeParameters().isEmpty() ) 1065 { 1066 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, "have type parameters" ), 1067 element ); 1068 } 1069 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 1070 if ( !scopedAnnotations.isEmpty() ) 1071 { 1072 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1073 "be annotated with an annotation that is " + 1074 "annotated with the " + Constants.JSR_330_SCOPE_CLASSNAME + 1075 " annotation such as " + scopedAnnotations ), 1076 element ); 1077 } 1078 1079 final boolean gwt = isGwtEnabled( element ); 1080 final boolean injectable = 1081 (boolean) AnnotationsUtil.getAnnotationValue( element, Constants.INJECTOR_CLASSNAME, "injectable" ).getValue(); 1082 final List<IncludeDescriptor> includes = extractIncludes( element, Constants.INJECTOR_CLASSNAME ); 1083 final List<InputDescriptor> inputs = extractInputs( element ); 1084 1085 final List<ServiceRequest> outputs = new ArrayList<>(); 1086 final List<ExecutableElement> methods = 1087 ElementsUtil.getMethods( element, processingEnv.getElementUtils(), processingEnv.getTypeUtils() ); 1088 for ( final ExecutableElement method : methods ) 1089 { 1090 if ( method.getModifiers().contains( Modifier.ABSTRACT ) ) 1091 { 1092 processInjectorOutputMethod( outputs, method ); 1093 } 1094 else if ( method.getModifiers().contains( Modifier.DEFAULT ) ) 1095 { 1096 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1097 " target must not include default methods", 1098 method ); 1099 } 1100 } 1101 for ( final Element enclosedElement : element.getEnclosedElements() ) 1102 { 1103 final ElementKind enclosedElementKind = enclosedElement.getKind(); 1104 if ( ElementKind.INTERFACE == enclosedElementKind && 1105 AnnotationsUtil.hasAnnotationOfType( enclosedElement, Constants.FRAGMENT_CLASSNAME ) ) 1106 { 1107 final DeclaredType type = (DeclaredType) enclosedElement.asType(); 1108 if ( includes.stream().noneMatch( d -> Objects.equals( d.getIncludedType(), type ) ) ) 1109 { 1110 includes.add( new IncludeDescriptor( type, type.toString(), true ) ); 1111 } 1112 else 1113 { 1114 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1115 " target must not include a " + 1116 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + " annotated " + 1117 "type that is auto-included as it is enclosed within the injector type", 1118 element ); 1119 } 1120 } 1121 else if ( ElementKind.CLASS == enclosedElementKind && 1122 AnnotationsUtil.hasAnnotationOfType( enclosedElement, Constants.INJECTABLE_CLASSNAME ) ) 1123 { 1124 final DeclaredType type = (DeclaredType) enclosedElement.asType(); 1125 if ( includes.stream().noneMatch( d -> Objects.equals( d.getIncludedType(), type ) ) ) 1126 { 1127 includes.add( new IncludeDescriptor( type, type.toString(), true ) ); 1128 } 1129 else 1130 { 1131 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1132 " target must not include an " + 1133 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + " annotated " + 1134 "type that is auto-included as it is enclosed within the injector type", 1135 element ); 1136 } 1137 } 1138 else if ( enclosedElementKind.isClass() || enclosedElementKind.isInterface() ) 1139 { 1140 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + 1141 " target must not contain a type that is not annotated " + 1142 "by either " + MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1143 " or " + MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ), 1144 element ); 1145 } 1146 } 1147 final InjectorDescriptor injector = new InjectorDescriptor( element, gwt, injectable, includes, inputs, outputs ); 1148 _registry.registerInjector( injector ); 1149 emitInjectorJsonDescriptor( injector ); 1150 } 1151 1152 private boolean isGwtEnabled( @Nonnull final TypeElement element ) 1153 { 1154 final String value = AnnotationsUtil.getEnumAnnotationParameter( element, Constants.INJECTOR_CLASSNAME, "gwt" ); 1155 return "ENABLE".equals( value ) || 1156 ( "AUTODETECT".equals( value ) && 1157 null != processingEnv.getElementUtils().getTypeElement( "javaemul.internal.annotations.DoNotInline" ) ); 1158 } 1159 1160 private void emitInjectorJsonDescriptor( @Nonnull final InjectorDescriptor injector ) 1161 throws IOException 1162 { 1163 if ( _emitJsonDescriptors ) 1164 { 1165 final TypeElement element = injector.getElement(); 1166 final String filename = toFilename( element ) + JSON_SUFFIX; 1167 JsonUtil.writeJsonResource( processingEnv, element, filename, injector::write ); 1168 } 1169 } 1170 1171 private void processInjectorOutputMethod( @Nonnull final List<ServiceRequest> outputs, 1172 @Nonnull final ExecutableElement method ) 1173 { 1174 assert method.getModifiers().contains( Modifier.ABSTRACT ); 1175 if ( TypeKind.VOID == method.getReturnType().getKind() ) 1176 { 1177 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1178 "contain a method that has a void return value" ), 1179 method ); 1180 } 1181 else if ( !method.getParameters().isEmpty() ) 1182 { 1183 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1184 "contain a method that has parameters" ), 1185 method ); 1186 } 1187 else if ( !method.getTypeParameters().isEmpty() ) 1188 { 1189 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1190 "contain a method that has any type parameters" ), 1191 method ); 1192 } 1193 else if ( !method.getThrownTypes().isEmpty() ) 1194 { 1195 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1196 "contain a method that throws any exceptions" ), 1197 method ); 1198 } 1199 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( method ); 1200 if ( !scopedAnnotations.isEmpty() ) 1201 { 1202 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1203 "contain a method that is annotated with an " + 1204 "annotation that is annotated with the " + 1205 Constants.JSR_330_SCOPE_CLASSNAME + 1206 " annotation such as " + scopedAnnotations ), 1207 method ); 1208 } 1209 outputs.add( processOutputMethod( method ) ); 1210 } 1211 1212 @Nonnull 1213 private ServiceRequest processOutputMethod( @Nonnull final ExecutableElement method ) 1214 { 1215 final TypeMirror type = method.getReturnType(); 1216 if ( TypesUtil.containsArrayType( type ) ) 1217 { 1218 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1219 "contain a method with a return type that contains an array type" ), 1220 method ); 1221 } 1222 else if ( TypesUtil.containsWildcard( type ) ) 1223 { 1224 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1225 "contain a method with a return type that contains a wildcard type parameter" ), 1226 method ); 1227 } 1228 else if ( TypesUtil.containsRawType( type ) ) 1229 { 1230 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1231 "contain a method with a return type that contains a raw type" ), 1232 method ); 1233 } 1234 else 1235 { 1236 TypeMirror dependencyType = null; 1237 ServiceRequest.Kind kind = null; 1238 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 1239 { 1240 dependencyType = candidate.extractType( type ); 1241 if ( null != dependencyType ) 1242 { 1243 kind = candidate; 1244 break; 1245 } 1246 } 1247 if ( null == kind ) 1248 { 1249 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1250 "contain a method with a return type that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 1251 method ); 1252 } 1253 else 1254 { 1255 final boolean optional = AnnotationsUtil.hasNullableAnnotation( method ); 1256 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 1257 { 1258 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1259 "contain a method annotated with " + 1260 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 1261 " that is not an instance dependency kind" ), 1262 method ); 1263 } 1264 final String qualifier = getQualifier( method ); 1265 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.JSR_330_NAMED_CLASSNAME ) ) 1266 { 1267 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTOR_CLASSNAME, 1268 "contain a method annotated with the " + 1269 Constants.JSR_330_NAMED_CLASSNAME + 1270 " annotation. Use the " + Constants.NAMED_CLASSNAME + 1271 " annotation instead" ), 1272 method ); 1273 } 1274 1275 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 1276 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 1277 return new ServiceRequest( kind, service, method, -1 ); 1278 } 1279 } 1280 } 1281 1282 private void verifyNamedElements( @Nonnull final RoundEnvironment env, 1283 @Nonnull final Set<? extends Element> elements ) 1284 { 1285 for ( final Element element : elements ) 1286 { 1287 if ( ElementKind.PARAMETER == element.getKind() ) 1288 { 1289 final Element executableElement = element.getEnclosingElement(); 1290 final boolean injectableType = 1291 AnnotationsUtil.hasAnnotationOfType( executableElement.getEnclosingElement(), 1292 Constants.INJECTABLE_CLASSNAME ); 1293 final boolean isFragmentType = 1294 !injectableType && 1295 AnnotationsUtil.hasAnnotationOfType( executableElement.getEnclosingElement(), Constants.FRAGMENT_CLASSNAME ); 1296 final ElementKind executableKind = executableElement.getKind(); 1297 final boolean isProvider = 1298 !injectableType && !isFragmentType && hasStingProvider( executableElement.getEnclosingElement() ); 1299 final boolean isActAsStingComponent = 1300 !injectableType && 1301 !isFragmentType && 1302 !isProvider && 1303 hasActAsStingComponent( executableElement.getEnclosingElement() ); 1304 if ( !injectableType && ElementKind.CONSTRUCTOR == executableKind && !isProvider && !isActAsStingComponent ) 1305 { 1306 reportError( env, 1307 MemberChecks.must( Constants.NAMED_CLASSNAME, 1308 "only be present on a constructor parameter if the constructor " + 1309 "is enclosed in a type annotated with " + 1310 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1311 " or the type is annotated with an annotation annotated by " + 1312 "@ActAsStringComponent or @StingProvider" ), 1313 element ); 1314 } 1315 else if ( !isFragmentType && ElementKind.METHOD == executableKind ) 1316 { 1317 reportError( env, 1318 MemberChecks.must( Constants.NAMED_CLASSNAME, 1319 "only be present on a method parameter if the method is enclosed in a type annotated with " + 1320 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) ), 1321 element ); 1322 } 1323 else 1324 { 1325 assert ( injectableType && ElementKind.CONSTRUCTOR == executableKind ) || 1326 ( isProvider && ElementKind.CONSTRUCTOR == executableKind ) || 1327 ( isActAsStingComponent && ElementKind.CONSTRUCTOR == executableKind ) || 1328 ( isFragmentType && ElementKind.METHOD == executableKind ); 1329 } 1330 } 1331 else if ( ElementKind.CLASS == element.getKind() ) 1332 { 1333 if ( !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) && 1334 !hasStingProvider( element ) && 1335 !hasActAsStingComponent( element ) ) 1336 { 1337 reportError( env, 1338 MemberChecks.must( Constants.NAMED_CLASSNAME, 1339 "only be present on a type if the type is annotated with " + 1340 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1341 " or the type is annotated with an annotation annotated by " + 1342 "@ActAsStringComponent or @StingProvider" ), 1343 element ); 1344 } 1345 } 1346 else if ( ElementKind.METHOD == element.getKind() ) 1347 { 1348 if ( !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.FRAGMENT_CLASSNAME ) && 1349 !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.INJECTOR_CLASSNAME ) && 1350 !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), 1351 Constants.INJECTOR_FRAGMENT_CLASSNAME ) ) 1352 { 1353 reportError( env, 1354 MemberChecks.mustNot( Constants.NAMED_CLASSNAME, 1355 "be a method unless the method is enclosed in a type annotated with " + 1356 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + ", " + 1357 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) + " or " + 1358 MemberChecks.toSimpleName( Constants.INJECTOR_FRAGMENT_CLASSNAME ) ), 1359 element ); 1360 } 1361 } 1362 else 1363 { 1364 reportError( env, 1365 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + " target is not valid", 1366 element ); 1367 } 1368 } 1369 } 1370 1371 private boolean hasStingProvider( @Nonnull final Element element ) 1372 { 1373 return hasAnnotationWithAnnotationMatching( element, 1374 ca -> ca.getAnnotationType() 1375 .asElement() 1376 .getSimpleName() 1377 .contentEquals( "StingProvider" ) ); 1378 } 1379 1380 private boolean hasActAsStingComponent( @Nonnull final Element element ) 1381 { 1382 return hasAnnotationWithAnnotationMatching( element, 1383 ca -> ca.getAnnotationType() 1384 .asElement() 1385 .getSimpleName() 1386 .contentEquals( "ActAsStingComponent" ) ); 1387 } 1388 1389 private boolean hasAnnotationWithAnnotationMatching( @Nonnull final AnnotatedConstruct element, 1390 @Nonnull final Predicate<? super AnnotationMirror> predicate ) 1391 { 1392 return element 1393 .getAnnotationMirrors() 1394 .stream() 1395 .anyMatch( a -> a.getAnnotationType() 1396 .asElement() 1397 .getAnnotationMirrors() 1398 .stream() 1399 .anyMatch( predicate ) ); 1400 } 1401 1402 private void verifyTypedElements( @Nonnull final RoundEnvironment env, 1403 @Nonnull final Set<? extends Element> elements ) 1404 { 1405 for ( final Element element : elements ) 1406 { 1407 if ( ElementKind.CLASS == element.getKind() ) 1408 { 1409 if ( !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) && 1410 !hasStingProvider( element ) ) 1411 { 1412 reportError( env, 1413 MemberChecks.must( Constants.TYPED_CLASSNAME, 1414 "only be present on a type if the type is annotated with " + 1415 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1416 " or the type is annotated with an annotation annotated by @StingProvider" ), 1417 element ); 1418 } 1419 } 1420 else if ( ElementKind.METHOD == element.getKind() ) 1421 { 1422 if ( !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.FRAGMENT_CLASSNAME ) && 1423 !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.INJECTOR_CLASSNAME ) ) 1424 { 1425 reportError( env, 1426 MemberChecks.mustNot( Constants.TYPED_CLASSNAME, 1427 "be a method unless the method is enclosed in a type annotated with " + 1428 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + " or " + 1429 MemberChecks.toSimpleName( Constants.INJECTOR_CLASSNAME ) ), 1430 element ); 1431 } 1432 } 1433 else 1434 { 1435 reportError( env, 1436 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + " target is not valid", 1437 element ); 1438 } 1439 } 1440 } 1441 1442 private void verifyEagerElements( @Nonnull final RoundEnvironment env, 1443 @Nonnull final Set<? extends Element> elements ) 1444 { 1445 for ( final Element element : elements ) 1446 { 1447 if ( ElementKind.CLASS == element.getKind() ) 1448 { 1449 if ( !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) && 1450 !hasStingProvider( element ) ) 1451 { 1452 reportError( env, 1453 MemberChecks.must( Constants.EAGER_CLASSNAME, 1454 "only be present on a type if the type is annotated with " + 1455 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1456 " or the type is annotated with an annotation annotated by @StingProvider" ), 1457 element ); 1458 } 1459 } 1460 else if ( ElementKind.METHOD == element.getKind() ) 1461 { 1462 if ( !AnnotationsUtil.hasAnnotationOfType( element.getEnclosingElement(), Constants.FRAGMENT_CLASSNAME ) ) 1463 { 1464 reportError( env, 1465 MemberChecks.must( Constants.EAGER_CLASSNAME, 1466 "only be present on a method if the method is enclosed in a type annotated with " + 1467 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) ), 1468 element ); 1469 } 1470 } 1471 else 1472 { 1473 reportError( env, 1474 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + " target is not valid", 1475 element ); 1476 } 1477 } 1478 } 1479 1480 private void processFragment( @Nonnull final TypeElement element ) 1481 { 1482 debug( () -> "Processing Fragment: " + element ); 1483 if ( ElementKind.INTERFACE != element.getKind() ) 1484 { 1485 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, "be an interface" ), 1486 element ); 1487 } 1488 else if ( !element.getTypeParameters().isEmpty() ) 1489 { 1490 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, "have type parameters" ), 1491 element ); 1492 } 1493 else if ( !element.getInterfaces().isEmpty() ) 1494 { 1495 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, "extend any interfaces" ), 1496 element ); 1497 } 1498 final List<IncludeDescriptor> includes = extractIncludes( element, Constants.FRAGMENT_CLASSNAME ); 1499 final Map<ExecutableElement, Binding> bindings = new LinkedHashMap<>(); 1500 for ( final Element enclosedElement : element.getEnclosedElements() ) 1501 { 1502 final ElementKind enclosedElementKind = enclosedElement.getKind(); 1503 if ( ElementKind.METHOD == enclosedElementKind ) 1504 { 1505 processProvidesMethod( element, bindings, (ExecutableElement) enclosedElement ); 1506 } 1507 if ( enclosedElementKind.isClass() || enclosedElementKind.isInterface() ) 1508 { 1509 throw new ProcessorException( MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1510 " target must not contain any types", 1511 element ); 1512 } 1513 } 1514 if ( bindings.isEmpty() && includes.isEmpty() ) 1515 { 1516 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 1517 "contain one or more methods or one or more includes" ), 1518 element ); 1519 } 1520 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 1521 if ( !scopedAnnotations.isEmpty() ) 1522 { 1523 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1524 "be annotated with an annotation that is " + 1525 "annotated with the " + Constants.JSR_330_SCOPE_CLASSNAME + 1526 " annotation such as " + scopedAnnotations ), 1527 element ); 1528 } 1529 _registry.registerFragment( new FragmentDescriptor( element, includes, bindings.values() ) ); 1530 } 1531 1532 private void processAutoFragment( @Nonnull final TypeElement element ) 1533 { 1534 debug( () -> "Processing Auto-Fragment: " + element ); 1535 if ( ElementKind.INTERFACE != element.getKind() ) 1536 { 1537 throw new ProcessorException( MemberChecks.must( Constants.AUTO_FRAGMENT_CLASSNAME, "be an interface" ), 1538 element ); 1539 } 1540 else if ( !element.getTypeParameters().isEmpty() ) 1541 { 1542 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, "have type parameters" ), 1543 element ); 1544 } 1545 else if ( !element.getInterfaces().isEmpty() ) 1546 { 1547 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, "extend any interfaces" ), 1548 element ); 1549 } 1550 if ( !element.getEnclosedElements().isEmpty() ) 1551 { 1552 final Element enclosedElement = element.getEnclosedElements().get( 0 ); 1553 final ElementKind kind = enclosedElement.getKind(); 1554 if ( kind.isField() ) 1555 { 1556 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, "contain any fields" ), 1557 element ); 1558 } 1559 else if ( ElementKind.METHOD == kind ) 1560 { 1561 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, "contain any methods" ), 1562 element ); 1563 } 1564 else 1565 { 1566 assert kind.isClass() || kind.isInterface(); 1567 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, "contain any types" ), 1568 element ); 1569 } 1570 } 1571 final String key = (String) 1572 AnnotationsUtil.getAnnotationValue( element, Constants.AUTO_FRAGMENT_CLASSNAME, "value" ).getValue(); 1573 1574 final AutoFragmentDescriptor existing = _registry.findAutoFragmentByKey( key ); 1575 if ( null != existing ) 1576 { 1577 throw new ProcessorException( MemberChecks.mustNot( Constants.AUTO_FRAGMENT_CLASSNAME, 1578 "have the same key as an existing AutoFragment " + 1579 "of type " + existing.getElement().getQualifiedName() ), 1580 element ); 1581 } 1582 1583 _registry.registerAutoFragment( new AutoFragmentDescriptor( key, element ) ); 1584 } 1585 1586 private void processContributeTo( @Nonnull final TypeElement element ) 1587 { 1588 debug( () -> "Processing ContributeTo: " + element ); 1589 if ( !AnnotationsUtil.hasAnnotationOfType( element, Constants.FRAGMENT_CLASSNAME ) && 1590 !AnnotationsUtil.hasAnnotationOfType( element, Constants.INJECTABLE_CLASSNAME ) && 1591 !hasStingProvider( element ) ) 1592 { 1593 if ( hasActAsStingComponent( element ) ) 1594 { 1595 debug( () -> "ContributeTo Element skipped due to @ActAsStringComponent: " + element ); 1596 return; 1597 } 1598 else 1599 { 1600 throw new ProcessorException( MemberChecks.must( Constants.CONTRIBUTE_TO_CLASSNAME, 1601 "be annotated with " + 1602 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1603 ", " + 1604 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1605 " or be annotated with an annotation annotated by " + 1606 "@ActAsStringComponent or @StingProvider" ), 1607 element ); 1608 } 1609 } 1610 final String key = (String) 1611 AnnotationsUtil.getAnnotationValue( element, Constants.CONTRIBUTE_TO_CLASSNAME, "value" ).getValue(); 1612 1613 final AutoFragmentDescriptor autoFragment = _registry.findAutoFragmentByKey( key ); 1614 if ( null != autoFragment && autoFragment.isFragmentGenerated() ) 1615 { 1616 throw new ProcessorException( MemberChecks.toSimpleName( Constants.CONTRIBUTE_TO_CLASSNAME ) + 1617 " target attempted to be added to the " + 1618 MemberChecks.toSimpleName( Constants.AUTO_FRAGMENT_CLASSNAME ) + 1619 " annotated type " + autoFragment.getElement().getQualifiedName() + 1620 " but the " + MemberChecks.toSimpleName( Constants.AUTO_FRAGMENT_CLASSNAME ) + 1621 " annotated type has already generated fragment", 1622 element ); 1623 } 1624 1625 boolean autoDiscoverable = false; 1626 final InjectableDescriptor injectable = 1627 _registry.findInjectableByClassName( element.getQualifiedName().toString() ); 1628 if ( null != injectable && !injectable.getBinding().isEager() && injectable.isAutoDiscoverable() ) 1629 { 1630 autoDiscoverable = true; 1631 if ( ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_AUTO_DISCOVERABLE_CONTRIBUTED ) ) 1632 { 1633 final String message = 1634 MemberChecks.shouldNot( Constants.CONTRIBUTE_TO_CLASSNAME, 1635 "be an auto-discoverable type. " + 1636 MemberChecks.suppressedBy( Constants.WARNING_AUTO_DISCOVERABLE_CONTRIBUTED ) ); 1637 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, element ); 1638 } 1639 } 1640 1641 final ContributorDescriptor contributor = new ContributorDescriptor( key, element, autoDiscoverable ); 1642 _registry.registerContributor( contributor ); 1643 if ( null != autoFragment ) 1644 { 1645 autoFragment.markAsModified(); 1646 autoFragment.getContributors().add( contributor ); 1647 } 1648 } 1649 1650 @SuppressWarnings( "unchecked" ) 1651 @Nonnull 1652 private List<InputDescriptor> extractInputs( @Nonnull final TypeElement element ) 1653 { 1654 final List<InputDescriptor> results = new ArrayList<>(); 1655 final AnnotationMirror annotation = AnnotationsUtil.getAnnotationByType( element, Constants.INJECTOR_CLASSNAME ); 1656 final AnnotationValue inputsAnnotationValue = AnnotationsUtil.findAnnotationValue( annotation, "inputs" ); 1657 assert null != inputsAnnotationValue; 1658 final List<AnnotationMirror> inputs = (List<AnnotationMirror>) inputsAnnotationValue.getValue(); 1659 1660 final int size = inputs.size(); 1661 for ( int i = 0; i < size; i++ ) 1662 { 1663 final AnnotationMirror input = inputs.get( i ); 1664 final String qualifier = AnnotationsUtil.getAnnotationValueValue( input, "qualifier" ); 1665 final AnnotationValue typeAnnotationValue = AnnotationsUtil.getAnnotationValue( input, "type" ); 1666 final TypeMirror type = (TypeMirror) typeAnnotationValue.getValue(); 1667 if ( TypeKind.ARRAY == type.getKind() ) 1668 { 1669 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1670 " must not specify an array type for the type parameter", 1671 element, 1672 input, 1673 typeAnnotationValue ); 1674 } 1675 else if ( TypeKind.VOID == type.getKind() ) 1676 { 1677 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1678 " must specify a non-void type for the type parameter", 1679 element, 1680 input, 1681 typeAnnotationValue ); 1682 } 1683 else if ( TypeKind.DECLARED == type.getKind() && 1684 !( (TypeElement) ( (DeclaredType) type ).asElement() ).getTypeParameters().isEmpty() ) 1685 { 1686 throw new ProcessorException( MemberChecks.toSimpleName( Constants.INPUT_CLASSNAME ) + 1687 " must not specify a parameterized type for the type parameter", 1688 element, 1689 input, 1690 typeAnnotationValue ); 1691 } 1692 final Coordinate coordinate = new Coordinate( qualifier, type ); 1693 final boolean optional = AnnotationsUtil.getAnnotationValueValue( input, "optional" ); 1694 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 1695 final Binding binding = 1696 new Binding( Binding.Kind.INPUT, 1697 element.getQualifiedName() + "#" + i, 1698 Collections.singletonList( service ), 1699 true, 1700 element, 1701 new ServiceRequest[ 0 ] ); 1702 results.add( new InputDescriptor( service, binding, "input" + ( i + 1 ) ) ); 1703 } 1704 return results; 1705 } 1706 1707 @Nonnull 1708 private List<IncludeDescriptor> extractIncludes( @Nonnull final TypeElement element, 1709 @Nonnull final String annotationClassname ) 1710 { 1711 final List<IncludeDescriptor> results = new ArrayList<>(); 1712 final List<TypeMirror> includes = 1713 AnnotationsUtil.getTypeMirrorsAnnotationParameter( element, annotationClassname, "includes" ); 1714 final Set<String> included = new HashSet<>(); 1715 for ( final TypeMirror include : includes ) 1716 { 1717 if ( processingEnv.getTypeUtils().isSameType( include, element.asType() ) ) 1718 { 1719 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1720 " target must not include self", 1721 element ); 1722 } 1723 if ( include.getKind().isPrimitive() ) 1724 { 1725 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1726 " target must not include a primitive in the includes parameter", 1727 element ); 1728 } 1729 final Element includeElement = processingEnv.getTypeUtils().asElement( include ); 1730 if ( AnnotationsUtil.hasAnnotationOfType( includeElement, Constants.FRAGMENT_CLASSNAME ) || 1731 AnnotationsUtil.hasAnnotationOfType( includeElement, Constants.INJECTABLE_CLASSNAME ) ) 1732 { 1733 results.add( new IncludeDescriptor( (DeclaredType) include, include.toString(), false ) ); 1734 } 1735 else 1736 { 1737 final ElementKind kind = includeElement.getKind(); 1738 if ( ElementKind.CLASS == kind || ElementKind.INTERFACE == kind ) 1739 { 1740 final List<ProviderEntry> providers = 1741 includeElement.getAnnotationMirrors() 1742 .stream() 1743 .map( a -> { 1744 final AnnotationMirror provider = 1745 getStingProvider( element, annotationClassname, (TypeElement) includeElement, a ); 1746 return null != provider ? new ProviderEntry( a, provider ) : null; 1747 } ) 1748 .filter( Objects::nonNull ) 1749 .collect( Collectors.toList() ); 1750 if ( providers.size() > 1 ) 1751 { 1752 final String message = 1753 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 1754 "'includes' parameter containing the value " + includeElement.asType() + 1755 " that is annotated by multiple @StingProvider annotations. Matching annotations:\n" + 1756 providers 1757 .stream() 1758 .map( a -> ( (TypeElement) a.getAnnotation().getAnnotationType().asElement() ).getQualifiedName() ) 1759 .map( a -> " " + a ) 1760 .collect( Collectors.joining( "\n" ) ); 1761 throw new ProcessorException( message, element ); 1762 } 1763 else if ( !providers.isEmpty() ) 1764 { 1765 final ProviderEntry entry = providers.get( 0 ); 1766 final AnnotationMirror providerAnnotation = entry.getProvider(); 1767 final String namePattern = AnnotationsUtil.getAnnotationValueValue( providerAnnotation, "value" ); 1768 1769 final String targetCompoundType = 1770 namePattern 1771 .replace( "[SimpleName]", includeElement.getSimpleName().toString() ) 1772 .replace( "[CompoundName]", getComponentName( (TypeElement) includeElement ) ) 1773 .replace( "[EnclosingName]", getEnclosingName( (TypeElement) includeElement ) ) 1774 .replace( "[FlatEnclosingName]", getEnclosingName( (TypeElement) includeElement ).replace( '.', '_' ) ); 1775 1776 final String targetQualifiedName = 1777 ElementsUtil.getPackageElement( includeElement ).getQualifiedName().toString() + "." + targetCompoundType; 1778 1779 results.add( new IncludeDescriptor( (DeclaredType) include, targetQualifiedName, false ) ); 1780 } 1781 else 1782 { 1783 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1784 " target has an includes parameter containing the value " + include + 1785 " that is not a type annotated by either " + 1786 MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1787 " or " + 1788 MemberChecks.toSimpleName( Constants.INJECTABLE_CLASSNAME ) + 1789 " and the type does not declare a provider", 1790 element ); 1791 } 1792 } 1793 } 1794 final String includedType = include.toString(); 1795 if ( included.contains( includedType ) ) 1796 { 1797 throw new ProcessorException( MemberChecks.toSimpleName( annotationClassname ) + 1798 " target has an includes parameter containing duplicate " + 1799 "includes with the type " + includedType, 1800 element ); 1801 } 1802 else 1803 { 1804 included.add( includedType ); 1805 } 1806 } 1807 return results; 1808 } 1809 1810 @Nullable 1811 private AnnotationMirror getStingProvider( @Nonnull final TypeElement element, 1812 @Nonnull final String annotationClassname, 1813 @Nonnull final TypeElement includeElement, 1814 @Nonnull final AnnotationMirror annotation ) 1815 { 1816 return annotation.getAnnotationType() 1817 .asElement() 1818 .getAnnotationMirrors() 1819 .stream() 1820 .filter( ca -> isStingProvider( element, annotationClassname, includeElement, ca ) ) 1821 .findAny() 1822 .orElse( null ); 1823 } 1824 1825 @Nonnull 1826 private String getComponentName( @Nonnull final TypeElement element ) 1827 { 1828 return getEnclosingName( element ) + element.getSimpleName(); 1829 } 1830 1831 @Nonnull 1832 private String getEnclosingName( @Nonnull final TypeElement element ) 1833 { 1834 Element enclosingElement = element.getEnclosingElement(); 1835 final List<String> nameParts = new ArrayList<>(); 1836 while ( ElementKind.PACKAGE != enclosingElement.getKind() ) 1837 { 1838 nameParts.add( enclosingElement.getSimpleName().toString() ); 1839 enclosingElement = enclosingElement.getEnclosingElement(); 1840 } 1841 if ( nameParts.isEmpty() ) 1842 { 1843 return ""; 1844 } 1845 else 1846 { 1847 Collections.reverse( nameParts ); 1848 return String.join( ".", nameParts ) + "."; 1849 } 1850 } 1851 1852 private boolean isStingProvider( @Nonnull final TypeElement element, 1853 @Nonnull final String annotationClassname, 1854 @Nonnull final Element includeElement, 1855 @Nonnull final AnnotationMirror annotation ) 1856 { 1857 if ( !annotation.getAnnotationType().asElement().getSimpleName().contentEquals( "StingProvider" ) ) 1858 { 1859 return false; 1860 } 1861 else 1862 { 1863 final boolean nameMatched = annotation.getElementValues() 1864 .entrySet() 1865 .stream() 1866 .anyMatch( e -> e.getKey().getSimpleName().contentEquals( "value" ) && 1867 e.getValue().getValue() instanceof String ); 1868 if ( nameMatched ) 1869 { 1870 return true; 1871 } 1872 else 1873 { 1874 final String message = 1875 MemberChecks.toSimpleName( annotationClassname ) + " target has an " + 1876 "'includes' parameter containing the value " + includeElement.asType() + 1877 " that is annotated by " + annotation + " that is annotated by an invalid @StingProvider " + 1878 "annotation missing a 'value' parameter of type string."; 1879 throw new ProcessorException( message, element ); 1880 } 1881 } 1882 } 1883 1884 private void emitFragmentJsonDescriptor( @Nonnull final FragmentDescriptor fragment ) 1885 throws IOException 1886 { 1887 if ( _emitJsonDescriptors ) 1888 { 1889 final TypeElement element = fragment.getElement(); 1890 final String filename = toFilename( element ) + JSON_SUFFIX; 1891 JsonUtil.writeJsonResource( processingEnv, element, filename, fragment::write ); 1892 } 1893 } 1894 1895 private void processProvidesMethod( @Nonnull final TypeElement element, 1896 @Nonnull final Map<ExecutableElement, Binding> bindings, 1897 @Nonnull final ExecutableElement method ) 1898 { 1899 if ( TypeKind.VOID == method.getReturnType().getKind() ) 1900 { 1901 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 1902 "only contain methods that return a value" ), 1903 method ); 1904 } 1905 if ( !method.getTypeParameters().isEmpty() ) 1906 { 1907 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1908 "contain methods with a type parameter" ), 1909 method ); 1910 } 1911 if ( !method.getModifiers().contains( Modifier.DEFAULT ) ) 1912 { 1913 throw new ProcessorException( MemberChecks.must( Constants.FRAGMENT_CLASSNAME, 1914 "only contain methods with a default modifier" ), 1915 method ); 1916 } 1917 final boolean nullablePresent = AnnotationsUtil.hasNullableAnnotation( method ); 1918 if ( nullablePresent && method.getReturnType().getKind().isPrimitive() ) 1919 { 1920 throw new ProcessorException( MemberChecks.toSimpleName( Constants.FRAGMENT_CLASSNAME ) + 1921 " contains a method that is incorrectly annotated with " + 1922 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 1923 " as the return type is a primitive value", 1924 method ); 1925 } 1926 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( method ); 1927 if ( !scopedAnnotations.isEmpty() ) 1928 { 1929 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1930 "contain a method that is annotated with an " + 1931 "annotation that is annotated with the " + 1932 Constants.JSR_330_SCOPE_CLASSNAME + 1933 " annotation such as " + scopedAnnotations ), 1934 method ); 1935 } 1936 1937 final boolean eager = AnnotationsUtil.hasAnnotationOfType( method, Constants.EAGER_CLASSNAME ); 1938 1939 final List<ServiceRequest> dependencies = new ArrayList<>(); 1940 int index = 0; 1941 final List<? extends TypeMirror> parameterTypes = ( (ExecutableType) method.asType() ).getParameterTypes(); 1942 for ( final VariableElement parameter : method.getParameters() ) 1943 { 1944 dependencies.add( processFragmentServiceParameter( parameter, parameterTypes.get( index ), index ) ); 1945 index++; 1946 } 1947 1948 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( method, Constants.TYPED_CLASSNAME ); 1949 final AnnotationValue value = 1950 null != annotation ? AnnotationsUtil.findAnnotationValue( annotation, "value" ) : null; 1951 1952 final String qualifier = getQualifier( method ); 1953 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.JSR_330_NAMED_CLASSNAME ) ) 1954 { 1955 final String message = 1956 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1957 "contain a method annotated with the " + Constants.JSR_330_NAMED_CLASSNAME + 1958 " annotation. Use the " + Constants.NAMED_CLASSNAME + " annotation instead" ); 1959 throw new ProcessorException( message, method ); 1960 } 1961 if ( AnnotationsUtil.hasAnnotationOfType( method, Constants.CDI_TYPED_CLASSNAME ) ) 1962 { 1963 final String message = 1964 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 1965 "contain a method annotated with the " + Constants.CDI_TYPED_CLASSNAME + 1966 " annotation. Use the " + Constants.TYPED_CLASSNAME + " annotation instead" ); 1967 throw new ProcessorException( message, method ); 1968 } 1969 1970 @SuppressWarnings( "unchecked" ) 1971 final List<TypeMirror> types = 1972 null == value ? 1973 Collections.singletonList( method.getReturnType() ) : 1974 ( (List<AnnotationValue>) value.getValue() ) 1975 .stream() 1976 .map( v -> (TypeMirror) v.getValue() ) 1977 .collect( Collectors.toList() ); 1978 1979 final ServiceSpec[] specs = new ServiceSpec[ types.size() ]; 1980 for ( int i = 0; i < specs.length; i++ ) 1981 { 1982 final TypeMirror type = types.get( i ); 1983 if ( !processingEnv.getTypeUtils().isAssignable( method.getReturnType(), type ) ) 1984 { 1985 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 1986 " specified a type that is not assignable to the return type of the method", 1987 element, 1988 annotation, 1989 value ); 1990 } 1991 else if ( TypeKind.DECLARED == type.getKind() && isParameterized( (DeclaredType) type ) ) 1992 { 1993 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 1994 " specified a type that is a a parameterized type", 1995 element, 1996 annotation, 1997 value ); 1998 } 1999 specs[ i ] = new ServiceSpec( new Coordinate( qualifier, type ), nullablePresent ); 2000 } 2001 2002 if ( 0 == specs.length && !eager ) 2003 { 2004 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2005 "contain methods that specify zero types with the " + 2006 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2007 " annotation and are not annotated with the " + 2008 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + 2009 " annotation otherwise the component can not be created by " + 2010 "the injector" ), 2011 element ); 2012 } 2013 if ( 0 == specs.length && !"".equals( qualifier ) ) 2014 { 2015 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2016 "contain methods that specify zero types with the " + 2017 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2018 " annotation and specify a qualifier with the " + 2019 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + 2020 " annotation as the qualifier is meaningless" ), 2021 element ); 2022 } 2023 2024 final Binding binding = 2025 new Binding( Binding.Kind.PROVIDES, 2026 element.getQualifiedName() + "#" + method.getSimpleName(), 2027 Arrays.asList( specs ), 2028 eager, 2029 method, 2030 dependencies.toArray( new ServiceRequest[ 0 ] ) ); 2031 bindings.put( method, binding ); 2032 } 2033 2034 @Nonnull 2035 private ServiceRequest processFragmentServiceParameter( @Nonnull final VariableElement parameter, 2036 @Nonnull final TypeMirror type, 2037 final int parameterIndex ) 2038 { 2039 if ( TypesUtil.containsArrayType( type ) ) 2040 { 2041 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2042 "contain a method with a parameter that contains an array type" ), 2043 parameter ); 2044 } 2045 else if ( TypesUtil.containsWildcard( type ) ) 2046 { 2047 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2048 "contain a method with a parameter that contains a wildcard type parameter" ), 2049 parameter ); 2050 } 2051 else if ( TypesUtil.containsRawType( type ) ) 2052 { 2053 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2054 "contain a method with a parameter that contains a raw type" ), 2055 parameter ); 2056 } 2057 else 2058 { 2059 TypeMirror dependencyType = null; 2060 ServiceRequest.Kind kind = null; 2061 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 2062 { 2063 dependencyType = candidate.extractType( type ); 2064 if ( null != dependencyType ) 2065 { 2066 kind = candidate; 2067 break; 2068 } 2069 } 2070 if ( null == kind ) 2071 { 2072 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2073 "contain a method with a parameter that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 2074 parameter ); 2075 } 2076 else 2077 { 2078 final boolean optional = AnnotationsUtil.hasNullableAnnotation( parameter ); 2079 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 2080 { 2081 throw new ProcessorException( MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2082 "contain a method with a parameter annotated with the " + 2083 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 2084 " annotation that is not an instance dependency kind" ), 2085 parameter ); 2086 } 2087 final String qualifier = getQualifier( parameter ); 2088 if ( AnnotationsUtil.hasAnnotationOfType( parameter, Constants.JSR_330_NAMED_CLASSNAME ) ) 2089 { 2090 final String message = 2091 MemberChecks.mustNot( Constants.FRAGMENT_CLASSNAME, 2092 "contain a method with a parameter annotated with the " + 2093 Constants.JSR_330_NAMED_CLASSNAME + " annotation. Use the " + 2094 Constants.NAMED_CLASSNAME + " annotation instead" ); 2095 throw new ProcessorException( message, parameter ); 2096 } 2097 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 2098 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 2099 return new ServiceRequest( kind, service, parameter, parameterIndex ); 2100 } 2101 } 2102 } 2103 2104 private void processInjectable( @Nonnull final TypeElement element ) 2105 { 2106 debug( () -> "Processing Injectable: " + element ); 2107 if ( ElementKind.CLASS != element.getKind() ) 2108 { 2109 throw new ProcessorException( MemberChecks.must( Constants.INJECTABLE_CLASSNAME, "be a class" ), 2110 element ); 2111 } 2112 else if ( element.getModifiers().contains( Modifier.ABSTRACT ) ) 2113 { 2114 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, "be abstract" ), 2115 element ); 2116 } 2117 else if ( ElementsUtil.isNonStaticNestedClass( element ) ) 2118 { 2119 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2120 "be a non-static nested class" ), 2121 element ); 2122 } 2123 else if ( !element.getTypeParameters().isEmpty() ) 2124 { 2125 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, "have type parameters" ), 2126 element ); 2127 } 2128 final List<ExecutableElement> constructors = ElementsUtil.getConstructors( element ); 2129 final ExecutableElement constructor = constructors.get( 0 ); 2130 if ( constructors.size() > 1 ) 2131 { 2132 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2133 "have multiple constructors" ), 2134 element ); 2135 } 2136 injectableConstructorShouldNotBeProtected( constructor ); 2137 injectableConstructorShouldNotBePublic( constructor ); 2138 injectableShouldNotHaveScopedAnnotation( element ); 2139 2140 final boolean eager = AnnotationsUtil.hasAnnotationOfType( element, Constants.EAGER_CLASSNAME ); 2141 2142 final List<ServiceRequest> dependencies = new ArrayList<>(); 2143 int index = 0; 2144 final List<? extends TypeMirror> parameterTypes = ( (ExecutableType) constructor.asType() ).getParameterTypes(); 2145 for ( final VariableElement parameter : constructor.getParameters() ) 2146 { 2147 dependencies.add( handleConstructorParameter( parameter, parameterTypes.get( index ), index ) ); 2148 index++; 2149 } 2150 2151 final AnnotationMirror annotation = 2152 AnnotationsUtil.findAnnotationByType( element, Constants.TYPED_CLASSNAME ); 2153 final AnnotationValue value = 2154 null != annotation ? AnnotationsUtil.findAnnotationValue( annotation, "value" ) : null; 2155 2156 final String qualifier = getQualifier( element ); 2157 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.JSR_330_NAMED_CLASSNAME ) && 2158 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_JSR_330_NAMED ) ) 2159 { 2160 final String message = 2161 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2162 "be annotated with the " + Constants.JSR_330_NAMED_CLASSNAME + " annotation. " + 2163 "Use the " + Constants.NAMED_CLASSNAME + " annotation instead. " + 2164 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_NAMED ) ); 2165 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, element ); 2166 } 2167 if ( AnnotationsUtil.hasAnnotationOfType( element, Constants.CDI_TYPED_CLASSNAME ) && 2168 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_CDI_TYPED ) ) 2169 { 2170 final String message = 2171 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2172 "be annotated with the " + Constants.CDI_TYPED_CLASSNAME + " annotation. " + 2173 "Use the " + Constants.TYPED_CLASSNAME + " annotation instead. " + 2174 MemberChecks.suppressedBy( Constants.WARNING_CDI_TYPED ) ); 2175 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, element ); 2176 } 2177 if ( AnnotationsUtil.hasAnnotationOfType( constructor, Constants.JSR_330_INJECT_CLASSNAME ) && 2178 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_JSR_330_INJECT ) ) 2179 { 2180 final String message = 2181 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2182 "be annotated with the " + Constants.JSR_330_INJECT_CLASSNAME + " annotation. " + 2183 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_INJECT ) ); 2184 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, constructor ); 2185 } 2186 @SuppressWarnings( "unchecked" ) 2187 final List<TypeMirror> types = 2188 null == value ? 2189 Collections.singletonList( element.asType() ) : 2190 ( (List<AnnotationValue>) value.getValue() ) 2191 .stream() 2192 .map( v -> (TypeMirror) v.getValue() ) 2193 .collect( Collectors.toList() ); 2194 2195 final ServiceSpec[] specs = new ServiceSpec[ types.size() ]; 2196 for ( int i = 0; i < specs.length; i++ ) 2197 { 2198 final TypeMirror type = types.get( i ); 2199 if ( !processingEnv.getTypeUtils().isAssignable( element.asType(), type ) ) 2200 { 2201 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2202 " specified a type that is not assignable to the declaring type", 2203 element, 2204 annotation, 2205 value ); 2206 } 2207 else if ( TypeKind.DECLARED == type.getKind() && isParameterized( (DeclaredType) type ) ) 2208 { 2209 throw new ProcessorException( MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2210 " specified a type that is a a parameterized type", 2211 element, 2212 annotation, 2213 value ); 2214 } 2215 specs[ i ] = new ServiceSpec( new Coordinate( qualifier, type ), false ); 2216 } 2217 2218 if ( 0 == specs.length && !eager ) 2219 { 2220 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2221 "specify zero types with the " + 2222 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2223 " annotation or must be annotated with the " + 2224 MemberChecks.toSimpleName( Constants.EAGER_CLASSNAME ) + 2225 " annotation otherwise the component can not be created by the injector" ), 2226 element ); 2227 } 2228 if ( 0 == specs.length && !"".equals( qualifier ) ) 2229 { 2230 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2231 "specify zero types with the " + 2232 MemberChecks.toSimpleName( Constants.TYPED_CLASSNAME ) + 2233 " annotation and specify a qualifier with the " + 2234 MemberChecks.toSimpleName( Constants.NAMED_CLASSNAME ) + 2235 " annotation as the qualifier is meaningless" ), 2236 element ); 2237 } 2238 2239 final Binding binding = 2240 new Binding( Binding.Kind.INJECTABLE, 2241 element.getQualifiedName().toString(), 2242 Arrays.asList( specs ), 2243 eager, 2244 constructor, 2245 dependencies.toArray( new ServiceRequest[ 0 ] ) ); 2246 final InjectableDescriptor injectable = new InjectableDescriptor( binding ); 2247 _registry.registerInjectable( injectable ); 2248 } 2249 2250 private void writeBinaryDescriptor( @Nonnull final TypeElement element, 2251 @Nonnull final Object descriptor ) 2252 throws IOException 2253 { 2254 final String[] nameParts = extractNameParts( element ); 2255 2256 // Write out the descriptor 2257 final FileObject resource = 2258 processingEnv.getFiler().createResource( StandardLocation.CLASS_OUTPUT, nameParts[ 0 ], nameParts[ 1 ], element ); 2259 try ( final OutputStream out = resource.openOutputStream() ) 2260 { 2261 try ( final DataOutputStream dos = new DataOutputStream( out ) ) 2262 { 2263 _descriptorIO.write( dos, descriptor ); 2264 } 2265 } 2266 2267 if ( _verifyDescriptors ) 2268 { 2269 verifyDescriptor( element, descriptor ); 2270 } 2271 } 2272 2273 @Nonnull 2274 private String[] extractNameParts( @Nonnull final TypeElement element ) 2275 { 2276 final String binaryName = processingEnv.getElementUtils().getBinaryName( element ).toString(); 2277 final int lastIndex = binaryName.lastIndexOf( "." ); 2278 final String packageName = -1 == lastIndex ? "" : binaryName.substring( 0, lastIndex ); 2279 final String relativeName = binaryName.substring( -1 == lastIndex ? 0 : lastIndex + 1 ) + SUFFIX; 2280 2281 return new String[]{ packageName, relativeName }; 2282 } 2283 2284 private void verifyDescriptor( @Nonnull final TypeElement element, @Nonnull final Object descriptor ) 2285 throws IOException 2286 { 2287 final ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); 2288 try ( final DataOutputStream dos = new DataOutputStream( baos1 ) ) 2289 { 2290 _descriptorIO.write( dos, descriptor ); 2291 } 2292 final Object newDescriptor; 2293 try ( final DataInputStream dos = new DataInputStream( new ByteArrayInputStream( baos1.toByteArray() ) ) ) 2294 { 2295 newDescriptor = _descriptorIO.read( dos, element.getQualifiedName().toString() ); 2296 } 2297 final ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); 2298 try ( final DataOutputStream dos = new DataOutputStream( baos2 ) ) 2299 { 2300 _descriptorIO.write( dos, newDescriptor ); 2301 } 2302 2303 if ( !Arrays.equals( baos1.toByteArray(), baos2.toByteArray() ) ) 2304 { 2305 throw new ProcessorException( "Failed to emit valid binary descriptor for " + element.getQualifiedName() + 2306 ". Reading the emitted descriptor did not produce an equivalent descriptor.", 2307 element ); 2308 } 2309 } 2310 2311 private void emitInjectableJsonDescriptor( @Nonnull final InjectableDescriptor injectable ) 2312 throws IOException 2313 { 2314 if ( _emitJsonDescriptors ) 2315 { 2316 final TypeElement element = injectable.getElement(); 2317 final String filename = toFilename( element ) + JSON_SUFFIX; 2318 JsonUtil.writeJsonResource( processingEnv, element, filename, injectable::write ); 2319 } 2320 } 2321 2322 @Nonnull 2323 private String toFilename( @Nonnull final TypeElement typeElement ) 2324 { 2325 return GeneratorUtil.getGeneratedClassName( typeElement, "", "" ).toString().replace( ".", "/" ); 2326 } 2327 2328 @Nonnull 2329 private ServiceRequest handleConstructorParameter( @Nonnull final VariableElement parameter, 2330 @Nonnull final TypeMirror type, 2331 final int parameterIndex ) 2332 { 2333 if ( TypesUtil.containsArrayType( type ) ) 2334 { 2335 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2336 "contain a constructor with a parameter that contains an array type" ), 2337 parameter ); 2338 } 2339 else if ( TypesUtil.containsWildcard( type ) ) 2340 { 2341 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2342 "contain a constructor with a parameter that contains a wildcard type parameter" ), 2343 parameter ); 2344 } 2345 else if ( TypesUtil.containsRawType( type ) ) 2346 { 2347 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2348 "contain a constructor with a parameter that contains a raw type" ), 2349 parameter ); 2350 } 2351 else 2352 { 2353 TypeMirror dependencyType = null; 2354 ServiceRequest.Kind kind = null; 2355 for ( final ServiceRequest.Kind candidate : ServiceRequest.Kind.values() ) 2356 { 2357 dependencyType = candidate.extractType( type ); 2358 if ( null != dependencyType ) 2359 { 2360 kind = candidate; 2361 break; 2362 } 2363 } 2364 if ( null == kind ) 2365 { 2366 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2367 "contain a constructor with a parameter that contains an unexpected parameterized type. Only parameterized types known to the framework are supported" ), 2368 parameter ); 2369 } 2370 else 2371 { 2372 final boolean optional = AnnotationsUtil.hasNullableAnnotation( parameter ); 2373 if ( optional && ServiceRequest.Kind.INSTANCE != kind ) 2374 { 2375 throw new ProcessorException( MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2376 "contain a constructor with a parameter annotated with " + 2377 MemberChecks.toSimpleName( AnnotationsUtil.NULLABLE_CLASSNAME ) + 2378 " that is not an instance dependency kind" ), 2379 parameter ); 2380 } 2381 final String qualifier = getQualifier( parameter ); 2382 if ( AnnotationsUtil.hasAnnotationOfType( parameter, Constants.JSR_330_NAMED_CLASSNAME ) && 2383 ElementsUtil.isWarningNotSuppressed( parameter, Constants.WARNING_JSR_330_NAMED ) ) 2384 { 2385 final String message = 2386 MemberChecks.mustNot( Constants.INJECTABLE_CLASSNAME, 2387 "contain a constructor with a parameter annotated with the " + 2388 Constants.JSR_330_NAMED_CLASSNAME + " annotation. Use the " + 2389 Constants.NAMED_CLASSNAME + " annotation instead. " + 2390 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_NAMED ) ); 2391 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, parameter ); 2392 } 2393 final Coordinate coordinate = new Coordinate( qualifier, dependencyType ); 2394 final ServiceSpec service = new ServiceSpec( coordinate, optional ); 2395 return new ServiceRequest( kind, service, parameter, parameterIndex ); 2396 } 2397 } 2398 } 2399 2400 @Nonnull 2401 private String getQualifier( @Nonnull final Element element ) 2402 { 2403 final AnnotationMirror annotation = AnnotationsUtil.findAnnotationByType( element, Constants.NAMED_CLASSNAME ); 2404 return null == annotation ? "" : AnnotationsUtil.getAnnotationValueValue( annotation, "value" ); 2405 } 2406 2407 private void injectableShouldNotHaveScopedAnnotation( @Nonnull final TypeElement element ) 2408 { 2409 final List<? extends AnnotationMirror> scopedAnnotations = getScopedAnnotations( element ); 2410 if ( !scopedAnnotations.isEmpty() && 2411 ElementsUtil.isWarningNotSuppressed( element, Constants.WARNING_JSR_330_SCOPED ) ) 2412 { 2413 final String message = 2414 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 2415 "be annotated with an annotation that is annotated with the " + 2416 Constants.JSR_330_SCOPE_CLASSNAME + " annotation such as " + scopedAnnotations + ". " + 2417 MemberChecks.suppressedBy( Constants.WARNING_JSR_330_SCOPED ) ); 2418 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, element ); 2419 } 2420 } 2421 2422 private void injectableConstructorShouldNotBePublic( @Nonnull final ExecutableElement constructor ) 2423 { 2424 if ( ElementsUtil.isNotSynthetic( constructor ) && 2425 constructor.getModifiers().contains( Modifier.PUBLIC ) && 2426 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_PUBLIC_CONSTRUCTOR ) ) 2427 { 2428 final String message = 2429 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 2430 "have a public constructor. The type is instantiated by the injector " + 2431 "and should have a package-access constructor. " + 2432 MemberChecks.suppressedBy( Constants.WARNING_PUBLIC_CONSTRUCTOR ) ); 2433 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, constructor ); 2434 } 2435 } 2436 2437 private void injectableConstructorShouldNotBeProtected( @Nonnull final ExecutableElement constructor ) 2438 { 2439 if ( constructor.getModifiers().contains( Modifier.PROTECTED ) && 2440 ElementsUtil.isWarningNotSuppressed( constructor, Constants.WARNING_PROTECTED_CONSTRUCTOR ) ) 2441 { 2442 final String message = 2443 MemberChecks.shouldNot( Constants.INJECTABLE_CLASSNAME, 2444 "have a protected constructor. The type is instantiated by the " + 2445 "injector and should have a package-access constructor. " + 2446 MemberChecks.suppressedBy( Constants.WARNING_PROTECTED_CONSTRUCTOR ) ); 2447 processingEnv.getMessager().printMessage( Diagnostic.Kind.WARNING, message, constructor ); 2448 } 2449 } 2450 2451 @Nullable 2452 private Object loadDescriptor( @Nonnull final Element originator, 2453 @Nonnull final String classname, 2454 @Nonnull final byte[] data ) 2455 { 2456 try 2457 { 2458 return _descriptorIO.read( new DataInputStream( new ByteArrayInputStream( data ) ), classname ); 2459 } 2460 catch ( final UnresolvedDeclaredTypeException e ) 2461 { 2462 return null; 2463 } 2464 catch ( final IOException e ) 2465 { 2466 throw new ProcessorException( "Failed to read the Sting descriptor for the type " + classname + ". Error: " + e, 2467 originator ); 2468 } 2469 } 2470 2471 @Nullable 2472 private byte[] tryLoadDescriptorData( @Nonnull final TypeElement element ) 2473 { 2474 byte[] data = tryLoadDescriptorData( StandardLocation.CLASS_PATH, element ); 2475 data = null != data ? data : tryLoadDescriptorData( StandardLocation.CLASS_OUTPUT, element ); 2476 // Some tools (IDEA?) will actually put dependencies on the boot/platform class path. This 2477 // seems like it should be an error but as long as the tools do this, the annotation processor 2478 // must also be capable of loading descriptor data from the platform classpath 2479 return null != data ? data : tryLoadDescriptorData( StandardLocation.PLATFORM_CLASS_PATH, element ); 2480 } 2481 2482 @Nullable 2483 private byte[] tryLoadDescriptorData( @Nonnull final JavaFileManager.Location location, 2484 @Nonnull final TypeElement element ) 2485 { 2486 final String[] nameParts = extractNameParts( element ); 2487 try 2488 { 2489 return IOUtil.readFully( processingEnv.getFiler().getResource( location, nameParts[ 0 ], nameParts[ 1 ] ) ); 2490 } 2491 catch ( final IOException ignored ) 2492 { 2493 return null; 2494 } 2495 catch ( final RuntimeException e ) 2496 { 2497 // The javac compiler in Java8 will return a null from the JavaFileManager when it should 2498 // throw an IOException which later causes a NullPointerException when wrapping the code 2499 // This ugly hack works around this scenario and just lets the compile continue 2500 if ( e.getClass().getCanonicalName().equals( "com.sun.tools.javac.util.ClientCodeException" ) && 2501 e.getCause() instanceof NullPointerException ) 2502 { 2503 return null; 2504 } 2505 else 2506 { 2507 throw e; 2508 } 2509 } 2510 } 2511 2512 private boolean isParameterized( @Nonnull final DeclaredType nestedParameterType ) 2513 { 2514 return !( (TypeElement) nestedParameterType.asElement() ).getTypeParameters().isEmpty(); 2515 } 2516 2517 @Nonnull 2518 private List<? extends AnnotationMirror> getScopedAnnotations( @Nonnull final Element element ) 2519 { 2520 return element 2521 .getAnnotationMirrors() 2522 .stream() 2523 .filter( a -> AnnotationsUtil.hasAnnotationOfType( a.getAnnotationType().asElement(), 2524 Constants.JSR_330_SCOPE_CLASSNAME ) ) 2525 .collect( Collectors.toList() ); 2526 } 2527}