package org.infinispan.marshall.core;
import java.util.Arrays;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* An efficient identity object map whose keys are {@link Class} objects and
* whose values are {@link AdvancedExternalizer} instances.
*/
final class ClassToExternalizerMap {
private static final Log log = LogFactory.getLog(ClassToExternalizerMap.class);
private AdvancedExternalizer[] values;
private Class[] keys;
private int count;
private int resizeCount;
private final float loadFactor;
/**
* Construct a new instance with the given initial capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadF the load factor
*/
public ClassToExternalizerMap(int initialCapacity, final float loadF) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity must be > 0");
}
if (loadF <= 0.0f || loadF >= 1.0f) {
throw new IllegalArgumentException("loadFactor must be > 0.0 and < 1.0");
}
if (initialCapacity < 16) {
initialCapacity = 16;
} else {
// round up
final int c = Integer.highestOneBit(initialCapacity) - 1;
initialCapacity = Integer.highestOneBit(initialCapacity + c);
}
keys = new Class[initialCapacity];
values = new AdvancedExternalizer[initialCapacity];
resizeCount = (int) ((double) initialCapacity * (double) loadF);
this.loadFactor = loadF;
}
/**
* Construct a new instance with the given load factor and an initial capacity of 64.
*
* @param loadFactor the load factor
*/
public ClassToExternalizerMap(final float loadFactor) {
this(64, loadFactor);
}
/**
* Construct a new instance with the given initial capacity and a load factor of {@code 0.5}.
*
* @param initialCapacity the initial capacity
*/
public ClassToExternalizerMap(final int initialCapacity) {
this(initialCapacity, 0.5f);
}
/**
* Construct a new instance with an initial capacity of 64 and a load factor of {@code 0.5}.
*/
public ClassToExternalizerMap() {
this(0.5f);
}
/**
* Get a value from the map.
*
* @param key the key
* @return the map value at the given key, or null if it's not found
*/
public AdvancedExternalizer get(Class key) {
final Class[] keys = this.keys;
final int mask = keys.length - 1;
int hc = System.identityHashCode(key) & mask;
Class k;
for (;;) {
k = keys[hc];
if (k == key) {
return values[hc];
}
if (k == null) {
// not found
return null;
}
hc = (hc + 1) & mask;
}
}
/**
* Put a value into the map. Any previous mapping is discarded silently.
*
* @param key the key
* @param value the value to store
*/
public void put(Class key, AdvancedExternalizer value) {
final Class[] keys = this.keys;
final int mask = keys.length - 1;
final AdvancedExternalizer[] values = this.values;
Class k;
int hc = System.identityHashCode(key) & mask;
for (int idx = hc;; idx = hc++ & mask) {
k = keys[idx];
if (k == null) {
keys[idx] = key;
values[idx] = value;
if (++count > resizeCount) {
resize();
}
return;
}
if (k == key) {
values[idx] = value;
return;
}
}
}
private void resize() {
final Class[] oldKeys = keys;
final int oldsize = oldKeys.length;
final AdvancedExternalizer[] oldValues = values;
if (oldsize >= 0x40000000) {
throw new IllegalStateException("Table full");
}
final int newsize = oldsize << 1;
final int mask = newsize - 1;
final Class[] newKeys = new Class[newsize];
final AdvancedExternalizer[] newValues = new AdvancedExternalizer[newsize];
keys = newKeys;
values = newValues;
if ((resizeCount <<= 1) == 0) {
resizeCount = Integer.MAX_VALUE;
}
for (int oi = 0; oi < oldsize; oi ++) {
final Class key = oldKeys[oi];
if (key != null) {
int ni = System.identityHashCode(key) & mask;
for (;;) {
final Object v = newKeys[ni];
if (v == null) {
// found
newKeys[ni] = key;
newValues[ni] = oldValues[oi];
break;
}
ni = (ni + 1) & mask;
}
}
}
}
public void clear() {
Arrays.fill(keys, null);
Arrays.fill(values, null);
count = 0;
}
/**
* Get a string summary representation of this map.
*
* @return a string representation
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Map length = ").append(keys.length).append(", count = ").append(count).append(", resize count = ").append(resizeCount).append('\n');
for (int i = 0; i < keys.length; i ++) {
builder.append('[').append(i).append("] = ");
if (keys[i] != null) {
final int hc = System.identityHashCode(keys[i]);
builder.append("{ ").append(keys[i]).append(" (hash ").append(hc).append(", modulus ").append(hc % keys.length).append(") => ").append(values[i]).append(" }");
} else {
builder.append("(blank)");
}
builder.append('\n');
}
return builder.toString();
}
public IdToExternalizerMap reverseMap() {
IdToExternalizerMap reverse = new IdToExternalizerHashMap(8, loadFactor);
fillReverseMap(reverse);
return reverse;
}
public IdToExternalizerMap reverseMap(int maxId) {
// If max identifier is known, provide a single array backed map
IdToExternalizerMap reverse = new IdToExternalizerArrayMap(maxId);
fillReverseMap(reverse);
return reverse;
}
private void fillReverseMap(IdToExternalizerMap reverse) {
for (AdvancedExternalizer ext : values) {
if (ext != null) {
AdvancedExternalizer prev = reverse.get(ext.getId());
if (prev != null && !prev.equals(ext))
throw log.duplicateExternalizerIdFound(
ext.getId(), prev.getClass().getName());
reverse.put(ext.getId(), ext);
}
}
}
interface IdToExternalizerMap {
AdvancedExternalizer get(int key);
void put(int key, AdvancedExternalizer value);
void clear();
}
private static final class IdToExternalizerArrayMap implements IdToExternalizerMap {
private AdvancedExternalizer[] values;
private int count;
private IdToExternalizerArrayMap(int initialCapacity) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity must be > 0");
}
if (initialCapacity < 16) {
initialCapacity = 16;
} else {
// round up
final int c = Integer.highestOneBit(initialCapacity) - 1;
initialCapacity = Integer.highestOneBit(initialCapacity + c);
}
values = new AdvancedExternalizer[initialCapacity];
}
/**
* Get a value from the map.
*
* @param key the key
* @return the map value at the given key, or null if it's not found
*/
public AdvancedExternalizer get(int key) {
final AdvancedExternalizer[] values = this.values;
return values[key];
}
/**
* Put a value into the map. Any previous mapping is discarded silently.
*
* @param key the key
* @param value the value to store
*/
public void put(int key, AdvancedExternalizer value) {
final AdvancedExternalizer[] values = this.values;
if (values[key] == null) {
values[key] = value;
if (++count > values.length)
resize();
}
}
private void resize() {
final AdvancedExternalizer[] oldValues = values;
final int oldsize = oldValues.length;
if (oldsize >= 0x40000000) {
throw new IllegalStateException("Table full");
}
final int newsize = oldsize << 1;
final AdvancedExternalizer[] newValues = new AdvancedExternalizer[newsize];
values = newValues;
for (int oi = 0; oi < oldsize; oi ++) {
if (oldValues[oi] != null) {
newValues[oi] = oldValues[oi];
}
}
}
public void clear() {
Arrays.fill(values, null);
count = 0;
}
/**
* Get a string summary representation of this map.
*
* @return a string representation
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Map length = ").append(values.length).append(", count = ").append(count).append('\n');
for (int i = 0; i < values.length; i ++) {
builder.append('[').append(i).append("] = ");
if (values[i] != null) {
builder.append("{ ").append(values[i]).append("}");
} else {
builder.append("(blank)");
}
builder.append('\n');
}
return builder.toString();
}
}
private static final class IdToExternalizerHashMap implements IdToExternalizerMap {
private AdvancedExternalizer[] values;
private int[] keys;
private int count;
private int resizeCount;
private IdToExternalizerHashMap(int initialCapacity, final float loadFactor) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity must be > 0");
}
if (loadFactor <= 0.0f || loadFactor >= 1.0f) {
throw new IllegalArgumentException("loadFactor must be > 0.0 and < 1.0");
}
if (initialCapacity < 16) {
initialCapacity = 16;
} else {
// round up
final int c = Integer.highestOneBit(initialCapacity) - 1;
initialCapacity = Integer.highestOneBit(initialCapacity + c);
}
keys = new int[initialCapacity];
values = new AdvancedExternalizer[initialCapacity];
resizeCount = (int) ((double) initialCapacity * (double) loadFactor);
}
/**
* Get a value from the map.
*
* @param key the key
* @return the map value at the given key, or null if it's not found
*/
public AdvancedExternalizer get(int key) {
final int[] keys = this.keys;
final AdvancedExternalizer[] values = this.values;
final int mask = keys.length - 1;
int hc = key & mask;
int k;
for (;;) {
k = keys[hc];
if (k == key) {
return values[hc];
}
if (values[hc] == null) {
// not found
return null;
}
hc = (hc + 1) & mask;
}
}
/**
* Put a value into the map. Any previous mapping is discarded silently.
*
* @param key the key
* @param value the value to store
*/
public void put(int key, AdvancedExternalizer value) {
final int[] keys = this.keys;
final int mask = keys.length - 1;
final AdvancedExternalizer[] values = this.values;
int k;
int hc = key & mask;
for (int idx = hc;; idx = hc++ & mask) {
k = keys[idx];
if (values[idx] == null) {
keys[idx] = key;
values[idx] = value;
if (++count > resizeCount) {
resize();
}
return;
}
if (k == key) {
values[idx] = value;
return;
}
}
}
private void resize() {
final int[] oldKeys = keys;
final int oldsize = oldKeys.length;
final AdvancedExternalizer[] oldValues = values;
if (oldsize >= 0x40000000) {
throw new IllegalStateException("Table full");
}
final int newsize = oldsize << 1;
final int mask = newsize - 1;
final int[] newKeys = new int[newsize];
final AdvancedExternalizer[] newValues = new AdvancedExternalizer[newsize];
keys = newKeys;
values = newValues;
if ((resizeCount <<= 1) == 0) {
resizeCount = Integer.MAX_VALUE;
}
for (int oi = 0; oi < oldsize; oi ++) {
final int key = oldKeys[oi];
if (oldValues[oi] != null) {
int ni = key & mask;
for (;;) {
if (newValues[ni] == null) {
// found
newKeys[ni] = key;
newValues[ni] = oldValues[oi];
break;
}
ni = (ni + 1) & mask;
}
}
}
}
public void clear() {
Arrays.fill(keys, 0);
count = 0;
}
/**
* Get a string summary representation of this map.
*
* @return a string representation
*/
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Map length = ").append(keys.length).append(", count = ").append(count).append(", resize count = ").append(resizeCount).append('\n');
for (int i = 0; i < keys.length; i ++) {
builder.append('[').append(i).append("] = ");
if (values[i] != null) {
final int hc = keys[i];
builder.append("{ ").append(keys[i]).append(" (hash ").append(hc).append(", modulus ").append(hc % keys.length).append(") => ").append(values[i]).append(" }");
} else {
builder.append("(blank)");
}
builder.append('\n');
}
return builder.toString();
}
}
}