> 'Record' confused me and made me think of databases. Is
> KeyValueHistory a better name?

Stephen, you're probably right. I had been bouncing back and forth
between 'Record' and 'History'... the data structure does *record*  a
kv-pair's *history*, but in fact only the most recent part (one state
transition 'record') of the history.

However, one would expect that a "KeyValueHistory" class should be able
to store the entire history of a key (as well as just a single value).
I hadn't initially needed this capability for my own purposes, but I
now see the usefulness. So,
 
 #getPreviousValue : Object

should probably be replaced with:

 #getAllValues : List

What do we think? In terms of design, either the constructors would get
very messy to use, or we need to add factory methods to distinguish
between:

KeyValueHistory( Object key, Object value)
KeyValueHistory( Object key, List values)

Attached is an implementation that supports multiple previous values,
and provides static factory methods. Thoughts?

- 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.ArrayList;
import java.util.Collections;
import java.util.List;
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 KeyValueHistory
{

        /**
         * Create a new <code>KeyValueHistory</code> with supplied key and value,
         * and no previous values.
         */
        public static KeyValueHistory createFromKeyValue(final Object key, final 
Object value)
        {
                return new KeyValueHistory(key, Collections.singletonList(value));
        }
        
        
        /**
         * Create a new <code>KeyValueHistory</code> with the specified key and values.
         * The supplied list must:
         * <ul>
         * <li>contain at least one value</li>
         * <li>contain the values in reverse-chronological order (i.e. most
         * recent value first)</li>
         * <li>be immutable</li>
         * </ul> 
         * 
         * @param values an immutable list of the values associated with this key.
         * @throws IllegalArgumentException if <code>values</code> is 
<code>null</code> or
         * is empty. <br />Note: If the value associated with the key is the value 
<code>null</code>,
         * then supply <code>Collections#singletonList( null )</code>.
         */
        public static KeyValueHistory createFromKeyValueList(final Object key, final 
List values)
        {
                if (values == null || values.size() < 1)
                {
                        throw new IllegalArgumentException("The supplied 'values' list 
must contain at least one value.");
                }

                return new KeyValueHistory(key, values);
        }
        
        /**
         * Create a new <code>KeyValueHistory</code> with supplied key, value
         * and previous value.
         */
        public static KeyValueHistory createFromKeyValuePrevious(final Object key, 
final Object value, final Object previous)
        {
                final List values = new ArrayList(2);
                values.add(value);
                values.add(previous);
                
                return new KeyValueHistory(key, Collections.unmodifiableList(values));
        }
        /**
         * Create a new <code>KeyValueHistory</code> with key and value from the 
supplied
         * <code>Map.Entry</code> and no previous value.
         */
        public static KeyValueHistory createFromMapEntry(final Map.Entry entry)
        {
                return new KeyValueHistory(entry.getKey(), 
Collections.singletonList(entry.getValue()));
        }

        private int hash = -1; // lazily calculated

        private final Object key;
        private final List values;

        


        /**
         * Create a new <code>KeyValueHistory</code> with the specified key and values.
         * The supplied list:
         * <ul>
         * <li>must contain at least one value</li>
         * <li>must contain the values in reverse-chronological order (i.e. most
         * recent value first)</li>
         * <li>must be immutable</li> 
         * 
         * @param values an immutable List of the values associated with this key.
         */
        protected KeyValueHistory(final Object key, final List values)
        {
                this.key = key;
                this.values = values;
        }

        /**
         * 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 (
                                        KeyValueHistory.this.getKey() == null
                                                ? e.getKey() == null
                                                : 
KeyValueHistory.this.getKey().equals(e.getKey()))
                                        && (KeyValueHistory.this.getValue() == null
                                                ? e.getValue() == null
                                                : 
KeyValueHistory.this.getValue().equals(e.getValue()));
                        }

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

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

                        public int hashCode()
                        {
                                if (this.hash == -1)
                                {
                                        this.hash =
                                                (KeyValueHistory.this.getKey() == null 
? 0 : KeyValueHistory.this.getKey().hashCode())
                                                        ^ 
(KeyValueHistory.this.getValue() == null ? 0 : 
KeyValueHistory.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(KeyValueHistory.this.getKey())
                                        .append('=')
                                        .append(KeyValueHistory.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 KeyValueHistory))
                {
                        return false;
                }

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

                final KeyValueHistory kvr = (KeyValueHistory) o;

                return (this.key == null ? kvr.key == null : this.key.equals(kvr.key))
                        && this.values.equals(kvr.values);
        }

        /**
         * Returns the first value previously associated with this key, if any. Note 
that
         * <code>null</code> will be returned 
         * if the previous value is <code>null</code> <i>or</i> if there are no 
previous values
         * Therefore [EMAIL PROTECTED] #hasPreviousValues()} 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.values.size() < 2) ? null : this.values.get(1);
        }*/
        
        /*
        public List getAllPreviousValues()
        {
                return this.values.subList(1, values.size() -1);
        }
        */
        
        /**
         * Return all values associated with this key, in reverse chronological
         * order. The most recent (current) value is at index zero, the previous value 
is
         * at index one, the value previous to that at index two, etc.. If there are no
         * previous values associated with this key, then list will contain only one 
element (the current value).
         * The returned list is immutable.
         * 
         * @return an immutable list of the values associated with this key, the list 
having a minimum size of one.
         */
        public List getAllValues()
        {
                return this.values;
        }

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

        /**
         * Returns the current (or most recent) value associated with this record's 
key.
         * 
         * @return the value (which may be <code>null</code>)
         * associated with this record's key
         */
        public Object getValue()
        {
                return this.values.get(0);
        }

        public int hashCode()
        {
                if (this.hash == -1)
                {
                        this.hash =
                                (this.key == null ? 0 : this.key.hashCode())
                                        ^ this.values.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 hasPreviousValues()
        {
                return this.values.size() > 1;
        }

        /**
         * Returns a string representation of this <code>KeyValueHistory</code>.
         * 
         */
        public String toString()
        {
                final StringBuffer sb = new 
StringBuffer().append(this.key).append('=').append(this.values);

                return sb.toString();
        }

}
/*
 * $Header: 
x:/apps/cvsnt/cvs_repository/main/notifyingcollections/src/test/org/apache/commons/collections/TestKeyValueHistory.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;

import junit.framework.TestCase;


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

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

        }

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

        public void testKeyValueHistory()
        {

                // KVR with no previous value
                KeyValueHistory kvr = KeyValueHistory.createFromKeyValue(key, value);

                assertTrue(
                        kvr.getKey() == key
                                && kvr.getValue() == value
                                && kvr.hasPreviousValues() == false);

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

                // KVR with a previous value
                KeyValueHistory kvr2 = KeyValueHistory.createFromKeyValuePrevious(key, 
value, previous);

                assertTrue(
                        kvr2.getKey() == key
                                && kvr2.getValue() == value
                                && kvr2.hasPreviousValues() == true
                                && kvr2.getAllValues().get(1) == previous);

                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
                KeyValueHistory kvr3 = KeyValueHistory.createFromKeyValuePrevious(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();

                KeyValueHistory kvr4 = KeyValueHistory.createFromMapEntry(entry);

                assertTrue(
                        kvr4.getKey() == key
                                && kvr4.getValue() == value
                                && kvr4.hasPreviousValues() == false);

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

                assertFalse("Map.Entry and KeyValueHistory 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