001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.bean;
015
016import java.lang.System.Logger;
017
018import java.lang.constant.ClassDesc;
019import java.lang.constant.Constable;
020import java.lang.constant.ConstantDesc;
021import java.lang.constant.DynamicConstantDesc;
022import java.lang.constant.MethodHandleDesc;
023
024import java.util.ArrayList;
025import java.util.Comparator;
026import java.util.List;
027import java.util.Map;
028import java.util.Objects;
029import java.util.Optional;
030import java.util.Set;
031
032import java.util.concurrent.ConcurrentHashMap;
033
034import java.util.function.Predicate;
035
036import javax.lang.model.element.Element;
037import javax.lang.model.element.QualifiedNameable;
038
039import javax.lang.model.type.ArrayType;
040import javax.lang.model.type.DeclaredType;
041import javax.lang.model.type.IntersectionType;
042import javax.lang.model.type.TypeKind;
043import javax.lang.model.type.TypeMirror;
044import javax.lang.model.type.TypeVariable;
045
046import org.microbean.lang.TypeAndElementSource;
047
048import static java.lang.System.Logger.Level.WARNING;
049
050import static java.lang.constant.ConstantDescs.BSM_INVOKE;
051
052import static java.util.HashSet.newHashSet;
053
054import static java.util.stream.Stream.concat;
055
056import static org.microbean.lang.ConstantDescs.CD_TypeAndElementSource;
057
058/**
059 * A utility for working with <dfn>bean types</dfn>.
060 *
061 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
062 *
063 * @see #beanTypes(TypeMirror)
064 *
065 * @see #legalBeanType(TypeMirror)
066 */
067public final class BeanTypes implements Constable {
068
069
070  /*
071   * Static fields.
072   */
073
074
075  private static final Logger LOGGER = System.getLogger(BeanTypes.class.getName());
076
077
078  /*
079   * Instance fields.
080   */
081
082
083  private final Map<TypeMirror, List<TypeMirror>> beanTypesCache;
084
085  private final Comparator<TypeMirror> c;
086
087  private final TypeAndElementSource tes;
088
089
090  /*
091   * Constructors.
092   */
093
094
095  /**
096   * Creates a new {@link BeanTypes}.
097   *
098   * @param tes a {@link TypeAndElementSource}; must not be {@code null}
099   */
100  public BeanTypes(final TypeAndElementSource tes) {
101    super();
102    this.tes = Objects.requireNonNull(tes, "tes");
103    this.c = new SpecializationComparator();
104    this.beanTypesCache = new ConcurrentHashMap<>();
105  }
106
107
108  /*
109   * Instance methods.
110   */
111
112  /**
113   * Returns an {@link Optional} housing a {@link ConstantDesc} that represents this {@link BeanTypes}.
114   *
115   * <p>This method never returns {@code null}.</p>
116   *
117   * <p>The default implementation of this method relies on the presence of a {@code public} constructor that accepts a
118   * single {@link TypeAndElementSource}-typed argument.</p>
119   *
120   * <p>The {@link Optional} returned by an invocation of this method may be, and often will be, {@linkplain
121   * Optional#isEmpty() empty}.</p>
122   *
123   * @return an {@link Optional} housing a {@link ConstantDesc} that represents this {@link BeanTypes}; never {@code
124   * null}
125   *
126   * @see Constable#describeConstable()
127   */
128  @Override // Constable
129  public final Optional<? extends ConstantDesc> describeConstable() {
130    return (this.tes instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
131      .map(tesDesc -> DynamicConstantDesc.of(BSM_INVOKE,
132                                             MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()),
133                                                                            CD_TypeAndElementSource),
134                                             tesDesc));
135  }
136
137  /**
138   * Clears caches that may be used internally by this {@link BeanTypes}.
139   *
140   * @idempotency This method may clear internal state but otherwise has no side effects.
141   *
142   * @threadsafety This method is safe for concurrent use by multiple threads.
143   */
144  public final void clearCaches() {
145    this.beanTypesCache.clear();
146  }
147
148  /**
149   * Returns an immutable {@link List} of {@linkplain #legalBeanType(TypeMirror) legal bean types} that the supplied
150   * {@link TypeMirror} bears.
151   *
152   * <p>The returned {@link List} may be empty.</p>
153   *
154   * @param t a {@link TypeMirror}; must not be {@code null}
155   *
156   * @return an immutable {@link List} of {@linkplain #legalBeanType(TypeMirror) legal bean types} that the supplied
157   * {@link TypeMirror} bears; never {@code null}
158   *
159   * @exception NullPointerException if {@code t} is {@code null}
160   *
161   * @nullability This method never returns {@code null}.
162   *
163   * @idempotency This method is idempotent and returns determinate values.
164   *
165   * @threadsafety This method is safe for concurrent use by multiple threads.
166   */
167  public final List<TypeMirror> beanTypes(final TypeMirror t) {
168    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#assignable_parameters
169    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#legal_bean_types
170    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#managed_bean_types
171    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#producer_field_types
172    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#producer_method_types
173    return switch (t.getKind()) {
174    case ARRAY                                                -> this.beanTypesCache.computeIfAbsent(t, t0 -> legalBeanType(t0) ? List.of(t0, tes.declaredType("java.lang.Object")) : List.of());
175    case BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT -> this.beanTypesCache.computeIfAbsent(t, t0 -> List.of(t0, tes.declaredType("java.lang.Object")));
176    case DECLARED, TYPEVAR                                    -> this.beanTypesCache.computeIfAbsent(t, t0 -> supertypes(t0, BeanTypes::legalBeanType));
177    default -> {
178      assert !legalBeanType(t);
179      yield List.of();
180    }
181    };
182  }
183
184  final List<TypeMirror> supertypes(final TypeMirror t) {
185    return this.supertypes(t, BeanTypes::returnTrue);
186  }
187
188  private final List<TypeMirror> supertypes(final TypeMirror t, final Predicate<? super TypeMirror> p) {
189    final ArrayList<TypeMirror> nonInterfaceTypes = new ArrayList<>(7); // arbitrary size
190    final ArrayList<TypeMirror> interfaceTypes = new ArrayList<>(17); // arbitrary size
191    supertypes(t, p, nonInterfaceTypes, interfaceTypes, newHashSet(13)); // arbitrary size
192    nonInterfaceTypes.trimToSize();
193    interfaceTypes.trimToSize();
194    return
195      concat(nonInterfaceTypes.stream(), // non-interface supertypes are already sorted from most-specific to least
196             interfaceTypes.stream().sorted(this.c)) // have to sort interfaces because you can extend them in any order
197      .toList();
198  }
199
200  private final void supertypes(final TypeMirror t,
201                                final Predicate<? super TypeMirror> p,
202                                final ArrayList<? super TypeMirror> nonInterfaceTypes,
203                                final ArrayList<? super TypeMirror> interfaceTypes,
204                                final Set<? super String> seen) {
205    if (seen.add(name(t))) {
206      if (p.test(t)) {
207        if (isInterface(t)) {
208          interfaceTypes.add(t); // reflexive
209        } else {
210          nonInterfaceTypes.add(t); // reflexive
211        }
212      }
213      for (final TypeMirror directSupertype : tes.directSupertypes(t)) {
214        this.supertypes(directSupertype, p, nonInterfaceTypes, interfaceTypes, seen); // note recursion
215      }
216    }
217  }
218
219
220  /*
221   * Static methods.
222   */
223
224
225  /**
226   * Returns {@code true} if and only if the supplied {@link TypeMirror} is a <dfn>legal bean type</dfn>.
227   *
228   * <p>Legal bean types are, exactly:</p>
229   *
230   * <ol>
231   *
232   * <li>{@linkplain TypeKind#ARRAY Array} types whose {@linkplain ArrayType#getComponentType() component type}s are
233   * legal bean types</li>
234   *
235   * <li>{@linkplain TypeKind#isPrimitive() Primitive} types</li>
236   *
237   * <li>{@linkplain TypeKind#DECLARED Declared} types that contain no {@linkplain TypeKind#WILDCARD wildcard type}s for
238   * every level of containment</li>
239   *
240   * </ol>
241   *
242   * @param t a {@link TypeMirror}; must not be {@code null}
243   *
244   * @return {@code true} if and only if {@code t} is a legal bean type; {@code false} otherwise
245   *
246   * @exception NullPointerException if {@code t} is {@code null}
247   *
248   * @idempotency This method is idempotent and deterministic.
249   *
250   * @threadsafety This method itself is safe for concurrent use by multiple threads, but {@link TypeMirror}
251   * implementations and {@link TypeAndElementSource} implementations may not be safe for such use.
252   */
253  public static final boolean legalBeanType(final TypeMirror t) {
254    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#assignable_parameters
255    // https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#legal_bean_types
256    return switch (t.getKind()) {
257
258    // "A bean type may be an array type."
259    //
260    // "However, some Java types are not legal bean types: [...] An array type whose component type is not a legal bean
261    // type"
262    case ARRAY -> {
263      if (!legalBeanType(((ArrayType)t).getComponentType())) { // note recursion
264        if (LOGGER.isLoggable(WARNING)) {
265          LOGGER.log(WARNING, t + " has a component type that is an illegal bean type (" + ((ArrayType)t).getComponentType() + ")");
266        }
267        yield false;
268      }
269      yield true;
270    }
271
272    // "A bean type may be a primitive type. Primitive types are considered to be identical to their corresponding
273    // wrapper types in java.lang."
274    case BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT -> true;
275
276    // "A bean type may be a parameterized type with actual [see below] type parameters [arguments] and type variables."
277    //
278    // "However, some Java types are not legal bean types: [...] A parameterized type that contains [see below] a
279    // wildcard type parameter [argument] is not a legal bean type."
280    //
281    // Some ink has been spilled on what it means for a "parameterized" (generic) type to "contain" a "wildcard type
282    // parameter [argument]" (https://issues.redhat.com/browse/CDI-502). Because it turns out that "actual type"
283    // apparently means, among other things, a non-wildcard type, it follows that *no* wildcard type argument appearing
284    // *anywhere* in a bean type is permitted. Note that the definition of "actual type" does not appear in the CDI
285    // specification, but only in a closed JIRA issue
286    // (https://issues.redhat.com/browse/CDI-502?focusedId=13036118&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-13036118).
287    //
288    // This still seems way overstrict to me but there you have it.
289    case DECLARED -> {
290      for (final TypeMirror typeArgument : ((DeclaredType)t).getTypeArguments()) {
291        if (typeArgument.getKind() != TypeKind.TYPEVAR && !legalBeanType(typeArgument)) { // note recursion
292          if (LOGGER.isLoggable(WARNING)) {
293            LOGGER.log(WARNING, t + " has a type argument that is an illegal bean type (" + typeArgument + ")");
294          }
295          yield false;
296        }
297      }
298      yield true;
299    }
300
301    // "A type variable is not a legal bean type." (Nothing else is either.)
302    default -> {
303      if (LOGGER.isLoggable(WARNING)) {
304        LOGGER.log(WARNING, t + " is an illegal bean type");
305      }
306      yield false;
307    }
308    };
309  }
310
311  static final String name(final TypeMirror t) {
312    return switch (t.getKind()) {
313    case ARRAY -> name(((ArrayType)t).getComponentType()) + "[]";
314    case BOOLEAN -> "boolean";
315    case BYTE -> "byte";
316    case CHAR -> "char";
317    case DECLARED -> name(((DeclaredType)t).asElement());
318    case DOUBLE -> "double";
319    case FLOAT -> "float";
320    case INT -> "int";
321    case INTERSECTION -> {
322      final java.util.StringJoiner sj = new java.util.StringJoiner("&");
323      for (final TypeMirror bound : ((IntersectionType)t).getBounds()) {
324        sj.add(name(bound));
325      }
326      yield sj.toString();
327    }
328    case LONG -> "long";
329    case SHORT -> "short";
330    case TYPEVAR -> name(((TypeVariable)t).asElement());
331    default -> t.toString();
332    };
333  }
334
335  static final String name(final Element e) {
336    return e instanceof QualifiedNameable qn ? name(qn) : name(e.getSimpleName());
337  }
338
339  private static final String name(final QualifiedNameable qn) {
340    final CharSequence n = qn.getQualifiedName();
341    return n == null || n.isEmpty() ? name(qn.getSimpleName()) : name(n);
342  }
343
344  private static final String name(final CharSequence cs) {
345    return cs instanceof String s ? s : cs.toString();
346  }
347
348  private static final boolean isInterface(final TypeMirror t) {
349    return t.getKind() == TypeKind.DECLARED && isInterface(((DeclaredType)t).asElement());
350  }
351
352  private static final boolean isInterface(final Element e) {
353    return e.getKind().isInterface();
354  }
355
356  private static final <T> boolean returnTrue(final T ignored) {
357    return true;
358  }
359
360
361  /*
362   * Inner and nested classes.
363   */
364
365
366  private final class SpecializationComparator implements Comparator<TypeMirror> {
367
368    private SpecializationComparator() {
369      super();
370    }
371
372    @Override
373    public final int compare(final TypeMirror t, final TypeMirror s) {
374      if (t == s) {
375        return 0;
376      } else if (t == null) {
377        return 1; // nulls right
378      } else if (s == null) {
379        return -1; // nulls right
380      } else if (tes.sameType(t, s)) {
381        return 0;
382      } else if (tes.subtype(t, s)) {
383        // t is a subtype of s; s is a proper supertype of t
384        return -1;
385      } else if (tes.subtype(s, t)) {
386        // s is a subtype of t; t is a proper supertype of s
387        return 1;
388      } else {
389        return name(t).compareTo(name(s));
390      }
391    }
392
393  }
394
395}