From: Markus Scherer Date: Fri, 6 Jun 2014 20:52:10 +0000 (+0000) Subject: ICU-10932 merge branches/markus/lowmemres to trunk: use ByteBuffer to read .res files... X-Git-Tag: milestone-59-0-1~1873 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b3a6ea1fa3fd7833c97e610f15783bcaac7f7a87;p=icu ICU-10932 merge branches/markus/lowmemres to trunk: use ByteBuffer to read .res files; allocate fewer/smaller objects; ResourceCache maps from resource int to immutable object; getString(key or index) avoid creating ICUResourceBundleImpl per string in many cases X-SVN-Rev: 35815 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUBinary.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUBinary.java index 8856516bd38..155f7712eb5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUBinary.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUBinary.java @@ -242,6 +242,14 @@ public final class ICUBinary } } + /** + * Returns a VersionInfo for the bytes in the compact version integer. + */ + public static VersionInfo getVersionInfoFromCompactInt(int version) { + return VersionInfo.getInstance( + version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); + } + // private variables ------------------------------------------------- /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java index 979840bb60f..5ea67c3f4ec 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundle.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.StringTokenizer; import com.ibm.icu.impl.URLHandler.URLVisitor; +import com.ibm.icu.util.Output; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.UResourceBundle; import com.ibm.icu.util.UResourceBundleIterator; @@ -335,11 +336,10 @@ public class ICUResourceBundle extends UResourceBundle { * @exception MissingResourceException If a resource was not found. */ public ICUResourceBundle getWithFallback(String path) throws MissingResourceException { - ICUResourceBundle result = null; ICUResourceBundle actualBundle = this; // now recurse to pick up sub levels of the items - result = findResourceWithFallback(path, actualBundle, null); + ICUResourceBundle result = findResourceWithFallback(path, actualBundle, null, null); if (result == null) { throw new MissingResourceException( @@ -348,8 +348,8 @@ public class ICUResourceBundle extends UResourceBundle { path, getKey()); } - if ( result.getType() == ICUResourceBundle.STRING && result.getString().equals(NO_INHERITANCE_MARKER)) { - throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER",path,getKey()); + if (result.getType() == STRING && result.getString().equals(NO_INHERITANCE_MARKER)) { + throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey()); } return result; @@ -384,12 +384,33 @@ public class ICUResourceBundle extends UResourceBundle { * @return the resource, or null */ public ICUResourceBundle findWithFallback(String path) { - return findResourceWithFallback(path, this, null); + return findResourceWithFallback(path, this, null, null); + } + public String findStringWithFallback(String path) { + Output outString = new Output(); + findResourceWithFallback(path, this, null, outString); + return outString.value; } // will throw type mismatch exception if the resource is not a string public String getStringWithFallback(String path) throws MissingResourceException { - return getWithFallback(path).getString(); + // Optimized form of getWithFallback(path).getString(); + ICUResourceBundle actualBundle = this; + Output outString = new Output(); + findResourceWithFallback(path, actualBundle, null, outString); + String result = outString.value; + + if (result == null) { + throw new MissingResourceException( + "Can't find resource for bundle " + + this.getClass().getName() + ", key " + getType(), + path, getKey()); + } + + if (result.equals(NO_INHERITANCE_MARKER)) { + throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey()); + } + return result; } /** @@ -776,7 +797,8 @@ public class ICUResourceBundle extends UResourceBundle { } protected static final ICUResourceBundle findResourceWithFallback(String path, - UResourceBundle actualBundle, UResourceBundle requested) { + UResourceBundle actualBundle, UResourceBundle requested, + Output outString) { ICUResourceBundle sub = null; if (requested == null) { requested = actualBundle; @@ -788,8 +810,19 @@ public class ICUResourceBundle extends UResourceBundle { while (base != null) { if (path.indexOf('/') == -1) { // skip the tokenizer + if (outString != null && base instanceof ICUResourceBundleImpl.ResourceTable) { + String s = ((ICUResourceBundleImpl.ResourceTable)base).getStringOrNull(path); + if (s != null) { + outString.value = s; + return null; + } + } sub = (ICUResourceBundle) base.handleGet(path, null, requested); if (sub != null) { + if (outString != null && sub.getType() == STRING) { + outString.value = sub.getString(); // string from alias handling + return null; + } break; } } else { @@ -797,8 +830,12 @@ public class ICUResourceBundle extends UResourceBundle { StringTokenizer st = new StringTokenizer(path, "/"); while (st.hasMoreTokens()) { String subKey = st.nextToken(); - sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested); + sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested, + st.hasMoreTokens() ? null : outString); if (sub == null) { + if (outString != null && outString.value != null) { + return null; + } break; } currentBase = sub; @@ -1182,7 +1219,7 @@ public class ICUResourceBundle extends UResourceBundle { // Get the top bundle of the requested bundle bundle = (ICUResourceBundle)getBundleInstance(bundleName, locale, loaderToUse, false); if (bundle != null) { - sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null); + sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null, null); // TODO // The resPath of the resolved bundle should reflect the resource path // requested by caller. However, overwriting resPath here will affect cached diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java index e7ff0326644..db2a7b20a48 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleImpl.java @@ -1,7 +1,7 @@ /* ******************************************************************************* - * Copyright (C) 2004-2011, International Business Machines Corporation and * - * others. All Rights Reserved. * + * Copyright (C) 2004-2014, International Business Machines Corporation and + * others. All Rights Reserved. ******************************************************************************* */ package com.ibm.icu.impl; @@ -12,7 +12,6 @@ import java.util.Set; import java.util.TreeSet; import com.ibm.icu.util.UResourceBundle; -import com.ibm.icu.util.UResourceBundleIterator; import com.ibm.icu.util.UResourceTypeMismatchException; class ICUResourceBundleImpl extends ICUResourceBundle { @@ -85,23 +84,28 @@ class ICUResourceBundleImpl extends ICUResourceBundle { private static final class ResourceString extends ICUResourceBundleImpl { private String value; public String getString() { - return value; + if (value != null) { + return value; + } + return reader.getString(resource); } ResourceString(ICUResourceBundleReader reader, String key, String resPath, int resource, ICUResourceBundleImpl container) { super(reader, key, resPath, resource, container); - value = reader.getString(resource); + String s = reader.getString(resource); + // Allow the reader cache's SoftReference to do its job. + if (s.length() < ICUResourceBundleReader.LARGE_SIZE / 2) { + value = s; + } } } private static final class ResourceIntVector extends ICUResourceBundleImpl { - private int[] value; public int[] getIntVector() { - return value; + return reader.getIntVector(resource); } ResourceIntVector(ICUResourceBundleReader reader, String key, String resPath, int resource, ICUResourceBundleImpl container) { super(reader, key, resPath, resource, container); - value = reader.getIntVector(resource); } } @@ -113,8 +117,20 @@ class ICUResourceBundleImpl extends ICUResourceBundle { public int getSize() { return value.getSize(); } + @Override + public String getString(int index) { + int res = value.getContainerResource(reader, index); + if (res == RES_BOGUS) { + throw new IndexOutOfBoundsException(); + } + String s = reader.getString(res); + if (s != null) { + return s; + } + return super.getString(index); + } protected int getContainerResource(int index) { - return value.getContainerResource(index); + return value.getContainerResource(reader, index); } protected UResourceBundle createBundleObject(int index, String resKey, HashMap table, UResourceBundle requested, boolean[] isAlias) { @@ -131,11 +147,14 @@ class ICUResourceBundleImpl extends ICUResourceBundle { } private static class ResourceArray extends ResourceContainer { protected String[] handleGetStringArray() { - String[] strings = new String[value.getSize()]; - UResourceBundleIterator iter = getIterator(); - int i = 0; - while (iter.hasNext()) { - strings[i++] = iter.next().getString(); + int length = value.getSize(); + String[] strings = new String[length]; + for (int i = 0; i < length; ++i) { + String s = reader.getString(value.getContainerResource(reader, i)); + if (s == null) { + throw new UResourceTypeMismatchException(""); + } + strings[i] = s; } return strings; } @@ -167,18 +186,18 @@ class ICUResourceBundleImpl extends ICUResourceBundle { } static class ResourceTable extends ResourceContainer { protected String getKey(int index) { - return ((ICUResourceBundleReader.Table)value).getKey(index); + return ((ICUResourceBundleReader.Table)value).getKey(reader, index); } protected Set handleKeySet() { TreeSet keySet = new TreeSet(); ICUResourceBundleReader.Table table = (ICUResourceBundleReader.Table)value; for (int i = 0; i < table.getSize(); ++i) { - keySet.add(table.getKey(i)); + keySet.add(table.getKey(reader, i)); } return keySet; } protected int getTableResource(String resKey) { - return ((ICUResourceBundleReader.Table)value).getTableResource(resKey); + return ((ICUResourceBundleReader.Table)value).getTableResource(reader, resKey); } protected int getTableResource(int index) { return getContainerResource(index); @@ -186,7 +205,7 @@ class ICUResourceBundleImpl extends ICUResourceBundle { protected UResourceBundle handleGetImpl(String resKey, HashMap table, UResourceBundle requested, int[] index, boolean[] isAlias) { - int i = ((ICUResourceBundleReader.Table)value).findTableItem(resKey); + int i = ((ICUResourceBundleReader.Table)value).findTableItem(reader, resKey); if(index != null) { index[0] = i; } @@ -197,12 +216,58 @@ class ICUResourceBundleImpl extends ICUResourceBundle { } protected UResourceBundle handleGetImpl(int index, HashMap table, UResourceBundle requested, boolean[] isAlias) { - String itemKey = ((ICUResourceBundleReader.Table)value).getKey(index); + String itemKey = ((ICUResourceBundleReader.Table)value).getKey(reader, index); if (itemKey == null) { throw new IndexOutOfBoundsException(); } return createBundleObject(index, itemKey, table, requested, isAlias); } + @Override + protected Object handleGetObject(String key) { + // Fast path for common cases: Avoid creating UResourceBundles if possible. + // It would be even better if we could override getString(key)/getStringArray(key), + // so that we know the expected object type, + // but those are final in java.util.ResourceBundle. + int index = ((ICUResourceBundleReader.Table)value).findTableItem(reader, key); + if (index >= 0) { + int res = value.getContainerResource(reader, index); + // getString(key) + String s = reader.getString(res); + if (s != null) { + return s; + } + // getStringArray(key) + ICUResourceBundleReader.Container array = reader.getArray(res); + if (array != null) { + int length = array.getSize(); + String[] strings = new String[length]; + for (int j = 0;; ++j) { + if (j == length) { + return strings; + } + s = reader.getString(array.getContainerResource(reader, j)); + if (s == null) { + // Equivalent to resolveObject(key, requested): + // If this is not a string array, + // then build and return a UResourceBundle. + break; + } + strings[j] = s; + } + } + } + return super.handleGetObject(key); + } + /** + * Returns a String if found, or null if not found or if the key item is not a string. + */ + String getStringOrNull(String key) { + int index = ((ICUResourceBundleReader.Table)value).findTableItem(reader, key); + if (index < 0) { + return null; + } + return reader.getString(value.getContainerResource(reader, index)); + } ResourceTable(ICUResourceBundleReader reader, String key, String resPath, int resource, ICUResourceBundleImpl container) { super(reader, key, resPath, resource, container); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java index 4a8b00e657c..72591fdae18 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceBundleReader.java @@ -1,17 +1,18 @@ /* ******************************************************************************* - * Copyright (C) 2004-2014, International Business Machines Corporation and * - * others. All Rights Reserved. * + * Copyright (C) 2004-2014, International Business Machines Corporation and + * others. All Rights Reserved. ******************************************************************************* */ package com.ibm.icu.impl; -import java.io.BufferedInputStream; -import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.SoftReference; import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import com.ibm.icu.util.ICUException; import com.ibm.icu.util.ICUUncheckedIOException; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.UResourceBundle; @@ -203,13 +204,19 @@ import com.ibm.icu.util.VersionInfo; * (no value restrictions, all values 0..ffff allowed!). * - Vectors of 32-bit words stored as type Integer Vector. */ -public final class ICUResourceBundleReader implements ICUBinary.Authenticate { +public final class ICUResourceBundleReader { /** * File format version that this class understands. * "ResB" */ - private static final byte DATA_FORMAT_ID[] = {(byte)0x52, (byte)0x65, - (byte)0x73, (byte)0x42}; + private static final int DATA_FORMAT = 0x52657342; + private static final class IsAcceptable implements ICUBinary.Authenticate { + // @Override when we switch to Java 6 + public boolean isDataVersionAcceptable(byte formatVersion[]) { + return (formatVersion[0] == 1 || formatVersion[0] == 2); + } + } + private static final IsAcceptable IS_ACCEPTABLE = new IsAcceptable(); /* indexes[] value names; indexes are generally 32-bit (Resource) indexes */ private static final int URES_INDEX_LENGTH = 0; /* contains URES_INDEX_TOP==the length of indexes[]; @@ -223,7 +230,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { //ivate static final int URES_INDEX_RESOURCES_TOP = 2; /* contains the top of all resources */ private static final int URES_INDEX_BUNDLE_TOP = 3; /* contains the top of the bundle, */ /* in case it were ever different from [2] */ - //ivate static final int URES_INDEX_MAX_TABLE_LENGTH = 4; /* max. length of any table */ + private static final int URES_INDEX_MAX_TABLE_LENGTH = 4; /* max. length of any table */ private static final int URES_INDEX_ATTRIBUTES = 5; /* attributes bit set, see URES_ATT_* (new in formatVersion 1.2) */ private static final int URES_INDEX_16BIT_TOP = 6; /* top of the 16-bit units (UTF-16 string v2 UChars, URES_TABLE16, URES_ARRAY16), * rounded up (new in formatVersion 2.0, ICU 4.4) */ @@ -252,26 +259,35 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { private static final int URES_ATT_IS_POOL_BUNDLE = 2; private static final int URES_ATT_USES_POOL_BUNDLE = 4; + private static final CharBuffer EMPTY_16_BIT_UNITS = CharBuffer.wrap("\0"); // read-only + + /** + * Objects with more value bytes are stored in SoftReferences. + * Smaller objects (which are not much larger than a SoftReference) + * are stored directly, avoiding the overhead of the reference. + */ + static final int LARGE_SIZE = 24; + private static final boolean DEBUG = false; - - private byte[] /* formatVersion, */ dataVersion; + + private int /* formatVersion, */ dataVersion; // See the ResourceData struct in ICU4C/source/common/uresdata.h. - private String s16BitUnits; - private byte[] poolBundleKeys; - private String poolBundleKeysAsString; + /** + * Buffer of all of the resource bundle bytes after the header. + * (equivalent of C++ pRoot) + */ + private ByteBuffer bytes; + private CharBuffer b16BitUnits; + private ByteBuffer poolBundleKeys; private int rootRes; private int localKeyLimit; private boolean noFallback; /* see URES_ATT_NO_FALLBACK */ private boolean isPoolBundle; private boolean usesPoolBundle; + private int poolCheckSum; - // Fields specific to the Java port. - private int[] indexes; - private byte[] keyStrings; - private String keyStringsAsString; // null except if isPoolBundle - private byte[] resourceBytes; - private int resourceBottom; // File offset where the mixed-type resources start. + private ResourceCache resourceCache; private static ReaderCache CACHE = new ReaderCache(); private static final ICUResourceBundleReader NULL_READER = new ICUResourceBundleReader(); @@ -321,27 +337,15 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } /* - * Sole constructor, just used for NULL_READER + * Default constructor, just used for NULL_READER. */ private ICUResourceBundleReader() { } private ICUResourceBundleReader(InputStream stream, String baseName, String localeID, ClassLoader loader) { - BufferedInputStream bs = new BufferedInputStream(stream); try { - if (DEBUG) { - System.out.println("The InputStream class is: " + stream.getClass().getName()); - System.out.println("The BufferedInputStream class is: " + bs.getClass().getName()); - System.out.println("The bytes avialable in stream before reading the header: " + bs.available()); - } - - dataVersion = ICUBinary.readHeader(bs, DATA_FORMAT_ID, this); - - if (DEBUG) System.out.println("The bytes available in stream after reading the header: " + bs.available()); - - readData(bs); - stream.close(); - + ByteBuffer inBytes = ICUBinary.getByteBufferFromInputStream(stream); + init(inBytes); } catch (IOException ex) { String fullName = ICUResourceBundleReader.getFullName(baseName, localeID); throw new ICUUncheckedIOException("Data file " + fullName + " is corrupt - " + ex.getMessage(), ex); @@ -353,11 +357,10 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { if (!poolBundleReader.isPoolBundle) { throw new IllegalStateException("pool.res is not a pool bundle"); } - if (poolBundleReader.indexes[URES_INDEX_POOL_CHECKSUM] != indexes[URES_INDEX_POOL_CHECKSUM]) { + if (poolBundleReader.poolCheckSum != poolCheckSum) { throw new IllegalStateException("pool.res has a different checksum than this bundle"); } - poolBundleKeys = poolBundleReader.keyStrings; - poolBundleKeysAsString = poolBundleReader.keyStringsAsString; + poolBundleKeys = poolBundleReader.bytes; } } @@ -371,103 +374,95 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } // See res_init() in ICU4C/source/common/uresdata.c. - private void readData(InputStream stream) throws IOException { - DataInputStream ds = new DataInputStream(stream); + private void init(ByteBuffer inBytes) throws IOException { + dataVersion = ICUBinary.readHeader(inBytes, DATA_FORMAT, IS_ACCEPTABLE); + boolean isFormatVersion10 = inBytes.get(16) == 1 && inBytes.get(17) == 0; + bytes = inBytes.slice(); + int dataLength = bytes.remaining(); + + if(DEBUG) System.out.println("The ByteBuffer is direct (memory-mapped): " + bytes.isDirect()); + if(DEBUG) System.out.println("The available bytes in the buffer before reading the data: " + dataLength); - if(DEBUG) System.out.println("The DataInputStream class is: " + ds.getClass().getName()); - if(DEBUG) System.out.println("The available bytes in the stream before reading the data: "+ds.available()); + rootRes = bytes.getInt(0); - rootRes = ds.readInt(); + if(isFormatVersion10) { + localKeyLimit = 0x10000; /* greater than any 16-bit key string offset */ + resourceCache = new ResourceCache(dataLength / 4 - 1); + return; + } // read the variable-length indexes[] array - int indexes0 = ds.readInt(); + int indexes0 = getIndexesInt(URES_INDEX_LENGTH); int indexLength = indexes0 & 0xff; - indexes = new int[indexLength]; - indexes[URES_INDEX_LENGTH] = indexes0; - for(int i=1; i URES_INDEX_ATTRIBUTES) { // determine if this resource bundle falls back to a parent bundle // along normal locale ID fallback - int att = indexes[URES_INDEX_ATTRIBUTES]; + int att = getIndexesInt(URES_INDEX_ATTRIBUTES); noFallback = (att & URES_ATT_NO_FALLBACK) != 0; isPoolBundle = (att & URES_ATT_IS_POOL_BUNDLE) != 0; usesPoolBundle = (att & URES_ATT_USES_POOL_BUNDLE) != 0; } - int length = indexes[URES_INDEX_BUNDLE_TOP]*4; - if(DEBUG) System.out.println("The number of bytes in the bundle: "+length); + // Read the array of 16-bit units. + if(indexLength > URES_INDEX_16BIT_TOP) { + int keysTop = getIndexesInt(URES_INDEX_KEYS_TOP); + int _16BitTop = getIndexesInt(URES_INDEX_16BIT_TOP); + if(_16BitTop > keysTop) { + int num16BitUnits = (_16BitTop - keysTop) * 2; + bytes.position(keysTop << 2); + b16BitUnits = bytes.slice().asCharBuffer(); + b16BitUnits.limit(num16BitUnits); + maxOffset |= num16BitUnits - 1; + } else { + b16BitUnits = EMPTY_16_BIT_UNITS; + } + } else { + b16BitUnits = EMPTY_16_BIT_UNITS; + } - // Read the local key strings. - // The keyStrings include NUL characters corresponding to the bytes - // up to the end of the indexes. - if(indexes[URES_INDEX_KEYS_TOP] > (1 + indexLength)) { - int keysBottom = (1 + indexLength) << 2; - int keysTop = indexes[URES_INDEX_KEYS_TOP] << 2; - resourceBottom = keysTop; + if(indexLength > URES_INDEX_POOL_CHECKSUM) { + poolCheckSum = getIndexesInt(URES_INDEX_POOL_CHECKSUM); + } + + // Handle key strings last: + // If this is a pool bundle, then we shift all bytes down, + // and getIndexesInt() will not work any more. + if(getIndexesInt(URES_INDEX_KEYS_TOP) > (1 + indexLength)) { if(isPoolBundle) { // Shift the key strings down: // Pool bundle key strings are used with a 0-based index, // unlike regular bundles' key strings for which indexes // are based on the start of the bundle data. - keysTop -= keysBottom; - keysBottom = 0; + bytes.position((1 + indexLength) << 2); + bytes = bytes.slice(); } else { - localKeyLimit = keysTop; - } - keyStrings = new byte[keysTop]; - ds.readFully(keyStrings, keysBottom, keysTop - keysBottom); - if(isPoolBundle) { - // Overwrite trailing padding bytes so that the conversion works. - while(keysBottom < keysTop && keyStrings[keysTop - 1] == (byte)0xaa) { - keyStrings[--keysTop] = 0; - } - keyStringsAsString = new String(keyStrings, "US-ASCII"); + localKeyLimit = getIndexesInt(URES_INDEX_KEYS_TOP) << 2; } } - // Read the array of 16-bit units. - // We are not using - // new String(keys, "UTF-16BE") - // because the 16-bit units may not be well-formed Unicode. - if( indexLength > URES_INDEX_16BIT_TOP && - indexes[URES_INDEX_16BIT_TOP] > indexes[URES_INDEX_KEYS_TOP] - ) { - int num16BitUnits = (indexes[URES_INDEX_16BIT_TOP] - - indexes[URES_INDEX_KEYS_TOP]) * 2; - char[] c16BitUnits = new char[num16BitUnits]; - // Note: Calling readFully() to read data into byte[] and copy - // the data to char[] is faster than calling readChar() one by one - // for large data - byte[] c16BitUnitsBytes = new byte[num16BitUnits * 2]; - ds.readFully(c16BitUnitsBytes); - for (int i = 0; i < num16BitUnits; i++) { - c16BitUnits[i] = (char)((c16BitUnitsBytes[i*2] << 8) | (c16BitUnitsBytes[i*2 + 1] & 0xFF)); - } - s16BitUnits = new String(c16BitUnits); - resourceBottom = indexes[URES_INDEX_16BIT_TOP] << 2; - } else { - s16BitUnits = "\0"; + if(!isPoolBundle) { + resourceCache = new ResourceCache(maxOffset); } - - // Read the block of bytes for the mixed-type resources. - resourceBytes = new byte[length - resourceBottom]; - ds.readFully(resourceBytes); } - VersionInfo getVersion(){ - return VersionInfo.getInstance(dataVersion[0],dataVersion[1],dataVersion[2],dataVersion[3]); + private int getIndexesInt(int i) { + return bytes.getInt((1 + i) << 2); } - public boolean isDataVersionAcceptable(byte version[]){ - // while ICU4C can read formatVersion 1.0 and up, - // ICU4J requires 1.1 as a minimum - // formatVersion = version; - return ((version[0] == 1 && version[1] >= 1) || version[0] == 2); + + VersionInfo getVersion() { + return ICUBinary.getVersionInfoFromCompactInt(dataVersion); } - + int getRootResource() { return rootRes; } @@ -485,7 +480,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { return res & 0x0fffffff; } private int getResourceByteOffset(int offset) { - return (offset << 2) - resourceBottom; + return offset << 2; } /* get signed and unsigned integer values directly from the Resource handle */ static int RES_GET_INT(int res) { @@ -494,46 +489,55 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { static int RES_GET_UINT(int res) { return res & 0x0fffffff; } + private static boolean URES_IS_ARRAY(int type) { + return type == UResourceBundle.ARRAY || type == ICUResourceBundle.ARRAY16; + } static boolean URES_IS_TABLE(int type) { return type==UResourceBundle.TABLE || type==ICUResourceBundle.TABLE16 || type==ICUResourceBundle.TABLE32; } - private static byte[] emptyBytes = new byte[0]; - private static ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer(); - private static char[] emptyChars = new char[0]; - private static int[] emptyInts = new int[0]; - private static String emptyString = ""; + private static final byte[] emptyBytes = new byte[0]; + private static final ByteBuffer emptyByteBuffer = ByteBuffer.allocate(0).asReadOnlyBuffer(); + private static final char[] emptyChars = new char[0]; + private static final int[] emptyInts = new int[0]; + private static final String emptyString = ""; + private static final Container EMPTY_ARRAY = new Container(); + private static final Table EMPTY_TABLE = new Table(); private char getChar(int offset) { - return (char)((resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff)); + return bytes.getChar(offset); } private char[] getChars(int offset, int count) { char[] chars = new char[count]; for(int i = 0; i < count; offset += 2, ++i) { - chars[i] = (char)(((int)resourceBytes[offset] << 8) | (resourceBytes[offset + 1] & 0xff)); + chars[i] = bytes.getChar(offset); } return chars; } private int getInt(int offset) { - return (resourceBytes[offset] << 24) | - ((resourceBytes[offset+1] & 0xff) << 16) | - ((resourceBytes[offset+2] & 0xff) << 8) | - ((resourceBytes[offset+3] & 0xff)); + return bytes.getInt(offset); } private int[] getInts(int offset, int count) { int[] ints = new int[count]; for(int i = 0; i < count; offset += 4, ++i) { - ints[i] = (resourceBytes[offset] << 24) | - ((resourceBytes[offset+1] & 0xff) << 16) | - ((resourceBytes[offset+2] & 0xff) << 8) | - ((resourceBytes[offset+3] & 0xff)); + ints[i] = bytes.getInt(offset); } return ints; } private char[] getTable16KeyOffsets(int offset) { - int length = s16BitUnits.charAt(offset++); + int length = b16BitUnits.charAt(offset++); if(length > 0) { - return s16BitUnits.substring(offset, offset + length).toCharArray(); + char[] result = new char[length]; + if(length <= 16) { + for(int i = 0; i < length; ++i) { + result[i] = b16BitUnits.charAt(offset++); + } + } else { + CharBuffer temp = b16BitUnits.duplicate(); + temp.position(offset); + temp.get(result); + } + return result; } else { return emptyChars; } @@ -555,118 +559,111 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } } - /** Refers to ASCII key string bytes, for key string matching. */ - private static final class ByteSequence { - private byte[] bytes; - private int offset; - public ByteSequence(byte[] bytes, int offset) { - this.bytes = bytes; - this.offset = offset; - } - public byte charAt(int index) { - return bytes[offset + index]; - } - } - private String makeKeyStringFromBytes(int keyOffset) { + private static String makeKeyStringFromBytes(ByteBuffer keyBytes, int keyOffset) { StringBuilder sb = new StringBuilder(); byte b; - while((b = keyStrings[keyOffset++]) != 0) { + while((b = keyBytes.get(keyOffset)) != 0) { + ++keyOffset; sb.append((char)b); } return sb.toString(); } - private String makeKeyStringFromString(int keyOffset) { - int endOffset = keyOffset; - while(poolBundleKeysAsString.charAt(endOffset) != 0) { - ++endOffset; - } - return poolBundleKeysAsString.substring(keyOffset, endOffset); - } - private ByteSequence RES_GET_KEY16(char keyOffset) { - if(keyOffset < localKeyLimit) { - return new ByteSequence(keyStrings, keyOffset); - } else { - return new ByteSequence(poolBundleKeys, keyOffset - localKeyLimit); - } - } private String getKey16String(int keyOffset) { if(keyOffset < localKeyLimit) { - return makeKeyStringFromBytes(keyOffset); - } else { - return makeKeyStringFromString(keyOffset - localKeyLimit); - } - } - private ByteSequence RES_GET_KEY32(int keyOffset) { - if(keyOffset >= 0) { - return new ByteSequence(keyStrings, keyOffset); + return makeKeyStringFromBytes(bytes, keyOffset); } else { - return new ByteSequence(poolBundleKeys, keyOffset & 0x7fffffff); + return makeKeyStringFromBytes(poolBundleKeys, keyOffset - localKeyLimit); } } private String getKey32String(int keyOffset) { if(keyOffset >= 0) { - return makeKeyStringFromBytes(keyOffset); + return makeKeyStringFromBytes(bytes, keyOffset); } else { - return makeKeyStringFromString(keyOffset & 0x7fffffff); + return makeKeyStringFromBytes(poolBundleKeys, keyOffset & 0x7fffffff); } } // Compare the length-specified input key with the - // NUL-terminated tableKey. - private static int compareKeys(CharSequence key, ByteSequence tableKey) { - int i; - for(i = 0; i < key.length(); ++i) { - int c2 = tableKey.charAt(i); + // NUL-terminated table key. + private static int compareKeys(CharSequence key, ByteBuffer keyBytes, int keyOffset) { + for(int i = 0;; ++i, ++keyOffset) { + int c2 = keyBytes.get(keyOffset); if(c2 == 0) { - return 1; // key > tableKey because key is longer. + if(i == key.length()) { + return 0; + } else { + return 1; // key > table key because key is longer. + } + } else if(i == key.length()) { + return -1; // key < table key because key is shorter. } int diff = (int)key.charAt(i) - c2; if(diff != 0) { return diff; } } - return -(int)tableKey.charAt(i); } private int compareKeys(CharSequence key, char keyOffset) { - return compareKeys(key, RES_GET_KEY16(keyOffset)); + if(keyOffset < localKeyLimit) { + return compareKeys(key, bytes, keyOffset); + } else { + return compareKeys(key, poolBundleKeys, keyOffset - localKeyLimit); + } } private int compareKeys32(CharSequence key, int keyOffset) { - return compareKeys(key, RES_GET_KEY32(keyOffset)); + if(keyOffset >= 0) { + return compareKeys(key, bytes, keyOffset); + } else { + return compareKeys(key, poolBundleKeys, keyOffset & 0x7fffffff); + } } String getString(int res) { int offset=RES_GET_OFFSET(res); - int length; - if(RES_GET_TYPE(res)==ICUResourceBundle.STRING_V2) { - int first = s16BitUnits.charAt(offset); + if(res != offset /* RES_GET_TYPE(res) != URES_STRING */ && + RES_GET_TYPE(res) != ICUResourceBundle.STRING_V2) { + return null; + } + if(offset == 0) { + return emptyString; + } + Object value = resourceCache.get(res); + if(value != null) { + return (String)value; + } + String s; + if(res != offset) { // STRING_V2 + int first = b16BitUnits.charAt(offset); if((first&0xfffffc00)!=0xdc00) { // C: if(!U16_IS_TRAIL(first)) { if(first==0) { - return emptyString; + return emptyString; // Should not occur, but is not forbidden. } - int endOffset; - for(endOffset=offset+1; s16BitUnits.charAt(endOffset)!=0; ++endOffset) {} - return s16BitUnits.substring(offset, endOffset); - } else if(first<0xdfef) { - length=first&0x3ff; - ++offset; - } else if(first<0xdfff) { - length=((first-0xdfef)<<16)|s16BitUnits.charAt(offset+1); - offset+=2; - } else { - length=((int)s16BitUnits.charAt(offset+1)<<16)|s16BitUnits.charAt(offset+2); - offset+=3; - } - return s16BitUnits.substring(offset, offset+length); - } else if(res==offset) /* RES_GET_TYPE(res)==URES_STRING */ { - if(res==0) { - return emptyString; + StringBuilder sb = new StringBuilder(); + sb.append((char)first); + char c; + while((c = b16BitUnits.charAt(++offset)) != 0) { + sb.append(c); + } + s = sb.toString(); } else { - offset=getResourceByteOffset(offset); - length=getInt(offset); - return new String(getChars(offset+4, length)); + int length; + if(first<0xdfef) { + length=first&0x3ff; + ++offset; + } else if(first<0xdfff) { + length=((first-0xdfef)<<16)|b16BitUnits.charAt(offset+1); + offset+=2; + } else { + length=((int)b16BitUnits.charAt(offset+1)<<16)|b16BitUnits.charAt(offset+2); + offset+=3; + } + s = b16BitUnits.subSequence(offset, offset + length).toString(); } } else { - return null; + offset=getResourceByteOffset(offset); + int length = getInt(offset); + s = new String(getChars(offset+4, length)); } + return (String)resourceCache.putIfAbsent(res, s, s.length() * 2); } String getAlias(int res) { @@ -676,9 +673,14 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { if(offset==0) { return emptyString; } else { + Object value = resourceCache.get(res); + if(value != null) { + return (String)value; + } offset=getResourceByteOffset(offset); length=getInt(offset); - return new String(getChars(offset+4, length)); + String s = new String(getChars(offset + 4, length)); + return (String)resourceCache.putIfAbsent(res, s, length * 2); } } else { return null; @@ -694,10 +696,24 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } else { offset=getResourceByteOffset(offset); length=getInt(offset); + if(length==0) { + return emptyBytes; + } + // Not cached: The array would have to be cloned anyway because + // the cache must not be writable via the returned reference. if(ba==null || ba.length!=length) { ba=new byte[length]; } - System.arraycopy(resourceBytes, offset+4, ba, 0, length); + offset += 4; + if(length <= 16) { + for(int i = 0; i < length; ++i) { + ba[i] = bytes.get(offset++); + } + } else { + ByteBuffer temp = bytes.duplicate(); + temp.position(offset); + temp.get(ba); + } return ba; } } else { @@ -715,9 +731,23 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { // in case it matters whether the buffer's mark is defined or undefined. return emptyByteBuffer.duplicate(); } else { + // Not cached: The returned buffer is small (shares its bytes with the bundle) + // and usually quickly discarded after use. + // Also, even a cached buffer would have to be cloned because it is mutable + // (position & mark). offset=getResourceByteOffset(offset); length=getInt(offset); - return ByteBuffer.wrap(resourceBytes, offset+4, length).slice().asReadOnlyBuffer(); + if(length == 0) { + return emptyByteBuffer.duplicate(); + } + offset += 4; + ByteBuffer result = bytes.duplicate(); + result.position(offset).limit(offset + length); + result = result.slice(); + if(!result.isReadOnly()) { + result = result.asReadOnlyBuffer(); + } + return result; } } else { return null; @@ -731,6 +761,8 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { if(offset==0) { return emptyInts; } else { + // Not cached: The array would have to be cloned anyway because + // the cache must not be writable via the returned reference. offset=getResourceByteOffset(offset); length=getInt(offset); return getInts(offset+4, length); @@ -742,101 +774,96 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { Container getArray(int res) { int type=RES_GET_TYPE(res); - int offset=RES_GET_OFFSET(res); - switch(type) { - case UResourceBundle.ARRAY: - case ICUResourceBundle.ARRAY16: - if(offset==0) { - return new Container(this); - } - break; - default: + if(!URES_IS_ARRAY(type)) { return null; } - switch(type) { - case UResourceBundle.ARRAY: - return new Array(this, offset); - case ICUResourceBundle.ARRAY16: - return new Array16(this, offset); - default: - return null; + int offset=RES_GET_OFFSET(res); + if(offset == 0) { + return EMPTY_ARRAY; + } + Object value = resourceCache.get(res); + if(value != null) { + return (Container)value; } + Container array = (type == UResourceBundle.ARRAY) ? + new Array(this, offset) : new Array16(this, offset); + return (Container)resourceCache.putIfAbsent(res, array, 0); } Table getTable(int res) { - int type=RES_GET_TYPE(res); - int offset=RES_GET_OFFSET(res); - switch(type) { - case UResourceBundle.TABLE: - case ICUResourceBundle.TABLE16: - case ICUResourceBundle.TABLE32: - if(offset==0) { - return new Table(this); - } - break; - default: + int type = RES_GET_TYPE(res); + if(!URES_IS_TABLE(type)) { return null; } - switch(type) { - case UResourceBundle.TABLE: - return new Table1632(this, offset); - case ICUResourceBundle.TABLE16: - return new Table16(this, offset); - case ICUResourceBundle.TABLE32: - return new Table32(this, offset); - default: - return null; + int offset = RES_GET_OFFSET(res); + if(offset == 0) { + return EMPTY_TABLE; } + Object value = resourceCache.get(res); + if(value != null) { + return (Table)value; + } + Table table; + int size; // Use size = 0 to never use SoftReferences for Tables? + if(type == UResourceBundle.TABLE) { + table = new Table1632(this, offset); + size = table.getSize() * 2; + } else if(type == ICUResourceBundle.TABLE16) { + table = new Table16(this, offset); + size = table.getSize() * 2; + } else /* type == ICUResourceBundle.TABLE32 */ { + table = new Table32(this, offset); + size = table.getSize() * 4; + } + return (Table)resourceCache.putIfAbsent(res, table, size); } // Container value classes --------------------------------------------- *** static class Container { - protected ICUResourceBundleReader reader; protected int size; protected int itemsOffset; int getSize() { return size; } - int getContainerResource(int index) { + int getContainerResource(ICUResourceBundleReader reader, int index) { return ICUResourceBundle.RES_BOGUS; } - protected int getContainer16Resource(int index) { + protected int getContainer16Resource(ICUResourceBundleReader reader, int index) { if (index < 0 || size <= index) { return ICUResourceBundle.RES_BOGUS; } return (ICUResourceBundle.STRING_V2 << 28) | - reader.s16BitUnits.charAt(itemsOffset + index); + reader.b16BitUnits.charAt(itemsOffset + index); } - protected int getContainer32Resource(int index) { + protected int getContainer32Resource(ICUResourceBundleReader reader, int index) { if (index < 0 || size <= index) { return ICUResourceBundle.RES_BOGUS; } return reader.getInt(itemsOffset + 4 * index); } - Container(ICUResourceBundleReader reader) { - this.reader = reader; + Container() { } } private static final class Array extends Container { - int getContainerResource(int index) { - return getContainer32Resource(index); + @Override + int getContainerResource(ICUResourceBundleReader reader, int index) { + return getContainer32Resource(reader, index); } Array(ICUResourceBundleReader reader, int offset) { - super(reader); offset = reader.getResourceByteOffset(offset); size = reader.getInt(offset); itemsOffset = offset + 4; } } private static final class Array16 extends Container { - int getContainerResource(int index) { - return getContainer16Resource(index); + @Override + int getContainerResource(ICUResourceBundleReader reader, int index) { + return getContainer16Resource(reader, index); } Array16(ICUResourceBundleReader reader, int offset) { - super(reader); - size = reader.s16BitUnits.charAt(offset); + size = reader.b16BitUnits.charAt(offset); itemsOffset = offset + 1; } } @@ -844,7 +871,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { protected char[] keyOffsets; protected int[] key32Offsets; - String getKey(int index) { + String getKey(ICUResourceBundleReader reader, int index) { if (index < 0 || size <= index) { return null; } @@ -853,7 +880,7 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { reader.getKey32String(key32Offsets[index]); } private static final int URESDATA_ITEM_NOT_FOUND = -1; - int findTableItem(CharSequence key) { + int findTableItem(ICUResourceBundleReader reader, CharSequence key) { int mid, start, limit; int result; @@ -878,19 +905,18 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } return URESDATA_ITEM_NOT_FOUND; /* not found or table is empty. */ } - int getTableResource(String resKey) { - return getContainerResource(findTableItem(resKey)); + int getTableResource(ICUResourceBundleReader reader, String resKey) { + return getContainerResource(reader, findTableItem(reader, resKey)); } - Table(ICUResourceBundleReader reader) { - super(reader); + Table() { } } private static final class Table1632 extends Table { - int getContainerResource(int index) { - return getContainer32Resource(index); + @Override + int getContainerResource(ICUResourceBundleReader reader, int index) { + return getContainer32Resource(reader, index); } Table1632(ICUResourceBundleReader reader, int offset) { - super(reader); offset = reader.getResourceByteOffset(offset); keyOffsets = reader.getTableKeyOffsets(offset); size = keyOffsets.length; @@ -898,22 +924,22 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } } private static final class Table16 extends Table { - int getContainerResource(int index) { - return getContainer16Resource(index); + @Override + int getContainerResource(ICUResourceBundleReader reader, int index) { + return getContainer16Resource(reader, index); } Table16(ICUResourceBundleReader reader, int offset) { - super(reader); keyOffsets = reader.getTable16KeyOffsets(offset); size = keyOffsets.length; itemsOffset = offset + 1 + size; } } private static final class Table32 extends Table { - int getContainerResource(int index) { - return getContainer32Resource(index); + @Override + int getContainerResource(ICUResourceBundleReader reader, int index) { + return getContainer32Resource(reader, index); } Table32(ICUResourceBundleReader reader, int offset) { - super(reader); offset = reader.getResourceByteOffset(offset); key32Offsets = reader.getTable32KeyOffsets(offset); size = key32Offsets.length; @@ -921,6 +947,261 @@ public final class ICUResourceBundleReader implements ICUBinary.Authenticate { } } + // Resource cache ------------------------------------------------------ *** + + /** + * Cache of some of one resource bundle's resources. + * Avoids creating multiple Java objects for the same resource items, + * including multiple copies of their contents. + * + *

