Home | FAQ | Contact me

Tuple List

One day, I felt an urge to create this data structure, which is missing from Java.

Tuple.java:

This is the fundamental, cellular unit on which the tuple list is based.

01.package com.etretatlogiciels.collections;
02. 
03.import java.io.Serializable;
04. 
05.public class Tuple< K, V > implements Serializable
06.{
07.    K key;
08.    V value;
09. 
10.    public Tuple( K key, V value )
11.    {
12.        this.key   = key;
13.        this.value = value;
14.    }
15. 
16.    public Tuple( Tuple< K, V > tuple )
17.    {
18.        this.key   = tuple.getKey();
19.        this.value = tuple.getValue();
20.    }
21. 
22.    public K getKey()   { return this.key; }
23.    public V getValue() { return this.value; }
24. 
25.    /**
26.     * The importance of this method is to ensure that tuples, which are key-value
27.     * pairs, are examined for equality not by their object reference, but by their
28.     * content. Of course, for optimization sake, if the object references match,
29.     * then there's no need to compare the strings character by character.
30.     */
31.    public boolean equals( Object o )
32.    {
33.        if( !( o instanceof Tuple ) )
34.            return false;
35. 
36.        @SuppressWarnings( "unchecked" )
37.        Tuple< K, V > tuple = ( Tuple< K, V > ) o;
38.        K             key   = tuple.getKey();
39.        V             value = tuple.getValue();
40. 
41.        return ( ( key == this.key && value == this.value )
42.                || key.equals( this.key ) && value.equals( this.value ) );
43.    }
44.}
TupleList.java:

This is the implementation.

