blob: 4f3dcd7201869670f8f0bba6dc5c5119a6954d3c [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.ofbiz.base.util.collections;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javolution.context.ObjectFactory;
import javolution.lang.Reusable;
import javolution.util.FastList;
import javolution.util.FastMap;
import javolution.util.FastSet;
import org.ofbiz.base.util.UtilGenerics;
/**
* Map Stack
*
*/
public class MapContext<K, V> implements Map<K, V>, Reusable, LocalizedMap<V> {
public static final String module = MapContext.class.getName();
protected static final ObjectFactory<MapContext<?, ?>> mapStackFactory = new ObjectFactory<MapContext<?, ?>>() {
@Override
protected MapContext<?, ?> create() {
return new MapContext<Object, Object>();
}
};
public static final <K, V> MapContext<K, V> getMapContext() {
return (MapContext<K, V>) UtilGenerics.<K, V>checkMap(mapStackFactory.object());
}
public static <K, V> MapContext<K, V> createMapContext() {
MapContext<K, V> newValue = MapContext.getMapContext();
// initialize with a single entry
newValue.push();
return newValue;
}
@SuppressWarnings("unchecked")
public static <K, V> MapContext<K, V> createMapContext(Map<K, V> baseMap) {
MapContext<K, V> newValue = MapContext.getMapContext();
if (baseMap instanceof MapContext) {
newValue.stackList.addAll(((MapContext) baseMap).stackList);
} else {
newValue.stackList.add(0, baseMap);
}
return newValue;
}
/** Does a shallow copy of the internal stack of the passed MapContext; enables simultaneous stacks that share common parent Maps */
public static <K, V> MapContext<K, V> createMapContext(MapContext<K, V> source) {
MapContext<K, V> newValue = MapContext.getMapContext();
newValue.stackList.addAll(source.stackList);
return newValue;
}
protected MapContext() {
super();
}
protected List<Map<K, V>> stackList = FastList.newInstance();
public void reset() {
stackList = FastList.newInstance();
}
/** Puts a new Map on the top of the stack */
public void push() {
Map<K, V> newMap = FastMap.newInstance();
this.stackList.add(0,newMap);
}
/** Puts an existing Map on the top of the stack (top meaning will override lower layers on the stack) */
public void push(Map<K, V> existingMap) {
if (existingMap == null) {
throw new IllegalArgumentException("Error: cannot push null existing Map onto a MapContext");
}
this.stackList.add(0, existingMap);
}
/** Puts an existing Map on the BOTTOM of the stack (bottom meaning will be overriden by lower layers on the stack, ie everything else already there) */
public void addToBottom(Map<K, V> existingMap) {
if (existingMap == null) {
throw new IllegalArgumentException("Error: cannot add null existing Map to bottom of a MapContext");
}
this.stackList.add(existingMap);
}
/** Remove and returns the Map from the top of the stack; if there is only one Map on the stack it returns null and does not remove it */
public Map<K, V> pop() {
// always leave at least one Map in the List, ie never pop off the last Map
if (this.stackList.size() > 1) {
return stackList.remove(0);
} else {
return null;
}
}
/**
* Creates a MapContext object that has the same Map objects on its stack;
* meant to be used to enable a
* situation where a parent and child context are operating simultaneously
* using two different MapContext objects, but sharing the Maps in common
*/
public MapContext<K, V> standAloneStack() {
MapContext<K, V> standAlone = MapContext.createMapContext(this);
return standAlone;
}
/**
* Creates a MapContext object that has the same Map objects on its stack,
* but with a new Map pushed on the top; meant to be used to enable a
* situation where a parent and child context are operating simultaneously
* using two different MapContext objects, but sharing the Maps in common
*/
public MapContext<K, V> standAloneChildStack() {
MapContext<K, V> standAloneChild = MapContext.createMapContext(this);
standAloneChild.push();
return standAloneChild;
}
/* (non-Javadoc)
* @see java.util.Map#size()
*/
public int size() {
// a little bit tricky; to represent the apparent size we need to aggregate all keys and get a count of unique keys
// this is a bit of a slow way, but gets the best number possible
Set<K> keys = this.keySet();
return keys.size();
}
/* (non-Javadoc)
* @see java.util.Map#isEmpty()
*/
public boolean isEmpty() {
// walk the stackList and if any is not empty, return false; otherwise return true
for (Map<K, V> curMap: this.stackList) {
if (!curMap.isEmpty()) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
// walk the stackList and for the first place it is found return true; otherwise refurn false
for (Map<K, V> curMap: this.stackList) {
if (curMap.containsKey(key)) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see java.util.Map#containsValue(java.lang.Object)
*/
public boolean containsValue(Object value) {
// walk the stackList and the entries for each Map and if nothing is in for the current key, consider it an option, otherwise ignore
Set<K> resultKeySet = FastSet.newInstance();
for (Map<K, V> curMap: this.stackList) {
for (Map.Entry<K, V> curEntry: curMap.entrySet()) {
if (!resultKeySet.contains(curEntry.getKey())) {
resultKeySet.add(curEntry.getKey());
if (value == null) {
if (curEntry.getValue() == null) {
return true;
}
} else {
if (value.equals(curEntry.getValue())) {
return true;
}
}
}
}
}
return false;
}
/* (non-Javadoc)
* @see java.util.Map#get(java.lang.Object)
*/
public V get(Object key) {
// walk the stackList and for the first place it is found return true; otherwise refurn false
for (Map<K, V> curMap: this.stackList) {
// only return if the curMap contains the key, rather than checking for null; this allows a null at a lower level to override a value at a higher level
if (curMap.containsKey(key)) {
return curMap.get(key);
}
}
return null;
}
/* (non-Javadoc)
* @see org.ofbiz.base.util.collections.LocalizedMap#get(java.lang.String, java.util.Locale)
*/
public V get(String name, Locale locale) {
// walk the stackList and for the first place it is found return true; otherwise refurn false
for (Map<K, V> curMap: this.stackList) {
// only return if the curMap contains the key, rather than checking for null; this allows a null at a lower level to override a value at a higher level
if (curMap.containsKey(name)) {
if (curMap instanceof LocalizedMap<?>) {
LocalizedMap<V> lmap = UtilGenerics.cast(curMap);
return lmap.get(name, locale);
} else {
return curMap.get(name);
}
}
}
return null;
}
/* (non-Javadoc)
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
*/
public V put(K key, V value) {
// all write operations are local: only put in the Map on the top of the stack
Map<K, V> currentMap = this.stackList.get(0);
return currentMap.put(key, value);
}
/* (non-Javadoc)
* @see java.util.Map#remove(java.lang.Object)
*/
public V remove(Object key) {
// all write operations are local: only remove from the Map on the top of the stack
Map<K, V> currentMap = this.stackList.get(0);
return currentMap.remove(key);
}
/* (non-Javadoc)
* @see java.util.Map#putAll(java.util.Map)
*/
public void putAll(Map<? extends K, ? extends V> arg0) {
// all write operations are local: only put in the Map on the top of the stack
Map<K, V> currentMap = this.stackList.get(0);
currentMap.putAll(arg0);
}
/* (non-Javadoc)
* @see java.util.Map#clear()
*/
public void clear() {
// all write operations are local: only clear the Map on the top of the stack
this.stackList.get(0).clear();
}
/* (non-Javadoc)
* @see java.util.Map#keySet()
*/
public Set<K> keySet() {
// walk the stackList and aggregate all keys
Set<K> resultSet = FastSet.newInstance();
for (Map<K, V> curMap: this.stackList) {
resultSet.addAll(curMap.keySet());
}
return Collections.unmodifiableSet(resultSet);
}
/* (non-Javadoc)
* @see java.util.Map#values()
*/
public Collection<V> values() {
// walk the stackList and the entries for each Map and if nothing is in for the current key, put it in
Set<K> resultKeySet = FastSet.newInstance();
List<V> resultValues = FastList.newInstance();
for (Map<K, V> curMap: this.stackList) {
for (Map.Entry<K, V> curEntry: curMap.entrySet()) {
if (!resultKeySet.contains(curEntry.getKey())) {
resultKeySet.add(curEntry.getKey());
resultValues.add(curEntry.getValue());
}
}
}
return Collections.unmodifiableCollection(resultValues);
}
/* (non-Javadoc)
* @see java.util.Map#entrySet()
*/
public Set<Map.Entry<K, V>> entrySet() {
// walk the stackList and the entries for each Map and if nothing is in for the current key, put it in
Set<K> resultKeySet = FastSet.newInstance();
Set<Map.Entry<K, V>> resultEntrySet = FastSet.newInstance();
for (Map<K, V> curMap: this.stackList) {
for (Map.Entry<K, V> curEntry: curMap.entrySet()) {
if (!resultKeySet.contains(curEntry.getKey())) {
resultKeySet.add(curEntry.getKey());
resultEntrySet.add(curEntry);
}
}
}
return Collections.unmodifiableSet(resultEntrySet);
}
@Override
public String toString() {
StringBuilder fullMapString = new StringBuilder();
int curLevel = 0;
for (Map<K, V> curMap: this.stackList) {
fullMapString.append("============================== Start stack level " + curLevel + "\n");
for (Map.Entry<K, V> curEntry: curMap.entrySet()) {
fullMapString.append("==>[");
fullMapString.append(curEntry.getKey());
fullMapString.append("]:");
// skip the instances of MapContext to avoid infinite loop
if (curEntry.getValue() instanceof MapContext<?, ?>) {
fullMapString.append("<Instance of MapContext, not printing to avoid infinite recursion>");
} else {
fullMapString.append(curEntry.getValue());
}
fullMapString.append("\n");
}
fullMapString.append("============================== End stack level " + curLevel + "\n");
curLevel++;
}
return fullMapString.toString();
}
}