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}