001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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.element;
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.MethodTypeDesc;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Objects;
030import java.util.Optional;
031import java.util.Set;
032
033import java.util.function.Supplier;
034
035import javax.lang.model.element.AnnotationMirror;
036import javax.lang.model.element.AnnotationValue;
037import javax.lang.model.element.ExecutableElement;
038
039import org.microbean.construct.PrimordialDomain;
040import org.microbean.construct.UniversalConstruct;
041
042import org.microbean.construct.type.UniversalType;
043
044import static java.lang.constant.ConstantDescs.BSM_INVOKE;
045
046import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
047
048import static java.lang.constant.MethodHandleDesc.ofMethod;
049
050import static java.util.Objects.requireNonNull;
051
052/**
053 * An {@link AnnotationMirror} implementation.
054 *
055 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
056 *
057 * @see AnnotationMirror
058 */
059public final class UniversalAnnotation implements AnnotationMirror, Constable {
060
061
062  /*
063   * Instance fields.
064   */
065
066
067  // Eventually this should become a lazy constant/stable value
068  // volatile not needed
069  private Supplier<? extends AnnotationMirror> delegateSupplier;
070
071  private final PrimordialDomain domain;
072
073
074  /*
075   * Constructors.
076   */
077
078
079  /**
080   * Creates a new {@link UniversalAnnotation}.
081   *
082   * @param delegate an {@link AnnotationMirror} to which operations will be delegated; must not be {@code null}
083   *
084   * @param domain a {@link PrimordialDomain}; must not be {@code null}
085   *
086   * @exception NullPointerException if either argument is {@code null}
087   */
088  @SuppressWarnings("try")
089  public UniversalAnnotation(final AnnotationMirror delegate, final PrimordialDomain domain) {
090    super();
091    this.domain = requireNonNull(domain, "domain");
092    final AnnotationMirror unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate"));
093    if (unwrappedDelegate == delegate) {
094      // No unwrapping happened so do symbol completion early; most common case
095      this.delegateSupplier = () -> {
096        try (var lock = domain.lock()) {
097          unwrappedDelegate.getElementValues(); // should take care of any symbol completion
098          this.delegateSupplier = () -> unwrappedDelegate;
099        }
100        return unwrappedDelegate;
101      };
102    } else {
103      assert delegate instanceof UniversalAnnotation;
104      // Symbol completion already happened
105      this.delegateSupplier = () -> unwrappedDelegate;
106    }
107  }
108
109
110  /*
111   * Instance methods.
112   */
113
114
115  /**
116   * Returns the delegate to which operations are delegated.
117   *
118   * @return a non-{@code null} delegate
119   *
120   * @see AnnotationMirror
121   */
122  public final AnnotationMirror delegate() {
123    final AnnotationMirror delegate = this.delegateSupplier.get();
124    assert !(delegate instanceof UniversalAnnotation);
125    return delegate;
126  }
127
128  @Override // Constable
129  public Optional<? extends ConstantDesc> describeConstable() {
130    return this.domain instanceof Constable c0 ? c0.describeConstable() : Optional.<ConstantDesc>empty()
131      .flatMap(primordialDomainDesc -> this.delegate() instanceof Constable c1 ? c1.describeConstable() : Optional.<ConstantDesc>empty()
132               .map(delegateDesc -> DynamicConstantDesc.of(BSM_INVOKE,
133                                                           ofMethod(STATIC,
134                                                                    ClassDesc.of(this.getClass().getName()),
135                                                                    "of",
136                                                                    MethodTypeDesc.of(ClassDesc.of(this.getClass().getName()),
137                                                                                      ClassDesc.of(AnnotationMirror.class.getName()),
138                                                                                      ClassDesc.of(PrimordialDomain.class.getName()))),
139                                                           delegateDesc,
140                                                           primordialDomainDesc)));
141  }
142
143  /**
144   * Returns the {@link PrimordialDomain} supplied at construction time.
145   *
146   * @return the non-{@code null} {@link PrimordialDomain} supplied at construction time
147   */
148  public final PrimordialDomain domain() {
149    return this.domain;
150  }
151
152  @Override // Object
153  @SuppressWarnings("try")
154  public final boolean equals(final Object other) {
155    return this == other || switch (other) {
156    case null -> false;
157    // No lock needed; see
158    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45
159    case UniversalAnnotation ar when this.getClass() == ar.getClass() -> this.delegate().equals(ar.delegate());
160    default -> false;
161    };
162  }
163
164  @Override // AnnotationMirror
165  @SuppressWarnings("try")
166  public final UniversalType getAnnotationType() {
167    // No lock needed; see
168    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L285-L288
169    return UniversalType.of(this.delegate().getAnnotationType(), this.domain());
170  }
171
172  @Override // AnnotationMirror
173  @SuppressWarnings("try")
174  public final Map<? extends UniversalElement, ? extends UniversalAnnotationValue> getElementValues() {
175    final Map<UniversalElement, UniversalAnnotationValue> map = new LinkedHashMap<>(17);
176    final PrimordialDomain d = this.domain();
177    // TODO: is this lock actually needed, given how delegateSupplier works?
178    // try (var lock = d.lock()) {
179      for (final Entry<? extends ExecutableElement, ? extends AnnotationValue> e : this.delegate().getElementValues().entrySet()) {
180        map.put(UniversalElement.of(e.getKey(), d), UniversalAnnotationValue.of(e.getValue(), d));
181      }
182    // }
183    return Collections.unmodifiableMap(map);
184  }
185
186  @Override // AnnotationMirror
187  public final int hashCode() {
188    // No lock needed; see
189    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45
190    return this.delegate().hashCode();
191  }
192
193
194  /*
195   * Static methods.
196   */
197
198
199  /**
200   * Returns a non-{@code null} {@link UniversalAnnotation} that is either the supplied {@link AnnotationMirror} (if it
201   * itself is an {@link UniversalAnnotation}) or one that wraps it.
202   *
203   * @param a an {@link AnnotationMirror}; must not be {@code null}
204   *
205   * @param d a {@link PrimordialDomain}; must not be {@code null}
206   *
207   * @return a non-{@code null} {@link UniversalAnnotation}
208   *
209   * @exception NullPointerException if either argument is {@code null}
210   *
211   * @see #UniversalAnnotation(AnnotationMirror, PrimordialDomain)
212   */
213  public static final UniversalAnnotation of(final AnnotationMirror a, final PrimordialDomain d) {
214    return a instanceof UniversalAnnotation ar ? ar : new UniversalAnnotation(a, d);
215  }
216
217  /**
218   * Returns a non-{@code null}, immutable {@link List} of {@link UniversalAnnotation}s whose elements wrap the supplied
219   * {@link List}'s elements.
220   *
221   * @param as a {@link Collection} of {@link AnnotationMirror}s; must not be {@code null}
222   *
223   * @param domain a {@link PrimordialDomain}; must not be {@code null}
224   *
225   * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotation}s
226   *
227   * @exception NullPointerException if either argument is {@code null}
228   */
229  public static final List<? extends UniversalAnnotation> of(final Collection<? extends AnnotationMirror> as,
230                                                             final PrimordialDomain domain) {
231    if (as.isEmpty()) {
232      return List.of();
233    }
234    final List<UniversalAnnotation> newAs = new ArrayList<>(as.size());
235    for (final AnnotationMirror a : as) {
236      newAs.add(UniversalAnnotation.of(a, domain));
237    }
238    return Collections.unmodifiableList(newAs);
239  }
240
241  /**
242   * <dfn>Unwraps</dfn> the supplied {@link AnnotationMirror} implementation such that the returned value is not an
243   * instance of {@link UniversalAnnotation}.
244   *
245   * @param a an {@link AnnotationMirror}; may be {@code null}
246   *
247   * @return an {@link AnnotationMirror} that is guaranteed not to be an instance of {@link UniversalAnnotation}
248   *
249   * @see #delegate()
250   */
251  public static final AnnotationMirror unwrap(AnnotationMirror a) {
252    while (a instanceof UniversalAnnotation ua) {
253      a = ua.delegate();
254    }
255    return a;
256  }
257
258
259}