One day, I felt an urge to create this data structure, which is missing from 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.
}
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.
}
...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.
}