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