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.TypeElement;
039
040import javax.lang.model.util.Elements;
041import javax.lang.model.util.Elements.Origin;
042import javax.lang.model.util.Types;
043
044import javax.lang.model.type.DeclaredType;
045import javax.lang.model.type.ExecutableType;
046import javax.lang.model.type.PrimitiveType;
047import javax.lang.model.type.TypeKind;
048import javax.lang.model.type.TypeMirror;
049
050import org.microbean.construct.element.StringName;
051import org.microbean.construct.element.UniversalElement;
052
053import org.microbean.construct.type.UniversalType;
054
055import static java.lang.constant.ConstantDescs.BSM_INVOKE;
056
057/**
058 * A {@linkplain Domain domain of Java constructs} that can be used at annotation processing time or at runtime.
059 *
060 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
061 *
062 * @see Domain
063 *
064 * @see RuntimeProcessingEnvironmentSupplier
065 */
066@SuppressWarnings({ "try", "unchecked" })
067public class DefaultDomain implements Constable, Domain {
068
069  private final Supplier<? extends ProcessingEnvironment> pe;
070
071  private final Supplier<? extends Unlockable> locker;
072
073  /**
074   * Creates a new {@link DefaultDomain} <strong>for use at runtime</strong>.
075   *
076   * @see #DefaultDomain(ProcessingEnvironment, Lock)
077   */
078  public DefaultDomain() {
079    this(null, null);
080  }
081
082  /**
083   * Creates a new {@link DefaultDomain} <strong>normally for use at annotation processing time</strong>, whose usage
084   * type is actually 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} <strong>normally for use at annotation processing time</strong>, whose usage
116   * type is actually 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 <strong>and this {@link DefaultDomain} therefore will not be safe
125   * for concurrent use by multiple threads</strong>
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  // Non-private for testing only.
289  final Elements elements() {
290    return this.pe().getElementUtils();
291  }
292
293  @Override // Object
294  public boolean equals(final Object other) {
295    if (this == other) {
296      return true;
297    } else if (other != null && other.getClass() == this.getClass()) {
298      final DefaultDomain her = (DefaultDomain)other;
299      return
300        Objects.equals(this.pe(), her.pe()) &&
301        Objects.equals(this.locker, her.locker);
302    } else {
303      return false;
304    }
305  }
306
307  @Override // Domain
308  public UniversalType erasure(TypeMirror t) {
309    t = unwrap(t);
310    try (var lock = lock()) {
311      return UniversalType.of(this.types().erasure(t), this);
312    }
313  }
314
315  // (Convenience.)
316  @Override // Domain
317  public UniversalElement executableElement(final TypeElement declaringElement,
318                                            final TypeMirror returnType,
319                                            final CharSequence name,
320                                            final TypeMirror... parameterTypes) {
321    return UniversalElement.of(Domain.super.executableElement(declaringElement, returnType, name, parameterTypes), this);
322  }
323
324  @Override // Object
325  public int hashCode() {
326    return this.pe().hashCode() ^ this.locker.hashCode();
327  }
328
329  // (Convenience.)
330  @Override // Domain
331  public UniversalElement javaLangObject() {
332    return UniversalElement.of(Domain.super.javaLangObject(), this);
333  }
334
335  // (Convenience.)
336  @Override // Domain
337  public UniversalType javaLangObjectType() {
338    return UniversalType.of(Domain.super.javaLangObjectType(), this);
339  }
340
341  /**
342   * Returns a non-{@code null} {@link Unlockable} that should be used in a {@code try}-with-resources block guarding
343   * operations that might cause symbol completion.
344   *
345   * @return a non-{@code null} {@link Unlockable}
346   *
347   * @see Unlockable#close()
348   */
349  public final Unlockable lock() {
350    return this.locker.get();
351  }
352
353  // (Canonical.)
354  @Override // Domain
355  public UniversalElement moduleElement(final CharSequence canonicalName) {
356    try (var lock = lock()) {
357      return UniversalElement.of(this.elements().getModuleElement(canonicalName), this);
358    }
359  }
360
361  // (Canonical.)
362  @Override // Domain
363  public StringName name(final CharSequence name) {
364    return switch (name) {
365    case StringName sn -> sn;
366    // You will be tempted to add other efficient cases here. Do not.
367    default -> {
368      try (var lock = lock()) {
369        yield StringName.of(this.elements().getName(name).toString(), this);
370      }
371    }
372    };
373  }
374
375  // (Canonical.)
376  @Override // Domain
377  public UniversalType noType(final TypeKind kind) {
378    return UniversalType.of(this.types().getNoType(kind), this);
379  }
380
381  // (Canonical.)
382  @Override // Domain
383  public UniversalType nullType() {
384    return UniversalType.of(this.types().getNullType(), this);
385  }
386
387  @Override // Domain
388  public Origin origin(Element e) {
389    e = unwrap(e);
390    try (var lock = lock()) {
391      return this.elements().getOrigin(e);
392    }
393  }
394
395  // (Canonical.)
396  @Override // Domain
397  public UniversalElement packageElement(final CharSequence canonicalName) {
398    try (var lock = lock()) {
399      return UniversalElement.of(this.elements().getPackageElement(canonicalName), this);
400    }
401  }
402
403  // (Canonical.)
404  @Override // Domain
405  public UniversalElement packageElement(ModuleElement asSeenFrom, final CharSequence canonicalName) {
406    asSeenFrom = unwrap(asSeenFrom);
407    try (var lock = lock()) {
408      return UniversalElement.of(this.elements().getPackageElement(asSeenFrom, canonicalName), this);
409    }
410  }
411
412  private final ProcessingEnvironment pe() {
413    return this.pe.get();
414  }
415
416  // (Canonical.)
417  @Override // Domain
418  public UniversalType primitiveType(final TypeKind kind) {
419    return UniversalType.of(this.types().getPrimitiveType(kind), this);
420  }
421
422  // (Convenience.)
423  // (Unboxing.)
424  @Override // Domain
425  public UniversalType primitiveType(final CharSequence canonicalName) {
426    return UniversalType.of(Domain.super.primitiveType(canonicalName), this);
427  }
428
429  // (Convenience.)
430  // (Unboxing.)
431  @Override // Domain
432  public UniversalType primitiveType(final TypeElement e) {
433    return UniversalType.of(Domain.super.primitiveType(e), this);
434  }
435
436  // (Canonical.)
437  // (Unboxing.)
438  @Override // Domain
439  public UniversalType primitiveType(TypeMirror t) {
440    t = unwrap(t);
441    try (var lock = lock()) {
442      return UniversalType.of(this.types().unboxedType(t), this);
443    }
444  }
445
446  @Override // Domain
447  public UniversalType rawType(final TypeMirror t) {
448    return UniversalType.of(Domain.super.rawType(t), this);
449  }
450
451  // (Canonical.)
452  @Override // Domain
453  public UniversalElement recordComponentElement(ExecutableElement e) {
454    e = unwrap(e);
455    try (var lock = lock()) {
456      return UniversalElement.of(this.elements().recordComponentFor(e), this);
457    }
458  }
459
460  @Override // Domain
461  public boolean sameType(TypeMirror t0, TypeMirror t1) {
462    if (t0 == t1) {
463      // Critical; javax.lang.model.Types#isSameType(TypeMirror, TypeMirror) returns false here, on purpose, if the two
464      // identical references are wildcards. This is almost never what anyone wants.
465      return true;
466    } else if (t0 == null || t1 == null) {
467      return false;
468    }
469    t0 = unwrap(t0);
470    t1 = unwrap(t1);
471    try (var lock = lock()) {
472      return this.types().isSameType(t0, t1);
473    }
474  }
475
476  @Override // Domain
477  public boolean subsignature(ExecutableType t0, ExecutableType t1) {
478    t0 = unwrap(t0);
479    t1 = unwrap(t1);
480    try (var lock = lock()) {
481      return this.types().isSubsignature(t0, t1);
482    }
483  }
484
485  @Override // Domain
486  public boolean subtype(TypeMirror candidateSubtype, TypeMirror candidateSupertype) {
487    candidateSubtype = unwrap(candidateSubtype);
488    candidateSupertype = unwrap(candidateSupertype);
489    try (var lock = lock()) {
490      return this.types().isSubtype(candidateSubtype, candidateSupertype);
491    }
492  }
493
494  @Override // Domain
495  public String toString(final CharSequence name) {
496    return switch (name) {
497    case null -> null;
498    case String s -> s;
499    case StringName sn -> sn.value();
500    case Name n -> {
501      try (var lock = lock()) {
502        yield n.toString();
503      }
504    }
505    default -> name.toString();
506    };
507  }
508
509  // (Canonical.)
510  @Override // Domain
511  public UniversalElement typeElement(final CharSequence canonicalName) {
512    try (var lock = lock()) {
513      return UniversalElement.of(this.elements().getTypeElement(switch (canonicalName.toString()) {
514          case "boolean" -> "java.lang.Boolean";
515          case "byte" -> "java.lang.Byte";
516          case "char" -> "java.lang.Character";
517          case "double" -> "java.lang.Double";
518          case "float" -> "java.lang.Float";
519          case "int" -> "java.lang.Integer";
520          case "long" -> "java.lang.Long";
521          case "short" -> "java.lang.Short";
522          default -> canonicalName;
523          }), this);
524    }
525  }
526
527  // (Canonical.)
528  @Override // Domain
529  public UniversalElement typeElement(ModuleElement asSeenFrom, final CharSequence canonicalName) {
530    asSeenFrom = unwrap(asSeenFrom);
531    try (var lock = lock()) {
532      return UniversalElement.of(this.elements().getTypeElement(asSeenFrom, switch (canonicalName.toString()) {
533          case "boolean" -> "java.lang.Boolean";
534          case "byte" -> "java.lang.Byte";
535          case "char" -> "java.lang.Character";
536          case "double" -> "java.lang.Double";
537          case "float" -> "java.lang.Float";
538          case "int" -> "java.lang.Integer";
539          case "long" -> "java.lang.Long";
540          case "short" -> "java.lang.Short";
541          default -> canonicalName;
542          }), this);
543    }
544  }
545
546  // (Canonical.)
547  // (Boxing.)
548  @Override // Domain
549  public UniversalElement typeElement(PrimitiveType t) {
550    t = unwrap(t);
551    try (var lock = lock()) {
552      return UniversalElement.of(this.types().boxedClass(t), this);
553    }
554  }
555
556  // (Convenience.)
557  // (Boxing.)
558  @Override // Domain
559  public UniversalElement typeElement(final TypeKind primitiveTypeKind) {
560    return UniversalElement.of(Domain.super.typeElement(primitiveTypeKind), this);
561  }
562
563  // (Convenience.)
564  @Override // Domain
565  public UniversalElement typeParameterElement(final Parameterizable p, final CharSequence name) {
566    return UniversalElement.of(Domain.super.typeParameterElement(p, name), this);
567  }
568
569  // Non-private for testing only.
570  final Types types() {
571    return this.pe().getTypeUtils();
572  }
573
574  // (Convenience.)
575  @Override // Domain
576  public UniversalType typeVariable(final Parameterizable p, final CharSequence name) {
577    return UniversalType.of(Domain.super.typeVariable(p, name), this);
578  }
579
580  // (Convenience.)
581  @Override // Domain
582  public UniversalElement variableElement(final Element e, final CharSequence name) {
583    return UniversalElement.of(Domain.super.variableElement(e, name), this);
584  }
585
586  @Override // Domain
587  public UniversalType wildcardType() {
588    return this.wildcardType(null, null);
589  }
590
591  @Override // Domain
592  public UniversalType wildcardType(TypeMirror extendsBound, TypeMirror superBound) {
593    extendsBound = unwrap(extendsBound);
594    superBound = unwrap(superBound);
595    try (var lock = lock()) {
596      return UniversalType.of(this.types().getWildcardType(extendsBound, superBound), this);
597    }
598  }
599
600
601  /*
602   * Static methods.
603   */
604
605
606  private static final void doNothing() {}
607
608  private static final Unlockable noopLock() {
609    return DefaultDomain::doNothing;
610  }
611
612  private static final <T extends TypeMirror> T unwrap(final T t) {
613    return UniversalType.unwrap(t);
614  }
615
616  private static final <E extends Element> E unwrap(final E e) {
617    return UniversalElement.unwrap(e);
618  }
619
620  private static final TypeMirror[] unwrap(final TypeMirror[] ts) {
621    if (ts == null || ts.length <= 0) {
622      return ts;
623    }
624    final TypeMirror[] rv = new TypeMirror[ts.length];
625    for (int i = 0; i < ts.length; i++) {
626      rv[i] = unwrap(ts[i]);
627    }
628    return rv;
629  }
630
631}