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