Mutable objects must not be cached and then returned to the caller + * because the cache must not be writable via the returned reference. + * + *

Resources are mapped by their resource integers. + * Empty resources with offset 0 cannot be mapped. + * Integers need not and should not be cached. + * Multiple .res items may share resource offsets (genrb eliminates some duplicates). + * + *

This cache uses int[] and Object[] arrays to minimize object creation + * and avoid auto-boxing. + * + *

Large resource objects are stored in SoftReferences. + * + *

For few resources, a small table is used with binary search. + * When more resources are cached, then the data structure changes to be faster + * but also use more memory. + */ + private static final class ResourceCache { + // Number of items to be stored in a simple array with binary search and insertion sort. + private static final int SIMPLE_LENGTH = 32; + + // When more than SIMPLE_LENGTH items are cached, + // then switch to a trie-like tree of levels with different array lengths. + private static final int ROOT_BITS = 7; + private static final int NEXT_BITS = 6; + + // Simple table, used when length >= 0. + private int[] keys = new int[SIMPLE_LENGTH]; + private Object[] values = new Object[SIMPLE_LENGTH]; + private int length; + + // Trie-like tree of levels, used when length < 0. + private int maxOffsetBits; + /** + * Number of bits in each level, each stored in a nibble. + */ + private int levelBitsList; + private Level rootLevel; + + @SuppressWarnings("unchecked") + private static final Object putIfCleared(Object[] values, int index, Object item, int size) { + Object value = values[index]; + if(!(value instanceof SoftReference)) { + assert size < LARGE_SIZE; // Caller should be consistent for each resource. + return value; + } + assert size >= LARGE_SIZE; + value = ((SoftReference)value).get(); + if(value != null) { + return value; + } + values[index] = new SoftReference(item); + return item; + } + + private static final class Level { + int levelBitsList; + int shift; + int mask; + int[] keys; + Object[] values; + + Level(int levelBitsList, int shift) { + this.levelBitsList = levelBitsList; + this.shift = shift; + int bits = levelBitsList & 0xf; + assert bits != 0; + int length = 1 << bits; + mask = length - 1; + keys = new int[length]; + values = new Object[length]; + } + + Object get(int key) { + int index = (key >> shift) & mask; + int k = keys[index]; + if(k == key) { + return values[index]; + } + if(k == 0) { + Level level = (Level)values[index]; + if(level != null) { + return level.get(key); + } + } + return null; + } + + Object putIfAbsent(int key, Object item, int size) { + int index = (key >> shift) & mask; + int k = keys[index]; + if(k == key) { + return putIfCleared(values, index, item, size); + } + if(k == 0) { + Level level = (Level)values[index]; + if(level != null) { + return level.putIfAbsent(key, item, size); + } + keys[index] = key; + values[index] = (size >= LARGE_SIZE) ? new SoftReference(item) : item; + return item; + } + // Collision: Add a child level, move the old item there, + // and then insert the current item. + Level level = new Level(levelBitsList >> 4, shift + (levelBitsList & 0xf)); + int i = (k >> level.shift) & level.mask; + level.keys[i] = k; + level.values[i] = values[index]; + keys[index] = 0; + values[index] = level; + return level.putIfAbsent(key, item, size); + } + } + + ResourceCache(int maxOffset) { + assert maxOffset != 0; + maxOffsetBits = 28; + while(maxOffset <= 0x7ffffff) { + maxOffset <<= 1; + --maxOffsetBits; + } + int keyBits = maxOffsetBits + 2; // +2 for mini type: at most 30 bits used in a key + // Precompute for each level the number of bits it handles. + if(keyBits <= ROOT_BITS) { + levelBitsList = keyBits; + } else if(keyBits < (ROOT_BITS + 3)) { + levelBitsList = 0x30 | (keyBits - 3); + } else { + levelBitsList = ROOT_BITS; + keyBits -= ROOT_BITS; + int shift = 4; + for(;;) { + if(keyBits <= NEXT_BITS) { + levelBitsList |= keyBits << shift; + break; + } else if(keyBits < (NEXT_BITS + 3)) { + levelBitsList |= (0x30 | (keyBits - 3)) << shift; + break; + } else { + levelBitsList |= NEXT_BITS << shift; + keyBits -= NEXT_BITS; + shift += 4; + } + } + } + } + + /** + * Turns a resource integer (with unused bits in the middle) + * into a key with fewer bits (at most keyBits). + */ + private int makeKey(int res) { + // It is possible for resources of different types in the 16-bit array + // to share a start offset; distinguish between those with a 2-bit value, + // as a tie-breaker in the bits just above the highest possible offset. + // It is not possible for "regular" resources to share a start offset with each other, + // but offsets for 16-bit and "regular" resources overlap; + // use 2-bit value 0 for "regular" resources. + int type = RES_GET_TYPE(res); + int miniType = + (type == ICUResourceBundle.STRING_V2) ? 1 : + (type == ICUResourceBundle.TABLE16) ? 3 : + (type == ICUResourceBundle.ARRAY16) ? 2 : 0; + return RES_GET_OFFSET(res) | (miniType << maxOffsetBits); + } + + private int findSimple(int key) { + // With Java 6, return Arrays.binarySearch(keys, 0, length, key). + int start = 0; + int limit = length; + while((limit - start) > 8) { + int mid = (start + limit) / 2; + if(key < keys[mid]) { + limit = mid; + } else { + start = mid; + } + } + // For a small number of items, linear search should be a little faster. + while(start < limit) { + int k = keys[start]; + if(key < k) { + return ~start; + } + if(key == k) { + return start; + } + ++start; + } + return ~start; + } + + @SuppressWarnings("unchecked") + synchronized Object get(int res) { + // Integers and empty resources need not be cached. + // The cache itself uses res=0 for "no match". + assert RES_GET_OFFSET(res) != 0; + Object value; + if(length >= 0) { + int index = findSimple(res); + if(index >= 0) { + value = values[index]; + } else { + return null; + } + } else { + value = rootLevel.get(makeKey(res)); + if(value == null) { + return null; + } + } + if(value instanceof SoftReference) { + value = ((SoftReference)value).get(); + } + return value; // null if the reference was cleared + } + + synchronized Object putIfAbsent(int res, Object item, int size) { + if(length >= 0) { + int index = findSimple(res); + if(index >= 0) { + return putIfCleared(values, index, item, size); + } else if(length < SIMPLE_LENGTH) { + index = ~index; + if(index < length) { + System.arraycopy(keys, index, keys, index + 1, length - index); + System.arraycopy(values, index, values, index + 1, length - index); + } + ++length; + keys[index] = res; + values[index] = (size >= LARGE_SIZE) ? new SoftReference(item) : item; + return item; + } else /* not found && length == SIMPLE_LENGTH */ { + // Grow to become trie-like. + rootLevel = new Level(levelBitsList, 0); + for(int i = 0; i < SIMPLE_LENGTH; ++i) { + rootLevel.putIfAbsent(makeKey(keys[i]), values[i], 0); + } + keys = null; + values = null; + length = -1; + } + } + return rootLevel.putIfAbsent(makeKey(res), item, size); + } + } + private static final String ICU_RESOURCE_SUFFIX = ".res"; /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceTableAccess.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceTableAccess.java index 939cf6bf4d4..14f6a27f0e6 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceTableAccess.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ICUResourceTableAccess.java @@ -1,7 +1,7 @@ /* ******************************************************************************* - * Copyright (C) 2009-2011, International Business Machines Corporation and * - * others. All Rights Reserved. * + * Copyright (C) 2009-2014, International Business Machines Corporation and + * others. All Rights Reserved. ******************************************************************************* */ package com.ibm.icu.impl; @@ -40,23 +40,22 @@ public class ICUResourceTableAccess { table = table.getWithFallback(item); return table.getString(1); } else { - ICUResourceBundle table = lookup(bundle, tableName); + ICUResourceBundle table = bundle.findWithFallback(tableName); if (table == null) { return item; } ICUResourceBundle stable = table; if (subtableName != null) { - stable = lookup(table, subtableName); + stable = table.findWithFallback(subtableName); } if (stable != null) { - ICUResourceBundle sbundle = lookup(stable, item); - if (sbundle != null) { - result = sbundle.getString(); // possible real exception - break; + result = stable.findStringWithFallback(item); + if (result != null) { + break; // possible real exception } } - // if we get here, stable was null, or sbundle was null + // if we get here, stable was null, or there was no string for the item if (subtableName == null) { // may be a deprecated code String currentName = null; @@ -65,20 +64,18 @@ public class ICUResourceTableAccess { } else if (tableName.equals("Languages")) { currentName = LocaleIDs.getCurrentLanguageID(item); } - ICUResourceBundle sbundle = lookup(table, currentName); - if (sbundle != null) { - result = sbundle.getString(); // possible real exception - break; + result = table.findStringWithFallback(currentName); + if (result != null) { + break; // possible real exception } } // still can't figure it out? try the fallback mechanism - ICUResourceBundle fbundle = lookup(table, "Fallback"); - if (fbundle == null) { + String fallbackLocale = table.findStringWithFallback("Fallback"); // again, possible exception + if (fallbackLocale == null) { return item; } - String fallbackLocale = fbundle.getString(); // again, possible exception if (fallbackLocale.length() == 0) { fallbackLocale = "root"; } @@ -99,9 +96,4 @@ public class ICUResourceTableAccess { // If the result is empty return item instead return ((result != null && result.length() > 0) ? result : item); } - - // utility to make the call sites in the above code cleaner - private static ICUResourceBundle lookup(ICUResourceBundle bundle, String resName) { - return ICUResourceBundle.findResourceWithFallback(resName, bundle, null); - } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java index bd18c0454d3..177206b90d3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2003-2013 International Business Machines +* Copyright (c) 2003-2014 International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu @@ -286,8 +286,7 @@ public final class ZoneMeta { try { UResourceBundle top = UResourceBundle.getBundleInstance( ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); - UResourceBundle names = top.get(kNAMES); - ZONEIDS = names.getStringArray(); + ZONEIDS = top.getStringArray(kNAMES); } catch (MissingResourceException ex) { // throw away.. } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java index f393b0e4581..1143bedd5df 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/RelativeDateTimeFormatter.java @@ -1,7 +1,7 @@ /* ******************************************************************************* - * Copyright (C) 2013-2014, International Business Machines Corporation and * - * others. All Rights Reserved. * + * Copyright (C) 2013-2014, International Business Machines Corporation and + * others. All Rights Reserved. ******************************************************************************* */ package com.ibm.icu.text; @@ -417,9 +417,9 @@ public final class RelativeDateTimeFormatter { Direction direction, ICUResourceBundle bundle, String key) { - bundle = bundle.findWithFallback(key); - if (bundle != null) { - unitStrings.put(direction, bundle.getString()); + String s = bundle.findStringWithFallback(key); + if (s != null) { + unitStrings.put(direction, s); } } diff --git a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java index adcbaa49028..fbda76c1704 100644 --- a/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java +++ b/icu4j/main/classes/currdata/src/com/ibm/icu/impl/ICUCurrencyDisplayInfoProvider.java @@ -1,7 +1,7 @@ /* ******************************************************************************* - * Copyright (C) 2009-2012, International Business Machines Corporation and * - * others. All Rights Reserved. * + * Copyright (C) 2009-2014, International Business Machines Corporation and + * others. All Rights Reserved. ******************************************************************************* */ package com.ibm.icu.impl; @@ -95,17 +95,17 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid if (plurals != null) { ICUResourceBundle pluralsBundle = plurals.findWithFallback(isoCode); if (pluralsBundle != null) { - ICUResourceBundle pluralBundle = pluralsBundle.findWithFallback(pluralKey); - if (pluralBundle == null) { + String pluralName = pluralsBundle.findStringWithFallback(pluralKey); + if (pluralName == null) { if (!fallback) { return null; } - pluralBundle = pluralsBundle.findWithFallback("other"); - if (pluralBundle == null) { + pluralName = pluralsBundle.findStringWithFallback("other"); + if (pluralName == null) { return getName(isoCode); } } - return pluralBundle.getString(); + return pluralName; } } @@ -186,12 +186,12 @@ public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvid ICUResourceBundle brb = srb.findWithFallback("beforeCurrency"); ICUResourceBundle arb = srb.findWithFallback("afterCurrency"); if (arb != null && brb != null) { - String beforeCurrencyMatch = brb.findWithFallback("currencyMatch").getString(); - String beforeContextMatch = brb.findWithFallback("surroundingMatch").getString(); - String beforeInsert = brb.findWithFallback("insertBetween").getString(); - String afterCurrencyMatch = arb.findWithFallback("currencyMatch").getString(); - String afterContextMatch = arb.findWithFallback("surroundingMatch").getString(); - String afterInsert = arb.findWithFallback("insertBetween").getString(); + String beforeCurrencyMatch = brb.findStringWithFallback("currencyMatch"); + String beforeContextMatch = brb.findStringWithFallback("surroundingMatch"); + String beforeInsert = brb.findStringWithFallback("insertBetween"); + String afterCurrencyMatch = arb.findStringWithFallback("currencyMatch"); + String afterContextMatch = arb.findStringWithFallback("surroundingMatch"); + String afterInsert = arb.findStringWithFallback("insertBetween"); return new CurrencySpacingInfo( beforeCurrencyMatch, beforeContextMatch, beforeInsert,