1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.commons.collections;
18
19 import static org.apache.juneau.commons.utils.CollectionUtils.*;
20 import static org.apache.juneau.commons.utils.Utils.*;
21
22 import java.util.*;
23
24 /**
25 * Represents a composite key composed of multiple values, suitable for use as a key in hash-based collections.
26 *
27 * <p>
28 * This class provides an immutable, thread-safe way to create composite keys from multiple values.
29 * It's commonly used for caching scenarios where you need to uniquely identify objects based on
30 * multiple configuration parameters or attributes.
31 *
32 * <h5 class='section'>Usage Pattern:</h5>
33 * <p class='bjava'>
34 * <jc>// Create a composite key from multiple values</jc>
35 * HashKey <jv>key</jv> = HashKey.<jsm>of</jsm>(
36 * <js>"config1"</js>,
37 * <js>"config2"</js>,
38 * <jk>true</jk>,
39 * <jk>42</jk>
40 * );
41 *
42 * <jc>// Use as a key in a cache or map</jc>
43 * Map<HashKey, MyObject> <jv>cache</jv> = <jk>new</jk> HashMap<>();
44 * <jv>cache</jv>.<jsm>put</jsm>(<jv>key</jv>, <jv>myObject</jv>);
45 *
46 * <jc>// Retrieve using an equivalent key</jc>
47 * HashKey <jv>lookupKey</jv> = HashKey.<jsm>of</jsm>(<js>"config1"</js>, <js>"config2"</js>, <jk>true</jk>, <jk>42</jk>);
48 * MyObject <jv>cached</jv> = <jv>cache</jv>.<jsm>get</jsm>(<jv>lookupKey</jv>); <jc>// Returns myObject</jc>
49 * </p>
50 *
51 * <h5 class='section'>Important Notes:</h5>
52 * <ul class='spaced-list'>
53 * <li>
54 * <b>All relevant values must be included</b> - When using {@code HashKey} for caching, ensure all
55 * values that affect the object's identity are included. Missing values can cause different objects
56 * to incorrectly share the same cache entry.
57 * <li>
58 * <b>Order matters</b> - The order of arguments in {@link #of(Object...)} is significant.
59 * Two keys with the same values in different orders will not be equal.
60 * <li>
61 * <b>Immutable</b> - Once created, a {@code HashKey} cannot be modified. This ensures keys
62 * remain stable when used in hash-based collections.
63 * <li>
64 * <b>Thread-safe</b> - This class is thread-safe and can be safely used as keys in concurrent
65 * collections and caches.
66 * <li>
67 * <b>Null values are supported</b> - {@code null} values can be included in the key and are
68 * handled correctly in equality comparisons.
69 * </ul>
70 *
71 * <h5 class='section'>Example:</h5>
72 * <p class='bjava'>
73 * <jc>// Create keys for caching based on configuration</jc>
74 * HashKey <jv>key1</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>false</jk>);
75 * HashKey <jv>key2</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>false</jk>);
76 * HashKey <jv>key3</jv> = HashKey.<jsm>of</jsm>(<js>"json"</js>, <jk>true</jk>, <jk>true</jk>);
77 *
78 * <jc>// key1 and key2 are equal (same values in same order)</jc>
79 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key2</jv>); <jc>// true</jc>
80 * <jv>key1</jv>.<jsm>hashCode</jsm>() == <jv>key2</jv>.<jsm>hashCode</jsm>(); <jc>// true</jc>
81 *
82 * <jc>// key1 and key3 are different (different values)</jc>
83 * <jv>key1</jv>.<jsm>equals</jsm>(<jv>key3</jv>); <jc>// false</jc>
84 *
85 * <jc>// Use in a cache</jc>
86 * Cache<HashKey, Processor> <jv>processorCache</jv> = Cache.<jsm>create</jsm>();
87 * <jv>processorCache</jv>.<jsm>put</jsm>(<jv>key1</jv>, <jk>new</jk> JsonProcessor());
88 * Processor <jv>p</jv> = <jv>processorCache</jv>.<jsm>get</jsm>(<jv>key2</jv>); <jc>// Returns cached instance</jc>
89 * </p>
90 */
91 public class HashKey {
92
93 /**
94 * Creates a new hash key from the specified values.
95 *
96 * <p>
97 * The order of arguments is significant - two calls with the same values in the same order
98 * will produce equal {@code HashKey} instances, while different orders or values will produce
99 * 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 }