blob: a3c4adfa676667c98976762aa3680d79b0eacf25 [file] [log] [blame]
/*
* Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.glassfish.jaxb.runtime.v2.util;
import org.glassfish.jaxb.runtime.v2.runtime.Name;
import javax.xml.namespace.QName;
import java.util.*;
/**
* Map keyed by {@link QName}.
*
* This specialized map allows a look up operation without constructing
* a new QName instance, for a performance reason. This {@link Map} assumes
* that both namespace URI and local name are {@link String#intern() intern}ed.
*
* @since JAXB 2.0
*/
public final class QNameMap<V> {
/**
* The default initial capacity - MUST be a power of two.
*/
private static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<V>[] table = new Entry[DEFAULT_INITIAL_CAPACITY];
/**
* The number of key-value mappings contained in this identity hash map.
*/
transient int size;
/**
* The next size value at which to resize . Taking it as
* MAXIMUM_CAPACITY
* @serial
*/
private int threshold;
/**
* The load factor used when none specified in constructor.
**/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* Gives an entrySet view of this map
*/
private Set<Entry<V>> entrySet = null;
public QNameMap() {
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
}
/**
* Associates the specified value with the specified keys in this map.
* If the map previously contained a mapping for this key, the old
* value is replaced.
*
* @param namespaceUri First key with which the specified value is to be associated.
* @param localname Second key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
*
*/
public void put(String namespaceUri,String localname, V value ) {
//keys cannot be null
assert localname !=null;
assert namespaceUri !=null;
// keys must be interned
assert localname == localname.intern();
assert namespaceUri == namespaceUri.intern();
int hash = hash(localname);
int i = indexFor(hash, table.length);
for (Entry<V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && localname == e.localName && namespaceUri==e.nsUri) {
e.value = value;
return;
}
}
addEntry(hash, namespaceUri,localname, value, i);
}
public void put(QName name, V value ) {
put(name.getNamespaceURI(),name.getLocalPart(),value);
}
public void put(Name name, V value ) {
put(name.nsUri,name.localName,value);
}
/**
* Returns the value to which the specified keys are mapped in this QNameMap,
* or {@code null} if the map contains no mapping for this key.
*
* @param nsUri the namespaceUri key whose associated value is to be returned.
* @param localPart the localPart key whose associated value is to be returned.
* @return the value to which this map maps the specified set of keya, or
* {@code null} if the map contains no mapping for this set of keys.
* @see #put(String,String, Object)
*/
public V get( String nsUri, String localPart ) {
Entry<V> e = getEntry(nsUri,localPart);
if(e==null) return null;
else return e.value;
}
public V get( QName name ) {
return get(name.getNamespaceURI(),name.getLocalPart());
}
/**
* Returns the number of keys-value mappings in this map.
*
* @return the number of keys-value mappings in this map.
*/
public int size() {
return size;
}
/**
* Copies all of the mappings from the specified map to this map
* These mappings will replace any mappings that
* this map had for any of the keys currently in the specified map.
*
* @param map mappings to be stored in this map.
*
*/
public QNameMap<V> putAll(QNameMap<? extends V> map) {
int numKeysToBeAdded = map.size();
if (numKeysToBeAdded == 0)
return this;
if (numKeysToBeAdded > threshold) {
int targetCapacity = numKeysToBeAdded;
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
for( Entry<? extends V> e : map.entrySet() )
put(e.nsUri,e.localName,e.getValue());
return this;
}
/**
* Returns a hash value for the specified object.The hash value is computed
* for the localName.
*/
private static int hash(String x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Returns index for hash code h.
*/
private static int indexFor(int h, int length) {
return h & (length-1);
}
/**
* Add a new entry with the specified keys, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
*/
private void addEntry(int hash, String nsUri, String localName, V value, int bucketIndex) {
Entry<V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, nsUri, localName, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*/
private void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = newCapacity;
}
/**
* Transfer all entries from current table to newTable.
*/
private void transfer(Entry<V>[] newTable) {
Entry<V>[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
/**
* Returns one random item in the map.
* If this map is empty, return null.
*
* <p>
* This method is useful to obtain the value from a map that only contains one element.
*/
public Entry<V> getOne() {
for( Entry<V> e : table ) {
if(e!=null)
return e;
}
return null;
}
public Collection<QName> keySet() {
Set<QName> r = new HashSet<>();
for (Entry<V> e : entrySet()) {
r.add(e.createQName());
}
return r;
}
private abstract class HashIterator<E> implements Iterator<E> {
Entry<V> next; // next entry to return
int index; // current slot
HashIterator() {
Entry<V>[] t = table;
int i = t.length;
Entry<V> n = null;
if (size != 0) { // advance to first entry
while (i > 0 && (n = t[--i]) == null) {}
}
next = n;
index = i;
}
@Override
public boolean hasNext() {
return next != null;
}
Entry<V> nextEntry() {
Entry<V> e = next;
if (e == null)
throw new NoSuchElementException();
Entry<V> n = e.next;
Entry<V>[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i];
index = i;
next = n;
return e;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public boolean containsKey(String nsUri,String localName) {
return getEntry(nsUri,localName)!=null;
}
/**
* Returns true if this map is empty.
*/
public boolean isEmpty() {
return size == 0;
}
public static final class Entry<V> {
/** The namespace URI. */
public final String nsUri;
/** The localPart. */
public final String localName;
V value;
final int hash;
Entry<V> next;
/**
* Create new entry.
*/
Entry(int h, String nsUri, String localName, V v, Entry<V> n) {
value = v;
next = n;
this.nsUri = nsUri;
this.localName = localName;
hash = h;
}
/**
* Creates a new QName object from {@link #nsUri} and {@link #localName}.
*/
public QName createQName() {
return new QName(nsUri,localName);
}
public V getValue() {
return value;
}
public V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Entry))
return false;
Entry e = (Entry)o;
String k1 = nsUri;
String k2 = e.nsUri;
String k3 = localName;
String k4 = e.localName;
if (k1 == k2 || (k1 != null && k1.equals(k2)) &&
(k3 == k4 ||(k3 !=null && k3.equals(k4)))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
@Override
public int hashCode() {
return ( localName.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
@Override
public String toString() {
return '"'+nsUri +"\",\"" +localName + "\"=" + getValue();
}
}
public Set<Entry<V>> entrySet() {
Set<Entry<V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
private Iterator<Entry<V>> newEntryIterator() {
return new EntryIterator();
}
private class EntryIterator extends HashIterator<Entry<V>> {
@Override
public Entry<V> next() {
return nextEntry();
}
}
private class EntrySet extends AbstractSet<Entry<V>> {
@Override
public Iterator<Entry<V>> iterator() {
return newEntryIterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Entry))
return false;
Entry<V> e = (Entry<V>) o;
Entry<V> candidate = getEntry(e.nsUri,e.localName);
return candidate != null && candidate.equals(e);
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public int size() {
return size;
}
}
private Entry<V> getEntry(String nsUri,String localName) {
// strings must be interned
assert nsUri==nsUri.intern();
assert localName==localName.intern();
int hash = hash(localName);
int i = indexFor(hash, table.length);
Entry<V> e = table[i];
while (e != null && !(localName == e.localName && nsUri == e.nsUri))
e = e.next;
return e;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append('{');
for( Entry<V> e : entrySet() ) {
if(buf.length()>1)
buf.append(',');
buf.append('[');
buf.append(e);
buf.append(']');
}
buf.append('}');
return buf.toString();
}
}