001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024 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.annotation.Annotation;
017
018import java.lang.constant.ClassDesc;
019import java.lang.constant.Constable;
020import java.lang.constant.ConstantDesc;
021import java.lang.constant.DynamicConstantDesc;
022import java.lang.constant.MethodHandleDesc;
023
024import java.util.List;
025import java.util.Objects;
026import java.util.Optional;
027
028import java.util.function.Supplier;
029
030import javax.lang.model.AnnotatedConstruct;
031
032import javax.lang.model.element.Element;
033
034import javax.lang.model.type.TypeMirror;
035
036import org.microbean.construct.constant.Constables;
037
038import org.microbean.construct.element.AnnotationRecord;
039import org.microbean.construct.element.UniversalElement;
040
041import org.microbean.construct.type.UniversalType;
042
043import static java.lang.constant.ConstantDescs.BSM_INVOKE;
044
045/**
046 * An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link
047 * UniversalType} descend.
048 *
049 * @param <T> a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror}
050 *
051 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
052 *
053 * @see AnnotatedConstruct
054 *
055 * @see UniversalElement
056 *
057 * @see UniversalType
058 */
059public abstract sealed class UniversalConstruct<T extends AnnotatedConstruct> implements AnnotatedConstruct, Constable
060  permits UniversalElement, UniversalType {
061
062
063  /*
064   * Instance fields.
065   */
066
067
068  private final Domain domain;
069
070  // volatile not needed
071  private Supplier<? extends T> delegateSupplier;
072
073  private volatile String s;
074
075
076  /*
077   * Constructors.
078   */
079
080
081  /**
082   * Creates a new {@link AnnotatedConstruct}.
083   *
084   * @param delegate a delegate to which operations are delegated; must not be {@code null}
085   *
086   * @param domain; a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not
087   * be {@code null}
088   *
089   * @exception NullPointerException if either argument is {@code null}
090   */
091  @SuppressWarnings("try")
092  protected UniversalConstruct(final T delegate, final Domain domain) {
093    super();
094    this.domain = Objects.requireNonNull(domain, "domain");
095    final T unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate"));
096    final Runnable symbolCompleter = switch (unwrappedDelegate) {
097    case null -> throw new IllegalArgumentException("delegate: " + delegate);
098    case Element e -> e::getKind;
099    case TypeMirror t -> t::getKind;
100    default -> UniversalConstruct::doNothing;
101    };
102    this.delegateSupplier = () -> {
103      try (var lock = domain.lock()) {
104        symbolCompleter.run();
105        this.delegateSupplier = () -> unwrappedDelegate;
106      }
107      return unwrappedDelegate;
108    };
109  }
110
111
112  /*
113   * Instance methods.
114   */
115
116
117  /**
118   * Returns the delegate to which operations are delegated.
119   *
120   * @return a non-{@code null} delegate
121   *
122   * @see Element
123   *
124   * @see TypeMirror
125   */
126  public final T delegate() {
127    return this.delegateSupplier.get();
128  }
129
130  @Override // Constable
131  public final Optional<? extends ConstantDesc> describeConstable() {
132    final T delegate = this.delegate();
133    final Domain domain = this.domain();
134    return Constables.describe(delegate, domain)
135      .map(delegateDesc -> DynamicConstantDesc.of(BSM_INVOKE,
136                                                  MethodHandleDesc.ofConstructor(ClassDesc.of(this.getClass().getName()),
137                                                                                 ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()),
138                                                                                 ClassDesc.of(Domain.class.getName())),
139                                                   delegateDesc,
140                                                   ((Constable)domain).describeConstable().orElseThrow()));
141  }
142
143  /**
144   * Returns the {@link Domain} supplied at construction time.
145   *
146   * @return the non-{@code null} {@link Domain} supplied at construction time
147   */
148  public final Domain domain() {
149    return this.domain;
150  }
151
152  @Override // AnnotatedConstruct
153  public final List<? extends AnnotationRecord> getAnnotationMirrors() {
154    return AnnotationRecord.of(this.delegate().getAnnotationMirrors(), this.domain());
155  }
156
157  @Override // AnnotatedConstruct
158  @SuppressWarnings("try")
159  public final <A extends Annotation> A getAnnotation(final Class<A> annotationType) {
160    try (var lock = this.domain().lock()) {
161      return this.delegate().getAnnotation(annotationType);
162    }
163  }
164
165  @Override // AnnotatedConstruct
166  @SuppressWarnings("try")
167  public final <A extends Annotation> A[] getAnnotationsByType(final Class<A> annotationType) {
168    try (var lock = this.domain().lock()) {
169      return this.delegate().getAnnotationsByType(annotationType);
170    }
171  }
172
173  @Override // Object
174  @SuppressWarnings("try")
175  public final String toString() {
176    String s = this.s; // volatile read
177    if (s == null) {
178      try (var lock = this.domain().lock()) {
179        s = this.s = this.delegate().toString(); // volatile write, read
180      }
181      assert s != null;
182    }
183    return s;
184  }
185
186
187  /*
188   * Static methods.
189   */
190
191
192  /**
193   * <dfn>Unwraps</dfn> the supplied {@link AnnotatedConstruct} implementation such that the returned value is not an
194   * instance of {@link UniversalConstruct}.
195   *
196   * @param <T> an {@link AnnotatedConstruct} subtype (possibly {@link UniversalElement} or {@link UniversalType})
197   *
198   * @param t an {@link AnnotatedConstruct}; may be {@code null}
199   *
200   * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalConstruct}
201   *
202   * @see #delegate()
203   */
204  @SuppressWarnings("unchecked")
205  public static final <T extends AnnotatedConstruct> T unwrap(T t) {
206    while (t instanceof UniversalConstruct<?> uc) {
207      t = (T)uc.delegate();
208    }
209    return t;
210  }
211
212  private static final void doNothing() {}
213
214}