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}