net.sf.eBus.util.MultiKey Maven / Gradle / Ivy
/*
* 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.
*
* CHANGE LOG
* (See the bottom of this file.)
*/
package net.sf.eBus.util;
import java.io.Serializable;
import java.util.Formatter;
/**
* 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:
*
* -
* 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.
*
* -
* 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.
*
*
* {@code MultiMap} stores multiple key values in a general
* class obviating the need to write a proprietary key class.
* The hash code algorithm is:
*
*
* Object[] keys;
* int i;
* int hashCode;
*
* for (i = 0, hashCode = 0; i < keys.length; ++i)
* {
* if (keys[i] != null)
* {
* hashCode ^= keys[i].hashCode();
* }
* }
*
*
*
* 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
* discrepency 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
*/
public class MultiKey
implements Comparable,
Serializable
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* This is eBus version 2.1.0.
*/
private static final long serialVersionUID = 0x050200L;
//-----------------------------------------------------------
// 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)
throws IndexOutOfBoundsException
{
if (keys.length == 0)
{
throw (new IndexOutOfBoundsException("no keys"));
}
else
{
int i;
int hashCode;
// Make a shallow copy of the keys array.
mKeys = new Object[keys.length];
System.arraycopy(keys, 0, mKeys, 0, keys.length);
// Since _hashCode is final, we can only assign
// it once. So use a local variable to calculate
// the hash code and then put the result into
// _hashCode.
for (i = 0, hashCode = 0; i < keys.length; ++i)
{
if (keys[i] != null)
{
hashCode ^= keys[i].hashCode();
}
}
mHashCode = hashCode;
}
} // 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:
*
* -
* If {@code this} key's size does not equal
* {@code key}'s size, then returns
* {@code this.size() - key.size()}.
*
* -
* Otherwise iterates over the keys, comparing each
* key in turn until a non-zero value is found or
* all the keys are compared.
*
*
* @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)
throws ClassCastException
{
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