diff options
124 files changed, 5217 insertions, 3808 deletions
diff --git a/NativeCode.mk b/NativeCode.mk index 238a875..b5cc769 100644 --- a/NativeCode.mk +++ b/NativeCode.mk @@ -95,15 +95,16 @@ endif # Define the rules. LOCAL_SRC_FILES := $(core_src_files) LOCAL_C_INCLUDES := $(core_c_includes) - -ifneq ($(TARGET_SIMULATOR),true) -LOCAL_C_INCLUDES += external/stlport/stlport bionic/ bionic/libstdc++/include -endif - LOCAL_SHARED_LIBRARIES := $(core_shared_libraries) LOCAL_STATIC_LIBRARIES := $(core_static_libraries) LOCAL_MODULE_TAGS := optional LOCAL_MODULE := libjavacore + +ifneq ($(TARGET_SIMULATOR),true) +LOCAL_C_INCLUDES += external/stlport/stlport bionic/ bionic/libstdc++/include +LOCAL_SHARED_LIBRARIES += libstlport +endif + include $(BUILD_STATIC_LIBRARY) # Deal with keystores required for security. Note: The path to this file diff --git a/dalvik/src/main/java/dalvik/system/VMDebug.java b/dalvik/src/main/java/dalvik/system/VMDebug.java index 0298680..1eb589e 100644 --- a/dalvik/src/main/java/dalvik/system/VMDebug.java +++ b/dalvik/src/main/java/dalvik/system/VMDebug.java @@ -397,9 +397,15 @@ public final class VMDebug { private static void startClassPrep() {} /** - * Returns a count of the extant instances of a class. + * Counts the instances of a class. * + * @param klass the class to be counted. + * @param assignable if false, direct instances of klass are + * counted. If true, instances that are + * assignable to klass, as defined by + * {@link Class#isAssignableFrom} are counted. + * @returns the number of matching instances. * @hide */ - public static native long countInstancesOfClass(Class cls); + public static native long countInstancesOfClass(Class klass, boolean assignable); } diff --git a/dom/src/test/java/org/w3c/domts/JAXPDOMTestDocumentBuilderFactory.java b/dom/src/test/java/org/w3c/domts/JAXPDOMTestDocumentBuilderFactory.java index c2d105e..0ca8f1f 100644 --- a/dom/src/test/java/org/w3c/domts/JAXPDOMTestDocumentBuilderFactory.java +++ b/dom/src/test/java/org/w3c/domts/JAXPDOMTestDocumentBuilderFactory.java @@ -12,6 +12,7 @@ package org.w3c.domts; +import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -120,7 +121,9 @@ public class JAXPDOMTestDocumentBuilderFactory try { LoadErrorHandler errorHandler = new LoadErrorHandler(); builder.setErrorHandler(errorHandler); - doc = builder.parse(url.openStream(), url.toString()); + InputStream stream = url.openStream(); + doc = builder.parse(stream, url.toString()); + stream.close(); parseException = errorHandler.getFirstException(); } catch (Exception ex) { diff --git a/luni/src/main/java/java/io/BufferedWriter.java b/luni/src/main/java/java/io/BufferedWriter.java index a91c0fa..e17f735 100644 --- a/luni/src/main/java/java/io/BufferedWriter.java +++ b/luni/src/main/java/java/io/BufferedWriter.java @@ -281,7 +281,8 @@ public class BufferedWriter extends Writer { return; } if (offset > str.length() - count || offset < 0) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("str.length=" + str.length() + + " offset=" + offset + " count=" + count); } if (pos == 0 && count >= buf.length) { char[] chars = new char[count]; diff --git a/luni/src/main/java/java/io/CharArrayWriter.java b/luni/src/main/java/java/io/CharArrayWriter.java index 03cfeba..8ac1493 100644 --- a/luni/src/main/java/java/io/CharArrayWriter.java +++ b/luni/src/main/java/java/io/CharArrayWriter.java @@ -231,7 +231,8 @@ public class CharArrayWriter extends Writer { // removed redundant check, used (offset | len) < 0 // instead of (offset < 0) || (len < 0) to safe one operation if ((offset | len) < 0 || len > str.length() - offset) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("str.length=" + str.length() + + " offset=" + offset + " len=" + len); } // END android-changed synchronized (lock) { diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java index ffc20aa..1e1d625 100644 --- a/luni/src/main/java/java/io/File.java +++ b/luni/src/main/java/java/io/File.java @@ -105,7 +105,7 @@ public class File implements Serializable, Comparable<File> { /** * The path we return from getAbsolutePath, and pass down to native code. */ - private String absolutePath; + private transient String absolutePath; static { // The default protection domain grants access to these properties. @@ -262,8 +262,8 @@ public class File implements Serializable, Comparable<File> { /** * Lists the file system roots. The Java platform may support zero or more * file systems, each with its own platform-dependent root. Further, the - * canonical pathname of any file on the system will always begin with one - * of the returned file system roots. + * {@link #getCanonicalPath canonical} path of any file on the system will + * always begin with one of the returned file system roots. * * @return the array of file system roots. */ @@ -436,9 +436,12 @@ public class File implements Serializable, Comparable<File> { private static native boolean existsImpl(String path); /** - * Returns the absolute path of this file. + * Returns the absolute path of this file. An absolute path is a path that starts at a root + * of the file system. On Android, there is only one root: {@code /}. * - * @return the absolute file path. + * <p>A common use for absolute paths is when passing paths to a {@code Process} as + * command-line arguments, to remove the requirement implied by relative paths, that the + * child must have the same working directory as its parent. */ public String getAbsolutePath() { return absolutePath; @@ -446,191 +449,46 @@ public class File implements Serializable, Comparable<File> { /** * Returns a new file constructed using the absolute path of this file. - * - * @return a new file from this file's absolute path. - * @see java.lang.SecurityManager#checkPropertyAccess + * Equivalent to {@code new File(this.getAbsolutePath())}. */ public File getAbsoluteFile() { return new File(this.getAbsolutePath()); } /** - * Returns the absolute path of this file with all references resolved. An - * <em>absolute</em> path is one that begins at the root of the file - * system. The canonical path is one in which all references have been - * resolved. For the cases of '..' and '.', where the file system supports - * parent and working directory respectively, these are removed and replaced - * with a direct directory reference. If the file does not exist, - * getCanonicalPath() may not resolve any references and simply returns an - * absolute path name or throws an IOException. + * Returns the canonical path of this file. + * An <i>absolute</i> path is one that begins at the root of the file system. + * A <i>canonical</i> path is an absolute path with symbolic links + * and references to "." or ".." resolved. If a path element does not exist (or + * is not searchable), there is a conflict between interpreting canonicalization + * as a textual operation (where "a/../b" is "b" even if "a" does not exist) . + * + * <p>Most callers should use {@link #getAbsolutePath} instead. A canonical path is + * significantly more expensive to compute, and not generally useful. The primary + * use for canonical paths is determining whether two paths point to the same file by + * comparing the canonicalized paths. + * + * <p>It can be actively harmful to use a canonical path, specifically because + * canonicalization removes symbolic links. It's wise to assume that a symbolic link + * is present for a reason, and that that reason is because the link may need to change. + * Canonicalization removes this layer of indirection. Good code should generally avoid + * caching canonical paths. * * @return the canonical path of this file. * @throws IOException * if an I/O error occurs. */ public String getCanonicalPath() throws IOException { - // BEGIN android-removed - // Caching the canonical path is bogus. Users facing specific - // performance problems can perform their own caching, with - // eviction strategies that are appropriate for their application. - // A VM-wide cache with no mechanism to evict stale elements is a - // disservice to applications that need up-to-date data. - // String canonPath = FileCanonPathCache.get(absPath); - // if (canonPath != null) { - // return canonPath; - // } - // END android-removed - - // TODO: rewrite getCanonicalPath, resolve, and resolveLink. - - String result = absolutePath; - if (separatorChar == '/') { - // resolve the full path first - result = resolveLink(result, result.length(), false); - // resolve the parent directories - result = resolve(result); - } - int numSeparators = 1; - for (int i = 0; i < result.length(); ++i) { - if (result.charAt(i) == separatorChar) { - numSeparators++; - } - } - int[] sepLocations = new int[numSeparators]; - int rootLoc = 0; - if (separatorChar != '/') { - if (result.charAt(0) == '\\') { - rootLoc = (result.length() > 1 && result.charAt(1) == '\\') ? 1 : 0; - } else { - rootLoc = 2; // skip drive i.e. c: - } - } - - char[] newResult = new char[result.length() + 1]; - int newLength = 0, lastSlash = 0, foundDots = 0; - sepLocations[lastSlash] = rootLoc; - for (int i = 0; i <= result.length(); ++i) { - if (i < rootLoc) { - newResult[newLength++] = result.charAt(i); - } else { - if (i == result.length() || result.charAt(i) == separatorChar) { - if (i == result.length() && foundDots == 0) { - break; - } - if (foundDots == 1) { - /* Don't write anything, just reset and continue */ - foundDots = 0; - continue; - } - if (foundDots > 1) { - /* Go back N levels */ - lastSlash = lastSlash > (foundDots - 1) ? lastSlash - (foundDots - 1) : 0; - newLength = sepLocations[lastSlash] + 1; - foundDots = 0; - continue; - } - sepLocations[++lastSlash] = newLength; - newResult[newLength++] = separatorChar; - continue; - } - if (result.charAt(i) == '.') { - foundDots++; - continue; - } - /* Found some dots within text, write them out */ - if (foundDots > 0) { - for (int j = 0; j < foundDots; j++) { - newResult[newLength++] = '.'; - } - } - newResult[newLength++] = result.charAt(i); - foundDots = 0; - } - } - // remove trailing slash - if (newLength > (rootLoc + 1) && newResult[newLength - 1] == separatorChar) { - newLength--; - } - return new String(newResult, 0, newLength); - } - - /* - * Resolve symbolic links in the parent directories. - */ - private static String resolve(String path) throws IOException { - int last = 1; - String linkPath = path; - String bytes; - boolean done; - for (int i = 1; i <= path.length(); i++) { - if (i == path.length() || path.charAt(i) == separatorChar) { - done = i >= path.length() - 1; - // if there is only one segment, do nothing - if (done && linkPath.length() == 1) { - return path; - } - boolean inPlace = false; - if (linkPath.equals(path)) { - bytes = path; - // if there are no symbolic links, truncate the path instead of copying - if (!done) { - inPlace = true; - path = path.substring(0, i); - } - } else { - int nextSize = i - last + 1; - int linkSize = linkPath.length(); - if (linkPath.charAt(linkSize - 1) == separatorChar) { - linkSize--; - } - bytes = linkPath.substring(0, linkSize) + - path.substring(last - 1, last - 1 + nextSize); - // the full path has already been resolved - } - if (done) { - return bytes; - } - linkPath = resolveLink(bytes, inPlace ? i : bytes.length(), true); - if (inPlace) { - // path[i] = '/'; - path = path.substring(0, i) + '/' + (i + 1 < path.length() ? path.substring(i + 1) : ""); - } - last = i + 1; - } - } - throw new InternalError(); + return realpath(absolutePath); } - /* - * Resolve a symbolic link. While the path resolves to an existing path, - * keep resolving. If an absolute link is found, resolve the parent - * directories if resolveAbsolute is true. + /** + * TODO: move this stuff to libcore.os. + * @hide */ - private static String resolveLink(String path, int length, boolean resolveAbsolute) throws IOException { - boolean restart = false; - do { - String fragment = path.substring(0, length); - String target = readlink(fragment); - if (target.equals(fragment)) { - break; - } - if (target.charAt(0) == separatorChar) { - // The link target was an absolute path, so we may need to start again. - restart = resolveAbsolute; - path = target + path.substring(length); - } else { - path = path.substring(0, path.lastIndexOf(separatorChar, length - 1) + 1) + target; - } - length = path.length(); - } while (existsImpl(path)); - // resolve the parent directories - if (restart) { - return resolve(path); - } - return path; - } - - private static native String readlink(String filePath); + public static native void symlink(String oldPath, String newPath); + private static native String realpath(String path); + private static native String readlink(String path); /** * Returns a new file created using the canonical path of this file. diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java index 25e5574..aa9e7e1 100644 --- a/luni/src/main/java/java/io/ObjectInputStream.java +++ b/luni/src/main/java/java/io/ObjectInputStream.java @@ -26,6 +26,7 @@ import dalvik.system.VMStack; import java.io.EmulatedFields.ObjectSlot; import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -35,6 +36,7 @@ import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import libcore.base.EmptyArray; import org.apache.harmony.luni.util.PriviAction; /** @@ -53,8 +55,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, // BEGIN android-note // this is non-static to avoid sync contention. Would static be faster? // END android-note - private InputStream emptyStream = new ByteArrayInputStream( - new byte[0]); + private InputStream emptyStream = new ByteArrayInputStream(EmptyArray.BYTE); // To put into objectsRead when reading unsharedObject private static final Object UNSHARED_OBJ = new Object(); // $NON-LOCK-1$ @@ -392,20 +393,16 @@ public class ObjectInputStream extends InputStream implements ObjectInput, .doPrivileged(new PrivilegedAction<Boolean>() { public Boolean run() { try { - Method method = implementationClass - .getMethod( - "readFields", - ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES); + Method method = implementationClass.getMethod("readFields", + EmptyArray.CLASS); if (method.getDeclaringClass() != thisClass) { return Boolean.TRUE; } } catch (NoSuchMethodException ignored) { } try { - Method method = implementationClass - .getMethod( - "readUnshared", - ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES); + Method method = implementationClass.getMethod("readUnshared", + EmptyArray.CLASS); if (method.getDeclaringClass() != thisClass) { return Boolean.TRUE; } @@ -471,8 +468,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, primitiveData = new ByteArrayInputStream(readBlockData()); return; case TC_BLOCKDATALONG: - primitiveData = new ByteArrayInputStream( - readBlockDataLong()); + primitiveData = new ByteArrayInputStream(readBlockDataLong()); return; case TC_RESET: resetState(); @@ -811,9 +807,8 @@ public class ObjectInputStream extends InputStream implements ObjectInput, return readNewClassDesc(false); case TC_PROXYCLASSDESC: Class<?> proxyClass = readNewProxyClassDesc(); - ObjectStreamClass streamClass = ObjectStreamClass - .lookup(proxyClass); - streamClass.setLoadFields(new ObjectStreamField[0]); + ObjectStreamClass streamClass = ObjectStreamClass.lookup(proxyClass); + streamClass.setLoadFields(ObjectStreamClass.NO_FIELDS); registerObjectRead(streamClass, nextHandle(), false); checkedSetSuperClassDesc(streamClass, readClassDesc()); return streamClass; @@ -1116,8 +1111,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, * @throws NotActiveException * if this stream is currently not reading an object. */ - public GetField readFields() throws IOException, ClassNotFoundException, - NotActiveException { + public GetField readFields() throws IOException, ClassNotFoundException, NotActiveException { // We can't be called from just anywhere. There are rules. if (currentObject == null) { throw new NotActiveException(); @@ -1150,8 +1144,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, */ private void readFieldValues(EmulatedFieldsForLoading emulatedFields) throws OptionalDataException, InvalidClassException, IOException { - EmulatedFields.ObjectSlot[] slots = emulatedFields.emulatedFields() - .slots(); + EmulatedFields.ObjectSlot[] slots = emulatedFields.emulatedFields().slots(); for (ObjectSlot element : slots) { element.defaulted = false; Class<?> type = element.field.getType(); @@ -1212,111 +1205,109 @@ public class ObjectInputStream extends InputStream implements ObjectInput, * @see #readFields * @see #readObject() */ - private void readFieldValues(Object obj, ObjectStreamClass classDesc) - throws OptionalDataException, ClassNotFoundException, IOException { + private void readFieldValues(Object obj, ObjectStreamClass classDesc) throws OptionalDataException, ClassNotFoundException, IOException { // Now we must read all fields and assign them to the receiver ObjectStreamField[] fields = classDesc.getLoadFields(); - fields = (null == fields ? new ObjectStreamField[] {} : fields); + fields = (null == fields ? ObjectStreamClass.NO_FIELDS : fields); Class<?> declaringClass = classDesc.forClass(); if (declaringClass == null && mustResolve) { throw new ClassNotFoundException(classDesc.getName()); } for (ObjectStreamField fieldDesc : fields) { - - // BEGIN android-removed - // // get associated Field - // long fieldID = fieldDesc.getFieldID(accessor, declaringClass); - // END android-removed - - // Code duplication starts, just because Java is typed - if (fieldDesc.isPrimitive()) { - try { - // BEGIN android-changed - switch (fieldDesc.getTypeCode()) { - case 'B': - setFieldByte(obj, declaringClass, fieldDesc.getName(), - input.readByte()); - break; - case 'C': - setFieldChar(obj, declaringClass, fieldDesc.getName(), - input.readChar()); - break; - case 'D': - setFieldDouble(obj, declaringClass, fieldDesc.getName(), - input.readDouble()); - break; - case 'F': - setFieldFloat(obj, declaringClass, fieldDesc.getName(), - input.readFloat()); - break; - case 'I': - setFieldInt(obj, declaringClass, fieldDesc.getName(), - input.readInt()); - break; - case 'J': - setFieldLong(obj, declaringClass, fieldDesc.getName(), - input.readLong()); - break; - case 'S': - setFieldShort(obj, declaringClass, fieldDesc.getName(), - input.readShort()); - break; - case 'Z': - setFieldBool(obj, declaringClass, fieldDesc.getName(), - input.readBoolean()); - break; - default: - throw new StreamCorruptedException("Invalid typecode: " + - fieldDesc.getTypeCode()); + Field field = classDesc.getReflectionField(fieldDesc); + // We may not have been able to find the field, but we still need to read the value + // and do the other checking, so there's no null check on 'field' here. + try { + switch (fieldDesc.getTypeCode()) { + case 'B': + byte b = input.readByte(); + if (field != null) { + field.setByte(obj, b); } - // END android-changed - } catch (NoSuchFieldError ignored) { - } - } else { - // Object type (array included). - String fieldName = fieldDesc.getName(); - boolean setBack = false; - // BEGIN android-added - ObjectStreamField field = classDesc.getField(fieldName); - // END android-added - if (mustResolve && fieldDesc == null) { - setBack = true; - mustResolve = false; - } - Object toSet; - if (fieldDesc != null && fieldDesc.isUnshared()) { - toSet = readUnshared(); - } else { - toSet = readObject(); - } - if (setBack) { - mustResolve = true; - } - if (fieldDesc != null) { - if (toSet != null) { - // BEGIN android-changed - // Get the field type from the local field rather than - // from the stream's supplied data. That's the field - // we'll be setting, so that's the one that needs to be - // validated. - Class<?> fieldType = field.getTypeInternal(); - // END android-added - Class<?> valueType = toSet.getClass(); - if (!fieldType.isAssignableFrom(valueType)) { - throw new ClassCastException(classDesc.getName() + "." + fieldName + - " - " + fieldType + " not compatible with " + valueType); - } - try { + break; + case 'C': + char c = input.readChar(); + if (field != null) { + field.setChar(obj, c); + } + break; + case 'D': + double d = input.readDouble(); + if (field != null) { + field.setDouble(obj, d); + } + break; + case 'F': + float f = input.readFloat(); + if (field != null) { + field.setFloat(obj, f); + } + break; + case 'I': + int i = input.readInt(); + if (field != null) { + field.setInt(obj, i); + } + break; + case 'J': + long j = input.readLong(); + if (field != null) { + field.setLong(obj, j); + } + break; + case 'S': + short s = input.readShort(); + if (field != null) { + field.setShort(obj, s); + } + break; + case 'Z': + boolean z = input.readBoolean(); + if (field != null) { + field.setBoolean(obj, z); + } + break; + case 'L': + case '[': + String fieldName = fieldDesc.getName(); + boolean setBack = false; + ObjectStreamField localFieldDesc = classDesc.getField(fieldName); + if (mustResolve && fieldDesc == null) { + setBack = true; + mustResolve = false; + } + boolean unshared = fieldDesc != null && fieldDesc.isUnshared(); + Object toSet = unshared ? readUnshared() : readObject(); + if (setBack) { + mustResolve = true; + } + if (fieldDesc != null) { + if (toSet != null) { // BEGIN android-changed - setFieldObject(obj, declaringClass, fieldName, field.getTypeString(), - toSet); - // END android-changed - } catch (NoSuchFieldError e) { - // Ignored + // Get the field type from the local field rather than + // from the stream's supplied data. That's the field + // we'll be setting, so that's the one that needs to be + // validated. + Class<?> fieldType = localFieldDesc.getTypeInternal(); + // END android-added + Class<?> valueType = toSet.getClass(); + if (!fieldType.isAssignableFrom(valueType)) { + throw new ClassCastException(classDesc.getName() + "." + fieldName + " - " + fieldType + " not compatible with " + valueType); + } + if (field != null) { + field.set(obj, toSet); + } } } + break; + default: + throw new StreamCorruptedException("Invalid typecode: " + fieldDesc.getTypeCode()); } + } catch (IllegalAccessException iae) { + // ObjectStreamField should have called setAccessible(true). + throw new AssertionError(iae); + } catch (NoSuchFieldError ignored) { } } } @@ -1405,8 +1396,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, throw new NotActiveException(); } - ArrayList<ObjectStreamClass> streamClassList = new ArrayList<ObjectStreamClass>( - 32); + ArrayList<ObjectStreamClass> streamClassList = new ArrayList<ObjectStreamClass>(32); ObjectStreamClass nextStreamClass = classDesc; while (nextStreamClass != null) { streamClassList.add(0, nextStreamClass); @@ -1827,7 +1817,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, // Resolve the field signatures using the class loader of the // resolved class ObjectStreamField[] fields = newClassDesc.getLoadFields(); - fields = (null == fields ? new ObjectStreamField[] {} : fields); + fields = (null == fields ? ObjectStreamClass.NO_FIELDS : fields); ClassLoader loader = newClassDesc.forClass() == null ? callerClassLoader : newClassDesc.forClass().getClassLoader(); for (ObjectStreamField element : fields) { @@ -1966,8 +1956,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Constructor<?> constructor = null; if (constructorClass != null) { try { - constructor = constructorClass - .getDeclaredConstructor(ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES); + constructor = constructorClass.getDeclaredConstructor(EmptyArray.CLASS); } catch (NoSuchMethodException nsmEx) { // Ignored } @@ -2052,15 +2041,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Object result, registeredResult = null; if (objectClass != null) { - - // BEGIN android-changed - // long constructor = classDesc.getConstructor(); - // if (constructor == ObjectStreamClass.CONSTRUCTOR_IS_NOT_RESOLVED) { - // constructor = accessor.getMethodID(resolveConstructorClass(objectClass, wasSerializable, wasExternalizable), null, new Class[0]); - // classDesc.setConstructor(constructor); - // } Class constructorClass = resolveConstructorClass(objectClass, wasSerializable, wasExternalizable); - // END android-changed // Now we know which class to instantiate and which constructor to // run. We are allowed to run the constructor. @@ -2582,60 +2563,6 @@ public class ObjectInputStream extends InputStream implements ObjectInput, return object; } - // BEGIN android-added - - /* - * These methods set the value of a field named fieldName of instance. The - * field is declared by declaringClass. The field is the same type as the - * value parameter. - * - * these methods could be implemented non-natively on top of - * java.lang.reflect at the expense of extra object creation - * (java.lang.reflect.Field). Otherwise Serialization could not fetch - * private fields, except by the use of a native method like this one. - * - * @throws NoSuchFieldError If the field does not exist. - */ - private static native void setFieldByte(Object instance, - Class<?> declaringClass, String fieldName, byte value) - throws NoSuchFieldError; - - - private static native void setFieldChar(Object instance, - Class<?> declaringClass, String fieldName, char value) - throws NoSuchFieldError; - - - private static native void setFieldDouble(Object instance, - Class<?> declaringClass, String fieldName, double value) - throws NoSuchFieldError; - - private static native void setFieldFloat(Object instance, - Class<?> declaringClass, String fieldName, float value) - throws NoSuchFieldError; - - private static native void setFieldInt(Object instance, - Class<?> declaringClass, String fieldName, int value) - throws NoSuchFieldError; - - private static native void setFieldLong(Object instance, - Class<?> declaringClass, String fieldName, long value) - throws NoSuchFieldError; - - private static native void setFieldObject(Object instance, - Class<?> declaringClass, String fieldName, String fieldTypeName, - Object value) throws NoSuchFieldError; - - private static native void setFieldShort(Object instance, - Class<?> declaringClass, String fieldName, short value) - throws NoSuchFieldError; - - private static native void setFieldBool(Object instance, - Class<?> declaringClass, String fieldName, boolean value) - throws NoSuchFieldError; - - // END android-added - /** * Skips {@code length} bytes on the source stream. This method should not * be used to skip bytes at any arbitrary position, just when reading diff --git a/luni/src/main/java/java/io/ObjectOutputStream.java b/luni/src/main/java/java/io/ObjectOutputStream.java index 2c5eb7a..e2e5c75 100644 --- a/luni/src/main/java/java/io/ObjectOutputStream.java +++ b/luni/src/main/java/java/io/ObjectOutputStream.java @@ -17,20 +17,17 @@ package java.io; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.nio.ByteOrder; import java.nio.charset.ModifiedUtf8; import java.util.IdentityHashMap; +import libcore.base.EmptyArray; import libcore.io.SizeOf; import org.apache.harmony.luni.platform.OSMemory; -// BEGIN android-note -// Harmony uses ObjectAccessors to access fields through JNI. Android has not -// yet migrated that API. As a consequence, there's a lot of changes here... -// END android-note - /** * A specialized {@link OutputStream} that is able to write (serialize) Java * objects as well as primitive data types (int, byte, char etc.). The data can @@ -44,6 +41,8 @@ import org.apache.harmony.luni.platform.OSMemory; public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants { + private static final Class<?>[] WRITE_UNSHARED_PARAM_TYPES = new Class[] { Object.class }; + /* * Mask to zero SC_BLOC_DATA bit. */ @@ -289,16 +288,14 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, if (implementationClass != thisClass) { boolean mustCheck = false; try { - Method method = implementationClass.getMethod("putFields", - ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES); + Method method = implementationClass.getMethod("putFields", EmptyArray.CLASS); mustCheck = method.getDeclaringClass() != thisClass; } catch (NoSuchMethodException e) { } if (!mustCheck) { try { - Method method = implementationClass.getMethod( - "writeUnshared", - ObjectStreamClass.UNSHARED_PARAM_TYPES); + Method method = implementationClass.getMethod("writeUnshared", + WRITE_UNSHARED_PARAM_TYPES); mustCheck = method.getDeclaringClass() != thisClass; } catch (NoSuchMethodException e) { } @@ -523,7 +520,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, output.flush(); } - // BEGIN android-added /* * These methods get the value of a field named fieldName of object * instance. The field is declared by declaringClass. The field is the same @@ -536,34 +532,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, * * @throws NoSuchFieldError If the field does not exist. */ - - private static native boolean getFieldBool(Object instance, - Class<?> declaringClass, String fieldName); - - private static native byte getFieldByte(Object instance, - Class<?> declaringClass, String fieldName); - - private static native char getFieldChar(Object instance, - Class<?> declaringClass, String fieldName); - - private static native double getFieldDouble(Object instance, - Class<?> declaringClass, String fieldName); - - private static native float getFieldFloat(Object instance, - Class<?> declaringClass, String fieldName); - - private static native int getFieldInt(Object instance, - Class<?> declaringClass, String fieldName); - - private static native long getFieldLong(Object instance, - Class<?> declaringClass, String fieldName); - - private static native Object getFieldObj(Object instance, - Class<?> declaringClass, String fieldName, String fieldTypeName); - - private static native short getFieldShort(Object instance, - Class<?> declaringClass, String fieldName); - // END android-added + private static native Object getFieldL(Object instance, Class<?> declaringClass, String fieldName, String fieldTypeName); /** * Return the next <code>Integer</code> handle to be used to indicate cyclic @@ -1025,11 +994,9 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, * @see #writeFields * @see #writeObject(Object) */ - private void writeFieldValues(EmulatedFieldsForDumping emulatedFields) - throws IOException { - EmulatedFields accessibleSimulatedFields = emulatedFields - .emulatedFields(); // Access internal fields which we can - // set/get. Users can't do this. + private void writeFieldValues(EmulatedFieldsForDumping emulatedFields) throws IOException { + // Access internal fields which we can set/get. Users can't do this. + EmulatedFields accessibleSimulatedFields = emulatedFields.emulatedFields(); EmulatedFields.ObjectSlot[] slots = accessibleSimulatedFields.slots(); for (int i = 0; i < slots.length; i++) { EmulatedFields.ObjectSlot slot = slots[i]; @@ -1037,29 +1004,21 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Class<?> type = slot.getField().getType(); // WARNING - default values exist for each primitive type if (type == Integer.TYPE) { - output.writeInt(fieldValue != null ? ((Integer) fieldValue) - .intValue() : 0); + output.writeInt(fieldValue != null ? ((Integer) fieldValue).intValue() : 0); } else if (type == Byte.TYPE) { - output.writeByte(fieldValue != null ? ((Byte) fieldValue) - .byteValue() : (byte) 0); + output.writeByte(fieldValue != null ? ((Byte) fieldValue).byteValue() : (byte) 0); } else if (type == Character.TYPE) { - output.writeChar(fieldValue != null ? ((Character) fieldValue) - .charValue() : (char) 0); + output.writeChar(fieldValue != null ? ((Character) fieldValue).charValue() : (char) 0); } else if (type == Short.TYPE) { - output.writeShort(fieldValue != null ? ((Short) fieldValue) - .shortValue() : (short) 0); + output.writeShort(fieldValue != null ? ((Short) fieldValue).shortValue() : (short) 0); } else if (type == Boolean.TYPE) { - output.writeBoolean(fieldValue != null ? ((Boolean) fieldValue) - .booleanValue() : false); + output.writeBoolean(fieldValue != null ? ((Boolean) fieldValue).booleanValue() : false); } else if (type == Long.TYPE) { - output.writeLong(fieldValue != null ? ((Long) fieldValue) - .longValue() : (long) 0); + output.writeLong(fieldValue != null ? ((Long) fieldValue).longValue() : (long) 0); } else if (type == Float.TYPE) { - output.writeFloat(fieldValue != null ? ((Float) fieldValue) - .floatValue() : (float) 0); + output.writeFloat(fieldValue != null ? ((Float) fieldValue).floatValue() : (float) 0); } else if (type == Double.TYPE) { - output.writeDouble(fieldValue != null ? ((Double) fieldValue) - .doubleValue() : (double) 0); + output.writeDouble(fieldValue != null ? ((Double) fieldValue).doubleValue() : (double) 0); } else { // Either array or Object writeObject(fieldValue); @@ -1067,7 +1026,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, } } - /** * Writes a collection of field values for the fields described by class * descriptor {@code classDesc} (an {@code ObjectStreamClass}). @@ -1086,72 +1044,59 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, * * @see #writeObject(Object) */ - private void writeFieldValues(Object obj, ObjectStreamClass classDesc) - throws IOException { - ObjectStreamField[] fields = classDesc.fields(); + private void writeFieldValues(Object obj, ObjectStreamClass classDesc) throws IOException { Class<?> declaringClass = classDesc.forClass(); - for(ObjectStreamField fieldDesc : fields) { + for (ObjectStreamField fieldDesc : classDesc.fields()) { try { - - // BEGIN android-changed - // // get associated Field - // long fieldID = fieldDesc.getFieldID(accessor, declaringClass); - - // Code duplication starts, just because Java is typed - if (fieldDesc.isPrimitive()) { - switch (fieldDesc.getTypeCode()) { - case 'B': - output.writeByte(getFieldByte(obj, declaringClass, - fieldDesc.getName())); - break; - case 'C': - output.writeChar(getFieldChar(obj, declaringClass, - fieldDesc.getName())); - break; - case 'D': - output.writeDouble(getFieldDouble(obj, - declaringClass, fieldDesc.getName())); - break; - case 'F': - output.writeFloat(getFieldFloat(obj, - declaringClass, fieldDesc.getName())); - break; - case 'I': - output.writeInt(getFieldInt(obj, declaringClass, - fieldDesc.getName())); - break; - case 'J': - output.writeLong(getFieldLong(obj, declaringClass, - fieldDesc.getName())); - break; - case 'S': - output.writeShort(getFieldShort(obj, - declaringClass, fieldDesc.getName())); - break; - case 'Z': - output.writeBoolean(getFieldBool(obj, - declaringClass, fieldDesc.getName())); - break; - default: - throw new IOException("Invalid typecode: " + - fieldDesc.getTypeCode()); - } - } else { - // Object type (array included). - Object objField = getFieldObj(obj, declaringClass, fieldDesc - .getName(), fieldDesc.getTypeString()); + Field field = classDesc.getReflectionField(fieldDesc); + if (field == null) { + throw new InvalidClassException(classDesc.getName() + " doesn't have a field " + fieldDesc.getName() + " of type " + fieldDesc.getTypeCode()); + } + switch (fieldDesc.getTypeCode()) { + case 'B': + output.writeByte(field.getByte(obj)); + break; + case 'C': + output.writeChar(field.getChar(obj)); + break; + case 'D': + output.writeDouble(field.getDouble(obj)); + break; + case 'F': + output.writeFloat(field.getFloat(obj)); + break; + case 'I': + output.writeInt(field.getInt(obj)); + break; + case 'J': + output.writeLong(field.getLong(obj)); + break; + case 'S': + output.writeShort(field.getShort(obj)); + break; + case 'Z': + output.writeBoolean(field.getBoolean(obj)); + break; + case 'L': + case '[': + // Reference types ('L' and '['). + Object objField = field.get(obj); if (fieldDesc.isUnshared()) { writeUnshared(objField); } else { writeObject(objField); } + break; + default: + throw new IOException("Invalid typecode: " + fieldDesc.getTypeCode()); } - // END android-changed + } catch (IllegalAccessException iae) { + // ObjectStreamField should have called setAccessible(true). + throw new AssertionError(iae); } catch (NoSuchFieldError nsf) { // The user defined serialPersistentFields but did not provide - // the glue to transfer values, - // (in writeObject) so we end up using the default mechanism and - // fail to set the emulated field + // the glue to transfer values in writeObject, so we ended up using + // the default mechanism but failed to set the emulated field. throw new InvalidClassException(classDesc.getName()); } } @@ -1673,40 +1618,39 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, writeObject(object, true); } - private void writeObject(Object object, boolean unshared) - throws IOException { + private void writeObject(Object object, boolean unshared) throws IOException { boolean setOutput = (primitiveTypes == output); if (setOutput) { primitiveTypes = null; } - // This is the spec'ed behavior in JDK 1.2. Very bizarre way to allow + // This is the specified behavior in JDK 1.2. Very bizarre way to allow // behavior overriding. if (subclassOverridingImplementation && !unshared) { writeObjectOverride(object); - } else { + return; + } - try { - // First we need to flush primitive types if they were written - drain(); - // Actual work, and class-based replacement should be computed - // if needed. - writeObjectInternal(object, unshared, true, true); - if (setOutput) { - primitiveTypes = output; - } - } catch (IOException ioEx1) { - // This will make it pass through until the top caller. It also - // lets it pass through the nested exception. - if (nestedLevels == 0 && ioEx1 != nestedException) { - try { - writeNewException(ioEx1); - } catch (IOException ioEx2) { - nestedException.fillInStackTrace(); - throw nestedException; - } + try { + // First we need to flush primitive types if they were written + drain(); + // Actual work, and class-based replacement should be computed + // if needed. + writeObjectInternal(object, unshared, true, true); + if (setOutput) { + primitiveTypes = output; + } + } catch (IOException ioEx1) { + // This will make it pass through until the top caller. It also + // lets it pass through the nested exception. + if (nestedLevels == 0 && ioEx1 != nestedException) { + try { + writeNewException(ioEx1); + } catch (IOException ioEx2) { + nestedException.fillInStackTrace(); + throw nestedException; } - throw ioEx1; // and then we propagate the original exception } + throw ioEx1; // and then we propagate the original exception } } @@ -1906,8 +1850,8 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, return classDesc; } - private Integer writeNewEnum(Object object, Class<?> theClass, - boolean unshared) throws IOException { + private Integer writeNewEnum(Object object, Class<?> theClass, boolean unshared) + throws IOException { // write new Enum EmulatedFieldsForDumping originalCurrentPutField = currentPutField; // save // null it, to make sure one will be computed if needed @@ -1932,18 +1876,22 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Class<?> declaringClass = classDesc.getSuperclass().forClass(); // Only write field "name" for enum class, which is the second field of // enum, that is fields[1]. Ignore all non-fields and fields.length < 2 - if (null != fields && fields.length > 1) { - // BEGIN android-changed - String str = (String) getFieldObj(object, declaringClass, fields[1] - .getName(), fields[1].getTypeString()); - // END android-changed - - Integer strhandle = null; - if (!unshared) { - strhandle = dumpCycle(str); + if (fields != null && fields.length > 1) { + Field field = classDesc.getReflectionField(fields[1]); + if (field == null) { + throw new NoSuchFieldError(); } - if (null == strhandle) { - writeNewString(str, unshared); + try { + String str = (String) field.get(object); + Integer strHandle = null; + if (!unshared) { + strHandle = dumpCycle(str); + } + if (strHandle == null) { + writeNewString(str, unshared); + } + } catch (IllegalAccessException iae) { + throw new AssertionError(iae); } } diff --git a/luni/src/main/java/java/io/ObjectStreamClass.java b/luni/src/main/java/java/io/ObjectStreamClass.java index 38bce57..7317d97 100644 --- a/luni/src/main/java/java/io/ObjectStreamClass.java +++ b/luni/src/main/java/java/io/ObjectStreamClass.java @@ -31,8 +31,10 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.WeakHashMap; +import libcore.base.EmptyArray; import org.apache.harmony.luni.platform.OSMemory; import org.apache.harmony.luni.util.PriviAction; @@ -57,42 +59,19 @@ public class ObjectStreamClass implements Serializable { static final long CONSTRUCTOR_IS_NOT_RESOLVED = -1; - private static final int CLASS_MODIFIERS_MASK; + private static final int CLASS_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.FINAL | + Modifier.INTERFACE | Modifier.ABSTRACT; - private static final int FIELD_MODIFIERS_MASK; + private static final int FIELD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE | + Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | + Modifier.TRANSIENT; - private static final int METHOD_MODIFIERS_MASK; + private static final int METHOD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE | + Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED | + Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT; - private static final Class<?>[] READ_PARAM_TYPES; - - private static final Class<?>[] WRITE_PARAM_TYPES; - - static final Class<?>[] EMPTY_CONSTRUCTOR_PARAM_TYPES; - - private static final Class<Void> VOID_CLASS; - - static final Class<?>[] UNSHARED_PARAM_TYPES; - - static { - CLASS_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.FINAL - | Modifier.INTERFACE | Modifier.ABSTRACT; - FIELD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE - | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL - | Modifier.VOLATILE | Modifier.TRANSIENT; - METHOD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE - | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL - | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT - | Modifier.STRICT; - - READ_PARAM_TYPES = new Class[1]; - WRITE_PARAM_TYPES = new Class[1]; - READ_PARAM_TYPES[0] = ObjectInputStream.class; - WRITE_PARAM_TYPES[0] = ObjectOutputStream.class; - EMPTY_CONSTRUCTOR_PARAM_TYPES = new Class[0]; - VOID_CLASS = Void.TYPE; - UNSHARED_PARAM_TYPES = new Class[1]; - UNSHARED_PARAM_TYPES[0] = Object.class; - } + private static final Class<?>[] READ_PARAM_TYPES = new Class[] { ObjectInputStream.class }; + private static final Class<?>[] WRITE_PARAM_TYPES = new Class[] { ObjectOutputStream.class }; /** * Constant indicating that the class has no Serializable fields. @@ -190,6 +169,11 @@ public class ObjectStreamClass implements Serializable { // Array of ObjectStreamField describing the serialized fields of this class private transient ObjectStreamField[] loadFields; + // ObjectStreamField doesn't override hashCode or equals, so this is equivalent to an + // IdentityHashMap, which is fine for our purposes. + private transient HashMap<ObjectStreamField, Field> reflectionFields = + new HashMap<ObjectStreamField, Field>(); + // MethodID for deserialization constructor private transient long constructor = CONSTRUCTOR_IS_NOT_RESOLVED; @@ -201,6 +185,28 @@ public class ObjectStreamClass implements Serializable { return constructor; } + Field getReflectionField(ObjectStreamField osf) { + synchronized (reflectionFields) { + Field field = reflectionFields.get(osf); + if (field != null) { + return field; + } + } + + try { + Class<?> declaringClass = forClass(); + Field field = declaringClass.getDeclaredField(osf.getName()); + field.setAccessible(true); + synchronized (reflectionFields) { + reflectionFields.put(osf, field); + } + return reflectionFields.get(osf); + } catch (NoSuchFieldException ex) { + // The caller messed up. We'll return null and won't try to resolve this again. + return null; + } + } + /* * If an ObjectStreamClass describes an Externalizable class, it (the * descriptor) should not have field descriptors (ObjectStreamField) at all. @@ -246,13 +252,12 @@ public class ObjectStreamClass implements Serializable { Field[] declaredFields = null; // Compute the SUID - if(serializable || externalizable) { + if (serializable || externalizable) { if (result.isEnum() || result.isProxy()) { result.setSerialVersionUID(0L); } else { declaredFields = cl.getDeclaredFields(); - result.setSerialVersionUID(computeSerialVersionUID(cl, - declaredFields)); + result.setSerialVersionUID(computeSerialVersionUID(cl, declaredFields)); } } @@ -294,12 +299,9 @@ public class ObjectStreamClass implements Serializable { } result.methodWriteReplace = findMethod(cl, "writeReplace"); result.methodReadResolve = findMethod(cl, "readResolve"); - result.methodWriteObject = findPrivateMethod(cl, "writeObject", - WRITE_PARAM_TYPES); - result.methodReadObject = findPrivateMethod(cl, "readObject", - READ_PARAM_TYPES); - result.methodReadObjectNoData = findPrivateMethod(cl, - "readObjectNoData", EMPTY_CONSTRUCTOR_PARAM_TYPES); + result.methodWriteObject = findPrivateMethod(cl, "writeObject", WRITE_PARAM_TYPES); + result.methodReadObject = findPrivateMethod(cl, "readObject", READ_PARAM_TYPES); + result.methodReadObjectNoData = findPrivateMethod(cl, "readObjectNoData", EmptyArray.CLASS); if (result.hasMethodWriteObject()) { flags |= ObjectStreamConstants.SC_WRITE_METHOD; } @@ -318,8 +320,7 @@ public class ObjectStreamClass implements Serializable { void buildFieldDescriptors(Field[] declaredFields) { // We could find the field ourselves in the collection, but calling // reflect is easier. Optimize if needed. - final Field f = ObjectStreamClass.fieldSerialPersistentFields(this - .forClass()); + final Field f = ObjectStreamClass.fieldSerialPersistentFields(this.forClass()); // If we could not find the emulated fields, we'll have to compute // dumpable fields from reflect fields boolean useReflectFields = f == null; // Assume we will compute the @@ -335,23 +336,18 @@ public class ObjectStreamClass implements Serializable { // static field, pass null _fields = (ObjectStreamField[]) f.get(null); } catch (IllegalAccessException ex) { - // WARNING - what should we do if we have no access ? This - // should not happen. - throw new RuntimeException(ex); + throw new AssertionError(ex); } } else { // Compute collection of dumpable fields based on reflect fields - List<ObjectStreamField> serializableFields = new ArrayList<ObjectStreamField>( - declaredFields.length); + List<ObjectStreamField> serializableFields = + new ArrayList<ObjectStreamField>(declaredFields.length); // Filter, we are only interested in fields that are serializable - for (int i = 0; i < declaredFields.length; i++) { - Field declaredField = declaredFields[i]; + for (Field declaredField : declaredFields) { int modifiers = declaredField.getModifiers(); - boolean shouldBeSerialized = !(Modifier.isStatic(modifiers) || Modifier - .isTransient(modifiers)); - if (shouldBeSerialized) { - ObjectStreamField field = new ObjectStreamField( - declaredField.getName(), declaredField.getType()); + if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { + ObjectStreamField field = new ObjectStreamField(declaredField.getName(), + declaredField.getType()); serializableFields.add(field); } } @@ -360,12 +356,10 @@ public class ObjectStreamClass implements Serializable { _fields = NO_FIELDS; // If no serializable fields, share the // special value so that users can test } else { - // Now convert from Vector to array - _fields = new ObjectStreamField[serializableFields.size()]; - _fields = serializableFields.toArray(_fields); + _fields = serializableFields.toArray(new ObjectStreamField[serializableFields.size()]); } } - ObjectStreamField.sortFields(_fields); + Arrays.sort(_fields); // assign offsets int primOffset = 0, objectOffset = 0; for (int i = 0; i < _fields.length; i++) { @@ -925,12 +919,7 @@ public class ObjectStreamClass implements Serializable { */ public static ObjectStreamClass lookup(Class<?> cl) { ObjectStreamClass osc = lookupStreamClass(cl); - - if (osc.isSerializable() || osc.isExternalizable()) { - return osc; - } - - return null; + return (osc.isSerializable() || osc.isExternalizable()) ? osc : null; } /** @@ -944,7 +933,7 @@ public class ObjectStreamClass implements Serializable { * @since 1.6 */ public static ObjectStreamClass lookupAny(Class<?> cl) { - return lookupStreamClass(cl); + return lookupStreamClass(cl); } /** @@ -958,8 +947,7 @@ public class ObjectStreamClass implements Serializable { * @return the corresponding descriptor */ static ObjectStreamClass lookupStreamClass(Class<?> cl) { - WeakHashMap<Class<?>,ObjectStreamClass> tlc = getCache(); - + WeakHashMap<Class<?>, ObjectStreamClass> tlc = getCache(); ObjectStreamClass cachedValue = tlc.get(cl); if (cachedValue == null) { cachedValue = createClassDesc(cl); @@ -1031,8 +1019,7 @@ public class ObjectStreamClass implements Serializable { Class<?>[] param) { try { Method method = cl.getDeclaredMethod(methodName, param); - if (Modifier.isPrivate(method.getModifiers()) - && method.getReturnType() == VOID_CLASS) { + if (Modifier.isPrivate(method.getModifiers()) && method.getReturnType() == Void.TYPE) { method.setAccessible(true); return method; } diff --git a/luni/src/main/java/java/io/ObjectStreamField.java b/luni/src/main/java/java/io/ObjectStreamField.java index 686ea15..7e05f40 100644 --- a/luni/src/main/java/java/io/ObjectStreamField.java +++ b/luni/src/main/java/java/io/ObjectStreamField.java @@ -17,19 +17,10 @@ package java.io; -// BEGIN android-note -// Harmony uses ObjectAccessors to access fields through JNI. Android has not -// yet migrated that API. -// END android-note - import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Comparator; -// BEGIN android-removed -// import org.apache.harmony.misc.accessors.ObjectAccessor; -// END android-removed - /** * Describes a field for the purpose of serialization. Classes can define the * collection of fields that are serialized, which may be different from the set @@ -40,9 +31,6 @@ import java.util.Comparator; */ public class ObjectStreamField implements Comparable<Object> { - static final int FIELD_IS_NOT_RESOLVED = -1; - static final int FIELD_IS_ABSENT = -2; - // Declared name of the field private String name; @@ -59,23 +47,6 @@ public class ObjectStreamField implements Comparable<Object> { private boolean isDeserialized; - private long assocFieldID = FIELD_IS_NOT_RESOLVED; - - // BEGIN android-removed - // long getFieldID(ObjectAccessor accessor, Class<?> declaringClass) { - // if (assocFieldID != FIELD_IS_NOT_RESOLVED) { - // return assocFieldID; - // } else { - // try { - // assocFieldID = accessor.getFieldID(declaringClass, name); - // } catch(NoSuchFieldError e) { - // assocFieldID = FIELD_IS_ABSENT; - // } - // return assocFieldID; - // } - // } - // END android-removed - /** * Constructs an ObjectStreamField with the specified name and type. * @@ -114,8 +85,7 @@ public class ObjectStreamField implements Comparable<Object> { throw new NullPointerException(); } this.name = name; - this.type = (cl.getClassLoader() == null) ? cl - : new WeakReference<Class<?>>(cl); + this.type = (cl.getClassLoader() == null) ? cl : new WeakReference<Class<?>>(cl); this.unshared = unshared; } @@ -165,19 +135,6 @@ public class ObjectStreamField implements Comparable<Object> { return this.getName().compareTo(f.getName()); } - // BEGIN android-removed - // There shouldn't be an implementation of these methods. - // @Override - // public boolean equals(Object arg0) { - // return (arg0 instanceof ObjectStreamField) && (compareTo(arg0) == 0); - // } - // - // @Override - // public int hashCode() { - // return getName().hashCode(); - // } - // END android-removed - /** * Gets the name of this field. * @@ -325,27 +282,7 @@ public class ObjectStreamField implements Comparable<Object> { */ @Override public String toString() { - return this.getClass().getName() + '(' + getName() + ':' - + getTypeInternal() + ')'; - } - - /** - * Sorts the fields for dumping. Primitive types come first, then regular - * types. - * - * @param fields - * ObjectStreamField[] fields to be sorted - */ - static void sortFields(ObjectStreamField[] fields) { - // Sort if necessary - if (fields.length > 1) { - Comparator<ObjectStreamField> fieldDescComparator = new Comparator<ObjectStreamField>() { - public int compare(ObjectStreamField f1, ObjectStreamField f2) { - return f1.compareTo(f2); - } - }; - Arrays.sort(fields, fieldDescComparator); - } + return this.getClass().getName() + '(' + getName() + ':' + getTypeInternal() + ')'; } void resolve(ClassLoader loader) { diff --git a/luni/src/main/java/java/io/OutputStreamWriter.java b/luni/src/main/java/java/io/OutputStreamWriter.java index e1a515b..57c2d55 100644 --- a/luni/src/main/java/java/io/OutputStreamWriter.java +++ b/luni/src/main/java/java/io/OutputStreamWriter.java @@ -285,13 +285,14 @@ public class OutputStreamWriter extends Writer { public void write(String str, int offset, int count) throws IOException { synchronized (lock) { if (count < 0) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException(count); } if (str == null) { throw new NullPointerException("str == null"); } if (offset < 0 || offset > str.length() - count) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("str.length=" + str.length() + + " offset=" + offset + " count=" + count); } checkStatus(); CharBuffer chars = CharBuffer.wrap(str, offset, count + offset); diff --git a/luni/src/main/java/java/io/Writer.java b/luni/src/main/java/java/io/Writer.java index ca909f3..89755ff 100644 --- a/luni/src/main/java/java/io/Writer.java +++ b/luni/src/main/java/java/io/Writer.java @@ -170,7 +170,7 @@ public abstract class Writer implements Appendable, Closeable, Flushable { */ public void write(String str, int offset, int count) throws IOException { if (count < 0) { // other cases tested by getChars() - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException(count); } char[] buf = new char[count]; str.getChars(offset, offset + count, buf, 0); diff --git a/luni/src/main/java/java/lang/AbstractStringBuilder.java b/luni/src/main/java/java/lang/AbstractStringBuilder.java index 2aa0e86..3e854b9 100644 --- a/luni/src/main/java/java/lang/AbstractStringBuilder.java +++ b/luni/src/main/java/java/lang/AbstractStringBuilder.java @@ -19,6 +19,7 @@ package java.lang; import java.io.InvalidObjectException; import java.util.Arrays; +import libcore.base.EmptyArray; /** * A modifiable {@link CharSequence sequence of characters} for use in creating @@ -59,7 +60,7 @@ abstract class AbstractStringBuilder { */ final void set(char[] val, int len) throws InvalidObjectException { if (val == null) { - val = new char[0]; + val = EmptyArray.CHAR; } if (val.length < len) { throw new InvalidObjectException("count out of range"); @@ -211,7 +212,7 @@ abstract class AbstractStringBuilder { */ public char charAt(int index) { if (index < 0 || index >= count) { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } return value[index]; } @@ -241,12 +242,14 @@ abstract class AbstractStringBuilder { return; } } - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "start=" + start + " end=" + end + " length=" + count); } final void deleteCharAt0(int location) { if (0 > location || location >= count) { - throw new StringIndexOutOfBoundsException(location); + throw new StringIndexOutOfBoundsException( + "location=" + location + " length=" + count); } int length = count - location - 1; if (length > 0) { @@ -305,14 +308,15 @@ abstract class AbstractStringBuilder { */ public void getChars(int start, int end, char[] dst, int dstStart) { if (start > count || end > count || start > end) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "start=" + start + " end=" + end + " length=" + count); } System.arraycopy(value, start, dst, dstStart, end - start); } final void insert0(int index, char[] chars) { if (0 > index || index > count) { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } if (chars.length != 0) { move(chars.length, index); @@ -332,11 +336,9 @@ abstract class AbstractStringBuilder { } return; } - throw new StringIndexOutOfBoundsException("offset " + start - + ", length " + length - + ", char[].length " + chars.length); } - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " chars.length=" + chars.length + + " start=" + start + " length=" + length + " this.length=" + count); } final void insert0(int index, char ch) { @@ -363,7 +365,7 @@ abstract class AbstractStringBuilder { count += min; } } else { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } } @@ -450,7 +452,8 @@ abstract class AbstractStringBuilder { return; } } - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "start=" + start + " end=" + end + " length=" + count); } final void reverse0() { @@ -543,7 +546,7 @@ abstract class AbstractStringBuilder { */ public void setCharAt(int index, char ch) { if (0 > index || index >= count) { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } if (shared) { value = value.clone(); @@ -604,7 +607,7 @@ abstract class AbstractStringBuilder { // Remove String sharing for more performance return new String(value, start, count - start); } - throw new StringIndexOutOfBoundsException(start); + throw new StringIndexOutOfBoundsException("start=" + start + " length=" + count); } /** @@ -629,7 +632,8 @@ abstract class AbstractStringBuilder { // Remove String sharing for more performance return new String(value, start, end - start); } - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "start=" + start + " end=" + end + " length=" + count); } /** @@ -834,7 +838,7 @@ abstract class AbstractStringBuilder { */ public int codePointAt(int index) { if (index < 0 || index >= count) { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } return Character.codePointAt(value, index, count); } @@ -854,7 +858,7 @@ abstract class AbstractStringBuilder { */ public int codePointBefore(int index) { if (index < 1 || index > count) { - throw new StringIndexOutOfBoundsException(index); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } return Character.codePointBefore(value, index); } @@ -878,7 +882,8 @@ abstract class AbstractStringBuilder { */ public int codePointCount(int beginIndex, int endIndex) { if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("beginIndex=" + beginIndex + + " endIndex" + endIndex + " length=" + count); } return Character.codePointCount(value, beginIndex, endIndex - beginIndex); diff --git a/luni/src/main/java/java/lang/Class.java b/luni/src/main/java/java/lang/Class.java index 451da89..eb721b8 100644 --- a/luni/src/main/java/java/lang/Class.java +++ b/luni/src/main/java/java/lang/Class.java @@ -658,8 +658,7 @@ public final class Class<T> implements Serializable, AnnotatedElement, GenericDe * access. * @see #getField(String) */ - public Field getDeclaredField(String name) - throws NoSuchFieldException, SecurityException { + public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException { checkDeclaredMemberAccess(); Field[] fields = getClassCache().getDeclaredFields(); @@ -871,6 +870,26 @@ public final class Class<T> implements Serializable, AnnotatedElement, GenericDe } /** + * Finds and returns a field with a given name and signature. Use + * this with one of the field lists returned by instances of ClassCache. + * + * @param list non-null; the list of fields to search through + * @return non-null; the matching field + * @throws NoSuchFieldException thrown if the field does not exist + */ + private Field findFieldByName(Field[] list, String name) throws NoSuchFieldException { + if (name == null) { + throw new NullPointerException("name == null"); + } + for (Field field : list) { + if (field.getName().equals(name)) { + return field; + } + } + throw new NoSuchFieldException("No field '" + name + "' in " + this); + } + + /** * Returns an array containing {@code Field} objects for all public fields * for the class C represented by this {@code Class}. Fields may be declared * in C, the interfaces it implements or in the superclasses of C. The @@ -1467,7 +1486,9 @@ public final class Class<T> implements Serializable, AnnotatedElement, GenericDe if (clazz.isAssignableFrom(this)) { return (Class<? extends U>)this; } - throw new ClassCastException(); + String actualClassName = this.getName(); + String desiredClassName = clazz.getName(); + throw new ClassCastException(actualClassName + " cannot be cast to " + desiredClassName); } /** @@ -1487,7 +1508,9 @@ public final class Class<T> implements Serializable, AnnotatedElement, GenericDe } else if (this.isInstance(obj)) { return (T)obj; } - throw new ClassCastException(); + String actualClassName = obj.getClass().getName(); + String desiredClassName = this.getName(); + throw new ClassCastException(actualClassName + " cannot be cast to " + desiredClassName); } /** diff --git a/luni/src/main/java/java/lang/ClassCache.java b/luni/src/main/java/java/lang/ClassCache.java index 5588f62..55b787f 100644 --- a/luni/src/main/java/java/lang/ClassCache.java +++ b/luni/src/main/java/java/lang/ClassCache.java @@ -235,7 +235,7 @@ import org.apache.harmony.kernel.vm.ReflectionAccess; public static Method findMethodByName(Method[] list, String name, Class<?>[] parameterTypes) throws NoSuchMethodException { if (name == null) { - throw new NullPointerException("Method name must not be null."); + throw new NullPointerException("name == null"); } for (Method method : list) { if (method.getName().equals(name) @@ -425,28 +425,6 @@ import org.apache.harmony.kernel.vm.ReflectionAccess; } /** - * Finds and returns a field with a given name and signature. Use - * this with one of the field lists returned by instances of this class. - * - * @param list non-null; the list of fields to search through - * @return non-null; the matching field - * @throws NoSuchFieldException thrown if the field does not exist - */ - public static Field findFieldByName(Field[] list, String name) - throws NoSuchFieldException { - if (name == null) { - throw new NullPointerException("Field name must not be null."); - } - for (Field field : list) { - if (field.getName().equals(name)) { - return field; - } - } - - throw new NoSuchFieldException(name); - } - - /** * Makes a deep copy of the given array of fields. This is useful * when handing out arrays from the public API. * diff --git a/luni/src/main/java/java/lang/String.java b/luni/src/main/java/java/lang/String.java index 605ea67..541d987 100644 --- a/luni/src/main/java/java/lang/String.java +++ b/luni/src/main/java/java/lang/String.java @@ -28,6 +28,7 @@ import java.util.Comparator; import java.util.Formatter; import java.util.Locale; import java.util.regex.Pattern; +import libcore.base.EmptyArray; /** * An immutable sequence of characters/code units ({@code char}s). A @@ -94,8 +95,6 @@ public final class String implements Serializable, Comparable<String>, CharSeque */ public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); - private static final char[] EMPTY_CHAR_ARRAY = new char[0]; - private static final char[] ASCII; static { ASCII = new char[128]; @@ -116,7 +115,7 @@ public final class String implements Serializable, Comparable<String>, CharSeque * Creates an empty string. */ public String() { - value = EMPTY_CHAR_ARRAY; + value = EmptyArray.CHAR; offset = 0; count = 0; } @@ -184,10 +183,11 @@ public final class String implements Serializable, Comparable<String>, CharSeque if (count > 0) { value = cb.array(); } else { - value = EMPTY_CHAR_ARRAY; + value = EmptyArray.CHAR; } } else { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("data.length=" + data.length + + " start=" + start + " length=" + length); } } @@ -224,7 +224,8 @@ public final class String implements Serializable, Comparable<String>, CharSeque value[i] = (char) (high + (data[start++] & 0xff)); } } else { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "data.length=" + data.length + " start=" + start + " length=" + length); } } else { throw new NullPointerException(); @@ -314,7 +315,8 @@ public final class String implements Serializable, Comparable<String>, CharSeque */ public String(byte[] data, int start, int length, Charset charset) { if (start < 0 || length < 0 || length > data.length - start) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "data.length=" + data.length + " start=" + start + " length=" + length); } // We inline UTF-8, ISO-8859-1, and US-ASCII decoders for speed and because 'count' and @@ -446,7 +448,7 @@ outer: this.value = new char[count]; System.arraycopy(cb.array(), 0, value, 0, count); } else { - value = EMPTY_CHAR_ARRAY; + value = EmptyArray.CHAR; } } } @@ -514,7 +516,8 @@ outer: count = length; System.arraycopy(data, start, value, 0, count); } else { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "data.length=" + data.length + " start=" + start + " length=" + length); } } @@ -623,7 +626,8 @@ outer: throw new NullPointerException(); } if (offset < 0 || count < 0 || (long) offset + (long) count > codePoints.length) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("codePoints.length=" + codePoints.length + + " offset=" + offset + " count=" + count); } this.offset = 0; this.value = new char[count * 2]; @@ -685,7 +689,7 @@ outer: if (0 <= index && index < count) { return value[offset + index]; } - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } // Optimized for ASCII @@ -940,12 +944,11 @@ outer: for (int i = offset + start; i < end; i++) { data[index++] = (byte) value[i]; } - } catch (ArrayIndexOutOfBoundsException e) { - throw new StringIndexOutOfBoundsException(); + } catch (ArrayIndexOutOfBoundsException ignored) { } - } else { - throw new StringIndexOutOfBoundsException(); } + throw new StringIndexOutOfBoundsException("start=" + start + " end=" + end + + " data.length=" + data.length + " index=" + index + " length=" + count); } /** @@ -1027,7 +1030,8 @@ outer: if (0 <= start && start <= end && end <= count) { System.arraycopy(value, start + offset, buffer, index, end - start); } else { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("start=" + start + " end=" + end + + " buffer.length=" + buffer.length + " index=" + index + " length=" + count); } } @@ -1618,7 +1622,7 @@ outer: if (0 <= start && start <= count) { return new String(offset + start, count - start, value); } - throw new StringIndexOutOfBoundsException(start); + throw new StringIndexOutOfBoundsException("start=" + start + " length=" + count); } /** @@ -1644,7 +1648,8 @@ outer: if (0 <= start && start <= end && end <= count) { return new String(offset + start, end - start, value); } - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException( + "start=" + start + " end=" + end + " length=" + count); } /** @@ -1894,6 +1899,23 @@ outer: } /** + * Equivalent to {@code equals(new String(chars, offset, length))}. + * + * @hide + */ + public boolean contentEquals(char[] chars, int offset, int length) { + if (count != length) { + return false; + } + for (int i = 0; i < length; i++) { + if (chars[offset + i] != value[this.offset + i]) { + return false; + } + } + return true; + } + + /** * Compares a {@code CharSequence} to this {@code String} to determine if * their contents are equal. * @@ -2045,7 +2067,7 @@ outer: */ public int codePointAt(int index) { if (index < 0 || index >= count) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } return Character.codePointAt(value, offset + index, offset + count); } @@ -2059,7 +2081,7 @@ outer: */ public int codePointBefore(int index) { if (index < 1 || index > count) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("index=" + index + " length=" + count); } return Character.codePointBefore(value, offset + index, offset); } @@ -2080,7 +2102,8 @@ outer: */ public int codePointCount(int beginIndex, int endIndex) { if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) { - throw new StringIndexOutOfBoundsException(); + throw new StringIndexOutOfBoundsException("beginIndex=" + beginIndex + + " endIndex=" + endIndex + " length=" + count); } return Character.codePointCount(value, offset + beginIndex, endIndex - beginIndex); } diff --git a/luni/src/main/java/java/lang/Thread.java b/luni/src/main/java/java/lang/Thread.java index 980c841..e26ecf0 100644 --- a/luni/src/main/java/java/lang/Thread.java +++ b/luni/src/main/java/java/lang/Thread.java @@ -69,6 +69,7 @@ import org.apache.harmony.security.fortress.SecurityUtils; * */ public class Thread implements Runnable { + private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; private static final int NANOS_PER_MILLI = 1000000; @@ -735,7 +736,7 @@ public class Thread implements Runnable { } StackTraceElement ste[] = VMStack.getThreadStackTrace(this); - return ste != null ? ste : new StackTraceElement[0]; + return ste != null ? ste : EMPTY_STACK_TRACE; } /** diff --git a/luni/src/main/java/java/lang/Void.java b/luni/src/main/java/java/lang/Void.java index 56d7c74..75e616a 100644 --- a/luni/src/main/java/java/lang/Void.java +++ b/luni/src/main/java/java/lang/Void.java @@ -18,6 +18,7 @@ package java.lang; import java.lang.reflect.Method; +import libcore.base.EmptyArray; /** * Placeholder class for the Java keyword {@code void}. @@ -38,7 +39,7 @@ public final class Void extends Object { private static Class<Void> lookupType() { Class<?> voidType = null; try { - Method method = Runnable.class.getMethod("run", new Class[0]); + Method method = Runnable.class.getMethod("run", EmptyArray.CLASS); voidType = method.getReturnType(); } catch (Exception e) { throw new RuntimeException(e); diff --git a/luni/src/main/java/java/lang/reflect/Array.java b/luni/src/main/java/java/lang/reflect/Array.java index 1e672d1..db0d426 100644 --- a/luni/src/main/java/java/lang/reflect/Array.java +++ b/luni/src/main/java/java/lang/reflect/Array.java @@ -47,14 +47,14 @@ public final class Array { /** * Returns the element of the array at the specified index. This reproduces * the effect of {@code array[index]}. If the array component is a primitive - * type, the result is automatically wrapped. + * type, the result is automatically boxed. * * @param array * the array * @param index * the index * - * @return the requested element, possibly wrapped + * @return the requested element, possibly boxed * * @throws NullPointerException * if the array is null @@ -486,7 +486,7 @@ public final class Array { /** * Sets the element of the array at the specified index to the value. This * reproduces the effect of {@code array[index] = value}. If the array - * component is a primitive type, the value is automatically unwrapped. + * component is a primitive type, the value is automatically unboxed. * * @param array * the array diff --git a/luni/src/main/java/java/lang/reflect/Constructor.java b/luni/src/main/java/java/lang/reflect/Constructor.java index fba433e..f511687 100644 --- a/luni/src/main/java/java/lang/reflect/Constructor.java +++ b/luni/src/main/java/java/lang/reflect/Constructor.java @@ -34,6 +34,7 @@ package java.lang.reflect; import dalvik.system.VMStack; import java.lang.annotation.Annotation; +import libcore.base.EmptyArray; import org.apache.harmony.kernel.vm.StringUtils; import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; import org.apache.harmony.luni.lang.reflect.ListOfTypes; @@ -291,8 +292,9 @@ public final class Constructor<T> extends AccessibleObject implements GenericDec * @return the declared exception classes */ public Class<?>[] getExceptionTypes() { - if (exceptionTypes == null) - return new Class[0]; + if (exceptionTypes == null) { + return EmptyArray.CLASS; + } return exceptionTypes.clone(); } @@ -381,7 +383,7 @@ public final class Constructor<T> extends AccessibleObject implements GenericDec * <li>For each argument passed: * <ul> * <li>If the corresponding parameter type is a primitive type, the argument - * is unwrapped. If the unwrapping fails, an IllegalArgumentException is + * is unboxed. If the unboxing fails, an IllegalArgumentException is * thrown.</li> * <li>If the resulting argument cannot be converted to the parameter type * via a widening conversion, an IllegalArgumentException is thrown.</li> diff --git a/luni/src/main/java/java/lang/reflect/Field.java b/luni/src/main/java/java/lang/reflect/Field.java index ecdb93a..d7898b9 100644 --- a/luni/src/main/java/java/lang/reflect/Field.java +++ b/luni/src/main/java/java/lang/reflect/Field.java @@ -209,23 +209,22 @@ public final class Field extends AccessibleObject implements Member { /** * Returns the value of the field in the specified object. This reproduces * the effect of {@code object.fieldName} - * <p> - * If the type of this field is a primitive type, the field value is - * automatically wrapped. - * <p> - * If this field is static, the object argument is ignored. + * + * <p>If the type of this field is a primitive type, the field value is + * automatically boxed. + * + * <p>If this field is static, the object argument is ignored. * Otherwise, if the object is null, a NullPointerException is thrown. If * the object is not an instance of the declaring class of the method, an * IllegalArgumentException is thrown. - * <p> - * If this Field object is enforcing access control (see AccessibleObject) + * + * <p>If this Field object is enforcing access control (see AccessibleObject) * and this field is not accessible from the current context, an * IllegalAccessException is thrown. - * <p> * * @param object * the object to access - * @return the field value, possibly wrapped + * @return the field value, possibly boxed * @throws NullPointerException * if the object is {@code null} and the field is non-static * @throws IllegalArgumentException @@ -527,18 +526,18 @@ public final class Field extends AccessibleObject implements Member { /** * Sets the value of the field in the specified object to the value. This * reproduces the effect of {@code object.fieldName = value} - * <p> - * If this field is static, the object argument is ignored. + * + * <p>If this field is static, the object argument is ignored. * Otherwise, if the object is {@code null}, a NullPointerException is * thrown. If the object is not an instance of the declaring class of the * method, an IllegalArgumentException is thrown. - * <p> - * If this Field object is enforcing access control (see AccessibleObject) + * + * <p>If this Field object is enforcing access control (see AccessibleObject) * and this field is not accessible from the current context, an * IllegalAccessException is thrown. - * <p> - * If the field type is a primitive type, the value is automatically - * unwrapped. If the unwrap fails, an IllegalArgumentException is thrown. If + * + * <p>If the field type is a primitive type, the value is automatically + * unboxed. If the unboxing fails, an IllegalArgumentException is thrown. If * the value cannot be converted to the field type via a widening * conversion, an IllegalArgumentException is thrown. * diff --git a/luni/src/main/java/java/lang/reflect/InvocationHandler.java b/luni/src/main/java/java/lang/reflect/InvocationHandler.java index c5bd7d2..330904e 100644 --- a/luni/src/main/java/java/lang/reflect/InvocationHandler.java +++ b/luni/src/main/java/java/lang/reflect/InvocationHandler.java @@ -46,10 +46,9 @@ public interface InvocationHandler { * @param args * an array of objects containing the parameters passed to the * method, or {@code null} if no arguments are expected. - * Primitive types are wrapped in the appropriate wrapper type + * Primitive types are boxed. * - * @return the result of executing the method. Primitive types need to be - * wrapped in the appropriate wrapper type + * @return the result of executing the method. Primitive types are boxed. * * @throws Throwable * the exception to throw from the invoked method on the proxy. @@ -57,6 +56,5 @@ public interface InvocationHandler { * of the invoked method or any unchecked exception type. If not * then an {@code UndeclaredThrowableException} is thrown */ - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable; + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } diff --git a/luni/src/main/java/java/lang/reflect/Method.java b/luni/src/main/java/java/lang/reflect/Method.java index f46d76f..68fe219 100644 --- a/luni/src/main/java/java/lang/reflect/Method.java +++ b/luni/src/main/java/java/lang/reflect/Method.java @@ -34,6 +34,7 @@ package java.lang.reflect; import java.lang.annotation.Annotation; import java.util.Comparator; +import libcore.base.EmptyArray; import org.apache.harmony.kernel.vm.StringUtils; import org.apache.harmony.luni.lang.reflect.GenericSignatureParser; import org.apache.harmony.luni.lang.reflect.ListOfTypes; @@ -378,9 +379,8 @@ public final class Method extends AccessibleObject implements GenericDeclaration */ public Class<?>[] getExceptionTypes() { if (exceptionTypes == null) { - return new Class[0]; + return EmptyArray.CLASS; } - return exceptionTypes.clone(); } @@ -444,73 +444,57 @@ public final class Method extends AccessibleObject implements GenericDeclaration } /** - * Returns the result of dynamically invoking this method. This reproduces - * the effect of {@code receiver.methodName(arg1, arg2, ... , argN)} This - * method performs the following: - * <ul> - * <li>If this method is static, the receiver argument is ignored.</li> - * <li>Otherwise, if the receiver is null, a NullPointerException is thrown. - * </li> - * <li>If the receiver is not an instance of the declaring class of the - * method, an IllegalArgumentException is thrown.</li> - * <li>If this Method object is enforcing access control (see - * AccessibleObject) and this method is not accessible from the current - * context, an IllegalAccessException is thrown.</li> - * <li>If the number of arguments passed and the number of parameters do not - * match, an IllegalArgumentException is thrown.</li> - * <li>For each argument passed: - * <ul> - * <li>If the corresponding parameter type is a primitive type, the argument - * is unwrapped. If the unwrapping fails, an IllegalArgumentException is - * thrown.</li> - * <li>If the resulting argument cannot be converted to the parameter type - * via a widening conversion, an IllegalArgumentException is thrown.</li> - * </ul> - * <li>If this method is static, it is invoked directly. If it is - * non-static, this method and the receiver are then used to perform a - * standard dynamic method lookup. The resulting method is then invoked.</li> - * <li>If an exception is thrown during the invocation it is caught and - * wrapped in an InvocationTargetException. This exception is then thrown.</li> - * <li>If the invocation completes normally, the return value itself is + * Returns the result of dynamically invoking this method. Equivalent to + * {@code receiver.methodName(arg1, arg2, ... , argN)}. + * + * <p>If the method is static, the receiver argument is ignored (and may be null). + * + * <p>If the method takes no arguments, you can pass {@code (Object[]) null} instead of + * allocating an empty array. + * + * <p>If you're calling a varargs method, you need to pass an {@code Object[]} for the + * varargs parameter: that conversion is usually done in {@code javac}, not the VM, and + * the reflection machinery does not do this for you. (It couldn't, because it would be + * ambiguous.) + * + * <p>Reflective method invocation follows the usual process for method lookup. + * + * <p>If an exception is thrown during the invocation it is caught and + * wrapped in an InvocationTargetException. This exception is then thrown. + * + * <p>If the invocation completes normally, the return value itself is * returned. If the method is declared to return a primitive type, the - * return value is first wrapped. If the return type is void, null is - * returned.</li> - * </ul> + * return value is boxed. If the return type is void, null is returned. * * @param receiver - * the object on which to call this method + * the object on which to call this method (or null for static methods) * @param args * the arguments to the method - * - * @return the new, initialized, object + * @return the result * * @throws NullPointerException - * if the receiver is null for a non-static method + * if {@code receiver == null} for a non-static method * @throws IllegalAccessException - * if this method is not accessible + * if this method is not accessible (see {@link AccessibleObject}) * @throws IllegalArgumentException - * if an incorrect number of arguments are passed, the receiver - * is incompatible with the declaring class, or an argument - * could not be converted by a widening conversion + * if the number of arguments doesn't match the number of parameters, the receiver + * is incompatible with the declaring class, or an argument could not be unboxed + * or converted by a widening conversion to the corresponding parameter type * @throws InvocationTargetException * if an exception was thrown by the invoked method - * - * @see AccessibleObject */ public Object invoke(Object receiver, Object... args) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException { + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (args == null) { - args = new Object[0]; + args = EmptyArray.OBJECT; } - - return invokeNative (receiver, args, declaringClass, parameterTypes, returnType, slot, flag); + return invokeNative(receiver, args, declaringClass, parameterTypes, returnType, slot, flag); } - private native Object invokeNative(Object obj, Object[] args, Class<?> declaringClass, Class<?>[] parameterTypes, Class<?> returnType, int slot, boolean noAccessCheck) - throws IllegalAccessException, - IllegalArgumentException, - InvocationTargetException; + private native Object invokeNative(Object obj, Object[] args, Class<?> declaringClass, + Class<?>[] parameterTypes, Class<?> returnType, int slot, boolean noAccessCheck) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException; /** * Returns a string containing a concise, human-readable description of this diff --git a/luni/src/main/java/java/net/DatagramPacket.java b/luni/src/main/java/java/net/DatagramPacket.java index 36ec922..cde5d7b 100644 --- a/luni/src/main/java/java/net/DatagramPacket.java +++ b/luni/src/main/java/java/net/DatagramPacket.java @@ -277,8 +277,7 @@ public final class DatagramPacket { * @throws SocketException * if an error in the underlying protocol occurs. */ - public DatagramPacket(byte[] data, int length, SocketAddress sockAddr) - throws SocketException { + public DatagramPacket(byte[] data, int length, SocketAddress sockAddr) throws SocketException { this(data, 0, length); setSocketAddress(sockAddr); } @@ -328,6 +327,9 @@ public final class DatagramPacket { (sockAddr == null ? null : sockAddr.getClass())); } InetSocketAddress inetAddr = (InetSocketAddress) sockAddr; + if (inetAddr.isUnresolved()) { + throw new IllegalArgumentException("Socket address unresolved: " + sockAddr); + } port = inetAddr.getPort(); address = inetAddr.getAddress(); } diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java index d1cc593..1bfe40e 100644 --- a/luni/src/main/java/java/net/DatagramSocket.java +++ b/luni/src/main/java/java/net/DatagramSocket.java @@ -429,18 +429,14 @@ public class DatagramSocket { } else { // not connected so the target address is not allowed to be null if (packAddr == null) { - if (pack.getPort() == -1) { - throw new NullPointerException("Destination address is null"); - } - return; + throw new NullPointerException("Destination address is null"); } SecurityManager security = System.getSecurityManager(); if (security != null) { if (packAddr.isMulticastAddress()) { security.checkMulticast(packAddr); } else { - security.checkConnect(packAddr.getHostName(), pack - .getPort()); + security.checkConnect(packAddr.getHostName(), pack.getPort()); } } } diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java index 24a069f..f8920fc 100644 --- a/luni/src/main/java/java/net/Inet6Address.java +++ b/luni/src/main/java/java/net/Inet6Address.java @@ -23,6 +23,7 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.util.Arrays; import java.util.Enumeration; +import libcore.base.EmptyArray; /** * An IPv6 address. See {@link InetAddress}. @@ -375,7 +376,7 @@ public final class Inet6Address extends InetAddress { } private static final ObjectStreamField[] serialPersistentFields = { - new ObjectStreamField("ipaddress", new byte[0].getClass()), + new ObjectStreamField("ipaddress", EmptyArray.BYTE.getClass()), new ObjectStreamField("scope_id", Integer.TYPE), new ObjectStreamField("scope_id_set", Boolean.TYPE), new ObjectStreamField("scope_ifname_set", Boolean.TYPE), diff --git a/luni/src/main/java/java/net/InetSocketAddress.java b/luni/src/main/java/java/net/InetSocketAddress.java index 8a33db8..f9f973b 100644 --- a/luni/src/main/java/java/net/InetSocketAddress.java +++ b/luni/src/main/java/java/net/InetSocketAddress.java @@ -28,10 +28,9 @@ public class InetSocketAddress extends SocketAddress { private static final long serialVersionUID = 5076001401234631237L; - private final String hostname; - + // Exactly one of hostname or addr should be set. private final InetAddress addr; - + private final String hostname; private final int port; /** @@ -59,14 +58,10 @@ public class InetSocketAddress extends SocketAddress { */ public InetSocketAddress(InetAddress address, int port) { if (port < 0 || port > 65535) { - throw new IllegalArgumentException(); - } - if (address == null) { - addr = Inet4Address.ANY; - } else { - addr = address; + throw new IllegalArgumentException("port=" + port); } - hostname = addr.getHostName(); + this.addr = (address == null) ? Inet4Address.ANY : address; + this.hostname = null; this.port = port; } @@ -97,7 +92,6 @@ public class InetSocketAddress extends SocketAddress { if (hostname == null || port < 0 || port > 65535) { throw new IllegalArgumentException("host=" + hostname + ", port=" + port); } - this.port = port; InetAddress addr = null; if (needResolved) { @@ -111,8 +105,9 @@ public class InetSocketAddress extends SocketAddress { } catch (UnknownHostException ignored) { } } - this.hostname = hostname; this.addr = addr; + this.hostname = hostname; + this.port = port; } /** @@ -157,7 +152,7 @@ public class InetSocketAddress extends SocketAddress { * @return the socket endpoint hostname. */ public final String getHostName() { - return (null != addr) ? addr.getHostName() : hostname; + return (addr != null) ? addr.getHostName() : hostname; } /** @@ -178,13 +173,7 @@ public class InetSocketAddress extends SocketAddress { */ @Override public String toString() { - String host; - if (addr != null) { - host = addr.toString(); - } else { - host = hostname; - } - return host + ":" + port; + return ((addr != null) ? addr.toString() : hostname) + ":" + port; } /** @@ -235,8 +224,7 @@ public class InetSocketAddress extends SocketAddress { return addr.hashCode() + port; } - private void readObject(ObjectInputStream stream) throws IOException, - ClassNotFoundException { + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); } } diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java index 457b0e6..c5675b3 100644 --- a/luni/src/main/java/java/net/NetworkInterface.java +++ b/luni/src/main/java/java/net/NetworkInterface.java @@ -24,6 +24,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import libcore.base.EmptyArray; /** * This class is used to represent a network interface of the local device. An @@ -471,7 +472,7 @@ public final class NetworkInterface extends Object { */ public byte[] getHardwareAddress() throws SocketException { if (addresses.isEmpty()) { - return new byte[0]; + return EmptyArray.BYTE; } return getHardwareAddressImpl(name); } diff --git a/luni/src/main/java/java/net/Proxy.java b/luni/src/main/java/java/net/Proxy.java index 752ea2b..b9d38ae 100644 --- a/luni/src/main/java/java/net/Proxy.java +++ b/luni/src/main/java/java/net/Proxy.java @@ -102,11 +102,15 @@ public class Proxy { */ @Override public String toString() { - String proxyString = String.valueOf(type); - if (null != address) { - proxyString += "/" + address.toString(); + StringBuilder builder = new StringBuilder(); + if (type != null) { + builder.append(type.toString()); + } + builder.append("@"); + if (type != Proxy.Type.DIRECT && address != null) { + builder.append(address.toString()); } - return proxyString; + return builder.toString(); } /** diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java index 71441d3..a1b95bf 100644 --- a/luni/src/main/java/java/net/SocketOptions.java +++ b/luni/src/main/java/java/net/SocketOptions.java @@ -101,10 +101,13 @@ public interface SocketOptions { public static final int SO_KEEPALIVE = 8; /** - * This option specifies the value for the Type-of-Service (TOS) field of - * the IP header. This may be ignored by the underlying OS. - * Values must be between 0 and 255 inclusive. - * See <a href="http://www.ietf.org/rfc/rfc1349.txt">RFC 1349</a> for more information. + * This option specifies the value for the type-of-service field of the IPv4 header, or the + * traffic class field of the IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS + * socket options. These may be ignored by the underlying OS. Values must be between 0 and 255 + * inclusive. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1349.txt">RFC 1349</a> for more about IPv4 + * and <a href="http://www.ietf.org/rfc/rfc2460.txt">RFC 2460</a> for more about IPv6. */ public static final int IP_TOS = 3; diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java index 7750830..6cc1217 100644 --- a/luni/src/main/java/java/nio/DatagramChannelImpl.java +++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java @@ -34,6 +34,7 @@ import java.nio.channels.DatagramChannel; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.spi.SelectorProvider; +import libcore.base.EmptyArray; import org.apache.harmony.luni.net.PlainDatagramSocketImpl; import org.apache.harmony.luni.platform.FileDescriptorHandler; import org.apache.harmony.luni.platform.INetworkSystem; @@ -43,8 +44,6 @@ import org.apache.harmony.luni.platform.Platform; * The default implementation class of java.nio.channels.DatagramChannel. */ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorHandler { - private static final byte[] stubArray = new byte[0]; - // The fd to interact with native code private final FileDescriptor fd; @@ -255,7 +254,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorHandl private SocketAddress receiveDirectImpl(ByteBuffer target, boolean loop) throws IOException { SocketAddress retAddr = null; - DatagramPacket receivePacket = new DatagramPacket(stubArray, 0); + DatagramPacket receivePacket = new DatagramPacket(EmptyArray.BYTE, 0); int oldposition = target.position(); int received = 0; do { diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java index e01181a..bf673b8 100644 --- a/luni/src/main/java/java/nio/SelectorImpl.java +++ b/luni/src/main/java/java/nio/SelectorImpl.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import libcore.base.EmptyArray; import org.apache.harmony.luni.platform.FileDescriptorHandler; import org.apache.harmony.luni.platform.Platform; @@ -43,9 +44,8 @@ import org.apache.harmony.luni.platform.Platform; */ final class SelectorImpl extends AbstractSelector { - private static final int[] EMPTY_INT_ARRAY = new int[0]; + static final FileDescriptor[] EMPTY_FILE_DESCRIPTORS_ARRAY = new FileDescriptor[0]; - private static final FileDescriptor[] EMPTY_FILE_DESCRIPTORS_ARRAY = new FileDescriptor[0]; private static final SelectionKeyImpl[] EMPTY_SELECTION_KEY_IMPLS_ARRAY = new SelectionKeyImpl[0]; @@ -121,7 +121,7 @@ final class SelectorImpl extends AbstractSelector { * actively selecting, all elements are 0. Corresponds to the ready keys * set. */ - private int[] flags = EMPTY_INT_ARRAY; + private int[] flags = EmptyArray.INT; public SelectorImpl(SelectorProvider selectorProvider) throws IOException { super(selectorProvider); diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java index abea8cf..240a0ba 100644 --- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java +++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java @@ -82,7 +82,7 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD if (!isBlocking) { int[] tryResult = new int[1]; boolean success = Platform.NETWORK.select(new FileDescriptor[] { fd }, - new FileDescriptor[0], 1, 0, 0, tryResult); + SelectorImpl.EMPTY_FILE_DESCRIPTORS_ARRAY, 1, 0, 0, tryResult); if (!success || 0 == tryResult[0]) { // no pending connections, returns immediately. return null; diff --git a/luni/src/main/java/java/security/cert/X509CertSelector.java b/luni/src/main/java/java/security/cert/X509CertSelector.java index 8745bf2..33e68c7 100644 --- a/luni/src/main/java/java/security/cert/X509CertSelector.java +++ b/luni/src/main/java/java/security/cert/X509CertSelector.java @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; +import libcore.base.EmptyArray; import org.apache.harmony.security.asn1.ASN1OctetString; import org.apache.harmony.security.x509.AlgorithmIdentifier; import org.apache.harmony.security.x509.CertificatePolicies; @@ -1281,8 +1282,7 @@ public class X509CertSelector implements CertSelector { // initialize the check map for (int i=0; i<9; i++) { map[i] = (subjectAltNames[i] == null) - ? new boolean[0] - : new boolean[subjectAltNames[i].size()]; + ? EmptyArray.BOOLEAN : new boolean[subjectAltNames[i].size()]; } Iterator it = sans.iterator(); while (it.hasNext()) { diff --git a/luni/src/main/java/java/text/ChoiceFormat.java b/luni/src/main/java/java/text/ChoiceFormat.java index 4d689df..b594efc 100644 --- a/luni/src/main/java/java/text/ChoiceFormat.java +++ b/luni/src/main/java/java/text/ChoiceFormat.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; +import libcore.base.EmptyArray; /** * Returns a fixed string based on a numeric value. The class can be used in @@ -147,8 +148,8 @@ public class ChoiceFormat extends NumberFormat { index = skipWhitespace(template, position.getIndex()); if (position.getErrorIndex() != -1 || index >= length) { // Fix Harmony 540 - choiceLimits = new double[0]; - choiceFormats = new String[0]; + choiceLimits = EmptyArray.DOUBLE; + choiceFormats = EmptyArray.STRING; return; } char ch = template.charAt(index++); diff --git a/luni/src/main/java/java/text/MessageFormat.java b/luni/src/main/java/java/text/MessageFormat.java index d740437..5abc9c8 100644 --- a/luni/src/main/java/java/text/MessageFormat.java +++ b/luni/src/main/java/java/text/MessageFormat.java @@ -27,6 +27,7 @@ import java.util.Date; import java.util.Iterator; import java.util.Locale; import java.util.Vector; +import libcore.base.EmptyArray; import libcore.base.Objects; /** @@ -866,7 +867,7 @@ public class MessageFormat extends Format { */ public Object[] parse(String string, ParsePosition position) { if (string == null) { - return new Object[0]; + return EmptyArray.OBJECT; } ParsePosition internalPos = new ParsePosition(0); int offset = position.getIndex(); diff --git a/luni/src/main/java/java/util/AbstractQueue.java b/luni/src/main/java/java/util/AbstractQueue.java index 8ec69f9..d368ac9 100644 --- a/luni/src/main/java/java/util/AbstractQueue.java +++ b/luni/src/main/java/java/util/AbstractQueue.java @@ -1,127 +1,163 @@ -/* Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain */ package java.util; +// BEGIN android-note +// removed link to collections framework docs +// END android-note + /** - * AbstractQueue is an abstract class which implements some of the methods in - * {@link Queue}. The provided implementations of {@code add, remove} and - * {@code element} are based on {@code offer, poll}, and {@code peek} except - * that they throw exceptions to indicate some error instead of returning true - * or false. + * This class provides skeletal implementations of some {@link Queue} + * operations. The implementations in this class are appropriate when + * the base implementation does <em>not</em> allow <tt>null</tt> + * elements. Methods {@link #add add}, {@link #remove remove}, and + * {@link #element element} are based on {@link #offer offer}, {@link + * #poll poll}, and {@link #peek peek}, respectively, but throw + * exceptions instead of indicating failure via <tt>false</tt> or + * <tt>null</tt> returns. + * + * <p>A <tt>Queue</tt> implementation that extends this class must + * minimally define a method {@link Queue#offer} which does not permit + * insertion of <tt>null</tt> elements, along with methods {@link + * Queue#peek}, {@link Queue#poll}, {@link Collection#size}, and + * {@link Collection#iterator}. Typically, additional methods will be + * overridden as well. If these requirements cannot be met, consider + * instead subclassing {@link AbstractCollection}. * - * @param <E> - * the type of the element in the collection. + * @since 1.5 + * @author Doug Lea + * @param <E> the type of elements held in this collection */ -public abstract class AbstractQueue<E> extends AbstractCollection<E> implements - Queue<E> { +public abstract class AbstractQueue<E> + extends AbstractCollection<E> + implements Queue<E> { /** - * Constructor to be used by subclasses. + * Constructor for use by subclasses. */ protected AbstractQueue() { - super(); } /** - * Adds an element to the queue. + * Inserts the specified element into this queue if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an <tt>IllegalStateException</tt> + * if no space is currently available. * - * @param o - * the element to be added to the queue. - * @return {@code true} if the operation succeeds, otherwise {@code false}. - * @throws IllegalStateException - * if the element is not allowed to be added to the queue. + * <p>This implementation returns <tt>true</tt> if <tt>offer</tt> succeeds, + * else throws an <tt>IllegalStateException</tt>. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this queue + * @throws NullPointerException if the specified element is null and + * this queue does not permit null elements + * @throws IllegalArgumentException if some property of this element + * prevents it from being added to this queue */ - @Override - public boolean add(E o) { - if (null == o) { - throw new NullPointerException(); - } - if (offer(o)) { + public boolean add(E e) { + if (offer(e)) return true; - } - throw new IllegalStateException(); + else + throw new IllegalStateException("Queue full"); } /** - * Adds all the elements of a collection to the queue. If the collection is - * the queue itself, then an IllegalArgumentException will be thrown. If - * during the process, some runtime exception is thrown, then those elements - * in the collection which have already successfully been added will remain - * in the queue. The result of the method is undefined if the collection is - * modified during the process of the method. + * Retrieves and removes the head of this queue. This method differs + * from {@link #poll poll} only in that it throws an exception if this + * queue is empty. * - * @param c - * the collection to be added to the queue. - * @return {@code true} if the operation succeeds, otherwise {@code false}. - * @throws NullPointerException - * if the collection or any element of it is null. - * @throws IllegalArgumentException - * If the collection to be added to the queue is the queue - * itself. - */ - @Override - public boolean addAll(Collection<? extends E> c) { - if (null == c) { - throw new NullPointerException(); - } - if (this == c) { - throw new IllegalArgumentException(); - } - return super.addAll(c); - } - - /** - * Removes the element at the head of the queue and returns it. + * <p>This implementation returns the result of <tt>poll</tt> + * unless the queue is empty. * - * @return the element at the head of the queue. - * @throws NoSuchElementException - * if the queue is empty. + * @return the head of this queue + * @throws NoSuchElementException if this queue is empty */ public E remove() { - E o = poll(); - if (null == o) { + E x = poll(); + if (x != null) + return x; + else throw new NoSuchElementException(); - } - return o; } /** - * Returns but does not remove the element at the head of the queue. + * Retrieves, but does not remove, the head of this queue. This method + * differs from {@link #peek peek} only in that it throws an exception if + * this queue is empty. * - * @return the element at the head of the queue. - * @throws NoSuchElementException - * if the queue is empty. + * <p>This implementation returns the result of <tt>peek</tt> + * unless the queue is empty. + * + * @return the head of this queue + * @throws NoSuchElementException if this queue is empty */ public E element() { - E o = peek(); - if (null == o) { + E x = peek(); + if (x != null) + return x; + else throw new NoSuchElementException(); - } - return o; } /** - * Removes all elements of the queue, leaving it empty. + * Removes all of the elements from this queue. + * The queue will be empty after this call returns. + * + * <p>This implementation repeatedly invokes {@link #poll poll} until it + * returns <tt>null</tt>. */ - @Override public void clear() { - E o; - do { - o = poll(); - } while (null != o); + while (poll() != null) + ; + } + + /** + * Adds all of the elements in the specified collection to this + * queue. Attempts to addAll of a queue to itself result in + * <tt>IllegalArgumentException</tt>. Further, the behavior of + * this operation is undefined if the specified collection is + * modified while the operation is in progress. + * + * <p>This implementation iterates over the specified collection, + * and adds each element returned by the iterator to this + * queue, in turn. A runtime exception encountered while + * trying to add an element (including, in particular, a + * <tt>null</tt> element) may result in only some of the elements + * having been successfully added when the associated exception is + * thrown. + * + * @param c collection containing elements to be added to this queue + * @return <tt>true</tt> if this queue changed as a result of the call + * @throws ClassCastException if the class of an element of the specified + * collection prevents it from being added to this queue + * @throws NullPointerException if the specified collection contains a + * null element and this queue does not permit null elements, + * or if the specified collection is null + * @throws IllegalArgumentException if some property of an element of the + * specified collection prevents it from being added to this + * queue, or if the specified collection is this queue + * @throws IllegalStateException if not all the elements can be added at + * this time due to insertion restrictions + * @see #add(Object) + */ + public boolean addAll(Collection<? extends E> c) { + if (c == null) + throw new NullPointerException(); + if (c == this) + throw new IllegalArgumentException(); + boolean modified = false; + for (E e : c) + if (add(e)) + modified = true; + return modified; } + } diff --git a/luni/src/main/java/java/util/ArrayDeque.java b/luni/src/main/java/java/util/ArrayDeque.java index feaac4d..fafcdb4 100644 --- a/luni/src/main/java/java/util/ArrayDeque.java +++ b/luni/src/main/java/java/util/ArrayDeque.java @@ -1,883 +1,838 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Written by Josh Bloch of Google Inc. and released to the public domain, + * as explained at http://creativecommons.org/licenses/publicdomain. */ package java.util; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.lang.reflect.Array; +// BEGIN android-note +// removed link to collections framework docs +// END android-note + +import java.io.*; /** - * An implementation of Deque, backed by an array. + * Resizable-array implementation of the {@link Deque} interface. Array + * deques have no capacity restrictions; they grow as necessary to support + * usage. They are not thread-safe; in the absence of external + * synchronization, they do not support concurrent access by multiple threads. + * Null elements are prohibited. This class is likely to be faster than + * {@link Stack} when used as a stack, and faster than {@link LinkedList} + * when used as a queue. + * + * <p>Most <tt>ArrayDeque</tt> operations run in amortized constant time. + * Exceptions include {@link #remove(Object) remove}, {@link + * #removeFirstOccurrence removeFirstOccurrence}, {@link #removeLastOccurrence + * removeLastOccurrence}, {@link #contains contains}, {@link #iterator + * iterator.remove()}, and the bulk operations, all of which run in linear + * time. * - * ArrayDeques have no size limit, can not contain null element, and they are - * not thread-safe. + * <p>The iterators returned by this class's <tt>iterator</tt> method are + * <i>fail-fast</i>: If the deque is modified at any time after the iterator + * is created, in any way except through the iterator's own <tt>remove</tt> + * method, the iterator will generally throw a {@link + * ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. * - * All optional operations are supported, and the elements can be any objects. + * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw <tt>ConcurrentModificationException</tt> on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: <i>the fail-fast behavior of iterators + * should be used only to detect bugs.</i> * - * @param <E> - * the type of elements in this collection + * <p>This class and its iterator implement all of the + * <em>optional</em> methods of the {@link Collection} and {@link + * Iterator} interfaces. * - * @since 1.6 + * @author Josh Bloch and Doug Lea + * @since 1.6 + * @param <E> the type of elements held in this collection */ -public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, - Cloneable, Serializable { - - private static final long serialVersionUID = 2340985798034038923L; - - private static final int DEFAULT_SIZE = 16; - - private enum DequeStatus { - Empty, Normal, Full; - } - - private transient DequeStatus status; - - private transient int modCount; - - // the pointer of the head element - private transient int front; - - // the pointer of the "next" position of the tail element - private transient int rear; - +public class ArrayDeque<E> extends AbstractCollection<E> + implements Deque<E>, Cloneable, Serializable +{ + /** + * The array in which the elements of the deque are stored. + * The capacity of the deque is the length of this array, which is + * always a power of two. The array is never allowed to become + * full, except transiently within an addX method where it is + * resized (see doubleCapacity) immediately upon becoming full, + * thus avoiding head and tail wrapping around to equal each + * other. We also guarantee that all array cells not holding + * deque elements are always null. + */ private transient E[] elements; - @SuppressWarnings("hiding") - private class ArrayDequeIterator<E> implements Iterator<E> { - private int pos; - - private final int expectedModCount; - - private boolean canRemove; + /** + * The index of the element at the head of the deque (which is the + * element that would be removed by remove() or pop()); or an + * arbitrary number equal to tail if the deque is empty. + */ + private transient int head; - @SuppressWarnings("synthetic-access") - ArrayDequeIterator() { - super(); - pos = front; - expectedModCount = modCount; - canRemove = false; - } + /** + * The index at which the next element would be added to the tail + * of the deque (via addLast(E), add(E), or push(E)). + */ + private transient int tail; - @SuppressWarnings("synthetic-access") - public boolean hasNext() { - if (expectedModCount != modCount) { - return false; - } - return hasNextInternal(); - } + /** + * The minimum capacity that we'll use for a newly created deque. + * Must be a power of 2. + */ + private static final int MIN_INITIAL_CAPACITY = 8; - private boolean hasNextInternal() { - // canRemove means "next" method is called, and the Full - // status can ensure that this method is not called just - // after "remove" method is call.(so, canRemove can keep - // true after "next" method called) - return (pos != rear) - || ((status == DequeStatus.Full) && !canRemove); - } + // ****** Array allocation and resizing utilities ****** - @SuppressWarnings( { "synthetic-access", "unchecked" }) - public E next() { - if (hasNextInternal()) { - E result = (E) elements[pos]; - if (expectedModCount == modCount && null != result) { - canRemove = true; - pos = circularBiggerPos(pos); - return result; - } - throw new ConcurrentModificationException(); - } - throw new NoSuchElementException(); - } + /** + * Allocate empty array to hold the given number of elements. + * + * @param numElements the number of elements to hold + */ + private void allocateElements(int numElements) { + int initialCapacity = MIN_INITIAL_CAPACITY; + // Find the best power of two to hold elements. + // Tests "<=" because arrays aren't kept full. + if (numElements >= initialCapacity) { + initialCapacity = numElements; + initialCapacity |= (initialCapacity >>> 1); + initialCapacity |= (initialCapacity >>> 2); + initialCapacity |= (initialCapacity >>> 4); + initialCapacity |= (initialCapacity >>> 8); + initialCapacity |= (initialCapacity >>> 16); + initialCapacity++; - @SuppressWarnings("synthetic-access") - public void remove() { - if (canRemove) { - int removedPos = circularSmallerPos(pos); - if (expectedModCount == modCount - && null != elements[removedPos]) { - removeInternal(removedPos, true); - canRemove = false; - return; - } - throw new ConcurrentModificationException(); - } - throw new IllegalStateException(); + if (initialCapacity < 0) // Too many elements, must back off + initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements } + elements = (E[]) new Object[initialCapacity]; } - /* - * NOTES:descendingIterator is not fail-fast, according to the documentation - * and test case. + /** + * Double the capacity of this deque. Call only when full, i.e., + * when head and tail have wrapped around to become equal. */ - @SuppressWarnings("hiding") - private class ReverseArrayDequeIterator<E> implements Iterator<E> { - private int pos; - - private final int expectedModCount; - - private boolean canRemove; - - @SuppressWarnings("synthetic-access") - ReverseArrayDequeIterator() { - super(); - expectedModCount = modCount; - pos = circularSmallerPos(rear); - canRemove = false; - } - - @SuppressWarnings("synthetic-access") - public boolean hasNext() { - if (expectedModCount != modCount) { - return false; - } - return hasNextInternal(); - } - - private boolean hasNextInternal() { - // canRemove means "next" method is called, and the Full - // status can ensure that this method is not called just - // after "remove" method is call.(so, canRemove can keep - // true after "next" method called) - return (circularBiggerPos(pos) != front) - || ((status == DequeStatus.Full) && !canRemove); - } - - @SuppressWarnings( { "synthetic-access", "unchecked" }) - public E next() { - if (hasNextInternal()) { - E result = (E) elements[pos]; - canRemove = true; - pos = circularSmallerPos(pos); - return result; - } - throw new NoSuchElementException(); - } + private void doubleCapacity() { + assert head == tail; + int p = head; + int n = elements.length; + int r = n - p; // number of elements to the right of p + int newCapacity = n << 1; + if (newCapacity < 0) + throw new IllegalStateException("Sorry, deque too big"); + Object[] a = new Object[newCapacity]; + System.arraycopy(elements, p, a, 0, r); + System.arraycopy(elements, 0, a, r, p); + elements = (E[])a; + head = 0; + tail = n; + } - @SuppressWarnings("synthetic-access") - public void remove() { - if (canRemove) { - removeInternal(circularBiggerPos(pos), false); - canRemove = false; - return; - } - throw new IllegalStateException(); + /** + * Copies the elements from our element array into the specified array, + * in order (from first to last element in the deque). It is assumed + * that the array is large enough to hold all elements in the deque. + * + * @return its argument + */ + private <T> T[] copyElements(T[] a) { + if (head < tail) { + System.arraycopy(elements, head, a, 0, size()); + } else if (head > tail) { + int headPortionLen = elements.length - head; + System.arraycopy(elements, head, a, 0, headPortionLen); + System.arraycopy(elements, 0, a, headPortionLen, tail); } + return a; } /** - * Constructs a new empty instance of ArrayDeque big enough for 16 elements. + * Constructs an empty array deque with an initial capacity + * sufficient to hold 16 elements. */ public ArrayDeque() { - this(DEFAULT_SIZE); + elements = (E[]) new Object[16]; } /** - * Constructs a new empty instance of ArrayDeque big enough for specified - * number of elements. + * Constructs an empty array deque with an initial capacity + * sufficient to hold the specified number of elements. * - * @param minSize - * the smallest size of the ArrayDeque - */ - @SuppressWarnings("unchecked") - public ArrayDeque(final int minSize) { - int size = countInitSize(minSize); - elements = (E[]) new Object[size]; - front = rear = 0; - status = DequeStatus.Empty; - modCount = 0; - } - - /* - * count out the size for a new deque, and ensure that size >= minSize + * @param numElements lower bound on initial capacity of the deque */ - private int countInitSize(final int minSize) { - int size = Math.max(minSize, DEFAULT_SIZE); - // get the smallest number that not smaller than size, - // and is a power of 2. - size = Integer.highestOneBit(size - 1) << 1; - if (0 >= size) { - size = minSize; - } - return size; + public ArrayDeque(int numElements) { + allocateElements(numElements); } /** - * Constructs a new instance of ArrayDeque containing the elements of the - * specified collection, with the order returned by the collection's - * iterator. + * Constructs a deque containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. (The first element returned by the collection's + * iterator becomes the first element, or <i>front</i> of the + * deque.) * - * @param c - * the source of the elements - * @throws NullPointerException - * if the collection is null + * @param c the collection whose elements are to be placed into the deque + * @throws NullPointerException if the specified collection is null */ - @SuppressWarnings("unchecked") public ArrayDeque(Collection<? extends E> c) { - elements = (E[]) new Object[countInitSize(c.size())]; - front = rear = 0; - status = DequeStatus.Empty; - modCount = 0; - Iterator<? extends E> it = c.iterator(); - while (it.hasNext()) { - addLastImpl(it.next()); - } + allocateElements(c.size()); + addAll(c); } + // The main insertion and extraction methods are addFirst, + // addLast, pollFirst, pollLast. The other methods are defined in + // terms of these. + /** - * {@inheritDoc} + * Inserts the specified element at the front of this deque. * - * @param e - * the element - * @throws NullPointerException - * if the element is null - * @see java.util.Deque#addFirst(java.lang.Object) + * @param e the element to add + * @throws NullPointerException if the specified element is null */ public void addFirst(E e) { - offerFirst(e); + if (e == null) + throw new NullPointerException(); + elements[head = (head - 1) & (elements.length - 1)] = e; + if (head == tail) + doubleCapacity(); } /** - * {@inheritDoc} + * Inserts the specified element at the end of this deque. + * + * <p>This method is equivalent to {@link #add}. * - * @param e - * the element - * @throws NullPointerException - * if the element is null - * @see java.util.Deque#addLast(java.lang.Object) + * @param e the element to add + * @throws NullPointerException if the specified element is null */ public void addLast(E e) { - addLastImpl(e); + if (e == null) + throw new NullPointerException(); + elements[tail] = e; + if ( (tail = (tail + 1) & (elements.length - 1)) == head) + doubleCapacity(); } /** - * {@inheritDoc} + * Inserts the specified element at the front of this deque. * - * @param e - * the element - * @return true - * @throws NullPointerException - * if the element is null - * @see java.util.Deque#offerFirst(java.lang.Object) + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Deque#offerFirst}) + * @throws NullPointerException if the specified element is null */ - public boolean offerFirst(E e) { - checkNull(e); - checkAndExpand(); - front = circularSmallerPos(front); - elements[front] = e; - resetStatus(true); - modCount++; + public boolean offerFirst(E e) { + addFirst(e); return true; } /** - * {@inheritDoc} + * Inserts the specified element at the end of this deque. * - * @param e - * the element - * @return true if the operation succeeds or false if it fails - * @throws NullPointerException - * if the element is null - * @see java.util.Deque#offerLast(java.lang.Object) + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Deque#offerLast}) + * @throws NullPointerException if the specified element is null */ public boolean offerLast(E e) { - return addLastImpl(e); + addLast(e); + return true; } /** - * Inserts the element at the tail of the deque. - * - * @param e - * the element - * @return true if the operation succeeds or false if it fails. - * @throws NullPointerException - * if the element is null - * @see java.util.Queue#offer(java.lang.Object) + * @throws NoSuchElementException {@inheritDoc} */ - public boolean offer(E e) { - return addLastImpl(e); + public E removeFirst() { + E x = pollFirst(); + if (x == null) + throw new NoSuchElementException(); + return x; } /** - * Inserts the element to the tail of the deque. - * - * @param e - * the element - * @return true - * @see java.util.AbstractCollection#add(java.lang.Object) + * @throws NoSuchElementException {@inheritDoc} */ - @Override - public boolean add(E e) { - return addLastImpl(e); + public E removeLast() { + E x = pollLast(); + if (x == null) + throw new NoSuchElementException(); + return x; } - /** - * {@inheritDoc} - * - * @param e - * the element to push - * @throws NullPointerException - * if the element is null - * @see java.util.Deque#push(java.lang.Object) - */ - public void push(E e) { - offerFirst(e); + public E pollFirst() { + int h = head; + E result = elements[h]; // Element is null if deque empty + if (result == null) + return null; + elements[h] = null; // Must null out slot + head = (h + 1) & (elements.length - 1); + return result; + } + + public E pollLast() { + int t = (tail - 1) & (elements.length - 1); + E result = elements[t]; + if (result == null) + return null; + elements[t] = null; + tail = t; + return result; } /** - * {@inheritDoc} - * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Deque#removeFirst() + * @throws NoSuchElementException {@inheritDoc} */ - public E removeFirst() { - checkEmpty(); - return removePollFirstImpl(); + public E getFirst() { + E x = elements[head]; + if (x == null) + throw new NoSuchElementException(); + return x; } /** - * Gets and removes the head element of this deque. This method throws an - * exception if the deque is empty. - * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Queue#remove() + * @throws NoSuchElementException {@inheritDoc} */ - public E remove() { - return removeFirst(); + public E getLast() { + E x = elements[(tail - 1) & (elements.length - 1)]; + if (x == null) + throw new NoSuchElementException(); + return x; + } + + public E peekFirst() { + return elements[head]; // elements[head] is null if deque empty + } + + public E peekLast() { + return elements[(tail - 1) & (elements.length - 1)]; } /** - * {@inheritDoc} + * Removes the first occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Deque#pop() + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if the deque contained the specified element */ - public E pop() { - return removeFirst(); + public boolean removeFirstOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + E x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i + 1) & mask; + } + return false; } /** - * {@inheritDoc} - * - * @return the tail element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Deque#removeLast() - */ - public E removeLast() { - checkEmpty(); - return removeLastImpl(); + * Removes the last occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if the deque contained the specified element + */ + public boolean removeLastOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = (tail - 1) & mask; + E x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i - 1) & mask; + } + return false; } + // *** Queue methods *** + /** - * {@inheritDoc} + * Inserts the specified element at the end of this deque. * - * @return the head element or null if the deque is empty - * @see java.util.Deque#pollFirst() + * <p>This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws NullPointerException if the specified element is null */ - public E pollFirst() { - return (status == DequeStatus.Empty) ? null : removePollFirstImpl(); + public boolean add(E e) { + addLast(e); + return true; } /** - * Gets and removes the head element of this deque. This method returns null - * if the deque is empty. + * Inserts the specified element at the end of this deque. + * + * <p>This method is equivalent to {@link #offerLast}. * - * @return the head element or null if the deque is empty - * @see java.util.Queue#poll() + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Queue#offer}) + * @throws NullPointerException if the specified element is null */ - public E poll() { - return pollFirst(); + public boolean offer(E e) { + return offerLast(e); } /** - * {@inheritDoc} + * Retrieves and removes the head of the queue represented by this deque. + * + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #removeFirst}. * - * @return the tail element or null if the deque is empty - * @see java.util.Deque#pollLast() + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} */ - public E pollLast() { - return (status == DequeStatus.Empty) ? null : removeLastImpl(); + public E remove() { + return removeFirst(); } /** - * {@inheritDoc} + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #pollFirst}. * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Deque#getFirst() + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty */ - public E getFirst() { - checkEmpty(); - return elements[front]; + public E poll() { + return pollFirst(); } /** - * Gets but does not remove the head element of this deque. It throws an - * exception if the deque is empty. + * Retrieves, but does not remove, the head of the queue represented by + * this deque. This method differs from {@link #peek peek} only in + * that it throws an exception if this deque is empty. * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Queue#element() + * <p>This method is equivalent to {@link #getFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} */ public E element() { return getFirst(); } /** - * {@inheritDoc} + * Retrieves, but does not remove, the head of the queue represented by + * this deque, or returns <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #peekFirst}. * - * @return the tail element - * @throws NoSuchElementException - * if the deque is empty - * @see java.util.Deque#getLast() + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty */ - public E getLast() { - checkEmpty(); - return elements[circularSmallerPos(rear)]; + public E peek() { + return peekFirst(); } + // *** Stack methods *** + /** - * {@inheritDoc} + * Pushes an element onto the stack represented by this deque. In other + * words, inserts the element at the front of this deque. + * + * <p>This method is equivalent to {@link #addFirst}. * - * @return the head element or null if the deque is empty - * @see java.util.Deque#peekFirst() + * @param e the element to push + * @throws NullPointerException if the specified element is null */ - public E peekFirst() { - return (status == DequeStatus.Empty) ? null : elements[front]; + public void push(E e) { + addFirst(e); } /** - * Gets but not removes the head element of this deque. This method returns - * null if the deque is empty. + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. + * + * <p>This method is equivalent to {@link #removeFirst()}. * - * @return the head element or null if the deque is empty - * @see java.util.Queue#peek() + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException {@inheritDoc} */ - public E peek() { - return (status == DequeStatus.Empty) ? null : elements[front]; + public E pop() { + return removeFirst(); + } + + private void checkInvariants() { + assert elements[tail] == null; + assert head == tail ? elements[head] == null : + (elements[head] != null && + elements[(tail - 1) & (elements.length - 1)] != null); + assert elements[(head - 1) & (elements.length - 1)] == null; } /** - * {@inheritDoc} + * Removes the element at the specified position in the elements array, + * adjusting head and tail as necessary. This can result in motion of + * elements backwards or forwards in the array. * - * @return the tail element or null if the deque is empty - * @see java.util.Deque#peekLast() + * <p>This method is called delete rather than remove to emphasize + * that its semantics differ from those of {@link List#remove(int)}. + * + * @return true if elements moved backwards */ - public E peekLast() { - return (status == DequeStatus.Empty) ? null - : elements[circularSmallerPos(rear)]; - } + private boolean delete(int i) { + checkInvariants(); + final E[] elements = this.elements; + final int mask = elements.length - 1; + final int h = head; + final int t = tail; + final int front = (i - h) & mask; + final int back = (t - i) & mask; - private void checkNull(E e) { - if (null == e) { - throw new NullPointerException(); - } - } + // Invariant: head <= i < tail mod circularity + if (front >= ((t - h) & mask)) + throw new ConcurrentModificationException(); - private void checkEmpty() { - if (status == DequeStatus.Empty) { - throw new NoSuchElementException(); + // Optimize for least element motion + if (front < back) { + if (h <= i) { + System.arraycopy(elements, h, elements, h + 1, front); + } else { // Wrap around + System.arraycopy(elements, 0, elements, 1, i); + elements[0] = elements[mask]; + System.arraycopy(elements, h, elements, h + 1, mask - h); + } + elements[h] = null; + head = (h + 1) & mask; + return false; + } else { + if (i < t) { // Copy the null tail as well + System.arraycopy(elements, i + 1, elements, i, back); + tail = t - 1; + } else { // Wrap around + System.arraycopy(elements, i + 1, elements, i, mask - i); + elements[mask] = elements[0]; + System.arraycopy(elements, 1, elements, 0, t); + tail = (t - 1) & mask; + } + return true; } } - private int circularSmallerPos(int current) { - return (current - 1 < 0) ? (elements.length - 1) : current - 1; - } + // *** Collection Methods *** - private int circularBiggerPos(int current) { - return (current + 1 >= elements.length) ? 0 : current + 1; + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size() { + return (tail - head) & (elements.length - 1); } - @SuppressWarnings("unchecked") - /* - * If array of elements is full, there will be a new bigger array to store - * the elements. + /** + * Returns <tt>true</tt> if this deque contains no elements. + * + * @return <tt>true</tt> if this deque contains no elements */ - private void checkAndExpand() { - if (status != DequeStatus.Full) { - return; - } - if (Integer.MAX_VALUE == elements.length) { - throw new IllegalStateException(); - } - int length = elements.length; - int newLength = length * 2; - // bigger than Integer.MAX_VALUE - if (newLength < 0) { - newLength = Integer.MAX_VALUE; - } - E[] newElements = (E[]) new Object[newLength]; - System.arraycopy(elements, front, newElements, 0, length - front); - System.arraycopy(elements, 0, newElements, length - front, front); - front = 0; - rear = length; - status = DequeStatus.Normal; - elements = newElements; + public boolean isEmpty() { + return head == tail; } /** - * Resets the status after adding or removing operation. + * Returns an iterator over the elements in this deque. The elements + * will be ordered from first (head) to last (tail). This is the same + * order that elements would be dequeued (via successive calls to + * {@link #remove} or popped (via successive calls to {@link #pop}). * - * @param adding - * if the method is called after an "adding" operation + * @return an iterator over the elements in this deque */ - private void resetStatus(boolean adding) { - if (front == rear) { - status = adding ? DequeStatus.Full : DequeStatus.Empty; - } else { - status = DequeStatus.Normal; - } + public Iterator<E> iterator() { + return new DeqIterator(); } - private boolean addLastImpl(E e) { - checkNull(e); - checkAndExpand(); - elements[rear] = e; - rear = circularBiggerPos(rear); - resetStatus(true); - modCount++; - return true; + public Iterator<E> descendingIterator() { + return new DescendingIterator(); } - private E removePollFirstImpl() { - E element = elements[front]; - elements[front] = null; - front = circularBiggerPos(front); - resetStatus(false); - modCount++; - return element; - } + private class DeqIterator implements Iterator<E> { + /** + * Index of element to be returned by subsequent call to next. + */ + private int cursor = head; - private E removeLastImpl() { - int last = circularSmallerPos(rear); - E element = elements[last]; - elements[last] = null; - rear = last; - resetStatus(false); - modCount++; - return element; - } + /** + * Tail recorded at construction (also in remove), to stop + * iterator and also to check for comodification. + */ + private int fence = tail; - /** - * {@inheritDoc} - * - * @param obj - * the element to be removed - * @return true if the operation succeeds or false if the deque does not - * contain the element - * @see java.util.Deque#removeFirstOccurrence(java.lang.Object) - */ - public boolean removeFirstOccurrence(Object obj) { - return removeFirstOccurrenceImpl(obj); - } + /** + * Index of element returned by most recent call to next. + * Reset to -1 if element is deleted by a call to remove. + */ + private int lastRet = -1; - /** - * Removes the first equivalent element of the specified object. If the - * deque does not contain the element, it is unchanged and returns false. - * - * @param obj - * the element to be removed - * @return true if the operation succeeds or false if the deque does not - * contain the element - * @see java.util.AbstractCollection#remove(java.lang.Object) - */ - @Override - public boolean remove(Object obj) { - return removeFirstOccurrenceImpl(obj); - } + public boolean hasNext() { + return cursor != fence; + } - /** - * {@inheritDoc} - * - * @param obj - * the element to be removed - * @return true if the operation succeeds or false if the deque does not - * contain the element. - * @see java.util.Deque#removeLastOccurrence(java.lang.Object) - */ - public boolean removeLastOccurrence(final Object obj) { - if (null != obj) { - Iterator<E> iter = descendingIterator(); - while (iter.hasNext()) { - if (iter.next().equals(obj)) { - iter.remove(); - return true; - } - } + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + E result = elements[cursor]; + // This check doesn't catch all possible comodifications, + // but does catch the ones that corrupt traversal + if (tail != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + cursor = (cursor + 1) & (elements.length - 1); + return result; } - return false; - } - private boolean removeFirstOccurrenceImpl(final Object obj) { - if (null != obj) { - Iterator<E> iter = iterator(); - while (iter.hasNext()) { - if (iter.next().equals(obj)) { - iter.remove(); - return true; - } + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (delete(lastRet)) { // if left-shifted, undo increment in next() + cursor = (cursor - 1) & (elements.length - 1); + fence = tail; } + lastRet = -1; } - return false; } - /* - * Removes the element in the cursor position and shifts front elements to - * fill the gap if frontShift is true, shifts rear elements otherwise. - * - */ - private void removeInternal(final int current, final boolean frontShift) { - int cursor = current; - if (frontShift) { - while (cursor != front) { - int next = circularSmallerPos(cursor); - elements[cursor] = elements[next]; - cursor = next; - } - front = circularBiggerPos(front); - } else { - while (cursor != rear) { - int next = circularBiggerPos(cursor); - elements[cursor] = elements[next]; - cursor = next; - } - rear = circularSmallerPos(rear); + private class DescendingIterator implements Iterator<E> { + /* + * This class is nearly a mirror-image of DeqIterator, using + * tail instead of head for initial cursor, and head instead of + * tail for fence. + */ + private int cursor = tail; + private int fence = head; + private int lastRet = -1; + + public boolean hasNext() { + return cursor != fence; } - elements[cursor] = null; - resetStatus(false); - } - /** - * Returns the size of the deque. - * - * @return the size of the deque - * @see java.util.AbstractCollection#size() - */ - @Override - public int size() { - if (status == DequeStatus.Full) { - return elements.length; + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + cursor = (cursor - 1) & (elements.length - 1); + E result = elements[cursor]; + if (head != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + return result; } - return (front <= rear) ? (rear - front) - : (rear + elements.length - front); - } - /** - * Returns true if the deque has no elements. - * - * @return true if the deque has no elements, false otherwise - * @see java.util.AbstractCollection#isEmpty() - */ - @Override - public boolean isEmpty() { - return 0 == size(); + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (!delete(lastRet)) { + cursor = (cursor + 1) & (elements.length - 1); + fence = head; + } + lastRet = -1; + } } /** - * Returns true if the specified element is in the deque. + * Returns <tt>true</tt> if this deque contains the specified element. + * More formally, returns <tt>true</tt> if and only if this deque contains + * at least one element <tt>e</tt> such that <tt>o.equals(e)</tt>. * - * @param obj - * the element - * @return true if the element is in the deque, false otherwise - * @see java.util.AbstractCollection#contains(java.lang.Object) + * @param o object to be checked for containment in this deque + * @return <tt>true</tt> if this deque contains the specified element */ - @SuppressWarnings("cast") - @Override - public boolean contains(final Object obj) { - if (null != obj) { - Iterator<E> it = new ArrayDequeIterator<E>(); - while (it.hasNext()) { - if (obj.equals((E) it.next())) { - return true; - } - } + public boolean contains(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + E x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) + return true; + i = (i + 1) & mask; } return false; } /** - * Empty the deque. + * Removes a single instance of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>o.equals(e)</tt> (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * - * @see java.util.AbstractCollection#clear() + * <p>This method is equivalent to {@link #removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if this deque contained the specified element */ - @SuppressWarnings("cast") - @Override - public void clear() { - if (status != DequeStatus.Empty) { - int cursor = front; - do { - elements[cursor] = null; - cursor = circularBiggerPos(cursor); - } while (cursor != rear); - status = DequeStatus.Empty; - } - front = rear = 0; - modCount = 0; + public boolean remove(Object o) { + return removeFirstOccurrence(o); } /** - * Returns a clone of the deque. - * - * @return the clone of the deque - * @see java.lang.Object#clone() - * @see java.lang.Cloneable + * Removes all of the elements from this deque. + * The deque will be empty after this call returns. */ - @SuppressWarnings("unchecked") - @Override - public ArrayDeque<E> clone() { - try { - ArrayDeque<E> newDeque = (ArrayDeque<E>) super.clone(); - newDeque.elements = elements.clone(); - return newDeque; - } catch (CloneNotSupportedException e) { - return null; + public void clear() { + int h = head; + int t = tail; + if (h != t) { // clear all cells + head = tail = 0; + int i = h; + int mask = elements.length - 1; + do { + elements[i] = null; + i = (i + 1) & mask; + } while (i != t); } } /** - * Returns all the elements in an array from head to tail. The result is a - * copy of all the elements. + * Returns an array containing all of the elements in this deque + * in proper sequence (from first to last element). + * + * <p>The returned array will be "safe" in that no references to it are + * maintained by this deque. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + * <p>This method acts as bridge between array-based and collection-based + * APIs. * - * @return the Array of all the elements - * @see java.util.AbstractCollection#toArray() + * @return an array containing all of the elements in this deque */ - @Override public Object[] toArray() { - return newArray(new Object[size()]); + return copyElements(new Object[size()]); } /** - * Returns all the elements in an array from head to tail, and the type of - * the result array is the type of the argument array. If the argument array - * is big enough, the elements from the deque will be stored in it(elements - * following the tail of the deque is set to null, if any); otherwise, it - * will return a new array with the size of the argument array and size of - * the deque. + * Returns an array containing all of the elements in this deque in + * proper sequence (from first to last element); the runtime type of the + * returned array is that of the specified array. If the deque fits in + * the specified array, it is returned therein. Otherwise, a new array + * is allocated with the runtime type of the specified array and the + * size of this deque. * - * @param array - * the array stores all the elements from the deque, if it has - * enough space; otherwise, a new array of the same type and the - * size of the deque will be used - * @return the Array of all the elements - * @throws ArrayStoreException - * if the type of the argument array is not compatible with - * every element in the deque - * @throws NullPointerException - * if the argument array is null - * @see java.util.AbstractCollection#toArray + * <p>If this deque fits in the specified array with room to spare + * (i.e., the array has more elements than this deque), the element in + * the array immediately following the end of the deque is set to + * <tt>null</tt>. + * + * <p>Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + * <p>Suppose <tt>x</tt> is a deque known to contain only strings. + * The following code can be used to dump the deque into a newly + * allocated array of <tt>String</tt>: + * + * <pre> + * String[] y = x.toArray(new String[0]);</pre> + * + * Note that <tt>toArray(new Object[0])</tt> is identical in function to + * <tt>toArray()</tt>. + * + * @param a the array into which the elements of the deque are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this deque + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this deque + * @throws NullPointerException if the specified array is null */ - @Override - public <T> T[] toArray(T[] array) { - return newArray(array); - } - - @SuppressWarnings("unchecked") - private <T> T[] newArray(T[] array) { + public <T> T[] toArray(T[] a) { int size = size(); - if (size > array.length) { - Class<?> clazz = array.getClass().getComponentType(); - array = (T[]) Array.newInstance(clazz, size); - } - if (front < rear) { - System.arraycopy(elements, front, array, 0, size); - } else if (size != 0) { - int length = elements.length; - System.arraycopy(elements, front, array, 0, length - front); - System.arraycopy(elements, 0, array, length - front, rear); - } - if (size < array.length) { - array[size] = null; - } - return array; + if (a.length < size) + a = (T[])java.lang.reflect.Array.newInstance( + a.getClass().getComponentType(), size); + copyElements(a); + if (a.length > size) + a[size] = null; + return a; } + // *** Object methods *** + /** - * Returns the iterator of the deque. The elements will be ordered from head - * to tail. + * Returns a copy of this deque. * - * @return the iterator - * @see java.util.AbstractCollection#iterator() + * @return a copy of this deque */ - @SuppressWarnings("synthetic-access") - @Override - public Iterator<E> iterator() { - return new ArrayDequeIterator<E>(); + public ArrayDeque<E> clone() { + try { + ArrayDeque<E> result = (ArrayDeque<E>) super.clone(); + result.elements = Arrays.copyOf(elements, elements.length); + return result; + + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } } /** - * {@inheritDoc} - * - * @return the reverse order Iterator - * @see java.util.Deque#descendingIterator() + * Appease the serialization gods. */ - public Iterator<E> descendingIterator() { - return new ReverseArrayDequeIterator<E>(); - } + private static final long serialVersionUID = 2340985798034038923L; /** - * Deserialization method. + * Serialize this deque. * - * @param stream - * the ObjectInputStream - * @throws IOException - * @throws ClassNotFoundException + * @serialData The current size (<tt>int</tt>) of the deque, + * followed by all of its elements (each an object reference) in + * first-to-last order. */ - @SuppressWarnings("unchecked") - private void readObject(ObjectInputStream stream) throws IOException, - ClassNotFoundException { - stream.defaultReadObject(); - int size = stream.readInt(); - elements = (E[]) new Object[countInitSize(size)]; - front = rear = 0; - status = DequeStatus.Empty; - modCount = 0; - for (int i = 0; i < size; i++) { - addLastImpl((E) stream.readObject()); - } + private void writeObject(ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + + // Write out size + s.writeInt(size()); + + // Write out elements in order. + int mask = elements.length - 1; + for (int i = head; i != tail; i = (i + 1) & mask) + s.writeObject(elements[i]); } /** - * Serialization method. - * - * @param stream - * the ObjectOutputStream - * @serialData The current size of the deque, followed by all the elements - * from head to tail. - * @throws IOException - * + * Deserialize this deque. */ - private void writeObject(ObjectOutputStream stream) throws IOException { - stream.defaultWriteObject(); - stream.writeInt(size()); - Iterator<?> it = new ArrayDequeIterator<E>(); - while (it.hasNext()) { - stream.writeObject(it.next()); - } - } + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Read in size and allocate array + int size = s.readInt(); + allocateElements(size); + head = 0; + tail = size; + // Read in all elements in the proper order. + for (int i = 0; i < size; i++) + elements[i] = (E)s.readObject(); + } } diff --git a/luni/src/main/java/java/util/ArrayList.java b/luni/src/main/java/java/util/ArrayList.java index 19d2449..d07ca60 100644 --- a/luni/src/main/java/java/util/ArrayList.java +++ b/luni/src/main/java/java/util/ArrayList.java @@ -27,6 +27,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; +import libcore.base.EmptyArray; /** * ArrayList is an implementation of {@link List}, backed by an array. @@ -44,13 +45,7 @@ import java.lang.reflect.Array; * @param <E> The element type of this list. * @since 1.2 */ -public class ArrayList<E> extends AbstractList<E> - implements Cloneable, Serializable, RandomAccess { - /** - * An empty array of objects (to be shared among all empty lists). - */ - private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - +public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess { /** * The minimum amount by which the capacity of an ArrayList will increase. * This tuning parameter controls a time-space tradeoff. This value (12) @@ -81,14 +76,14 @@ public class ArrayList<E> extends AbstractList<E> if (capacity < 0) { throw new IllegalArgumentException(); } - array = (capacity == 0 ? EMPTY_OBJECT_ARRAY : new Object[capacity]); + array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]); } /** * Constructs a new {@code ArrayList} instance with zero initial capacity. */ public ArrayList() { - array = EMPTY_OBJECT_ARRAY; + array = EmptyArray.OBJECT; } /** @@ -542,7 +537,7 @@ public class ArrayList<E> extends AbstractList<E> return; } if (s == 0) { - array = EMPTY_OBJECT_ARRAY; + array = EmptyArray.OBJECT; } else { Object[] newArray = new Object[s]; System.arraycopy(array, 0, newArray, 0, s); @@ -652,15 +647,14 @@ public class ArrayList<E> extends AbstractList<E> } } - private void readObject(ObjectInputStream stream) throws IOException, - ClassNotFoundException { + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int cap = stream.readInt(); if (cap < size) { throw new InvalidObjectException( "Capacity: " + cap + " < size: " + size); } - array = (cap == 0 ? EMPTY_OBJECT_ARRAY : new Object[cap]); + array = (cap == 0 ? EmptyArray.OBJECT : new Object[cap]); for (int i = 0; i < size; i++) { array[i] = stream.readObject(); } diff --git a/luni/src/main/java/java/util/Arrays.java b/luni/src/main/java/java/util/Arrays.java index d5bfbc8..5f38db3 100644 --- a/luni/src/main/java/java/util/Arrays.java +++ b/luni/src/main/java/java/util/Arrays.java @@ -114,15 +114,9 @@ public class Arrays { @Override public E set(int location, E object) { - try { - E result = a[location]; - a[location] = object; - return result; - } catch (ArrayIndexOutOfBoundsException e) { - throw new IndexOutOfBoundsException(); - } catch (ArrayStoreException e) { - throw new ClassCastException(); - } + E result = a[location]; + a[location] = object; + return result; } @Override diff --git a/luni/src/main/java/java/util/Deque.java b/luni/src/main/java/java/util/Deque.java index 7ccc6f4..cb6bd90 100644 --- a/luni/src/main/java/java/util/Deque.java +++ b/luni/src/main/java/java/util/Deque.java @@ -1,255 +1,548 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Written by Doug Lea and Josh Bloch with assistance from members of + * JCP JSR-166 Expert Group and released to the public domain, as explained + * at http://creativecommons.org/licenses/publicdomain */ package java.util; +// BEGIN android-note +// removed link to collections framework docs +// changed {@link #offer(Object)} to {@link #offer} to satisfy DroidDoc +// END android-note + /** - * A kind of collection that can insert or remove element at both ends("double - * ended queue"). Mostly a deque has no limit of its size. + * A linear collection that supports element insertion and removal at + * both ends. The name <i>deque</i> is short for "double ended queue" + * and is usually pronounced "deck". Most <tt>Deque</tt> + * implementations place no fixed limits on the number of elements + * they may contain, but this interface supports capacity-restricted + * deques as well as those with no fixed size limit. + * + * <p>This interface defines methods to access the elements at both + * ends of the deque. Methods are provided to insert, remove, and + * examine the element. Each of these methods exists in two forms: + * one throws an exception if the operation fails, the other returns a + * special value (either <tt>null</tt> or <tt>false</tt>, depending on + * the operation). The latter form of the insert operation is + * designed specifically for use with capacity-restricted + * <tt>Deque</tt> implementations; in most implementations, insert + * operations cannot fail. + * + * <p>The twelve methods described above are summarized in the + * following table: + * + * <p> + * <table BORDER CELLPADDING=3 CELLSPACING=1> + * <tr> + * <td></td> + * <td ALIGN=CENTER COLSPAN = 2> <b>First Element (Head)</b></td> + * <td ALIGN=CENTER COLSPAN = 2> <b>Last Element (Tail)</b></td> + * </tr> + * <tr> + * <td></td> + * <td ALIGN=CENTER><em>Throws exception</em></td> + * <td ALIGN=CENTER><em>Special value</em></td> + * <td ALIGN=CENTER><em>Throws exception</em></td> + * <td ALIGN=CENTER><em>Special value</em></td> + * </tr> + * <tr> + * <td><b>Insert</b></td> + * <td>{@link #addFirst addFirst(e)}</td> + * <td>{@link #offerFirst offerFirst(e)}</td> + * <td>{@link #addLast addLast(e)}</td> + * <td>{@link #offerLast offerLast(e)}</td> + * </tr> + * <tr> + * <td><b>Remove</b></td> + * <td>{@link #removeFirst removeFirst()}</td> + * <td>{@link #pollFirst pollFirst()}</td> + * <td>{@link #removeLast removeLast()}</td> + * <td>{@link #pollLast pollLast()}</td> + * </tr> + * <tr> + * <td><b>Examine</b></td> + * <td>{@link #getFirst getFirst()}</td> + * <td>{@link #peekFirst peekFirst()}</td> + * <td>{@link #getLast getLast()}</td> + * <td>{@link #peekLast peekLast()}</td> + * </tr> + * </table> + * + * <p>This interface extends the {@link Queue} interface. When a deque is + * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are + * added at the end of the deque and removed from the beginning. The methods + * inherited from the <tt>Queue</tt> interface are precisely equivalent to + * <tt>Deque</tt> methods as indicated in the following table: + * + * <p> + * <table BORDER CELLPADDING=3 CELLSPACING=1> + * <tr> + * <td ALIGN=CENTER> <b><tt>Queue</tt> Method</b></td> + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#add add(e)}</td> + * <td>{@link #addLast addLast(e)}</td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#offer offer(e)}</td> + * <td>{@link #offerLast offerLast(e)}</td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#remove remove()}</td> + * <td>{@link #removeFirst removeFirst()}</td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#poll poll()}</td> + * <td>{@link #pollFirst pollFirst()}</td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#element element()}</td> + * <td>{@link #getFirst getFirst()}</td> + * </tr> + * <tr> + * <td>{@link java.util.Queue#peek peek()}</td> + * <td>{@link #peek peekFirst()}</td> + * </tr> + * </table> + * + * <p>Deques can also be used as LIFO (Last-In-First-Out) stacks. This + * interface should be used in preference to the legacy {@link Stack} class. + * When a deque is used as a stack, elements are pushed and popped from the + * beginning of the deque. Stack methods are precisely equivalent to + * <tt>Deque</tt> methods as indicated in the table below: * - * <p>Extending from Queue, a deque can be used as a Queue which behavior is - * first-in-first-out. Furthermore, a deque can also be used as a Stack(legacy - * class) which behavior is last-in-first-out. + * <p> + * <table BORDER CELLPADDING=3 CELLSPACING=1> + * <tr> + * <td ALIGN=CENTER> <b>Stack Method</b></td> + * <td ALIGN=CENTER> <b>Equivalent <tt>Deque</tt> Method</b></td> + * </tr> + * <tr> + * <td>{@link #push push(e)}</td> + * <td>{@link #addFirst addFirst(e)}</td> + * </tr> + * <tr> + * <td>{@link #pop pop()}</td> + * <td>{@link #removeFirst removeFirst()}</td> + * </tr> + * <tr> + * <td>{@link #peek peek()}</td> + * <td>{@link #peekFirst peekFirst()}</td> + * </tr> + * </table> * - * <p>A typical deque does not allow null to be inserted as its element, while some - * implementations allow it. But null should not be inserted even in these - * implementations, since method poll return null to indicate that there is no - * element left in the deque. + * <p>Note that the {@link #peek peek} method works equally well when + * a deque is used as a queue or a stack; in either case, elements are + * drawn from the beginning of the deque. * - * <p>A deque can also remove interior elements by removeFirstOccurrence and - * removeLastOccurrence methods. A deque can not access elements by index. + * <p>This interface provides two methods to remove interior + * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and + * {@link #removeLastOccurrence removeLastOccurrence}. * - * @param <E> - * the type of elements in this collection - * @since 1.6 + * <p>Unlike the {@link List} interface, this interface does not + * provide support for indexed access to elements. + * + * <p>While <tt>Deque</tt> implementations are not strictly required + * to prohibit the insertion of null elements, they are strongly + * encouraged to do so. Users of any <tt>Deque</tt> implementations + * that do allow null elements are strongly encouraged <i>not</i> to + * take advantage of the ability to insert nulls. This is so because + * <tt>null</tt> is used as a special return value by various methods + * to indicated that the deque is empty. + * + * <p><tt>Deque</tt> implementations generally do not define + * element-based versions of the <tt>equals</tt> and <tt>hashCode</tt> + * methods, but instead inherit the identity-based versions from class + * <tt>Object</tt>. + * + * @author Doug Lea + * @author Josh Bloch + * @since 1.6 + * @param <E> the type of elements held in this collection */ -public interface Deque<E> extends Queue<E> { +public interface Deque<E> extends Queue<E> { /** - * Inserts an element at the head of this deque if it dose not violate size - * limit immediately. It is better to use offerFirst(E) if a deque is - * size-limited. + * Inserts the specified element at the front of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerFirst}. * - * @param e - * the element - * @throws IllegalStateException - * if it can not add now due to size limit - * @throws ClassCastException - * if the class of element can not be added into this deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element - * @throws IllegalArgumentException - * if the element can not be added due to some property. + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque */ void addFirst(E e); /** - * Inserts an element at the tail of this deque if it dose not violate size - * limit immediately. It is better to use offerLast(E) if a deque is - * size-limited. + * Inserts the specified element at the end of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerLast}. * - * @param e - * the element - * @throws IllegalStateException - * if it can not add now due to size limit - * @throws ClassCastException - * if the class of element can not be added into this deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element - * @throws IllegalArgumentException - * if the element can not be added due to some property. + * <p>This method is equivalent to {@link #add}. + * + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque */ void addLast(E e); /** - * Inserts an element at the head of this deque unless it would violate size - * limit. It is better than the addFirst(E) method in a size-limited deque, - * because the latter one may fail to add the element only by throwing an - * exception. + * Inserts the specified element at the front of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addFirst} method, + * which can fail to insert an element only by throwing an exception. * - * @param e - * the element - * @return true if the operation succeeds or false if it fails. - * @throws ClassCastException - * if the class of element can not be added into this deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element - * @throws IllegalArgumentException - * if the element can not be added due to some property. + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque */ boolean offerFirst(E e); /** - * Inserts an element at the tail of this deque unless it would violate size - * limit. It is better than the addLast(E) method in a size-limited deque, - * because the latter one may fail to add the element only by throwing an - * exception. + * Inserts the specified element at the end of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addLast} method, + * which can fail to insert an element only by throwing an exception. * - * @param e - * the element - * @return true if the operation succeeds or false if it fails - * @throws ClassCastException - * if the class of element can not be added into this deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element - * @throws IllegalArgumentException - * if the element can not be added due to some property + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque */ boolean offerLast(E e); /** - * Gets and removes the head element of this deque. This method throws an - * exception if the deque is empty. + * Retrieves and removes the first element of this deque. This method + * differs from {@link #pollFirst pollFirst} only in that it throws an + * exception if this deque is empty. * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty */ E removeFirst(); /** - * Gets and removes the tail element of this deque. This method throws an - * exception if the deque is empty. + * Retrieves and removes the last element of this deque. This method + * differs from {@link #pollLast pollLast} only in that it throws an + * exception if this deque is empty. * - * @return the tail element - * @throws NoSuchElementException - * if the deque is empty + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty */ E removeLast(); /** - * Gets and removes the head element of this deque. This method returns null - * if the deque is empty. + * Retrieves and removes the first element of this deque, + * or returns <tt>null</tt> if this deque is empty. * - * @return the head element or null if the deque is empty + * @return the head of this deque, or <tt>null</tt> if this deque is empty */ E pollFirst(); /** - * Gets and removes the tail element of this deque. This method returns null - * if the deque is empty. + * Retrieves and removes the last element of this deque, + * or returns <tt>null</tt> if this deque is empty. * - * @return the tail element or null if the deque is empty + * @return the tail of this deque, or <tt>null</tt> if this deque is empty */ E pollLast(); /** - * Gets but not removes the head element of this deque. This method throws - * an exception if the deque is empty. + * Retrieves, but does not remove, the first element of this deque. + * + * This method differs from {@link #peekFirst peekFirst} only in that it + * throws an exception if this deque is empty. * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty */ E getFirst(); /** - * Gets but not removes the tail element of this deque. This method throws - * an exception if the deque is empty. + * Retrieves, but does not remove, the last element of this deque. + * This method differs from {@link #peekLast peekLast} only in that it + * throws an exception if this deque is empty. * - * @return the tail element - * @throws NoSuchElementException - * if the deque is empty + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty */ E getLast(); /** - * Gets but not removes the head element of this deque. This method returns - * null if the deque is empty. + * Retrieves, but does not remove, the first element of this deque, + * or returns <tt>null</tt> if this deque is empty. * - * @return the head element or null if the deque is empty + * @return the head of this deque, or <tt>null</tt> if this deque is empty */ E peekFirst(); /** - * Gets but not removes the tail element of this deque. This method returns - * null if the deque is empty. + * Retrieves, but does not remove, the last element of this deque, + * or returns <tt>null</tt> if this deque is empty. * - * @return the tail element or null if the deque is empty + * @return the tail of this deque, or <tt>null</tt> if this deque is empty */ E peekLast(); /** - * Removes the first equivalent element of the specified object. If the - * deque does not contain the element, it is unchanged and returns false. + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * - * @param o - * the element to be removed - * @return true if the operation succeeds or false if the deque does not - * contain the element. - * @throws ClassCastException - * if the class of the element is incompatible with the deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) */ boolean removeFirstOccurrence(Object o); /** - * Removes the last equivalent element of the specified object. If the deque - * does not contain the element, it is unchanged and returns false. + * Removes the last occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). * - * @param o - * the element to be removed - * @return true if the operation succeeds or false if the deque does not - * contain the element. - * @throws ClassCastException - * if the class of the element is incompatible with the deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) */ boolean removeLastOccurrence(Object o); + // *** Queue methods *** + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an + * <tt>IllegalStateException</tt> if no space is currently available. + * When using a capacity-restricted deque, it is generally preferable to + * use {@link #offer offer}. + * + * <p>This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean add(E e); + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and <tt>false</tt> if no space is currently + * available. When using a capacity-restricted deque, this method is + * generally preferable to the {@link #add} method, which can fail to + * insert an element only by throwing an exception. + * + * <p>This method is equivalent to {@link #offerLast}. + * + * @param e the element to add + * @return <tt>true</tt> if the element was added to this deque, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offer(E e); + /** - * Pushes the element to the deque(at the head of the deque), just same as - * addFirst(E). + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque). + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. * - * @param e - * the element - * @throws IllegalStateException - * if it can not add now due to size limit - * @throws ClassCastException - * if the class of element can not be added into this deque - * @throws NullPointerException - * if the element is null and the deque can not contain null - * element - * @throws IllegalArgumentException - * if the element can not be added due to some property. + * <p>This method is equivalent to {@link #removeFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E remove(); + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #pollFirst()}. + * + * @return the first element of this deque, or <tt>null</tt> if + * this deque is empty + */ + E poll(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque). + * This method differs from {@link #peek peek} only in that it throws an + * exception if this deque is empty. + * + * <p>This method is equivalent to {@link #getFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E element(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque), or + * returns <tt>null</tt> if this deque is empty. + * + * <p>This method is equivalent to {@link #peekFirst()}. + * + * @return the head of the queue represented by this deque, or + * <tt>null</tt> if this deque is empty + */ + E peek(); + + + // *** Stack methods *** + + /** + * Pushes an element onto the stack represented by this deque (in other + * words, at the head of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an + * <tt>IllegalStateException</tt> if no space is currently available. + * + * <p>This method is equivalent to {@link #addFirst}. + * + * @param e the element to push + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque */ void push(E e); /** - * Pops the head element of the deque, just same as removeFirst(). + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. * - * @return the head element - * @throws NoSuchElementException - * if the deque is empty + * <p>This method is equivalent to {@link #removeFirst()}. + * + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException if this deque is empty */ E pop(); + + // *** Collection methods *** + /** - * Returns the iterator in reverse order, from tail to head. + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt> + * (if such an element exists). + * Returns <tt>true</tt> if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * <p>This method is equivalent to {@link #removeFirstOccurrence}. * - * @return the iterator in reverse order + * @param o element to be removed from this deque, if present + * @return <tt>true</tt> if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean remove(Object o); + + /** + * Returns <tt>true</tt> if this deque contains the specified element. + * More formally, returns <tt>true</tt> if and only if this deque contains + * at least one element <tt>e</tt> such that + * <tt>(o==null ? e==null : o.equals(e))</tt>. + * + * @param o element whose presence in this deque is to be tested + * @return <tt>true</tt> if this deque contains the specified element + * @throws ClassCastException if the type of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean contains(Object o); + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size(); + + /** + * Returns an iterator over the elements in this deque in proper sequence. + * The elements will be returned in order from first (head) to last (tail). + * + * @return an iterator over the elements in this deque in proper sequence + */ + Iterator<E> iterator(); + + /** + * Returns an iterator over the elements in this deque in reverse + * sequential order. The elements will be returned in order from + * last (tail) to first (head). + * + * @return an iterator over the elements in this deque in reverse + * sequence */ Iterator<E> descendingIterator(); -}
\ No newline at end of file + +} diff --git a/luni/src/main/java/java/util/EnumMap.java b/luni/src/main/java/java/util/EnumMap.java index 2ccaff1..36ff03a 100644 --- a/luni/src/main/java/java/util/EnumMap.java +++ b/luni/src/main/java/java/util/EnumMap.java @@ -769,9 +769,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements if (null == key) { throw new NullPointerException(); } - if (!isValidKeyType(key)) { - throw new ClassCastException(); - } + keyType.cast(key); // Called to throw ClassCastException. int keyOrdinal = key.ordinal(); if (!hasMapping[keyOrdinal]) { hasMapping[keyOrdinal] = true; diff --git a/luni/src/main/java/java/util/EnumSet.java b/luni/src/main/java/java/util/EnumSet.java index 48bfd3a..1b4723e 100644 --- a/luni/src/main/java/java/util/EnumSet.java +++ b/luni/src/main/java/java/util/EnumSet.java @@ -55,7 +55,7 @@ public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> */ public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { if (!elementType.isEnum()) { - throw new ClassCastException(); + throw new ClassCastException(elementType.getClass().getName() + " is not an Enum"); } // BEGIN android-changed E[] enums = SpecialAccess.LANG.getEnumValuesInOrder(elementType); diff --git a/luni/src/main/java/java/util/HugeEnumSet.java b/luni/src/main/java/java/util/HugeEnumSet.java index ef90d8e..2c6395f 100644 --- a/luni/src/main/java/java/util/HugeEnumSet.java +++ b/luni/src/main/java/java/util/HugeEnumSet.java @@ -121,10 +121,7 @@ final class HugeEnumSet<E extends Enum<E>> extends EnumSet<E> { @Override public boolean add(E element) { - if (!isValidType(element.getDeclaringClass())) { - throw new ClassCastException(); - } - + elementClass.cast(element); // Called to throw ClassCastException. int ordinal = element.ordinal(); int index = ordinal / BIT_IN_LONG; int inBits = ordinal % BIT_IN_LONG; @@ -146,9 +143,7 @@ final class HugeEnumSet<E extends Enum<E>> extends EnumSet<E> { if (collection instanceof EnumSet) { EnumSet<?> set = (EnumSet) collection; // raw type due to javac bug 6548436 - if (!isValidType(set.elementClass)) { - throw new ClassCastException(); - } + set.elementClass.asSubclass(elementClass); // Called to throw ClassCastException. HugeEnumSet<E> hugeSet = (HugeEnumSet<E>) set; boolean changed = false; diff --git a/luni/src/main/java/java/util/IdentityHashMap.java b/luni/src/main/java/java/util/IdentityHashMap.java index 53d3f09..827ffcf 100644 --- a/luni/src/main/java/java/util/IdentityHashMap.java +++ b/luni/src/main/java/java/util/IdentityHashMap.java @@ -83,8 +83,11 @@ public class IdentityHashMap<K, V> extends AbstractMap<K, V> implements private static final Object NULL_OBJECT = new Object(); //$NON-LOCK-1$ static class IdentityHashMapEntry<K, V> extends MapEntry<K, V> { - IdentityHashMapEntry(K theKey, V theValue) { + private final IdentityHashMap<K,V> map; + + IdentityHashMapEntry(IdentityHashMap<K,V> map, K theKey, V theValue) { super(theKey, theValue); + this.map = map; } @Override @@ -114,6 +117,13 @@ public class IdentityHashMap<K, V> extends AbstractMap<K, V> implements public String toString() { return key + "=" + value; } + + @Override + public V setValue(V object) { + V result = super.setValue(object); + map.put(key, object); + return result; + } } static class IdentityHashMapIterator<E, KT, VT> implements Iterator<E> { @@ -407,7 +417,7 @@ public class IdentityHashMap<K, V> extends AbstractMap<K, V> implements value = null; } - return new IdentityHashMapEntry<K, V>((K) key, (V) value); + return new IdentityHashMapEntry<K, V>(this, (K) key, (V) value); } /** diff --git a/luni/src/main/java/java/util/MiniEnumSet.java b/luni/src/main/java/java/util/MiniEnumSet.java index e19612a..c4c7364 100644 --- a/luni/src/main/java/java/util/MiniEnumSet.java +++ b/luni/src/main/java/java/util/MiniEnumSet.java @@ -108,10 +108,7 @@ final class MiniEnumSet<E extends Enum<E>> extends EnumSet<E> { @Override public boolean add(E element) { - if (!isValidType(element.getDeclaringClass())) { - throw new ClassCastException(); - } - + elementClass.cast(element); // Called to throw ClassCastException. long oldBits = bits; long newBits = oldBits | (1L << element.ordinal()); if (oldBits != newBits) { @@ -129,9 +126,7 @@ final class MiniEnumSet<E extends Enum<E>> extends EnumSet<E> { } if (collection instanceof EnumSet) { EnumSet<?> set = (EnumSet) collection; // raw type due to javac bug 6548436 - if (!isValidType(set.elementClass)) { - throw new ClassCastException(); - } + set.elementClass.asSubclass(elementClass); // Called to throw ClassCastException. MiniEnumSet<?> miniSet = (MiniEnumSet<?>) set; long oldBits = bits; diff --git a/luni/src/main/java/java/util/NavigableMap.java b/luni/src/main/java/java/util/NavigableMap.java index a87a8c0..29961c8 100644 --- a/luni/src/main/java/java/util/NavigableMap.java +++ b/luni/src/main/java/java/util/NavigableMap.java @@ -1,262 +1,396 @@ -/* Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/* + * Written by Doug Lea and Josh Bloch with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain */ package java.util; +// BEGIN android-note +// removed link to collections framework docs +// changed {@link #subMap(Object)} to {@link #subMap} to satisfy DroidDoc +// END android-note + /** - * NavigableMap is a SortedMap with navigation methods answering the closest - * matches for specified item. + * A {@link SortedMap} extended with navigation methods returning the + * closest matches for given search targets. Methods + * {@code lowerEntry}, {@code floorEntry}, {@code ceilingEntry}, + * and {@code higherEntry} return {@code Map.Entry} objects + * associated with keys respectively less than, less than or equal, + * greater than or equal, and greater than a given key, returning + * {@code null} if there is no such key. Similarly, methods + * {@code lowerKey}, {@code floorKey}, {@code ceilingKey}, and + * {@code higherKey} return only the associated keys. All of these + * methods are designed for locating, not traversing entries. + * + * <p>A {@code NavigableMap} may be accessed and traversed in either + * ascending or descending key order. The {@code descendingMap} + * method returns a view of the map with the senses of all relational + * and directional methods inverted. The performance of ascending + * operations and views is likely to be faster than that of descending + * ones. Methods {@code subMap}, {@code headMap}, + * and {@code tailMap} differ from the like-named {@code + * SortedMap} methods in accepting additional arguments describing + * whether lower and upper bounds are inclusive versus exclusive. + * Submaps of any {@code NavigableMap} must implement the {@code + * NavigableMap} interface. * - * @param <K> - * the type of key - * @param <V> - * the type of value + * <p>This interface additionally defines methods {@code firstEntry}, + * {@code pollFirstEntry}, {@code lastEntry}, and + * {@code pollLastEntry} that return and/or remove the least and + * greatest mappings, if any exist, else returning {@code null}. + * + * <p>Implementations of entry-returning methods are expected to + * return {@code Map.Entry} pairs representing snapshots of mappings + * at the time they were produced, and thus generally do <em>not</em> + * support the optional {@code Entry.setValue} method. Note however + * that it is possible to change mappings in the associated map using + * method {@code put}. + * + * <p>Methods + * {@link #subMap subMap(K, K)}, + * {@link #headMap headMap(K)}, and + * {@link #tailMap tailMap(K)} + * are specified to return {@code SortedMap} to allow existing + * implementations of {@code SortedMap} to be compatibly retrofitted to + * implement {@code NavigableMap}, but extensions and implementations + * of this interface are encouraged to override these methods to return + * {@code NavigableMap}. Similarly, + * {@link #keySet()} can be overriden to return {@code NavigableSet}. + * + * @author Doug Lea + * @author Josh Bloch + * @param <K> the type of keys maintained by this map + * @param <V> the type of mapped values * @since 1.6 */ -public interface NavigableMap<K, V> extends SortedMap<K, V> { +public interface NavigableMap<K,V> extends SortedMap<K,V> { /** - * Returns the entry with the smallest key, or null if the map is empty. + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or {@code null} if there is + * no such key. * - * @return the entry with the smallest key, or null if the map is empty + * @param key the key + * @return an entry with the greatest key less than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> firstEntry(); + Map.Entry<K,V> lowerEntry(K key); /** - * Returns the entry with the biggest key, or null if the map is empty. + * Returns the greatest key strictly less than the given key, or + * {@code null} if there is no such key. * - * @return the entry with the biggest key, or null if the map is empty + * @param key the key + * @return the greatest key less than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> lastEntry(); + K lowerKey(K key); /** - * Deletes and returns the entry with the smallest key, or null if the map - * is empty. + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or {@code null} if there + * is no such key. * - * @return the entry with the smallest key, or null if the map is empty + * @param key the key + * @return an entry with the greatest key less than or equal to + * {@code key}, or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> pollFirstEntry(); + Map.Entry<K,V> floorEntry(K key); /** - * Deletes and returns the entry with the biggest key, or null if the map is - * empty. + * Returns the greatest key less than or equal to the given key, + * or {@code null} if there is no such key. * - * @return the entry with the biggest key, or null if the map is empty + * @param key the key + * @return the greatest key less than or equal to {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> pollLastEntry(); + K floorKey(K key); /** - * Returns an entry related with the smallest key greater than or equal to - * the specified key, or null if no such key. + * Returns a key-value mapping associated with the least key + * greater than or equal to the given key, or {@code null} if + * there is no such key. * - * @param key - * the key - * @return the entry, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @param key the key + * @return an entry with the least key greater than or equal to + * {@code key}, or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> ceilingEntry(K key); + Map.Entry<K,V> ceilingEntry(K key); /** - * Returns the smallest key greater than or equal to the specified key, or - * null if no such key. + * Returns the least key greater than or equal to the given key, + * or {@code null} if there is no such key. * - * @param key - * the key - * @return the smallest key greater than or equal to key, or null if no such - * key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @param key the key + * @return the least key greater than or equal to {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ K ceilingKey(K key); /** - * Returns an entry related with the smallest key greater than the specified - * key, or null if no such key. + * Returns a key-value mapping associated with the least key + * strictly greater than the given key, or {@code null} if there + * is no such key. * - * @param key - * the key - * @return the entry, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @param key the key + * @return an entry with the least key greater than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ - Map.Entry<K, V> higherEntry(K key); + Map.Entry<K,V> higherEntry(K key); /** - * Returns the smallest key greater than the specified key, or null if no - * such key. + * Returns the least key strictly greater than the given key, or + * {@code null} if there is no such key. * - * @param key - * the key - * @return the smallest key greater than key, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @param key the key + * @return the least key greater than {@code key}, + * or {@code null} if there is no such key + * @throws ClassCastException if the specified key cannot be compared + * with the keys currently in the map + * @throws NullPointerException if the specified key is null + * and this map does not permit null keys */ K higherKey(K key); /** - * Returns an entry related with the biggest key less than or equal to the - * specified key, or null if no such key. + * Returns a key-value mapping associated with the least + * key in this map, or {@code null} if the map is empty. * - * @param key - * the key - * @return the entry, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @return an entry with the least key, + * or {@code null} if this map is empty */ - Map.Entry<K, V> floorEntry(K key); + Map.Entry<K,V> firstEntry(); /** - * Returns the biggest key less than or equal to the specified key, or null - * if no such key. + * Returns a key-value mapping associated with the greatest + * key in this map, or {@code null} if the map is empty. * - * @param key - * the key - * @return the biggest key less than or equal to key, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @return an entry with the greatest key, + * or {@code null} if this map is empty */ - K floorKey(K key); + Map.Entry<K,V> lastEntry(); /** - * Returns an entry related with the biggest key less than the specified - * key, or null if no such key. + * Removes and returns a key-value mapping associated with + * the least key in this map, or {@code null} if the map is empty. * - * @param key - * the key - * @return the entry, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @return the removed first entry of this map, + * or {@code null} if this map is empty */ - Map.Entry<K, V> lowerEntry(K key); + Map.Entry<K,V> pollFirstEntry(); /** - * Returns the biggest key less than the specified key, or null if no such - * key. + * Removes and returns a key-value mapping associated with + * the greatest key in this map, or {@code null} if the map is empty. * - * @param key - * the key - * @return the biggest key less than key, or null if no such key - * @throws ClassCastException - * if the key cannot be compared with the keys in the map - * @throws NullPointerException - * if the key is null and the map can not contain null key + * @return the removed last entry of this map, + * or {@code null} if this map is empty */ - K lowerKey(K key); + Map.Entry<K,V> pollLastEntry(); /** - * Returns a NavigableSet view of the keys in ascending order. + * Returns a reverse order view of the mappings contained in this map. + * The descending map is backed by this map, so changes to the map are + * reflected in the descending map, and vice-versa. If either map is + * modified while an iteration over a collection view of either map + * is in progress (except through the iterator's own {@code remove} + * operation), the results of the iteration are undefined. + * + * <p>The returned map has an ordering equivalent to + * <tt>{@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator())</tt>. + * The expression {@code m.descendingMap().descendingMap()} returns a + * view of {@code m} essentially equivalent to {@code m}. * - * @return the navigable set view + * @return a reverse order view of this map */ - NavigableSet<K> navigableKeySet(); + NavigableMap<K,V> descendingMap(); /** - * Returns a reverse order view of the map. + * Returns a {@link NavigableSet} view of the keys contained in this map. + * The set's iterator returns the keys in ascending order. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress (except through the iterator's own {@code + * remove} operation), the results of the iteration are undefined. The + * set supports element removal, which removes the corresponding mapping + * from the map, via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. + * It does not support the {@code add} or {@code addAll} operations. * - * @return the reverse order view of the map + * @return a navigable set view of the keys in this map */ - NavigableMap<K, V> descendingMap(); + NavigableSet<K> navigableKeySet(); /** - * Returns a NavigableSet view of the keys in descending order. + * Returns a reverse order {@link NavigableSet} view of the keys contained in this map. + * The set's iterator returns the keys in descending order. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress (except through the iterator's own {@code + * remove} operation), the results of the iteration are undefined. The + * set supports element removal, which removes the corresponding mapping + * from the map, via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. + * It does not support the {@code add} or {@code addAll} operations. * - * @return the navigable set view + * @return a reverse order navigable set view of the keys in this map */ NavigableSet<K> descendingKeySet(); /** - * Returns a view of part of the map whose keys is from startKey to endKey. + * Returns a view of the portion of this map whose keys range from + * {@code fromKey} to {@code toKey}. If {@code fromKey} and + * {@code toKey} are equal, the returned map is empty unless + * {@code fromExclusive} and {@code toExclusive} are both true. The + * returned map is backed by this map, so changes in the returned map are + * reflected in this map, and vice-versa. The returned map supports all + * optional map operations that this map supports. + * + * <p>The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside of its range, or to construct a + * submap either of whose endpoints lie outside its range. + * + * @param fromKey low endpoint of the keys in the returned map + * @param fromInclusive {@code true} if the low endpoint + * is to be included in the returned view + * @param toKey high endpoint of the keys in the returned map + * @param toInclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys range from + * {@code fromKey} to {@code toKey} + * @throws ClassCastException if {@code fromKey} and {@code toKey} + * cannot be compared to one another using this map's comparator + * (or, if the map has no comparator, using natural ordering). + * Implementations may, but are not required to, throw this + * exception if {@code fromKey} or {@code toKey} + * cannot be compared to keys currently in the map. + * @throws NullPointerException if {@code fromKey} or {@code toKey} + * is null and this map does not permit null keys + * @throws IllegalArgumentException if {@code fromKey} is greater than + * {@code toKey}; or if this map itself has a restricted + * range, and {@code fromKey} or {@code toKey} lies + * outside the bounds of the range + */ + NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive); + + /** + * Returns a view of the portion of this map whose keys are less than (or + * equal to, if {@code inclusive} is true) {@code toKey}. The returned + * map is backed by this map, so changes in the returned map are reflected + * in this map, and vice-versa. The returned map supports all optional + * map operations that this map supports. + * + * <p>The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside its range. + * + * @param toKey high endpoint of the keys in the returned map + * @param inclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys are less than + * (or equal to, if {@code inclusive} is true) {@code toKey} + * @throws ClassCastException if {@code toKey} is not compatible + * with this map's comparator (or, if the map has no comparator, + * if {@code toKey} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code toKey} cannot be compared to keys + * currently in the map. + * @throws NullPointerException if {@code toKey} is null + * and this map does not permit null keys + * @throws IllegalArgumentException if this map itself has a + * restricted range, and {@code toKey} lies outside the + * bounds of the range + */ + NavigableMap<K,V> headMap(K toKey, boolean inclusive); + + /** + * Returns a view of the portion of this map whose keys are greater than (or + * equal to, if {@code inclusive} is true) {@code fromKey}. The returned + * map is backed by this map, so changes in the returned map are reflected + * in this map, and vice-versa. The returned map supports all optional + * map operations that this map supports. + * + * <p>The returned map will throw an {@code IllegalArgumentException} + * on an attempt to insert a key outside its range. + * + * @param fromKey low endpoint of the keys in the returned map + * @param inclusive {@code true} if the low endpoint + * is to be included in the returned view + * @return a view of the portion of this map whose keys are greater than + * (or equal to, if {@code inclusive} is true) {@code fromKey} + * @throws ClassCastException if {@code fromKey} is not compatible + * with this map's comparator (or, if the map has no comparator, + * if {@code fromKey} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code fromKey} cannot be compared to keys + * currently in the map. + * @throws NullPointerException if {@code fromKey} is null + * and this map does not permit null keys + * @throws IllegalArgumentException if this map itself has a + * restricted range, and {@code fromKey} lies outside the + * bounds of the range + */ + NavigableMap<K,V> tailMap(K fromKey, boolean inclusive); + + /** + * {@inheritDoc} * - * @param startKey - * the start key - * @param startInclusive - * true if the start key is in the returned map - * @param endKey - * the end key - * @param endInclusive - * true if the end key is in the returned map - * @return the sub-map view + * <p>Equivalent to {@code subMap(fromKey, true, toKey, false)}. * - * @exception ClassCastException - * when the class of the start or end key is inappropriate - * for this SubMap - * @exception NullPointerException - * when the start or end key is null and this SortedMap does - * not support null keys - * @exception IllegalArgumentException - * when the start key is greater than the end key + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} */ - NavigableMap<K, V> subMap(K startKey, boolean startInclusive, K endKey, - boolean endInclusive); + SortedMap<K,V> subMap(K fromKey, K toKey); /** - * Returns a view of the head of the map whose keys are smaller than (or - * equal to, depends on inclusive argument) endKey. + * {@inheritDoc} * - * @param endKey - * the end key - * @param inclusive - * true if the end key is in the returned map - * @return the head-map view + * <p>Equivalent to {@code headMap(toKey, false)}. * - * @exception ClassCastException - * when the class of the end key is inappropriate for this - * SubMap - * @exception NullPointerException - * when the end key is null and this SortedMap does not - * support null keys - * @exception IllegalArgumentException - * when the map is range-limited and end key is out of the - * range of the map + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} */ - NavigableMap<K, V> headMap(K endKey, boolean inclusive); + SortedMap<K,V> headMap(K toKey); /** - * Returns a view of the tail of the map whose keys are bigger than (or - * equal to, depends on inclusive argument) startKey. + * {@inheritDoc} * - * @param startKey - * the start key - * @param inclusive - * true if the start key is in the returned map - * @return the tail-map view + * <p>Equivalent to {@code tailMap(fromKey, true)}. * - * @exception ClassCastException - * when the class of the start key is inappropriate for this - * SubMap - * @exception NullPointerException - * when the start key is null and this SortedMap does not - * support null keys - * @exception IllegalArgumentException - * when the map is range-limited and start key is out of the - * range of the map + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} */ - NavigableMap<K, V> tailMap(K startKey, boolean inclusive); -}
\ No newline at end of file + SortedMap<K,V> tailMap(K fromKey); +} diff --git a/luni/src/main/java/java/util/NavigableSet.java b/luni/src/main/java/java/util/NavigableSet.java index ae94b77..cff0800 100644 --- a/luni/src/main/java/java/util/NavigableSet.java +++ b/luni/src/main/java/java/util/NavigableSet.java @@ -1,192 +1,291 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Written by Doug Lea and Josh Bloch with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain */ package java.util; +// BEGIN android-note +// removed link to collections framework docs +// changed {@link #subSet(Object)} to {@link #subSet} to satisfy DroidDoc +// END android-note + /** - * NavigableSet is a SortedSet with navigation methods answering the closest - * matches for specified item. + * A {@link SortedSet} extended with navigation methods reporting + * closest matches for given search targets. Methods {@code lower}, + * {@code floor}, {@code ceiling}, and {@code higher} return elements + * respectively less than, less than or equal, greater than or equal, + * and greater than a given element, returning {@code null} if there + * is no such element. A {@code NavigableSet} may be accessed and + * traversed in either ascending or descending order. The {@code + * descendingSet} method returns a view of the set with the senses of + * all relational and directional methods inverted. The performance of + * ascending operations and views is likely to be faster than that of + * descending ones. This interface additionally defines methods + * {@code pollFirst} and {@code pollLast} that return and remove the + * lowest and highest element, if one exists, else returning {@code + * null}. Methods {@code subSet}, {@code headSet}, + * and {@code tailSet} differ from the like-named {@code + * SortedSet} methods in accepting additional arguments describing + * whether lower and upper bounds are inclusive versus exclusive. + * Subsets of any {@code NavigableSet} must implement the {@code + * NavigableSet} interface. * - * @param <E> - * the type of element + * <p> The return values of navigation methods may be ambiguous in + * implementations that permit {@code null} elements. However, even + * in this case the result can be disambiguated by checking + * {@code contains(null)}. To avoid such issues, implementations of + * this interface are encouraged to <em>not</em> permit insertion of + * {@code null} elements. (Note that sorted sets of {@link + * Comparable} elements intrinsically do not permit {@code null}.) + * + * <p>Methods + * {@link #subSet subSet(E, E)}, + * {@link #headSet headSet(E)}, and + * {@link #tailSet tailSet(E)} + * are specified to return {@code SortedSet} to allow existing + * implementations of {@code SortedSet} to be compatibly retrofitted to + * implement {@code NavigableSet}, but extensions and implementations + * of this interface are encouraged to override these methods to return + * {@code NavigableSet}. + * + * @author Doug Lea + * @author Josh Bloch + * @param <E> the type of elements maintained by this set * @since 1.6 */ public interface NavigableSet<E> extends SortedSet<E> { + /** + * Returns the greatest element in this set strictly less than the + * given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the greatest element less than {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements + */ + E lower(E e); /** - * Deletes and returns the smallest element, or null if the set is empty. + * Returns the greatest element in this set less than or equal to + * the given element, or {@code null} if there is no such element. * - * @return the smallest element, or null if the set is empty + * @param e the value to match + * @return the greatest element less than or equal to {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements */ - E pollFirst(); + E floor(E e); /** - * Deletes and returns the biggest element, or null if the set is empty. + * Returns the least element in this set greater than or equal to + * the given element, or {@code null} if there is no such element. * - * @return the biggest element, or null if the set is empty + * @param e the value to match + * @return the least element greater than or equal to {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements */ - E pollLast(); + E ceiling(E e); /** - * Returns the smallest element bigger than the specified one, or null if no - * such element. - * - * @param e - * the specified element - * @return the smallest element bigger than the specified one, or null if no - * such element - * @throws ClassCastException - * if the element cannot be compared with the ones in the set - * @throws NullPointerException - * if the element is null and the set can not contain null + * Returns the least element in this set strictly greater than the + * given element, or {@code null} if there is no such element. + * + * @param e the value to match + * @return the least element greater than {@code e}, + * or {@code null} if there is no such element + * @throws ClassCastException if the specified element cannot be + * compared with the elements currently in the set + * @throws NullPointerException if the specified element is null + * and this set does not permit null elements */ E higher(E e); /** - * Returns the smallest element bigger than or equal to the specified one, - * or null if no such element. - * - * @param e - * the specified element - * @return the smallest element bigger than or equal to the specified one, - * or null if no such element - * @throws ClassCastException - * if the element cannot be compared with the ones in the set - * @throws NullPointerException - * if the element is null and the set can not contain null + * Retrieves and removes the first (lowest) element, + * or returns {@code null} if this set is empty. + * + * @return the first element, or {@code null} if this set is empty */ - E ceiling(E e); + E pollFirst(); /** - * Returns the biggest element less than the specified one, or null if no - * such element. - * - * @param e - * the specified element - * @return the biggest element less than the specified one, or null if no - * such element - * @throws ClassCastException - * if the element cannot be compared with the ones in the set - * @throws NullPointerException - * if the element is null and the set can not contain null + * Retrieves and removes the last (highest) element, + * or returns {@code null} if this set is empty. + * + * @return the last element, or {@code null} if this set is empty */ - E lower(E e); + E pollLast(); /** - * Returns the biggest element less than or equal to the specified one, or - * null if no such element. - * - * @param e - * the specified element - * @return the biggest element less than or equal to the specified one, or - * null if no such element - * @throws ClassCastException - * if the element cannot be compared with the ones in the set - * @throws NullPointerException - * if the element is null and the set can not contain null + * Returns an iterator over the elements in this set, in ascending order. + * + * @return an iterator over the elements in this set, in ascending order */ - E floor(E e); + Iterator<E> iterator(); /** - * Returns a descending iterator of this set. + * Returns a reverse order view of the elements contained in this set. + * The descending set is backed by this set, so changes to the set are + * reflected in the descending set, and vice-versa. If either set is + * modified while an iteration over either set is in progress (except + * through the iterator's own {@code remove} operation), the results of + * the iteration are undefined. + * + * <p>The returned set has an ordering equivalent to + * <tt>{@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator())</tt>. + * The expression {@code s.descendingSet().descendingSet()} returns a + * view of {@code s} essentially equivalent to {@code s}. * - * @return the descending iterator + * @return a reverse order view of this set + */ + NavigableSet<E> descendingSet(); + + /** + * Returns an iterator over the elements in this set, in descending order. + * Equivalent in effect to {@code descendingSet().iterator()}. + * + * @return an iterator over the elements in this set, in descending order */ Iterator<E> descendingIterator(); /** - * Returns a reverse order view of this set. + * Returns a view of the portion of this set whose elements range from + * {@code fromElement} to {@code toElement}. If {@code fromElement} and + * {@code toElement} are equal, the returned set is empty unless {@code + * fromExclusive} and {@code toExclusive} are both true. The returned set + * is backed by this set, so changes in the returned set are reflected in + * this set, and vice-versa. The returned set supports all optional set + * operations that this set supports. + * + * <p>The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. * - * @return the reverse order view + * @param fromElement low endpoint of the returned set + * @param fromInclusive {@code true} if the low endpoint + * is to be included in the returned view + * @param toElement high endpoint of the returned set + * @param toInclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements range from + * {@code fromElement}, inclusive, to {@code toElement}, exclusive + * @throws ClassCastException if {@code fromElement} and + * {@code toElement} cannot be compared to one another using this + * set's comparator (or, if the set has no comparator, using + * natural ordering). Implementations may, but are not required + * to, throw this exception if {@code fromElement} or + * {@code toElement} cannot be compared to elements currently in + * the set. + * @throws NullPointerException if {@code fromElement} or + * {@code toElement} is null and this set does + * not permit null elements + * @throws IllegalArgumentException if {@code fromElement} is + * greater than {@code toElement}; or if this set itself + * has a restricted range, and {@code fromElement} or + * {@code toElement} lies outside the bounds of the range. */ - NavigableSet<E> descendingSet(); + NavigableSet<E> subSet(E fromElement, boolean fromInclusive, + E toElement, boolean toInclusive); + + /** + * Returns a view of the portion of this set whose elements are less than + * (or equal to, if {@code inclusive} is true) {@code toElement}. The + * returned set is backed by this set, so changes in the returned set are + * reflected in this set, and vice-versa. The returned set supports all + * optional set operations that this set supports. + * + * <p>The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. + * + * @param toElement high endpoint of the returned set + * @param inclusive {@code true} if the high endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements are less than + * (or equal to, if {@code inclusive} is true) {@code toElement} + * @throws ClassCastException if {@code toElement} is not compatible + * with this set's comparator (or, if the set has no comparator, + * if {@code toElement} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code toElement} cannot be compared to elements + * currently in the set. + * @throws NullPointerException if {@code toElement} is null and + * this set does not permit null elements + * @throws IllegalArgumentException if this set itself has a + * restricted range, and {@code toElement} lies outside the + * bounds of the range + */ + NavigableSet<E> headSet(E toElement, boolean inclusive); /** - * Returns a NavigableSet of the specified portion of this set which - * contains elements greater (or equal to, depends on startInclusive) the - * start element but less than (or equal to, depends on endInclusive) the - * end element. The returned NavigableSet is backed by this set so changes - * to one are reflected by the other. - * - * @param start - * the start element - * @param startInclusive - * true if the start element is in the returned set - * @param end - * the end element - * @param endInclusive - * true if the end element is in the returned set - * @return the subset - * - * @throws ClassCastException - * when the start or end object cannot be compared with the - * elements in this set - * @throws NullPointerException - * when the start or end object is null and the set cannot - * contain null - * @throws IllegalArgumentException - * when the start is bigger than end; or start or end is out of - * range and the set has a range + * Returns a view of the portion of this set whose elements are greater + * than (or equal to, if {@code inclusive} is true) {@code fromElement}. + * The returned set is backed by this set, so changes in the returned set + * are reflected in this set, and vice-versa. The returned set supports + * all optional set operations that this set supports. + * + * <p>The returned set will throw an {@code IllegalArgumentException} + * on an attempt to insert an element outside its range. + * + * @param fromElement low endpoint of the returned set + * @param inclusive {@code true} if the low endpoint + * is to be included in the returned view + * @return a view of the portion of this set whose elements are greater + * than or equal to {@code fromElement} + * @throws ClassCastException if {@code fromElement} is not compatible + * with this set's comparator (or, if the set has no comparator, + * if {@code fromElement} does not implement {@link Comparable}). + * Implementations may, but are not required to, throw this + * exception if {@code fromElement} cannot be compared to elements + * currently in the set. + * @throws NullPointerException if {@code fromElement} is null + * and this set does not permit null elements + * @throws IllegalArgumentException if this set itself has a + * restricted range, and {@code fromElement} lies outside the + * bounds of the range */ - NavigableSet<E> subSet(E start, boolean startInclusive, E end, - boolean endInclusive); + NavigableSet<E> tailSet(E fromElement, boolean inclusive); /** - * Returns a NavigableSet of the specified portion of this set which - * contains elements less than (or equal to, depends on endInclusive) the - * end element. The returned NavigableSet is backed by this set so changes - * to one are reflected by the other. - * - * @param end - * the end element - * @param endInclusive - * true if the end element is in the returned set - * @return the subset - * - * @throws ClassCastException - * when the end object cannot be compared with the elements in - * this set - * @throws NullPointerException - * when the end object is null and the set cannot contain handle - * null - * @throws IllegalArgumentException - * when end is out of range and the set has a range + * {@inheritDoc} + * + * <p>Equivalent to {@code subSet(fromElement, true, toElement, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} */ - NavigableSet<E> headSet(E end, boolean endInclusive); + SortedSet<E> subSet(E fromElement, E toElement); /** - * Returns a NavigableSet of the specified portion of this set which - * contains elements greater (or equal to, depends on startInclusive) the - * start element. The returned NavigableSet is backed by this set so changes - * to one are reflected by the other. - * - * @param start - * the start element - * @param startInclusive - * true if the start element is in the returned set - * @return the subset - * - * @throws ClassCastException - * when the start object cannot be compared with the elements in - * this set - * @throws NullPointerException - * when the start object is null and the set cannot contain null - * @throws IllegalArgumentException - * when start is out of range and the set has a range + * {@inheritDoc} + * + * <p>Equivalent to {@code headSet(toElement, false)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} +na */ + SortedSet<E> headSet(E toElement); + + /** + * {@inheritDoc} + * + * <p>Equivalent to {@code tailSet(fromElement, true)}. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} */ - NavigableSet<E> tailSet(E start, boolean startInclusive); -}
\ No newline at end of file + SortedSet<E> tailSet(E fromElement); +} diff --git a/luni/src/main/java/java/util/Queue.java b/luni/src/main/java/java/util/Queue.java index 51899e3..5aef944 100644 --- a/luni/src/main/java/java/util/Queue.java +++ b/luni/src/main/java/java/util/Queue.java @@ -1,91 +1,188 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain */ + package java.util; +// BEGIN android-note +// removed link to collections framework docs +// END android-note + /** - * This kind of collection provides advanced operations compared to basic - * collections, such as insertion, extraction, and inspection. - * <p> - * Generally, a queue orders its elements by means of first-in-first-out. - * However, a priority queue orders its elements according to a comparator - * specified or the elements' natural order. Furthermore, a stack orders its - * elements last-in-first out. - * <p> - * A typical queue does not allow {@code null} to be inserted as its element, - * while some implementations such as {@code LinkedList} allow it. But {@code - * null} should not be inserted even in these implementations, since the method - * {@code poll} returns {@code null} to indicate that there is no element left - * in the queue. + * A collection designed for holding elements prior to processing. + * Besides basic {@link java.util.Collection Collection} operations, + * queues provide additional insertion, extraction, and inspection + * operations. Each of these methods exists in two forms: one throws + * an exception if the operation fails, the other returns a special + * value (either <tt>null</tt> or <tt>false</tt>, depending on the + * operation). The latter form of the insert operation is designed + * specifically for use with capacity-restricted <tt>Queue</tt> + * implementations; in most implementations, insert operations cannot + * fail. + * * <p> - * {@code Queue} does not provide blocking queue methods, which would block - * until the operation of the method is allowed. See the - * {@link java.util.concurrent.BlockingQueue} interface for information about - * blocking queue methods. + * <table BORDER CELLPADDING=3 CELLSPACING=1> + * <tr> + * <td></td> + * <td ALIGN=CENTER><em>Throws exception</em></td> + * <td ALIGN=CENTER><em>Returns special value</em></td> + * </tr> + * <tr> + * <td><b>Insert</b></td> + * <td>{@link #add add(e)}</td> + * <td>{@link #offer offer(e)}</td> + * </tr> + * <tr> + * <td><b>Remove</b></td> + * <td>{@link #remove remove()}</td> + * <td>{@link #poll poll()}</td> + * </tr> + * <tr> + * <td><b>Examine</b></td> + * <td>{@link #element element()}</td> + * <td>{@link #peek peek()}</td> + * </tr> + * </table> + * + * <p>Queues typically, but do not necessarily, order elements in a + * FIFO (first-in-first-out) manner. Among the exceptions are + * priority queues, which order elements according to a supplied + * comparator, or the elements' natural ordering, and LIFO queues (or + * stacks) which order the elements LIFO (last-in-first-out). + * Whatever the ordering used, the <em>head</em> of the queue is that + * element which would be removed by a call to {@link #remove() } or + * {@link #poll()}. In a FIFO queue, all new elements are inserted at + * the <em> tail</em> of the queue. Other kinds of queues may use + * different placement rules. Every <tt>Queue</tt> implementation + * must specify its ordering properties. + * + * <p>The {@link #offer offer} method inserts an element if possible, + * otherwise returning <tt>false</tt>. This differs from the {@link + * java.util.Collection#add Collection.add} method, which can fail to + * add an element only by throwing an unchecked exception. The + * <tt>offer</tt> method is designed for use when failure is a normal, + * rather than exceptional occurrence, for example, in fixed-capacity + * (or "bounded") queues. + * + * <p>The {@link #remove()} and {@link #poll()} methods remove and + * return the head of the queue. + * Exactly which element is removed from the queue is a + * function of the queue's ordering policy, which differs from + * implementation to implementation. The <tt>remove()</tt> and + * <tt>poll()</tt> methods differ only in their behavior when the + * queue is empty: the <tt>remove()</tt> method throws an exception, + * while the <tt>poll()</tt> method returns <tt>null</tt>. + * + * <p>The {@link #element()} and {@link #peek()} methods return, but do + * not remove, the head of the queue. + * + * <p>The <tt>Queue</tt> interface does not define the <i>blocking queue + * methods</i>, which are common in concurrent programming. These methods, + * which wait for elements to appear or for space to become available, are + * defined in the {@link java.util.concurrent.BlockingQueue} interface, which + * extends this interface. + * + * <p><tt>Queue</tt> implementations generally do not allow insertion + * of <tt>null</tt> elements, although some implementations, such as + * {@link LinkedList}, do not prohibit insertion of <tt>null</tt>. + * Even in the implementations that permit it, <tt>null</tt> should + * not be inserted into a <tt>Queue</tt>, as <tt>null</tt> is also + * used as a special return value by the <tt>poll</tt> method to + * indicate that the queue contains no elements. + * + * <p><tt>Queue</tt> implementations generally do not define + * element-based versions of methods <tt>equals</tt> and + * <tt>hashCode</tt> but instead inherit the identity based versions + * from class <tt>Object</tt>, because element-based equality is not + * always well-defined for queues with the same elements but different + * ordering properties. + * + * @see java.util.Collection + * @see LinkedList + * @see PriorityQueue + * @see java.util.concurrent.LinkedBlockingQueue + * @see java.util.concurrent.BlockingQueue + * @see java.util.concurrent.ArrayBlockingQueue + * @see java.util.concurrent.LinkedBlockingQueue + * @see java.util.concurrent.PriorityBlockingQueue + * @since 1.5 + * @author Doug Lea + * @param <E> the type of elements held in this collection */ public interface Queue<E> extends Collection<E> { - /** - * Inserts the specified element into the queue provided that the condition - * allows such an operation. The method is generally preferable to - * {@link Collection#add}, since the latter might throw an exception if the - * operation fails. + * Inserts the specified element into this queue if it is possible to do so + * immediately without violating capacity restrictions, returning + * <tt>true</tt> upon success and throwing an <tt>IllegalStateException</tt> + * if no space is currently available. * - * @param o - * the specified element to insert into the queue. - * @return {@code true} if the operation succeeds and {@code false} if it - * fails. + * @param e the element to add + * @return <tt>true</tt> (as specified by {@link Collection#add}) + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this queue + * @throws NullPointerException if the specified element is null and + * this queue does not permit null elements + * @throws IllegalArgumentException if some property of this element + * prevents it from being added to this queue */ - public boolean offer(E o); + boolean add(E e); /** - * Gets and removes the element at the head of the queue, or returns {@code - * null} if there is no element in the queue. + * Inserts the specified element into this queue if it is possible to do + * so immediately without violating capacity restrictions. + * When using a capacity-restricted queue, this method is generally + * preferable to {@link #add}, which can fail to insert an element only + * by throwing an exception. * - * @return the element at the head of the queue or {@code null} if there is - * no element in the queue. + * @param e the element to add + * @return <tt>true</tt> if the element was added to this queue, else + * <tt>false</tt> + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this queue + * @throws NullPointerException if the specified element is null and + * this queue does not permit null elements + * @throws IllegalArgumentException if some property of this element + * prevents it from being added to this queue */ - public E poll(); + boolean offer(E e); /** - * Gets and removes the element at the head of the queue. Throws a - * NoSuchElementException if there is no element in the queue. + * Retrieves and removes the head of this queue. This method differs + * from {@link #poll poll} only in that it throws an exception if this + * queue is empty. * - * @return the element at the head of the queue. - * @throws NoSuchElementException - * if there is no element in the queue. + * @return the head of this queue + * @throws NoSuchElementException if this queue is empty */ - public E remove(); + E remove(); /** - * Gets but does not remove the element at the head of the queue. + * Retrieves and removes the head of this queue, + * or returns <tt>null</tt> if this queue is empty. * - * @return the element at the head of the queue or {@code null} if there is - * no element in the queue. + * @return the head of this queue, or <tt>null</tt> if this queue is empty */ - public E peek(); + E poll(); /** - * Gets but does not remove the element at the head of the queue. Throws a - * {@code NoSuchElementException} if there is no element in the queue. + * Retrieves, but does not remove, the head of this queue. This method + * differs from {@link #peek peek} only in that it throws an exception + * if this queue is empty. * - * @return the element at the head of the queue. - * @throws NoSuchElementException - * if there is no element in the queue. + * @return the head of this queue + * @throws NoSuchElementException if this queue is empty */ - public E element(); + E element(); + /** + * Retrieves, but does not remove, the head of this queue, + * or returns <tt>null</tt> if this queue is empty. + * + * @return the head of this queue, or <tt>null</tt> if this queue is empty + */ + E peek(); } diff --git a/luni/src/main/java/java/util/TreeMap.java b/luni/src/main/java/java/util/TreeMap.java index ad0f182..59f240c 100644 --- a/luni/src/main/java/java/util/TreeMap.java +++ b/luni/src/main/java/java/util/TreeMap.java @@ -251,7 +251,7 @@ public class TreeMap<K, V> extends AbstractMap<K, V> Node<K, V> find(K key, Relation relation) { if (root == null) { if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) { - throw new ClassCastException(key.getClass().getName()); // NullPointerException ok + throw new ClassCastException(key.getClass().getName() + " is not Comparable"); // NullPointerException ok } if (relation == Relation.CREATE) { root = new Node<K, V>(null, key); diff --git a/luni/src/main/java/java/util/concurrent/SynchronousQueue.java b/luni/src/main/java/java/util/concurrent/SynchronousQueue.java index 153d449..85fd53e 100644 --- a/luni/src/main/java/java/util/concurrent/SynchronousQueue.java +++ b/luni/src/main/java/java/util/concurrent/SynchronousQueue.java @@ -14,6 +14,7 @@ import java.util.Iterator; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; +import libcore.base.EmptyArray; // BEGIN android-note // removed link to collections framework docs @@ -1011,7 +1012,7 @@ public class SynchronousQueue<E> extends AbstractQueue<E> * @return a zero-length array */ public Object[] toArray() { - return new Object[0]; + return EmptyArray.OBJECT; } /** diff --git a/luni/src/main/java/java/util/jar/Attributes.java b/luni/src/main/java/java/util/jar/Attributes.java index a0f6bf4..9041c26 100644 --- a/luni/src/main/java/java/util/jar/Attributes.java +++ b/luni/src/main/java/java/util/jar/Attributes.java @@ -387,7 +387,7 @@ public class Attributes implements Cloneable, Map<Object, Object> { */ public void putAll(Map<?, ?> attrib) { if (attrib == null || !(attrib instanceof Attributes)) { - throw new ClassCastException(); + throw new ClassCastException(attrib.getClass().getName() + " not an Attributes"); } this.map.putAll(attrib); } diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java index 0f42d78..ed39f67 100644 --- a/luni/src/main/java/java/util/jar/JarFile.java +++ b/luni/src/main/java/java/util/jar/JarFile.java @@ -28,7 +28,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import libcore.base.Streams; import org.apache.harmony.archive.util.Util; -import org.apache.harmony.luni.util.InputStreamHelper; /** * {@code JarFile} is used to read jar entries and their associated data from @@ -291,8 +290,11 @@ public class JarFile extends ZipFile { try { InputStream is = super.getInputStream(manifestEntry); if (verifier != null) { - verifier.addMetaEntry(manifestEntry.getName(), - InputStreamHelper.readFullyAndClose(is)); + try { + verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is)); + } finally { + is.close(); + } is = super.getInputStream(manifestEntry); } try { @@ -329,8 +331,7 @@ public class JarFile extends ZipFile { for (ZipEntry entry : metaEntries) { String entryName = entry.getName(); // Is this the entry for META-INF/MANIFEST.MF ? - if (manifestEntry == null - && Util.asciiEqualsIgnoreCase(MANIFEST_NAME, entryName)) { + if (manifestEntry == null && Util.asciiEqualsIgnoreCase(MANIFEST_NAME, entryName)) { manifestEntry = entry; // If there is no verifier then we don't need to look any further. if (verifier == null) { @@ -344,8 +345,11 @@ public class JarFile extends ZipFile { || Util.asciiEndsWithIgnoreCase(entryName, ".RSA"))) { signed = true; InputStream is = super.getInputStream(entry); - byte[] buf = InputStreamHelper.readFullyAndClose(is); - verifier.addMetaEntry(entryName, buf); + try { + verifier.addMetaEntry(entryName, Streams.readFully(is)); + } finally { + is.close(); + } } } } diff --git a/luni/src/main/java/java/util/jar/Manifest.java b/luni/src/main/java/java/util/jar/Manifest.java index e7476cf..6216261 100644 --- a/luni/src/main/java/java/util/jar/Manifest.java +++ b/luni/src/main/java/java/util/jar/Manifest.java @@ -17,10 +17,12 @@ package java.util.jar; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; @@ -30,7 +32,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.apache.harmony.luni.util.InputStreamHelper; +import libcore.base.Streams; /** * The {@code Manifest} class is used to obtain attribute information for a @@ -45,6 +47,19 @@ public class Manifest implements Cloneable { private static final Attributes.Name NAME_ATTRIBUTE = new Attributes.Name("Name"); + private static final Field BAIS_BUF = getByteArrayInputStreamField("buf"); + private static final Field BAIS_POS = getByteArrayInputStreamField("pos"); + + private static Field getByteArrayInputStreamField(String name) { + try { + Field f = ByteArrayInputStream.class.getDeclaredField(name); + f.setAccessible(true); + return f; + } catch (Exception ex) { + throw new AssertionError(ex); + } + } + private Attributes mainAttributes = new Attributes(); private HashMap<String, Attributes> entries = new HashMap<String, Attributes>(); @@ -185,11 +200,10 @@ public class Manifest implements Cloneable { */ public void read(InputStream is) throws IOException { byte[] buf; - // Try to read get a reference to the bytes directly - try { - buf = InputStreamHelper.expose(is); - } catch (UnsupportedOperationException uoe) { - buf = readFully(is); + if (is instanceof ByteArrayInputStream) { + buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is); + } else { + buf = Streams.readFully(is); } if (buf.length == 0) { @@ -211,53 +225,31 @@ public class Manifest implements Cloneable { im.initEntries(entries, chunks); } - /* - * Helper to read the entire contents of the manifest from the - * given input stream. Usually we can do this in a single read - * but we need to account for 'infinite' streams, by ensuring we - * have a line feed within a reasonable number of characters. + /** + * Returns a byte[] containing all the bytes from a ByteArrayInputStream. + * Where possible, this returns the actual array rather than a copy. */ - private byte[] readFully(InputStream is) throws IOException { - // Initial read - byte[] buffer = new byte[4096]; - int count = is.read(buffer); - int nextByte = is.read(); - - // Did we get it all in one read? - if (nextByte == -1) { - return Arrays.copyOf(buffer, count); - } - - // Does it look like a manifest? - if (!containsLine(buffer, count)) { - throw new IOException("Manifest is too long"); - } - - // Requires additional reads - ByteArrayOutputStream baos = new ByteArrayOutputStream(count * 2); - baos.write(buffer, 0, count); - baos.write(nextByte); - while (true) { - count = is.read(buffer); - if (count == -1) { - return baos.toByteArray(); + private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) { + byte[] buffer; + synchronized (bais) { + byte[] buf; + int pos; + try { + buf = (byte[]) BAIS_BUF.get(bais); + pos = BAIS_POS.getInt(bais); + } catch (IllegalAccessException iae) { + throw new AssertionError(iae); } - baos.write(buffer, 0, count); - } - } - - /* - * Check to see if the buffer contains a newline or carriage - * return character within the first 'length' bytes. Used to - * check the validity of the manifest input stream. - */ - private boolean containsLine(byte[] buffer, int length) { - for (int i = 0; i < length; i++) { - if (buffer[i] == 0x0A || buffer[i] == 0x0D) { - return true; + int available = bais.available(); + if (pos == 0 && buf.length == available) { + buffer = buf; + } else { + buffer = new byte[available]; + System.arraycopy(buf, pos, buffer, 0, available); } + bais.skip(available); } - return false; + return buffer; } /** diff --git a/luni/src/main/java/java/util/prefs/AbstractPreferences.java b/luni/src/main/java/java/util/prefs/AbstractPreferences.java index 34073a1..be768c5 100644 --- a/luni/src/main/java/java/util/prefs/AbstractPreferences.java +++ b/luni/src/main/java/java/util/prefs/AbstractPreferences.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; import org.apache.harmony.luni.util.Base64; +import libcore.base.EmptyArray; /** * This abstract class is a partial implementation of the abstract class @@ -439,7 +440,7 @@ public abstract class AbstractPreferences extends Preferences { return deflt; } if (svalue.length() == 0) { - return new byte[0]; + return EmptyArray.BYTE; } try { byte[] bavalue = svalue.getBytes(Charsets.US_ASCII); diff --git a/luni/src/main/java/java/util/prefs/XMLParser.java b/luni/src/main/java/java/util/prefs/XMLParser.java index ed1fe0d..4934212 100644 --- a/luni/src/main/java/java/util/prefs/XMLParser.java +++ b/luni/src/main/java/java/util/prefs/XMLParser.java @@ -21,10 +21,13 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.Reader; import java.io.StringReader; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -39,6 +42,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; +import libcore.base.EmptyArray; import libcore.io.IoUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -85,11 +89,6 @@ class XMLParser { static final String DOCTYPE = "<!DOCTYPE preferences SYSTEM"; /* - * empty string array constant - */ - private static final String[] EMPTY_SARRAY = new String[0]; - - /* * Constant - used by FilePreferencesImpl, which is default implementation of Linux platform */ private static final String FILE_PREFS = "<!DOCTYPE map SYSTEM 'http://java.sun.com/dtd/preferences.dtd'>"; @@ -488,14 +487,14 @@ class XMLParser { if (!file.exists()) { file.getParentFile().mkdirs(); } else if (file.canRead()) { - InputStream in = null; + Reader reader = null; FileLock lock = null; try { - FileInputStream istream = new FileInputStream(file); - in = new BufferedInputStream(istream); - FileChannel channel = istream.getChannel(); + FileInputStream fileInputStream = new FileInputStream(file); + reader = new InputStreamReader(fileInputStream, "UTF-8"); + FileChannel channel = fileInputStream.getChannel(); lock = channel.lock(0L, Long.MAX_VALUE, true); - Document doc = builder.parse(in); + Document doc = builder.parse(new InputSource(reader)); NodeList entries = selectNodeList(doc.getDocumentElement(), "entry"); int length = entries.getLength(); for (int i = 0; i < length; i++) { @@ -509,7 +508,7 @@ class XMLParser { } catch (SAXException e) { } finally { releaseQuietly(lock); - IoUtils.closeQuietly(in); + IoUtils.closeQuietly(reader); } } else { file.delete(); @@ -545,7 +544,7 @@ class XMLParser { out.write(FILE_PREFS); out.newLine(); if (prefs.size() == 0) { - exportEntries(EMPTY_SARRAY, EMPTY_SARRAY, out); + exportEntries(EmptyArray.STRING, EmptyArray.STRING, out); } else { String[] keys = prefs.keySet().toArray(new String[prefs.size()]); int length = keys.length; diff --git a/luni/src/main/java/java/util/zip/Adler32.java b/luni/src/main/java/java/util/zip/Adler32.java index 7682ef9..504d272 100644 --- a/luni/src/main/java/java/util/zip/Adler32.java +++ b/luni/src/main/java/java/util/zip/Adler32.java @@ -22,7 +22,7 @@ package java.util.zip; * of data. Compared to {@link CRC32} it trades reliability for speed. * Refer to RFC 1950 for the specification. */ -public class Adler32 implements java.util.zip.Checksum { +public class Adler32 implements Checksum { private long adler = 1; diff --git a/luni/src/main/java/java/util/zip/CRC32.java b/luni/src/main/java/java/util/zip/CRC32.java index 8fc6bb7..1529dfe 100644 --- a/luni/src/main/java/java/util/zip/CRC32.java +++ b/luni/src/main/java/java/util/zip/CRC32.java @@ -19,9 +19,9 @@ package java.util.zip; /** * The CRC32 class is used to compute a CRC32 checksum from data provided as - * input value. + * input value. See also {@link Adler32} which is almost as good, but cheaper. */ -public class CRC32 implements java.util.zip.Checksum { +public class CRC32 implements Checksum { private long crc = 0L; diff --git a/luni/src/main/java/java/util/zip/Checksum.java b/luni/src/main/java/java/util/zip/Checksum.java index fac7e4e..824b85a 100644 --- a/luni/src/main/java/java/util/zip/Checksum.java +++ b/luni/src/main/java/java/util/zip/Checksum.java @@ -18,8 +18,7 @@ package java.util.zip; /** - * Holds information about a checksum which was computed with the methods - * implementing a checksum algorithm. + * The interface common to checksum classes such as {@link Adler32} and {@link CRC32}. */ public interface Checksum { diff --git a/luni/src/main/java/java/util/zip/Deflater.java b/luni/src/main/java/java/util/zip/Deflater.java index 755db4e..4442333 100644 --- a/luni/src/main/java/java/util/zip/Deflater.java +++ b/luni/src/main/java/java/util/zip/Deflater.java @@ -18,6 +18,7 @@ package java.util.zip; import dalvik.system.CloseGuard; +import libcore.base.EmptyArray; /** * This class compresses data using the <i>DEFLATE</i> algorithm (see <a @@ -109,10 +110,6 @@ public class Deflater { */ private static final int FINISH = 4; - // A stub buffer used when deflate() called while inputBuffer has not been - // set. - private static final byte[] STUB_INPUT_BUFFER = new byte[0]; - private int flushParm = NO_FLUSH; private boolean finished; @@ -237,7 +234,7 @@ public class Deflater { throw new ArrayIndexOutOfBoundsException(); } if (inputBuffer == null) { - setInput(STUB_INPUT_BUFFER); + setInput(EmptyArray.BYTE); } return deflateImpl(buf, off, nbytes, streamHandle, flush); } diff --git a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java index 7cb47db..fcbcb59 100644 --- a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java +++ b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java @@ -150,6 +150,7 @@ public class DeflaterOutputStream extends FilterOutputStream { */ @Override public void close() throws IOException { + // everything closed here should also be closed in ZipOuputStream.close() if (!def.finished()) { finish(); } diff --git a/luni/src/main/java/java/util/zip/GZIPInputStream.java b/luni/src/main/java/java/util/zip/GZIPInputStream.java index c52bf72..9107164 100644 --- a/luni/src/main/java/java/util/zip/GZIPInputStream.java +++ b/luni/src/main/java/java/util/zip/GZIPInputStream.java @@ -27,9 +27,23 @@ import org.apache.harmony.luni.platform.OSMemory; * The {@code GZIPInputStream} class is used to read data stored in the GZIP * format, reading and decompressing GZIP data from the underlying stream into * its buffer. + * + * <h3>Example</h3> + * <p>Using {@code GZIPInputStream} is easier than {@link ZipInputStream} + * because GZIP is only for compression, and is not a container for multiple files. + * This code decompresses the data from a GZIP stream, similar to the {@code gunzip(1)} utility. + * <pre> + * InputStream is = ... + * GZIPInputStream zis = new GZIPInputStream(new BufferedInputStream(is)); + * try { + * // Reading from 'zis' gets you the uncompressed bytes... + * processStream(zis); + * } finally { + * zis.close(); + * } + * </pre> */ public class GZIPInputStream extends InflaterInputStream { - private static final int FCOMMENT = 16; private static final int FEXTRA = 4; diff --git a/luni/src/main/java/java/util/zip/GZIPOutputStream.java b/luni/src/main/java/java/util/zip/GZIPOutputStream.java index 02510db..7d30ae8 100644 --- a/luni/src/main/java/java/util/zip/GZIPOutputStream.java +++ b/luni/src/main/java/java/util/zip/GZIPOutputStream.java @@ -23,6 +23,21 @@ import java.io.OutputStream; /** * The {@code GZIPOutputStream} class is used to write data to a stream in the * GZIP storage format. + * + * <h3>Example</h3> + * <p>Using {@code GZIPOutputStream} is a little easier than {@link ZipOutputStream} + * because GZIP is only for compression, and is not a container for multiple files. + * This code creates a GZIP stream, similar to the {@code gzip(1)} utility. + * <pre> + * OutputStream os = ... + * byte[] bytes = ... + * GZIPOutputStream zos = new GZIPOutputStream(new BufferedOutputStream(os)); + * try { + * zos.write(bytes); + * } finally { + * zos.close(); + * } + * </pre> */ public class GZIPOutputStream extends DeflaterOutputStream { diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java index a502796..bb9f20c 100644 --- a/luni/src/main/java/java/util/zip/ZipInputStream.java +++ b/luni/src/main/java/java/util/zip/ZipInputStream.java @@ -30,17 +30,43 @@ import org.apache.harmony.luni.platform.OSMemory; /** * This class provides an implementation of {@code FilterInputStream} that - * decompresses data from a <i>ZIP-archive</i> input stream. - * <p> - * A <i>ZIP-archive</i> is a collection of compressed (or uncompressed) files - - * the so called ZIP entries. Therefore when reading from a {@code - * ZipInputStream} first the entry's attributes will be retrieved with {@code - * getNextEntry} before its data is read. - * <p> - * While {@code InflaterInputStream} can read a compressed <i>ZIP-archive</i> - * entry, this extension can read uncompressed entries as well. - * <p> - * Use {@code ZipFile} if you can access the archive as a file directly. + * decompresses data from an {@code InputStream} containing a ZIP archive. + * + * <p>A ZIP archive is a collection of (possibly) compressed files. + * When reading from a {@code ZipInputStream}, you retrieve the + * entry's metadata with {@code getNextEntry} before you can read the userdata. + * + * <p>Although {@code InflaterInputStream} can only read compressed ZIP archive + * entries, this class can read non-compressed entries as well. + * + * <p>Use {@code ZipFile} if you can access the archive as a file directly, + * especially if you want random access to entries, rather than needing to + * iterate over all entries. + * + * <h3>Example</h3> + * <p>Using {@code ZipInputStream} is a little more complicated than {@link GZIPInputStream} + * because ZIP archives are containers that can contain multiple files. This code pulls all the + * files out of a ZIP archive, similar to the {@code unzip(1)} utility. + * <pre> + * InputStream is = ... + * ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is)); + * try { + * ZipEntry ze; + * while ((ze = zis.getNextEntry()) != null) { + * ByteArrayOutputStream baos = new ByteArrayOutputStream(); + * byte[] buffer = new byte[1024]; + * int count; + * while ((count = zis.read(buffer)) != -1) { + * baos.write(buffer, 0, count); + * } + * String filename = ze.getName(); + * byte[] bytes = baos.toByteArray(); + * // do something with 'filename' and 'bytes'... + * } + * } finally { + * zis.close(); + * } + * </pre> * * @see ZipEntry * @see ZipFile diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java index 7586c84..067fc43 100644 --- a/luni/src/main/java/java/util/zip/ZipOutputStream.java +++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java @@ -36,11 +36,31 @@ import java.util.Vector; * href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">file format * specification</a>. * + * <h3>Example</h3> + * <p>Using {@code ZipOutputStream} is a little more complicated than {@link GZIPOutputStream} + * because ZIP archives are containers that can contain multiple files. This code creates a ZIP + * archive containing several files, similar to the {@code zip(1)} utility. + * <pre> + * OutputStream os = ... + * ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os)); + * try { + * for (int i = 0; i < fileCount; ++i) { + * String filename = ... + * byte[] bytes = ... + * ZipEntry entry = new ZipEntry(filename); + * zos.putNextEntry(entry); + * zos.write(bytes); + * zos.closeEntry(); + * } + * } finally { + * zos.close(); + * } + * </pre> + * * @see ZipEntry * @see ZipFile */ -public class ZipOutputStream extends DeflaterOutputStream implements - ZipConstants { +public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { /** * Indicates deflated entries. @@ -92,8 +112,10 @@ public class ZipOutputStream extends DeflaterOutputStream implements */ @Override public void close() throws IOException { + // don't call super.close() because that calls finish() conditionally if (out != null) { finish(); + def.end(); out.close(); out = null; } diff --git a/luni/src/main/java/javax/crypto/spec/PBEKeySpec.java b/luni/src/main/java/javax/crypto/spec/PBEKeySpec.java index 1d67f68..c84cdcb 100644 --- a/luni/src/main/java/javax/crypto/spec/PBEKeySpec.java +++ b/luni/src/main/java/javax/crypto/spec/PBEKeySpec.java @@ -19,6 +19,7 @@ package javax.crypto.spec; import java.security.spec.KeySpec; import java.util.Arrays; +import libcore.base.EmptyArray; /** * The key specification for a <i>password based encryption</i> key. @@ -41,7 +42,7 @@ public class PBEKeySpec implements KeySpec { */ public PBEKeySpec(char[] password) { if (password == null) { - this.password = new char[0]; + this.password = EmptyArray.CHAR; } else { this.password = new char[password.length]; System.arraycopy(password, 0, this.password, 0, password.length); @@ -85,7 +86,7 @@ public class PBEKeySpec implements KeySpec { } if (password == null) { - this.password = new char[0]; + this.password = EmptyArray.CHAR; } else { this.password = new char[password.length]; System.arraycopy(password, 0, this.password, 0, password.length); @@ -123,7 +124,7 @@ public class PBEKeySpec implements KeySpec { } if (password == null) { - this.password = new char[0]; + this.password = EmptyArray.CHAR; } else { this.password = new char[password.length]; System.arraycopy(password, 0, this.password, 0, password.length); diff --git a/luni/src/main/java/javax/crypto/spec/PSource.java b/luni/src/main/java/javax/crypto/spec/PSource.java index a7f592c..5f520f3 100644 --- a/luni/src/main/java/javax/crypto/spec/PSource.java +++ b/luni/src/main/java/javax/crypto/spec/PSource.java @@ -17,6 +17,8 @@ package javax.crypto.spec; +import libcore.base.EmptyArray; + /** * The source of the label <code>L</code> as specified in <a * href="http://www.ietf.org/rfc/rfc3447.txt"> PKCS #1</a>. @@ -68,7 +70,7 @@ public class PSource { private PSpecified() { super("PSpecified"); - p = new byte[0]; + p = EmptyArray.BYTE; } /** diff --git a/luni/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java b/luni/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java index 3e58897..267648c 100644 --- a/luni/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java +++ b/luni/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.SocketException; +import libcore.base.EmptyArray; /** * Default inoperative implementation of javax.net.ssl.SSLServerSocketFactory @@ -35,12 +36,12 @@ class DefaultSSLServerSocketFactory extends SSLServerSocketFactory { @Override public String[] getDefaultCipherSuites() { - return new String[0]; + return EmptyArray.STRING; } @Override public String[] getSupportedCipherSuites() { - return new String[0]; + return EmptyArray.STRING; } @Override diff --git a/luni/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java b/luni/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java index a44404f..5b5025d 100644 --- a/luni/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java +++ b/luni/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java @@ -22,6 +22,7 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; +import libcore.base.EmptyArray; /** * Default inoperative implementation of javax.net.ssl.SSLSocketFactory @@ -37,12 +38,12 @@ class DefaultSSLSocketFactory extends SSLSocketFactory { @Override public String[] getDefaultCipherSuites() { - return new String[0]; + return EmptyArray.STRING; } @Override public String[] getSupportedCipherSuites() { - return new String[0]; + return EmptyArray.STRING; } @Override diff --git a/luni/src/main/java/libcore/base/EmptyArray.java b/luni/src/main/java/libcore/base/EmptyArray.java new file mode 100644 index 0000000..6e97876 --- /dev/null +++ b/luni/src/main/java/libcore/base/EmptyArray.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.base; + +public final class EmptyArray { + private EmptyArray() {} + + public static final boolean[] BOOLEAN = new boolean[0]; + public static final byte[] BYTE = new byte[0]; + public static final char[] CHAR = new char[0]; + public static final double[] DOUBLE = new double[0]; + public static final int[] INT = new int[0]; + + public static final Class<?>[] CLASS = new Class[0]; + public static final Object[] OBJECT = new Object[0]; + public static final String[] STRING = new String[0]; +} diff --git a/luni/src/main/java/libcore/base/Streams.java b/luni/src/main/java/libcore/base/Streams.java index e746389..0ef5189 100644 --- a/luni/src/main/java/libcore/base/Streams.java +++ b/luni/src/main/java/libcore/base/Streams.java @@ -16,6 +16,7 @@ package libcore.base; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -53,6 +54,22 @@ public final class Streams { } } + /** + * Returns a new byte[] containing the entire contents of the given InputStream. + * Useful when you don't know in advance how much data there is to be read. + */ + public static byte[] readFully(InputStream in) throws IOException { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + while (true) { + int byteCount = in.read(buffer); + if (byteCount == -1) { + return bytes.toByteArray(); + } + bytes.write(buffer, 0, byteCount); + } + } + public static void skipAll(InputStream in) throws IOException { do { in.skip(Long.MAX_VALUE); diff --git a/luni/src/main/java/libcore/internal/StringPool.java b/luni/src/main/java/libcore/internal/StringPool.java new file mode 100644 index 0000000..779a59a --- /dev/null +++ b/luni/src/main/java/libcore/internal/StringPool.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.internal; + +/** + * A pool of string instances. Unlike the {@link String#intern() VM's + * interned strings}, this pool provides no guarantee of reference equality. + * It is intended only to save allocations. This class is not thread safe. + */ +public final class StringPool { + + private final String[] pool = new String[512]; + + /** + * Returns a string equal to {@code new String(array, start, length)}. + */ + public String get(char[] array, int start, int length) { + // Compute an arbitrary hash of the content + int hashCode = 0; + for (int i = start; i < start + length; i++) { + hashCode = (hashCode * 31) + array[i]; + } + + // Pick a bucket using Doug Lea's supplemental secondaryHash function (from HashMap) + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + hashCode ^= (hashCode >>> 7) ^ (hashCode >>> 4); + int index = hashCode & (pool.length - 1); + + String pooled = pool[index]; + if (pooled != null && pooled.contentEquals(array, start, length)) { + return pooled; + } + + String result = new String(array, start, length); + pool[index] = result; + return result; + } +} diff --git a/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfo.java b/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfo.java index 3a70767..78e5440 100644 --- a/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfo.java +++ b/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfo.java @@ -17,8 +17,10 @@ package org.apache.harmony.luni.internal.util; import java.nio.charset.Charsets; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; +import java.util.Formatter; import java.util.TimeZone; import libcore.base.Objects; @@ -41,38 +43,38 @@ final class ZoneInfo extends TimeZone { private int mRawOffset; private final int[] mTransitions; - private final int[] mGmtOffs; + private final int[] mOffsets; private final byte[] mTypes; private final byte[] mIsDsts; private final boolean mUseDst; - ZoneInfo(String name, int[] transitions, byte[] type, int[] gmtoff, byte[] isdst) { + ZoneInfo(String name, int[] transitions, byte[] type, int[] gmtOffsets, byte[] isDsts) { mTransitions = transitions; mTypes = type; - mGmtOffs = gmtoff; - mIsDsts = isdst; + mIsDsts = isDsts; setID(name); // Use the latest non-daylight offset (if any) as the raw offset. - int laststd; - for (laststd = mTransitions.length - 1; laststd >= 0; laststd--) { - if (mIsDsts[mTypes[laststd] & 0xFF] == 0) { + int lastStd; + for (lastStd = mTransitions.length - 1; lastStd >= 0; lastStd--) { + if (mIsDsts[mTypes[lastStd] & 0xFF] == 0) { break; } } - if (laststd < 0) { - laststd = 0; + if (lastStd < 0) { + lastStd = 0; } - if (laststd >= mTypes.length) { - mRawOffset = mGmtOffs[0]; + if (lastStd >= mTypes.length) { + mRawOffset = gmtOffsets[0]; } else { - mRawOffset = mGmtOffs[mTypes[laststd] & 0xFF]; + mRawOffset = gmtOffsets[mTypes[lastStd] & 0xFF]; } - // Subtract the raw offset from all offsets so it can be changed - // and affect them too. - for (int i = 0; i < mGmtOffs.length; i++) { - mGmtOffs[i] -= mRawOffset; + // Rather than keep offsets from UTC, we use offsets from local time, so the raw offset + // can be changed and automatically affect all the offsets. + mOffsets = gmtOffsets; + for (int i = 0; i < mOffsets.length; i++) { + mOffsets[i] -= mRawOffset; } // Is this zone still observing DST? @@ -109,8 +111,9 @@ final class ZoneInfo extends TimeZone { calc += year * (365 * MILLISECONDS_PER_DAY); calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY; - if (year > 0) + if (year > 0) { calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY; + } boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0)); int[] mlen = isLeap ? LEAP : NORMAL; @@ -131,13 +134,13 @@ final class ZoneInfo extends TimeZone { int trans = Arrays.binarySearch(mTransitions, unix); if (trans == ~0) { - return mGmtOffs[0] * 1000 + mRawOffset; + return mRawOffset + mOffsets[0] * 1000; } if (trans < 0) { trans = ~trans - 1; } - return mGmtOffs[mTypes[trans] & 0xFF] * 1000 + mRawOffset; + return mRawOffset + mOffsets[mTypes[trans] & 0xFF] * 1000; } @Override @@ -183,7 +186,7 @@ final class ZoneInfo extends TimeZone { } return mRawOffset == other.mRawOffset // Arrays.equals returns true if both arrays are null - && Arrays.equals(mGmtOffs, other.mGmtOffs) + && Arrays.equals(mOffsets, other.mOffsets) && Arrays.equals(mIsDsts, other.mIsDsts) && Arrays.equals(mTypes, other.mTypes) && Arrays.equals(mTransitions, other.mTransitions); @@ -202,7 +205,7 @@ final class ZoneInfo extends TimeZone { final int prime = 31; int result = 1; result = prime * result + getID().hashCode(); - result = prime * result + Arrays.hashCode(mGmtOffs); + result = prime * result + Arrays.hashCode(mOffsets); result = prime * result + Arrays.hashCode(mIsDsts); result = prime * result + mRawOffset; result = prime * result + Arrays.hashCode(mTransitions); @@ -213,7 +216,29 @@ final class ZoneInfo extends TimeZone { @Override public String toString() { - return getClass().getName() + "[" + getID() + ",mRawOffset=" + mRawOffset + - ",mUseDst=" + mUseDst + "]"; + StringBuilder sb = new StringBuilder(); + // First the basics... + sb.append(getClass().getName() + "[" + getID() + ",mRawOffset=" + mRawOffset + + ",mUseDst=" + mUseDst + "]"); + // ...followed by a zdump(1)-like description of all our transition data. + sb.append("\n"); + Formatter f = new Formatter(sb); + for (int i = 0; i < mTransitions.length; ++i) { + int type = mTypes[i] & 0xff; + String utcTime = formatTime(mTransitions[i], TimeZone.getTimeZone("UTC")); + String localTime = formatTime(mTransitions[i], this); + int offset = mOffsets[type]; + int gmtOffset = mRawOffset/1000 + offset; + f.format("%4d : time=%10d %s = %s isDst=%d offset=%5d gmtOffset=%d\n", + i, mTransitions[i], utcTime, localTime, mIsDsts[type], offset, gmtOffset); + } + return sb.toString(); + } + + private static String formatTime(int s, TimeZone tz) { + SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy zzz"); + sdf.setTimeZone(tz); + long ms = ((long) s) * 1000L; + return sdf.format(new Date(ms)); } } diff --git a/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfoDB.java b/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfoDB.java index 264a9a0..e20d82d 100644 --- a/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfoDB.java +++ b/luni/src/main/java/org/apache/harmony/luni/internal/util/ZoneInfoDB.java @@ -219,7 +219,14 @@ public final class ZoneInfoDB { for (int i = 0; i < tzh_typecnt; ++i) { gmtOffsets[i] = data.readInt(); isDsts[i] = data.readByte(); - data.skip(1); // Skip abbreviation index. + // We skip the abbreviation index. This would let us provide historically-accurate + // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in + // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current + // names, though, so even if we did use this data to provide the correct abbreviations + // for en_US, we wouldn't be able to provide correct abbreviations for other locales, + // nor would we be able to provide correct long forms (such as "Yukon Standard Time") + // for any locale. (The RI doesn't do any better than us here either.) + data.skip(1); } return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts); diff --git a/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java b/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java index 17d3864..8fa73e0 100644 --- a/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java +++ b/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java @@ -29,6 +29,7 @@ import java.net.NetworkInterface; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; +import libcore.base.EmptyArray; import org.apache.harmony.luni.platform.Platform; /** @@ -154,8 +155,7 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override protected int peek(InetAddress sender) throws IOException { // We don't actually want the data: we just want the DatagramPacket's filled-in address. - byte[] bytes = new byte[0]; - DatagramPacket packet = new DatagramPacket(bytes, bytes.length); + DatagramPacket packet = new DatagramPacket(EmptyArray.BYTE, 0); int result = peekData(packet); Platform.NETWORK.setInetAddress(sender, packet.getAddress().getAddress()); return result; diff --git a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java b/luni/src/main/java/org/apache/harmony/luni/util/Base64.java index 52aaa96..3521b2d 100644 --- a/luni/src/main/java/org/apache/harmony/luni/util/Base64.java +++ b/luni/src/main/java/org/apache/harmony/luni/util/Base64.java @@ -22,6 +22,7 @@ package org.apache.harmony.luni.util; import java.nio.charset.Charset; +import libcore.base.EmptyArray; /** * This class implements Base64 encoding/decoding functionality @@ -40,7 +41,7 @@ public class Base64 { int length = len / 4 * 3; // return an empty array on empty or short input without padding if (length == 0) { - return new byte[0]; + return EmptyArray.BYTE; } // temporary array byte[] out = new byte[length]; diff --git a/luni/src/main/java/org/apache/harmony/luni/util/InputStreamHelper.java b/luni/src/main/java/org/apache/harmony/luni/util/InputStreamHelper.java deleted file mode 100644 index d1ceba5..0000000 --- a/luni/src/main/java/org/apache/harmony/luni/util/InputStreamHelper.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.harmony.luni.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Arrays; - -/** - * The class contains static {@link java.io.InputStream} utilities. - */ -public class InputStreamHelper { - - /** - * Provides access to a protected underlying buffer of - * <code>ByteArrayInputStream</code>. - */ - private static final Field BAIS_BUF; - - /** - * Provides access to a protected position in the underlying buffer of - * <code>ByteArrayInputStream</code>. - */ - private static final Field BAIS_POS; - - static { - final Field[] f = new Field[2]; - AccessController.doPrivileged(new PrivilegedAction<Object>() { - public Object run() { - try { - f[0] = ByteArrayInputStream.class.getDeclaredField("buf"); - f[0].setAccessible(true); - f[1] = ByteArrayInputStream.class.getDeclaredField("pos"); - f[1].setAccessible(true); - } catch (NoSuchFieldException nsfe) { - throw new InternalError(nsfe.getLocalizedMessage()); - } - return null; - } - }); - BAIS_BUF = f[0]; - BAIS_POS = f[1]; - } - - /** - * The extension of <code>ByteArrayInputStream</code> which exposes an - * underlying buffer. - */ - static class ExposedByteArrayInputStream extends ByteArrayInputStream { - public ExposedByteArrayInputStream(byte[] buf) { - super(buf); - } - - public ExposedByteArrayInputStream(byte[] buf, int offset, int length) { - super(buf, offset, length); - } - - /** - * Reads the whole stream and returns the stream snapshot. - */ - public synchronized byte[] expose() { - if (pos == 0 && count == buf.length) { - skip(count); - return buf; - } - - final int available = available(); - final byte[] buffer = new byte[available]; - System.arraycopy(buf, pos, buffer, 0, available); - skip(available); - return buffer; - } - } - - /** - * Reads all bytes from {@link java.io.ByteArrayInputStream} using its - * underlying buffer directly. - * - * @return an underlying buffer, if a current position is at the buffer - * beginning, and an end position is at the buffer end, or a copy of - * the underlying buffer part. - */ - private static byte[] expose(ByteArrayInputStream bais) { - byte[] buffer, buf; - int pos; - synchronized (bais) { - int available = bais.available(); - try { - buf = (byte[]) BAIS_BUF.get(bais); - pos = BAIS_POS.getInt(bais); - } catch (IllegalAccessException iae) { - throw new InternalError(iae.getLocalizedMessage()); - } - if (pos == 0 && available == buf.length) { - buffer = buf; - } else { - buffer = new byte[available]; - System.arraycopy(buf, pos, buffer, 0, available); - } - bais.skip(available); - } - return buffer; - } - - /** - * The utility method for reading the whole input stream into a snapshot - * buffer. To speed up the access it works with an underlying buffer for a - * given {@link java.io.ByteArrayInputStream}. - * - * @param is - * the stream to be read. - * @return the snapshot wrapping the buffer where the bytes are read to. - * @throws UnsupportedOperationException - * if the input stream data cannot be exposed - */ - public static byte[] expose(InputStream is) throws IOException, - UnsupportedOperationException { - if (is instanceof ExposedByteArrayInputStream) { - return ((ExposedByteArrayInputStream) is).expose(); - } - - if (is.getClass().equals(ByteArrayInputStream.class)) { - return expose((ByteArrayInputStream) is); - } - - // We don't know how to do this - throw new UnsupportedOperationException(); - } - - /** - * Reads all the bytes from the given input stream. - * - * Calls read multiple times on the given input stream until it receives an - * end of file marker. Returns the combined results as a byte array. Note - * that this method may block if the underlying stream read blocks. - * - * @param is - * the input stream to be read. - * @return the content of the stream as a byte array. - * @throws IOException - * if a read error occurs. - */ - public static byte[] readFullyAndClose(InputStream is) throws IOException { - - try { - // Initial read - byte[] buffer = new byte[1024]; - int count = is.read(buffer); - int nextByte = is.read(); - - // Did we get it all in one read? - if (nextByte == -1) { - return Arrays.copyOf(buffer, count); - } - - // Requires additional reads - ByteArrayOutputStream baos = new ByteArrayOutputStream(count * 2); - baos.write(buffer, 0, count); - baos.write(nextByte); - while (true) { - count = is.read(buffer); - if (count == -1) { - return baos.toByteArray(); - } - baos.write(buffer, 0, count); - } - } finally { - is.close(); - } - } -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java index c07e2b1..1ffd55a 100644 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java +++ b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java @@ -24,6 +24,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.security.InvalidParameterException; import java.security.SecureRandomSpi; +import libcore.base.EmptyArray; /** * This class extends the SecureRandomSpi class implementing all its abstract methods. <BR> @@ -234,13 +235,12 @@ public class SHA1PRNG_SecureRandomImpl extends SecureRandomSpi implements Serial throw new NegativeArraySizeException(Integer.toString(numBytes)); } if (numBytes == 0) { - return new byte[0]; + return EmptyArray.BYTE; } if (myRandom == null) { myRandom = new SHA1PRNG_SecureRandomImpl(); - myRandom.engineSetSeed(RandomBitsSupplier - .getRandomBits(DIGEST_LENGTH)); + myRandom.engineSetSeed(RandomBitsSupplier.getRandomBits(DIGEST_LENGTH)); } myBytes = new byte[numBytes]; diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatPullParser.java b/luni/src/main/java/org/apache/harmony/xml/ExpatPullParser.java index c48962a..2ddf464 100644 --- a/luni/src/main/java/org/apache/harmony/xml/ExpatPullParser.java +++ b/luni/src/main/java/org/apache/harmony/xml/ExpatPullParser.java @@ -763,7 +763,7 @@ public class ExpatPullParser implements XmlPullParser { } if (length == 0) { - return; + return; // TODO: can't happen? } flush(parser, length); diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java index 6942894..b39b9e4 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java @@ -20,6 +20,7 @@ package org.apache.harmony.xnet.provider.jsse; import java.io.IOException; import java.security.SecureRandom; import java.util.Arrays; +import libcore.base.EmptyArray; /** * Represents Client Hello message @@ -141,7 +142,7 @@ public class ClientHello extends Message { if (challenge_length < 16) { fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, short challenge data"); } - session_id = new byte[0]; + session_id = EmptyArray.BYTE; cipher_suites = new CipherSuite[cipher_spec_length/3]; for (int i = 0; i < cipher_suites.length; i++) { byte b0 = (byte) in.read(); diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java index 4103cf4..3f2daeb 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java @@ -19,6 +19,7 @@ package org.apache.harmony.xnet.provider.jsse; import java.io.IOException; import java.math.BigInteger; +import libcore.base.EmptyArray; /** * Represents client key exchange message @@ -79,7 +80,7 @@ public class ClientKeyExchange extends Message { * */ public ClientKeyExchange() { - exchange_keys = new byte[0]; + exchange_keys = EmptyArray.BYTE; length = 0; isRSA = false; } @@ -97,7 +98,7 @@ public class ClientKeyExchange extends Message { this.isRSA = isRSA; if (length == 0) { this.length = 0; - exchange_keys = new byte[0]; + exchange_keys = EmptyArray.BYTE; } else { int size; if (isRSA && !isTLS) {// SSL3.0 RSA diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java index e0ca33c..a100513 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java @@ -30,6 +30,7 @@ import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.net.ssl.SSLException; +import libcore.base.EmptyArray; /** * This class represents Signature type, as described in TLS v 1.0 Protocol @@ -190,15 +191,15 @@ public class DigitalSignature { } else if (cipher != null) { return cipher.doFinal(); } - return new byte[0]; + return EmptyArray.BYTE; } catch (DigestException e){ - return new byte[0]; + return EmptyArray.BYTE; } catch (SignatureException e){ - return new byte[0]; + return EmptyArray.BYTE; } catch (BadPaddingException e){ - return new byte[0]; + return EmptyArray.BYTE; } catch (IllegalBlockSizeException e){ - return new byte[0]; + return EmptyArray.BYTE; } } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java index 7df12c0..b5ffa06 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java @@ -30,6 +30,7 @@ import java.security.cert.CertificateException; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactorySpi; import javax.net.ssl.ManagerFactoryParameters; +import libcore.base.EmptyArray; /** * KeyManagerFactory implementation. @@ -56,7 +57,7 @@ public class KeyManagerFactoryImpl extends KeyManagerFactorySpi { if (password != null) { pwd = password.clone(); } else { - pwd = new char[0]; + pwd = EmptyArray.CHAR; } } else { keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); @@ -85,7 +86,7 @@ public class KeyManagerFactoryImpl extends KeyManagerFactorySpi { } }); if (keyStorePwd == null) { - pwd = new char[0]; + pwd = EmptyArray.CHAR; } else { pwd = keyStorePwd.toCharArray(); } diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java index 08c0822..da974f9 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java @@ -20,6 +20,7 @@ package org.apache.harmony.xnet.provider.jsse; import java.io.PrintStream; import java.security.AccessController; import java.security.PrivilegedAction; +import libcore.base.EmptyArray; /** * This class provides debug logging for JSSE provider implementation @@ -106,7 +107,7 @@ public class Logger { } }); } catch (Exception e) { - names = new String[0]; + names = EmptyArray.STRING; } } @@ -119,4 +120,3 @@ public class Logger { return null; } } - diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java index ee2b33a..40724e7 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java @@ -32,6 +32,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.net.ssl.SSLException; /** @@ -247,8 +248,11 @@ public final class NativeCrypto { // add(null, "PSK-RC4-SHA"); } - private static final String[] SUPPORTED_CIPHER_SUITES - = STANDARD_TO_OPENSSL_CIPHER_SUITES.keySet().toArray(new String[0]); + private static final String[] SUPPORTED_CIPHER_SUITES; + static { + Set<String> suites = STANDARD_TO_OPENSSL_CIPHER_SUITES.keySet(); + SUPPORTED_CIPHER_SUITES = suites.toArray(new String[suites.size()]); + } // SSL mode from ssl.h public static long SSL_MODE_HANDSHAKE_CUTTHROUGH = 0x00000040L; diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSocketFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSocketFactoryImpl.java index 222ebec..1899342 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSocketFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSocketFactoryImpl.java @@ -22,6 +22,7 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.security.KeyManagementException; import javax.net.ssl.SSLServerSocketFactory; +import libcore.base.EmptyArray; /** * Implementation of SSLServerSocketFactory. @@ -61,7 +62,7 @@ public class SSLServerSocketFactoryImpl extends SSLServerSocketFactory { @Override public String[] getDefaultCipherSuites() { if (instantiationException != null) { - return new String[0]; + return EmptyArray.STRING; } return sslParameters.getEnabledCipherSuites(); } @@ -72,7 +73,7 @@ public class SSLServerSocketFactoryImpl extends SSLServerSocketFactory { @Override public String[] getSupportedCipherSuites() { if (instantiationException != null) { - return new String[0]; + return EmptyArray.STRING; } return CipherSuite.getSupportedCipherSuiteNames(); } @@ -127,4 +128,3 @@ public class SSLServerSocketFactoryImpl extends SSLServerSocketFactory { (SSLParametersImpl) sslParameters.clone()); } } - diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java index 582d316..0bf4007 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java @@ -33,6 +33,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; +import libcore.base.EmptyArray; import libcore.base.Objects; /** @@ -175,7 +176,7 @@ public class SSLSessionImpl implements SSLSession, Cloneable { lastAccessedTime = creationTime; if (cipher_suite == null) { this.cipherSuite = CipherSuite.SSL_NULL_WITH_NULL_NULL; - id = new byte[0]; + id = EmptyArray.BYTE; isServer = false; isValid = false; } else { diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java index 4e185fd..9cd9eeb 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSocketFactoryImpl.java @@ -23,6 +23,7 @@ import java.net.Socket; import java.net.UnknownHostException; import java.security.KeyManagementException; import javax.net.ssl.SSLSocketFactory; +import libcore.base.EmptyArray; /** * Implementation of SSLSocketFactory. @@ -62,7 +63,7 @@ public class SSLSocketFactoryImpl extends SSLSocketFactory { @Override public String[] getDefaultCipherSuites() { if (instantiationException != null) { - return new String[0]; + return EmptyArray.STRING; } return sslParameters.getEnabledCipherSuites(); } @@ -73,7 +74,7 @@ public class SSLSocketFactoryImpl extends SSLSocketFactory { @Override public String[] getSupportedCipherSuites() { if (instantiationException != null) { - return new String[0]; + return EmptyArray.STRING; } return CipherSuite.getSupportedCipherSuiteNames(); } @@ -159,4 +160,3 @@ public class SSLSocketFactoryImpl extends SSLSocketFactory { // ------------------------------------------------------------------ } - diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java index 149be0c..f894331 100644 --- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java +++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java @@ -32,6 +32,7 @@ import java.security.cert.CertificateException; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactorySpi; +import libcore.base.EmptyArray; /** * @@ -91,7 +92,7 @@ public class TrustManagerFactoryImpl extends TrustManagerFactorySpi { }); char[] pwd; if (keyStorePwd == null) { - pwd = new char[0]; + pwd = EmptyArray.CHAR; } else { pwd = keyStorePwd.toCharArray(); } diff --git a/luni/src/main/java/org/apache/xml/serializer/ToHTMLStream.java b/luni/src/main/java/org/apache/xml/serializer/ToHTMLStream.java index 4f2927a..9414875 100644 --- a/luni/src/main/java/org/apache/xml/serializer/ToHTMLStream.java +++ b/luni/src/main/java/org/apache/xml/serializer/ToHTMLStream.java @@ -25,6 +25,8 @@ import java.util.Properties; import javax.xml.transform.Result; +import libcore.base.EmptyArray; + import org.apache.xml.serializer.utils.MsgKey; import org.apache.xml.serializer.utils.Utils; import org.xml.sax.Attributes; @@ -2054,7 +2056,7 @@ public class ToHTMLStream extends ToStream final Node m_Root; /** helper buffer to convert Strings to char arrays */ - private char[] m_charBuffer = new char[0]; + private char[] m_charBuffer = EmptyArray.CHAR; /** true if the search for an object is lower case only with the key */ private final boolean m_lowerCaseOnly; diff --git a/luni/src/main/java/org/xml/sax/ext/Attributes2Impl.java b/luni/src/main/java/org/xml/sax/ext/Attributes2Impl.java index 83cee5b..b3ec94a 100644 --- a/luni/src/main/java/org/xml/sax/ext/Attributes2Impl.java +++ b/luni/src/main/java/org/xml/sax/ext/Attributes2Impl.java @@ -7,7 +7,7 @@ package org.xml.sax.ext; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; - +import libcore.base.EmptyArray; /** * SAX2 extension helper for additional Attributes information, @@ -46,8 +46,8 @@ public class Attributes2Impl extends AttributesImpl implements Attributes2 */ public Attributes2Impl () { // BEGIN android-added - declared = new boolean[0]; - specified = new boolean[0]; + declared = EmptyArray.BOOLEAN; + specified = EmptyArray.BOOLEAN; // END android-added } diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp index 380bb7e..653c125 100644 --- a/luni/src/main/native/Register.cpp +++ b/luni/src/main/native/Register.cpp @@ -29,7 +29,6 @@ extern int register_java_io_Console(JNIEnv* env); extern int register_java_io_File(JNIEnv* env); extern int register_java_io_FileDescriptor(JNIEnv* env); extern int register_java_io_ObjectInputStream(JNIEnv* env); -extern int register_java_io_ObjectOutputStream(JNIEnv* env); extern int register_java_io_ObjectStreamClass(JNIEnv* env); extern int register_java_lang_Character(JNIEnv* env); extern int register_java_lang_Double(JNIEnv* env); @@ -80,7 +79,6 @@ extern "C" int registerCoreLibrariesJni(JNIEnv* env) { register_java_io_File(env) != -1 && register_java_io_FileDescriptor(env) != -1 && register_java_io_ObjectInputStream(env) != -1 && - register_java_io_ObjectOutputStream(env) != -1 && register_java_io_ObjectStreamClass(env) != -1 && register_java_lang_Character(env) != -1 && register_java_lang_Double(env) != -1 && diff --git a/luni/src/main/native/java_io_File.cpp b/luni/src/main/native/java_io_File.cpp index ea8d12b..81794d8 100644 --- a/luni/src/main/native/java_io_File.cpp +++ b/luni/src/main/native/java_io_File.cpp @@ -25,6 +25,9 @@ #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" #include "StaticAssert.h" +#include "readlink.h" + +#include <string> #include <dirent.h> #include <errno.h> @@ -32,8 +35,8 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> -#include <sys/vfs.h> #include <sys/types.h> +#include <sys/vfs.h> #include <time.h> #include <unistd.h> #include <utime.h> @@ -57,8 +60,7 @@ static bool doStat(JNIEnv* env, jstring javaPath, struct stat& sb) { static jlong File_lengthImpl(JNIEnv* env, jclass, jstring javaPath) { struct stat sb; if (!doStat(env, javaPath, sb)) { - // We must return 0 for files that don't exist. - // TODO: shouldn't we throw an IOException for ELOOP or EACCES? + // The RI returns 0 on error. (Even for errors like EACCES or ELOOP.) return 0; } @@ -126,24 +128,27 @@ static jstring File_readlink(JNIEnv* env, jclass, jstring javaPath) { return NULL; } - // We can't know how big a buffer readlink(2) will need, so we need to - // loop until it says "that fit". - size_t bufSize = 512; - while (true) { - LocalArray<512> buf(bufSize); - ssize_t len = readlink(path.c_str(), &buf[0], buf.size() - 1); - if (len == -1) { - // An error occurred. - return javaPath; - } - if (static_cast<size_t>(len) < buf.size() - 1) { - // The buffer was big enough. - buf[len] = '\0'; // readlink(2) doesn't NUL-terminate. - return env->NewStringUTF(&buf[0]); - } - // Try again with a bigger buffer. - bufSize *= 2; + std::string result; + if (!readlink(path.c_str(), result)) { + jniThrowIOException(env, errno); + return NULL; + } + return env->NewStringUTF(result.c_str()); +} + +static jstring File_realpath(JNIEnv* env, jclass, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + if (path.c_str() == NULL) { + return NULL; + } + + extern bool realpath(const char* path, std::string& resolved); + std::string result; + if (!realpath(path.c_str(), result)) { + jniThrowIOException(env, errno); + return NULL; } + return env->NewStringUTF(result.c_str()); } static jboolean File_setLastModifiedImpl(JNIEnv* env, jclass, jstring javaPath, jlong ms) { @@ -421,6 +426,22 @@ static jboolean File_renameToImpl(JNIEnv* env, jclass, jstring javaOldPath, jstr return (rename(oldPath.c_str(), newPath.c_str()) == 0); } +static void File_symlink(JNIEnv* env, jclass, jstring javaOldPath, jstring javaNewPath) { + ScopedUtfChars oldPath(env, javaOldPath); + if (oldPath.c_str() == NULL) { + return; + } + + ScopedUtfChars newPath(env, javaNewPath); + if (newPath.c_str() == NULL) { + return; + } + + if (symlink(oldPath.c_str(), newPath.c_str()) == -1) { + jniThrowIOException(env, errno); + } +} + static JNINativeMethod gMethods[] = { NATIVE_METHOD(File, canExecuteImpl, "(Ljava/lang/String;)Z"), NATIVE_METHOD(File, canReadImpl, "(Ljava/lang/String;)Z"), @@ -438,11 +459,13 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(File, listImpl, "(Ljava/lang/String;)[Ljava/lang/String;"), NATIVE_METHOD(File, mkdirImpl, "(Ljava/lang/String;)Z"), NATIVE_METHOD(File, readlink, "(Ljava/lang/String;)Ljava/lang/String;"), + NATIVE_METHOD(File, realpath, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(File, renameToImpl, "(Ljava/lang/String;Ljava/lang/String;)Z"), NATIVE_METHOD(File, setExecutableImpl, "(Ljava/lang/String;ZZ)Z"), NATIVE_METHOD(File, setLastModifiedImpl, "(Ljava/lang/String;J)Z"), NATIVE_METHOD(File, setReadableImpl, "(Ljava/lang/String;ZZ)Z"), NATIVE_METHOD(File, setWritableImpl, "(Ljava/lang/String;ZZ)Z"), + NATIVE_METHOD(File, symlink, "(Ljava/lang/String;Ljava/lang/String;)V"), }; int register_java_io_File(JNIEnv* env) { return jniRegisterNativeMethods(env, "java/io/File", gMethods, NELEM(gMethods)); diff --git a/luni/src/main/native/java_io_ObjectInputStream.cpp b/luni/src/main/native/java_io_ObjectInputStream.cpp index 1dd2f89..6d8aae7 100644 --- a/luni/src/main/native/java_io_ObjectInputStream.cpp +++ b/luni/src/main/native/java_io_ObjectInputStream.cpp @@ -17,53 +17,7 @@ #define LOG_TAG "ObjectInputStream" -#include "JNIHelp.h" #include "JniConstants.h" -#include "ScopedUtfChars.h" - -#define SETTER(FUNCTION_NAME, JNI_C_TYPE, JNI_TYPE_STRING, JNI_SETTER_FUNCTION) \ - static void FUNCTION_NAME(JNIEnv* env, jclass, jobject instance, \ - jclass declaringClass, jstring javaFieldName, JNI_C_TYPE newValue) { \ - if (instance == NULL) { \ - return; \ - } \ - ScopedUtfChars fieldName(env, javaFieldName); \ - if (fieldName.c_str() == NULL) { \ - return; \ - } \ - jfieldID fid = env->GetFieldID(declaringClass, fieldName.c_str(), JNI_TYPE_STRING); \ - if (fid != 0) { \ - env->JNI_SETTER_FUNCTION(instance, fid, newValue); \ - } \ - } - -SETTER(ObjectInputStream_setFieldBool, jboolean, "Z", SetBooleanField) -SETTER(ObjectInputStream_setFieldByte, jbyte, "B", SetByteField) -SETTER(ObjectInputStream_setFieldChar, jchar, "C", SetCharField) -SETTER(ObjectInputStream_setFieldDouble, jdouble, "D", SetDoubleField) -SETTER(ObjectInputStream_setFieldFloat, jfloat, "F", SetFloatField) -SETTER(ObjectInputStream_setFieldInt, jint, "I", SetIntField) -SETTER(ObjectInputStream_setFieldLong, jlong, "J", SetLongField) -SETTER(ObjectInputStream_setFieldShort, jshort, "S", SetShortField) - -static void ObjectInputStream_setFieldObject(JNIEnv* env, jclass, jobject instance, - jclass declaringClass, jstring javaFieldName, jstring javaFieldTypeName, jobject newValue) { - if (instance == NULL) { - return; - } - ScopedUtfChars fieldName(env, javaFieldName); - if (fieldName.c_str() == NULL) { - return; - } - ScopedUtfChars fieldTypeName(env, javaFieldTypeName); - if (fieldTypeName.c_str() == NULL) { - return; - } - jfieldID fid = env->GetFieldID(declaringClass, fieldName.c_str(), fieldTypeName.c_str()); - if (fid != 0) { - env->SetObjectField(instance, fid, newValue); - } -} static jobject ObjectInputStream_newInstance(JNIEnv* env, jclass, jclass instantiationClass, jclass constructorClass) { @@ -76,15 +30,6 @@ static jobject ObjectInputStream_newInstance(JNIEnv* env, jclass, static JNINativeMethod gMethods[] = { NATIVE_METHOD(ObjectInputStream, newInstance, "(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"), - NATIVE_METHOD(ObjectInputStream, setFieldObject, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"), - NATIVE_METHOD(ObjectInputStream, setFieldByte, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;B)V"), - NATIVE_METHOD(ObjectInputStream, setFieldChar, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;C)V"), - NATIVE_METHOD(ObjectInputStream, setFieldDouble, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;D)V"), - NATIVE_METHOD(ObjectInputStream, setFieldFloat, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;F)V"), - NATIVE_METHOD(ObjectInputStream, setFieldInt, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;I)V"), - NATIVE_METHOD(ObjectInputStream, setFieldLong, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;J)V"), - NATIVE_METHOD(ObjectInputStream, setFieldShort, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;S)V"), - NATIVE_METHOD(ObjectInputStream, setFieldBool, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Z)V"), }; int register_java_io_ObjectInputStream(JNIEnv* env) { return jniRegisterNativeMethods(env, "java/io/ObjectInputStream", gMethods, NELEM(gMethods)); diff --git a/luni/src/main/native/java_io_ObjectOutputStream.cpp b/luni/src/main/native/java_io_ObjectOutputStream.cpp deleted file mode 100644 index e99f9b2..0000000 --- a/luni/src/main/native/java_io_ObjectOutputStream.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "ObjectOutputStream" - -#include "JNIHelp.h" -#include "JniConstants.h" -#include "ScopedUtfChars.h" - -#define GETTER(FUNCTION_NAME, JNI_C_TYPE, JNI_TYPE_STRING, JNI_GETTER_FUNCTION) \ - static JNI_C_TYPE FUNCTION_NAME(JNIEnv* env, jclass, jobject instance, jclass declaringClass, \ - jstring javaFieldName) { \ - if (instance == NULL) { \ - return JNI_C_TYPE(); \ - } \ - ScopedUtfChars fieldName(env, javaFieldName); \ - if (fieldName.c_str() == NULL) { \ - return JNI_C_TYPE(); \ - } \ - jfieldID fid = env->GetFieldID(declaringClass, fieldName.c_str(), JNI_TYPE_STRING); \ - if (fid == 0) { \ - return JNI_C_TYPE(); \ - } \ - return env->JNI_GETTER_FUNCTION(instance, fid); \ - } - -GETTER(ObjectOutputStream_getFieldBool, jboolean, "Z", GetBooleanField) -GETTER(ObjectOutputStream_getFieldByte, jbyte, "B", GetByteField) -GETTER(ObjectOutputStream_getFieldChar, jchar, "C", GetCharField) -GETTER(ObjectOutputStream_getFieldDouble, jdouble, "D", GetDoubleField) -GETTER(ObjectOutputStream_getFieldFloat, jfloat, "F", GetFloatField) -GETTER(ObjectOutputStream_getFieldInt, jint, "I", GetIntField) -GETTER(ObjectOutputStream_getFieldLong, jlong, "J", GetLongField) -GETTER(ObjectOutputStream_getFieldShort, jshort, "S", GetShortField) - -static jobject ObjectOutputStream_getFieldObj(JNIEnv* env, jclass, jobject instance, - jclass declaringClass, jstring javaFieldName, jstring javaFieldTypeName) { - ScopedUtfChars fieldName(env, javaFieldName); - if (fieldName.c_str() == NULL) { - return NULL; - } - ScopedUtfChars fieldTypeName(env, javaFieldTypeName); - if (fieldTypeName.c_str() == NULL) { - return NULL; - } - jfieldID fid = env->GetFieldID(declaringClass, fieldName.c_str(), fieldTypeName.c_str()); - if (fid == 0) { - return NULL; - } - return env->GetObjectField(instance, fid); -} - -static JNINativeMethod gMethods[] = { - NATIVE_METHOD(ObjectOutputStream, getFieldBool, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)Z"), - NATIVE_METHOD(ObjectOutputStream, getFieldByte, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)B"), - NATIVE_METHOD(ObjectOutputStream, getFieldChar, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)C"), - NATIVE_METHOD(ObjectOutputStream, getFieldDouble, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)D"), - NATIVE_METHOD(ObjectOutputStream, getFieldFloat, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)F"), - NATIVE_METHOD(ObjectOutputStream, getFieldInt, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)I"), - NATIVE_METHOD(ObjectOutputStream, getFieldLong, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)J"), - NATIVE_METHOD(ObjectOutputStream, getFieldObj, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;"), - NATIVE_METHOD(ObjectOutputStream, getFieldShort, "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)S"), -}; -int register_java_io_ObjectOutputStream(JNIEnv* env) { - return jniRegisterNativeMethods(env, "java/io/ObjectOutputStream", gMethods, NELEM(gMethods)); -} diff --git a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp index c41eb82..a5ee710 100644 --- a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp +++ b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp @@ -1000,9 +1000,11 @@ static jint ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEnco } /** - * Expat decides for itself what character encoding it's looking at. The interface is in terms of - * bytes, which may point to UTF-8, UTF-16, ISO-8859-1, or US-ASCII. appendBytes, appendCharacters, - * and appendString thus all call through to this method, strange though that appears. + * Decodes the bytes as characters and parse the characters as XML. This + * performs character decoding using the charset specified at XML_Parser + * creation. For Java chars, that charset must be UTF-16 so that a Java char[] + * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars + * and appendString all call through this method. */ static void append(JNIEnv* env, jobject object, jint pointer, const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) { diff --git a/luni/src/main/native/readlink.cpp b/luni/src/main/native/readlink.cpp new file mode 100644 index 0000000..555d515 --- /dev/null +++ b/luni/src/main/native/readlink.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LocalArray.h" +#include "readlink.h" + +#include <string> +#include <unistd.h> + +bool readlink(const char* path, std::string& result) { + // We can't know how big a buffer readlink(2) will need, so we need to + // loop until it says "that fit". + size_t bufSize = 512; + while (true) { + LocalArray<512> buf(bufSize); + ssize_t len = readlink(path, &buf[0], buf.size()); + if (len == -1) { + // An error occurred. + return false; + } + if (static_cast<size_t>(len) < buf.size()) { + // The buffer was big enough. + result.assign(&buf[0], len); + return true; + } + // Try again with a bigger buffer. + bufSize *= 2; + } +} diff --git a/luni/src/main/native/readlink.h b/luni/src/main/native/readlink.h new file mode 100644 index 0000000..14031dc --- /dev/null +++ b/luni/src/main/native/readlink.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +/** + * Fills 'result' with the contents of the symbolic link 'path'. Sets errno and returns false on + * failure, returns true on success. The contents of 'result' on failure are undefined. Possible + * errors are those defined for readlink(2), except that this function takes care of sizing the + * buffer appropriately. + */ +bool readlink(const char* path, std::string& result); diff --git a/luni/src/main/native/realpath.cpp b/luni/src/main/native/realpath.cpp new file mode 100644 index 0000000..d1960a4 --- /dev/null +++ b/luni/src/main/native/realpath.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "readlink.h" + +#include <string> + +#include <errno.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <unistd.h> + +/** + * This differs from realpath(3) mainly in its behavior when a path element does not exist or can + * not be searched. realpath(3) treats that as an error and gives up, but we have Java-compatible + * behavior where we just assume the path element was not a symbolic link. This leads to a textual + * treatment of ".." from that point in the path, which may actually lead us back to a path we + * can resolve (as in "/tmp/does-not-exist/../blah.txt" which would be an error for realpath(3) + * but "/tmp/blah.txt" under the traditional Java interpretation). + * + * This implementation also removes all the fixed-length buffers of the C original. + */ +bool realpath(const char* path, std::string& resolved) { + // 'path' must be an absolute path. + if (path[0] != '/') { + errno = EINVAL; + return false; + } + + resolved = "/"; + if (path[1] == '\0') { + return true; + } + + // Iterate over path components in 'left'. + int symlinkCount = 0; + std::string left(path + 1); + while (!left.empty()) { + // Extract the next path component. + size_t nextSlash = left.find('/'); + std::string nextPathComponent = left.substr(0, nextSlash); + if (nextSlash != std::string::npos) { + left.erase(0, nextSlash + 1); + } else { + left.clear(); + } + if (nextPathComponent.empty()) { + continue; + } else if (nextPathComponent == ".") { + continue; + } else if (nextPathComponent == "..") { + // Strip the last path component except when we have single "/". + if (resolved.size() > 1) { + resolved.erase(resolved.rfind('/')); + } + continue; + } + + // Append the next path component. + if (resolved[resolved.size() - 1] != '/') { + resolved += '/'; + } + resolved += nextPathComponent; + + // See if we've got a symbolic link, and resolve it if so. + struct stat sb; + if (lstat(resolved.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) { + if (symlinkCount++ > MAXSYMLINKS) { + errno = ELOOP; + return false; + } + + std::string symlink; + if (!readlink(resolved.c_str(), symlink)) { + return false; + } + if (symlink[0] == '/') { + // The symbolic link is absolute, so we need to start from scratch. + resolved = "/"; + } else if (resolved.size() > 1) { + // The symbolic link is relative, so we just lose the last path component (which + // was the link). + resolved.erase(resolved.rfind('/')); + } + + if (!left.empty()) { + const char* maybeSlash = (symlink[symlink.size() - 1] != '/') ? "/" : ""; + left = symlink + maybeSlash + left; + } else { + left = symlink; + } + } + } + + // Remove trailing slash except when the resolved pathname is a single "/". + if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/') { + resolved.erase(resolved.size() - 1, 1); + } + return true; +} diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk index 774e8cb..43127d0 100644 --- a/luni/src/main/native/sub.mk +++ b/luni/src/main/native/sub.mk @@ -26,7 +26,6 @@ LOCAL_SRC_FILES := \ java_io_File.cpp \ java_io_FileDescriptor.cpp \ java_io_ObjectInputStream.cpp \ - java_io_ObjectOutputStream.cpp \ java_io_ObjectStreamClass.cpp \ java_lang_Character.cpp \ java_lang_Double.cpp \ @@ -52,6 +51,8 @@ LOCAL_SRC_FILES := \ org_apache_harmony_luni_platform_OSNetworkSystem.cpp \ org_apache_harmony_luni_util_FloatingPointParser.cpp \ org_apache_harmony_xml_ExpatParser.cpp \ + readlink.cpp \ + realpath.cpp \ valueOf.cpp LOCAL_C_INCLUDES += \ diff --git a/luni/src/test/java/libcore/internal/StringPoolTest.java b/luni/src/test/java/libcore/internal/StringPoolTest.java new file mode 100644 index 0000000..82c7c4e --- /dev/null +++ b/luni/src/test/java/libcore/internal/StringPoolTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.internal; + +import junit.framework.TestCase; +import libcore.internal.StringPool; + +public final class StringPoolTest extends TestCase { + + public void testStringPool() { + StringPool stringPool = new StringPool(); + String bcd = stringPool.get(new char[] { 'a', 'b', 'c', 'd', 'e' }, 1, 3); + assertEquals("bcd", bcd); + assertSame(bcd, stringPool.get(new char[] { 'a', 'b', 'c', 'd', 'e' }, 1, 3)); + } + + public void testHashCollision() { + StringPool stringPool = new StringPool(); + char[] a = { (char) 1, (char) 0 }; + char[] b = { (char) 0, (char) 31 }; + assertEquals(new String(a).hashCode(), new String(b).hashCode()); + + String aString = stringPool.get(a, 0, 2); + assertEquals(new String(a), aString); + String bString = stringPool.get(b, 0, 2); + assertEquals(new String(b), bString); + assertSame(bString, stringPool.get(b, 0, 2)); + assertNotSame(aString, stringPool.get(a, 0, 2)); + } +} diff --git a/luni/src/test/java/libcore/java/io/FileTest.java b/luni/src/test/java/libcore/java/io/FileTest.java index cd9b877..535975d 100644 --- a/luni/src/test/java/libcore/java/io/FileTest.java +++ b/luni/src/test/java/libcore/java/io/FileTest.java @@ -186,18 +186,6 @@ public class FileTest extends junit.framework.TestCase { } private static void ln_s(String target, String linkName) throws Exception { - String[] args = new String[] { "ln", "-s", target, linkName }; - // System.err.println("ln -s " + target + " " + linkName); - Process p = Runtime.getRuntime().exec(args); - int result = p.waitFor(); - if (result != 0) { - BufferedReader r = new BufferedReader(new InputStreamReader(p.getErrorStream())); - String line; - while ((line = r.readLine()) != null) { - System.err.println(line); - } - fail("ln -s " + target + " " + linkName + " failed. " + - "Does that file system support symlinks?"); - } + File.symlink(target, linkName); } } diff --git a/luni/src/test/java/libcore/java/lang/ArrayIndexOutOfBoundsExceptionTest.java b/luni/src/test/java/libcore/java/lang/ArrayIndexOutOfBoundsExceptionTest.java index ee7f6e8..041b931 100644 --- a/luni/src/test/java/libcore/java/lang/ArrayIndexOutOfBoundsExceptionTest.java +++ b/luni/src/test/java/libcore/java/lang/ArrayIndexOutOfBoundsExceptionTest.java @@ -24,7 +24,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { bs[2] = 0; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } @@ -34,7 +34,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { byte b = bs[2]; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } @@ -44,7 +44,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { ds[2] = 0.0; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } @@ -54,7 +54,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { double d = ds[2]; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } @@ -64,7 +64,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { os[2] = null; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } @@ -74,7 +74,7 @@ public final class ArrayIndexOutOfBoundsExceptionTest extends TestCase { try { Object o = os[2]; fail(); - } catch (Exception ex) { + } catch (ArrayIndexOutOfBoundsException ex) { assertEquals("index=2 length=1", ex.getMessage()); } } diff --git a/luni/src/test/java/libcore/java/lang/ArrayStoreExceptionTest.java b/luni/src/test/java/libcore/java/lang/ArrayStoreExceptionTest.java new file mode 100644 index 0000000..1b4288e --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/ArrayStoreExceptionTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.lang; + +import junit.framework.TestCase; + +public final class ArrayStoreExceptionTest extends TestCase { + public void testArrayStoreException() throws Exception { + Object[] array = new String[10]; + Object o = new Exception(); + try { + array[0] = o; + fail(); + } catch (ArrayStoreException ex) { + ex.printStackTrace(); + assertEquals("java.lang.Exception cannot be stored in an array of type java.lang.String[]", ex.getMessage()); + } + } +} diff --git a/luni/src/test/java/libcore/java/lang/ClassCastExceptionTest.java b/luni/src/test/java/libcore/java/lang/ClassCastExceptionTest.java new file mode 100644 index 0000000..780f620 --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/ClassCastExceptionTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.lang; + +import java.util.EnumMap; +import java.util.EnumSet; +import junit.framework.TestCase; + +public final class ClassCastExceptionTest extends TestCase { + public void testCast() throws Exception { + Object o = new Exception(); + try { + String s = (String) o; + fail(); + } catch (ClassCastException ex) { + assertEquals("java.lang.Exception cannot be cast to java.lang.String", ex.getMessage()); + } + } + + public void testClassCast() throws Exception { + Object o = new Exception(); + try { + String.class.cast(o); + fail(); + } catch (ClassCastException ex) { + assertEquals("java.lang.Exception cannot be cast to java.lang.String", ex.getMessage()); + } + } + + public void testClassAsSubclass() throws Exception { + try { + Exception.class.asSubclass(String.class); + fail(); + } catch (ClassCastException ex) { + assertEquals("java.lang.Exception cannot be cast to java.lang.String", ex.getMessage()); + } + } + + enum E { A, B, C }; + enum F { A, B, C }; + + public void testEnumMapPut() throws Exception { + EnumMap m = new EnumMap(E.class); + try { + m.put(F.A, "world"); + fail(); + } catch (ClassCastException ex) { + ex.printStackTrace(); + assertNotNull(ex.getMessage()); + } + } + + public void testMiniEnumSetAdd() throws Exception { + EnumSet m = EnumSet.noneOf(E.class); + try { + m.add(F.A); + fail(); + } catch (ClassCastException ex) { + ex.printStackTrace(); + assertNotNull(ex.getMessage()); + } + } + + public void testMiniEnumSetAddAll() throws Exception { + EnumSet m = EnumSet.noneOf(E.class); + EnumSet n = EnumSet.allOf(F.class); + try { + m.addAll(n); + fail(); + } catch (ClassCastException ex) { + ex.printStackTrace(); + assertNotNull(ex.getMessage()); + } + } + + enum HugeE { + A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0, L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0, Z0, + A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1, + A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, V2, W2, X2, Y2, Z2, + }; + enum HugeF { + A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0, L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0, Z0, + A1, B1, C1, D1, E1, F1, G1, H1, I1, J1, K1, L1, M1, N1, O1, P1, Q1, R1, S1, T1, U1, V1, W1, X1, Y1, Z1, + A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, V2, W2, X2, Y2, Z2, + }; + + public void testHugeEnumSetAdd() throws Exception { + EnumSet m = EnumSet.noneOf(HugeE.class); + try { + m.add(HugeF.A0); + fail(); + } catch (ClassCastException ex) { + ex.printStackTrace(); + assertNotNull(ex.getMessage()); + } + } + + public void testHugeEnumSetAddAll() throws Exception { + EnumSet m = EnumSet.noneOf(HugeE.class); + EnumSet n = EnumSet.allOf(HugeF.class); + try { + m.addAll(n); + fail(); + } catch (ClassCastException ex) { + ex.printStackTrace(); + assertNotNull(ex.getMessage()); + } + } +} diff --git a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java index 5968fdf..726b05a 100644 --- a/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java +++ b/luni/src/test/java/libcore/java/net/ConcurrentCloseTest.java @@ -28,6 +28,7 @@ import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import tests.net.StuckServer; /** * Test that Socket.close called on another thread interrupts a thread that's blocked doing @@ -47,24 +48,28 @@ public class ConcurrentCloseTest extends junit.framework.TestCase { } public void test_connect() throws Exception { + StuckServer ss = new StuckServer(); Socket s = new Socket(); new Killer(s).start(); try { System.err.println("connect..."); - s.connect(new InetSocketAddress("10.0.0.1", 7)); + s.connect(ss.getLocalSocketAddress()); fail("connect returned!"); } catch (SocketException expected) { assertEquals("Socket closed", expected.getMessage()); + } finally { + ss.close(); } } public void test_connect_nonBlocking() throws Exception { + StuckServer ss = new StuckServer(); SocketChannel s = SocketChannel.open(); new Killer(s.socket()).start(); try { System.err.println("connect (non-blocking)..."); s.configureBlocking(false); - s.connect(new InetSocketAddress("10.0.0.2", 7)); + s.connect(ss.getLocalSocketAddress()); while (!s.finishConnect()) { // Spin like a mad thing! } @@ -76,6 +81,8 @@ public class ConcurrentCloseTest extends junit.framework.TestCase { } catch (ClosedChannelException alsoOkay) { // For now, I'm assuming that we're happy as long as we get any reasonable exception. // It may be that we're supposed to guarantee only one or the other. + } finally { + ss.close(); } } diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index a1f99e9..1a11bd9 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -68,6 +68,7 @@ import tests.http.DefaultResponseCache; import tests.http.MockResponse; import tests.http.MockWebServer; import tests.http.RecordedRequest; +import tests.net.StuckServer; public class URLConnectionTest extends junit.framework.TestCase { @@ -1393,27 +1394,16 @@ public class URLConnectionTest extends junit.framework.TestCase { } public void testConnectTimeouts() throws IOException { - // Set a backlog and use it up so that we can expect the - // URLConnection to properly timeout. According to Steven's - // 4.5 "listen function", linux adds 3 to the specified - // backlog, so we need to connect 4 times before it will hang. - ServerSocket serverSocket = new ServerSocket(0, 1); - int serverPort = serverSocket.getLocalPort(); - Socket[] sockets = new Socket[4]; - for (int i = 0; i < sockets.length; i++) { - sockets[i] = new Socket("localhost", serverPort); - } - + StuckServer ss = new StuckServer(); + int serverPort = ss.getLocalPort(); URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection(); urlConnection.setConnectTimeout(1000); try { urlConnection.getInputStream(); fail(); } catch (SocketTimeoutException expected) { - } - - for (Socket s : sockets) { - s.close(); + } finally { + ss.close(); } } diff --git a/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java b/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java index a4012a8..4da54e6 100644 --- a/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java +++ b/luni/src/test/java/libcore/java/text/OldSimpleDateFormatTest.java @@ -408,12 +408,6 @@ public class OldSimpleDateFormatTest extends junit.framework.TestCase { assertFalse(test.testsFailed); } - public void testDefaultMinimalDaysInFirstWeek() { - Locale.setDefault(Locale.US); - assertEquals(1, new GregorianCalendar().getMinimalDaysInFirstWeek()); - assertEquals(1, new GregorianCalendar().getFirstDayOfWeek()); - } - /** * @tests java.text.SimpleDateFormat#format(java.util.Date) */ diff --git a/luni/src/test/java/libcore/java/util/zip/GzipTest.java b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java index f7e03dc..a28fae5 100644 --- a/luni/src/test/java/libcore/java/util/zip/GzipTest.java +++ b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java @@ -27,23 +27,25 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import junit.framework.TestCase; -public final class GzipTest extends TestCase { - - public void testRoundtripShortMessage() throws IOException { - byte[] data = gzip(("Hello World").getBytes("UTF-8")); - assertTrue(Arrays.equals(data, gunzip(gzip(data)))); +public final class GZIPInputStreamTest extends TestCase { + public void testShortMessage() throws IOException { + byte[] data = new byte[] { + 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, -13, 72, -51, -55, -55, 87, 8, -49, + 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 + }; + assertEquals("Hello World", new String(gunzip(data), "UTF-8")); } - public void testRoundtripLongMessage() throws IOException { + public void testLongMessage() throws IOException { byte[] data = new byte[1024 * 1024]; new Random().nextBytes(data); - assertTrue(Arrays.equals(data, gunzip(gzip(data)))); + assertTrue(Arrays.equals(data, gunzip(GZIPOutputStreamTest.gzip(data)))); } /** http://b/3042574 GzipInputStream.skip() causing CRC failures */ public void testSkip() throws IOException { byte[] data = new byte[1024 * 1024]; - byte[] gzipped = gzip(data); + byte[] gzipped = GZIPOutputStreamTest.gzip(data); GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(gzipped)); long totalSkipped = 0; @@ -53,19 +55,11 @@ public final class GzipTest extends TestCase { totalSkipped += count; } while (count > 0); - assertEquals(data.length, totalSkipped); + in.close(); } - public byte[] gzip(byte[] bytes) throws IOException { - ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); - OutputStream gzippedOut = new GZIPOutputStream(bytesOut); - gzippedOut.write(bytes); - gzippedOut.close(); - return bytesOut.toByteArray(); - } - - public byte[] gunzip(byte[] bytes) throws IOException { + public static byte[] gunzip(byte[] bytes) throws IOException { InputStream in = new GZIPInputStream(new ByteArrayInputStream(bytes)); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; @@ -73,6 +67,7 @@ public final class GzipTest extends TestCase { while ((count = in.read(buffer)) != -1) { out.write(buffer, 0, count); } + in.close(); return out.toByteArray(); } } diff --git a/luni/src/test/java/libcore/java/util/zip/GZIPOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/GZIPOutputStreamTest.java new file mode 100644 index 0000000..a61880f --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/GZIPOutputStreamTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Random; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import junit.framework.TestCase; + +public final class GZIPOutputStreamTest extends TestCase { + public void testShortMessage() throws IOException { + byte[] data = gzip(("Hello World").getBytes("UTF-8")); + assertEquals("[31, -117, 8, 0, 0, 0, 0, 0, 0, 0, -13, 72, -51, -55, -55, 87, 8, -49, " + + "47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0]", Arrays.toString(data)); + } + + public void testLongMessage() throws IOException { + byte[] data = new byte[1024 * 1024]; + new Random().nextBytes(data); + assertTrue(Arrays.equals(data, GZIPInputStreamTest.gunzip(gzip(data)))); + } + + public static byte[] gzip(byte[] bytes) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + OutputStream gzippedOut = new GZIPOutputStream(bytesOut); + gzippedOut.write(bytes); + gzippedOut.close(); + return bytesOut.toByteArray(); + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/ZipInputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/ZipInputStreamTest.java new file mode 100644 index 0000000..cb98322 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/ZipInputStreamTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Random; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import junit.framework.TestCase; + +public final class ZipInputStreamTest extends TestCase { + public void testShortMessage() throws IOException { + byte[] data = "Hello World".getBytes("UTF-8"); + byte[] zipped = ZipOutputStreamTest.zip("short", data); + assertEquals(Arrays.toString(data), Arrays.toString(unzip("short", zipped))); + } + + public void testLongMessage() throws IOException { + byte[] data = new byte[1024 * 1024]; + new Random().nextBytes(data); + assertTrue(Arrays.equals(data, unzip("r", ZipOutputStreamTest.zip("r", data)))); + } + + public static byte[] unzip(String name, byte[] bytes) throws IOException { + ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(bytes)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + ZipEntry entry = in.getNextEntry(); + assertEquals(name, entry.getName()); + + byte[] buffer = new byte[1024]; + int count; + while ((count = in.read(buffer)) != -1) { + out.write(buffer, 0, count); + } + + assertNull(in.getNextEntry()); // There's only one entry in the Zip files we create. + + in.close(); + return out.toByteArray(); + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java new file mode 100644 index 0000000..e7c518f --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Random; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import junit.framework.TestCase; + +public final class ZipOutputStreamTest extends TestCase { + public void testShortMessage() throws IOException { + byte[] data = "Hello World".getBytes("UTF-8"); + byte[] zipped = zip("short", data); + assertEquals(Arrays.toString(data), Arrays.toString(ZipInputStreamTest.unzip("short", zipped))); + } + + // http://b/3181430 --- a sign-extension bug on CRCs with the top bit set. + public void test3181430() throws IOException { + byte[] data = new byte[1]; // CRC32({ 0 }) == 0xd202ef8d + byte[] zipped = zip("z", data); + assertEquals(Arrays.toString(data), Arrays.toString(ZipInputStreamTest.unzip("z", zipped))); + } + + public void testLongMessage() throws IOException { + byte[] data = new byte[1024 * 1024]; + new Random().nextBytes(data); + assertTrue(Arrays.equals(data, ZipInputStreamTest.unzip("r", zip("r", data)))); + } + + public static byte[] zip(String name, byte[] bytes) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + ZipOutputStream zippedOut = new ZipOutputStream(bytesOut); + + ZipEntry entry = new ZipEntry(name); + zippedOut.putNextEntry(entry); + zippedOut.write(bytes); + zippedOut.closeEntry(); + + zippedOut.close(); + return bytesOut.toByteArray(); + } +} diff --git a/luni/src/test/java/org/apache/harmony/xml/ExpatPullParserTest.java b/luni/src/test/java/libcore/xml/ExpatPullParserTest.java index 71e2671..5d1725d 100644 --- a/luni/src/test/java/org/apache/harmony/xml/ExpatPullParserTest.java +++ b/luni/src/test/java/libcore/xml/ExpatPullParserTest.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; +import org.apache.harmony.xml.ExpatPullParser; import org.xmlpull.v1.XmlPullParser; public final class ExpatPullParserTest extends PullParserTest { @Override XmlPullParser newPullParser() { - ExpatPullParser parser = new ExpatPullParser(); - return parser; + return new ExpatPullParser(); } } diff --git a/luni/src/test/java/org/apache/harmony/xml/ExpatSaxParserTest.java b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java index 51804f2..2db8e82 100644 --- a/luni/src/test/java/org/apache/harmony/xml/ExpatSaxParserTest.java +++ b/luni/src/test/java/libcore/xml/ExpatSaxParserTest.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; import junit.framework.Assert; import junit.framework.TestCase; +import org.apache.harmony.xml.ExpatReader; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -45,6 +46,19 @@ public class ExpatSaxParserTest extends TestCase { private static final String SNIPPET = "<dagny dad=\"bob\">hello</dagny>"; + public void testGlobalReferenceTableOverflow() throws Exception { + // We used to use a JNI global reference per interned string. + // Framework apps have a limit of 2000 JNI global references per VM. + StringBuilder xml = new StringBuilder(); + xml.append("<root>"); + for (int i = 0; i < 4000; ++i) { + xml.append("<tag" + i + ">"); + xml.append("</tag" + i + ">"); + } + xml.append("</root>"); + parse(xml.toString(), new DefaultHandler()); + } + public void testExceptions() { // From startElement(). ContentHandler contentHandler = new DefaultHandler() { @@ -675,8 +689,7 @@ public class ExpatSaxParserTest extends TestCase { XMLReader reader = new ExpatReader(); reader.setContentHandler(contentHandler); reader.parse(new InputSource(new StringReader(xml))); - } - catch (IOException e) { + } catch (IOException e) { throw new AssertionError(e); } } diff --git a/luni/src/test/java/org/apache/harmony/xml/JaxenXPathTestSuite.java b/luni/src/test/java/libcore/xml/JaxenXPathTestSuite.java index 17f0341..eb790aa 100644 --- a/luni/src/test/java/org/apache/harmony/xml/JaxenXPathTestSuite.java +++ b/luni/src/test/java/libcore/xml/JaxenXPathTestSuite.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; import junit.framework.AssertionFailedError; import junit.framework.Test; diff --git a/luni/src/test/java/org/apache/harmony/xml/KxmlPullParserTest.java b/luni/src/test/java/libcore/xml/KxmlPullParserTest.java index 3603d89..71f25e9 100644 --- a/luni/src/test/java/org/apache/harmony/xml/KxmlPullParserTest.java +++ b/luni/src/test/java/libcore/xml/KxmlPullParserTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; diff --git a/luni/src/test/java/org/apache/harmony/xml/NamespacedAttributesLookupTest.java b/luni/src/test/java/libcore/xml/NamespacedAttributesLookupTest.java index 4f58262..cf12000 100644 --- a/luni/src/test/java/org/apache/harmony/xml/NamespacedAttributesLookupTest.java +++ b/luni/src/test/java/libcore/xml/NamespacedAttributesLookupTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; import junit.framework.TestCase; import org.xml.sax.Attributes; diff --git a/luni/src/test/java/libcore/xml/PullParserTest.java b/luni/src/test/java/libcore/xml/PullParserTest.java new file mode 100644 index 0000000..d76bed6 --- /dev/null +++ b/luni/src/test/java/libcore/xml/PullParserTest.java @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.xml; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; +import junit.framework.TestCase; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public abstract class PullParserTest extends TestCase { + + public void testAttributeNoValueWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + parser.setInput(new StringReader("<input checked></input>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("input", parser.getName()); + assertEquals("checked", parser.getAttributeName(0)); + assertEquals("checked", parser.getAttributeValue(0)); + } + + public void testAttributeUnquotedValueWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + parser.setInput(new StringReader("<input checked=true></input>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("input", parser.getName()); + assertEquals("checked", parser.getAttributeName(0)); + assertEquals("true", parser.getAttributeValue(0)); + } + + public void testUnterminatedEntityWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + parser.setInput(new StringReader("<foo bar='A&W'>mac&cheese</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("foo", parser.getName()); + assertEquals("bar", parser.getAttributeName(0)); + assertEquals("A&W", parser.getAttributeValue(0)); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("mac&cheese", parser.getText()); + } + + public void testEntitiesAndNamespaces() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#process-namespaces", true); + parser.setInput(new StringReader( + "<foo:a xmlns:foo='http://foo' xmlns:bar='http://bar'><bar:b/></foo:a>")); + testNamespace(parser); + } + + public void testEntitiesAndNamespacesWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#process-namespaces", true); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + parser.setInput(new StringReader( + "<foo:a xmlns:foo='http://foo' xmlns:bar='http://bar'><bar:b/></foo:a>")); + testNamespace(parser); + } + + private void testNamespace(XmlPullParser parser) throws XmlPullParserException, IOException { + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("http://foo", parser.getNamespace()); + assertEquals("a", parser.getName()); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("http://bar", parser.getNamespace()); + assertEquals("b", parser.getName()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + assertEquals("http://bar", parser.getNamespace()); + assertEquals("b", parser.getName()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + assertEquals("http://foo", parser.getNamespace()); + assertEquals("a", parser.getName()); + } + + public void testRegularNumericEntities() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>A</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken()); + assertEquals("#65", parser.getName()); + assertEquals("A", parser.getText()); + } + + public void testNumericEntitiesLargerThanChar() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<foo>� &#-2147483648;</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testNumericEntitiesLargerThanInt() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<foo>�</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testCharacterReferenceOfHexUtf16Surrogates() throws Exception { + testCharacterReferenceOfUtf16Surrogates("<foo>𐀀 𐎁 􏿰</foo>"); + } + + public void testCharacterReferenceOfDecimalUtf16Surrogates() throws Exception { + testCharacterReferenceOfUtf16Surrogates("<foo>𐀀 𐎁 􏿰</foo>"); + } + + private void testCharacterReferenceOfUtf16Surrogates(String xml) throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader(xml)); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals(new String(new int[] { 65536, ' ', 66433, ' ', 1114096 }, 0, 5), + parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testCharacterReferenceOfLastUtf16Surrogate() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals(new String(new int[] { 0x10FFFF }, 0, 1), parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testOmittedNumericEntities() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>&#;</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + /** + * Carriage returns followed by line feeds are silently discarded. + */ + public void testCarriageReturnLineFeed() throws Exception { + testLineEndings("\r\n<foo\r\na='b\r\nc'\r\n>d\r\ne</foo\r\n>\r\n"); + } + + /** + * Lone carriage returns are treated like newlines. + */ + public void testLoneCarriageReturn() throws Exception { + testLineEndings("\r<foo\ra='b\rc'\r>d\re</foo\r>\r"); + } + + public void testLoneNewLine() throws Exception { + testLineEndings("\n<foo\na='b\nc'\n>d\ne</foo\n>\n"); + } + + private void testLineEndings(String xml) throws XmlPullParserException, IOException { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader(xml)); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("foo", parser.getName()); + assertEquals("b c", parser.getAttributeValue(0)); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("d\ne", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + assertEquals("foo", parser.getName()); + assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); + } + + public void testXmlDeclaration() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<?xml version='1.0' encoding='UTF-8' standalone='no'?><foo/>")); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("1.0", parser.getProperty( + "http://xmlpull.org/v1/doc/properties.html#xmldecl-version")); + assertEquals(Boolean.FALSE, parser.getProperty( + "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone")); + assertEquals("UTF-8", parser.getInputEncoding()); + } + + public void testXmlDeclarationExtraAttributes() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<?xml version='1.0' encoding='UTF-8' standalone='no' a='b'?><foo/>")); + try { + parser.nextToken(); + fail(); + } catch (XmlPullParserException expected) { + } + } + + public void testCustomEntitiesUsingNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<foo a='cd&aaaaaaaaaa;ef'>wx&aaaaaaaaaa;yz</foo>")); + parser.defineEntityReplacementText("aaaaaaaaaa", "b"); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("cdbef", parser.getAttributeValue(0)); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("wxbyz", parser.getText()); + } + + public void testCustomEntitiesUsingNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<foo a='cd&aaaaaaaaaa;ef'>wx&aaaaaaaaaa;yz</foo>")); + parser.defineEntityReplacementText("aaaaaaaaaa", "b"); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("cdbef", parser.getAttributeValue(0)); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("wx", parser.getText()); + assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken()); + assertEquals("aaaaaaaaaa", parser.getName()); + assertEquals("b", parser.getText()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("yz", parser.getText()); + } + + public void testMissingEntities() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>&aaa;</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testMissingEntitiesWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + parser.setInput(new StringReader("<foo>&aaa;</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals(null, parser.getName()); + assertEquals("&aaa;", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testMissingEntitiesUsingNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + testMissingEntitiesUsingNextToken(parser); + } + + public void testMissingEntitiesUsingNextTokenWithRelaxed() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true); + testMissingEntitiesUsingNextToken(parser); + } + + private void testMissingEntitiesUsingNextToken(XmlPullParser parser) + throws XmlPullParserException, IOException { + parser.setInput(new StringReader("<foo>&aaa;</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken()); + assertEquals("aaa", parser.getName()); + assertEquals(null, parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testEntityInAttributeWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo bar=\"&\"></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("foo", parser.getName()); + assertEquals("&", parser.getAttributeValue(null, "bar")); + } + + public void testGreaterThanInText() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals(">", parser.getText()); + } + + public void testGreaterThanInAttribute() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo a='>'></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(">", parser.getAttributeValue(0)); + } + + public void testLessThanInText() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testLessThanInAttribute() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo a='<'></foo>")); + assertNextFails(parser); + } + + public void testQuotesInAttribute() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo a='\"' b=\"'\"></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("\"", parser.getAttributeValue(0)); + assertEquals("'", parser.getAttributeValue(1)); + } + + public void testQuotesInText() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>\" '</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("\" '", parser.getText()); + } + + public void testCdataDelimiterInAttribute() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo a=']]>'></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals("]]>", parser.getAttributeValue(0)); + } + + public void testCdataDelimiterInText() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>]]></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testUnexpectedEof() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><![C")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testUnexpectedSequence() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><![Cdata[bar]]></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testThreeDashCommentDelimiter() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><!--a---></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testTwoDashesInComment() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><!-- -- --></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertNextFails(parser); + } + + public void testEmptyComment() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><!----></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.COMMENT, parser.nextToken()); + assertEquals("", parser.getText()); + } + + /** + * Close braces require lookaheads because we need to defend against "]]>". + */ + public void testManyCloseBraces() throws Exception{ + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>]]]]]]]]]]]]]]]]]]]]]]]</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("]]]]]]]]]]]]]]]]]]]]]]]", parser.getText()); + } + + public void testCommentWithNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<!-- comment! -->cd</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("abcd", parser.getText()); + } + + public void testCommentWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<!-- comment! -->cd</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("ab", parser.getText()); + assertEquals(XmlPullParser.COMMENT, parser.nextToken()); + assertEquals(" comment! ", parser.getText()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("cd", parser.getText()); + } + + public void testCdataWithNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<![CDATA[cdef]]gh&i]]>jk</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("abcdef]]gh&ijk", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testCdataWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<![CDATA[cdef]]gh&i]]>jk</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("ab", parser.getText()); + assertEquals(XmlPullParser.CDSECT, parser.nextToken()); + assertEquals("cdef]]gh&i", parser.getText()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("jk", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + } + + public void testEntityLooksLikeCdataClose() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>]]></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("]]>", parser.getText()); + } + + public void testDoctypeWithNext() throws Exception { + String s = "<!DOCTYPE foo [" + + " <!ENTITY bb \"bar baz\">" + + " <!NOTATION png SYSTEM \"image/png\">" + + "]><foo>a&bb;c</foo>"; + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<!DOCTYPE foo [<!ENTITY bb \"bar baz\">]><foo>a&bb;c</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("abar bazc", parser.getText()); // TODO: this fails on gingerbread + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testDoctypeWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader( + "<!DOCTYPE foo [<!ENTITY bb \"bar baz\">]><foo>a&bb;c</foo>")); + assertEquals(XmlPullParser.DOCDECL, parser.nextToken()); + assertEquals(" foo [<!ENTITY bb \"bar baz\">]", parser.getText()); + assertNull(parser.getName()); + + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("a", parser.getText()); + assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken()); + assertEquals("bb", parser.getName()); + assertEquals("bar baz", parser.getText()); // TODO: this fails on gingerbread + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("c", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testProcessingInstructionWithNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<?cd efg hij?>kl</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals("abkl", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testProcessingInstructionWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>ab<?cd efg hij?>kl</foo>")); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("ab", parser.getText()); + assertEquals(XmlPullParser.PROCESSING_INSTRUCTION, parser.nextToken()); + assertEquals("cd efg hij", parser.getText()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("kl", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testWhitespaceWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader(" \n <foo> \n </foo> \n ")); + assertEquals(XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken()); + assertEquals(true, parser.isWhitespace()); + assertEquals(" \n ", parser.getText()); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals(true, parser.isWhitespace()); + assertEquals(" \n ", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + assertEquals(XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken()); + assertEquals(true, parser.isWhitespace()); + assertEquals(" \n ", parser.getText()); + assertEquals(XmlPullParser.END_DOCUMENT, parser.nextToken()); + } + + public void testLinesAndColumns() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("\n" + + " <foo><bar a='\n" + + "' b='cde'></bar\n" + + "><!--\n" + + "\n" + + "--><baz/>fg\n" + + "</foo>")); + assertEquals("1,1", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.IGNORABLE_WHITESPACE, parser.nextToken()); + assertEquals("2,3", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("2,8", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("3,11", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + assertEquals("4,2", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.COMMENT, parser.nextToken()); + assertEquals("6,4", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals("6,10", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + assertEquals("6,10", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.TEXT, parser.nextToken()); + assertEquals("7,1", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + assertEquals("7,7", parser.getLineNumber() + "," + parser.getColumnNumber()); + assertEquals(XmlPullParser.END_DOCUMENT, parser.nextToken()); + assertEquals("7,7", parser.getLineNumber() + "," + parser.getColumnNumber()); + } + + public void testEmptyEntityReferenceWithNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>∅</foo>")); + parser.defineEntityReplacementText("empty", ""); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testEmptyEntityReferenceWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo>∅</foo>")); + parser.defineEntityReplacementText("empty", ""); + assertEquals(XmlPullParser.START_TAG, parser.nextToken()); + assertEquals(XmlPullParser.ENTITY_REF, parser.nextToken()); + assertEquals("empty", parser.getName()); + assertEquals("", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.nextToken()); + } + + public void testEmptyCdataWithNext() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><![CDATA[]]></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testEmptyCdataWithNextToken() throws Exception { + XmlPullParser parser = newPullParser(); + parser.setInput(new StringReader("<foo><![CDATA[]]></foo>")); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(XmlPullParser.CDSECT, parser.nextToken()); + assertEquals("", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + } + + public void testParseReader() throws Exception { + String snippet = "<dagny dad=\"bob\">hello</dagny>"; + XmlPullParser parser = newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(new StringReader(snippet)); + validate(parser); + } + + public void testParseInputStream() throws Exception { + String snippet = "<dagny dad=\"bob\">hello</dagny>"; + XmlPullParser parser = newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(new ByteArrayInputStream(snippet.getBytes()), "UTF-8"); + validate(parser); + } + + static void validate(XmlPullParser parser) + throws XmlPullParserException, IOException { + assertEquals(XmlPullParser.START_DOCUMENT, parser.getEventType()); + assertEquals(0, parser.getDepth()); + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(1, parser.getDepth()); + assertEquals("dagny", parser.getName()); + assertEquals(1, parser.getAttributeCount()); + assertEquals("dad", parser.getAttributeName(0)); + assertEquals("bob", parser.getAttributeValue(0)); + assertEquals("bob", parser.getAttributeValue(null, "dad")); + assertEquals(XmlPullParser.TEXT, parser.next()); + assertEquals(1, parser.getDepth()); + assertEquals("hello", parser.getText()); + assertEquals(XmlPullParser.END_TAG, parser.next()); + assertEquals(1, parser.getDepth()); + assertEquals("dagny", parser.getName()); + assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); + assertEquals(0, parser.getDepth()); + } + + public void testNamespaces() throws Exception { + String xml = "<one xmlns='ns:default' xmlns:n1='ns:1' a='b'>\n" + + " <n1:two c='d' n1:e='f' xmlns:n2='ns:2'>text</n1:two>\n" + + "</one>"; + + XmlPullParser parser = newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new StringReader(xml)); + + assertEquals(0, parser.getDepth()); + assertEquals(0, parser.getNamespaceCount(0)); + + try { + parser.getNamespaceCount(1); + fail(); + } catch (IndexOutOfBoundsException e) { /* expected */ } + + // one + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(1, parser.getDepth()); + + checkNamespacesInOne(parser); + + // n1:two + assertEquals(XmlPullParser.START_TAG, parser.nextTag()); + + assertEquals(2, parser.getDepth()); + checkNamespacesInTwo(parser); + + // Body of two. + assertEquals(XmlPullParser.TEXT, parser.next()); + + // End of two. + assertEquals(XmlPullParser.END_TAG, parser.nextTag()); + + // Depth should still be 2. + assertEquals(2, parser.getDepth()); + + // We should still be able to see the namespaces from two. + checkNamespacesInTwo(parser); + + // End of one. + assertEquals(XmlPullParser.END_TAG, parser.nextTag()); + + // Depth should be back to 1. + assertEquals(1, parser.getDepth()); + + // We can still see the namespaces in one. + checkNamespacesInOne(parser); + + // We shouldn't be able to see the namespaces in two anymore. + try { + parser.getNamespaceCount(2); + fail(); + } catch (IndexOutOfBoundsException e) { /* expected */ } + + assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); + + // We shouldn't be able to see the namespaces in one anymore. + try { + parser.getNamespaceCount(1); + fail(); + } catch (IndexOutOfBoundsException e) { /* expected */ } + + assertEquals(0, parser.getNamespaceCount(0)); + } + + private void checkNamespacesInOne(XmlPullParser parser) throws XmlPullParserException { + assertEquals(2, parser.getNamespaceCount(1)); + + // Prefix for default namespace is null. + assertNull(parser.getNamespacePrefix(0)); + assertEquals("ns:default", parser.getNamespaceUri(0)); + + assertEquals("n1", parser.getNamespacePrefix(1)); + assertEquals("ns:1", parser.getNamespaceUri(1)); + + assertEquals("ns:default", parser.getNamespace(null)); + + // KXML returns null. + // assertEquals("ns:default", parser.getNamespace("")); + } + + private void checkNamespacesInTwo(XmlPullParser parser) throws XmlPullParserException { + // These should still be valid. + checkNamespacesInOne(parser); + + assertEquals(3, parser.getNamespaceCount(2)); + + // Default ns should still be in the stack + assertNull(parser.getNamespacePrefix(0)); + assertEquals("ns:default", parser.getNamespaceUri(0)); + } + + private void assertNextFails(XmlPullParser parser) throws IOException { + try { + parser.next(); + fail(); + } catch (XmlPullParserException expected) { + } + } + + /** + * Creates a new pull parser with namespace support. + */ + abstract XmlPullParser newPullParser(); +} diff --git a/luni/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java index 8623d58..7bf1980 100644 --- a/luni/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java +++ b/luni/src/test/java/libcore/xml/XsltXPathConformanceTestSuite.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.apache.harmony.xml; +package libcore.xml; import junit.framework.Assert; import junit.framework.AssertionFailedError; diff --git a/luni/src/test/java/org/apache/harmony/xml/PullParserTest.java b/luni/src/test/java/org/apache/harmony/xml/PullParserTest.java deleted file mode 100644 index d6d5370..0000000 --- a/luni/src/test/java/org/apache/harmony/xml/PullParserTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.harmony.xml; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.StringReader; -import junit.framework.TestCase; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -public abstract class PullParserTest extends TestCase { - private static final String SNIPPET = "<dagny dad=\"bob\">hello</dagny>"; - - public void testPullParser() { - try { - XmlPullParser parser = newPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - - // Test reader. - parser.setInput(new StringReader(SNIPPET)); - validate(parser); - - // Test input stream. - parser.setInput(new ByteArrayInputStream(SNIPPET.getBytes()), - "UTF-8"); - validate(parser); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - static void validate(XmlPullParser parser) - throws XmlPullParserException, IOException { - assertEquals(XmlPullParser.START_DOCUMENT, parser.getEventType()); - - assertEquals(0, parser.getDepth()); - - assertEquals(XmlPullParser.START_TAG, parser.next()); - - assertEquals(1, parser.getDepth()); - - assertEquals("dagny", parser.getName()); - assertEquals(1, parser.getAttributeCount()); - assertEquals("dad", parser.getAttributeName(0)); - assertEquals("bob", parser.getAttributeValue(0)); - assertEquals("bob", parser.getAttributeValue(null, "dad")); - - assertEquals(XmlPullParser.TEXT, parser.next()); - - assertEquals(1, parser.getDepth()); - - assertEquals("hello", parser.getText()); - - assertEquals(XmlPullParser.END_TAG, parser.next()); - - assertEquals(1, parser.getDepth()); - - assertEquals("dagny", parser.getName()); - - assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); - - assertEquals(0, parser.getDepth()); - } - - static final String XML = - "<one xmlns='ns:default' xmlns:n1='ns:1' a='b'>\n" - + " <n1:two c='d' n1:e='f' xmlns:n2='ns:2'>text</n1:two>\n" - + "</one>"; - - public void testExpatPullParserNamespaces() throws Exception { - XmlPullParser parser = newPullParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new StringReader(XML)); - - assertEquals(0, parser.getDepth()); - assertEquals(0, parser.getNamespaceCount(0)); - - try { - parser.getNamespaceCount(1); - fail(); - } catch (IndexOutOfBoundsException e) { /* expected */ } - - // one - assertEquals(XmlPullParser.START_TAG, parser.next()); - assertEquals(1, parser.getDepth()); - - checkNamespacesInOne(parser); - - // n1:two - assertEquals(XmlPullParser.START_TAG, parser.nextTag()); - - assertEquals(2, parser.getDepth()); - checkNamespacesInTwo(parser); - - // Body of two. - assertEquals(XmlPullParser.TEXT, parser.next()); - - // End of two. - assertEquals(XmlPullParser.END_TAG, parser.nextTag()); - - // Depth should still be 2. - assertEquals(2, parser.getDepth()); - - // We should still be able to see the namespaces from two. - checkNamespacesInTwo(parser); - - // End of one. - assertEquals(XmlPullParser.END_TAG, parser.nextTag()); - - // Depth should be back to 1. - assertEquals(1, parser.getDepth()); - - // We can still see the namespaces in one. - checkNamespacesInOne(parser); - - // We shouldn't be able to see the namespaces in two anymore. - try { - parser.getNamespaceCount(2); - fail(); - } catch (IndexOutOfBoundsException e) { /* expected */ } - - assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); - - // We shouldn't be able to see the namespaces in one anymore. - try { - parser.getNamespaceCount(1); - fail(); - } catch (IndexOutOfBoundsException e) { /* expected */ } - - assertEquals(0, parser.getNamespaceCount(0)); - } - - private void checkNamespacesInOne(XmlPullParser parser) throws XmlPullParserException { - assertEquals(2, parser.getNamespaceCount(1)); - - // Prefix for default namespace is null. - assertNull(parser.getNamespacePrefix(0)); - assertEquals("ns:default", parser.getNamespaceUri(0)); - - assertEquals("n1", parser.getNamespacePrefix(1)); - assertEquals("ns:1", parser.getNamespaceUri(1)); - - assertEquals("ns:default", parser.getNamespace(null)); - - // KXML returns null. - // assertEquals("ns:default", parser.getNamespace("")); - } - - private void checkNamespacesInTwo(XmlPullParser parser) throws XmlPullParserException { - // These should still be valid. - checkNamespacesInOne(parser); - - assertEquals(3, parser.getNamespaceCount(2)); - - // Default ns should still be in the stack - assertNull(parser.getNamespacePrefix(0)); - assertEquals("ns:default", parser.getNamespaceUri(0)); - } - - /** - * Creates a new pull parser with namespace support. - */ - abstract XmlPullParser newPullParser(); -} diff --git a/support/src/test/java/tests/net/StuckServer.java b/support/src/test/java/tests/net/StuckServer.java new file mode 100644 index 0000000..4230f17 --- /dev/null +++ b/support/src/test/java/tests/net/StuckServer.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests.net; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; + +/** + * A test ServerSocket that you can't connect to --- connects will time out. + */ +public final class StuckServer { + private ServerSocket serverSocket; + private ArrayList<Socket> clients = new ArrayList<Socket>(); + + public StuckServer() throws IOException { + // Set a backlog and use it up so that we can expect the + // connection to time out. According to Steven's + // 4.5 "listen function", Linux adds 3 to the specified + // backlog, so we need to connect 4 times before it will hang. + serverSocket = new ServerSocket(0, 1); + for (int i = 0; i < 4; i++) { + clients.add(new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort())); + } + } + + public InetSocketAddress getLocalSocketAddress() { + return (InetSocketAddress) serverSocket.getLocalSocketAddress(); + } + + public int getLocalPort() { + return serverSocket.getLocalPort(); + } + + public void close() throws IOException { + serverSocket.close(); + for (Socket client : clients) { + client.close(); + } + } +} diff --git a/support/src/test/java/tests/resources/GZIPInputStream/hyts_gInput.txt.gz b/support/src/test/java/tests/resources/GZIPInputStream/hyts_gInput.txt.gz Binary files differdeleted file mode 100644 index e0f5a00..0000000 --- a/support/src/test/java/tests/resources/GZIPInputStream/hyts_gInput.txt.gz +++ /dev/null diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java index dd73f0b..4f41fe0 100644 --- a/xml/src/main/java/org/kxml2/io/KXmlParser.java +++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java @@ -22,31 +22,45 @@ package org.kxml2.io; -import java.io.*; -import java.util.*; - -import org.xmlpull.v1.*; - -/** A simple, pull based XML parser. This classe replaces the kXML 1 - XmlParser class and the corresponding event classes. */ - +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; +import libcore.internal.StringPool; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * A pull based XML parser. + */ public class KXmlParser implements XmlPullParser { - private Object location; + private static final char[] START_COMMENT = { '<', '!', '-', '-' }; + private static final char[] END_COMMENT = { '-', '-', '>' }; + private static final char[] COMMENT_DOUBLE_DASH = { '-', '-' }; + private static final char[] START_CDATA = { '<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[' }; + private static final char[] END_CDATA = { ']', ']', '>' }; + private static final char[] START_PROCESSING_INSTRUCTION = { '<', '?' }; + private static final char[] END_PROCESSING_INSTRUCTION = { '?', '>' }; + private static final char[] START_DOCTYPE = { '<', '!', 'D', 'O', 'C', 'T', 'Y', 'P', 'E' }; + // no END_DOCTYPE because doctype must be parsed + static final private String UNEXPECTED_EOF = "Unexpected EOF"; static final private String ILLEGAL_TYPE = "Wrong event type"; - static final private int LEGACY = 999; - static final private int XML_DECL = 998; + static final private int XML_DECLARATION = 998; // general + private String location; private String version; private Boolean standalone; private boolean processNsp; private boolean relaxed; - private boolean keepNamespaceAttributes; // android-added - private Hashtable entityMap; + private boolean keepNamespaceAttributes; + private Map<String, String> entityMap; private int depth; private String[] elementStack = new String[16]; private String[] nspStack = new String[8]; @@ -56,33 +70,31 @@ public class KXmlParser implements XmlPullParser { private Reader reader; private String encoding; - private char[] srcBuf; - - private int srcPos; - private int srcCount; - - private int line; - private int column; - - // txtbuffer + private final char[] buffer = new char[8192]; + private int position = 0; + private int limit = 0; - /** Target buffer for storing incoming text (including aggregated resolved entities) */ - private char[] txtBuf = new char[128]; - /** Write position */ - private int txtPos; + /* + * Track the number of newlines and columns preceding the current buffer. To + * compute the line and column of a position in the buffer, compute the line + * and column in the buffer and add the preceding values. + */ + private int bufferStartLine; + private int bufferStartColumn; - // Event-related + // the current token private int type; private boolean isWhitespace; private String namespace; private String prefix; private String name; + private String text; private boolean degenerated; private int attributeCount; - /** + /* * The current element's attributes arranged in groups of 4: * i + 0 = attribute namespace URI * i + 1 = attribute namespace prefix @@ -90,54 +102,38 @@ public class KXmlParser implements XmlPullParser { * i + 3 = attribute value */ private String[] attributes = new String[16]; -// private int stackMismatch = 0; - private String error; - - /** - * A separate peek buffer seems simpler than managing - * wrap around in the first level read buffer */ - private int[] peek = new int[2]; - private int peekCount; - private boolean wasCR; + private String error; private boolean unresolved; - private boolean token; - public KXmlParser() { - srcBuf = - new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128]; - } + public final StringPool stringPool = new StringPool(); - // BEGIN android-added /** - * Retains namespace attributes like {@code xmlns="http://foo"} or {@code - * xmlns:foo="http:foo"} in pulled elements. Most applications will only be - * interested in the effective namespaces of their elements, so these - * attributes aren't useful. But for structure preserving wrappers like DOM, - * it is necessary to keep the namespace data around. + * Retains namespace attributes like {@code xmlns="http://foo"} or {@code xmlns:foo="http:foo"} + * in pulled elements. Most applications will only be interested in the effective namespaces of + * their elements, so these attributes aren't useful. But for structure preserving wrappers like + * DOM, it is necessary to keep the namespace data around. */ public void keepNamespaceAttributes() { this.keepNamespaceAttributes = true; } - // END android-added - private final boolean isProp(String n1, boolean prop, String n2) { - if (!n1.startsWith("http://xmlpull.org/v1/doc/")) + private boolean isProp(String n1, boolean prop, String n2) { + if (!n1.startsWith("http://xmlpull.org/v1/doc/")) { return false; - if (prop) + } + if (prop) { return n1.substring(42).equals(n2); - else + } else { return n1.substring(40).equals(n2); + } } - private final boolean adjustNsp() throws XmlPullParserException { - + private boolean adjustNsp() throws XmlPullParserException { boolean any = false; for (int i = 0; i < attributeCount << 2; i += 4) { - // * 4 - 4; i >= 0; i -= 4) { - String attrName = attributes[i + 2]; int cut = attrName.indexOf(':'); String prefix; @@ -145,32 +141,26 @@ public class KXmlParser implements XmlPullParser { if (cut != -1) { prefix = attrName.substring(0, cut); attrName = attrName.substring(cut + 1); - } - else if (attrName.equals("xmlns")) { + } else if (attrName.equals("xmlns")) { prefix = attrName; attrName = null; - } - else + } else { continue; + } if (!prefix.equals("xmlns")) { any = true; - } - else { + } else { int j = (nspCounts[depth]++) << 1; nspStack = ensureCapacity(nspStack, j + 2); nspStack[j] = attrName; nspStack[j + 1] = attributes[i + 3]; - if (attrName != null && attributes[i + 3].isEmpty()) - error("illegal empty namespace"); - - // prefixMap = new PrefixMap (prefixMap, attrName, attr.getValue ()); - - //System.out.println (prefixMap); + if (attrName != null && attributes[i + 3].isEmpty()) { + checkRelaxed("illegal empty namespace"); + } - // BEGIN android-changed if (keepNamespaceAttributes) { // explicitly set the namespace for unprefixed attributes // such as xmlns="http://foo" @@ -186,7 +176,6 @@ public class KXmlParser implements XmlPullParser { i -= 4; } - // END android-changed } } @@ -196,45 +185,33 @@ public class KXmlParser implements XmlPullParser { String attrName = attributes[i + 2]; int cut = attrName.indexOf(':'); - if (cut == 0 && !relaxed) + if (cut == 0 && !relaxed) { throw new RuntimeException( - "illegal attribute name: " + attrName + " at " + this); - - else if (cut != -1) { + "illegal attribute name: " + attrName + " at " + this); + } else if (cut != -1) { String attrPrefix = attrName.substring(0, cut); attrName = attrName.substring(cut + 1); String attrNs = getNamespace(attrPrefix); - if (attrNs == null && !relaxed) + if (attrNs == null && !relaxed) { throw new RuntimeException( - "Undefined Prefix: " + attrPrefix + " in " + this); + "Undefined Prefix: " + attrPrefix + " in " + this); + } attributes[i] = attrNs; attributes[i + 1] = attrPrefix; attributes[i + 2] = attrName; - - /* - if (!relaxed) { - for (int j = (attributeCount << 2) - 4; j > i; j -= 4) - if (attrName.equals(attributes[j + 2]) - && attrNs.equals(attributes[j])) - exception( - "Duplicate Attribute: {" - + attrNs - + "}" - + attrName); - } - */ } } } int cut = name.indexOf(':'); - if (cut == 0) - error("illegal tag name: " + name); + if (cut == 0) { + checkRelaxed("illegal tag name: " + name); + } if (cut != -1) { prefix = name.substring(0, cut); @@ -244,488 +221,494 @@ public class KXmlParser implements XmlPullParser { this.namespace = getNamespace(prefix); if (this.namespace == null) { - if (prefix != null) - error("undefined prefix: " + prefix); + if (prefix != null) { + checkRelaxed("undefined prefix: " + prefix); + } this.namespace = NO_NAMESPACE; } return any; } - private final String[] ensureCapacity(String[] arr, int required) { - if (arr.length >= required) + private String[] ensureCapacity(String[] arr, int required) { + if (arr.length >= required) { return arr; + } String[] bigger = new String[required + 16]; System.arraycopy(arr, 0, bigger, 0, arr.length); return bigger; } - private final void error(String desc) throws XmlPullParserException { - if (relaxed) { - if (error == null) - error = "ERR: " + desc; + private void checkRelaxed(String errorMessage) throws XmlPullParserException { + if (!relaxed) { + throw new XmlPullParserException(errorMessage, this, null); + } + if (error == null) { + error = "Error: " + errorMessage; } - else - exception(desc); } - private final void exception(String desc) throws XmlPullParserException { - throw new XmlPullParserException( - desc.length() < 100 ? desc : desc.substring(0, 100) + "\n", - this, - null); + public int next() throws XmlPullParserException, IOException { + return next(false); } - /** - * common base for next and nextToken. Clears the state, except from - * txtPos and whitespace. Does not set the type variable */ - - private final void nextImpl() throws IOException, XmlPullParserException { + public int nextToken() throws XmlPullParserException, IOException { + return next(true); + } - if (reader == null) - exception("No Input specified"); + private int next(boolean justOneToken) throws IOException, XmlPullParserException { + if (reader == null) { + throw new XmlPullParserException("setInput() must be called first.", this, null); + } - if (type == END_TAG) + if (type == END_TAG) { depth--; + } - while (true) { - attributeCount = -1; - - // degenerated needs to be handled before error because of possible - // processor expectations(!) - - if (degenerated) { - degenerated = false; - type = END_TAG; - return; - } + // degenerated needs to be handled before error because of possible + // processor expectations(!) + if (degenerated) { + degenerated = false; + type = END_TAG; + return type; + } - if (error != null) { - for (int i = 0; i < error.length(); i++) - push(error.charAt(i)); - //text = error; - error = null; + if (error != null) { + if (justOneToken) { + text = error; type = COMMENT; - return; + error = null; + return type; + } else { + error = null; } + } + type = peekType(); -// if (relaxed -// && (stackMismatch > 0 || (peek(0) == -1 && depth > 0))) { -// int sp = (depth - 1) << 2; -// type = END_TAG; -// namespace = elementStack[sp]; -// prefix = elementStack[sp + 1]; -// name = elementStack[sp + 2]; -// if (stackMismatch != 1) -// error = "missing end tag /" + name + " inserted"; -// if (stackMismatch > 0) -// stackMismatch--; -// return; -// } - - prefix = null; - name = null; - namespace = null; - // text = null; - + if (type == XML_DECLARATION) { + readXmlDeclaration(); type = peekType(); + } - switch (type) { + text = null; + isWhitespace = true; + prefix = null; + name = null; + namespace = null; + attributeCount = -1; - case ENTITY_REF : - pushEntity(); - return; + while (true) { + switch (type) { - case START_TAG : - parseStartTag(false); - return; + /* + * Return immediately after encountering a start tag, end tag, or + * the end of the document. + */ + case START_TAG: + parseStartTag(false); + return type; + case END_TAG: + readEndTag(); + return type; + case END_DOCUMENT: + return type; + + /* + * Return after any text token when we're looking for a single + * token. Otherwise concatenate all text between tags. + */ + case ENTITY_REF: + if (justOneToken) { + StringBuilder entityTextBuilder = new StringBuilder(); + readEntity(entityTextBuilder, true); + text = entityTextBuilder.toString(); + break; + } + // fall-through + case TEXT: + text = readValue('<', !justOneToken, false); + if (depth == 0 && isWhitespace) { + type = IGNORABLE_WHITESPACE; + } + break; + case CDSECT: + read(START_CDATA); + text = readUntil(END_CDATA, true); + break; - case END_TAG : - parseEndTag(); - return; + /* + * Comments, processing instructions and declarations are returned + * when we're looking for a single token. Otherwise they're skipped. + */ + case COMMENT: + String commentText = readComment(justOneToken); + if (justOneToken) { + text = commentText; + } + break; + case PROCESSING_INSTRUCTION: + read(START_PROCESSING_INSTRUCTION); + String processingInstruction = readUntil(END_PROCESSING_INSTRUCTION, justOneToken); + if (justOneToken) { + text = processingInstruction; + } + break; + case DOCDECL: + String doctype = readDoctype(justOneToken); + if (justOneToken) { + text = doctype; + } + break; + } - case END_DOCUMENT : - return; + if (justOneToken) { + return type; + } - case TEXT : - // BEGIN android-changed: distinguish attribute values from normal text. - pushText('<', !token, false); - // END android-changed - if (depth == 0) { - if (isWhitespace) - type = IGNORABLE_WHITESPACE; - // make exception switchable for instances.chg... !!!! - // else - // exception ("text '"+getText ()+"' not allowed outside root element"); - } - return; + if (type == IGNORABLE_WHITESPACE) { + text = null; + } - default : - type = parseLegacy(token); - if (type != XML_DECL) - return; + /* + * We've read all that we can of a non-empty text block. Always + * report this as text, even if it was a CDATA block or entity + * reference. + */ + int peek = peekType(); + if (text != null && !text.isEmpty() && peek < TEXT) { + type = TEXT; + return type; } + + type = peek; } } - private final int parseLegacy(boolean push) - throws IOException, XmlPullParserException { - - String req = ""; - int term; - int result; - int prev = 0; - - read(); // < - int c = read(); - - if (c == '?') { - if ((peek(0) == 'x' || peek(0) == 'X') - && (peek(1) == 'm' || peek(1) == 'M')) { + /** + * Reads text until the specified delimiter is encountered. Consumes the + * text and the delimiter. + * + * @param returnText true to return the read text excluding the delimiter; + * false to return null. + */ + private String readUntil(char[] delimiter, boolean returnText) + throws IOException, XmlPullParserException { + int start = position; + StringBuilder result = null; + + if (returnText && text != null) { + result = new StringBuilder(); + result.append(text); + } - if (push) { - push(peek(0)); - push(peek(1)); + search: + while (true) { + if (position + delimiter.length >= limit) { + if (start < position && returnText) { + if (result == null) { + result = new StringBuilder(); + } + result.append(buffer, start, position - start); } - read(); - read(); - - if ((peek(0) == 'l' || peek(0) == 'L') && peek(1) <= ' ') { - - if (line != 1 || column > 4) - error("PI must not start with xml"); - - parseStartTag(true); + if (!fillBuffer(delimiter.length)) { + checkRelaxed(UNEXPECTED_EOF); + type = COMMENT; + return null; + } + start = position; + } - if (attributeCount < 1 || !"version".equals(attributes[2])) - error("version expected"); + // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length) + // when the VM has better method inlining + for (int i = 0; i < delimiter.length; i++) { + if (buffer[position + i] != delimiter[i]) { + position++; + continue search; + } + } - version = attributes[3]; + break; + } - int pos = 1; + int end = position; + position += delimiter.length; - if (pos < attributeCount - && "encoding".equals(attributes[2 + 4])) { - encoding = attributes[3 + 4]; - pos++; - } + if (!returnText) { + return null; + } else if (result == null) { + return stringPool.get(buffer, start, end - start); + } else { + result.append(buffer, start, end - start); + return result.toString(); + } + } - if (pos < attributeCount - && "standalone".equals(attributes[4 * pos + 2])) { - String st = attributes[3 + 4 * pos]; - if ("yes".equals(st)) - standalone = new Boolean(true); - else if ("no".equals(st)) - standalone = new Boolean(false); - else - error("illegal standalone value: " + st); - pos++; - } + /** + * Returns true if an XML declaration was read. + */ + private void readXmlDeclaration() throws IOException, XmlPullParserException { + if (bufferStartLine != 0 || bufferStartColumn != 0 || position != 0) { + checkRelaxed("processing instructions must not start with xml"); + } - if (pos != attributeCount) - error("illegal xmldecl"); + read(START_PROCESSING_INSTRUCTION); + parseStartTag(true); - isWhitespace = true; - txtPos = 0; + if (attributeCount < 1 || !"version".equals(attributes[2])) { + checkRelaxed("version expected"); + } - return XML_DECL; - } - } + version = attributes[3]; - /* int c0 = read (); - int c1 = read (); - int */ + int pos = 1; - term = '?'; - result = PROCESSING_INSTRUCTION; + if (pos < attributeCount && "encoding".equals(attributes[2 + 4])) { + encoding = attributes[3 + 4]; + pos++; } - else if (c == '!') { - if (peek(0) == '-') { - result = COMMENT; - req = "--"; - term = '-'; - } - else if (peek(0) == '[') { - result = CDSECT; - req = "[CDATA["; - term = ']'; - push = true; - } - else { - result = DOCDECL; - req = "DOCTYPE"; - term = -1; + + if (pos < attributeCount && "standalone".equals(attributes[4 * pos + 2])) { + String st = attributes[3 + 4 * pos]; + if ("yes".equals(st)) { + standalone = Boolean.TRUE; + } else if ("no".equals(st)) { + standalone = Boolean.FALSE; + } else { + checkRelaxed("illegal standalone value: " + st); } + pos++; } - else { - error("illegal: <" + c); - return COMMENT; - } - - for (int i = 0; i < req.length(); i++) - read(req.charAt(i)); - - if (result == DOCDECL) - parseDoctype(push); - else { - while (true) { - c = read(); - if (c == -1){ - error(UNEXPECTED_EOF); - return COMMENT; - } - if (push) - push(c); - - if ((term == '?' || c == term) - && peek(0) == term - && peek(1) == '>') - break; - - prev = c; - } + if (pos != attributeCount) { + checkRelaxed("unexpected attributes in XML declaration"); + } - if (term == '-' && prev == '-' && !relaxed) - error("illegal comment delimiter: --->"); + isWhitespace = true; + text = null; + } - read(); - read(); + private String readComment(boolean returnText) throws IOException, XmlPullParserException { + read(START_COMMENT); - if (push && term != '?') - txtPos--; + if (relaxed) { + return readUntil(END_COMMENT, returnText); + } + String commentText = readUntil(COMMENT_DOUBLE_DASH, returnText); + if (peekCharacter() != '>') { + throw new XmlPullParserException("Comments may not contain --", this, null); } - return result; + position++; + return commentText; } - /** precondition: <! consumed */ - - private final void parseDoctype(boolean push) - throws IOException, XmlPullParserException { + private String readDoctype(boolean returnText) throws IOException, XmlPullParserException { + read(START_DOCTYPE); + int start = position; + StringBuilder result = null; int nesting = 1; boolean quoted = false; - // read(); - while (true) { - int i = read(); - switch (i) { - - case -1 : - error(UNEXPECTED_EOF); - return; - - case '\'' : - quoted = !quoted; - break; + if (position >= limit) { + if (start < position && returnText) { + if (result == null) { + result = new StringBuilder(); + } + result.append(buffer, start, position - start); + } + if (!fillBuffer(1)) { + checkRelaxed(UNEXPECTED_EOF); + return null; + } + start = position; + } - case '<' : - if (!quoted) - nesting++; - break; + char i = buffer[position++]; - case '>' : - if (!quoted) { - if ((--nesting) == 0) - return; - } + if (i == '\'') { + quoted = !quoted; // TODO: should this include a double quote as well? + } else if (i == '<') { + if (!quoted) { + nesting++; + } + } else if (i == '>') { + if (!quoted && --nesting == 0) { break; + } } - if (push) - push(i); } - } - /* precondition: </ consumed */ - - private final void parseEndTag() - throws IOException, XmlPullParserException { + if (!returnText) { + return null; + } else if (result == null) { + return stringPool.get(buffer, start, position - start - 1); // omit the '>' + } else { + result.append(buffer, start, position - start - 1); // omit the '>' + return result.toString(); + } + } - read(); // '<' - read(); // '/' - name = readName(); + private void readEndTag() throws IOException, XmlPullParserException { + read('<'); + read('/'); + name = readName(); // TODO: pass the expected name in as a hint? skip(); read('>'); - int sp = (depth - 1) << 2; + int sp = (depth - 1) * 4; if (depth == 0) { - error("element stack empty"); + checkRelaxed("read end tag " + name + " with no tags open"); type = COMMENT; return; } - if (!relaxed) { - if (!name.equals(elementStack[sp + 3])) { - error("expected: /" + elementStack[sp + 3] + " read: " + name); - - // become case insensitive in relaxed mode - -// int probe = sp; -// while (probe >= 0 && !name.toLowerCase().equals(elementStack[probe + 3].toLowerCase())) { -// stackMismatch++; -// probe -= 4; -// } -// -// if (probe < 0) { -// stackMismatch = 0; -// // text = "unexpected end tag ignored"; -// type = COMMENT; -// return; -// } - } - - namespace = elementStack[sp]; - prefix = elementStack[sp + 1]; - name = elementStack[sp + 2]; + if (name.equals(elementStack[sp + 3])) { + namespace = elementStack[sp]; + prefix = elementStack[sp + 1]; + name = elementStack[sp + 2]; + } else if (!relaxed) { + throw new XmlPullParserException( + "expected: /" + elementStack[sp + 3] + " read: " + name, this, null); } } - private final int peekType() throws IOException { - switch (peek(0)) { - case -1 : - return END_DOCUMENT; - case '&' : - return ENTITY_REF; - case '<' : - switch (peek(1)) { - case '/' : - return END_TAG; - case '?' : - case '!' : - return LEGACY; - default : - return START_TAG; - } - default : - return TEXT; + /** + * Returns the type of the next token. + */ + private int peekType() throws IOException, XmlPullParserException { + if (position >= limit && !fillBuffer(1)) { + return END_DOCUMENT; } - } - - private final String get(int pos) { - return new String(txtBuf, pos, txtPos - pos); - } - - /* - private final String pop (int pos) { - String result = new String (txtBuf, pos, txtPos - pos); - txtPos = pos; - return result; - } - */ - private final void push(int c) { + if (buffer[position] == '&') { + return ENTITY_REF; - isWhitespace &= c <= ' '; + } else if (buffer[position] == '<') { + if (position + 2 >= limit && !fillBuffer(3)) { + throw new XmlPullParserException("Dangling <", this, null); + } - if (txtPos == txtBuf.length) { - char[] bigger = new char[txtPos * 4 / 3 + 4]; - System.arraycopy(txtBuf, 0, bigger, 0, txtPos); - txtBuf = bigger; + if (buffer[position + 1] == '/') { + return END_TAG; + } else if (buffer[position + 1] == '?') { + // we're looking for "<?xml " with case insensitivity + if ((position + 5 < limit || fillBuffer(6)) + && (buffer[position + 2] == 'x' || buffer[position + 2] == 'X') + && (buffer[position + 3] == 'm' || buffer[position + 3] == 'M') + && (buffer[position + 4] == 'l' || buffer[position + 4] == 'L') + && (buffer[position + 5] == ' ')) { + return XML_DECLARATION; + } else { + return PROCESSING_INSTRUCTION; + } + } else if (buffer[position + 1] == '!') { + if (buffer[position + 2] == START_DOCTYPE[2]) { + return DOCDECL; + } else if (buffer[position + 2] == START_CDATA[2]) { + return CDSECT; + } else if (buffer[position + 2] == START_COMMENT[2]) { + return COMMENT; + } else { + throw new XmlPullParserException("Unexpected <!", this, null); + } + } else { + return START_TAG; + } + } else { + return TEXT; } - - txtBuf[txtPos++] = (char) c; } - /** Sets name and attributes */ - - private final void parseStartTag(boolean xmldecl) - throws IOException, XmlPullParserException { - - if (!xmldecl) - read(); + /** + * Sets name and attributes + */ + private void parseStartTag(boolean xmldecl) throws IOException, XmlPullParserException { + if (!xmldecl) { + read('<'); + } name = readName(); attributeCount = 0; while (true) { skip(); - int c = peek(0); + if (position >= limit && !fillBuffer(1)) { + checkRelaxed(UNEXPECTED_EOF); + return; + } + + int c = buffer[position]; if (xmldecl) { if (c == '?') { - read(); + position++; read('>'); return; } - } - else { + } else { if (c == '/') { degenerated = true; - read(); + position++; skip(); read('>'); break; - } - - if (c == '>' && !xmldecl) { - read(); + } else if (c == '>') { + position++; break; } } - if (c == -1) { - error(UNEXPECTED_EOF); - //type = COMMENT; - return; - } - String attrName = readName(); - if (attrName.length() == 0) { - error("attr name expected"); - //type = COMMENT; - break; - } - - int i = (attributeCount++) << 2; - + int i = (attributeCount++) * 4; attributes = ensureCapacity(attributes, i + 4); - attributes[i++] = ""; attributes[i++] = null; attributes[i++] = attrName; skip(); + if (position >= limit && !fillBuffer(1)) { + checkRelaxed(UNEXPECTED_EOF); + return; + } + + if (buffer[position] == '=') { + position++; - if (peek(0) != '=') { - if(!relaxed){ - error("Attr.value missing f. "+attrName); - } - attributes[i] = attrName; - } else { - read('='); skip(); - int delimiter = peek(0); + if (position >= limit && !fillBuffer(1)) { + checkRelaxed(UNEXPECTED_EOF); + return; + } + char delimiter = buffer[position]; - if (delimiter != '\'' && delimiter != '"') { - if(!relaxed){ - error("attr value delimiter missing!"); - } + if (delimiter == '\'' || delimiter == '"') { + position++; + } else if (relaxed) { delimiter = ' '; } else { - read(); + throw new XmlPullParserException("attr value delimiter missing!", this, null); } - int p = txtPos; - // BEGIN android-changed: distinguish attribute values from normal text. - pushText(delimiter, true, true); - // END android-changed - - attributes[i] = get(p); - txtPos = p; + attributes[i] = readValue(delimiter, true, true); - if (delimiter != ' ') - read(); // skip endquote + if (delimiter != ' ') { + position++; // end quote + } + } else if (relaxed) { + attributes[i] = attrName; + } else { + checkRelaxed("Attr.value missing f. " + attrName); + attributes[i] = attrName; } } - int sp = depth++ << 2; - + int sp = depth++ * 4; elementStack = ensureCapacity(elementStack, sp + 4); elementStack[sp + 3] = name; @@ -737,20 +720,11 @@ public class KXmlParser implements XmlPullParser { nspCounts[depth] = nspCounts[depth - 1]; - /* - if(!relaxed){ - for (int i = attributeCount - 1; i > 0; i--) { - for (int j = 0; j < i; j++) { - if (getAttributeName(i).equals(getAttributeName(j))) - exception("Duplicate Attribute: " + getAttributeName(i)); - } - } - } - */ - if (processNsp) + if (processNsp) { adjustNsp(); - else + } else { namespace = ""; + } elementStack[sp] = namespace; elementStack[sp + 1] = prefix; @@ -758,236 +732,355 @@ public class KXmlParser implements XmlPullParser { } /** - * result: isWhitespace; if the setName parameter is set, - * the name of the entity is stored in "name" */ - - private final void pushEntity() - throws IOException, XmlPullParserException { - - push(read()); // & + * Reads an entity reference from the buffer, resolves it, and writes the + * resolved entity to {@code out}. If the entity cannot be read or resolved, + * {@code out} will contain the partial entity reference. + */ + private void readEntity(StringBuilder out, boolean isEntityToken) + throws IOException, XmlPullParserException { + int start = out.length(); + if (buffer[position++] != '&') { + throw new AssertionError(); + } - int pos = txtPos; + out.append('&'); while (true) { - int c = peek(0); - if (c == ';') { - read(); - break; - } - if (c < 128 && (c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') - && c != '_' && c != '-' && c != '#') { - if(!relaxed){ - error("unterminated entity ref"); - } + int c = peekCharacter(); - // BEGIN android-removed: avoid log spam. - // System.out.println("broken entitiy: "+get(pos-1)); - // END android-removed + if (c == ';') { + out.append(';'); + position++; + break; - //; ends with:"+(char)c); -// if (c != -1) -// push(c); + } else if (c >= 128 + || (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == '-' + || c == '#') { + position++; + out.append((char) c); + + } else if (relaxed) { + // intentionally leave the partial reference in 'out' return; - } - push(read()); + } else { + throw new XmlPullParserException("unterminated entity ref", this, null); + } } - String code = get(pos); - txtPos = pos - 1; - if (token && type == ENTITY_REF){ - name = code; - } + String code = out.substring(start + 1, out.length() - 1); - if (code.charAt(0) == '#') { - int c = - (code.charAt(1) == 'x' - ? Integer.parseInt(code.substring(2), 16) - : Integer.parseInt(code.substring(1))); - push(c); - return; + if (isEntityToken) { + name = code; } - String result = (String) entityMap.get(code); - - unresolved = result == null; - - if (unresolved) { - if (!token) - error("unresolved: &" + code + ";"); - } - else { - for (int i = 0; i < result.length(); i++) - push(result.charAt(i)); + String resolved; + if (code.startsWith("#")) { + try { + int c = code.startsWith("#x") + ? Integer.parseInt(code.substring(2), 16) + : Integer.parseInt(code.substring(1)); + out.delete(start, out.length()); + out.appendCodePoint(c); + unresolved = false; + } catch (NumberFormatException notANumber) { + throw new XmlPullParserException("Invalid character reference: &" + code); + } catch (IllegalArgumentException invalidCodePoint) { + throw new XmlPullParserException("Invalid character reference: &" + code); + } + } else if ((resolved = entityMap.get(code)) != null) { + out.delete(start, out.length()); + out.append(resolved); + unresolved = false; + } else { + // keep the unresolved entity "&code;" in the text for relaxed clients + unresolved = true; + if (!isEntityToken) { + checkRelaxed("unresolved: &" + code + ";"); + } } } - /** types: - '<': parse to any token (for nextToken ()) - '"': parse to quote - ' ': parse to whitespace or '>' - */ - - private final void pushText(int delimiter, boolean resolveEntities, boolean inAttributeValue) - throws IOException, XmlPullParserException { + /** + * Returns the current text or attribute value. This also has the side + * effect of setting isWhitespace to false if a non-whitespace character is + * encountered. + * + * @param delimiter {@code >} for text, {@code "} and {@code '} for quoted + * attributes, or a space for unquoted attributes. + */ + private String readValue(char delimiter, boolean resolveEntities, + boolean inAttributeValue) throws IOException, XmlPullParserException { - int next = peek(0); - int cbrCount = 0; + /* + * This method returns all of the characters from the current position + * through to an appropriate delimiter. + * + * If we're lucky (which we usually are), we'll return a single slice of + * the buffer. This fast path avoids allocating a string builder. + * + * There are 5 unlucky characters we could encounter: + * - "&": entities must be resolved. + * - "<": this isn't permitted in attributes unless relaxed. + * - "]": this requires a lookahead to defend against the forbidden + * CDATA section delimiter "]]>". + * - "\r": If a "\r" is followed by a "\n", we discard the "\r". If it + * isn't followed by "\n", we replace "\r" with either a "\n" + * in text nodes or a space in attribute values. + * - "\n": In attribute values, "\n" must be replaced with a space. + * + * We could also get unlucky by needing to refill the buffer midway + * through the text. + */ + + int start = position; + StringBuilder result = null; + + // if a text section was already started, prefix the start + if (!inAttributeValue && text != null) { + result = new StringBuilder(); + result.append(text); + } - while (next != -1 && next != delimiter) { // covers eof, '<', '"' + while (true) { - if (delimiter == ' ') - if (next <= ' ' || next == '>') - break; + /* + * Make sure we have at least a single character to read from the + * buffer. This mutates the buffer, so save the partial result + * to the slow path string builder first. + */ + if (position >= limit) { + if (start < position) { + if (result == null) { + result = new StringBuilder(); + } + result.append(buffer, start, position - start); + } + if (!fillBuffer(1)) { + return result != null ? result.toString() : ""; + } + start = position; + } - // BEGIN android-changed: "<" is not allowed in attribute values. - if (next == '&') { - if (!resolveEntities) - break; + char c = buffer[position]; - pushEntity(); + if (c == delimiter + || (delimiter == ' ' && (c <= ' ' || c == '>')) + || c == '&' && !resolveEntities) { + break; } - else if (next == '<' && inAttributeValue) { - error("Illegal: \"<\" inside attribute value"); + + if (c != '\r' + && (c != '\n' || !inAttributeValue) + && c != '&' + && c != '<' + && (c != ']' || inAttributeValue)) { + isWhitespace &= (c <= ' '); + position++; + continue; } - else if (next == '\n' && type == START_TAG) { - read(); - push(' '); + + /* + * We've encountered an unlucky character! Convert from fast + * path to slow path if we haven't done so already. + */ + if (result == null) { + result = new StringBuilder(); } - else - push(read()); - // END android-changed - - // BEGIN android-changed: "]]>" *is* allowed in attribute values, but - // is not allowed in regular text between markup. - final boolean allowCloseCdata = inAttributeValue; - if (!allowCloseCdata && (next == '>' && cbrCount >= 2 && delimiter != ']')) { - error("Illegal: \"]]>\" outside CDATA section"); + result.append(buffer, start, position - start); + + if (c == '\r') { + if ((position + 1 < limit || fillBuffer(2)) && buffer[position + 1] == '\n') { + position++; + } + c = inAttributeValue ? ' ' : '\n'; + + } else if (c == '\n') { + c = ' '; + + } else if (c == '&') { + isWhitespace = false; // TODO: what if the entity resolves to whitespace? + readEntity(result, false); + start = position; + continue; + + } else if (c == '<') { + if (inAttributeValue) { + checkRelaxed("Illegal: \"<\" inside attribute value"); + } + isWhitespace = false; + + } else if (c == ']') { + if ((position + 2 < limit || fillBuffer(3)) + && buffer[position + 1] == ']' && buffer[position + 2] == '>') { + checkRelaxed("Illegal: \"]]>\" outside CDATA section"); + } + isWhitespace = false; + + } else { + throw new AssertionError(); } - // END android-changed - if (next == ']') - cbrCount++; - else - cbrCount = 0; + position++; + result.append(c); + start = position; + } - next = peek(0); + if (result == null) { + return stringPool.get(buffer, start, position - start); + } else { + result.append(buffer, start, position - start); + return result.toString(); } } - private final void read(char c) - throws IOException, XmlPullParserException { - int a = read(); - if (a != c) - error("expected: '" + c + "' actual: '" + ((char) a) + "'"); + private void read(char expected) throws IOException, XmlPullParserException { + int c = peekCharacter(); + if (c != expected) { + checkRelaxed("expected: '" + expected + "' actual: '" + ((char) c) + "'"); + } + position++; } - private final int read() throws IOException { - int result; - - if (peekCount == 0) - result = peek(0); - else { - result = peek[0]; - peek[0] = peek[1]; + private void read(char[] chars) throws IOException, XmlPullParserException { + if (position + chars.length >= limit && !fillBuffer(chars.length)) { + checkRelaxed("expected: '" + new String(chars) + "' but was EOF"); + return; } - // else { - // result = peek[0]; - // System.arraycopy (peek, 1, peek, 0, peekCount-1); - // } - peekCount--; - column++; + // TODO: replace with Arrays.equals(buffer, position, delimiter, 0, delimiter.length) + // when the VM has better method inlining + for (int i = 0; i < chars.length; i++) { + if (buffer[position + i] != chars[i]) { + checkRelaxed("expected: \"" + new String(chars) + "\" but was \"" + + new String(buffer, position, chars.length) + "...\""); + } + } - if (result == '\n') { + position += chars.length; + } - line++; - column = 1; + private int peekCharacter() throws IOException, XmlPullParserException { + if (position < limit || fillBuffer(1)) { + return buffer[position]; } - - return result; + return -1; } - /** Does never read more than needed */ + /** + * Returns true once {@code limit - position >= minimum}. If the data is + * exhausted before that many characters are available, this returns + * false. + */ + private boolean fillBuffer(int minimum) throws IOException { + // Before clobbering the old characters, update where buffer starts + for (int i = 0; i < position; i++) { + if (buffer[i] == '\n') { + bufferStartLine++; + bufferStartColumn = 0; + } else { + bufferStartColumn++; + } + } - private final int peek(int pos) throws IOException { + if (limit != position) { + limit -= position; + System.arraycopy(buffer, position, buffer, 0, limit); + } else { + limit = 0; + } - while (pos >= peekCount) { + position = 0; + int total; + while ((total = reader.read(buffer, limit, buffer.length - limit)) != -1) { + limit += total; + if (limit >= minimum) { + return true; + } + } + return false; + } - int nw; + /** + * Returns an element or attribute name. This is always non-empty for + * non-relaxed parsers. + */ + private String readName() throws IOException, XmlPullParserException { + if (position >= limit && !fillBuffer(1)) { + checkRelaxed("name expected"); + return ""; + } - if (srcBuf.length <= 1) - nw = reader.read(); - else if (srcPos < srcCount) - nw = srcBuf[srcPos++]; - else { - srcCount = reader.read(srcBuf, 0, srcBuf.length); - if (srcCount <= 0) - nw = -1; - else - nw = srcBuf[0]; + int start = position; + StringBuilder result = null; + + // read the first character + char c = buffer[position]; + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || c == '_' + || c == ':' + || c >= '\u00c0' // TODO: check the XML spec + || relaxed) { + position++; + } else { + checkRelaxed("name expected"); + return ""; + } - srcPos = 1; + while (true) { + /* + * Make sure we have at least a single character to read from the + * buffer. This mutates the buffer, so save the partial result + * to the slow path string builder first. + */ + if (position >= limit) { + if (result == null) { + result = new StringBuilder(); + } + result.append(buffer, start, position - start); + if (!fillBuffer(1)) { + return result.toString(); + } + start = position; } - if (nw == '\r') { - wasCR = true; - peek[peekCount++] = '\n'; + // read another character + c = buffer[position]; + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_' + || c == '-' + || c == ':' + || c == '.' + || c >= '\u00b7') { // TODO: check the XML spec + position++; + continue; } - else { - if (nw == '\n') { - if (!wasCR) - peek[peekCount++] = '\n'; - } - else - peek[peekCount++] = nw; - wasCR = false; + // we encountered a non-name character. done! + if (result == null) { + return stringPool.get(buffer, start, position - start); + } else { + result.append(buffer, start, position - start); + return result.toString(); } } - - return peek[pos]; } - private final String readName() - throws IOException, XmlPullParserException { - - int pos = txtPos; - int c = peek(0); - if ((c < 'a' || c > 'z') - && (c < 'A' || c > 'Z') - && c != '_' - && c != ':' - && c < 0x0c0 - && !relaxed) - error("name expected"); - - do { - push(read()); - c = peek(0); - } - while ((c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') - || c == '_' - || c == '-' - || c == ':' - || c == '.' - || c >= 0x0b7); - - String result = get(pos); - txtPos = pos; - return result; - } - - private final void skip() throws IOException { - - while (true) { - int c = peek(0); - if (c > ' ' || c == -1) + private void skip() throws IOException { + while (position < limit || fillBuffer(1)) { + int c = buffer[position]; + if (c > ' ') { break; - read(); + } + position++; } } @@ -996,8 +1089,6 @@ public class KXmlParser implements XmlPullParser { public void setInput(Reader reader) throws XmlPullParserException { this.reader = reader; - line = 1; - column = 0; type = START_DOCUMENT; name = null; namespace = null; @@ -1007,15 +1098,17 @@ public class KXmlParser implements XmlPullParser { version = null; standalone = null; - if (reader == null) + if (reader == null) { return; + } - srcPos = 0; - srcCount = 0; - peekCount = 0; + position = 0; + limit = 0; + bufferStartLine = 0; + bufferStartColumn = 0; depth = 0; - entityMap = new Hashtable(); + entityMap = new HashMap<String, String>(); entityMap.put("amp", "&"); entityMap.put("apos", "'"); entityMap.put("gt", ">"); @@ -1023,82 +1116,81 @@ public class KXmlParser implements XmlPullParser { entityMap.put("quot", "\""); } - public void setInput(InputStream is, String _enc) - throws XmlPullParserException { - - srcPos = 0; - srcCount = 0; + public void setInput(InputStream is, String _enc) throws XmlPullParserException { + position = 0; + limit = 0; String enc = _enc; - if (is == null) + if (is == null) { throw new IllegalArgumentException(); + } try { - if (enc == null) { - // read four bytes - - int chk = 0; - - while (srcCount < 4) { + // read the four bytes looking for an indication of the encoding in use + int firstFourBytes = 0; + while (limit < 4) { int i = is.read(); - if (i == -1) + if (i == -1) { break; - chk = (chk << 8) | i; - srcBuf[srcCount++] = (char) i; + } + firstFourBytes = (firstFourBytes << 8) | i; + buffer[limit++] = (char) i; } - if (srcCount == 4) { - switch (chk) { - case 0x00000FEFF : + if (limit == 4) { + switch (firstFourBytes) { + case 0x00000FEFF: // UTF-32BE BOM enc = "UTF-32BE"; - srcCount = 0; + limit = 0; break; - case 0x0FFFE0000 : + case 0x0FFFE0000: // UTF-32LE BOM enc = "UTF-32LE"; - srcCount = 0; + limit = 0; break; - case 0x03c : + case 0x0000003c: // '>' in UTF-32BE enc = "UTF-32BE"; - srcBuf[0] = '<'; - srcCount = 1; + buffer[0] = '<'; + limit = 1; break; - case 0x03c000000 : + case 0x03c000000: // '<' in UTF-32LE enc = "UTF-32LE"; - srcBuf[0] = '<'; - srcCount = 1; + buffer[0] = '<'; + limit = 1; break; - case 0x0003c003f : + case 0x0003c003f: // "<?" in UTF-16BE enc = "UTF-16BE"; - srcBuf[0] = '<'; - srcBuf[1] = '?'; - srcCount = 2; + buffer[0] = '<'; + buffer[1] = '?'; + limit = 2; break; - case 0x03c003f00 : + case 0x03c003f00: // "<?" in UTF-16LE enc = "UTF-16LE"; - srcBuf[0] = '<'; - srcBuf[1] = '?'; - srcCount = 2; + buffer[0] = '<'; + buffer[1] = '?'; + limit = 2; break; - case 0x03c3f786d : + case 0x03c3f786d: // "<?xm" in ASCII etc. while (true) { int i = is.read(); - if (i == -1) + if (i == -1) { break; - srcBuf[srcCount++] = (char) i; + } + buffer[limit++] = (char) i; if (i == '>') { - String s = new String(srcBuf, 0, srcCount); + String s = new String(buffer, 0, limit); int i0 = s.indexOf("encoding"); if (i0 != -1) { while (s.charAt(i0) != '"' - && s.charAt(i0) != '\'') + && s.charAt(i0) != '\'') { i0++; + } char deli = s.charAt(i0++); int i1 = s.indexOf(deli, i0); enc = s.substring(i0, i1); @@ -1107,51 +1199,46 @@ public class KXmlParser implements XmlPullParser { } } - default : - if ((chk & 0x0ffff0000) == 0x0FEFF0000) { + default: + // handle a byte order mark followed by something other than <? + if ((firstFourBytes & 0x0ffff0000) == 0x0FEFF0000) { enc = "UTF-16BE"; - srcBuf[0] = - (char) ((srcBuf[2] << 8) | srcBuf[3]); - srcCount = 1; - } - else if ((chk & 0x0ffff0000) == 0x0fffe0000) { + buffer[0] = (char) ((buffer[2] << 8) | buffer[3]); + limit = 1; + } else if ((firstFourBytes & 0x0ffff0000) == 0x0fffe0000) { enc = "UTF-16LE"; - srcBuf[0] = - (char) ((srcBuf[3] << 8) | srcBuf[2]); - srcCount = 1; - } - else if ((chk & 0x0ffffff00) == 0x0EFBBBF00) { + buffer[0] = (char) ((buffer[3] << 8) | buffer[2]); + limit = 1; + } else if ((firstFourBytes & 0x0ffffff00) == 0x0EFBBBF00) { enc = "UTF-8"; - srcBuf[0] = srcBuf[3]; - srcCount = 1; + buffer[0] = buffer[3]; + limit = 1; } } } } - if (enc == null) + if (enc == null) { enc = "UTF-8"; + } - int sc = srcCount; + int sc = limit; setInput(new InputStreamReader(is, enc)); encoding = _enc; - srcCount = sc; - } - catch (Exception e) { - throw new XmlPullParserException( - "Invalid stream or encoding: " + e.toString(), - this, - e); + limit = sc; + } catch (Exception e) { + throw new XmlPullParserException("Invalid stream or encoding: " + e, this, e); } } public boolean getFeature(String feature) { - if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) + if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) { return processNsp; - else if (isProp(feature, false, "relaxed")) + } else if (isProp(feature, false, "relaxed")) { return relaxed; - else + } else { return false; + } } public String getInputEncoding() { @@ -1159,50 +1246,58 @@ public class KXmlParser implements XmlPullParser { } public void defineEntityReplacementText(String entity, String value) - throws XmlPullParserException { - if (entityMap == null) + throws XmlPullParserException { + if (entityMap == null) { throw new RuntimeException("entity replacement text must be defined after setInput!"); + } entityMap.put(entity, value); } public Object getProperty(String property) { - if (isProp(property, true, "xmldecl-version")) + if (isProp(property, true, "xmldecl-version")) { return version; - if (isProp(property, true, "xmldecl-standalone")) + } + if (isProp(property, true, "xmldecl-standalone")) { return standalone; - if (isProp(property, true, "location")) + } + if (isProp(property, true, "location")) { return location != null ? location : reader.toString(); + } return null; } public int getNamespaceCount(int depth) { - if (depth > this.depth) + if (depth > this.depth) { throw new IndexOutOfBoundsException(); + } return nspCounts[depth]; } public String getNamespacePrefix(int pos) { - return nspStack[pos << 1]; + return nspStack[pos * 2]; } public String getNamespaceUri(int pos) { - return nspStack[(pos << 1) + 1]; + return nspStack[(pos * 2) + 1]; } public String getNamespace(String prefix) { - if ("xml".equals(prefix)) + if ("xml".equals(prefix)) { return "http://www.w3.org/XML/1998/namespace"; - if ("xmlns".equals(prefix)) + } + if ("xmlns".equals(prefix)) { return "http://www.w3.org/2000/xmlns/"; + } for (int i = (getNamespaceCount(depth) << 1) - 2; i >= 0; i -= 2) { if (prefix == null) { - if (nspStack[i] == null) + if (nspStack[i] == null) { return nspStack[i + 1]; - } - else if (prefix.equals(nspStack[i])) + } + } else if (prefix.equals(nspStack[i])) { return nspStack[i + 1]; + } } return null; } @@ -1212,50 +1307,52 @@ public class KXmlParser implements XmlPullParser { } public String getPositionDescription() { - - StringBuffer buf = - new StringBuffer(type < TYPES.length ? TYPES[type] : "unknown"); + StringBuilder buf = new StringBuilder(type < TYPES.length ? TYPES[type] : "unknown"); buf.append(' '); if (type == START_TAG || type == END_TAG) { - if (degenerated) + if (degenerated) { buf.append("(empty) "); + } buf.append('<'); - if (type == END_TAG) + if (type == END_TAG) { buf.append('/'); + } - if (prefix != null) + if (prefix != null) { buf.append("{" + namespace + "}" + prefix + ":"); + } buf.append(name); - int cnt = attributeCount << 2; + int cnt = attributeCount * 4; for (int i = 0; i < cnt; i += 4) { buf.append(' '); - if (attributes[i + 1] != null) - buf.append( - "{" + attributes[i] + "}" + attributes[i + 1] + ":"); + if (attributes[i + 1] != null) { + buf.append("{" + attributes[i] + "}" + attributes[i + 1] + ":"); + } buf.append(attributes[i + 2] + "='" + attributes[i + 3] + "'"); } buf.append('>'); - } - else if (type == IGNORABLE_WHITESPACE); - else if (type != TEXT) + } else if (type == IGNORABLE_WHITESPACE) { + ; + } else if (type != TEXT) { buf.append(getText()); - else if (isWhitespace) + } else if (isWhitespace) { buf.append("(whitespace)"); - else { + } else { String text = getText(); - if (text.length() > 16) + if (text.length() > 16) { text = text.substring(0, 16) + "..."; + } buf.append(text); } - buf.append("@"+line + ":" + column); - if(location != null){ + buf.append("@" + getLineNumber() + ":" + getColumnNumber()); + if (location != null) { buf.append(" in "); buf.append(location); - } else if(reader != null){ + } else if (reader != null) { buf.append(" in "); buf.append(reader.toString()); } @@ -1263,39 +1360,55 @@ public class KXmlParser implements XmlPullParser { } public int getLineNumber() { - return line; + int result = bufferStartLine; + for (int i = 0; i < position; i++) { + if (buffer[i] == '\n') { + result++; + } + } + return result + 1; // the first line is '1' } public int getColumnNumber() { - return column; + int result = bufferStartColumn; + for (int i = 0; i < position; i++) { + if (buffer[i] == '\n') { + result = 0; + } else { + result++; + } + } + return result + 1; // the first column is '1' } public boolean isWhitespace() throws XmlPullParserException { - if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT) - exception(ILLEGAL_TYPE); + if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT) { + throw new XmlPullParserException(ILLEGAL_TYPE, this, null); + } return isWhitespace; } public String getText() { - return type < TEXT - || (type == ENTITY_REF && unresolved) ? null : get(0); + if (type < TEXT || (type == ENTITY_REF && unresolved)) { + return null; + } else if (text == null) { + return ""; + } else { + return text; + } } public char[] getTextCharacters(int[] poslen) { - if (type >= TEXT) { - if (type == ENTITY_REF) { - poslen[0] = 0; - poslen[1] = name.length(); - return name.toCharArray(); - } - poslen[0] = 0; - poslen[1] = txtPos; - return txtBuf; + String text = getText(); + if (text == null) { + poslen[0] = -1; + poslen[1] = -1; + return null; } - - poslen[0] = -1; - poslen[1] = -1; - return null; + char[] result = text.toCharArray(); + poslen[0] = 0; + poslen[1] = result.length; + return result; } public String getNamespace() { @@ -1311,8 +1424,9 @@ public class KXmlParser implements XmlPullParser { } public boolean isEmptyElementTag() throws XmlPullParserException { - if (type != START_TAG) - exception(ILLEGAL_TYPE); + if (type != START_TAG) { + throw new XmlPullParserException(ILLEGAL_TYPE, this, null); + } return degenerated; } @@ -1329,35 +1443,39 @@ public class KXmlParser implements XmlPullParser { } public String getAttributeNamespace(int index) { - if (index >= attributeCount) + if (index >= attributeCount) { throw new IndexOutOfBoundsException(); - return attributes[index << 2]; + } + return attributes[index * 4]; } public String getAttributeName(int index) { - if (index >= attributeCount) + if (index >= attributeCount) { throw new IndexOutOfBoundsException(); - return attributes[(index << 2) + 2]; + } + return attributes[(index * 4) + 2]; } public String getAttributePrefix(int index) { - if (index >= attributeCount) + if (index >= attributeCount) { throw new IndexOutOfBoundsException(); - return attributes[(index << 2) + 1]; + } + return attributes[(index * 4) + 1]; } public String getAttributeValue(int index) { - if (index >= attributeCount) + if (index >= attributeCount) { throw new IndexOutOfBoundsException(); - return attributes[(index << 2) + 3]; + } + return attributes[(index * 4) + 3]; } public String getAttributeValue(String namespace, String name) { - - for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) { + for (int i = (attributeCount * 4) - 4; i >= 0; i -= 4) { if (attributes[i + 2].equals(name) - && (namespace == null || attributes[i].equals(namespace))) + && (namespace == null || attributes[i].equals(namespace))) { return attributes[i + 3]; + } } return null; @@ -1367,123 +1485,70 @@ public class KXmlParser implements XmlPullParser { return type; } - public int next() throws XmlPullParserException, IOException { - - txtPos = 0; - isWhitespace = true; - int minType = 9999; - token = false; - - do { - nextImpl(); - if (type < minType) - minType = type; - // if (curr <= TEXT) type = curr; - } - while (minType > ENTITY_REF // ignorable - || (minType >= TEXT && peekType() >= TEXT)); - - type = minType; - if (type > TEXT) - type = TEXT; - - return type; - } - - public int nextToken() throws XmlPullParserException, IOException { - - isWhitespace = true; - txtPos = 0; - - token = true; - nextImpl(); - return type; - } - - // // utility methods to make XML parsing easier ... public int nextTag() throws XmlPullParserException, IOException { - next(); - if (type == TEXT && isWhitespace) + if (type == TEXT && isWhitespace) { next(); + } - if (type != END_TAG && type != START_TAG) - exception("unexpected type"); + if (type != END_TAG && type != START_TAG) { + throw new XmlPullParserException("unexpected type", this, null); + } return type; } public void require(int type, String namespace, String name) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { if (type != this.type - || (namespace != null && !namespace.equals(getNamespace())) - || (name != null && !name.equals(getName()))) - exception( - "expected: " + TYPES[type] + " {" + namespace + "}" + name); + || (namespace != null && !namespace.equals(getNamespace())) + || (name != null && !name.equals(getName()))) { + throw new XmlPullParserException( + "expected: " + TYPES[type] + " {" + namespace + "}" + name, this, null); + } } public String nextText() throws XmlPullParserException, IOException { - if (type != START_TAG) - exception("precondition: START_TAG"); + if (type != START_TAG) { + throw new XmlPullParserException("precondition: START_TAG", this, null); + } next(); String result; - if (type == TEXT) { result = getText(); next(); - } - else + } else { result = ""; + } - if (type != END_TAG) - exception("END_TAG expected"); + if (type != END_TAG) { + throw new XmlPullParserException("END_TAG expected", this, null); + } return result; } - public void setFeature(String feature, boolean value) - throws XmlPullParserException { - if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) + public void setFeature(String feature, boolean value) throws XmlPullParserException { + if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature)) { processNsp = value; - else if (isProp(feature, false, "relaxed")) + } else if (isProp(feature, false, "relaxed")) { + // "http://xmlpull.org/v1/doc/features.html#relaxed" relaxed = value; - else - exception("unsupported feature: " + feature); - } - - public void setProperty(String property, Object value) - throws XmlPullParserException { - if(isProp(property, true, "location")) { - location = value; } else { - throw new XmlPullParserException("unsupported property: " + property); + throw new XmlPullParserException("unsupported feature: " + feature, this, null); } } - /** - * Skip sub tree that is currently porser positioned on. - * <br>NOTE: parser must be on START_TAG and when funtion returns - * parser will be positioned on corresponding END_TAG. - */ - - // Implementation copied from Alek's mail... - - public void skipSubTree() throws XmlPullParserException, IOException { - require(START_TAG, null, null); - int level = 1; - while (level > 0) { - int eventType = next(); - if (eventType == END_TAG) { - --level; - } - else if (eventType == START_TAG) { - ++level; - } + public void setProperty(String property, Object value) throws XmlPullParserException { + if (isProp(property, true, "location")) { + location = String.valueOf(value); + } else { + throw new XmlPullParserException("unsupported property: " + property); } } } diff --git a/xml/src/main/java/org/xmlpull/v1/XmlPullParser.java b/xml/src/main/java/org/xmlpull/v1/XmlPullParser.java index 66c8f4d..36e6025 100644 --- a/xml/src/main/java/org/xmlpull/v1/XmlPullParser.java +++ b/xml/src/main/java/org/xmlpull/v1/XmlPullParser.java @@ -59,7 +59,7 @@ import java.io.Reader; * getProperty("<a href="http://xmlpull.org/v1/doc/properties.html#xmldecl-version">http://xmlpull.org/v1/doc/properties.html#xmldecl-version</a>") * returns String ("1.0") or null if XMLDecl was not read or if property is not supported * <li><b>standalone</b>: - * getProperty("<a href="http://xmlpull.org/v1/doc/features.html#xmldecl-standalone">http://xmlpull.org/v1/doc/features.html#xmldecl-standalone</a>") + * getProperty("<a href="http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone">http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone</a>") * returns Boolean: null if there was no standalone declaration * or if property is not supported * otherwise returns Boolean(true) if standalone="yes" and Boolean(false) when standalone="no" @@ -74,7 +74,7 @@ import java.io.Reader; * import java.io.StringReader; * * import org.xmlpull.v1.XmlPullParser; - * import org.xmlpull.v1.<a href="XmlPullParserException.html">XmlPullParserException.html</a>; + * import org.xmlpull.v1.<a href="XmlPullParserException.html">XmlPullParserException</a>; * import org.xmlpull.v1.<a href="XmlPullParserFactory.html">XmlPullParserFactory</a>; * * public class SimpleXmlPullApp @@ -92,8 +92,6 @@ import java.io.Reader; * while (eventType != XmlPullParser.END_DOCUMENT) { * if(eventType == XmlPullParser.START_DOCUMENT) { * System.out.println("Start document"); - * } else if(eventType == XmlPullParser.END_DOCUMENT) { - * System.out.println("End document"); * } else if(eventType == XmlPullParser.START_TAG) { * System.out.println("Start tag "+xpp.<a href="#getName()">getName()</a>); * } else if(eventType == XmlPullParser.END_TAG) { @@ -103,6 +101,7 @@ import java.io.Reader; * } * eventType = xpp.next(); * } + * System.out.println("End document"); * } * } * </pre> |