001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024 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;
020import java.util.Objects;
021
022import javax.lang.model.element.AnnotationMirror;
023import javax.lang.model.element.AnnotationValue;
024import javax.lang.model.element.AnnotationValueVisitor;
025import javax.lang.model.element.ExecutableElement;
026import javax.lang.model.element.VariableElement;
027
028import javax.lang.model.type.TypeMirror;
029
030import org.microbean.construct.Domain;
031
032import org.microbean.construct.type.UniversalType;
033
034/**
035 * An {@link AnnotationValue} implementation.
036 *
037 * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null}
038 *
039 * @param domain a {@link Domain}; must not be {@code null}
040 *
041 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
042 *
043 * @see AnnotationValue
044 */
045public final record AnnotationValueRecord(AnnotationValue delegate, Domain domain) implements AnnotationValue {
046
047
048  /*
049   * Constructors.
050   */
051
052
053  /**
054   * Creates a new {@link AnnotationValueRecord}.
055   *
056   * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null}
057   *
058   * @param domain a {@link Domain}; must not be {@code null}
059   *
060   * @exception NullPointerException if either argument is {@code null}
061   */
062  public AnnotationValueRecord {
063    Objects.requireNonNull(delegate, "delegate");
064    Objects.requireNonNull(domain, "domain");
065  }
066
067
068  /*
069   * Instance methods.
070   */
071
072
073  @Override // AnnotationValue
074  @SuppressWarnings({ "try", "unchecked" })
075  public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) {
076    try (var lock = this.domain().lock()) {
077      return switch (this.getValue()) {
078      case null -> v.visitUnknown(this, p); // ...or AssertionError?
079      case AnnotationMirror a -> v.visitAnnotation(a, p);
080      case List<?> l -> v.visitArray((List<? extends AnnotationValue>)l, p);
081      case TypeMirror t -> v.visitType(t, p);
082      case VariableElement e -> v.visitEnumConstant(e, p);
083      case Boolean b -> v.visitBoolean(b, p);
084      case Byte b -> v.visitByte(b, p);
085      case Character c -> v.visitChar(c, p);
086      case Double d -> v.visitDouble(d, p);
087      case Float f -> v.visitFloat(f, p);
088      case Integer i -> v.visitInt(i, p);
089      case Long l -> v.visitLong(l, p);
090      case Short s -> v.visitShort(s, p);
091      case String s -> v.visitString(s, p);
092      default -> v.visitUnknown(this, p);
093      };
094    }
095  }
096
097  @Override // Object
098  @SuppressWarnings("try")
099  public final boolean equals(final Object other) {
100    return this == other || switch (other) {
101    case null -> false;
102    case AnnotationValue av -> {
103      try (var lock = this.domain().lock()) {
104        // The mere act of getting a value (even of type String) can trigger symbol completion:
105        // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49
106        yield this.delegate().equals(av instanceof AnnotationValueRecord avr ? avr.delegate() : av);
107      }
108    }
109    default -> false;
110    };
111  }
112
113  @Override // AnnotationValue
114  @SuppressWarnings({ "try", "unchecked" })
115  public final Object getValue() {
116    final Domain domain = this.domain();
117    final Object value;
118    try (var lock = domain.lock()) {
119      // The mere act of getting a value (even of type String) can trigger symbol completion:
120      // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49
121      value = this.delegate().getValue();
122    }
123    return switch (value) {
124    case null -> throw new AssertionError();
125    case AnnotationMirror a -> AnnotationRecord.of(a, domain);
126    case List<?> l -> of((List<? extends AnnotationValue>)l, domain);
127    case TypeMirror t -> UniversalType.of(t, domain);
128    case VariableElement e -> UniversalElement.of(e, domain);
129    default -> value;
130    };
131  }
132
133  @Override // Object
134  public final int hashCode() {
135    return this.getValue().hashCode();
136  }
137
138  @Override // AnnotationValue
139  @SuppressWarnings("try")
140  public final String toString() {
141    try (var lock = this.domain().lock()) {
142      // Can cause symbol completion and/or Name#toString() calls
143      return this.delegate().toString();
144    }
145  }
146
147
148  /*
149   * Static methods.
150   */
151
152
153  /**
154   * Returns a non-{@code null} {@link AnnotationValueRecord} that is either the supplied {@link AnnotationValue} (if it
155   * itself is {@code null} or is an {@link AnnotationValueRecord}) or one that wraps it.
156   *
157   * @param av an {@link AnnotationValue}; may be {@code null}
158   *
159   * @param domain a {@link Domain}; must not be {@code null}
160   *
161   * @return an {@link AnnotationValueRecord}, or {@code null} (if {@code av} is {@code null})
162   *
163   * @exception NullPointerException if {@code domain} is {@code null}
164   *
165   * @see #AnnotationValueRecord(AnnotationValue, Domain)
166   */
167  public static final AnnotationValueRecord of(final AnnotationValue av, final Domain domain) {
168    return switch (av) {
169    case null -> null;
170    case AnnotationValueRecord avr -> avr;
171    default -> new AnnotationValueRecord(av, domain);
172    };
173  }
174
175  /**
176   * Returns a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s whose elements wrap the
177   * supplied {@link List}'s elements.
178   *
179   * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null}
180   *
181   * @param domain a {@link Domain}; must not be {@code null}
182   *
183   * @return a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s
184   *
185   * @exception NullPointerException if either argument is {@code null}
186   */
187  public static final List<? extends AnnotationValueRecord> of(final Collection<? extends AnnotationValue> avs,
188                                                               final Domain domain) {
189    if (avs.isEmpty()) {
190      return List.of();
191    }
192    final List<AnnotationValueRecord> newAvs = new ArrayList<>(avs.size());
193    for (final AnnotationValue av : avs) {
194      newAvs.add(AnnotationValueRecord.of(av, domain));
195    }
196    return Collections.unmodifiableList(newAvs);
197  }
198
199
200}