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