All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.util.MultiKey Maven / Gradle / Ivy

There is a newer version: 7.4.0
Show newest version
/*
 *  Copyright 2003-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package net.sf.eBus.util;

import java.io.Serializable;
import java.util.Arrays;

/**
 * Allows multi-valued keys to be used in Java's single key
 * {@code Map} collections. If an {@code Order} object is
 * associated with a tuple
 * (String customer, Date timestamp, OrderId id), Java cannot
 * handle multi-object keys but only a single object key. The
 * solutions to this mismatch is:
 * 
    *
  1. * Use nested {@code Map} objects: * * Map<String, Map<Date, Map<OrderId, Order>>>. * * This solution is both cumbersome to implement, uses much * memory and is not recommended. *
  2. *
  3. * Write a class to store the tuple objects and provides * the appropriate hash code for the key values. This is * the only solution if {@code MultiKey}'s hash code is * insufficient. *
  4. *
* {@code MultiMap} stores multiple key values in a general * class obviating the need to write a proprietary key class. * The hash code algorithm is {@link Arrays#hashCode(Object[])}. * Note: that {@code Arrays.hashCode(Object[])} * allows {@code Object[]} to contain {@code null} * references. *

* Unlike a proprietary key class, {@code MultiKey} cannot * enforce key value number and type requirements. It is possible * to use keys of differing lengths and type within the same map. * If {@code MultiKey}'s size and type ordering enforcement is * required, use the subclasses {@link MultiKey2}, * {@link MultiKey3} or {@link MultiKey4}. *

*

* It is recommended that only immutable objects be used in the * key but if mutable objects are used and are modified * after creating the {@code MultiKey}, the * {@code MultiKey} hash code is unaffected because the hash code * is calculated once in the constructor. This results in a * discrepancy between the key's values and hash code. *

* * @since Commons Collections 3.0 * @version $Revision: 1.1 $ $Date: 2005/10/28 14:37:52 $ * * @author Howard Lewis Ship * @author Stephen Colebourne * @author Charles W. Rapp */ @SuppressWarnings({"java:S1948"}) public class MultiKey implements Comparable, Serializable { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * This is eBus version 2.1.0. */ private static final long serialVersionUID = 0x050500L; //----------------------------------------------------------- // Locals. // /** * The key objects. */ private final Object[] mKeys; /** * The overall hash code for the keys. Calculated once in the * constructor. */ private final int mHashCode; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a multiple key container for the given objects. * @param keys One or more key objects. May contain * {@code null} references. * @exception IndexOutOfBoundsException * if no keys are specified. */ public MultiKey(final Object... keys) { if (keys.length == 0) { throw (new IndexOutOfBoundsException("no keys")); } else { // Make a shallow copy of the keys array. mKeys = new Object[keys.length]; System.arraycopy(keys, 0, mKeys, 0, keys.length); // Note that Arrays.hashCode allows for null keys. mHashCode = Arrays.hashCode(mKeys); } } // end of MultiKey(Object...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Comparable Interface Implementation. // /** * Returns a negative integer, zero or a positive integer if * this object is less than, equal to or greater than the * argument, respectively. *

* The comparison is done as follows: *

    *
  1. * If {@code this} key's size does not equal * {@code key}'s size, then returns * {@code this.size() - key.size()}. *
  2. *
  3. * Otherwise iterates over the keys, comparing each * key in turn until a non-zero value is found or * all the keys are compared. *
  4. *
* @param key compare against this multi-valued key. * @return a negative integer, zero or a positive integer if * this object is less than, equal to or greater than the * argument, respectively. * @exception ClassCastException * if the keys contain incompatible or incomparable types. */ @Override @SuppressWarnings("unchecked") public int compareTo(final MultiKey key) { int retval = 0; if (this != key) { int i; retval = (mKeys.length - key.mKeys.length); for (i = 0; i < mKeys.length && retval == 0; ++i) { if (mKeys[i] == null) { if (key.mKeys[i] != null) { retval = -1; } // else retval is already zero. } else { retval = ((Comparable) mKeys[i]).compareTo( key.mKeys[i]); } } } return (retval); } // end of compareTo(MultiKey) // // end of ComparableInterface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Object Method Overrides. // /** * Returns {@code true} if {@code key} is a non-null * MultiKey instance with the same number of key values and * the key values are equal and in the same order. Returns * {@code false} otherwise. * @param o Test equality with this object. * @return {@code true} if {@code key} is a non-null * MultiKey instance with the same number of key values and * the key values are equal and in the same order; * {@code false} otherwise. */ @Override public boolean equals(final Object o) { boolean retcode = (this == o); if (!retcode && o instanceof MultiKey) { final MultiKey mkey = (MultiKey) o; if (mKeys.length == mkey.mKeys.length) { int i; for (retcode = true, i = 0; retcode && i < mKeys.length; ++i) { retcode = (mKeys[i] == null ? (mkey.mKeys[i] == null) : mKeys[i].equals(mkey.mKeys[i])); } } } return (retcode); } // end of equals(Object) /** * Returns a hash code value for this multi-valued key. *

* The hash code is calculated once in the constructor and * cached. If any key objects is modified after construction, * its new hash code will not be reflected in this * {@code MultiKey} object's hash code. *

* The hash code calculation is: *

     *   
     *     Object[] keys;
     *     int i;
     *     int hashCode;
     *
     *     for (i = 0, hashCode = 0; i < keys.length; ++i)
     *     {
     *         if (keys[i] != null)
     *         {
     *             hashCode ^= keys[i].hashCode();
     *         }
     *     }
     *   
     * 
* @return a hash code value for this multi-valued key. */ @Override public int hashCode() { return (mHashCode); } // end of hashCode() /** * Returns a textual representation of this multi-valued * key. * @return a textual representation of this multi-valued * key. */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(); int i; String sep; buffer.append('{'); for (i = 0, sep = ""; i < mKeys.length; ++i, sep = ", ") { buffer.append(sep).append(mKeys[i]); } buffer.append('}'); return (buffer.toString()); } // end of toString() // // end of Object Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get methods. // /** * Returns the number of key values. * @return the number of key values. */ public int size() { return (mKeys.length); } // end of size() /** * Returns the key value at the specified index. *

* If the returned object is mutable, modifying it will * result in unspecified behavior by the map. * @param index Index to desired key value. * @return the key value at the specified index. * @exception IndexOutOfBoundsException * if {@code index} is either < zero or > * key size. */ public Object key(final int index) { if (index < 0 || index >= mKeys.length) { throw ( new IndexOutOfBoundsException( Integer.toString(index) + " invalid index")); } return (mKeys[index]); } // end of key(int) /** * Returns a copy of the key array. *

* If any of the returned key objects is mutable, modifying a * key will result in unspecified behavior by the map. * @return the key array copy. */ public Object[] keys() { final Object[] keys = new Object[mKeys.length]; System.arraycopy(mKeys, 0, keys, 0, mKeys.length); return (keys); } // end of keys() // // end of Get methods. //----------------------------------------------------------- } // end of class MultiKey