001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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.construct;
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;
021
022import java.util.List;
023import java.util.Objects;
024import java.util.Optional;
025
026import java.util.concurrent.locks.Lock;
027import java.util.concurrent.locks.ReentrantLock;
028
029import java.util.function.Supplier;
030
031import javax.annotation.processing.ProcessingEnvironment;
032
033import javax.lang.model.element.Element;
034import javax.lang.model.element.ExecutableElement;
035import javax.lang.model.element.ModuleElement;
036import javax.lang.model.element.Name;
037import javax.lang.model.element.Parameterizable;
038import javax.lang.model.element.QualifiedNameable;
039import javax.lang.model.element.RecordComponentElement;
040import javax.lang.model.element.TypeElement;
041
042import javax.lang.model.util.Elements;
043import javax.lang.model.util.Elements.Origin;
044import javax.lang.model.util.Types;
045
046import javax.lang.model.type.DeclaredType;
047import javax.lang.model.type.ExecutableType;
048import javax.lang.model.type.PrimitiveType;
049import javax.lang.model.type.TypeKind;
050import javax.lang.model.type.TypeMirror;
051
052import org.microbean.construct.element.StringName;
053import org.microbean.construct.element.UniversalElement;
054
055import org.microbean.construct.type.UniversalType;
056
057import static java.lang.constant.ConstantDescs.BSM_INVOKE;
058
059/**
060 * A {@linkplain Domain domain of Java constructs} that can be used at annotation processing time or at runtime.
061 *
062 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
063 *
064 * @see Domain
065 *
066 * @see RuntimeProcessingEnvironmentSupplier
067 */
068@SuppressWarnings({ "try", "unchecked" })
069public class DefaultDomain implements Constable, Domain {
070
071  private final Supplier<? extends ProcessingEnvironment> pe;
072
073  private final Supplier<? extends Unlockable> locker;
074
075  /**
076   * Creates a new {@link DefaultDomain} <strong>for use at runtime</strong>.
077   *
078   * @see #DefaultDomain(ProcessingEnvironment, Lock)
079   */
080  public DefaultDomain() {
081    this(null, null);
082  }
083
084  /**
085   * Creates a new {@link DefaultDomain} whose usage type is determined by the argument supplied to this constructor.
086   *
087   * @param pe a {@link ProcessingEnvironment}; may be {@code null} in which case the return value of an invocation of
088   * {@link Supplier#get()} on the return value of an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()}
089   * will be used instead
090   *
091   * @see #DefaultDomain(ProcessingEnvironment, Lock)
092   *
093   * @see SymbolCompletionLock
094   */
095  public DefaultDomain(final ProcessingEnvironment pe) {
096    this(pe, null);
097  }
098
099  /**
100   * Creates a new {@link DefaultDomain} <strong>for use at runtime</strong>.
101   *
102   * @param lock a {@link Lock} to use to serialize symbol completion; may be {@code null} in which case a global {@link
103   * ReentrantLock} will be used instead
104   *
105   * @see #DefaultDomain(ProcessingEnvironment, Lock)
106   *
107   * @see RuntimeProcessingEnvironmentSupplier
108   *
109   * @see SymbolCompletionLock
110   */
111  public DefaultDomain(final Lock lock) {
112    this(null, lock);
113  }
114
115  /**
116   * Creates a new {@link DefaultDomain} whose usage type is determined by the arguments supplied to this constructor.
117   *
118   * @param pe a {@link ProcessingEnvironment}; may be {@code null} in which case the return value of an invocation of
119   * {@link Supplier#get()} on the return value of an invocation of {@link RuntimeProcessingEnvironmentSupplier#of()}
120   * will be used instead
121   *
122   * @param lock a {@link Lock} to use to serialize symbol completion; if {@code null} and {@code pe} is {@code null},
123   * then a global {@link ReentrantLock} will be used instead; if {@code null} and {@code pe} is non-{@code null}, then
124   * no serialization of symbol completion will occur and this {@link DefaultDomain} will not be safe for concurrent use
125   * by multiple threads
126   *
127   * @see RuntimeProcessingEnvironmentSupplier
128   *
129   * @see SymbolCompletionLock
130   */
131  public DefaultDomain(final ProcessingEnvironment pe, final Lock lock) {
132    super();
133    if (pe == null) {
134      this.pe = RuntimeProcessingEnvironmentSupplier.of();
135      final Lock l = lock == null ? SymbolCompletionLock.INSTANCE : lock;
136      this.locker = () -> {
137        l.lock();
138        return l::unlock;
139      };
140    } else {
141      this.pe = () -> pe;
142      this.locker = lock == null ? DefaultDomain::noopLock : () -> {
143        lock.lock();
144        return lock::unlock;
145      };
146    }
147  }
148
149
150  /*
151   * Instance methods.
152   */
153
154
155  /**
156   * Returns a {@link UniversalType} representing an {@link javax.lang.model.type.ArrayType} whose {@linkplain
157   * javax.lang.model.type.ArrayType#getComponentType() component type} {@linkplain #sameType(TypeMirror, TypeMirror) is
158   * the same as} the supplied {@link TypeMirror}.
159   *
160   * @param t a {@link TypeMirror} representing a {@linkplain javax.lang.model.type.ArrayType#getComponentType()
161   * component type}; must not be {@code null}
162   *
163   * @return a non-{@code null} {@link UniversalType}
164   *
165   * @exception NullPointerException if {@code t} is {@code null}
166   *
167   * @exception IllegalArgumentException if {@code t} is unsuitable for use as a {@linkplain
168   * javax.lang.model.type.ArrayType#getComponentType() component type}
169   *
170   * @see Types#getArrayType(TypeMirror)
171   */
172  @Override // Domain
173  public UniversalType arrayTypeOf(TypeMirror t) {
174    t = unwrap(t);
175    try (var lock = lock()) {
176      return UniversalType.of(this.types().getArrayType(t), this);
177    }
178  }
179
180  @Override // Domain
181  public UniversalType asMemberOf(DeclaredType containingType, Element e) {
182    containingType = unwrap(containingType);
183    e = unwrap(e);
184    try (var lock = lock()) {
185      return UniversalType.of(this.types().asMemberOf(containingType, e), this);
186    }
187  }
188
189  @Override // Domain
190  // Note the strange positioning of payload and receiver.
191  public boolean assignable(TypeMirror payload, TypeMirror receiver) {
192    payload = unwrap(payload);
193    receiver = unwrap(receiver);
194    try (var lock = lock()) {
195      return this.types().isAssignable(payload, receiver);
196    }
197  }
198
199  @Override // Domain
200  public StringName binaryName(TypeElement e) {
201    e = unwrap(e);
202    try (var lock = lock()) {
203      return StringName.of(this.elements().getBinaryName(e).toString(), this);
204    }
205  }
206
207  @Override // Domain
208  public boolean bridge(ExecutableElement e) {
209    e = unwrap(e);
210    try (var lock = lock()) {
211      return this.elements().isBridge(e);
212    }
213  }
214
215  @Override // Domain
216  public UniversalType capture(TypeMirror t) {
217    t = unwrap(t);
218    try (var lock = lock()) {
219      return UniversalType.of(this.types().capture(t), this);
220    }
221  }
222
223  @Override // Domain
224  public boolean contains(TypeMirror t0, TypeMirror t1) {
225    t0 = unwrap(t0);
226    t1 = unwrap(t1);
227    try (var lock = lock()) {
228      return this.types().contains(t0, t1);
229    }
230  }
231
232  // (Convenience.)
233  @Override // Domain
234  public UniversalType declaredType(final CharSequence canonicalName) {
235    return UniversalType.of(Domain.super.declaredType(canonicalName), this);
236  }
237
238  @Override // Domain
239  public UniversalType declaredType(TypeElement typeElement,
240                                    TypeMirror... typeArguments) {
241    typeElement = unwrap(typeElement);
242    typeArguments = unwrap(typeArguments);
243    try (var lock = lock()) {
244      return UniversalType.of(this.types().getDeclaredType(typeElement, typeArguments), this);
245    }
246  }
247
248  @Override // Domain
249  public UniversalType declaredType(DeclaredType enclosingType,
250                                    TypeElement typeElement,
251                                    TypeMirror... typeArguments) {
252    enclosingType = unwrap(enclosingType);
253    typeElement = unwrap(typeElement);
254    typeArguments = unwrap(typeArguments);
255    try (var lock = lock()) {
256      return UniversalType.of(this.types().getDeclaredType(enclosingType, typeElement, typeArguments), this);
257    }
258  }
259
260  @Override // Constable
261  public Optional<? extends ConstantDesc> describeConstable() {
262    return
263      Optional.of(DynamicConstantDesc.of(BSM_INVOKE,
264                                         MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()))));
265  }
266
267  @Override // Domain
268  public List<? extends UniversalType> directSupertypes(TypeMirror t) {
269    t = unwrap(t);
270    try (var lock = lock()) {
271      return UniversalType.of(this.types().directSupertypes(t), this);
272    }
273  }
274
275  @Override // Domain
276  public UniversalElement element(TypeMirror t) {
277    t = unwrap(t);
278    try (var lock = lock()) {
279      return UniversalElement.of(this.types().asElement(t), this);
280    }
281  }
282
283  @Override // Domain
284  public UniversalType elementType(final TypeMirror t) {
285    return UniversalType.of(Domain.super.elementType(t), this);
286  }
287
288  private final Elements elements() {
289    return this.pe().getElementUtils();
290  }
291
292  @Override // Object
293  public boolean equals(final Object other) {
294    if (this == other) {
295      return true;
296    } else if (other != null && other.getClass() == this.getClass()) {
297      final DefaultDomain her = (DefaultDomain)other;
298      return
299        Objects.equals(this.pe(), her.pe()) &&
300        Objects.equals(this.locker, her.locker);
301    } else {
302      return false;
303    }
304  }
305
306  @Override // Domain
307  public UniversalType erasure(TypeMirror t) {
308    t = unwrap(t);
309    try (var lock = lock()) {
310      return UniversalType.of(this.types().erasure(t), this);
311    }
312  }
313
314  // (Convenience.)
315  @Override // Domain
316  public UniversalElement executableElement(final TypeElement declaringElement,
317                                            final TypeMirror returnType,
318                                            final CharSequence name,
319                                            final TypeMirror... parameterTypes) {
320    return UniversalElement.of(Domain.super.executableElement(declaringElement, returnType, name, parameterTypes), this);
321  }
322
323  @Override // Object
324  public int hashCode() {
325    return this.pe().hashCode() ^ this.locker.hashCode();
326  }
327
328  // (Convenience.)
329  @Override // Domain
330  public UniversalElement javaLangObject() {
331    return UniversalElement.of(Domain.super.javaLangObject(), this);
332  }
333
334  /**
335   * Returns a non-{@code null} {@link Unlockable} that should be used in a {@code try}-with-resources block guarding
336   * operations that might cause symbol completion.
337   *
338   * @return a non-{@code null} {@link Unlockable}
339   *
340   * @see Unlockable#close()
341   */
342  public final Unlockable lock() {
343    return this.locker.get();
344  }
345
346  // (Canonical.)
347  @Override // Domain
348  public UniversalElement moduleElement(final CharSequence canonicalName) {
349    try (var lock = lock()) {
350      return UniversalElement.of(this.elements().getModuleElement(canonicalName), this);
351    }
352  }
353
354  // (Canonical.)
355  @Override // Domain
356  public StringName name(final CharSequence name) {
357    return switch (name) {
358    case StringName sn -> sn;
359    // You will be tempted to add other efficient cases here. Do not.
360    default -> {
361      try (var lock = lock()) {
362        yield StringName.of(this.elements().getName(name).toString(), this);
363      }
364    }
365    };
366  }
367
368  // (Canonical.)
369  @Override // Domain
370  public UniversalType noType(final TypeKind kind) {
371    return UniversalType.of(this.types().getNoType(kind), this);
372  }
373
374  // (Canonical.)
375  @Override // Domain
376  public UniversalType nullType() {
377    return UniversalType.of(this.types().getNullType(), this);
378  }
379
380  @Override // Domain
381  public Origin origin(Element e) {
382    e = unwrap(e);
383    try (var lock = lock()) {
384      return this.elements().getOrigin(e);
385    }
386  }
387
388  // (Canonical.)
389  @Override // Domain
390  public UniversalElement packageElement(final CharSequence canonicalName) {
391    try (var lock = lock()) {
392      return UniversalElement.of(this.elements().getPackageElement(canonicalName), this);
393    }
394  }
395
396  // (Canonical.)
397  @Override // Domain
398  public UniversalElement packageElement(ModuleElement asSeenFrom, final CharSequence canonicalName) {
399    asSeenFrom = unwrap(asSeenFrom);
400    try (var lock = lock()) {
401      return UniversalElement.of(this.elements().getPackageElement(asSeenFrom, canonicalName), this);
402    }
403  }
404
405  private final ProcessingEnvironment pe() {
406    return this.pe.get();
407  }
408
409  // (Canonical.)
410  @Override // Domain
411  public UniversalType primitiveType(final TypeKind kind) {
412    return UniversalType.of(this.types().getPrimitiveType(kind), this);
413  }
414
415  // (Convenience.)
416  // (Unboxing.)
417  @Override // Domain
418  public UniversalType primitiveType(final CharSequence canonicalName) {
419    return UniversalType.of(Domain.super.primitiveType(canonicalName), this);
420  }
421
422  // (Convenience.)
423  // (Unboxing.)
424  @Override // Domain
425  public UniversalType primitiveType(final TypeElement e) {
426    return UniversalType.of(Domain.super.primitiveType(e), this);
427  }
428
429  // (Canonical.)
430  // (Unboxing.)
431  @Override // Domain
432  public UniversalType primitiveType(TypeMirror t) {
433    t = unwrap(t);
434    try (var lock = lock()) {
435      return UniversalType.of(this.types().unboxedType(t), this);
436    }
437  }
438
439  @Override // Domain
440  public UniversalType rawType(final TypeMirror t) {
441    return UniversalType.of(Domain.super.rawType(t), this);
442  }
443
444  // (Canonical.)
445  @Override // Domain
446  public UniversalElement recordComponentElement(ExecutableElement e) {
447    e = unwrap(e);
448    try (var lock = lock()) {
449      return UniversalElement.of(this.elements().recordComponentFor(e), this);
450    }
451  }
452
453  @Override // Domain
454  public boolean sameType(TypeMirror t0, TypeMirror t1) {
455    if (t0 == t1) {
456      // Critical; javax.lang.model.Types#isSameType(TypeMirror, TypeMirror) returns false here, on purpose, if the two
457      // identical references are wildcards. This is almost never what anyone wants.
458      return true;
459    } else if (t0 == null || t1 == null) {
460      return false;
461    }
462    t0 = unwrap(t0);
463    t1 = unwrap(t1);
464    try (var lock = lock()) {
465      return this.types().isSameType(t0, t1);
466    }
467  }
468
469  @Override // Domain
470  public boolean subsignature(ExecutableType t0, ExecutableType t1) {
471    t0 = unwrap(t0);
472    t1 = unwrap(t1);
473    try (var lock = lock()) {
474      return this.types().isSubsignature(t0, t1);
475    }
476  }
477
478  @Override // Domain
479  public boolean subtype(TypeMirror candidateSubtype, TypeMirror candidateSupertype) {
480    candidateSubtype = unwrap(candidateSubtype);
481    candidateSupertype = unwrap(candidateSupertype);
482    try (var lock = lock()) {
483      return this.types().isSubtype(candidateSubtype, candidateSupertype);
484    }
485  }
486
487  @Override // Domain
488  public String toString(final CharSequence name) {
489    return switch (name) {
490    case null -> null;
491    case String s -> s;
492    case StringName sn -> sn.value();
493    case Name n -> {
494      try (var lock = lock()) {
495        yield n.toString();
496      }
497    }
498    default -> name.toString();
499    };
500  }
501
502  // (Canonical.)
503  @Override // Domain
504  public UniversalElement typeElement(final CharSequence canonicalName) {
505    try (var lock = lock()) {
506      return UniversalElement.of(this.elements().getTypeElement(switch (canonicalName.toString()) {
507          case "boolean" -> "java.lang.Boolean";
508          case "byte" -> "java.lang.Byte";
509          case "char" -> "java.lang.Character";
510          case "double" -> "java.lang.Double";
511          case "float" -> "java.lang.Float";
512          case "int" -> "java.lang.Integer";
513          case "long" -> "java.lang.Long";
514          case "short" -> "java.lang.Short";
515          default -> canonicalName;
516          }), this);
517    }
518  }
519
520  // (Canonical.)
521  @Override // Domain
522  public UniversalElement typeElement(ModuleElement asSeenFrom, final CharSequence canonicalName) {
523    asSeenFrom = unwrap(asSeenFrom);
524    try (var lock = lock()) {
525      return UniversalElement.of(this.elements().getTypeElement(asSeenFrom, switch (canonicalName.toString()) {
526          case "boolean" -> "java.lang.Boolean";
527          case "byte" -> "java.lang.Byte";
528          case "char" -> "java.lang.Character";
529          case "double" -> "java.lang.Double";
530          case "float" -> "java.lang.Float";
531          case "int" -> "java.lang.Integer";
532          case "long" -> "java.lang.Long";
533          case "short" -> "java.lang.Short";
534          default -> canonicalName;
535          }), this);
536    }
537  }
538
539  // (Canonical.)
540  // (Boxing.)
541  @Override // Domain
542  public UniversalElement typeElement(PrimitiveType t) {
543    t = unwrap(t);
544    try (var lock = lock()) {
545      return UniversalElement.of(this.types().boxedClass(t), this);
546    }
547  }
548
549  // (Convenience.)
550  // (Boxing.)
551  @Override // Domain
552  public UniversalElement typeElement(final TypeKind primitiveTypeKind) {
553    return UniversalElement.of(Domain.super.typeElement(primitiveTypeKind), this);
554  }
555
556  // (Convenience.)
557  @Override // Domain
558  public UniversalElement typeParameterElement(final Parameterizable p, final CharSequence name) {
559    return UniversalElement.of(Domain.super.typeParameterElement(p, name), this);
560  }
561
562  private final Types types() {
563    return this.pe().getTypeUtils();
564  }
565
566  // (Convenience.)
567  @Override // Domain
568  public UniversalType typeVariable(final Parameterizable p, final CharSequence name) {
569    return UniversalType.of(Domain.super.typeVariable(p, name), this);
570  }
571
572  // (Convenience.)
573  @Override // Domain
574  public UniversalElement variableElement(final Element e, final CharSequence name) {
575    return UniversalElement.of(Domain.super.variableElement(e, name), this);
576  }
577
578  @Override // Domain
579  public UniversalType wildcardType() {
580    return this.wildcardType(null, null);
581  }
582
583  @Override // Domain
584  public UniversalType wildcardType(TypeMirror extendsBound, TypeMirror superBound) {
585    extendsBound = unwrap(extendsBound);
586    superBound = unwrap(superBound);
587    try (var lock = lock()) {
588      return UniversalType.of(this.types().getWildcardType(extendsBound, superBound), this);
589    }
590  }
591
592
593  /*
594   * Static methods.
595   */
596
597
598  private static final void doNothing() {}
599
600  private static final Unlockable noopLock() {
601    return DefaultDomain::doNothing;
602  }
603
604  private static final <T extends TypeMirror> T unwrap(final T t) {
605    return UniversalType.unwrap(t);
606  }
607
608  private static final <E extends Element> E unwrap(final E e) {
609    return UniversalElement.unwrap(e);
610  }
611
612  private static final TypeMirror[] unwrap(final TypeMirror[] ts) {
613    if (ts == null || ts.length <= 0) {
614      return ts;
615    }
616    final TypeMirror[] rv = new TypeMirror[ts.length];
617    for (int i = 0; i < ts.length; i++) {
618      rv[i] = unwrap(ts[i]);
619    }
620    return rv;
621  }
622
623}