View Javadoc
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&lt;HashKey, MyObject&gt; <jv>cache</jv> = <jk>new</jk> HashMap&lt;&gt;();
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&lt;HashKey, Processor&gt; <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 }