001.package com.etretatlogiciels.collections;
002. 
003.import java.io.Serializable;
004.import java.util.ArrayList;
005.import java.util.Collection;
006.import java.util.Iterator;
007. 
008.import javax.xml.bind.annotation.adapters.XmlAdapter;
009.import javax.xml.parsers.DocumentBuilder;
010.import javax.xml.parsers.DocumentBuilderFactory;
011. 
012.import org.w3c.dom.Document;
013.import org.w3c.dom.Element;
014.import org.w3c.dom.Node;
015.import org.w3c.dom.NodeList;
016. 
017./**
018. * This class sits atop Collection and uses ArrayList to create a collection of Tuples.
019. * The motivation behind this is that we were using a Map to implement this, but it's
020. * totally legitimate to have tuples with the same key, in fact, we were doing that
021. * ourselves. That Map couldn't support this didn't really scream at us until testing
022. * demonstrated it, a "Doh!" sort of moment.
023. *
024. * Therefore, this class is nothing more than a list of Tuples. There
025. * may be duplicates, even duplicate identical by both their key and their value.
026. *
027. * @author Thomas Greger and Russell Bateman
028. * @since August 2012
029. */
030.public class TupleList extends XmlAdapter< Element, TupleList >
031.                         implements Collection< Tuple< String, String > >, Serializable
032.{
033.    private ArrayList< Tuple< String, String > > list = new ArrayList< Tuple< String, String > >();
034. 
035.    public TupleList() { }
036. 
037.    /**
038.     * This constructor names the field when serializing. Typically, it's called by a subclass and
039.     * the whole TupleList class is never used directly.
040.     * @param tagname
041.     */
042.    public TupleList( String tagname )
043.    {
044.        this.tagname = tagname;
045.    }
046. 
047.    /**
048.     * This is a special constructor created as shorthand for the JaxB serializer to use.
049.     * @param list array of tuples.
050.     */
051.    public TupleList( ArrayList< Tuple< String, String > > list )
052.    {
053.        addAll( list );
054.    }
055. 
056.    @Override
057.    public int size() { return this.list.size(); }
058. 
059.    @Override
060.    public boolean isEmpty() { return this.list.size() == 0; }
061. 
062.    @Override
063.    public boolean contains( Object o )
064.    {
065.        return list.contains( o );
066.    }
067. 
068.    /**
069.     * Determine whether the [ key, value ] pair are present in the list.
070.     *
071.     * @param key the key string.
072.     * @param value the value string.
073.     * @return true/false that the tuple is present.
074.     */
075.    public boolean contains( String key, String value )
076.    {
077.        for( Tuple< String, String > v : list )
078.        {
079.            if(  v.getKey().equals( key ) && v.getValue().equals( value ) )
080.              return true;
081.        }
082. 
083.        return false;
084.    }
085. 
086.    /**
087.     * Determine whether a tuple with the specified key is present in the list.
088.     *
089.     * @param key the key string.
090.     * @return true/false that the key is present.
091.     */
092.    public boolean contains( String key )
093.    {
094.        for( Tuple< String, String > v : list )
095.        {
096.            if(  v.getKey().equals( key ) )
097.              return true;
098.        }
099. 
100.        return false;
101.    }
102. 
103.    /**
104.     * Determine whether the [ key, value ] pair in the passed tuple are present
105.     * in the list.
106.     *
107.     * @param tuple containing the key and value to match.
108.     * @return true/false that the tuple is present.
109.     */
110.    public boolean contains( Tuple< String, String > tuple )
111.    {
112.        String key   = tuple.getKey();
113.        String value = tuple.getValue();
114. 
115.        return contains( key, value );
116.    }
117. 
118.    @Override
119.    public Iterator< Tuple< String, String > > iterator()
120.    {
121.        return list.iterator();
122.    }
123. 
124.    @Override
125.    public Object[] toArray()
126.    {
127.        return list.toArray();
128.    }
129. 
130.    @Override
131.    public < T > T[] toArray( T[] a )
132.    {
133.        return list.toArray( a );
134.    }
135. 
136.    /**
137.     * This is to overcome the difficulty I have working with the two preceding methods.
138.     * @return a copy of our array of tuples.
139.     */
140.    public ArrayList< Tuple< String, String > > toTupleArray()
141.    {
142.        ArrayList< Tuple< String, String > > array = new ArrayList< Tuple< String, String > >( this.size() );
143. 
144.        for( Tuple< String, String > tuple : list )
145.            array.add( new Tuple< String, String >( tuple ) );
146. 
147.        return array;
148.    }
149. 
150.    /**
151.     * Add a new tuple to the list.
152.     * @param [ key, value ] pair to add.
153.     * @return true/false that it was added.
154.     */
155.    @Override
156.    public boolean add( Tuple< String, String > e )
157.    {
158.        return list.add( e );
159.    }
160. 
161.    /**
162.     * Add a new tuple [ key, value ] pair to the list.
163.     * @param key the key string.
164.     * @param value the value string.
165.     * @return true/false that it was added.
166.     */
167.    public boolean add( String key, String value )
168.    {
169.        Tuple< String, String > tuple = new Tuple< String, String >( key, value );
170. 
171.        return add( tuple );
172.    }
173. 
174.    /**
175.     * Remove an existing tuple [ key, value ] pair to the list.
176.     * @param key the key string.
177.     * @param value the value string.
178.     * @return true/false that it was added.
179.     */
180.    @Override
181.    public boolean remove( Object o )
182.    {
183.        if( !( o instanceof Tuple ) )
184.            return false;
185. 
186.        @SuppressWarnings( "unchecked" )
187.        Tuple< String, String > t = ( Tuple< String, String > ) o;
188. 
189.        return list.remove( t );
190.    }
191. 
192.    /**
193.     * Remove an existing tuple [ key, value ] pair to the list.
194.     * @param key the key string.
195.     * @param value the value string.
196.     * @return true/false that it was added.
197.     */
198.    public boolean remove( String key, String value )
199.    {
200.        Tuple< String, String > tuple = new Tuple< String, String >( key, value );
201. 
202.        return list.remove( tuple );
203.    }
204. 
205.    @Override
206.    public boolean containsAll( Collection< ? > c )
207.    {
208.        return list.containsAll( c );
209.    }
210. 
211.    @Override
212.    public boolean addAll( Collection< ? extends Tuple< String, String > > c )
213.    {
214.        return list.addAll( c );
215.    }
216. 
217.    @Override
218.    public boolean removeAll( Collection< ? > c )
219.    {
220.        return list.removeAll( c );
221.    }
222. 
223.    @Override
224.    public boolean retainAll( Collection< ? > c )
225.    {
226.        return list.retainAll( c );
227.    }
228. 
229.    @Override
230.    public void clear()
231.    {
232.        list.clear();
233.    }
234. 
235.    public boolean equals( TupleList tuplelist )
236.    {
237.        if( tuplelist == this )
238.            return true;
239. 
240.        if( tuplelist.size() != this.size() )
241.            return false;
242. 
243.        // find every tuple in this object in the tuplelist passed or else!
244.        for( Tuple< String, String > tuple : list )
245.        {
246.            boolean found = false;
247. 
248.            ArrayList< Tuple< String, String > > thatlist = tuplelist.toTupleArray();
249. 
250.            for( Tuple< String, String > that : thatlist )
251.            {
252.                if( that.equals( tuple ) )
253.                    found = true;
254.            }
255. 
256.            if( !found )
257.                return false;
258.        }
259. 
260.        return true;
261.    }
262. 
263.    public String toString()
264.    {
265.        if( this.size() < 1 )
266.            return "";
267. 
268.        StringBuffer buffer = new StringBuffer( this.size() * 7 * 3 );
269. 
270.        buffer.append( "{<n" );
271. 
272.        int count = this.size();
273. 
274.        for( Tuple< String, String > tuple : list )
275.        {
276.            buffer.append( "  \"" + tuple.getKey() + "\"" );
277.            buffer.append( ":\""  + tuple.getValue() + "\"" );
278. 
279.            if( count -- > 1 )
280.                buffer.append( "," );
281. 
282.            buffer.append( "\n" );
283.        }
284. 
285.        buffer.append( "}" );
286. 
287.        return buffer.toString();
288.    }
289. 
290.    /**
291.     * Returns all tuples that with the indicated key. The point to this class in
292.     * the first place is that there can be any tuples including tuples with the same
293.     * key or even the same key and value.
294.     * @param key to search for.
295.     * @return list of matching tuples.
296.     */
297.    public ArrayList< Tuple< String, String > > getAll( String key )
298.    {
299.        ArrayList< Tuple< String, String > > list = new ArrayList< Tuple< String, String > >();
300. 
301.        for( Tuple< String, String > tuple : this.list )
302.        {
303.            if( tuple.key.equals( key ) )
304.                list.add( tuple );
305.        }
306. 
307.        return list;
308.    }
309. 
310.    /**
311.     * This returns the first tuple found with the specified key. There may be others,
312.     * but there is no order maintained so which value comes back cannot be predicted.
313.     * @param key to search for.
314.     * @return value associated with the key found.
315.     */
316.    public String getFirst( String key )
317.    {
318.        ArrayList< Tuple< String, String > > list = getAll( key );
319. 
320.        if( list == null || list.size() < 1 )
321.            return null;
322. 
323.        return list.get(  0 ).getValue();
324.    }
325. 
326.    /* ========== J A X B   s e r i a l i z a t i o n   c o d e ========================================
327.     * This enables us to make use of TupleList through Jersey. Originally, I got this code
328.     * for using Map/HashMap from Blaise Doughan, team lead for EclipseLink; see
330.     */
331.    private static boolean  initDocumentBuilder = false;
332.    private DocumentBuilder builder = null;
333.    private String          tagname = "tuplelist";   // generic name; subclass this
334. 
335.    public final String getTagname() { return this.tagname; }
336. 
337.    private void initDocumentBuilder()
338.    {
339.        try
340.        {
341.            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
342.            initDocumentBuilder = true;
343.        }
344.        catch( Exception e )
345.        {
346.            e.printStackTrace();
347.        }
348.    }
349. 
350.    @Override
351.    public Element marshal( TupleList tuplelist ) throws Exception
352.    {
353.        if( !initDocumentBuilder )
354.            initDocumentBuilder();
355. 
356.        Document document    = builder.newDocument();
357.        Element  rootElement = document.createElement( tagname );
358. 
359.        document.appendChild( rootElement );
360. 
361.        ArrayList< Tuple< String, String > > list = tuplelist.toTupleArray();
362. 
363.        for( Tuple< String, String > tuple : list )
364.        {
365.            Element childElement = document.createElement( tuple.getKey() );
366. 
367.            childElement.setTextContent( tuple.getValue() );
368.            rootElement.appendChild( childElement );
369.        }
370. 
371.        return rootElement;
372.    }
373. 
374.    @Override
375.    public TupleList unmarshal( Element rootElement ) throws Exception
376.    {
377.        NodeList                             nodeList = rootElement.getChildNodes();
378.        ArrayList< Tuple< String, String > > list
379.                        = new ArrayList< Tuple< String, String > >( nodeList.getLength() );
380. 
381.        /* Pre-allocate the array we'll use in the TupleList; fill it using the child nodes
382.         * of the in-coming Element.
383.         */
384.        for( int x = 0; x < nodeList.getLength(); x++ )
385.        {
386.            Node node = nodeList.item( x );
387. 
388.            if( node.getNodeType() == Node.ELEMENT_NODE )
389.                list.add( new Tuple< String, String >( node.getNodeName(), node.getTextContent() ) );
390.        }
391. 
392.        this.list = list;
393.        return this;
394.    }
395.}
TupleListTest.java:

