Attached is source (& associated tests) for KeyValueRecord.java.
KeyValueRecord is an immutable {key, value, previous-value} triplet, a
simple but very useful data structure. It's particularly of use with
Map (and Map.Entry) for storing, tracking, and comparing entry state,
but can obviously be used for any key-value data. The constructors
include one to create a KeyValueRecord from a Map.Entry. There is also
a method to view the KeyValueRecord as a Map.Entry (#asMapEntry). See
the javadoc for more details.

** I had a swift look through [collections] and [lang], and didn't see
a simple KeyValuePair implementation... Have I missed this somewhere?
If not, I have an implementation which I will post.

Thanks,

Neil
/*
 * $Header: 
x:/apps/cvsnt/cvs_repository/main/notifyingcollections/src/java/org/apache/commons/collections/KeyValueRecord.java,v
 1.2 2003/09/20 22:00:32 otoolen Exp $
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.commons.collections;

import java.util.Map;

/**
 * An immutable {key, value, previous-value} triplet. This class is frequently used
 * in conjunction with <code>Map.Entry</code>. A constructor is provided to create
 * a <code>KeyValueRecord</code> from a <code>Map.Entry</code>, and the [EMAIL 
PROTECTED] #asMapEntry}
 * method can be used to view an object of this class as a <code>Map.Entry</code>.
 * Note that it is not possible for <code>KeyValueRecord</code> to implement the 
<code>Map.Entry</code>
 * interface as the <code>#equals</code> implementations are not compatible.
 * 
 *  
 * 
 * @author Neil O'Toole
 */

public class KeyValueRecord
{
        private static final Object NO_PREVIOUS_VALUE = new Object();

        private int hash = -1; // lazily calculated

        private final Object key;
        private final Object previous;
        private final Object value;

        /**
         * Create a new <code>KeyValueRecord</code> with key and value from the 
supplied
         * <code>Map.Entry</code> and no previous value.
         */
        public KeyValueRecord(final Map.Entry entry)
        {
                this(entry.getKey(), entry.getValue(), NO_PREVIOUS_VALUE);
        }

        /**
         * Create a new <code>KeyValueRecord</code> with the specified key and value 
and
         * no previous value.
         */
        public KeyValueRecord(final Object key, final Object value)
        {
                this(key, value, NO_PREVIOUS_VALUE);
        }

        /**
         * Create a new <code>KeyValueRecord</code> with the specified key, value, and 
previous value.
         */
        public KeyValueRecord(final Object key, final Object value, final Object 
previousValue)
        {
                this.key = key;
                this.value = value;
                this.previous = previousValue;
        }

        /**
         * Returns a [EMAIL PROTECTED] Map.Entry} view of the supplied 
<code>KeyValueRecord</code>. The
         * returned entry is unmodifiable (<code>#setValue</code> throws an [EMAIL 
PROTECTED] UnsupportedOperationException}),
         * as the backing <code>KeyValueRecord</code> is itself immutable.
         * The returned entry correctly implements the <code>#hashCode</code> and 
         * <code>#equals</code> operations as per the <code>Map.Entry</code> contract.
         */

        public Map.Entry asMapEntry()
        {
                return new Map.Entry()
                {
                        private int hash = -1;

                        public boolean equals(Object o)
                        {
                                if (o instanceof Map.Entry == false)
                                {
                                        return false;
                                }

                                if (o == this)
                                {
                                        return true;
                                }

                                Map.Entry e = (Map.Entry) o;

                                return (
                                        KeyValueRecord.this.getKey() == null
                                                ? e.getKey() == null
                                                : 
KeyValueRecord.this.getKey().equals(e.getKey()))
                                        && (KeyValueRecord.this.getValue() == null
                                                ? e.getValue() == null
                                                : 
KeyValueRecord.this.getValue().equals(e.getValue()));
                        }

                        public Object getKey()
                        {
                                return KeyValueRecord.this.getKey();
                        }

                        public Object getValue()
                        {
                                return KeyValueRecord.this.getValue();
                        }

                        public int hashCode()
                        {
                                if (this.hash == -1)
                                {
                                        this.hash =
                                                (KeyValueRecord.this.getKey() == null 
? 0 : KeyValueRecord.this.getKey().hashCode())
                                                        ^ 
(KeyValueRecord.this.getValue() == null ? 0 : 
KeyValueRecord.this.getValue().hashCode());
                                }

                                return this.hash;
                        }

                        public Object setValue(Object value)
                        {
                                throw new UnsupportedOperationException("This 
Map.Entry is unmodifiable.");
                        }

                        public String toString()
                        {
                                return new StringBuffer()
                                        .append(KeyValueRecord.this.getKey())
                                        .append('=')
                                        .append(KeyValueRecord.this.getValue())
                                        .toString();
                        }
                };

        }

        /**
         * Compares the specified object with this <code>KeyValueRecord</code> for 
equality. Returns
         * true if the given object is also a <code>KeyValueRecord</code> and
         * the records' key, value, and previous value are equal.
         *
         * @param o object to be compared for equality with this 
<code>KeyValueRecord</code>.
         * @return <code>true</code> if the specified object is equal to this
         *         record.
        */
        public boolean equals(final Object o)
        {
                if (!(o instanceof KeyValueRecord))
                {
                        return false;
                }

                if (this == o)
                {
                        return true;
                }

                final KeyValueRecord kvr = (KeyValueRecord) o;

                return (this.key == null ? kvr.key == null : this.key.equals(kvr.key))
                        && (this.value == null ? kvr.value == null : 
this.value.equals(kvr.value))
                        && (this.previous == NO_PREVIOUS_VALUE
                                ? (kvr.previous == NO_PREVIOUS_VALUE)
                                : (this.previous == null ? kvr.previous == null : 
this.previous.equals(kvr.previous)));
        }

        /**
         * Returns the key associated with this record.
         */
        public Object getKey()
        {
                return this.key;
        }

        /**
         * Returns the value previously associated with this record's key, if any. 
Note that
         * <code>null</code> will be returned 
         * if the previous value is <code>null</code> <i>or</i> if there is no 
previous value.
         * Therefore [EMAIL PROTECTED] #hasPreviousValue()} should be used to test if 
there is a previous value.
         * 
         * @return the previous value (which may be <code>null</code>)
         * associated with this record's key, or <code>null</code> if there is no
         * previous value.
         */
        public Object getPreviousValue()
        {
                return (this.previous == NO_PREVIOUS_VALUE) ? null : this.previous;
        }

        /**
         * Returns the value associated with this record's key.
         * 
         * @return the object (which may be <code>null</code>)
         * associated with this record's key
         */
        public Object getValue()
        {
                return this.value;
        }

        public int hashCode()
        {
                if (this.hash == -1)
                {
                        this.hash =
                                (this.key == null ? 0 : this.key.hashCode())
                                        ^ (this.value == null ? 0 : 
this.value.hashCode())
                                        ^ (this.previous == null ? 0 : 
this.previous.hashCode());
                }

                return this.hash;
        }

        /**
         * Returns true if this record's key
         * was associated with a value previous to being
         * associated with its current value.
         * 
         * @return <code>true</code> if this key previously had a value associated
         * with it, <code>false</code> otherwise.
         * @see #getPreviousValue()
         */
        public boolean hasPreviousValue()
        {
                return this.previous != NO_PREVIOUS_VALUE;
        }

        /**
         * Returns a string representation of this <code>KeyValueRecord</code>. The 
text
         * rendering is <code>key=value</code> or <code>key=value (previous)</code>
         * depending on whether this <code>KeyValueRecord</code>
         * has a previous value.
         * 
         */
        public String toString()
        {
                final StringBuffer sb = new 
StringBuffer().append(this.key).append('=').append(this.value);

                if (this.hasPreviousValue())
                {
                        sb.append(' 
').append('(').append(this.getPreviousValue()).append(')');
                }

                return sb.toString();
        }

}
/*
 * $Header: 
x:/apps/cvsnt/cvs_repository/main/notifyingcollections/src/test/org/apache/commons/collections/TestKeyValueRecord.java,v
 1.1 2003/09/20 22:00:46 otoolen Exp $
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.commons.collections;

import java.util.HashMap;
import java.util.Map;
/*
 * $Header: 
x:/apps/cvsnt/cvs_repository/main/notifyingcollections/src/test/org/apache/commons/collections/TestKeyValueRecord.java,v
 1.1 2003/09/20 22:00:46 otoolen Exp $
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
import junit.framework.TestCase;

import org.apache.commons.collections.KeyValueRecord;

/**
 * 
 * @author Neil O'Toole
 */
public class TestKeyValueRecord extends TestCase
{
        private final String key = "key";
        private final String value = "value";
        private final String previous = "previous";

        public TestKeyValueRecord(String testName)
        {
                super(testName);

        }

        public static void main(String[] args)
        {
                junit.textui.TestRunner.run(TestKeyValueRecord.class);
        }

        public void testKeyValueRecord()
        {

                // KVR with no previous value
                KeyValueRecord kvr = new KeyValueRecord(key, value);

                assertTrue(
                        kvr.getKey() == key
                                && kvr.getValue() == value
                                && kvr.getPreviousValue() == null
                                && kvr.hasPreviousValue() == false);

                assertTrue(kvr.equals(kvr));
                assertTrue(kvr.toString().equals("key=value"));

                // KVR with a previous value
                KeyValueRecord kvr2 = new KeyValueRecord(key, value, previous);

                assertTrue(
                        kvr2.getKey() == key
                                && kvr2.getValue() == value
                                && kvr2.getPreviousValue() == previous
                                && kvr2.hasPreviousValue() == true);

                assertTrue(kvr2.equals(kvr2));
                assertTrue(kvr2.toString().equals("key=value (previous)"));

                assertFalse(kvr.equals(kvr2));
                assertFalse(kvr.hashCode() == kvr2.hashCode());

                // test that a previous value of 'null' is treated differently to no 
previous value
                KeyValueRecord kvr3 = new KeyValueRecord(key, value, null);
                assertTrue(kvr3.equals(kvr3));
                assertTrue(kvr3.toString().equals("key=value (null)"));
                assertFalse(kvr3.equals(kvr));
                assertFalse(kvr3.equals(kvr2));

                assertFalse(kvr3.hashCode() == kvr2.hashCode());
                assertFalse(kvr3.hashCode() == kvr.hashCode());

                // test the Map.Entry handling
                Map map = new HashMap();
                map.put(key, value);

                Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();

                KeyValueRecord kvr4 = new KeyValueRecord(entry);

                assertTrue(
                        kvr4.getKey() == key
                                && kvr4.getValue() == value
                                && kvr4.getPreviousValue() == null
                                && kvr4.hasPreviousValue() == false);

                assertTrue(kvr4.equals(kvr4));
                assertTrue(kvr4.equals(kvr));
                assertTrue(kvr4.toString().equals("key=value"));

                assertFalse("Map.Entry and KeyValueRecord are never equal", 
kvr4.equals(entry));

                Map.Entry entry2 = kvr4.asMapEntry();

                assertTrue(entry.equals(entry2));
                assertTrue(entry.hashCode() == entry2.hashCode());

        }

}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to