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