001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2020 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.settings;
018
019import java.beans.IntrospectionException;
020
021import java.lang.annotation.Annotation;
022
023import java.lang.reflect.Executable;
024import java.lang.reflect.GenericArrayType;
025import java.lang.reflect.Member;
026import java.lang.reflect.Parameter;
027import java.lang.reflect.ParameterizedType;
028import java.lang.reflect.Type;
029import java.lang.reflect.TypeVariable;
030import java.lang.reflect.WildcardType;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.Map;
038import java.util.Map.Entry;
039import java.util.Objects;
040import java.util.Set;
041
042import java.util.function.BiFunction;
043import java.util.function.Supplier;
044
045import javax.enterprise.context.Dependent;
046
047import javax.enterprise.context.spi.CreationalContext;
048
049import javax.enterprise.event.Observes;
050
051import javax.enterprise.inject.Any;
052import javax.enterprise.inject.CreationException;
053import javax.enterprise.inject.Default;
054import javax.enterprise.inject.UnsatisfiedResolutionException;
055
056import javax.enterprise.inject.spi.AfterBeanDiscovery;
057import javax.enterprise.inject.spi.AfterDeploymentValidation;
058import javax.enterprise.inject.spi.Annotated;
059import javax.enterprise.inject.spi.AnnotatedField;
060import javax.enterprise.inject.spi.AnnotatedMethod;
061import javax.enterprise.inject.spi.AnnotatedParameter;
062import javax.enterprise.inject.spi.AnnotatedType;
063import javax.enterprise.inject.spi.Bean;
064import javax.enterprise.inject.spi.BeanAttributes;
065import javax.enterprise.inject.spi.BeanManager;
066import javax.enterprise.inject.spi.DefinitionException;
067import javax.enterprise.inject.spi.Extension;
068import javax.enterprise.inject.spi.InjectionPoint;
069import javax.enterprise.inject.spi.ProcessInjectionPoint;
070import javax.enterprise.inject.spi.Producer;
071import javax.enterprise.inject.spi.ProducerFactory;
072
073import javax.enterprise.util.TypeLiteral;
074
075import javax.inject.Singleton;
076
077/**
078 * An {@link Extension} providing support for the {@link Setting} and
079 * {@link Configured} annotations, as well as injection of {@link
080 * Settings} instances.
081 *
082 * @author <a href="https://about.me/lairdnelson"
083 * target="_parent">Laird Nelson</a>
084 */
085public class SettingsExtension implements Extension {
086
087
088  /*
089   * Instance fields.
090   */
091
092
093  private final Map<Set<Annotation>, Set<Type>> configuredTypes;
094
095  private final Set<InjectionPoint> settingInjectionPoints;
096  
097  private final Set<Set<Annotation>> settingQualifierSets;
098
099  private final Set<Set<Annotation>> settingsQualifierSets;
100
101  private final Set<Type> knownConversionTypes;
102
103
104  /*
105   * Constructors.
106   */
107
108
109  /**
110   * Creates a new {@link SettingsExtension}.
111   */
112  public SettingsExtension() {
113    super();
114    this.configuredTypes = new HashMap<>();
115    this.settingInjectionPoints = new HashSet<>();
116    this.settingQualifierSets = new HashSet<>();
117    this.settingsQualifierSets = new HashSet<>();
118    this.knownConversionTypes = new HashSet<>(Collections.singleton(String.class));
119  }
120
121
122  /*
123   * Observer methods.
124   */
125
126
127  private final <T, X extends Settings> void processSettingsInjectionPoint(@Observes final ProcessInjectionPoint<T, X> event) {
128    final Set<Annotation> qualifiers = new HashSet<>(event.getInjectionPoint().getQualifiers());
129    qualifiers.add(Any.Literal.INSTANCE);
130    this.settingsQualifierSets.add(qualifiers);
131  }
132
133  private final <T, X> void processInjectionPoint(@Observes final ProcessInjectionPoint<T, X> event,
134                                                  final BeanManager beanManager) {
135    final InjectionPoint injectionPoint = event.getInjectionPoint();
136    try {
137      if (!this.processSettingInjectionPoint(injectionPoint)) {
138        this.processNonSettingInjectionPoint(injectionPoint, beanManager);
139      }
140    } catch (final Exception definitionException) {
141      event.addDefinitionError(definitionException);
142    }
143  }
144
145  private final boolean processSettingInjectionPoint(final InjectionPoint injectionPoint) {
146    final boolean returnValue;
147    final Type type = injectionPoint.getType();
148    if (Settings.class.equals(type)) {
149      returnValue = false;
150    } else {
151      final Set<Annotation> injectionPointQualifiers = injectionPoint.getQualifiers();
152      boolean containsSetting = false;
153      for (final Annotation injectionPointQualifier : injectionPointQualifiers) {
154        if (injectionPointQualifier instanceof Setting) {
155          final Setting setting = (Setting)injectionPointQualifier;
156          if (setting.required()) {
157            final Object defaultValue = setting.defaultValue();
158            if (defaultValue != null && !defaultValue.equals(Setting.UNSET)) {
159              throw new DefinitionException("While processing the injection point " + injectionPoint +
160                                            " the Setting annotation named " + setting.name() +
161                                            " had a defaultValue element specified (" + defaultValue +
162                                            ") and returned true from its required() element.");
163            }
164          }
165          containsSetting = true;
166          break;
167        }
168      }
169      if (containsSetting) {
170        this.knownConversionTypes.add(type);
171
172        this.settingInjectionPoints.add(injectionPoint);
173
174        final Set<Annotation> settingQualifiers = new HashSet<>(injectionPointQualifiers);
175        settingQualifiers.add(Any.Literal.INSTANCE);
176        this.settingQualifierSets.add(settingQualifiers);
177
178        final Set<Annotation> settingsQualifiers = new HashSet<>(injectionPointQualifiers);
179        settingsQualifiers.removeIf(e -> e instanceof Setting);
180        if (settingsQualifiers.isEmpty()) {
181          settingsQualifiers.add(Default.Literal.INSTANCE);
182        }
183        settingsQualifiers.add(Any.Literal.INSTANCE);
184        this.settingsQualifierSets.add(settingsQualifiers);
185        returnValue = true;
186      } else {
187        returnValue = false;
188      }      
189    }
190    return returnValue;
191  }
192
193  private final void processNonSettingInjectionPoint(final InjectionPoint injectionPoint,
194                                                     final BeanManager beanManager) {
195    Objects.requireNonNull(injectionPoint);
196    Objects.requireNonNull(beanManager);
197    final Set<Annotation> qualifiers = injectionPoint.getQualifiers();
198    if (qualifiers != null && !qualifiers.isEmpty()) {
199      for (final Annotation qualifier : qualifiers) {
200        if (qualifier instanceof Configured) {
201          Set<Type> types = this.configuredTypes.get(qualifiers);
202          if (types == null) {
203            types = new HashSet<>();
204            this.configuredTypes.put(qualifiers, types);
205          }
206          types.add(injectionPoint.getType());
207          break;
208        }
209      }
210    }
211  }
212
213  private final void installConverterProviderBeans(@Observes final AfterBeanDiscovery event,
214                                                   final BeanManager beanManager) {
215    final Type type = ConverterProvider.class;
216    addBean(event,
217            beanManager,
218            type,
219            this.settingsQualifierSets,
220            (e, bm, t, nq) -> e.addBean()
221              .addTransitiveTypeClosure(BeanManagerBackedConverterProvider.class)
222              .scope(Singleton.class)
223              .qualifiers(nq)
224              .beanClass(BeanManagerBackedConverterProvider.class)
225              .createWith(cc -> new BeanManagerBackedConverterProvider(bm, nq)));
226  }
227
228  private final void installSourcesSupplierBeans(@Observes final AfterBeanDiscovery event,
229                                                 final BeanManager beanManager) {
230    final Type type = new TypeLiteral<BiFunction<String, Set<Annotation>, Set<Source>>>() {
231      private static final long serialVersionUID = 1L;
232    }.getType();
233    addBean(event,
234            beanManager,
235            type,
236            this.settingsQualifierSets,
237            (e, bm, t, nq) -> e.addBean()
238              .types(t)
239              .scope(Singleton.class)
240              .qualifiers(nq)
241              .beanClass(BeanManagerBackedSourcesSupplier.class)
242              .createWith(cc -> new BeanManagerBackedSourcesSupplier(bm)));
243  }
244
245  private final void installSettingsBeans(@Observes final AfterBeanDiscovery event,
246                                          final BeanManager beanManager) {
247    addBean(event,
248            beanManager,
249            Settings.class,
250            this.settingsQualifierSets,
251            (e, bm, t, nq) -> e.addBean()
252              .types(t)
253              .scope(Singleton.class)
254              .qualifiers(nq)
255              .beanClass(Settings.class)
256              .produceWith(instance -> {
257                final Annotation[] qualifiersArray = nq.toArray(new Annotation[nq.size()]);
258                final BiFunction<? super String, ? super Set<Annotation>, ? extends Set<? extends Source>> sourcesSupplier =
259                  instance.select(new TypeLiteral<BiFunction<? super String,
260                                                             ? super Set<Annotation>,
261                                                             ? extends Set<? extends Source>>>() {
262                    private static final long serialVersionUID = 1L;
263                  },
264                  qualifiersArray).get();
265                final ConverterProvider converterProvider = instance.select(ConverterProvider.class, qualifiersArray).get();
266                final Iterable<? extends Arbiter> arbiters = instance.select(Arbiter.class, qualifiersArray);
267                return new Settings(nq, sourcesSupplier, converterProvider, arbiters);
268              }));
269  }
270
271  private final void installSettingProducers(@Observes final AfterBeanDiscovery event,
272                                             final BeanManager beanManager) {
273    if (!this.settingQualifierSets.isEmpty()) {
274      final AnnotatedType<SettingsExtension> annotatedType = beanManager.createAnnotatedType(SettingsExtension.class);
275      final AnnotatedMethod<? super SettingsExtension> producerMethodTemplate = annotatedType.getMethods()
276        .stream()
277        .filter(m -> m.getJavaMember().getName().equals("producerMethodTemplate"))
278        .findFirst() // ...and only
279        .get();
280      final BeanAttributes<?> delegate = beanManager.createBeanAttributes(producerMethodTemplate);
281      for (final Set<Annotation> settingQualifiers : this.settingQualifierSets) {
282        final Set<Annotation> settingsQualifiers = new HashSet<>(settingQualifiers);
283        settingsQualifiers.removeIf(e -> e instanceof Setting);
284        if (settingsQualifiers.isEmpty()) {
285          settingsQualifiers.add(Default.Literal.INSTANCE);
286        }
287        for (final Type type : this.knownConversionTypes) {
288          if (noBeans(beanManager, type, settingQualifiers)) {
289            // type is the type of, say, an injection point.  So it
290            // could be a wildcard or a type variable.  Or it could be
291            // a ParameterizedType with recursive wildcards or type
292            // variables.
293            final BeanAttributes<?> beanAttributes =
294              new FlexiblyTypedBeanAttributes<Object>(delegate, settingQualifiers, Collections.singleton(synthesizeLegalBeanType(type, 1)));
295            final ProducerFactory<SettingsExtension> defaultProducerFactory =
296              beanManager.getProducerFactory(producerMethodTemplate, null);
297            final ProducerFactory<SettingsExtension> producerFactory = new ProducerFactory<SettingsExtension>() {
298                @Override
299                public final <T> Producer<T> createProducer(final Bean<T> bean) {
300                  final Producer<T> defaultProducer = defaultProducerFactory.createProducer(bean);
301                  final Set<InjectionPoint> injectionPoints =
302                  qualifyInjectionPoints(defaultProducer.getInjectionPoints(), settingsQualifiers);
303                  return new DelegatingProducer<T>(defaultProducer) {
304                    @Override
305                    public final Set<InjectionPoint> getInjectionPoints() {
306                      return injectionPoints;
307                    }
308                  };
309                }
310              };
311            final Bean<?> bean = beanManager.createBean(beanAttributes, SettingsExtension.class, producerFactory);
312            event.addBean(bean);
313          }
314        }
315      }
316    }
317  }
318
319  private final <T> void installConfiguredBeans(@Observes final AfterBeanDiscovery event,
320                                                final BeanManager beanManager) {
321    final Set<Entry<Set<Annotation>, Set<Type>>> entrySet = this.configuredTypes.entrySet();
322    for (final Entry<Set<Annotation>, Set<Type>> entry : entrySet) {
323      final Set<Annotation> qualifiers = entry.getKey();
324      assert qualifiers != null;
325      assert qualifiers.contains(Configured.Literal.INSTANCE);
326      final Set<Type> types = entry.getValue();
327      assert types != null;
328      for (final Type type : types) {
329        if (noBeans(beanManager, type, qualifiers)) {
330          final Set<Annotation> newQualifiers = new HashSet<>(qualifiers);
331          newQualifiers.removeIf(e -> e instanceof Configured);
332          if (newQualifiers.isEmpty()) {
333            newQualifiers.add(Default.Literal.INSTANCE);
334          }
335          final Annotation[] qualifiersArray = newQualifiers.toArray(new Annotation[newQualifiers.size()]);
336          final Set<Bean<?>> nonConfiguredBeans = beanManager.getBeans(type, qualifiersArray);
337          if (nonConfiguredBeans != null && !nonConfiguredBeans.isEmpty()) {
338            @SuppressWarnings("unchecked")
339            final Bean<T> bean = (Bean<T>)beanManager.resolve(nonConfiguredBeans);
340            assert bean.getTypes().contains(type);
341            event.<T>addBean()
342              .scope(bean.getScope())
343              .types(type)
344              .beanClass(bean.getBeanClass())
345              .qualifiers(qualifiers)
346              .createWith(cc -> {
347                  Set<Bean<?>> settingsBeans = beanManager.getBeans(Settings.class, qualifiersArray);
348                  if (settingsBeans == null || settingsBeans.isEmpty()) {
349                    settingsBeans = beanManager.getBeans(Settings.class);
350                  }
351                  final Bean<?> settingsBean = beanManager.resolve(settingsBeans);
352                  final Settings settings = (Settings)beanManager.getReference(settingsBean, Settings.class, cc);
353                  
354                  final T contextualInstance = bean.create(cc);
355                  try {
356                    settings.configure(contextualInstance, qualifiers);
357                  } catch (final IntrospectionException | ReflectiveOperationException exception) {
358                    throw new CreationException(exception.getMessage(), exception);
359                  }
360                  return contextualInstance;
361                })
362              .destroyWith((contextualInstance, cc) -> bean.destroy(contextualInstance, cc));
363          }          
364        }
365      }
366    }
367  }
368  
369  private final void validate(@Observes final AfterDeploymentValidation event,
370                              final BeanManager beanManager) {
371    final CreationalContext<?> cc = beanManager.createCreationalContext(null);
372    try {
373      for (final InjectionPoint settingInjectionPoint : this.settingInjectionPoints) {
374        beanManager.validate(settingInjectionPoint);
375        beanManager.getInjectableReference(settingInjectionPoint, cc);
376      }
377    } finally {
378      cc.release();
379    }
380    this.settingInjectionPoints.clear();
381    this.settingQualifierSets.clear();
382    this.settingsQualifierSets.clear();
383    this.configuredTypes.clear();
384  }
385
386
387  /*
388   * Utility methods.
389   */
390
391
392  private final void addBean(final AfterBeanDiscovery event,
393                             final BeanManager beanManager,
394                             final Type type,
395                             final Set<Set<Annotation>> qualifiersSets,
396                             final BeanAdder beanAdder) {
397    for (final Set<Annotation> qualifiers : qualifiersSets) {
398      if (noBeans(beanManager, type, qualifiers)) {
399        beanAdder.addBean(event, beanManager, type, qualifiers);
400      }
401    }
402  }
403
404
405  /*
406   * Static methods.
407   */
408
409
410  /**
411   * A <strong>method template that must not be called
412   * directly</strong> used in building <a
413   * href="https://lairdnelson.wordpress.com/2017/02/11/dynamic-cdi-producer-methods/">dynamic
414   * producer methods</a>.
415   *
416   * @param injectionPoint the injection point for which a setting
417   * value is destined; must not be {@code null}
418   *
419   * @param settings an appropriate {@link Settings} to use to
420   * {@linkplain Settings#get(String, Set, Type, Supplier) acquire a
421   * value}; must not be {@code null}
422   *
423   * @return a value for a setting, or {@code null}
424   *
425   * @nullability This method may return {@code null}.
426   *
427   * @idempotency No guarantees with respect to idempotency are made
428   * of this method.
429   *
430   * @threadsafety This method is safe for concurrent use by mulitple
431   * threads.
432   *
433   * @see <a
434   * href="https://lairdnelson.wordpress.com/2017/02/11/dynamic-cdi-producer-methods/">Dynamic
435   * CDI Producer Methods</a>
436   *
437   * @deprecated This method should be called only by the CDI container.
438   */
439  @Dependent
440  @Deprecated
441  private static final Object producerMethodTemplate(final InjectionPoint injectionPoint,
442                                                     final BeanManager beanManager,
443                                                     final Settings settings) {
444    Objects.requireNonNull(injectionPoint);
445    Objects.requireNonNull(settings);
446    final Set<Annotation> qualifiers = new HashSet<>(Objects.requireNonNull(injectionPoint.getQualifiers()));
447    qualifiers.removeIf(e -> e instanceof Setting);
448    if (qualifiers.isEmpty()) {
449      qualifiers.add(Default.Literal.INSTANCE);
450    }
451    return settings.get(getName(injectionPoint),
452                        qualifiers,
453                        injectionPoint.getType(),
454                        getDefaultValueFunction(injectionPoint, beanManager));
455  }
456
457  private static final Setting extractSetting(final InjectionPoint injectionPoint) {
458    Setting returnValue = null;
459    if (injectionPoint != null) {
460      returnValue = extractSetting(injectionPoint.getQualifiers());
461    }
462    return returnValue;
463  }
464
465  private static final Setting extractSetting(final Set<Annotation> qualifiers) {
466    Setting returnValue = null;
467    if (qualifiers != null && !qualifiers.isEmpty()) {
468      for (final Annotation qualifier : qualifiers) {
469        if (qualifier instanceof Setting) {
470          returnValue = (Setting)qualifier;
471          break;
472        }
473      }
474    }
475    return returnValue;
476  }
477
478  private static final BiFunction<? super String,
479                                  ? super Set<? extends Annotation>,
480                                  ? extends String> getDefaultValueFunction(final InjectionPoint injectionPoint,
481                                                                            final BeanManager beanManager) {
482    Objects.requireNonNull(injectionPoint);
483    Objects.requireNonNull(beanManager);
484    final Setting setting = Objects.requireNonNull(extractSetting(injectionPoint));
485    final BiFunction<? super String, ? super Set<? extends Annotation>, ? extends String> returnValue;
486    if (setting.required()) {
487      returnValue = (n, qs) -> {
488        final Set<Annotation> qualifiers = new HashSet<>(Objects.requireNonNull(injectionPoint.getQualifiers()));
489        qualifiers.removeIf(e -> e instanceof Setting);
490        if (qualifiers.isEmpty()) {
491          qualifiers.add(Default.Literal.INSTANCE);
492        }
493        throw new UnsatisfiedResolutionException("No value was found in any source for the setting named " + getName(injectionPoint) + " with qualifiers " + qualifiers);
494      };
495    } else {
496      final String defaultValue = setting.defaultValue();
497      if (defaultValue == null || defaultValue.equals(Setting.UNSET)) {
498        returnValue = SettingsExtension::returnNull;
499      } else {
500        returnValue = (n, qs) -> setting.defaultValue();
501      }
502    }
503    return returnValue;
504  }
505
506  private static final String getName(final InjectionPoint injectionPoint) {
507    return getName(extractSetting(injectionPoint), injectionPoint.getAnnotated());
508  }
509
510  private static final String getName(final Setting setting, final Annotated annotated) {
511    Objects.requireNonNull(setting);
512    Objects.requireNonNull(annotated);
513    String name = setting.name();
514    if (name == null || name.isEmpty() || name.equals(Setting.UNSET)) {
515      if (annotated instanceof AnnotatedField) {
516        name = ((AnnotatedField)annotated).getJavaMember().getName();
517      } else if (annotated instanceof AnnotatedParameter) {
518        final AnnotatedParameter<?> annotatedParameter = (AnnotatedParameter<?>)annotated;
519        final int parameterIndex = annotatedParameter.getPosition();
520        final Member member = annotatedParameter.getDeclaringCallable().getJavaMember();
521        final Parameter[] parameters = ((Executable)member).getParameters();
522        final Parameter parameter = parameters[parameterIndex];
523        if (parameter.isNamePresent()) {
524          name = parameter.getName();
525        } else {
526          throw new IllegalStateException("The parameter at index " +
527                                          parameterIndex +
528                                          " in " +
529                                          member +
530                                          " did not have a name available via reflection. " +
531                                          "Make sure you compiled its enclosing class, " +
532                                          member.getDeclaringClass().getName() +
533                                          ", with the -parameters option supplied to javac, " +
534                                          " or make use of the name() element of the " +
535                                          Setting.class.getName() +
536                                          " annotation.");
537        }
538      }
539    }
540    return name;
541  }
542
543  private static final String returnNull(final String name,
544                                         final Set<? extends Annotation> qualifiers) {
545    return null;
546  }
547
548  private static final Set<InjectionPoint> qualifyInjectionPoints(final Set<InjectionPoint> injectionPoints,
549                                                                  final Set<Annotation> qualifiers) {
550    final Set<InjectionPoint> returnValue = new HashSet<>();
551    for (final InjectionPoint injectionPoint : injectionPoints) {
552      final Type injectionPointType = injectionPoint.getType();
553      if (InjectionPoint.class.equals(injectionPointType) || BeanManager.class.equals(injectionPointType)) {
554        returnValue.add(injectionPoint);
555      } else {
556        returnValue.add(qualifyInjectionPoint(injectionPoint, qualifiers));
557      }
558    }
559    return returnValue;
560  }
561
562  private static final InjectionPoint qualifyInjectionPoint(final InjectionPoint injectionPoint,
563                                                            final Set<Annotation> qualifiers) {
564    final InjectionPoint returnValue;
565    final Set<Annotation> originalQualifiers = injectionPoint.getQualifiers();
566    if (originalQualifiers == null || originalQualifiers.isEmpty()) {
567      if (qualifiers == null || qualifiers.isEmpty()) {
568        returnValue = injectionPoint;
569      } else {
570        returnValue = new FlexiblyQualifiedInjectionPoint(injectionPoint, qualifiers);
571      }
572    } else if (qualifiers == null || qualifiers.isEmpty() || originalQualifiers.equals(qualifiers)) {
573      returnValue = injectionPoint; // just leave it alone
574    } else {
575      final Set<Annotation> newQualifiers = new HashSet<>(qualifiers);
576      newQualifiers.addAll(originalQualifiers);
577      returnValue = new FlexiblyQualifiedInjectionPoint(injectionPoint, newQualifiers);
578    }
579    return returnValue;
580  }
581
582  private static final boolean noBeans(final BeanManager beanManager, final Type type, final Set<Annotation> qualifiers) {
583    Objects.requireNonNull(beanManager);
584    Objects.requireNonNull(type);
585    final Collection<?> beans;
586    if (qualifiers == null || qualifiers.isEmpty()) {
587      beans = beanManager.getBeans(type);
588    } else {
589      beans = beanManager.getBeans(type, qualifiers.toArray(new Annotation[qualifiers.size()]));
590    }
591    return beans == null || beans.isEmpty();
592  }
593
594  private static final Type synthesizeLegalBeanType(final Type type) {
595    return synthesizeLegalBeanType(type, -1, 0);
596  }
597
598  static final Type synthesizeLegalBeanType(final Type type, final int depth) {
599    return synthesizeLegalBeanType(type, Math.max(0, depth), 0);
600  }
601  
602  private static final Type synthesizeLegalBeanType(final Type type, final int depth, final int currentLevel) {
603    final Type returnValue;
604    if (type instanceof Class || depth == 0 || (depth > 0 && currentLevel > depth)) {
605      returnValue = type;
606    } else if (type instanceof ParameterizedType) {
607      final ParameterizedType ptype = (ParameterizedType)type;
608      final Type rawType = ptype.getRawType();
609      assert rawType instanceof Class;
610      final Type[] actualTypeArguments = ptype.getActualTypeArguments();
611      assert actualTypeArguments != null;
612      assert actualTypeArguments.length > 0;
613      final Collection<Type> newTypeArguments = new ArrayList<>();
614      for (final Type actualTypeArgument : actualTypeArguments) {
615        newTypeArguments.add(synthesizeLegalBeanType(actualTypeArgument, depth, currentLevel + 1)); // XXX recursive
616      }
617      returnValue = new ParameterizedTypeImplementation(ptype.getOwnerType(),
618                                                        (Class<?>)rawType,
619                                                        newTypeArguments.toArray(new Type[newTypeArguments.size()]));
620    } else if (type instanceof WildcardType) {
621      final WildcardType wtype = (WildcardType)type;
622      final Type[] upperBounds = wtype.getUpperBounds();
623      assert upperBounds != null;
624      assert upperBounds.length > 0;
625      final Type[] lowerBounds = wtype.getLowerBounds();
626      assert lowerBounds != null;
627      if (lowerBounds.length == 0) {
628        // Upper-bounded wildcard, e.g. ? extends Something
629        if (upperBounds.length == 1) {
630          // Turn ? extends Something into Something
631          returnValue = synthesizeLegalBeanType(upperBounds[0], depth, currentLevel + 1); // XXX recursive
632        } else {
633          // Too complicated/unsupported; just let it fly and CDI will
634          // fail later
635          returnValue = type;
636        }
637      } else {
638        // Lower-bounded wildcard, e.g. ? super Something
639        assert upperBounds.length == 1;
640        assert Object.class.equals(upperBounds[0]);
641        if (lowerBounds.length == 1) {
642          // Turn ? super Something into Something
643          returnValue = synthesizeLegalBeanType(lowerBounds[0], depth, currentLevel + 1); // XXX recursive
644        } else {
645          // Too complicated/unsupported; just let it fly and CDI will
646          // fail later
647          returnValue = type;
648        }
649      }
650    } else if (type instanceof TypeVariable) {
651      final TypeVariable<?> tv = (TypeVariable<?>)type;
652      final Type[] bounds = tv.getBounds();
653      assert bounds != null;
654      assert bounds.length > 0;
655      if (bounds.length == 1) {
656        returnValue = synthesizeLegalBeanType(bounds[0], depth, currentLevel + 1); // XXX recursive
657      } else {
658        // Too complicated/unsupported; just let it fly and CDI will
659        // fail later
660        returnValue = type;
661      }
662    } else if (type instanceof GenericArrayType) {
663      returnValue = type;
664    } else {
665      throw new IllegalArgumentException("Unsupported Type implementation: " + type);
666    }
667    return returnValue;
668  }
669
670
671  /*
672   * Inner and nested classes.
673   */
674
675
676  @FunctionalInterface
677  private interface BeanAdder {
678
679    void addBean(final AfterBeanDiscovery event,
680                 final BeanManager beanManager,
681                 final Type type,
682                 final Set<Annotation> qualifiers);
683
684  }
685
686  private static class DelegatingBeanAttributes<T> implements BeanAttributes<T> {
687
688    private final BeanAttributes<?> delegate;
689
690    protected DelegatingBeanAttributes(final BeanAttributes<?> delegate) {
691      super();
692      this.delegate = Objects.requireNonNull(delegate);
693    }
694
695    @Override
696    public String getName() {
697      return this.delegate.getName();
698    }
699
700    @Override
701    public Set<Annotation> getQualifiers() {
702      return this.delegate.getQualifiers();
703    }
704
705    @Override
706    public Class<? extends Annotation> getScope() {
707      return this.delegate.getScope();
708    }
709
710    @Override
711    public Set<Class<? extends Annotation>> getStereotypes() {
712      return this.delegate.getStereotypes();
713    }
714
715    @Override
716    public Set<Type> getTypes() {
717      return this.delegate.getTypes();
718    }
719
720    @Override
721    public boolean isAlternative() {
722      return this.delegate.isAlternative();
723    }
724
725    @Override
726    public String toString() {
727      return this.delegate.toString();
728    }
729
730  }
731
732  private static class FlexiblyTypedBeanAttributes<T> extends DelegatingBeanAttributes<T> {
733
734    private final Set<Annotation> qualifiers;
735
736    private final Set<Type> types;
737
738    private FlexiblyTypedBeanAttributes(final BeanAttributes<?> delegate,
739                                        final Set<Annotation> qualifiers,
740                                        final Set<Type> types) {
741      super(delegate);
742      if (qualifiers == null) {
743        this.qualifiers = null;
744      } else if (qualifiers.isEmpty()) {
745        this.qualifiers = Collections.emptySet();
746      } else {
747        this.qualifiers = Collections.unmodifiableSet(qualifiers);
748      }
749      if (types == null) {
750        this.types = null;
751      } else if (types.isEmpty()) {
752        this.types = Collections.emptySet();
753      } else {
754        this.types = Collections.unmodifiableSet(types);
755      }
756    }
757
758    @Override
759    public Set<Annotation> getQualifiers() {
760      return this.qualifiers;
761    }
762
763    @Override
764    public Set<Type> getTypes() {
765      return this.types;
766    }
767
768  }
769
770  private static class DelegatingInjectionPoint implements InjectionPoint {
771
772    private final InjectionPoint delegate;
773
774    protected DelegatingInjectionPoint(final InjectionPoint delegate) {
775      super();
776      this.delegate = Objects.requireNonNull(delegate);
777    }
778
779    @Override
780    public Annotated getAnnotated() {
781      return this.delegate.getAnnotated();
782    }
783
784    @Override
785    public Bean<?> getBean() {
786      return this.delegate.getBean();
787    }
788
789    @Override
790    public Member getMember() {
791      return this.delegate.getMember();
792    }
793
794    @Override
795    public Set<Annotation> getQualifiers() {
796      return this.delegate.getQualifiers();
797    }
798
799    @Override
800    public Type getType() {
801      return this.delegate.getType();
802    }
803
804    @Override
805    public boolean isDelegate() {
806      return this.delegate.isDelegate();
807    }
808
809    @Override
810    public boolean isTransient() {
811      return this.delegate.isTransient();
812    }
813
814  }
815
816  private static class FlexiblyQualifiedInjectionPoint extends DelegatingInjectionPoint {
817
818    private final Set<Annotation> qualifiers;
819
820    private FlexiblyQualifiedInjectionPoint(final InjectionPoint delegate,
821                                            final Set<Annotation> qualifiers) {
822      super(delegate);
823      if (qualifiers == null) {
824        this.qualifiers = null;
825      } else if (qualifiers.isEmpty()) {
826        this.qualifiers = Collections.emptySet();
827      } else {
828        this.qualifiers = Collections.unmodifiableSet(qualifiers);
829      }
830    }
831
832    @Override
833    public Set<Annotation> getQualifiers() {
834      return this.qualifiers;
835    }
836
837  }
838
839  private static class DelegatingProducer<T> implements Producer<T> {
840
841    private final Producer<T> delegate;
842
843    protected DelegatingProducer(final Producer<T> producer) {
844      super();
845      this.delegate = Objects.requireNonNull(producer);
846    }
847
848    @Override
849    public void dispose(final T instance) {
850      this.delegate.dispose(instance);
851    }
852
853    @Override
854    public Set<InjectionPoint> getInjectionPoints() {
855      return this.delegate.getInjectionPoints();
856    }
857
858    @Override
859    public T produce(final CreationalContext<T> cc) {
860      return this.delegate.produce(cc);
861    }
862
863  }
864
865}