001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025 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.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020import java.lang.constant.MethodHandleDesc;
021import java.lang.constant.MethodTypeDesc;
022
023import java.util.AbstractList;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Comparator;
028import java.util.List;
029import java.util.Objects;
030import java.util.Optional;
031
032import javax.lang.model.element.Element;
033import javax.lang.model.element.ElementKind;
034import javax.lang.model.element.ExecutableElement;
035import javax.lang.model.element.Modifier;
036import javax.lang.model.element.TypeElement;
037
038import javax.lang.model.type.DeclaredType;
039import javax.lang.model.type.TypeKind;
040import javax.lang.model.type.TypeMirror;
041
042import org.microbean.assign.ClassesThenInterfacesElementKindComparator;
043import org.microbean.assign.PrimitiveAndReferenceTypeKindComparator;
044import org.microbean.assign.SpecializationComparator;
045import org.microbean.assign.SupertypeList;
046import org.microbean.assign.Types;
047
048import org.microbean.constant.Constables;
049
050import org.microbean.construct.Domain;
051
052import static java.lang.constant.ConstantDescs.BSM_INVOKE;
053import static java.lang.constant.ConstantDescs.CD_Collection;
054
055import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
056
057import static java.util.Collections.unmodifiableList;
058
059import static org.microbean.bean.BeanTypes.legalBeanType;
060import static org.microbean.bean.BeanTypes.proxiableElement;
061
062/**
063 * An immutable {@link AbstractList} of {@link TypeMirror}s that contains only {@linkplain
064 * BeanTypes#legalBeanType(TypeMirror) legal bean types}, sorted in a specific manner.
065 *
066 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
067 *
068 * @see #of(Domain, Collection)
069 */
070public final class BeanTypeList extends AbstractList<TypeMirror> implements Constable {
071
072  private final Domain domain; // for Constable only; used by elementKind()
073
074  private final List<TypeMirror> types;
075
076  private final int interfaceIndex;
077
078  private final boolean proxiable;
079
080  private BeanTypeList(final Domain domain, final Collection<? extends TypeMirror> types) {
081    super();
082    Objects.requireNonNull(types, "types");
083    if (types instanceof BeanTypeList btl) {
084      this.domain = btl.domain;
085      this.types = btl.types;
086      this.interfaceIndex = btl.interfaceIndex;
087      this.proxiable = btl.proxiable;
088    } else {
089      this.domain = domain;
090      int size = types.size();
091      if (size == 0) {
092        this.types = List.of();
093        this.interfaceIndex = -1;
094        this.proxiable = false;
095      } else {
096        final ArrayList<TypeMirror> newTypes;
097        if (types instanceof SupertypeList stl) {
098          // SupertypeList instances are already sorted, which is why we check here. Now we can (potentially) avoid
099          // copying, too, since we can use List#copyOf() in the sunny-day case, which itself tries very hard not to
100          // copy.
101          int i = 0;
102          for (; i < size; i++) {
103            if (!legalBeanType(stl.get(i))) {
104              break;
105            }
106          }
107          if (i == size) {
108            // All types were legal, everything is sorted
109            newTypes = null;
110            this.types = List.copyOf(types);
111            this.interfaceIndex = stl.interfaceIndex();
112            this.proxiable = proxiable(this.types.get(0), size);
113          } else {
114            newTypes = new ArrayList<>(size);
115            for (int j = 0; j < i; j++) {
116              newTypes.add(stl.get(j)); // the type is known to be legal
117            }
118            ++i; // skip past the illegal type that was encountered
119            for (; i < size; i++) {
120              final TypeMirror t = stl.get(i);
121              if (legalBeanType(t)) {
122                newTypes.add(t);
123              }
124            }
125            newTypes.trimToSize();
126            size = newTypes.size();
127            if (newTypes.isEmpty()) {
128              this.types = List.of();
129              this.interfaceIndex = -1;
130              this.proxiable = false;
131            } else {
132              this.types = unmodifiableList(newTypes);
133              this.interfaceIndex = stl.interfaceIndex() >= size ? -1 : stl.interfaceIndex();
134              this.proxiable = proxiable(newTypes.get(0), size);
135            }
136          }
137        } else {
138          newTypes = new ArrayList<>(size);
139          for (final TypeMirror t : types) {
140            if (legalBeanType(t)) {
141              newTypes.add(t);
142            }
143          }
144          if (newTypes.isEmpty()) {
145            this.types = List.of();
146            this.interfaceIndex = -1;
147            this.proxiable = false;
148          } else {            
149            newTypes.trimToSize();
150            size = newTypes.size();
151            if (size > 1) {
152              Collections.sort(newTypes,
153                               Comparator.comparing(TypeMirror::getKind,
154                                                    PrimitiveAndReferenceTypeKindComparator.INSTANCE)
155                               .thenComparing(new SpecializationComparator(domain))
156                               .thenComparing(this::elementKind,
157                                              ClassesThenInterfacesElementKindComparator.INSTANCE)
158                               .thenComparing(Types::erasedName));
159            }
160            this.types = unmodifiableList(newTypes);
161            int interfaceIndex = -1;
162            for (int i = 0; i < size; i++) {
163              final ElementKind k = this.elementKind(newTypes.get(i));
164              if (k != null && k.isInterface()) {
165                interfaceIndex = i;
166                break;
167              }
168            }
169            this.interfaceIndex = interfaceIndex;
170            this.proxiable = proxiable(newTypes.get(0), size);
171          }
172        }
173      }
174    }
175  }
176
177  private ElementKind elementKind(final TypeMirror t) {
178    final Element e = this.domain.element(t);
179    return e == null ? null : e.getKind();
180  }
181
182  @Override // AbstractList<TypeMirror>
183  public final TypeMirror get(final int index) {
184    return this.types.get(index);
185  }
186
187  /**
188   * Returns the index of the first interface type this {@link BeanTypeList} contains, or a negative value if it
189   * contains no interface types.
190   *
191   * @return the index of the first interface type this {@link BeanTypeList} contains, or a negative value if it
192   * contains no interface types
193   */
194  public final int interfaceIndex() {
195    return this.interfaceIndex;
196  }
197
198  @Override // AbstractList<TypeMirror>
199  public final boolean isEmpty() {
200    return this.types.isEmpty();
201  }
202
203  @Override // Constable
204  public final Optional<? extends ConstantDesc> describeConstable() {
205    return (this.domain instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
206      .flatMap(domainDesc -> Constables.describeConstable(this.types)
207               .map(typesDesc -> DynamicConstantDesc.of(BSM_INVOKE,
208                                                        MethodHandleDesc.ofMethod(STATIC,
209                                                                                  ClassDesc.of(this.getClass().getName()),
210                                                                                  "of",
211                                                                                  MethodTypeDesc.of(ClassDesc.of(BeanTypeList.class.getName()),
212                                                                                                    ClassDesc.of(Domain.class.getName()),
213                                                                                                    CD_Collection)),
214                                                        domainDesc,
215                                                        typesDesc)));
216  }
217
218  /**
219   * Returns {@code true} if and only if this {@link BeanTypeList} is <dfn>proxiable</dfn>.
220   *
221   * @return {@code true} if and only if this {@link BeanTypeList} is <dfn>proxiable</dfn>
222   *
223   * @see BeanTypes#proxiableElement(Element)
224   *
225   * @spec https://jakarta.ee/specifications/cdi/4.0/jakarta-cdi-spec-4.0#unproxyable CDI Specification, version 4.0,
226   * section 2.2.10
227   */
228  public final boolean proxiable() {
229    return this.proxiable;
230  }
231
232  @Override // AbstractList<TypeMirror>
233  public final int size() {
234    return this.types.size();
235  }
236
237
238  /*
239   * Static methods.
240   */
241
242
243  /**
244   * Returns a non-{@code null} {@link BeanTypeList} suitable for the supplied arguments.
245   *
246   * @param domain a {@link Domain} that may be used to determine {@linkplain Domain#subtype(TypeMirror, TypeMirror)
247   * subtype relationships}; may be {@code null} if {@code types} is itself a {@link BeanTypeList}
248   *
249   * @param types a non-{@code null} {@link Collection} of {@link TypeMirror}s; must not be {@code null}
250   *
251   * @return a non-{@code null} {@link BeanTypeList}
252   *
253   * @exception NullPointerException if {@code types} is {@code null}, or if {@code types} is not a {@link BeanTypeList}
254   * and {@code domain} is {@code null}
255   */
256  // Called by describeConstable()
257  public static final BeanTypeList of(final Domain domain, final Collection<? extends TypeMirror> types) {
258    return types instanceof BeanTypeList btl ? btl : new BeanTypeList(domain, types);
259  }
260
261  private static final boolean proxiable(final TypeMirror firstLegalBeanType, final int size) {
262    return
263      // Non-declared otherwise legal bean types cannot be proxied.
264      firstLegalBeanType.getKind() == TypeKind.DECLARED &&
265      ((DeclaredType)firstLegalBeanType).asElement() instanceof TypeElement e &&
266      // This BeanTypeList is still potentially proxiable if the reason the first legal bean type's element failed the
267      // BeanTypes#proxiableElement(Element) test was because the first non-interface bean type was java.lang.Object but we
268      // know that there are interfaces in this list.
269      (proxiableElement(e) || e.getQualifiedName().contentEquals("java.lang.Object") && size > 1);
270  }
271
272}