001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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;
020
021import java.util.List;
022import java.util.Optional;
023
024import javax.lang.model.element.AnnotationMirror;
025import javax.lang.model.element.AnnotationValue;
026import javax.lang.model.element.AnnotationValueVisitor;
027import javax.lang.model.element.VariableElement;
028
029import javax.lang.model.type.TypeMirror;
030
031import org.microbean.construct.constant.Constables;
032
033import static java.lang.constant.ConstantDescs.BSM_INVOKE;
034import static java.lang.constant.ConstantDescs.CD_Object;
035
036import static java.lang.constant.MethodHandleDesc.ofConstructor;
037
038/**
039 * An <strong>experimental</strong> {@link AnnotationValue} implementation that is partially or wholly synthetic.
040 *
041 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
042 */
043public final class SyntheticAnnotationValue implements AnnotationValue, Constable {
044
045
046  /*
047   * Instance fields.
048   */
049
050
051  // Will be one of:
052  //
053  // * AnnotationMirror
054  // * List<SyntheticAnnotationValue>
055  // * TypeMirror (DeclaredType, PrimitiveType)
056  // * VariableElement (ENUM_CONSTANT)
057  // * Boolean
058  // * Byte
059  // * Character
060  // * Double
061  // * Float
062  // * Integer
063  // * Long
064  // * Short
065  // * String
066  private final Object value;
067
068
069  /*
070   * Constructors.
071   */
072
073
074  /**
075   * Creates a new {@link SyntheticAnnotationValue}.
076   *
077   * @param value a value legal for an {@link AnnotationValue}; must not be {@code null}
078   *
079   * @exception NullPointerException if {@code value} is {@code null}
080   *
081   * @exception IllegalArgumentException if {@code value} is not legal for an {@link AnnotationValue}
082   *
083   * @see AnnotationValue
084   */
085  public SyntheticAnnotationValue(final Object value) {
086    super();
087    this.value = value(value);
088  }
089
090
091  /*
092   * Instance methods.
093   */
094
095
096  @Override // AnnotationValue
097  @SuppressWarnings("unchecked")
098  public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) {
099    return switch (this.value) {
100    case null               -> v.visitUnknown(this, p); // ...or AssertionError?
101    case AnnotationMirror a -> v.visitAnnotation(a, p);
102    case List<?> l          -> v.visitArray((List<? extends AnnotationValue>)l, p);
103    case TypeMirror t       -> v.visitType(t, p);
104    case VariableElement e  -> v.visitEnumConstant(e, p);
105    case Boolean b          -> v.visitBoolean(b, p);
106    case Byte b             -> v.visitByte(b, p);
107    case Character c        -> v.visitChar(c, p);
108    case Double d           -> v.visitDouble(d, p);
109    case Float f            -> v.visitFloat(f, p);
110    case Integer i          -> v.visitInt(i, p);
111    case Long l             -> v.visitLong(l, p);
112    case Short s            -> v.visitShort(s, p);
113    case String s           -> v.visitString(s, p);
114    default                 -> v.visitUnknown(this, p);
115    };
116  }
117
118  @Override // Constable
119  public final Optional<DynamicConstantDesc<SyntheticAnnotationValue>> describeConstable() {
120    final Optional<? extends ConstantDesc> valueDescOptional = switch (this.value) {
121    case Constable c -> c.describeConstable();
122    case ConstantDesc cd -> Optional.of(cd);
123    case List<?> l -> Constables.describe(l);
124    default -> Optional.<ConstantDesc>empty();
125    };
126    return valueDescOptional.map(valueDesc -> DynamicConstantDesc.ofNamed(BSM_INVOKE,
127                                                                          this.getClass().getSimpleName(),
128                                                                          this.getClass().describeConstable().orElseThrow(),
129                                                                          ofConstructor(this.getClass().describeConstable().orElseThrow(),
130                                                                                        CD_Object),
131                                                                          valueDesc));
132  }
133
134  @Override // Object
135  public final boolean equals(final Object other) {
136    return this == other || switch (other) {
137    case null -> false;
138    case SyntheticAnnotationValue sav when this.getClass() == sav.getClass() -> this.value.equals(sav.value);
139    default -> false;
140    };
141  }
142
143  @Override // AnnotationValue
144  public final Object getValue() {
145    return this.value;
146  }
147
148  @Override // Object
149  public final int hashCode() {
150    return this.value.hashCode();
151  }
152
153  @Override // Object
154  public final String toString() {
155    return this.value.toString();
156  }
157
158
159  /*
160   * Static methods.
161   */
162
163
164  /**
165   * Returns a non-{@code null}, determinate {@link SyntheticAnnotationValue} that represents the supplied {@code
166   * value}.
167   *
168   * <p>If {@code value} is a {@link SyntheticAnnotationValue}, then it is returned unchanged.</p>
169   *
170   * @param value a value legal for an {@link AnnotationValue}; must not be {@code null}
171   *
172   * @return a non-{@code null}, determinate {@link SyntheticAnnotationValue}
173   *
174   * @exception NullPointerException if {@code value} is {@code null}
175   *
176   * @exception IllegalArgumentException if {@code value} is not legal for an {@link AnnotationValue}
177   *
178   * @see AnnotationValue
179   */
180  public static final SyntheticAnnotationValue of(final Object value) {
181    return switch (value) {
182    case null -> throw new NullPointerException("value");
183    case SyntheticAnnotationValue sav -> sav;
184    default -> new SyntheticAnnotationValue(value);
185    };
186  }
187
188  private static final Object value(final Object value) {
189    return switch (value) {
190    case null               -> throw new NullPointerException("value");
191
192    case AnnotationValue av -> av.getValue(); // not part of the spec; just good hygiene
193
194    case List<?> l          -> l.stream().map(SyntheticAnnotationValue::new).toList();
195
196    case TypeMirror t       -> switch (t.getKind()) {
197    case ARRAY, BOOLEAN, BYTE, CHAR, DECLARED, DOUBLE, FLOAT, INT, LONG, SHORT, VOID -> t;
198    default -> throw new IllegalArgumentException("value: " + value);
199    };
200
201    case VariableElement e  -> switch (e.getKind()) {
202    case ENUM_CONSTANT -> e;
203    default -> throw new IllegalArgumentException("value: " + value);
204    };
205
206    case AnnotationMirror a -> a;
207    case Boolean b          -> b;
208    case Byte b             -> b;
209    case Character c        -> c;
210    case Double d           -> d;
211    case Float f            -> f;
212    case Integer i          -> i;
213    case Long l             -> l;
214    case Short s            -> s;
215    case String s           -> s;
216
217    default                 -> throw new IllegalArgumentException("value: " + value);
218    };
219  }
220
221}