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.util.Iterator;
017import java.util.List;
018import java.util.Map.Entry;
019import java.util.Objects;
020
021import javax.lang.model.element.AnnotationMirror;
022import javax.lang.model.element.AnnotationValue;
023import javax.lang.model.element.ExecutableElement;
024import javax.lang.model.element.QualifiedNameable;
025import javax.lang.model.element.TypeElement;
026import javax.lang.model.element.VariableElement;
027
028import javax.lang.model.type.ArrayType;
029import javax.lang.model.type.DeclaredType;
030import javax.lang.model.type.NoType;
031import javax.lang.model.type.PrimitiveType;
032import javax.lang.model.type.TypeMirror;
033
034import javax.lang.model.util.AbstractAnnotationValueVisitor14;
035
036import static javax.lang.model.element.ElementKind.ENUM;
037import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
038
039import static javax.lang.model.type.TypeKind.ARRAY;
040import static javax.lang.model.type.TypeKind.DECLARED;
041import static javax.lang.model.type.TypeKind.VOID;
042
043import static org.microbean.construct.element.AnnotationMirrors.allAnnotationValues;
044
045/**
046 * An {@link AbstractAnnotationValueVisitor14} that determines if the otherwise opaque values {@linkplain
047 * AnnotationValue#getValue() represented} by two {@link AnnotationValue} implementations are to be considered the
048 * <dfn>same</dfn>.
049 *
050 * <p>Unlike some other annotation-processing-related facilities, the relation represented by this {@link
051 * SameAnnotationValueVisitor} does not require that the values being logically compared originate from {@link
052 * AnnotationValue} instances from the same vendor or toolkit.</p>
053 *
054 * <p>The second argument passed to {@link #visit(AnnotationValue, Object)} is expected to be either {@code null}, an
055 * {@link AnnotationValue}, or the result of an invocation of an {@link AnnotationValue}'s {@link
056 * AnnotationValue#getValue() getValue()} method.</p>
057 *
058 * <p>Any two {@link TypeElement}s encountered during traversal are considered equal if their {@linkplain
059 * TypeElement#getQualifiedName() qualified names} have {@linkplain
060 * javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.</p>
061 *
062 * <p>Any two {@link VariableElement}s representing enum constants encountered during traversal are considered equal if
063 * their {@linkplain VariableElement#getSimpleName() simple names} have {@linkplain
064 * javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.</p>
065 *
066 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
067 *
068 * @see AnnotationValue#accept(javax.lang.model.element.AnnotationValueVisitor, Object)
069 *
070 * @see AnnotationMirrors#allAnnotationValues(AnnotationMirror)
071 */
072public final class SameAnnotationValueVisitor extends AbstractAnnotationValueVisitor14<Boolean, Object> {
073
074
075  /*
076   * Constructors.
077   */
078
079
080  /**
081   * Creates a new {@link SameAnnotationValueVisitor}.
082   */
083  public SameAnnotationValueVisitor() {
084    super();
085  }
086
087
088  /*
089   * Instance methods.
090   */
091
092
093  @Override // AbstractAnnotationValueVisitor14
094  public final Boolean visitAnnotation(final AnnotationMirror am0, final Object v1) {
095    return am0 == v1 || am0 != null && switch (v1) {
096    case null -> false;
097    case AnnotationValue av1 -> this.visitAnnotation(am0, av1.getValue());
098    case AnnotationMirror am1 -> {
099      final Iterator<Entry<ExecutableElement, AnnotationValue>> i0 = allAnnotationValues(am0).entrySet().iterator();
100      final Iterator<Entry<ExecutableElement, AnnotationValue>> i1 = allAnnotationValues(am1).entrySet().iterator();
101      while (i0.hasNext()) {
102        if (!i1.hasNext()) {
103          yield false;
104        }
105        final Entry<ExecutableElement, AnnotationValue> e0 = i0.next();
106        final Entry<ExecutableElement, AnnotationValue> e1 = i1.next();
107        if (!e0.getKey().getSimpleName().contentEquals(e1.getKey().getSimpleName()) ||
108            !this.visit(e0.getValue(), e1.getValue().getValue())) {
109          yield false;
110        }
111      }
112      yield i1.hasNext();
113    }
114    default -> false;
115    };
116  }
117
118  @Override // AbstractAnnotationValueVisitor14
119  public final Boolean visitArray(final List<? extends AnnotationValue> l0, final Object v1) {
120    return l0 == v1 || l0 != null && switch (v1) {
121    case null -> false;
122    case AnnotationValue av1 -> this.visitArray(l0, av1.getValue());
123    case List<?> l1 -> {
124      final int size = l0.size();
125      if (size != l1.size()) {
126        yield false;
127      }
128      for (int i = 0; i < size; i++) {
129        if (l1.get(i) instanceof AnnotationValue av1 && this.visit(av1, l0.get(i).getValue())) {
130          continue;
131        }
132        yield false;
133      }
134      yield true;
135    }
136    default -> false;
137    };
138  }
139
140  @Override // AbstractAnnotationValueVisitor14
141  public final Boolean visitBoolean(final boolean b0, final Object v1) {
142    return v1 instanceof AnnotationValue av1 ? this.visitBoolean(b0, av1.getValue()) : Boolean.valueOf(b0).equals(v1);
143  }
144
145  @Override // AbstractAnnotationValueVisitor14
146  public final Boolean visitByte(final byte b0, final Object v1) {
147    return v1 instanceof AnnotationValue av1 ? this.visitByte(b0, av1.getValue()) : Byte.valueOf(b0).equals(v1);
148  }
149
150  @Override // AbstractAnnotationValueVisitor14
151  public final Boolean visitChar(final char c0, final Object v1) {
152    return v1 instanceof AnnotationValue av1 ? this.visitChar(c0, av1.getValue()) : Character.valueOf(c0).equals(v1);
153  }
154
155  @Override // AbstractAnnotationValueVisitor14
156  public final Boolean visitDouble(final double d0, final Object v1) {
157    return v1 instanceof AnnotationValue av1 ? this.visitDouble(d0, av1.getValue()) : Double.valueOf(d0).equals(v1);
158  }
159
160  @Override // AbstractAnnotationValueVisitor14
161  public final Boolean visitEnumConstant(final VariableElement ve0, final Object v1) {
162    return ve0 == v1 || ve0!= null && switch (v1) {
163    case null -> false;
164    case AnnotationValue av1 -> this.visitEnumConstant(ve0, av1.getValue());
165    case VariableElement ve1 when ve0.getKind() == ENUM_CONSTANT && ve1.getKind() == ENUM_CONSTANT -> {
166      final TypeElement te0 = (TypeElement)ve0.getEnclosingElement();
167      final TypeElement te1 = (TypeElement)ve1.getEnclosingElement();
168      yield switch (te0.getKind()) {
169      case ENUM -> te1.getKind() == ENUM && ve0.getSimpleName().contentEquals(ve1.getSimpleName()) && te0.getQualifiedName().contentEquals(te1.getQualifiedName());
170      default -> false;
171      };
172    }
173    default -> false;
174    };
175  }
176
177  @Override // AbstractAnnotationValueVisitor14
178  public final Boolean visitFloat(final float f0, final Object v1) {
179    return v1 instanceof AnnotationValue av1 ? this.visitFloat(f0, av1.getValue()) : Float.valueOf(f0).equals(v1);
180  }
181
182  @Override // AbstractAnnotationValueVisitor14
183  public final Boolean visitInt(final int i0, final Object v1) {
184    return v1 instanceof AnnotationValue av1 ? this.visitInt(i0, av1.getValue()) : Integer.valueOf(i0).equals(v1);
185  }
186
187  @Override // AbstractAnnotationValueVisitor14
188  public final Boolean visitLong(final long l0, final Object v1) {
189    return v1 instanceof AnnotationValue av1 ? this.visitLong(l0, av1.getValue()) : Long.valueOf(l0).equals(v1);
190  }
191
192  @Override // AbstractAnnotationValueVisitor14
193  public final Boolean visitShort(final short s0, final Object v1) {
194    return v1 instanceof AnnotationValue av1 ? this.visitShort(s0, av1.getValue()) : Short.valueOf(s0).equals(v1);
195  }
196
197  @Override // AbstractAnnotationValueVisitor14
198  public final Boolean visitString(final String s0, final Object v1) {
199    return v1 instanceof AnnotationValue av1 ? this.visitString(s0, av1.getValue()) : Objects.equals(s0, v1);
200  }
201
202  @Override // AbstractAnnotationValueVisitor14
203  public final Boolean visitType(final TypeMirror t0, final Object v1) {
204    return t0 == v1 || v1 != null && switch (t0) {
205    case null -> false;
206    case AnnotationValue av1 -> this.visitType(t0, av1.getValue());
207    case ArrayType a0 when a0.getKind() == ARRAY -> this.visitArrayType(a0, v1); // e.g. Object[].class
208    case DeclaredType dt0 when dt0.getKind() == DECLARED -> this.visitDeclaredType(dt0, v1); // e.g. Foo.class
209    case PrimitiveType p0 when p0.getKind().isPrimitive() -> this.visitPrimitiveType(p0, v1); // e.g. int.class
210    case NoType n0 when n0.getKind() == VOID -> this.visitNoType(n0, v1); // e.g. void.class
211    default -> t0.equals(v1);
212    };
213  }
214
215  @Override // AbstractAnnotationValueVisitor14
216  public final Boolean visitUnknown(final AnnotationValue av0, final Object v1) {
217    return v1 instanceof AnnotationValue av1 ? this.visitUnknown(av0, av1.getValue()) : Objects.equals(av0, v1);
218  }
219
220
221  /*
222   * Private instance methods.
223   */
224
225
226  private final Boolean visitArrayType(final ArrayType a0, final Object v1) {
227    assert a0.getKind() == ARRAY;
228    assert !(v1 instanceof AnnotationValue);
229    return switch (v1) {
230    case ArrayType a1 when a1.getKind() == ARRAY -> this.visitType(a0.getComponentType(), a1.getComponentType());
231    default -> false;
232    };
233  }
234
235  private final Boolean visitDeclaredType(final DeclaredType dt0, final Object v1) {
236    assert dt0.getKind() == DECLARED;
237    assert !(v1 instanceof AnnotationValue);
238    return switch (v1) {
239    case DeclaredType dt1 when dt1.getKind() == DECLARED -> ((QualifiedNameable)dt0.asElement()).getQualifiedName().contentEquals((((QualifiedNameable)dt1.asElement()).getQualifiedName()));
240    default -> false;
241    };
242  }
243
244  private final Boolean visitNoType(final NoType n0, final Object v1) {
245    assert n0.getKind() == VOID;
246    assert !(v1 instanceof AnnotationValue);
247    return switch (v1) {
248    case NoType n1 -> n1.getKind() == VOID;
249    default -> false;
250    };
251  }
252
253  private final Boolean visitPrimitiveType(final PrimitiveType p0, final Object v1) {
254    assert p0.getKind().isPrimitive();
255    assert !(v1 instanceof AnnotationValue);
256    return switch (v1) {
257    case PrimitiveType p1 -> p1.getKind() == p0.getKind();
258    default -> false;
259    };
260  }
261
262}