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