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.List;
020
021import java.util.function.Supplier;
022
023import javax.lang.model.element.AnnotationMirror;
024import javax.lang.model.element.AnnotationValue;
025import javax.lang.model.element.AnnotationValueVisitor;
026import javax.lang.model.element.ExecutableElement;
027import javax.lang.model.element.VariableElement;
028
029import javax.lang.model.type.TypeMirror;
030
031import org.microbean.construct.Domain;
032
033import org.microbean.construct.type.UniversalType;
034
035import static java.util.Objects.requireNonNull;
036
037/**
038 * An {@link AnnotationValue} implementation.
039 *
040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
041 *
042 * @see AnnotationValue
043 */
044public final class UniversalAnnotationValue implements AnnotationValue {
045
046
047  // Eventually this should become a lazy constant/stable value
048  // volatile not needed
049  private Supplier<? extends AnnotationValue> delegateSupplier;
050
051  private final Domain domain;
052
053  private String s;
054
055  private Object v;
056
057
058  /*
059   * Constructors.
060   */
061
062
063  /**
064   * Creates a new {@link UniversalAnnotationValue}.
065   *
066   * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null}
067   *
068   * @param domain a {@link Domain}; must not be {@code null}
069   *
070   * @exception NullPointerException if either argument is {@code null}
071   */
072  @SuppressWarnings("try")
073  public UniversalAnnotationValue(final AnnotationValue delegate, final Domain domain) {
074    super();
075    this.domain = requireNonNull(domain, "domain");
076    final AnnotationValue unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate"));
077    if (unwrappedDelegate == delegate) {
078      // No unwrapping happened so do symbol completion early; most common case
079      this.delegateSupplier = () -> {
080        try (var lock = domain.lock()) {
081          // Should trigger symbol completion; see
082          // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49;
083          // any invocation of getTag() will do it.
084          //
085          // We bother to eagerly cache the value and the string representation because honestly you're going to call
086          // those methods anyway, probably repeatedly.
087          this.v = unwrappedDelegate.getValue();
088          this.s = unwrappedDelegate.toString(); // names will do it too
089          this.delegateSupplier = () -> unwrappedDelegate;
090        }
091        return unwrappedDelegate;
092      };
093    } else {
094      assert delegate instanceof UniversalAnnotationValue;
095      // Symbol completion already happened; no lock needed
096      final UniversalAnnotationValue uav = (UniversalAnnotationValue)delegate;
097      this.v = uav.getValue(); // already cached/computed
098      this.s = uav.toString(); // already cached/computed
099      this.delegateSupplier = () -> unwrappedDelegate;
100    }
101  }
102
103
104  /*
105   * Instance methods.
106   */
107
108
109  @Override // AnnotationValue
110  @SuppressWarnings("unchecked")
111  public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) {
112    return switch (this.getValue()) {
113    case null               -> v.visitUnknown(this, p); // ...or AssertionError?
114    case AnnotationMirror a -> v.visitAnnotation(a, p);
115    case List<?> l          -> v.visitArray((List<? extends AnnotationValue>)l, p);
116    case TypeMirror t       -> v.visitType(t, p);
117    case VariableElement e  -> v.visitEnumConstant(e, p);
118    case Boolean b          -> v.visitBoolean(b, p);
119    case Byte b             -> v.visitByte(b, p);
120    case Character c        -> v.visitChar(c, p);
121    case Double d           -> v.visitDouble(d, p);
122    case Float f            -> v.visitFloat(f, p);
123    case Integer i          -> v.visitInt(i, p);
124    case Long l             -> v.visitLong(l, p);
125    case Short s            -> v.visitShort(s, p);
126    case String s           -> v.visitString(s, p);
127    default                 -> v.visitUnknown(this, p);
128    };
129  }
130
131  /**
132   * Returns the delegate to which operations are delegated.
133   *
134   * @return a non-{@code null} delegate
135   *
136   * @see AnnotationValue
137   */
138  public final AnnotationValue delegate() {
139    final AnnotationValue delegate = this.delegateSupplier.get();
140    assert !(delegate instanceof UniversalAnnotationValue);
141    return delegate;
142  }
143
144  /**
145   * Returns the {@link Domain} supplied at construction time.
146   *
147   * @return the non-{@code null} {@link Domain} supplied at construction time
148   */
149  public final Domain domain() {
150    return this.domain;
151  }
152
153  @Override // Object
154  @SuppressWarnings("try")
155  public final boolean equals(final Object other) {
156    return this == other || switch (other) {
157    case null -> false;
158    // No lock needed; see
159    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45
160    case UniversalAnnotationValue uav when this.getClass() == uav.getClass() -> this.delegate().equals(uav.delegate());
161    default -> false;
162    };
163  }
164
165  @Override // AnnotationValue
166  @SuppressWarnings("unchecked")
167  public final Object getValue() {
168    final Domain domain = this.domain();
169    final Object value = this.v;
170    return switch (value) {
171    case null -> throw new AssertionError();
172    case AnnotationMirror a -> UniversalAnnotation.of(a, domain);
173    case List<?> l -> of((List<? extends AnnotationValue>)l, domain);
174    case TypeMirror t -> UniversalType.of(t, domain);
175    case VariableElement e -> UniversalElement.of(e, domain);
176    default -> value;
177    };
178  }
179
180  @Override // Object
181  @SuppressWarnings({ "try", "unchecked" })
182  public final int hashCode() {
183    // No lock needed; see
184    // https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Attribute.java#L45
185    return this.delegate().hashCode();
186  }
187
188  @Override // AnnotationValue
189  @SuppressWarnings("try")
190  public final String toString() {
191    return this.s;
192  }
193
194
195  /*
196   * Static methods.
197   */
198
199
200  /**
201   * Returns a non-{@code null} {@link UniversalAnnotationValue} that is either the supplied {@link AnnotationValue} (if it
202   * itself is {@code null} or is an {@link UniversalAnnotationValue}) or one that wraps it.
203   *
204   * @param av an {@link AnnotationValue}; may be {@code null}
205   *
206   * @param domain a {@link Domain}; must not be {@code null}
207   *
208   * @return an {@link UniversalAnnotationValue}, or {@code null} (if {@code av} is {@code null})
209   *
210   * @exception NullPointerException if {@code domain} is {@code null}
211   *
212   * @see #UniversalAnnotationValue(AnnotationValue, Domain)
213   */
214  public static final UniversalAnnotationValue of(final AnnotationValue av, final Domain domain) {
215    return switch (av) {
216    case null -> null;
217    case UniversalAnnotationValue uav -> uav;
218    default -> new UniversalAnnotationValue(av, domain);
219    };
220  }
221
222  /**
223   * Returns a non-{@code null}, immutable {@link List} of {@link UniversalAnnotationValue}s whose elements wrap the
224   * supplied {@link List}'s elements.
225   *
226   * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null}
227   *
228   * @param domain a {@link Domain}; must not be {@code null}
229   *
230   * @return a non-{@code null}, immutable {@link List} of {@link UniversalAnnotationValue}s
231   *
232   * @exception NullPointerException if either argument is {@code null}
233   */
234  public static final List<? extends UniversalAnnotationValue> of(final Collection<? extends AnnotationValue> avs,
235                                                                  final Domain domain) {
236    if (avs.isEmpty()) {
237      return List.of();
238    }
239    final List<UniversalAnnotationValue> newAvs = new ArrayList<>(avs.size());
240    for (final AnnotationValue av : avs) {
241      newAvs.add(UniversalAnnotationValue.of(av, domain));
242    }
243    return Collections.unmodifiableList(newAvs);
244  }
245
246  /**
247   * <dfn>Unwraps</dfn> the supplied {@link AnnotationValue} implementation such that the returned value is not an
248   * instance of {@link UniversalAnnotationValue}.
249   *
250   * @param a an {@link AnnotationValue}; may be {@code null}
251   *
252   * @return an {@link AnnotationValue} that is guaranteed not to be an instance of {@link UniversalAnnotationValue}
253   *
254   * @see #delegate()
255   */
256  public static final AnnotationValue unwrap(AnnotationValue a) {
257    while (a instanceof UniversalAnnotationValue uav) {
258      a = uav.delegate();
259    }
260    return a;
261  }
262
263
264}