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