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}