Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.force.i18n.settings.SharedKeyMap Maven / Gradle / Ivy
Go to download
Localization Framework that allows grammatically correct renaming of nouns
/*
* Copyright (c) 2017, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
package com.force.i18n.settings;
import java.io.Serializable;
import java.util.*;
import com.force.i18n.commons.util.collection.ExpandableArray;
/**
* SharedKeyMap is an implementation of the Map interface where many SharedKeyMap instances
* share a single copy of their key mappings. This is useful if you have many maps that all have
* the same (or nearly the same) keys, because you don't have to store the keys over and over.
*
* Because SharedKeyMaps only make sense when there are multiple maps in play, the only way
* to create one is with the static factory methods createSharedKeyMaps and createEmptySharedKeyMaps,
* which create a List of SharedKeyMaps that all share the same keys.
*
* Internally, each SharedKeyMap points to a single Map that maps keys to integer indices. Then each
* SharedKeyMap has its own ArrayList that contains its values, indexed by the indices mentioned above.
* Removing from an individual SharedKeyMap doesn't affect the shared map, but adding to an individual
* SharedKeyMap with a key that wasn't in the shared map will add that key to the shared map.
*
* @author shansma
* @param type for the key that will be shared
* @param type for the value
*/
public class SharedKeyMap extends AbstractMap implements Serializable {
private final ExpandableArray values;
private final Map keyToIndex;
private int size = 0;
/**
* See HashMap.modCount
*/
private transient volatile int modCount = 0;
private static final Object NULL_MARKER = new Object();
private static final long serialVersionUID = -1L;
/**
* Takes a list of Maps and returns a list of SharedKeyMaps with the same mappings and values.
*
* @param maps - the maps to re-create as SharedKeyMaps
* @param type for the key that will be shared
* @param type for the value
* @return a new shareKeyMap
*/
public static List> createSharedKeyMaps(List> maps) {
return createSharedKeyMaps(maps, false);
}
/**
* Takes a list of Maps and returns a list of SharedKeyMaps with the same mappings and values.
*
* @param maps - the maps to re-create as SharedKeyMaps
* @param removeFromList - remove the passed-in maps from the list as they are processed, so they can be garbage collected.
* @param type for the key that will be shared
* @param type for the value
* @return a new shareKeyMap
*/
public static List> createSharedKeyMaps(List> maps, final boolean removeFromList) {
Map keyToIndex = new HashMap();
List> results = new ArrayList>(maps.size());
// build the shared key maps
for (Iterator> i = maps.iterator(); i.hasNext();) {
Map values = i.next();
SharedKeyMap newMap = new SharedKeyMap(keyToIndex, values);
results.add(newMap);
if (removeFromList)
i.remove();
}
return results;
}
/**
* Creates a list of empty SharedKeyMaps that all use the specified keys. If you know ahead of time
* what the keys in your maps will (likely) be, but don't yet have the maps created, you can call this
* function and then use the maps it returns as you would use normal maps.
*
* @param keys - the keys that all your maps will (likely) use
* @param numberOfMaps - the number of maps this function will create for you
* @param type for the key that will be shared
* @param type for the value
* @return a new shareKeyMap
*/
public static List> createEmptySharedKeyMaps(List keys, int numberOfMaps) {
Map keyToIndex = new HashMap(keys.size() * 3 / 2);
List> results = new ArrayList>(numberOfMaps);
int index = 0;
// build the key-to-index map
for (K key : keys) {
keyToIndex.put(key, index++);
}
// build the shared key maps
for (int i = 0; i < numberOfMaps; i++) {
Map emptyMap = Collections.emptyMap();
results.add(new SharedKeyMap(keyToIndex, emptyMap));
}
return results;
}
/**
* Creates a list of empty SharedKeyMaps that all share a single (empty) key map.
*
* @param numberOfMaps - the number of maps this function will create for you
* @param type for the key that will be shared
* @param type for the value
* @return a new shareKeyMap
*/
public static List> createEmptySharedKeyMaps(int numberOfMaps) {
Map keyToIndex = new HashMap();
List> results = new ArrayList>(numberOfMaps);
// build the shared key maps
for (int i = 0; i < numberOfMaps; i++) {
Map emptyMap = Collections.emptyMap();
results.add(new SharedKeyMap(keyToIndex, emptyMap));
}
return results;
}
/**
* Creates a list of empty SharedKeyMaps that all use the key map of the specified SharedKeyMap.
*
* @param numberOfMaps - the number of maps this function will create for you
* @param map the key map with which to share keys
* @param type for the key that will be shared
* @param type for the value
* @return a new shareKeyMap
*/
public static List> createEmptySharedKeyMaps(int numberOfMaps, SharedKeyMap map) {
if (map == null)
return createEmptySharedKeyMaps(numberOfMaps);
List> results = new ArrayList>(numberOfMaps);
// build the shared key maps
for (int i = 0; i < numberOfMaps; i++) {
results.add(new SharedKeyMap(map));
}
return results;
}
/**
* Creates a new shared key map that shares the keymap of map.
* @param map the shared key map
*/
public SharedKeyMap(SharedKeyMap map) {
this.keyToIndex = map.keyToIndex;
this.values = new ExpandableArray();
}
/**
* Creates an empty, isolated SharedKeyMap.
*/
public SharedKeyMap() {
this(new HashMap(), 16);
}
/**
* Creates an empty, isolated SharedKeyMap, with control over
* the initial size of the values array. This is useful when you have a SharedKeyMap
* that you use as the canonical version to "clone" for other SharedKeyMaps
* and you want to save the overhead of the unnecessary value arrays that would
* remain empty.
*
* @param initialValueCapacity if this is non-negative, then its used as
* the initial size for the values array. If its negative, then
* this SharedKeyMap cannot have any values stored in it.
*/
public SharedKeyMap(int initialValueCapacity) {
this(new HashMap(), initialValueCapacity);
}
/**
* Creates an empty, isolated SharedKeyMap, with control over
* the key-to-index value map and also initial size of the values array.
* This is useful when you have a SharedKeyMap
* that you use as the canonical version to "clone" for other SharedKeyMaps
* and you want to save the overhead of the unnecessary value arrays that would
* remain empty.
*
* @param keyToIndex maps keys to their index in the values array. This
* structure is shared across threads.
* @param initialValueCapacity if this is non-negative, then its used as
* the initial size for the values array. If its negative, then
* this SharedKeyMap cannot have any values stored in it.
*/
public SharedKeyMap(Map keyToIndex, int initialValueCapacity) {
this.keyToIndex = keyToIndex;
if (initialValueCapacity >= 0) {
this.values = new ExpandableArray(initialValueCapacity);
} else {
this.values = null;
}
}
private SharedKeyMap(Map keyToIndex, Map values) {
this.keyToIndex = keyToIndex;
if (this.keyToIndex.size() > 0) {
this.values = new ExpandableArray(this.keyToIndex.size());
} else {
this.values = new ExpandableArray();
}
putAll(values);
}
@Override
public int size() {
return this.size;
}
public void trimToSize() {
this.values.trimToSize();
}
/**
* Same as get, but returns NULL_MARKER instead of null
*/
private final V inner_get(Object key) {
Integer index = this.keyToIndex.get(key);
if (index == null)
return null;
if (this.values == null) return null;
return this.values.get(index.intValue());
}
@Override
public boolean containsKey(Object key) {
return inner_get(key) != null;
}
@Override
public boolean containsValue(Object value) {
if (this.values == null) return false;
value = maskNull(value);
for (int i = 0; i < this.values.size(); i++) {
if (value.equals(this.values.get(i)))
return true;
}
return false;
}
@Override
public V get(Object key) {
return unmaskNull(inner_get(key));
}
@Override
public V put(K key, V value) {
Integer index = this.keyToIndex.get(key);
if (index == null) {
// add the key to our key-to-index map
index = this.keyToIndex.get(key);
if (index == null) {
index = Integer.valueOf(this.keyToIndex.size());
this.keyToIndex.put(key, index);
}
}
return setAt(index.intValue(), value);
}
private V setAt(int index, V value) {
if (this.values == null) throw new UnsupportedOperationException();
this.modCount++;
value = maskNull(value);
V oldValue = this.values.get(index);
this.values.set(index, value);
if (oldValue == null) {
this.size++;
}
this.values.set(index, value);
return unmaskNull(oldValue);
}
@Override
public V remove(Object key) {
Integer index = this.keyToIndex.get(key);
if (index == null)
return null;
return removeAt(index.intValue());
}
private V removeAt(int index) {
if (this.values == null) throw new UnsupportedOperationException();
this.modCount++;
V oldValue = this.values.get(index);
if (oldValue != null) {
this.size--;
}
this.values.set(index, null);
return unmaskNull(oldValue);
}
/**
* Clears out the values in this instance of a SharedKeyMap; does not affect the contents of
* the key-to-index map.
*/
@Override
public void clear() {
if (this.values == null) throw new UnsupportedOperationException();
this.modCount++;
this.size = 0;
this.values.clear();
}
@Override
public Set keySet() {
return new AbstractSet() {
@Override
public Iterator iterator() {
final Iterator backingIterator = SharedKeyMap.this.keyToIndex.keySet().iterator();
return new Iterator() {
private K lookaheadNext = null; // the value we will return the next time next() is called
private K currentNext = null; // the value we returned the most recent time next() was called
private int expectedModCount = SharedKeyMap.this.modCount;
@Override
public boolean hasNext() {
if (SharedKeyMap.this.modCount != expectedModCount)
throw new ConcurrentModificationException();
if (lookaheadNext != null)
return true;
while (backingIterator.hasNext()) {
lookaheadNext = backingIterator.next();
if (SharedKeyMap.this.containsKey(lookaheadNext))
return true;
}
return false;
}
@Override
public K next() {
if (!hasNext())
throw new NoSuchElementException();
this.currentNext = lookaheadNext;
lookaheadNext = null;
return this.currentNext;
}
@Override
public void remove() {
if (SharedKeyMap.this.modCount != expectedModCount)
throw new ConcurrentModificationException();
if (this.currentNext == null)
throw new NoSuchElementException();
SharedKeyMap.this.remove(this.currentNext);
this.expectedModCount = SharedKeyMap.this.modCount;
}
};
}
@Override
public int size() {
return SharedKeyMap.this.size();
}
@Override
public boolean contains(Object o) {
return containsKey(o);
}
};
}
private static T unmaskNull(T o) {
if (o == NULL_MARKER)
return null;
return o;
}
@SuppressWarnings("unchecked")
private static T maskNull(T o) {
if (o == null)
return (T)NULL_MARKER;
return o;
}
/**
* The getKey function on the MapEntries returned by this iterator takes O(keys) time,
* so don't call it if you can avoid it.
*/
@Override
public Set> entrySet() {
final Set keySet = keySet();
return new AbstractSet>() {
@Override
public Iterator> iterator() {
final Iterator keyIterator = keySet.iterator();
return new Iterator>() {
@Override
public boolean hasNext() {
return keyIterator.hasNext();
}
@Override
public Entry next() {
final K key = keyIterator.next();
return new Map.Entry() {
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return SharedKeyMap.this.get(key);
}
@Override
public V setValue(V newValue) {
// this is an optional operation
throw new UnsupportedOperationException();
}
};
}
@Override
public void remove() {
keyIterator.remove();
}
};
}
@Override
public int size() {
return SharedKeyMap.this.size();
}
};
}
/**
* @return the number of entries in the keymap. This isn't something you should generally care about,
* but if you're the curious type, here it is. It's also useful if you're serializing SharedKeyMaps
* one by one -- since the keymap is always appended to, and entries are never modified or removed,
* keyMapSize() will change whenever the contents of the keymap have changed (and thus need to be
* re-serialized).
*/
public int keyMapSize() {
return this.keyToIndex.size();
}
}