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 */ 036public class HashCode { 037 038 /** 039 * Create a new HashCode object. 040 * 041 * @return A new HashCode object. 042 */ 043 public static final HashCode create() { 044 return new HashCode(); 045 } 046 047 /** 048 * Calculates a hash code over the specified objects. 049 * 050 * <p> 051 * Uses the same algorithm as {@link java.util.Objects#hash(Object...)} (31 * result + element hash). 052 * 053 * <p> 054 * Special handling is provided for: 055 * <ul> 056 * <li><b>Annotations:</b> Uses {@link AnnotationUtils#hash(Annotation)} to ensure consistent hashing 057 * according to the {@link java.lang.annotation.Annotation#hashCode()} contract. 058 * <li><b>Arrays:</b> Uses content-based hashing via {@link java.util.Arrays#hashCode(Object[])} 059 * instead of identity-based hashing. 060 * <li><b>Null values:</b> Treated as 0 in the hash calculation. 061 * </ul> 062 * 063 * @param objects The objects to calculate a hashcode over. 064 * @return A numerical hashcode value. 065 * @see #add(Object) 066 * @see AnnotationUtils#hash(Annotation) 067 * @see java.util.Objects#hash(Object...) 068 */ 069 public static final int of(Object...objects) { 070 HashCode x = create(); 071 for (var oo : objects) 072 x.add(oo); 073 return x.get(); 074 } 075 076 private int hashCode = 1; 077 078 /** 079 * Hashes the hashcode into this object. 080 * 081 * <p> 082 * The formula is simply <c>hashCode = 31*hashCode + i;</c> 083 * 084 * @param i The hashcode to hash into this object's hashcode. 085 * @return This object. 086 */ 087 public HashCode add(int i) { 088 hashCode = 31 * hashCode + i; 089 return this; 090 } 091 092 /** 093 * Hashes the hashcode of the specified object into this object. 094 * 095 * <p> 096 * The formula is <c>hashCode = 31*hashCode + elementHash;</c> 097 * 098 * <p> 099 * Special handling is provided for: 100 * <ul> 101 * <li><b>Null values:</b> Adds 0 to the hash code. 102 * <li><b>Annotations:</b> Uses {@link AnnotationUtils#hash(Annotation)} to ensure consistent hashing 103 * according to the {@link java.lang.annotation.Annotation#hashCode()} contract. 104 * <li><b>Arrays:</b> Uses content-based hashing via {@link java.util.Arrays#hashCode(Object[])} 105 * instead of identity-based hashing. Supports all primitive array types and object arrays. 106 * <li><b>Other objects:</b> Uses the object's {@link Object#hashCode()} method. 107 * </ul> 108 * 109 * @param o The object whose hashcode will be hashed with this object. 110 * @return This object. 111 * @see AnnotationUtils#hash(Annotation) 112 * @see java.util.Arrays#hashCode(Object[]) 113 */ 114 public HashCode add(Object o) { 115 o = unswap(o); 116 if (o == null) { 117 add(0); 118 } else if (o instanceof Annotation a) { 119 add(AnnotationUtils.hash(a)); 120 } else if (o.getClass().isArray()) { 121 // Use content-based hashcode for arrays 122 if (o instanceof Object[] o2) 123 add(deepHashCode(o2)); 124 else if (o instanceof int[] o2) 125 add(Arrays.hashCode(o2)); 126 else if (o instanceof long[] o2) 127 add(Arrays.hashCode(o2)); 128 else if (o instanceof short[] o2) 129 add(Arrays.hashCode(o2)); 130 else if (o instanceof byte[] o2) 131 add(Arrays.hashCode(o2)); 132 else if (o instanceof char[] o2) 133 add(Arrays.hashCode(o2)); 134 else if (o instanceof boolean[] o2) 135 add(Arrays.hashCode(o2)); 136 else if (o instanceof float[] o2) 137 add(java.util.Arrays.hashCode(o2)); 138 else if (o instanceof double[] o2) 139 add(java.util.Arrays.hashCode(o2)); 140 } else { 141 add(o.hashCode()); 142 } 143 return this; 144 } 145 146 /** 147 * Return the calculated hashcode value. 148 * 149 * @return The calculated hashcode. 150 */ 151 public int get() { 152 return hashCode; 153 } 154 155 /** 156 * Converts the object to a normalized form before grabbing it's hashcode. 157 * 158 * <p> 159 * Subclasses can override this method to provide specialized handling (e.g. converting numbers to strings so that 160 * <c>123</c> and <js>"123"</js> end up creating the same hashcode.) 161 * 162 * <p> 163 * Default implementation does nothing. 164 * 165 * @param o The object to normalize before getting it's hashcode. 166 * @return The normalized object. 167 */ 168 protected Object unswap(Object o) { 169 return o; 170 } 171}