"What if I have an arbitrary number of components?"
Well then suppress the heck out of some warnings and go old school with well hidden raw maps.
import java.util.HashMap;
import java.util.Map;
/**
* Map lookup with arbitrary number of keys, as set with first use of lay()
* Missing keys map to null if null key exists
*/
public class MultiKeyMap<K, V>
{
int expectedNumberOfKeys = -1;
V value;
@SuppressWarnings("rawtypes")
Map<K, Map> topMap = new HashMap<K, Map>();
/** Map to value from keys */
@SuppressWarnings({ "rawtypes", "unchecked" })
public V lay(V value, K... keys)
{
if (keys == null)
{
//there are no keys.
expectedNumberOfKeys = 0;
V oldValue = this.value;
this.value = value;
return oldValue;
}
if (expectedNumberOfKeys != -1 && expectedNumberOfKeys != keys.length)
{
throw new IllegalArgumentException("Expecting " + expectedNumberOfKeys + " keys. Was " + keys.length );
}
expectedNumberOfKeys = keys.length;
Map<K, Map> currentMap = topMap;
//all but last key
for(int i = 0; i < keys.length - 1; i++)
{
K key = keys[i];
currentMap = linkToNextMap(currentMap, key);
}
//last key
V oldValue = ((Map<K,V>)currentMap).put(keys[keys.length - 1], value);
return oldValue;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
Map<K,Map> linkToNextMap(Map<K,Map> map, K key)
{
Map<K, Map> nextMap = null;
if ( ! map.containsKey(key) )
{
map.put(key, new HashMap<K, Map>() );
}
nextMap = map.get(key);
return nextMap;
}
/**
* Get value maped from keys. Must include as many keys as laid down.
* Keys not found are taken as null keys
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public V get(K... keys)
{
if (keys == null)
{
return value;
}
//System.out.println(topMap+" <- topMap");//TODO remove
if (expectedNumberOfKeys == -1)
{
return null;
}
if (expectedNumberOfKeys == 0)
{
return value;
}
if (expectedNumberOfKeys != keys.length)
{
throw new IllegalArgumentException("Expecting " + expectedNumberOfKeys + " keys. Was " + keys.length );
}
Map<K, Map> currentMap = topMap;
//All but last key
for(int i = 0; i < keys.length - 1; i++)
{
currentMap = (Map) getDefault(currentMap, keys[i]);
}
//Last key
V result = (V) getDefault(currentMap, keys[keys.length - 1]);
return result;
}
@SuppressWarnings("rawtypes")
Object getDefault(Map map, K key)
{
Object result = null;
if (map != null)
{
//Use default key (null) if not found
if ( ! map.containsKey(key) )
{
key = null;
}
result = map.get(key);
}
return result;
}
public static void main(String[] args)
{
//Build {null={D=4, null=3}, A={null=1, Z=2}}
MultiKeyMap<String, Integer> map2 = new MultiKeyMap<String, Integer>();
map2.lay(1, "A", null);
map2.lay(2, "A", "Z");
map2.lay(3, null, null);
map2.lay(4, null, "D");
System.out.println(map2.get("A", null)); //1
System.out.println(map2.get("A", "Z")); //2
System.out.println(map2.get("A", "F")); //1 F not found so treating as null
System.out.println(map2.get(null, null));//3
System.out.println(map2.get(null, "D")); //4
System.out.println(map2.get("F", "D")); //4 F not found so treating as null
System.out.println();
//Build {null={D={C=4}, null={C=3}}, A={null={B=1}, Z={B=2}}}
MultiKeyMap<String, Integer> map3 = new MultiKeyMap<String, Integer>();
map3.lay(1, "A", null, "B");
map3.lay(2, "A", "Z", "B");
map3.lay(3, null, null, "C");
map3.lay(4, null, "D", "C");
System.out.println(map3.get("A", null, "B")); //1
System.out.println(map3.get("A", "Z", "B")); //2
System.out.println(map3.get("A", "F", "B")); //1 F not found so treating as null
System.out.println(map3.get(null, null, "C"));//3
System.out.println(map3.get(null, "D", "C")); //4
System.out.println(map3.get("F", "D", "C")); //4 F not found so treating as null
}
}
Displays:
1
2
1
3
4
4
1
2
1
3
4
4
I know no one is going to vote for this but I couldn't sleep until I got it out of my head.
Good night SO.