001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024–2026 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.util.ArrayList; 019import java.util.Collection; 020import java.util.Comparator; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Set; 024 025import javax.lang.model.AnnotatedConstruct; 026 027import javax.lang.model.element.AnnotationMirror; 028import javax.lang.model.element.Element; 029import javax.lang.model.element.QualifiedNameable; 030import javax.lang.model.element.TypeElement; 031 032import javax.lang.model.type.DeclaredType; 033import javax.lang.model.type.TypeMirror; 034 035import org.microbean.assign.Annotated; 036import org.microbean.assign.Matcher; 037import org.microbean.assign.Selectable; 038 039import org.microbean.construct.Domain; 040 041import org.microbean.construct.element.SyntheticAnnotationMirror; 042import org.microbean.construct.element.SyntheticAnnotationTypeElement; 043import org.microbean.construct.element.SyntheticAnnotationTypeElement.SyntheticAnnotationElement; 044import org.microbean.construct.element.SyntheticAnnotationValue; 045import org.microbean.construct.element.SyntheticName; 046 047import static java.lang.System.getLogger; 048 049import static java.util.Collections.sort; 050import static java.util.Objects.requireNonNull; 051 052import static javax.lang.model.type.TypeKind.BOOLEAN; 053import static javax.lang.model.type.TypeKind.DECLARED; 054import static javax.lang.model.type.TypeKind.INT; 055 056import static org.microbean.construct.element.AnnotationMirrors.sameAnnotation; 057import static org.microbean.construct.element.AnnotationMirrors.get; 058 059/** 060 * Utility methods for working with {@link Bean}s. 061 * 062 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 063 * 064 * @see #normalize(Collection) 065 * 066 * @see Bean 067 */ 068public final class Beans { 069 070 071 /* 072 * Static fields. 073 */ 074 075 076 private static final Logger LOGGER = getLogger(Beans.class.getName()); 077 078 079 /* 080 * Instance fields. 081 */ 082 083 084 private final AnnotationMirror alternate; 085 086 private final AnnotationMirror rank; 087 088 private final Comparator<Bean<?>> byAlternateThenByRankComparator; 089 090 091 /* 092 * Constructors. 093 */ 094 095 /** 096 * Creates a new {@link Beans}. 097 * 098 * @param d a non-{@code null} {@link Domain} 099 * 100 * @see #Beans(Domain, AnnotationMirror, AnnotationMirror) 101 */ 102 public Beans(final Domain d) { 103 this(d, null, null); 104 } 105 106 /** 107 * Creates a new {@link Beans}. 108 * 109 * @param alternate a non-{@code null} {@link AnnotationMirror} that will designate a {@link Bean} as being an 110 * alternate 111 * 112 * @param rank a non-{@code null} {@link AnnotationMirror} that can be examined to determine the <dfn>rank</dfn> of a {@link Bean} 113 * that has been deemed to be an alternate 114 * 115 * @exception NullPointerException if any argument is {@code null} 116 * 117 * @see #Beans(Domain, AnnotationMirror, AnnotationMirror) 118 */ 119 public Beans(final AnnotationMirror alternate, final AnnotationMirror rank) { 120 this(null, alternate, rank); 121 } 122 123 /** 124 * Creates a new {@link Beans}. 125 * 126 * @param d a non-{@code null} {@link Domain} 127 * 128 * @param alternate an {@link AnnotationMirror} that will designate a {@link Bean} as being an alternate; may be 129 * {@code null}; if non-{@code null} must have a {@code boolean}-typed {@code value} element 130 * 131 * @param rank an {@link AnnotationMirror} that can be examined to determine the <dfn>rank</dfn> of a {@link Bean} 132 * that has been deemed to be an alternate; may be {@code null}; if non-{@code null} must have an {@code int}-typed 133 * {@code value} element 134 */ 135 public Beans(final Domain d, 136 final AnnotationMirror alternate, 137 final AnnotationMirror rank) { 138 super(); 139 if (alternate == null) { 140 if (d == null) { 141 this.alternate = null; 142 } else { 143 this.alternate = 144 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(), 145 new SyntheticName("Alternate"), 146 List.of(new SyntheticAnnotationElement(d.primitiveType(BOOLEAN), 147 new SyntheticName("value"), 148 new SyntheticAnnotationValue(false))))); 149 } 150 } else { 151 this.alternate = alternate; 152 } 153 if (rank == null) { 154 if (d == null || this.alternate == null) { 155 this.rank = null; 156 } else { 157 this.rank = 158 new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(), 159 new SyntheticName("Rank"), 160 List.of(new SyntheticAnnotationElement(d.primitiveType(INT), 161 new SyntheticName("value"), 162 new SyntheticAnnotationValue(0))))); 163 } 164 } else { 165 this.rank = rank; 166 } 167 this.byAlternateThenByRankComparator = 168 Comparator.<Bean<?>, Boolean>comparing(this::alternate).reversed() 169 .thenComparing(Comparator.<Bean<?>, Integer>comparing(this::rank).reversed()); 170 } 171 172 /** 173 * Returns a non-{@code null}, determinate, immutable {@link List} containing a subset of {@linkplain 174 * Bean#equals(Object) distinct elements} contained in the supplied {@link Collection}, sorted in a deliberately 175 * unspecified fashion. 176 * 177 * @param beans a {@link Collection} of {@link Bean}s; must not be {@code null} 178 * 179 * @return a non-{@code null}, determinate, immutable {@link List} 180 * 181 * @exception NullPointerException if {@code beans} is {@code null} 182 * 183 * @see Bean#equals(Object) 184 */ 185 public final List<Bean<?>> normalize(final Collection<? extends Bean<?>> beans) { 186 return beans.isEmpty() ? List.of() : switch (beans.size()) { 187 case 1 -> List.copyOf(beans); 188 default -> { 189 final List<Bean<?>> newBeans = new ArrayList<>(beans instanceof Set<? extends Bean<?>> s ? s : new HashSet<>(beans)); 190 yield newBeans.isEmpty() ? List.of() : switch (newBeans.size()) { 191 case 1 -> List.of(newBeans.get(0)); 192 default -> { 193 sort(newBeans, this.byAlternateThenByRankComparator); 194 yield List.copyOf(newBeans); 195 } 196 }; 197 } 198 }; 199 } 200 201 /** 202 * {@linkplain #normalize(Collection) Normalizes} the supplied {@link Collection} of {@link Bean}s and returns a 203 * {@link Selectable} suitable for it and the supplied {@link Matcher}, using the {@link 204 * org.microbean.assign.Selectables#filtering(Collection, BiPredicate)} method internally. 205 * 206 * <p>The returned {@link Selectable} does not cache its results or resolve ambiguities.</p> 207 * 208 * @param beanCollection a {@link Collection} of {@link Bean}s; must not be {@code null} 209 * 210 * @param m a {@link Matcher}; must not be {@code null} 211 * 212 * @return a non-{@code null}, determinate {@link Selectable} 213 * 214 * @exception NullPointerException if any argument is {@code null} 215 * 216 * @see Matcher#test(Object, Object) 217 * 218 * @see org.microbean.assign.Selectables#filtering(Collection, java.util.function.BiPredicate) 219 * 220 * @see Selectables#ambiguityReducing(Selectable, Predicate, ToIntFunction) 221 * 222 * @see #normalize(Collection) 223 */ 224 public final Selectable<Annotated<? extends AnnotatedConstruct>, Bean<?>> typesafeFilteringSelectable(final Collection<? extends Bean<?>> beanCollection, 225 final Matcher<? super Annotated<? extends AnnotatedConstruct>, ? super Id> m) { 226 requireNonNull(m, "m"); // often an IdMatcher; need to overhaul this 227 return 228 beanCollection.isEmpty() ? 229 org.microbean.assign.Selectables.empty() : 230 org.microbean.assign.Selectables.filtering(this.normalize(beanCollection), (b, aac) -> m.test(aac, b.id())); 231 } 232 233 /** 234 * Returns {@code true} if and only if the supplied {@link Bean} {@linkplain Bean#id() has an <code>Id</code>} for which 235 * {@link #alternate(Id)} returns {@code true}. 236 * 237 * @param b a non-{@code null} {@link Bean} 238 * 239 * @return {@code true} if and only if the supplied {@link Bean} {@linkplain Bean#id() has an <code>Id</code>} for which 240 * {@link #alternate(Id)} returns {@code true} 241 * 242 * @exception NullPointerException if {@code b} is {@code null} 243 * 244 * @see #alternate(Id) 245 */ 246 public final boolean alternate(final Bean<?> b) { 247 return this.alternate(b.id()); 248 } 249 250 /** 251 * Returns {@code true} if and only if the supplied {@link Id} {@linkplain Id#annotations() has annotations} for which 252 * {@link #alternate(Collection)} returns {@code true}. 253 * 254 * @param id a non-{@code null} {@link Id} 255 * 256 * @return {@code true} if and only if the supplied {@link Id} {@linkplain Id#annotations() has annotations} for which 257 * {@link #alternate(Collection)} returns {@code true} 258 * 259 * @exception NullPointerException if {@code id} is {@code null} 260 * 261 * @see #alternate(Collection) 262 */ 263 public final boolean alternate(final Id id) { 264 return this.alternate(id.annotations()); 265 } 266 267 /** 268 * Returns {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains at least 269 * one {@link AnnotationMirror} for which {@link #alternate(AnnotationMirror)} returns {@code true}. 270 * 271 * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s 272 * 273 * @return {@code true} if and only if the supplied {@link Collection} of {@link AnnotationMirror}s contains at least 274 * one {@link AnnotationMirror} for which {@link #alternate(AnnotationMirror)} returns {@code true} 275 * 276 * @exception NullPointerException if {@code as} is {@code null} 277 * 278 * @see #alternate(AnnotationMirror) 279 */ 280 public final boolean alternate(final Collection<? extends AnnotationMirror> as) { 281 for (final AnnotationMirror a : as) { 282 if (this.alternate(a)) { 283 return true; 284 } 285 } 286 return false; 287 } 288 289 /** 290 * Returns {@code true} if and only if the supplied {@link AnnotationMirror} designates whatever it annotates as being 291 * an <dfn>alternate</dfn>. 292 * 293 * @param a a non-{@code null} {@link AnnotationMirror} 294 * 295 * @return {@code true} if and only if the supplied {@link AnnotationMirror} designates whatever it annotates as being 296 * an <dfn>alternate</dfn> 297 * 298 * @exception NullPointerException if {@code a} is {@code null} 299 * 300 * @see #Beans(Domain, AnnotationMirror, AnnotationMirror) 301 */ 302 public final boolean alternate(final AnnotationMirror a) { 303 return 304 this.alternate != null && 305 ((QualifiedNameable)this.alternate.getAnnotationType().asElement()).getQualifiedName().contentEquals(((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName()) && 306 get(a, "value") instanceof Boolean b && 307 b.booleanValue(); 308 } 309 310 /** 311 * Returns the result of invoking the {@link #rank(Id)} method with the supplied {@link Bean}'s {@link Bean#id() Id}. 312 * 313 * @param b a non-{@code null} {@link Bean} 314 * 315 * @return a rank, which may be {@code 0} 316 * 317 * @exception NullPointerException if {@code b} is {@code null} 318 * 319 * @see #rank(Id) 320 */ 321 public final int rank(final Bean<?> b) { 322 return this.rank(b.id()); 323 } 324 325 /** 326 * Returns the result of invoking the {@link #rank(Collection)} method with the supplied {@link Id}'s {@linkplain 327 * Id#annotations() annotations}. 328 * 329 * @param id a non-{@code null} {@link Id} 330 * 331 * @return a rank, which may be {@code 0} 332 * 333 * @exception NullPointerException if {@code id} is {@code null} 334 * 335 * @see #rank(Collection) 336 */ 337 public final int rank(final Id id) { 338 return this.alternate(id) ? this.rank(id.annotations()) : 0; 339 } 340 341 /** 342 * Iterates over the supplied {@link Collection} of {@link AnnotationMirror}s and invokes the {@link 343 * #rank(AnnotationMirror)} method on each one until a non-zero result is found and returns that result, or {@code 0} 344 * in all other cases. 345 * 346 * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s 347 * 348 * @return a rank, which may be {@code 0} 349 * 350 * @exception NullPointerException if {@code as} is {@code null} 351 * 352 * @see #rank(AnnotationMirror) 353 */ 354 public final int rank(final Collection<? extends AnnotationMirror> as) { 355 for (final AnnotationMirror a : as) { 356 final int rank = this.rank(a); 357 if (rank != 0) { 358 return rank; 359 } 360 } 361 return 0; 362 } 363 364 /** 365 * Returns an {@code int} representing the <dfn>rank</dfn> derivable from the supplied {@link AnnotationMirror}, which 366 * may be {@code 0}, or {@code 0} if the supplied {@link AnnotationMirror} does not or cannot supply a rank. 367 * 368 * @param a a non-{@code null} {@link AnnotationMirror} 369 * 370 * @return an {@code int} representing the <dfn>rank</dfn> derivable from the supplied {@link AnnotationMirror}, which 371 * may be {@code 0}, or {@code 0} if the supplied {@link AnnotationMirror} does not or cannot supply a rank 372 * 373 * @exception NullPointerException if {@code a} is {@code null} 374 * 375 * @see #Beans(Domain, AnnotationMirror, AnnotationMirror) 376 */ 377 public final int rank(final AnnotationMirror a) { 378 return 379 this.rank != null && 380 ((QualifiedNameable)this.rank.getAnnotationType().asElement()).getQualifiedName().contentEquals(((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName()) && 381 get(a, "value") instanceof Integer i ? 382 i.intValue() : 383 0; 384 } 385 386}