/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/
/*
* Copyright (c) 2002-2004 Extreme! Lab, Indiana University. All rights reserved.
*
* This software is open source. See the bottom of this file for the licence.
*
* $Id: MXParserCachingStrings.java,v 1.11 2006/10/23 13:36:27 aslom Exp $
*/
package org.xmlpull.mxp1;
import java.io.Reader;
import org.xmlpull.v1.XmlPullParserException;
/**
* Extend MXP parser to use string cache of char[] to interned String
* <p>
* NOTE: it is not non-validaint parser as there is no supporting internal DTD
* parsing no full XML 1.0 (or 1.1) character classes are supported.
*
* @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander
* Slominski</a>
*/
public class MXParserCachingStrings extends MXParser implements Cloneable {
protected final static boolean CACHE_STATISTICS = false;
protected final static boolean TRACE_SIZING = false;
protected final static int INITIAL_CAPACITY = 13;
private static final boolean keysAreEqual(char[] a, int astart,
int alength, char[] b, int bstart, int blength) {
if (alength != blength) {
return false;
} else {
for (int i = 0; i < alength; i++) {
if (a[astart + i] != b[bstart + i]) {
return false;
}
}
return true;
}
}
protected int cacheStatCalls;
protected int cacheStatWalks;
protected int cacheStatResets;
protected int cacheStatRehash;
/** NOTE: implemented as integers and not flot to allow to work on J2ME. */
protected static final int CACHE_LOAD = 77; // in % ie. 77% == 77/100
protected int cacheEntriesCount;
protected int cacheEntriesThreshold;
// entries are kept in a simple linear list
protected char[][] keys;
// private boolean global;
// // as it is shared state ALL access to those varaibles must be
// synchronized!
// // global cache variables shadow per-instance variables for maximum
// performance
// // as cache is alway growing the access is optimized for really fast
// unsychronized lookups
// private static int globalCacheEntriesCount;
// private static int globalCacheEntriesThreshold;
// private static char[][] globalKeys;
// private static String[] globalValues;
protected String[] values;
public MXParserCachingStrings() {
super();
allStringsInterned = true;
initStringCache();
}
@Override
public Object clone() throws CloneNotSupportedException {
if (reader != null) {
if (!(reader instanceof Cloneable)) {
throw new CloneNotSupportedException(
"reader used in parser must implement Cloneable!");
}
}
final MXParserCachingStrings cloned = (MXParserCachingStrings) super
.clone();
// protected Reader reader;
if (reader != null) {
// why Cloneable has no clone() inside? good question and needs for
// stupid CloneableReader ...
// cloned.reader = (Reader) ((Cloneable)reader).clone(); //BING BONG
// doe snot work ...
// use reflection to call clone() -- this is getting ugly!!!!
// more background on this in
// http://www.artima.com/intv/issues3.html "The clone Dilemma"
try {
final Object o = reader.getClass().getMethod("clone", null)
.invoke(reader, null);
cloned.reader = (Reader) o;
} catch (final Exception e) {
// throw new
// CloneNotSupportedException("failed to call clone() on reader "+reader+e);
final CloneNotSupportedException ee = new CloneNotSupportedException(
"failed to call clone() on reader " + reader + ":" + e);
ee.initCause(e);
throw ee;
}
}
// NOTE: "A clone of a multidimensional array is shallow, which is to
// say that
// "it creates only a single new array. Subarrays are shared, ..."
// http://java.sun.com/docs/books/jls/second_edition/html/arrays.doc.html#64347
// protected char[][] keys;
if (keys != null) {
// TODO REVISIT: it seems cloneCCArr() is not needed
cloned.keys = keys.clone();
}
// protected String[] values;
if (values != null) {
cloned.values = values.clone();
}
// ---- base class
// protected char[] elRawName[];
if (elRawName != null) {
cloned.elRawName = cloneCCArr(elRawName);
}
// protected int elRawNameEnd[];
if (elRawNameEnd != null) {
cloned.elRawNameEnd = elRawNameEnd.clone();
}
// protected int elRawNameLine[];
if (elRawNameLine != null) {
cloned.elRawNameLine = elRawNameLine.clone();
}
// protected String elName[];
if (elName != null) {
cloned.elName = elName.clone();
}
// protected String elPrefix[];
if (elPrefix != null) {
cloned.elPrefix = elPrefix.clone();
}
// protected String elUri[];
if (elUri != null) {
cloned.elUri = elUri.clone();
}
// protected int elNamespaceCount[];
if (elNamespaceCount != null) {
cloned.elNamespaceCount = elNamespaceCount.clone();
}
// protected String attributeName[];
if (attributeName != null) {
cloned.attributeName = attributeName.clone();
}
// protected int attributeNameHash[];
if (attributeNameHash != null) {
cloned.attributeNameHash = attributeNameHash.clone();
}
// protected String attributePrefix[];
if (attributePrefix != null) {
cloned.attributePrefix = attributePrefix.clone();
}
// protected String attributeUri[];
if (attributeUri != null) {
cloned.attributeUri = attributeUri.clone();
}
// protected String attributeValue[];
if (attributeValue != null) {
cloned.attributeValue = attributeValue.clone();
}
// protected String namespacePrefix[];
if (namespacePrefix != null) {
cloned.namespacePrefix = namespacePrefix.clone();
}
// protected int namespacePrefixHash[];
if (namespacePrefixHash != null) {
cloned.namespacePrefixHash = namespacePrefixHash.clone();
}
// protected String namespaceUri[];
if (namespaceUri != null) {
cloned.namespaceUri = namespaceUri.clone();
}
// protected String entityName[];
if (entityName != null) {
cloned.entityName = entityName.clone();
}
// protected char[] entityNameBuf[];
if (entityNameBuf != null) {
cloned.entityNameBuf = cloneCCArr(entityNameBuf);
}
// protected int entityNameHash[];
if (entityNameHash != null) {
cloned.entityNameHash = entityNameHash.clone();
}
// protected char[] entityReplacementBuf[];
if (entityReplacementBuf != null) {
cloned.entityReplacementBuf = cloneCCArr(entityReplacementBuf);
}
// protected String entityReplacement[];
if (entityReplacement != null) {
cloned.entityReplacement = entityReplacement.clone();
}
// protected char buf[];
if (buf != null) {
cloned.buf = buf.clone();
}
// protected char pc[];
if (pc != null) {
cloned.pc = pc.clone();
}
// protected char[] charRefOneCharBuf;
if (charRefOneCharBuf != null) {
cloned.charRefOneCharBuf = charRefOneCharBuf.clone();
}
return cloned;
}
private char[][] cloneCCArr(char[][] ccarr) {
final char[][] cca = ccarr.clone();
for (int i = 0; i < cca.length; i++) {
if (cca[i] != null) {
cca[i] = cca[i].clone();
}
}
return cca;
}
/**
* Hook to GC finalization to print statistics about pool cache impl. perf.
*/
@Override
public void finalize() {
if (CACHE_STATISTICS) {
if (cacheStatCalls > 0) {
System.out.println("statistics: average walk:" + cacheStatWalks
+ "/" + cacheStatCalls + " ("
+ ((double) cacheStatWalks) / cacheStatCalls + ")"
+ " resets=" + cacheStatResets + " rehash="
+ cacheStatRehash);
} else {
System.out.println("statistics: cache was not used");
}
}
}
@Override
public boolean getFeature(String name) {
if (FEATURE_NAMES_INTERNED.equals(name)) {
return allStringsInterned;
} else {
return super.getFeature(name);
}
}
@SuppressWarnings("unused")
protected void initStringCache() {
if (keys == null) {
// int initialCapacity = 13;
if (INITIAL_CAPACITY < 0) {
throw new IllegalArgumentException("Illegal initial capacity: "
+ INITIAL_CAPACITY);
}
if (CACHE_LOAD < 0 || CACHE_LOAD > 99) {
throw new IllegalArgumentException("Illegal load factor: "
+ CACHE_LOAD);
}
cacheEntriesThreshold = ((INITIAL_CAPACITY * CACHE_LOAD) / 100);
if (cacheEntriesThreshold >= INITIAL_CAPACITY) {
throw new RuntimeException(
"internal error: threshold must be less than capacity: "
+ INITIAL_CAPACITY);
}
keys = new char[INITIAL_CAPACITY][];
values = new String[INITIAL_CAPACITY];
cacheEntriesCount = 0;
}
}
/**
* If feature name interning is enabled then this funtion MUST return
* interned string.
*/
@Override
protected String newString(char[] cbuf, int off, int len) {
if (allStringsInterned) {
return newStringIntern(cbuf, off, len);
} else {
return super.newString(cbuf, off, len);
}
}
/**
* This is <b>efficient</b> implementation of pool that returns interned
* String based on char[] input.
*/
@Override
protected String newStringIntern(char[] cbuf, int off, int len) {
// return (new String(cbuf, off, len)).intern();
if (CACHE_STATISTICS) {
++cacheStatCalls;
}
if (cacheEntriesCount >= cacheEntriesThreshold) {
rehash();
}
int offset = fastHash(cbuf, off, len) % keys.length;
char[] k = null;
while ((k = keys[offset]) != null
&& !keysAreEqual(k, 0, k.length, cbuf, off, len)) {
offset = (offset + 1) % keys.length;
if (CACHE_STATISTICS) {
++cacheStatWalks;
}
}
if (k != null) {
return values[offset];
} else {
k = new char[len];
System.arraycopy(cbuf, off, k, 0, len);
final String v = new String(k).intern();
keys[offset] = k;
values[offset] = v;
++cacheEntriesCount;
return v;
}
}
private void rehash() {
if (CACHE_STATISTICS) {
++cacheStatRehash;
}
final int newSize = 2 * keys.length + 1;
cacheEntriesThreshold = ((newSize * CACHE_LOAD) / 100);
if (cacheEntriesThreshold >= newSize) {
throw new RuntimeException(
"internal error: threshold must be less than capacity: "
+ newSize);
}
if (TRACE_SIZING) {
System.err.println("resized " + keys.length + " => " + newSize);
}
final char[][] newKeys = new char[newSize][];
final String[] newValues = new String[newSize];
for (int i = 0; i < keys.length; i++) {
final char[] k = keys[i];
keys[i] = null;
final String v = values[i];
values[i] = null;
if (k != null) {
int newOffset = fastHash(k, 0, k.length) % newSize;
char[] newk = null;
while ((newk = newKeys[newOffset]) != null) {
if (keysAreEqual(newk, 0, newk.length, k, 0, k.length)) {
throw new RuntimeException(
"internal cache error: duplicated keys: "
+ new String(newk) + " and "
+ new String(k));
}
newOffset = (newOffset + 1) % newSize;
}
newKeys[newOffset] = k;
newValues[newOffset] = v;
}
}
keys = newKeys;
values = newValues;
}
@Override
protected void resetStringCache() {
// System.out.println("reset string cache()");
if (CACHE_STATISTICS) {
++cacheStatResets;
}
initStringCache();
}
/**
* This allows to change name iterning property in this enhanced impl.
*/
@Override
public void setFeature(String name, boolean state)
throws XmlPullParserException {
if (FEATURE_NAMES_INTERNED.equals(name)) {
if (eventType != START_DOCUMENT) {
throw new XmlPullParserException(
"interning names feature can only be changed before parsing",
this, null);
}
allStringsInterned = state;
if (state == false) {
if (keys != null) {
resetStringCache();
}
}
} else {
super.setFeature(name, state);
}
}
}
/*
* Indiana University Extreme! Lab Software License, Version 1.2
*
* Copyright (c) 2002-2004 The Trustees of Indiana University. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1) All redistributions of source code must retain the above copyright notice,
* the list of authors in the original source code, this list of conditions and
* the disclaimer listed in this license;
*
* 2) All redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the disclaimer listed in this license in
* the documentation and/or other materials provided with the distribution;
*
* 3) Any documentation included with all redistributions must include the
* following acknowledgement:
*
* "This product includes software developed by the Indiana University Extreme!
* Lab. For further information please visit http://www.extreme.indiana.edu/"
*
* Alternatively, this acknowledgment may appear in the software itself, and
* wherever such third-party acknowledgments normally appear.
*
* 4) The name "Indiana University" or "Indiana University Extreme! Lab" shall
* not be used to endorse or promote products derived from this software without
* prior written permission from Indiana University. For written permission,
* please contact http://www.extreme.indiana.edu/.
*
* 5) Products derived from this software may not use "Indiana
* University" name nor may "Indiana University" appear in their name, without
* prior written permission of the Indiana University.
*
* Indiana University provides no reassurances that the source code provided
* does not infringe the patent or any other intellectual property rights of any
* other entity. Indiana University disclaims any liability to any recipient for
* claims brought by any other entity based on infringement of intellectual
* property rights or otherwise.
*
* LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
* WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
* NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
* INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
* INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
* "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
* LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
* ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
* GENERATED USING SOFTWARE.
*/