blob: fb5c23a2b1f13235f7d72ab3fc1ce7c695c73fb7 [file] [log] [blame]
package org.codehaus.jackson.map.deser.impl;
import java.util.*;
import org.codehaus.jackson.map.deser.SettableBeanProperty;
/**
* Helper class used for storing mapping from property name to
* {@link SettableBeanProperty} instances.
*<p>
* Note that this class is used instead of generic {@link java.util.HashMap}
* is performance: although default implementation is very good for generic
* use cases, it can still be streamlined a bit for specific use case
* we have.
*
* @since 1.7
*/
public final class BeanPropertyMap
{
private final Bucket[] _buckets;
private final int _hashMask;
private final int _size;
public BeanPropertyMap(Collection<SettableBeanProperty> properties)
{
_size = properties.size();
int bucketCount = findSize(_size);
_hashMask = bucketCount-1;
Bucket[] buckets = new Bucket[bucketCount];
for (SettableBeanProperty property : properties) {
String key = property.getName();
int index = key.hashCode() & _hashMask;
buckets[index] = new Bucket(buckets[index], key, property);
}
_buckets = buckets;
}
public void assignIndexes()
{
// order is arbitrary, but stable:
int index = 0;
for (Bucket bucket : _buckets) {
while (bucket != null) {
bucket.value.assignIndex(index++);
bucket = bucket.next;
}
}
}
private final static int findSize(int size)
{
// For small enough results (32 or less), we'll require <= 50% fill rate; otherwise 80%
int needed = (size <= 32) ? (size + size) : (size + (size >> 2));
int result = 2;
while (result < needed) {
result += result;
}
return result;
}
/*
/**********************************************************
/* Public API
/**********************************************************
*/
public int size() { return _size; }
/**
* Accessor for traversing over all contained properties.
*/
public Iterator<SettableBeanProperty> allProperties() {
return new IteratorImpl(_buckets);
}
public SettableBeanProperty find(String key)
{
int index = key.hashCode() & _hashMask;
Bucket bucket = _buckets[index];
// Let's unroll first lookup since that is null or match in 90+% cases
if (bucket == null) {
return null;
}
// Primarily we do just identity comparison as keys should be interned
if (bucket.key == key) {
return bucket.value;
}
while ((bucket = bucket.next) != null) {
if (bucket.key == key) {
return bucket.value;
}
}
// Do we need fallback for non-interned Strings?
return _findWithEquals(key, index);
}
/**
* Specialized method that can be used to replace an existing entry
* (note: entry MUST exist; otherwise exception is thrown) with
* specified replacement.
*/
public void replace(SettableBeanProperty property)
{
String name = property.getName();
int index = name.hashCode() & (_buckets.length-1);
/* This is bit tricky just because buckets themselves
* are immutable, so we need to recreate the chain. Fine.
*/
Bucket tail = null;
boolean found = false;
for (Bucket bucket = _buckets[index]; bucket != null; bucket = bucket.next) {
// match to remove?
if (!found && bucket.key.equals(name)) {
found = true;
} else {
tail = new Bucket(tail, bucket.key, bucket.value);
}
}
// Not finding specified entry is error, so:
if (!found) {
throw new NoSuchElementException("No entry '"+property+"' found, can't replace");
}
/* So let's attach replacement in front: useful also because
* it allows replacement even when iterating over entries
*/
_buckets[index] = new Bucket(tail, name, property);
}
/**
* Specialized method for removing specified existing entry.
* NOTE: entry MUST exist, otherwise an exception is thrown.
*
* @since 1.9
*/
public void remove(SettableBeanProperty property)
{
// Mostly this is the same as code with 'replace', just bit simpler...
String name = property.getName();
int index = name.hashCode() & (_buckets.length-1);
Bucket tail = null;
boolean found = false;
// slightly complex just because chain is immutable, must recreate
for (Bucket bucket = _buckets[index]; bucket != null; bucket = bucket.next) {
// match to remove?
if (!found && bucket.key.equals(name)) {
found = true;
} else {
tail = new Bucket(tail, bucket.key, bucket.value);
}
}
if (!found) { // must be found
throw new NoSuchElementException("No entry '"+property+"' found, can't remove");
}
_buckets[index] = tail;
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private SettableBeanProperty _findWithEquals(String key, int index)
{
Bucket bucket = _buckets[index];
while (bucket != null) {
if (key.equals(bucket.key)) {
return bucket.value;
}
bucket = bucket.next;
}
return null;
}
/*
/**********************************************************
/* Helper beans
/**********************************************************
*/
private final static class Bucket
{
public final Bucket next;
public final String key;
public final SettableBeanProperty value;
public Bucket(Bucket next, String key, SettableBeanProperty value)
{
this.next = next;
this.key = key;
this.value = value;
}
}
private final static class IteratorImpl implements Iterator<SettableBeanProperty>
{
/**
* Buckets of the map
*/
private final Bucket[] _buckets;
/**
* Bucket that contains next value to return (if any); null if nothing more to iterate
*/
private Bucket _currentBucket;
/**
* Index of the next bucket in bucket array to check.
*/
private int _nextBucketIndex;
public IteratorImpl(Bucket[] buckets) {
_buckets = buckets;
// need to initialize to point to first entry...
int i = 0;
for (int len = _buckets.length; i < len; ) {
Bucket b = _buckets[i++];
if (b != null) {
_currentBucket = b;
break;
}
}
_nextBucketIndex = i;
}
@Override
public boolean hasNext() {
return _currentBucket != null;
}
@Override
public SettableBeanProperty next()
{
Bucket curr = _currentBucket;
if (curr == null) { // sanity check
throw new NoSuchElementException();
}
// need to advance, too
Bucket b = curr.next;
while (b == null && _nextBucketIndex < _buckets.length) {
b = _buckets[_nextBucketIndex++];
}
_currentBucket = b;
return curr.value;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}