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 java.util.function.Predicate;
022
023import javax.lang.model.element.AnnotationMirror;
024import javax.lang.model.element.AnnotationValue;
025import javax.lang.model.element.ExecutableElement;
026import javax.lang.model.element.QualifiedNameable;
027import javax.lang.model.element.TypeElement;
028import javax.lang.model.element.VariableElement;
029
030import javax.lang.model.type.ArrayType;
031import javax.lang.model.type.DeclaredType;
032import javax.lang.model.type.NoType;
033import javax.lang.model.type.PrimitiveType;
034import javax.lang.model.type.TypeMirror;
035
036import javax.lang.model.util.AbstractAnnotationValueVisitor14;
037
038import static javax.lang.model.element.ElementKind.ENUM;
039import static javax.lang.model.element.ElementKind.ENUM_CONSTANT;
040
041import static javax.lang.model.type.TypeKind.ARRAY;
042import static javax.lang.model.type.TypeKind.DECLARED;
043import static javax.lang.model.type.TypeKind.VOID;
044
045import static org.microbean.construct.element.AnnotationMirrors.allAnnotationValues;
046
047/**
048 * An {@link AbstractAnnotationValueVisitor14} that determines if the otherwise opaque values {@linkplain
049 * AnnotationValue#getValue() represented} by two {@link AnnotationValue} implementations are to be considered the
050 * <dfn>same</dfn>.
051 *
052 * <p>This class implements the rules described by the {@link java.lang.annotation.Annotation#equals(Object)} contract,
053 * for want of a more authoritative source. This contract appears to define in a mostly agnostic manner what it means
054 * for two annotations to be "the same".</p>
055 *
056 * <p>Unlike some other annotation-processing-related facilities, the relation represented by this {@link
057 * SameAnnotationValueVisitor} does not require that the values being logically compared originate from {@link
058 * AnnotationValue} instances from the same vendor or toolkit.</p>
059 *
060 * <p>The second argument passed to {@link #visit(AnnotationValue, Object)} is expected to be either {@code null}, an
061 * {@link AnnotationValue}, or the result of an invocation of an {@link AnnotationValue}'s {@link
062 * AnnotationValue#getValue() getValue()} method.</p>
063 *
064 * <p>Any two {@link TypeElement}s encountered during traversal are considered equal if their {@linkplain
065 * TypeElement#getQualifiedName() qualified names} have {@linkplain
066 * javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.</p>
067 *
068 * <p>Any two {@link VariableElement}s representing enum constants encountered during traversal are considered equal if
069 * they belong to the same enum class and their {@linkplain VariableElement#getSimpleName() simple names} have
070 * {@linkplain javax.lang.model.element.Name#contentEquals(CharSequence) equal contents}.</p>
071 *
072 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
073 *
074 * @see AnnotationValue#accept(javax.lang.model.element.AnnotationValueVisitor, Object)
075 *
076 * @see AnnotationMirrors#allAnnotationValues(AnnotationMirror)
077 *
078 * @see AnnotationValueHashcodeVisitor
079 *
080 * @see java.lang.annotation.Annotation#equals(Object)
081 */
082public final class SameAnnotationValueVisitor extends AbstractAnnotationValueVisitor14<Boolean, Object> {
083
084
085
086  /*
087   * Instance fields.
088   */
089
090
091  private final Predicate<? super ExecutableElement> p;
092
093
094
095  /*
096   * Constructors.
097   */
098
099
100  /**
101   * Creates a new {@link SameAnnotationValueVisitor}.
102   *
103   * @see #SameAnnotationValueVisitor(Predicate)
104   */
105  public SameAnnotationValueVisitor() {
106    this(null);
107  }
108
109  /**
110   * Creates a new {@link SameAnnotationValueVisitor}.
111   *
112   * @param p a {@link Predicate} that returns {@code true} if a given {@link ExecutableElement}, representing an
113   * annotation interface element, is to be included in the computation; may be {@code null} in which case it is as if
114   * {@code ()-> true} were supplied instead
115   */
116  public SameAnnotationValueVisitor(final Predicate<? super ExecutableElement> p) {
117    super();
118    this.p = p == null ? ee -> true : p;
119  }
120
121
122  /*
123   * Instance methods.
124   */
125
126
127  @Override // AbstractAnnotationValueVisitor14
128  public final Boolean visitAnnotation(final AnnotationMirror am0, final Object v1) {
129    return am0 == v1 || am0 != null && switch (v1) {
130    case null -> false;
131    case AnnotationValue av1 -> this.visitAnnotation(am0, av1.getValue());
132    case AnnotationMirror am1 -> {
133      if (!((QualifiedNameable)am0.getAnnotationType().asElement()).getQualifiedName().contentEquals(((QualifiedNameable)am1.getAnnotationType().asElement()).getQualifiedName())) {
134        yield false;
135      }
136      final Iterator<Entry<ExecutableElement, AnnotationValue>> i0 = allAnnotationValues(am0).entrySet().iterator();
137      final Iterator<Entry<ExecutableElement, AnnotationValue>> i1 = allAnnotationValues(am1).entrySet().iterator();
138      while (i0.hasNext()) {
139        if (!i1.hasNext()) {
140          yield false;
141        }
142        final Entry<ExecutableElement, AnnotationValue> e0 = i0.next();
143        final Entry<ExecutableElement, AnnotationValue> e1 = i1.next();
144        final ExecutableElement ee0 = e0.getKey();
145        if (!ee0.getSimpleName().contentEquals(e1.getKey().getSimpleName()) ||
146            this.p.test(ee0) && !this.visit(e0.getValue(), e1.getValue().getValue())) {
147          yield false;
148        }
149      }
150      yield !i1.hasNext();
151    }
152    default -> false;
153    };
154  }
155
156  @Override // AbstractAnnotationValueVisitor14
157  public final Boolean visitArray(final List<? extends AnnotationValue> l0, final Object v1) {
158    return l0 == v1 || l0 != null && switch (v1) {
159    case null -> false;
160    case AnnotationValue av1 -> this.visitArray(l0, av1.getValue());
161    case List<?> l1 -> {
162      final int size = l0.size();
163      if (size != l1.size()) {
164        yield false;
165      }
166      for (int i = 0; i < size; i++) {
167        // Yes, order is important (!)
168        if (!(l1.get(i) instanceof AnnotationValue av1) || !this.visit(av1, l0.get(i).getValue())) {
169          yield false;
170        }
171      }
172      yield true;
173    }
174    default -> false;
175    };
176  }
177
178  @Override // AbstractAnnotationValueVisitor14
179  public final Boolean visitBoolean(final boolean b0, final Object v1) {
180    return switch (v1) {
181    case null -> false;
182    case AnnotationValue av1 -> this.visitBoolean(b0, av1.getValue());
183    case Boolean b1 -> b0 && b1.booleanValue();
184    default -> false;
185    };
186  }
187
188  @Override // AbstractAnnotationValueVisitor14
189  public final Boolean visitByte(final byte b0, final Object v1) {
190    return switch (v1) {
191    case null -> false;
192    case AnnotationValue av1 -> this.visitByte(b0, av1.getValue());
193    case Byte b1 -> b0 == b1.byteValue();
194    default -> false;
195    };
196  }
197
198  @Override // AbstractAnnotationValueVisitor14
199  public final Boolean visitChar(final char c0, final Object v1) {
200    return switch (v1) {
201    case null -> false;
202    case AnnotationValue av1 -> this.visitChar(c0, av1.getValue());
203    case Character c1 -> c0 == c1.charValue();
204    default -> false;
205    };
206  }
207
208  @Override // AbstractAnnotationValueVisitor14
209  public final Boolean visitDouble(final double d0, final Object v1) {
210    return v1 instanceof AnnotationValue av1 ? this.visitDouble(d0, av1.getValue()) : Double.valueOf(d0).equals(v1);
211  }
212
213  @Override // AbstractAnnotationValueVisitor14
214  public final Boolean visitEnumConstant(final VariableElement ve0, final Object v1) {
215    return ve0 == v1 || ve0!= null && switch (v1) {
216    case null -> false;
217    case AnnotationValue av1 -> this.visitEnumConstant(ve0, av1.getValue());
218    case VariableElement ve1 when ve0.getKind() == ENUM_CONSTANT && ve1.getKind() == ENUM_CONSTANT -> {
219      final TypeElement te0 = (TypeElement)ve0.getEnclosingElement();
220      final TypeElement te1 = (TypeElement)ve1.getEnclosingElement();
221      yield switch (te0.getKind()) {
222      case ENUM -> te1.getKind() == ENUM && ve0.getSimpleName().contentEquals(ve1.getSimpleName()) && te0.getQualifiedName().contentEquals(te1.getQualifiedName());
223      default -> false;
224      };
225    }
226    default -> false;
227    };
228  }
229
230  @Override // AbstractAnnotationValueVisitor14
231  public final Boolean visitFloat(final float f0, final Object v1) {
232    return v1 instanceof AnnotationValue av1 ? this.visitFloat(f0, av1.getValue()) : Float.valueOf(f0).equals(v1);
233  }
234
235  @Override // AbstractAnnotationValueVisitor14
236  public final Boolean visitInt(final int i0, final Object v1) {
237    return switch (v1) {
238    case null -> false;
239    case AnnotationValue av1 -> this.visitInt(i0, av1.getValue());
240    case Integer i1 -> i0 == i1.intValue();
241    default -> false;
242    };
243  }
244
245  @Override // AbstractAnnotationValueVisitor14
246  public final Boolean visitLong(final long l0, final Object v1) {
247    return switch (v1) {
248    case null -> false;
249    case AnnotationValue av1 -> this.visitLong(l0, av1.getValue());
250    case Long l1 -> l0 == l1.longValue();
251    default -> false;
252    };
253  }
254
255  @Override // AbstractAnnotationValueVisitor14
256  public final Boolean visitShort(final short s0, final Object v1) {
257    return switch (v1) {
258    case null -> false;
259    case AnnotationValue av1 -> this.visitShort(s0, av1.getValue());
260    case Short s1 -> s0 == s1.shortValue();
261    default -> false;
262    };
263  }
264
265  @Override // AbstractAnnotationValueVisitor14
266  public final Boolean visitString(final String s0, final Object v1) {
267    return v1 instanceof AnnotationValue av1 ? this.visitString(s0, av1.getValue()) : Objects.equals(s0, v1);
268  }
269
270  @Override // AbstractAnnotationValueVisitor14
271  public final Boolean visitType(final TypeMirror t0, final Object v1) {
272    return t0 == v1 || v1 != null && switch (t0) {
273    case null -> false;
274    case ArrayType a0 when a0.getKind() == ARRAY -> this.privateVisitArrayType(a0, v1); // e.g. Object[].class
275    case DeclaredType dt0 when dt0.getKind() == DECLARED -> this.privateVisitDeclaredType(dt0, v1); // e.g. Foo.class
276    case PrimitiveType p0 when p0.getKind().isPrimitive() -> this.privateVisitPrimitiveType(p0, v1); // e.g. int.class
277    case NoType n0 when n0.getKind() == VOID -> this.privateVisitNoType(n0, v1); // e.g. void.class
278    default -> t0.equals(v1);
279    };
280  }
281
282
283  /*
284   * Private instance methods.
285   */
286
287
288  private final Boolean privateVisitArrayType(final ArrayType a0, final Object v1) {
289    assert a0.getKind() == ARRAY;
290    return switch (v1) {
291    case AnnotationValue av1 -> this.privateVisitArrayType(a0, av1.getValue());
292    case ArrayType a1 when a1.getKind() == ARRAY -> this.visitType(a0.getComponentType(), a1.getComponentType());
293    default -> false;
294    };
295  }
296
297  private final Boolean privateVisitDeclaredType(final DeclaredType dt0, final Object v1) {
298    assert dt0.getKind() == DECLARED;
299    return switch (v1) {
300    case AnnotationValue av1 -> this.privateVisitDeclaredType(dt0, av1.getValue());
301    case DeclaredType dt1 when dt1.getKind() == DECLARED -> ((QualifiedNameable)dt0.asElement()).getQualifiedName().contentEquals((((QualifiedNameable)dt1.asElement()).getQualifiedName()));
302    default -> false;
303    };
304  }
305
306  private final Boolean privateVisitNoType(final NoType n0, final Object v1) {
307    assert n0.getKind() == VOID;
308    return switch (v1) {
309    case AnnotationValue av1 -> this.privateVisitNoType(n0, av1.getValue());
310    case NoType n1 -> n1.getKind() == VOID;
311    default -> false;
312    };
313  }
314
315  private final Boolean privateVisitPrimitiveType(final PrimitiveType p0, final Object v1) {
316    assert p0.getKind().isPrimitive();
317    return switch (v1) {
318    case AnnotationValue av1 -> this.privateVisitPrimitiveType(p0, av1.getValue());
319    case PrimitiveType p1 -> p1.getKind() == p0.getKind();
320    default -> false;
321    };
322  }
323
324}