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}