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.collections; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.util.*; 023 024import org.apache.juneau.commons.utils.*; 025 026/** 027 * Represents a composite key composed of multiple values, suitable for use as a key in hash-based collections. 028 * 029 * <p> 030 * This class provides an immutable, thread-safe way to create composite keys from multiple values. 031 * It's commonly used for caching scenarios where you need to uniquely identify objects based on 032 * multiple configuration parameters or attributes. 033 * 034 * <h5 class='section'>Usage Pattern:</h5> 035 * <p class='bjava'> 036 * <jc>// Create a composite key from multiple values</jc> 037 * HashKey <jv>key</jv> = HashKey.<jsm>of</jsm>( 038 * <js>"config1"</js>, 039 * <js>"config2"</js>, 040 * <jk>true</jk>, 041 * <jk>42</jk> 042 * ); 043 * 044 * <jc>// Use as a key in a cache or map</jc> 045 * Map<HashKey, MyObject> <jv>cache</jv> = <jk>new</jk> HashMap<>(); 046 * <jv>cache</jv>.<jsm>put</jsm>(<jv>key</jv>, <jv>myObject</jv>); 047 * 048 * <jc>// Retrieve using an equivalent key</jc> 049 * HashKey <jv>lookupKey</jv> = HashKey.<jsm>of</jsm>(<js>"config1"</js>, <js>"config2"</js>, <jk>true</jk>, <jk>42</jk>); 050 * MyObject <jv>cached</jv> = <jv>cache</jv>.<jsm>get</jsm>(<jv>lookupKey</jv>); <jc>// Returns myObject</jc> 051 * </p> 052 * 053 * <h5 class='section'>Important Notes:</h5> 054 * <ul class='spaced-list'> 055 * <li> 056 * <b>All relevant values must be included</b> - When using {@code HashKey} for caching, ensure all 057 * values that affect the object's identity are included. Missing values can cause different objects 058 * to incorrectly share the same cache entry. 059 * <li> 060 * <b>Order matters</b> - The order of arguments in {@link #of(Object...)} is significant. 061 * Two keys with the same values in different orders will not be equal. 062 * <li> 063 * <b>Immutable</b> - Once created, a {@code HashKey} cannot be modified. This ensures keys 064 * remain stable when used in hash-based collections. 065 * <li> 066 * <b>Thread-safe</b> - This class is thread-safe and can be safely used as keys in concurrent 067 * collections and caches. 068 * <li> 069 * <b>Null values are supported</b> - {@code null} values can be included in the key and are 070 * handled correctly in equality comparisons. 071 * </ul> 072 * 073 * <h5 class='section'>Example:</h5> 074 * <p class='bjava'> 075 * <jc>// Create keys for caching based on configuration</jc> 076 * HashKey <jv>key1</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>false</jk>); 077 * HashKey <jv>key2</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>false</jk>); 078 * HashKey <jv>key3</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>true</jk>); 079 * 080 * <jc>// key1 and key2 are equal (same values in same order)</jc> 081 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key2</jv>); <jc>// true</jc> 082 * <jv>key1</jv>.<jsm>hashCode</jsm>() == <jv>key2</jv>.<jsm>hashCode</jsm>(); <jc>// true</jc> 083 * 084 * <jc>// key1 and key3 are different (different values)</jc> 085 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key3</jv>); <jc>// false</jc> 086 * 087 * <jc>// Use in a cache</jc> 088 * Cache<HashKey, Processor> <jv>processorCache</jv> = Cache.<jsm>create</jsm>(); 089 * <jv>processorCache</jv>.<jsm>put</jsm>(<jv>key1</jv>, <jk>new</jk> JsonProcessor()); 090 * Processor <jv>p</jv> = <jv>processorCache</jv>.<jsm>get</jsm>(<jv>key2</jv>); <jc>// Returns cached instance</jc> 091 * </p> 092 */ 093@SuppressWarnings({ 094 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 095}) 096public class HashKey { 097 098 /** 099 * Creates a new hash key from the specified values. 100 * 101 * <p> 102 * The order of arguments is significant - two calls with the same values in the same order 103 * will produce equal {@code HashKey} instances, while different orders or values will produce 104 * different keys. 105 * 106 * <h5 class='section'>Example:</h5> 107 * <p class='bjava'> 108 * HashKey <jv>key1</jv> = HashKey.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <jk>true</jk>); 109 * HashKey <jv>key2</jv> = HashKey.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <jk>true</jk>); 110 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key2</jv>); <jc>// true</jc> 111 * 112 * HashKey <jv>key3</jv> = HashKey.<jsm>of</jsm>(<js>"a"</js>, <js>"b"</js>, <jk>false</jk>); 113 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key3</jv>); <jc>// false</jc> 114 * 115 * <jc>// Order matters</jc> 116 * HashKey <jv>key4</jv> = HashKey.<jsm>of</jsm>(<js>"b"</js>, <js>"a"</js>, <jk>true</jk>); 117 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key4</jv>); <jc>// false (different order)</jc> 118 * </p> 119 * 120 * @param array The values that compose this composite key. 121 * All values that affect the key's identity should be included. 122 * <br>Can be empty (produces a key representing no values). 123 * <br>Can contain {@code null} values (handled correctly in equality comparisons). 124 * @return A new immutable hash key instance. 125 */ 126 public static HashKey of(Object...array) { 127 return new HashKey(array); 128 } 129 130 private final int hashCode; 131 private final Object[] array; 132 133 HashKey(Object[] array) { 134 this.array = array; 135 this.hashCode = Arrays.deepHashCode(array); 136 } 137 138 /** 139 * Compares this hash key with another object for equality. 140 * 141 * <p> 142 * Two {@code HashKey} instances are considered equal if they contain the same values in the same order. 143 * The comparison uses deep equality checking for array elements via {@link Shorts#eq(Object, Object)}. 144 * 145 * <p> 146 * A <jk>null</jk> argument, or an object that is not a {@code HashKey} instance, returns <jk>false</jk>. 147 * 148 * @param o The object to compare with. Can be <jk>null</jk>. 149 * @return {@code true} if the objects are equal, {@code false} otherwise (including when <c>o</c> is <jk>null</jk> or not a {@code HashKey}). 150 */ 151 @Override 152 public boolean equals(Object o) { 153 if (!(o instanceof HashKey)) 154 return false; 155 var x = (HashKey)o; 156 if (array.length != x.array.length) 157 return false; 158 for (var i = 0; i < array.length; i++) 159 if (neq(array[i], x.array[i])) 160 return false; 161 return true; 162 } 163 164 /** 165 * Returns the hash code for this hash key. 166 * 167 * <p> 168 * The hash code is computed from all values in the key using {@link Arrays#deepHashCode(Object[])}. 169 * This ensures that equal keys have equal hash codes, making {@code HashKey} suitable for use 170 * as keys in hash-based collections like {@link HashMap} and {@link HashSet}. 171 * Arrays with the same contents (but different references) will produce the same hash code. 172 * 173 * @return The hash code value for this object. 174 */ 175 @Override 176 public int hashCode() { 177 return hashCode; 178 } 179 180 protected FluentMap<String,Object> properties() { 181 // @formatter:off 182 return filteredBeanPropertyMap() 183 .a("hashCode", hashCode()) 184 .a("array", array); 185 // @formatter:on 186 } 187 188 @Override /* Overridden from Object */ 189 public String toString() { 190 return r(properties()); 191 } 192}