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}