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