...and a test for it.

001.package com.etretatlogiciels.collections;
002. 
003.import static org.junit.Assert.assertEquals;
004.import static org.junit.Assert.assertFalse;
005.import static org.junit.Assert.assertTrue;
006.import static org.junit.Assert.assertNotNull;
007.import java.util.ArrayList;
008.import java.util.Iterator;
009. 
010.import org.junit.Test;
011. 
012.public class TupleListTest
013.{
014.    private static final String SMACK_NAME = "Smackaroonie";
015.    private static final String SHIZL_NAME = "Schizzle";
016.    private static final String SHICK_NAME = "Schnikies";
017. 
018.    private static TupleList SMACK = new TupleList( "smackaroonie" );
019.    private static TupleList SHICK = new TupleList( "smackaroonie" );
020. 
021.    static
022.    {
023.        SMACK.add( "companyname", SMACK_NAME );
024.        SMACK.add( SHIZL_NAME,    "write" );
025.        SMACK.add( SHIZL_NAME,    "password" );
026.        SMACK.add( SHICK_NAME,    "read" );
027.        SMACK.add( SHICK_NAME,    "password" );
028. 
029.        SHICK.add( SHICK_NAME,    "Schnikies" );
030.        SHICK.add( SMACK_NAME,    "read" );
031.        SHICK.add( SMACK_NAME,    "password" );
032.    }
033. 
034.    @Test
035.    public void testTupleList_arrayconstructor()
036.    {
037.        ArrayList< Tuple< String, String > > list = new ArrayList< Tuple< String, String > >();
038. 
039.        list.add( new Tuple< String, String >( "companyname", SMACK_NAME ) );
040.        list.add( new Tuple< String, String >( SHIZL_NAME,    "write" ) );
041.        list.add( new Tuple< String, String >( SHIZL_NAME,    "password" ) );
042.        list.add( new Tuple< String, String >( SHICK_NAME,    "read" ) );
043.        list.add( new Tuple< String, String >( SHICK_NAME,    "password" ) );
044. 
045.        TupleList s = new TupleList( list );
046. 
047.        assertTrue( SMACK.containsAll( s.toTupleArray() ) );
048.    }
049. 
050.    @Test
051.    public void testTupleList_tagnameconstructor()
052.    {
053.        TupleList s = new TupleList( "smackaroonie" );
054. 
055.        assertTrue( s.getTagname().equals( "smackaroonie" ) );
056.    }
057. 
058.    @Test
059.    public void testSize()
060.    {
061.        assertEquals( SMACK.size(), 5 );
062.    }
063. 
064.    @Test
065.    public void testIsEmpty()
066.    {
067.        TupleList s = new TupleList();
068. 
069.        assertTrue( "This one is not empty", SMACK.size() != 0 );
070.        assertFalse( "This one is empty", s.size() != 0 );
071.    }
072. 
073.    @Test
074.    public void testContains_object()
075.    {
076.        Tuple< String, String > tuple = new Tuple< String, String >( "companyname", SMACK_NAME );
077.        assertTrue( SMACK.contains( ( Object ) tuple ) );
078.    }
079. 
080.    @Test
081.    public void testContains_keyvaluepair()
082.    {
083.        assertTrue( SMACK.contains( "companyname", SMACK_NAME ) );
084.    }
085. 
086.    @Test
087.    public void testContains_key()
088.    {
089.        assertTrue( SMACK.contains( "companyname" ) );
090.    }
091. 
092.    @Test
093.    public void testContains_tuple()
094.    {
095.        Tuple< String, String > tuple = new Tuple< String, String >( "companyname", SMACK_NAME );
096.        assertTrue( SMACK.contains( tuple ) );
097.    }
098. 
099.    @Test
100.    public void testIterator()
101.    {
102.        Iterator< Tuple< String, String > > iterator = SMACK.iterator();
103. 
104.        assertNotNull( iterator );
105. 
106.        while( iterator.hasNext() )
107.        {
108.            Tuple< String, String > tuple = iterator.next();
109. 
110.            assertNotNull( tuple );
111.        }
112.    }
113. 
114.    @Test
115.    public void testToArray()
116.    {
117.        Object[] object = SMACK.toArray();
118. 
119.        assertTrue( object.length == SMACK.size() );
120.    }
121. 
122.    @Test
123.    public void testToArray_tarray()
124.    {
125.        @SuppressWarnings( "unchecked" )
126.        Tuple< String, String >[] list = new Tuple[ SMACK.size() ];
127. 
128.        list = SMACK.toArray( list );
129. 
130.        int length = list.length;
131. 
132.        assertTrue( length == SMACK.size() );
133. 
134.        TupleList s = new TupleList( SMACK.toTupleArray() );
135. 
136.        ArrayList< Tuple< String, String > > tuples = new ArrayList< Tuple< String, String > >( length );
137. 
138.        for( int i = 0; i < length; i++ )
139.            tuples.add( new Tuple< String, String >( list[ i ].key, list[ i ].value ) );
140. 
141.        assertTrue( s.removeAll( tuples ) );
142.        assertTrue( s.size() == 0 );
143.    }
144. 
145.    @Test
146.    public void testToTupleArray()
147.    {
148.        ArrayList< Tuple< String, String > > list = SMACK.toTupleArray();
149. 
150.        assertTrue( list.size() == SMACK.size() );
151.    }
152. 
153.    @Test
154.    public void testAdd_tuple()
155.    {
156.        Tuple< String, String > tuple = new Tuple< String, String >( "this", "and that" );
157.        TupleList             s     = new TupleList();
158. 
159.        assertTrue( s.add( tuple ) );
160.        assertTrue( s.size() == 1 );
161.    }
162. 
163.    @Test
164.    public void testAdd_keyvaluepair()
165.    {
166.        TupleList s = new TupleList();
167. 
168.        assertTrue( s.add( "this", "and that" ) );
169.        assertTrue( s.size() == 1 );
170.    }
171. 
172.    @Test
173.    public void testRemove_object()
174.    {
175.        Tuple< String, String > tuple = new Tuple< String, String >( "companyname", SMACK_NAME );
176.        TupleList             s     = new TupleList( SMACK.toTupleArray() );
177. 
178.        s.remove( ( Object ) tuple );
179. 
180.        assertTrue( s.size() == SMACK.size() - 1 );
181.    }
182. 
183.    @Test
184.    public void testRemove_keyvaluepair()
185.    {
186.        TupleList s = new TupleList( SMACK.toTupleArray() );
187. 
188.        s.remove( "companyname", SMACK_NAME );
189. 
190.        assertTrue( s.size() == SMACK.size() - 1 );
191.    }
192. 
193.    @Test
194.    public void testContainsAll()
195.    {
196.        TupleList s = new TupleList( SMACK.toTupleArray() );
197.        assertTrue( SMACK.containsAll( s.toTupleArray() ) );
198.    }
199. 
200.    @Test
201.    public void testAddAll()
202.    {
203.        TupleList s = new TupleList( SMACK.toTupleArray() );
204. 
205.        assertTrue( s.addAll( SHICK.toTupleArray() ) );
206.    }
207. 
208.    @Test
209.    public void testRemoveAll()
210.    {
211.        TupleList s = new TupleList( SMACK.toTupleArray() );
212. 
213.        s.removeAll( SMACK.toTupleArray() );
214. 
215.        assertTrue( s.isEmpty() );
216.    }
217. 
218.    @Test
219.    public void testRetainAll()
220.    {
221.        Tuple< String, String >              tuple = new Tuple< String, String >( "companyname", SMACK_NAME );
222.        ArrayList< Tuple< String, String > > list  = SMACK.toTupleArray();
223. 
224.        // remove one of the tuples...
225.        list.remove( tuple );
226. 
227.        TupleList s = new TupleList( SMACK.toTupleArray() );
228. 
229.        // retain from the origin SMACK list only the tuples present in the list (from which we removed one)...
230.        s.retainAll( list );
231. 
232.        assertTrue( SMACK.size() - 1 == s.size() );
233.    }
234. 
235.    @Test
236.    public void testClear()
237.    {
238.        TupleList s = new TupleList( SMACK.toTupleArray() );
239. 
240.        s.clear();
241. 
242.        assertTrue( s.size() == 0 );
243.    }
244. 
245.    @Test
246.    public void testEquals()
247.    {
248.        TupleList s = new TupleList( SMACK.toTupleArray() );
249. 
250.        assertTrue( s.equals( SMACK ) );
251.    }
252. 
253.    @Test
254.    public void testToString()
255.    {
256.        System.out.println( SMACK.toString() );
257.    }
258. 
259.    @Test
260.    public void testGetAll()
261.    {
262.        TupleList                          s    = new TupleList( SMACK.toTupleArray() );
263.        ArrayList< Tuple< String, String > > list = s.getAll( SHICK_NAME );
264. 
265.        int length = s.size();
266. 
267.        assertTrue( list.size() == 2 );         // should have found 2 tuples...
268.        assertTrue( s.removeAll( list ) );      // should modify the internal list...
269.        assertTrue( s.size() == length - 2 );   // should have matched (and removed) the 2 tuples...
270.    }
271. 
272.    @Test
273.    public void testGetFirst()
274.    {
275.        String value = SMACK.getFirst( "companyname" );
276. 
277.        assertNotNull( value );
278.        assertTrue( value.equals( SMACK_NAME ) );
279.    }
280.}