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.List; 017import java.util.Map; 018 019import java.util.function.Predicate; 020 021import javax.lang.model.element.AnnotationMirror; 022import javax.lang.model.element.AnnotationValue; 023import javax.lang.model.element.Element; 024import javax.lang.model.element.ExecutableElement; 025import javax.lang.model.element.VariableElement; 026 027import javax.lang.model.type.TypeMirror; 028 029import javax.lang.model.util.AbstractAnnotationValueVisitor14; 030 031import static javax.lang.model.element.ElementKind.METHOD; 032 033/** 034 * An {@link AbstractAnnotationValueVisitor14} that computes a hashcode for an {@link AnnotationValue}, emulating as 035 * closely as possible the rules described by the {@link java.lang.annotation.Annotation#hashCode()} contract. 036 * 037 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 038 * 039 * @see SameAnnotationValueVisitor 040 */ 041// Goal is to emulate as much as possible the rules from java.lang.annotation.Annotation which capture what it means for 042// two annotations to be "the same", and read in part as follows: 043// 044// The hash code of an annotation is the sum of the hash codes of its members (including those with default values). The 045// hash code of an annotation member is (127 times the hash code of the member-name as computed by String.hashCode()) 046// XOR the hash code of the member-value. The hash code of a member-value depends on its type as defined below: 047// 048// * The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode() [or, equivalently, 049// WrapperType.hashCode(v)], where WrapperType is the wrapper type corresponding to the primitive type of v (Byte, 050// Character, Double, Float, Integer, Long, Short, or Boolean). 051// 052// * The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In 053// the case of annotation member values, this is a recursive definition.) 054// 055// * The hash code of an array member-value is computed by calling the appropriate overloading of Arrays.hashCode on 056// the value. (There is one overloading for each primitive type, and one for object reference types.) 057// 058public final class AnnotationValueHashcodeVisitor extends AbstractAnnotationValueVisitor14<Integer, Predicate<? super ExecutableElement>> { 059 060 /** 061 * Creates a new {@link AnnotationValueHashcodeVisitor}. 062 */ 063 public AnnotationValueHashcodeVisitor() { 064 super(); 065 } 066 067 @Override // AbstractAnnotationValueVisitor14q 068 public final Integer visitAnnotation(final AnnotationMirror am0, Predicate<? super ExecutableElement> p) { 069 if (am0 == null) { 070 return 0; 071 } 072 if (p == null) { 073 p = ee -> true; 074 } 075 int hashCode = 0; 076 final Map<? extends ExecutableElement, ? extends AnnotationValue> explicitValues = am0.getElementValues(); 077 for (final Element e : am0.getAnnotationType().asElement().getEnclosedElements()) { 078 if (e.getKind() == METHOD && e instanceof ExecutableElement ee && p.test(ee)) { 079 final AnnotationValue v = explicitValues.containsKey(ee) ? explicitValues.get(ee) : ee.getDefaultValue(); 080 // An annotation member value is either explicit or default but cannot be null. 081 assert v != null : "v == null; ee: " + ee; 082 // "The hash code of an annotation is the sum of the hash codes of its members (including those with default values). The 083 // hash code of an annotation member is (127 times the hash code of the member-name as computed by String.hashCode()) 084 // XOR the hash code of the member-value." 085 hashCode += ((127 * ee.getSimpleName().toString().hashCode()) ^ this.visit(v, p).intValue()); 086 } 087 } 088 return hashCode; 089 } 090 091 @Override // AbstractAnnotationValueVisitor14 092 public final Integer visitArray(final List<? extends AnnotationValue> l0, final Predicate<? super ExecutableElement> ignored) { 093 // "The hash code of an array[-typed annotation] member-value is computed by calling the appropriate overloading of 094 // Arrays.hashCode on the value. (There is one overloading for each primitive type, and one for object reference 095 // types.)" 096 // 097 // More cumbersome than you might think. Somewhat conveniently, in general Arrays.hashCode(something) will perform 098 // the same calculation as an equivalent List. So perform the calculation on a "de-AnnotationValueized" List. 099 // 100 // (Implementation note: in the JDK as of this writing, ArraySupport will do fancy stuff with vectors, which 101 // presumably List's hashCode calculations do not. If this ends up being some kind of hot spot, we could turn the 102 // List into an array appropriately.) 103 return l0.stream().map(AnnotationValue::getValue).toList().hashCode(); 104 } 105 106 @Override // AbstractAnnotationValueVisitor14 107 public final Integer visitBoolean(final boolean b0, final Predicate<? super ExecutableElement> ignored) { 108 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 109 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 110 // Boolean)." 111 return Boolean.hashCode(b0); 112 } 113 114 @Override // AbstractAnnotationValueVisitor14 115 public final Integer visitByte(final byte b0, final Predicate<? super ExecutableElement> ignored) { 116 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 117 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 118 // Boolean)." 119 return Byte.hashCode(b0); 120 } 121 122 @Override // AbstractAnnotationValueVisitor14 123 public final Integer visitChar(final char c0, final Predicate<? super ExecutableElement> ignored) { 124 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 125 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 126 // Boolean)." 127 return Character.hashCode(c0); 128 } 129 130 @Override // AbstractAnnotationValueVisitor14 131 public final Integer visitDouble(final double d0, final Predicate<? super ExecutableElement> ignored) { 132 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 133 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 134 // Boolean)." 135 return Double.hashCode(d0); 136 } 137 138 @Override // AbstractAnnotationValueVisitor14 139 public final Integer visitEnumConstant(final VariableElement ve0, final Predicate<? super ExecutableElement> ignored) { 140 // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In 141 // the case of annotation member values, this is a recursive definition.)" 142 return ve0 == null ? 0 : ve0.hashCode(); 143 } 144 145 @Override // AbstractAnnotationValueVisitor14 146 public final Integer visitFloat(final float f0, final Predicate<? super ExecutableElement> ignored) { 147 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 148 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 149 // Boolean)." 150 return Float.hashCode(f0); 151 } 152 153 @Override // AbstractAnnotationValueVisitor14 154 public final Integer visitInt(final int i0, final Predicate<? super ExecutableElement> ignored) { 155 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 156 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 157 // Boolean)." 158 return Integer.hashCode(i0); 159 } 160 161 @Override // AbstractAnnotationValueVisitor14 162 public final Integer visitLong(final long l0, final Predicate<? super ExecutableElement> ignored) { 163 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 164 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 165 // Boolean)." 166 return Long.hashCode(l0); 167 } 168 169 @Override // AbstractAnnotationValueVisitor14 170 public final Integer visitShort(final short s0, final Predicate<? super ExecutableElement> ignored) { 171 // "The hash code of a primitive value v is equal to WrapperType.valueOf(v).hashCode(), where WrapperType is the 172 // wrapper type corresponding to the primitive type of v (Byte, Character, Double, Float, Integer, Long, Short, or 173 // Boolean)." 174 return Short.hashCode(s0); 175 } 176 177 @Override // AbstractAnnotationValueVisitor14 178 public final Integer visitString(final String s0, final Predicate<? super ExecutableElement> ignored) { 179 // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In 180 // the case of annotation member values, this is a recursive definition.)" 181 return s0 == null ? 0 : s0.hashCode(); 182 } 183 184 @Override // AbstractAnnotationValueVisitor14 185 public final Integer visitType(final TypeMirror t0, final Predicate<? super ExecutableElement> ignored) { 186 // "The hash code of a string, enum, class, or annotation member-value v is computed as by calling v.hashCode(). (In 187 // the case of annotation member values, this is a recursive definition.)" 188 return t0 == null ? 0 : t0.hashCode(); 189 } 190 191}