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