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