AMHashMap.java revision ba3a3712480c928969c1051ed875c776faaecd93
/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: AMHashMap.java,v 1.7 2009/11/03 00:06:31 hengming Exp $
*
*/
/*
* Portions Copyrighted 2014 ForgeRock, AS.
*/
package com.iplanet.am.sdk;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.sun.identity.common.CaseInsensitiveHashMap;
import com.sun.identity.common.CaseInsensitiveHashSet;
/**
* This class will lower the case of the key that is used to access the HashMap.
* Primarily this implementation of HashMap is customized for storing and
* returning attributes.
*
* In the case of String values, the values are stored in a Set, whereas in case
* of byte values the values are stored as byte array. The special byte methods
* in this class should be used to do specific byte operations.
*
* This HashMap stores information about the type of values, (String or byte).
* All the operations are performed on this basis. The default case is that it
* stores String values.
*
* Note: For byte values there is no negative caching done.
*
*/
public class AMHashMap extends CaseInsensitiveHashMap {
private static final long serialVersionUID = 6468554078141700418L;
private boolean byteValues = false;
// Use a Collections.EMPTY_SET, as you want to avoid instantiating a Set
// for every AMHashMap created.
private Set byteNegativeAttrs = Collections.EMPTY_SET;
public AMHashMap() {
super();
}
/**
* Creates a new AMHashMap
*
* @param forByteValues
* if true then, this map is used for storing byte values. if
* false, then used for storing String values
*/
public AMHashMap(boolean forByteValues) {
super();
byteValues = forByteValues;
}
public AMHashMap(int initialCapacity) {
super(initialCapacity);
}
public AMHashMap(int initialCapacity, boolean forByteValues) {
super(initialCapacity);
byteValues = forByteValues;
}
public AMHashMap(HashMap map) {
super();
if (map != null) {
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String n = (String) it.next();
put(n, map.get(n));
}
}
}
/*
* Override the default isEmpty(). This map is not empty, if the type is
* byteValues and if either the map is not empty or the negative set is not
*/
public boolean isEmpty() {
if (!byteValues) {
return super.isEmpty();
} else {
return (super.isEmpty() && byteNegativeAttrs.isEmpty());
}
}
/*
* Override the default clear(). This method will clear the negative
* attributes set too, if the type is byteValues
*/
public void clear() {
super.clear();
if (byteValues) {
clearNegativeByteAttrs();
}
}
private void addNegativeByteAttr(String name) {
if (byteNegativeAttrs == Collections.EMPTY_SET) {
byteNegativeAttrs = new CaseInsensitiveHashSet();
}
byteNegativeAttrs.add(name);
}
private void removeNegativeByteAttr(String name) {
if (byteNegativeAttrs != Collections.EMPTY_SET) {
byteNegativeAttrs.remove(name);
}
}
protected void clearNegativeByteAttrs() {
if (byteNegativeAttrs != Collections.EMPTY_SET) {
byteNegativeAttrs.clear();
}
}
private boolean isNegativeByteAttr(String name) {
// Collections.EMPTY_SET implements contains and returns false. So, we
// are okay here.
return byteNegativeAttrs.contains(name);
}
/*
* Methods to get and get negative set of AMHashMap
*/
protected void setNegativeByteAttr(Set n) {
byteNegativeAttrs = n;
}
public Set getNegativeByteAttrClone() {
if (byteNegativeAttrs == Collections.EMPTY_SET) {
return Collections.EMPTY_SET;
}
Set res = new HashSet();
Iterator it = byteNegativeAttrs.iterator();
while (it.hasNext()) {
res.add(it.next());
}
return res;
}
/**
* This compares all the elements of the specified and returns a Set which
* contains all the keys missing in the map. If a key is missing, this
* method adds puts a new element in this map with the missing key and empty
* Set.
*
* @param keys
* the Set of keys that will be compared against the keys in this
* map.
* @return a set of keys which are missing
*/
public Set getMissingKeys(Set keys) {
Set missAttrNames = new HashSet();
Iterator itr = keys.iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
if (get(name) == null) {
if (byteValues) {
if (!isNegativeByteAttr(name)) {
// need to mark it as negative
missAttrNames.add(name);
addNegativeByteAttr(name);
}
} else { // String values
missAttrNames.add(name);
put(name, new HashSet());
}
}
}
return missAttrNames;
}
/**
* This compares all the elements of the specified and returns a Set which
* contains all the keys missing in the map and also those keys whose values
* are empty. If a key is missing, this method adds puts a new element in
* this map with the missing key and empty Set.
*
* @param keys
* the Set of keys that will be compared against the keys in this
* map.
* @return a set of keys which are missing and keys whose values are empty
* Sets.
*/
public Set getMissingAndEmptyKeys(Set keys) {
Set missAttrNames = new HashSet();
Iterator itr = keys.iterator();
if (!byteValues) { // String values
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) get(name);
if (values == null) {
missAttrNames.add(name);
put(name, new HashSet());
} else if (values.isEmpty()) {
missAttrNames.add(name);
}
}
} else { // byte values
while (itr.hasNext()) {
String name = (String) itr.next();
byte[][] values = (byte[][]) get(name);
if (values == null) {
missAttrNames.add(name);
addNegativeByteAttr(name);
} else if (isNegativeByteAttr(name)) {
missAttrNames.add(name);
}
}
}
return missAttrNames;
}
public void removeKeys(Set keys) {
if ((keys != null) && (!keys.isEmpty())) {
Iterator itr = keys.iterator();
while (itr.hasNext()) {
String t = (String) itr.next();
remove(t);
removeNegativeByteAttr(t);
}
}
}
/**
* Copies the contents of the specified map to this map. This operation
* overwrites the values in this map.
*
* @param map
* the map that needs to be copied
*/
public void copy(Map map) {
if (map == null || map.isEmpty()) {
return;
}
// Replicating to avoid comparison inside loop
Iterator itr = map.keySet().iterator();
if (!byteValues) { // String values
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) map.get(name);
this.put(name, getSetCopy(values));
}
} else { // byte values
while (itr.hasNext()) {
String name = (String) itr.next();
byte[][] values = (byte[][]) map.get(name);
this.put(name, values);
this.removeNegativeByteAttr(name);
}
if (map instanceof AMHashMap) {
this.setNegativeByteAttr(((AMHashMap) map)
.getNegativeByteAttrClone());
}
}
}
/**
* Copies the contents of the specified map to this map. This operation
* overwrites the values in this map with new contents. This method copies
* only Sets with values and not empty sets. NOTE: This method will not
* worry about any negative attributes. Hence will be used in CacheBlock
* when the entries are being stored.
*
* @param map
* the map that needs to be copied
*
* @return a set of attributes which have empty sets stored with them
*/
public Set copyValuesOnly(Map map) {
if (map == null || map.isEmpty()) {
return null;
}
Set attrsWithValues = new HashSet();
// Replicating to avoid comparison inside loop
Iterator itr = map.keySet().iterator();
if (!byteValues) { // String values
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) map.get(name);
if ((values == null) || (values.isEmpty())) {
this.put(name, Collections.EMPTY_SET);
attrsWithValues.add(name);
} else {
this.put(name, getSetCopy(values));
attrsWithValues.add(name);
}
}
} else { // byte values
while (itr.hasNext()) {
String name = (String) itr.next();
byte[][] values = (byte[][]) map.get(name);
this.put(name, values);
attrsWithValues.add(name);
}
}
return attrsWithValues;
}
/**
* Merges all the values from the specified map to the current map. Adds new
* values into the current map if not already present. The method assumes
* that the values in the map are of Set type.
*
* @param map
* the map whose values need to merge and added to the current
* map.
* @throws NullPointerException
* if the map is null.
*/
public void merge(Map map) {
if (map == null) {
throw new NullPointerException();
}
if (!byteValues) { // For String values merge
Iterator itr = map.keySet().iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) this.get(name);
if (values != null) {
values.addAll((Set) map.get(name));
} else {
this.put(name, (Set) map.get(name));
}
}
} else { // For byte values replace ??
putAll(map);
}
}
/**
* Method which adds new entries to the map with empty set as values
*
* @param names
* new entries to be added to the map
*/
public void addEmptyValues(Set names) {
Iterator itr = names.iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
if (!byteValues) {
put(name, new HashSet());
} else {
addNegativeByteAttr(name);
}
}
}
/**
* Removes all the empty values (empty sets) from the map.
*/
public void removeEmptyValues() {
// We are removing negative attributes here
Set emptyKeys = new HashSet();
if (!byteValues) {
Iterator itr = keySet().iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) get(name);
if (values.isEmpty()) {
emptyKeys.add(name);
}
}
removeKeys(emptyKeys);
}
// Remove the negative byte attrs
clearNegativeByteAttrs();
}
/**
* This method is to some what clone() behaviour. However, this AMHashMap is
* designed to mostly store attribute names as keys and Set of values
* corresponding to each key, a new protected method has been created for
* this purpose.
*
* @return a new AMHashMap that has a copy of all the elements in this map
*/
public Map getCopy() {
if (!byteValues) { // String values
Map map = new AMHashMap(size(), false);
if (!isEmpty()) {
Iterator itr = keySet().iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) get(name);
map.put(name, getSetCopy(values));
}
}
return map;
} else { // byte values
Map map = new AMHashMap(size(), true);
if (!isEmpty()) {
Iterator itr = keySet().iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
byte[][] values = (byte[][]) get(name);
map.put(name, getByteArrayCopy(values));
}
}
((AMHashMap) map).setNegativeByteAttr(getNegativeByteAttrClone());
return map;
}
}
/**
* This method is to some what clone() behaviour. However, this AMHashMap is
* designed to mostly store attribute names as keys and Set of values
* corresponding to each key, a new protected method has been created for
* this purpose.
*
* @param names
* the names of the attributes that need to be obtained from Map
*
* @return a new AMHashMap that has a copy of all the elements in this map
*/
public Map getCopy(Set names) {
if (!byteValues) { // String values
Map map = new AMHashMap(false);
if (!isEmpty() && !names.isEmpty()) {
Iterator itr = names.iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
Set values = (Set) get(name);
if (values != null) {
map.put(name, getSetCopy(values));
}
}
}
return map;
} else { // byte values
Map map = new AMHashMap(true);
if (!isEmpty() && !names.isEmpty()) {
Iterator itr = names.iterator();
while (itr.hasNext()) {
String name = (String) itr.next();
byte[][] values = (byte[][]) get(name);
if (values != null) {
map.put(name, getByteArrayCopy(values));
}
}
}
((AMHashMap) map).setNegativeByteAttr(getNegativeByteAttrClone());
return map;
}
}
private Set getSetCopy(Set values) {
if (values == null) {
return null;
}
Set copyValues = new HashSet(values.size());
if (!values.isEmpty()) {
Iterator itr = values.iterator();
while (itr.hasNext()) {
String value = (String) itr.next();
copyValues.add(value);
}
}
return copyValues;
}
private byte[][] getByteArrayCopy(byte[][] values) {
int count1 = values.length;
byte[][] copyValues = new byte[count1][];
for (int i = 0; i < count1; i++) {
int count2 = values[i].length;
copyValues[i] = new byte[count2];
for (int j = 0; j < count2; j++) {
copyValues[i][j] = values[i][j];
}
}
return copyValues;
}
/**
* Checks whether this map contains byte array or string values.
*
* @return true if the type of values is {@code byte[][]} otherwise false.
*/
public boolean isByteValues() {
return byteValues;
}
}