001: package com.jclark.xml.tok;
002:
003: /**
004: * Caches conversion of byte subarrays into Strings.
005: * @version $Revision: 1.3 $ $Date: 1998/02/17 04:24:24 $
006: */
007: public class StringConversionCache {
008:
009: private static final int DEFAULT_CACHE_SIZE = 1009;
010: private static final int CONVERSION_BUF_SIZE = 64;
011:
012: private static class Bucket {
013: /* JDK 1.1 javac incorrectly fails to allows access to private members
014: of inner class from enclosing class. */
015: /* private */byte[] bytes;
016: /* private */String string;
017: /* private */Bucket nextMru;
018: /* private */Bucket prevMru;
019: /* private */Bucket nextBucket;
020: /* private */Bucket prevBucket;
021:
022: /* private */final boolean matches(byte[] buf, int start,
023: int end) {
024: if (end - start != bytes.length)
025: return false;
026: for (int i = 0; i < bytes.length; i++)
027: if (bytes[i] != buf[start++])
028: return false;
029: return true;
030: }
031: }
032:
033: /* JDK 1.1 javac can't handle blank finals correcltly when there are
034: inner classes. */
035: private/* final */Bucket[] table;
036: private/* final */Bucket mru = new Bucket();
037: private char[] conversionBuf = new char[CONVERSION_BUF_SIZE];
038: private Encoding enc;
039: private/* final */int minBPC;
040: private int cacheFree;
041: private static final double LOAD_FACTOR = 0.7;
042:
043: /**
044: * Create a cache of the specified size
045: * for converting byte subarrays in the specified encoding
046: * into Strings.
047: */
048: public StringConversionCache(Encoding enc, int cacheSize) {
049: this .enc = enc;
050: minBPC = enc.getMinBytesPerChar();
051: table = new Bucket[cacheSize];
052: cacheFree = (int) (table.length * LOAD_FACTOR);
053: mru.nextMru = mru;
054: mru.prevMru = mru;
055: }
056:
057: /**
058: * Create a cache of the default size for converting byte subarrays
059: * in the specified encoding into Strings.
060: */
061: public StringConversionCache(Encoding enc) {
062: this (enc, DEFAULT_CACHE_SIZE);
063: }
064:
065: /**
066: * Changes the encoding for the cache.
067: * This cannot be called after any calls to <code>convert</code>
068: * have been made.
069: */
070: public void setEncoding(Encoding enc) {
071: if (cacheFree != (int) (table.length * LOAD_FACTOR))
072: throw new IllegalStateException("cache already used");
073: this .enc = enc;
074: if (minBPC != enc.getMinBytesPerChar())
075: throw new IllegalStateException(
076: "change to incompatible encoding");
077: }
078:
079: /**
080: * Convert a byte subarray into a String.
081: * If <code>permanent</code> is true, then this conversion will
082: * be kept in the cache in preference to any non-permanent conversions.
083: */
084: public String convert(byte[] buf, int start, int end,
085: boolean permanent) {
086: int i = hash(buf, start, end) % table.length;
087: Bucket bucket = table[i];
088:
089: if (bucket != null) {
090: for (Bucket p = bucket.nextBucket; p != bucket; p = p.nextBucket) {
091: if (p.matches(buf, start, end)) {
092: if (p.nextMru != null) {
093: unlinkMru(p);
094: if (permanent)
095: p.nextMru = null;
096: else
097: linkMru(mru, p);
098: }
099: return p.string;
100: }
101: }
102: } else {
103: bucket = new Bucket();
104: bucket.nextBucket = bucket.prevBucket = bucket;
105: table[i] = bucket;
106: }
107: Bucket tem;
108: if (cacheFree <= 0) {
109: if (mru.nextMru == mru) {
110: for (int j = 0; j < table.length; j++) {
111: Bucket h = table[j];
112: if (h != null) {
113: for (Bucket p = h.nextBucket; p != h; p = p.nextBucket) {
114: linkMru(mru, p);
115: }
116: h.nextBucket = h.prevBucket = h;
117: }
118: }
119: }
120: tem = mru.prevMru;
121: unlinkMru(tem);
122: tem.prevBucket.nextBucket = tem.nextBucket;
123: tem.nextBucket.prevBucket = tem.prevBucket;
124: if (permanent)
125: tem.nextMru = null;
126: else
127: linkMru(mru, tem);
128: } else {
129: --cacheFree;
130: tem = new Bucket();
131: if (!permanent)
132: linkMru(mru, tem);
133: }
134:
135: if (end - start > conversionBuf.length * minBPC)
136: conversionBuf = new char[(end - start) / minBPC];
137:
138: tem.string = new String(conversionBuf, 0, enc.convert(buf,
139: start, end, conversionBuf, 0));
140:
141: byte[] bytes = new byte[end - start];
142: System.arraycopy(buf, start, bytes, 0, bytes.length);
143: tem.bytes = bytes;
144:
145: tem.nextBucket = bucket.nextBucket;
146: tem.nextBucket.prevBucket = tem;
147: tem.prevBucket = bucket;
148: bucket.nextBucket = tem;
149: return tem.string;
150: }
151:
152: private static final int hash(byte buf[], int start, int end) {
153: int h = 0;
154: while (start != end)
155: h += (h << 5) + (buf[start++] & 0xFF);
156: return h & 0x7FFFFFFF;
157: }
158:
159: static private final void linkMru(Bucket after, Bucket s) {
160: s.nextMru = after.nextMru;
161: s.nextMru.prevMru = s;
162: s.prevMru = after;
163: after.nextMru = s;
164: }
165:
166: static private final void unlinkMru(Bucket s) {
167: s.prevMru.nextMru = s.nextMru;
168: s.nextMru.prevMru = s.prevMru;
169: }
170:
171: }
|