001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.lang;
018
019import static java.util.Arrays.*;
020
021import java.lang.annotation.*;
022import java.util.*;
023
024import org.apache.juneau.commons.utils.*;
025
026/**
027 * Utility class for generating integer hash codes.
028 *
029 * <p>
030 * General usage:
031 * <p class='bjava'>
032 *    <jk>int</jk> <jv>hashCode</jv> = HashCode.<jsm>create</jsm>().add(<js>"foobar"</js>).add(<jv>myobject</jv>).add(123).get();
033 * </p>
034 *
035 */
036@SuppressWarnings({
037   "java:S1700" // Field 'hashCode' intentionally shares the class name; it accumulates the computed hash value for this HashCode instance
038})
039public class HashCode {
040
041   /**
042    * Create a new HashCode object.
043    *
044    * @return A new HashCode object.
045    */
046   public static final HashCode create() {
047      return new HashCode();
048   }
049
050   /**
051    * Calculates a hash code over the specified objects.
052    *
053    * <p>
054    * Uses the same algorithm as {@link Objects#hash(Object...)} (31 * result + element hash).
055    *
056    * <p>
057    * Special handling is provided for:
058    * <ul>
059    *    <li><b>Annotations:</b> Uses {@link AnnotationUtils#hash(Annotation)} to ensure consistent hashing
060    *       according to the {@link Annotation#hashCode()} contract.
061    *    <li><b>Arrays:</b> Uses content-based hashing via {@link Arrays#hashCode(Object[])}
062    *       instead of identity-based hashing.
063    *    <li><b>Null values:</b> Treated as 0 in the hash calculation.
064    * </ul>
065    *
066    * @param objects The objects to calculate a hashcode over.
067    * @return A numerical hashcode value.
068    * @see #add(Object)
069    * @see AnnotationUtils#hash(Annotation)
070    * @see Objects#hash(Object...)
071    */
072   public static final int of(Object...objects) {
073      HashCode x = create();
074      for (var oo : objects)
075         x.add(oo);
076      return x.get();
077   }
078
079   @SuppressWarnings({
080      "java:S1845" // Field name intentionally matches Object.hashCode() method name
081   })
082   private int hashCode = 1;
083
084   /**
085    * Hashes the hashcode into this object.
086    *
087    * <p>
088    * The formula is simply <c>hashCode = 31*hashCode + i;</c>
089    *
090    * @param i The hashcode to hash into this object's hashcode.
091    * @return This object.
092    */
093   public HashCode add(int i) {
094      hashCode = 31 * hashCode + i;
095      return this;
096   }
097
098   /**
099    * Hashes the hashcode of the specified object into this object.
100    *
101    * <p>
102    * The formula is <c>hashCode = 31*hashCode + elementHash;</c>
103    *
104    * <p>
105    * Special handling is provided for:
106    * <ul>
107    *    <li><b>Null values:</b> Adds 0 to the hash code.
108    *    <li><b>Annotations:</b> Uses {@link AnnotationUtils#hash(Annotation)} to ensure consistent hashing
109    *       according to the {@link Annotation#hashCode()} contract.
110    *    <li><b>Arrays:</b> Uses content-based hashing via {@link Arrays#hashCode(Object[])}
111    *       instead of identity-based hashing. Supports all primitive array types and object arrays.
112    *    <li><b>Other objects:</b> Uses the object's {@link Object#hashCode()} method.
113    * </ul>
114    *
115    * @param o The object whose hashcode will be hashed with this object.  Can be <jk>null</jk> (adds <c>0</c> to the hash code).
116    * @return This object.
117    * @see AnnotationUtils#hash(Annotation)
118    * @see Arrays#hashCode(Object[])
119    */
120   public HashCode add(Object o) {
121      o = unswap(o);
122      if (o == null) {
123         add(0);
124      } else if (o instanceof Annotation o2) {
125         add(AnnotationUtils.hash(o2));
126      } else if (o.getClass().isArray()) {
127         // Use content-based hashcode for arrays
128         if (o instanceof Object[] o2)
129            add(deepHashCode(o2));
130         else if (o instanceof int[] o2)
131            add(Arrays.hashCode(o2));
132         else if (o instanceof long[] o2)
133            add(Arrays.hashCode(o2));
134         else if (o instanceof short[] o2)
135            add(Arrays.hashCode(o2));
136         else if (o instanceof byte[] o2)
137            add(Arrays.hashCode(o2));
138         else if (o instanceof char[] o2)
139            add(Arrays.hashCode(o2));
140         else if (o instanceof boolean[] o2)
141            add(Arrays.hashCode(o2));
142         else if (o instanceof float[] o2)
143            add(Arrays.hashCode(o2));
144         else if (o instanceof double[] o2)
145            add(Arrays.hashCode(o2));
146      } else {
147         add(o.hashCode());
148      }
149      return this;
150   }
151
152   /**
153    * Return the calculated hashcode value.
154    *
155    * @return The calculated hashcode.
156    */
157   public int get() {
158      return hashCode;
159   }
160
161   /**
162    * Converts the object to a normalized form before grabbing it's hashcode.
163    *
164    * <p>
165    * Subclasses can override this method to provide specialized handling (e.g. converting numbers to strings so that
166    * <c>123</c> and <js>"123"</js> end up creating the same hashcode.)
167    *
168    * <p>
169    * Default implementation does nothing.
170    *
171    * @param o The object to normalize before getting it's hashcode.  Can be <jk>null</jk>.
172    * @return The normalized object.
173    */
174   protected Object unswap(Object o) {
175      return o;
176   }
177}