/**
* Copyright 2011 meltmedia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xchain.framework.util;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.namespace.QName;
/**
* A map implementation that can use multiple maps. The maps are unmodified so if a map is attached and later modified,
* the modifications will be present in the CompositeMap.
*
* @param <K> The type of key values. Must extend {@link QName}.
* @param <V> The key values.
*
* @author Christian Trimble
*/
public abstract class CompositeMap<K extends QName, V>
extends AbstractMap<K, V>
{
/**
* Get a list of all the maps which make up this composite map. The order
* of the list is important as the first entry may be modified if the
* put() or clear() methods are used.
*
* @return A list of Map<K extends {@link QName}, V> objects.
*/
protected abstract List<Map<K, V>> mapList();
/**
* Get the first map from mapList().
*
* @return A Map<K extends {@link QName}, V>.
*/
protected Map<K, V> firstMap()
{
List<Map<K, V>> mapList = mapList();
if( !mapList.isEmpty() ) {
return mapList.get(0);
}
return null;
}
/**
* Clear out the entries in the first map.
*/
public void clear()
{
Map<K, V> firstMap = firstMap();
if( firstMap != null ) {
firstMap.clear();
}
}
/**
* Add an entry to the first map.
*/
public V put( K key, V value )
{
Map<K, V> firstMap = firstMap();
if( firstMap != null ) {
return firstMap.put(key, value);
}
return null;
}
public Set<Entry<K, V>>entrySet()
{
return new EntrySet<Entry<K, V>>();
}
/**
* A set implementation to consider the size of all the attached maps.
*
* @param <S> The type of elements in the set.
*/
private class EntrySet<S extends Entry<K, V>>
extends AbstractSet<S>
{
/**
* Compute the size based on all the attached maps.
*/
public int size()
{
LinkedHashSet<K> keys = new LinkedHashSet<K>();
List<Map<K, V>> mapList = mapList();
Iterator<Map<K, V>> mapListIterator = mapList.iterator();
while( mapListIterator.hasNext() ) {
keys.addAll(mapListIterator.next().keySet());
}
return keys.size();
}
public Iterator<S> iterator()
{
return new EntryIterator<S>();
}
}
/**
* An iterator implementation to iterate over all the entries in the attached maps.
*
* @param <I> The type of entries to iterate through.
*/
private class EntryIterator<I extends Entry<K, V>>
implements Iterator<I>
{
private List<Map<K, V>> mapList = null;
private Iterator<Map<K, V>> mapListIterator = null;
private Iterator<I> currentMapIterator = null;
private I nextEntry = null;
private TreeSet<I> returnedEntrySet = new TreeSet<I>(
new Comparator<I>()
{
public int compare(I o1, I o2)
{
QName key1 = o1.getKey();
QName key2 = o2.getKey();
// nulls are equal.
if( key1 == null && key2 == null ) {
return 0;
}
// sort nulls to the end.
if( key1 == null || key2 == null ) {
return key1 == null ? -1 : 1;
}
// compare the qNames by namespace.
int namespaceCompare = key1.getNamespaceURI().compareTo(key2.getNamespaceURI());
if( namespaceCompare != 0 ) {
return namespaceCompare;
}
else {
return key1.getLocalPart().compareTo(key2.getLocalPart());
}
}
public boolean equals(Object obj)
{
return this == obj;
}
}
);
public EntryIterator()
{
mapList = mapList();
mapListIterator = mapList.iterator();
}
public boolean hasNext()
{
advanceState();
return nextEntry != null;
}
public I next()
{
advanceState();
I next = nextEntry;
nextEntry = null;
return next;
}
/**
* Advance to the next entry. This will span across all the attached maps.
*/
private void advanceState()
{
// if the next entry has not been consumed.
if( nextEntry != null ) {
return;
}
while( nextEntry == null ) {
// search for a currentMapIterator that has an element to return.
while( (currentMapIterator == null || !currentMapIterator.hasNext()) && mapListIterator.hasNext() ) {
currentMapIterator = (Iterator<I>) mapListIterator.next().entrySet().iterator();
}
// if we didn't advance to a state where the currentMapIterator has a next element, then return.
if( currentMapIterator == null || !currentMapIterator.hasNext() ) {
return;
}
nextEntry = currentMapIterator.next();
// if the next entry is in the returnedEntrySet, then keep looking.
if( !returnedEntrySet.add(nextEntry) ) {
nextEntry = null;
}
}
}
public void remove()
{
if( currentMapIterator == null ) {
throw new IllegalStateException("The next() method has not been called.");
}
currentMapIterator.remove();
}
}
}