/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.dom.persistent; import net.jcip.annotations.NotThreadSafe; import org.exist.dom.QName; import org.exist.util.hashtable.AbstractHashSet; import java.util.Iterator; import java.util.Objects; /** * A pool for QNames. This is a temporary pool for QName objects to avoid * allocating the same QName multiple times. If the pool is full, it will just be * cleared. * * @author wolf */ @NotThreadSafe public class QNamePool extends AbstractHashSet<QName> { private static final int DEFAULT_POOL_SIZE = 512; private QName[] values; public QNamePool() { super(DEFAULT_POOL_SIZE); values = new QName[tabSize]; } /** * @param size The size of the QName pool */ public QNamePool(final int size) { super(size); values = new QName[tabSize]; } /** * Return a QName object for the given local name, namespace and * prefix. Return null if the QName has not yet been added to the pool. * * @param type * @param namespaceURI * @param localName * @param prefix * @return QName object */ public final QName get(final byte type, final String namespaceURI, final String localName, final String prefix) { int idx = hashCode(localName, namespaceURI, prefix, type) % tabSize; if(idx < 0) { idx *= -1; } if(values[idx] == null) { return null; // key does not exist } else if(equals(values[idx], localName, namespaceURI, prefix, type)) { return values[idx]; //no hash-collision } else { //hash-collision rehash final int rehashVal = rehash(idx); for(int i = 0; i < tabSize; i++) { idx = (idx + rehashVal) % tabSize; if(values[idx] == null) { return null; // key not found } else if(equals(values[idx], localName, namespaceURI, prefix, type)) { return values[idx]; } } return null; } } /** * Add a QName, consisting of namespace, local name and prefix, to the * pool. */ public final QName add(final byte type, final String namespaceURI, final String localName, final String prefix) { final QName qn = new QName(localName, namespaceURI, prefix, type); try { return insert(qn); } catch(final HashSetOverflowException e) { clear(); try { return insert(qn); } catch(final HashSetOverflowException e1) { throw new RuntimeException(e1); } } } private void clear() { // just clear the pool and try again values = new QName[tabSize]; items = 0; } private QName insert(final QName value) throws HashSetOverflowException { if(value == null) { throw new IllegalArgumentException("Illegal value: null"); } int idx = hashCode(value.getLocalPart(), value.getNamespaceURI(), value.getPrefix(), value.getNameType()) % tabSize; if(idx < 0) { idx *= -1; } int bucket = -1; // look for an empty bucket if(values[idx] == null) { values[idx] = value; ++items; return values[idx]; } else if(values[idx] == REMOVED) { // remember the bucket, but continue to check // for duplicate keys bucket = idx; } else if(values[idx].equals(value)) { // duplicate value return values[idx]; } //System.out.println("Hash collision: " + value + " with " + values[idx]); final int rehashVal = rehash(idx); for(int i = 0; i < tabSize; i++) { idx = (idx + rehashVal) % tabSize; if(values[idx] == REMOVED) { bucket = idx; } else if(values[idx] == null) { if(bucket > -1) { // store key into the empty bucket first found idx = bucket; } values[idx] = value; ++items; return values[idx]; } else if(values[idx].equals(value)) { // duplicate value return values[idx]; } } // should never happen, but just to be sure: // if the key has not been inserted yet, do it now if(bucket > -1) { values[bucket] = value; ++items; return values[bucket]; } else { throw new HashSetOverflowException(); } } private int rehash(final int iVal) { int retVal = (iVal + iVal / 2) % tabSize; if(retVal == 0) { retVal = 1; } return retVal; } /** * Used to calculate a hashCode for a QName * <p/> * This varies from {@see org.exist.dom.QName#hashCode()} in so far * as it also includes the prefix in the hash calculation * * @param localPart * @param namespaceURI * @param prefix * @param nameType */ private static int hashCode(final String localPart, final String namespaceURI, final String prefix, final byte nameType) { int h = nameType + 31 + localPart.hashCode(); h += 31 * h + namespaceURI.hashCode(); h += 31 * h + (prefix == null ? 1 : prefix.hashCode()); return h; } /** * Used to calculate equality for a QName and it's constituent components * <p/> * This varies from {@see org.exist.dom.QName#equals(Object)} in so far * as it also includes the prefix in the equality test * * @param qname The QName to check equality against the other* * @param otherLocalPart * @param otherNamespaceURI * @param otherPrefix * @param otherNameType */ private static boolean equals(final QName qname, final String otherLocalPart, final String otherNamespaceURI, final String otherPrefix, final byte otherNameType) { return qname.getNameType() == otherNameType && qname.getNamespaceURI().equals(otherNamespaceURI) && qname.getLocalPart().equals(otherLocalPart) && Objects.equals(qname.getPrefix(), otherPrefix); } @Override public Iterator<QName> iterator() { throw new UnsupportedOperationException(); } }