Merge "Keep the order from getaddrinfo() unchanged if preferIPv6Addresses() is true."
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index b12b47d..64fdb7e 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -75,7 +75,7 @@
LOCAL_JAVA_RESOURCE_DIRS := $(test_resource_dirs)
LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core caliper
+LOCAL_JAVA_LIBRARIES := core
LOCAL_DX_FLAGS := --core-library
LOCAL_MODULE_TAGS := tests
@@ -181,4 +181,4 @@
include $(BUILD_HOST_JAVA_LIBRARY)
-endif
\ No newline at end of file
+endif
diff --git a/dalvik/src/main/java/dalvik/system/VMDebug.java b/dalvik/src/main/java/dalvik/system/VMDebug.java
index ce3e95c..6f64c5f 100644
--- a/dalvik/src/main/java/dalvik/system/VMDebug.java
+++ b/dalvik/src/main/java/dalvik/system/VMDebug.java
@@ -34,6 +34,8 @@
public final class VMDebug {
/**
* Specifies the default method trace data file name.
+ *
+ * @deprecated only used in one place, which is unused and deprecated
*/
static public final String DEFAULT_METHOD_TRACE_FILE_NAME = "/sdcard/dmtrace.trace";
@@ -44,11 +46,13 @@
public static final int TRACE_COUNT_ALLOCS = 1;
/* constants for getAllocCount */
- private static final int KIND_ALLOCATED_OBJECTS = 1<<0;
- private static final int KIND_ALLOCATED_BYTES = 1<<1;
- private static final int KIND_FREED_OBJECTS = 1<<2;
- private static final int KIND_FREED_BYTES = 1<<3;
- private static final int KIND_GC_INVOCATIONS = 1<<4;
+ private static final int KIND_ALLOCATED_OBJECTS = 1<<0;
+ private static final int KIND_ALLOCATED_BYTES = 1<<1;
+ private static final int KIND_FREED_OBJECTS = 1<<2;
+ private static final int KIND_FREED_BYTES = 1<<3;
+ private static final int KIND_GC_INVOCATIONS = 1<<4;
+ private static final int KIND_CLASS_INIT_COUNT = 1<<5;
+ private static final int KIND_CLASS_INIT_TIME = 1<<6;
private static final int KIND_EXT_ALLOCATED_OBJECTS = 1<<12;
private static final int KIND_EXT_ALLOCATED_BYTES = 1<<13;
private static final int KIND_EXT_FREED_OBJECTS = 1<<14;
@@ -64,6 +68,10 @@
KIND_FREED_BYTES;
public static final int KIND_GLOBAL_GC_INVOCATIONS =
KIND_GC_INVOCATIONS;
+ public static final int KIND_GLOBAL_CLASS_INIT_COUNT =
+ KIND_CLASS_INIT_COUNT;
+ public static final int KIND_GLOBAL_CLASS_INIT_TIME =
+ KIND_CLASS_INIT_TIME;
public static final int KIND_GLOBAL_EXT_ALLOCATED_OBJECTS =
KIND_EXT_ALLOCATED_OBJECTS;
public static final int KIND_GLOBAL_EXT_ALLOCATED_BYTES =
@@ -83,6 +91,10 @@
KIND_FREED_BYTES << 16;
public static final int KIND_THREAD_GC_INVOCATIONS =
KIND_GC_INVOCATIONS << 16;
+ public static final int KIND_THREAD_CLASS_INIT_COUNT =
+ KIND_CLASS_INIT_COUNT << 16;
+ public static final int KIND_THREAD_CLASS_INIT_TIME =
+ KIND_CLASS_INIT_TIME << 16;
public static final int KIND_THREAD_EXT_ALLOCATED_OBJECTS =
KIND_EXT_ALLOCATED_OBJECTS << 16;
public static final int KIND_THREAD_EXT_ALLOCATED_BYTES =
@@ -131,6 +143,8 @@
/**
* Start method tracing with default name, size, and with <code>0</code>
* flags.
+ *
+ * @deprecated not used, not needed
*/
public static void startMethodTracing() {
startMethodTracing(DEFAULT_METHOD_TRACE_FILE_NAME, 0, 0);
diff --git a/include/UniquePtr.h b/include/UniquePtr.h
new file mode 100644
index 0000000..f5c7c2c
--- /dev/null
+++ b/include/UniquePtr.h
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#ifndef UNIQUE_PTR_H_included
+#define UNIQUE_PTR_H_included
+
+#include <cstdlib> // For NULL.
+
+// Default deleter for pointer types.
+template <typename T>
+struct DefaultDelete {
+ enum { type_must_be_complete = sizeof(T) };
+ DefaultDelete() {}
+ void operator()(T* p) const {
+ delete p;
+ }
+};
+
+// Default deleter for array types.
+template <typename T>
+struct DefaultDelete<T[]> {
+ enum { type_must_be_complete = sizeof(T) };
+ void operator()(T* p) const {
+ delete[] p;
+ }
+};
+
+// A smart pointer that deletes the given pointer on destruction.
+// Equivalent to C++0x's std::unique_ptr (a combination of boost::scoped_ptr
+// and boost::scoped_array).
+// Named to be in keeping with Android style but also to avoid
+// collision with any other implementation, until we can switch over
+// to unique_ptr.
+// Use thus:
+// UniquePtr<C> c(new C);
+template <typename T, typename D = DefaultDelete<T> >
+class UniquePtr {
+public:
+ // Construct a new UniquePtr, taking ownership of the given raw pointer.
+ explicit UniquePtr(T* ptr = NULL) : mPtr(ptr) {
+ }
+
+ ~UniquePtr() {
+ reset();
+ }
+
+ // Accessors.
+ T& operator*() const { return *mPtr; }
+ T* operator->() const { return mPtr; }
+ T* get() const { return mPtr; }
+
+ // Returns the raw pointer and hands over ownership to the caller.
+ // The pointer will not be deleted by UniquePtr.
+ T* release() {
+ T* result = mPtr;
+ mPtr = NULL;
+ return result;
+ }
+
+ // Takes ownership of the given raw pointer.
+ // If this smart pointer previously owned a different raw pointer, that
+ // raw pointer will be freed.
+ void reset(T* ptr = NULL) {
+ if (ptr != mPtr) {
+ D()(mPtr);
+ mPtr = ptr;
+ }
+ }
+
+private:
+ // The raw pointer.
+ T* mPtr;
+
+ // Comparing unique pointers is probably a mistake, since they're unique.
+ template <typename T2> bool operator==(const UniquePtr<T2>& p) const;
+ template <typename T2> bool operator!=(const UniquePtr<T2>& p) const;
+
+ // Disallow copy and assignment.
+ UniquePtr(const UniquePtr&);
+ void operator=(const UniquePtr&);
+};
+
+// Partial specialization for array types. Like std::unique_ptr, this removes
+// operator* and operator-> but adds operator[].
+template <typename T, typename D>
+class UniquePtr<T[], D> {
+public:
+ explicit UniquePtr(T* ptr = NULL) : mPtr(ptr) {
+ }
+
+ ~UniquePtr() {
+ reset();
+ }
+
+ T& operator[](size_t i) const {
+ return mPtr[i];
+ }
+ T* get() const { return mPtr; }
+
+ T* release() {
+ T* result = mPtr;
+ mPtr = NULL;
+ return result;
+ }
+
+ void reset(T* ptr = NULL) {
+ if (ptr != mPtr) {
+ D()(mPtr);
+ mPtr = ptr;
+ }
+ }
+
+private:
+ T* mPtr;
+
+ // Disallow copy and assignment.
+ UniquePtr(const UniquePtr&);
+ void operator=(const UniquePtr&);
+};
+
+#if UNIQUE_PTR_TESTS
+
+// Run these tests with:
+// g++ -g -DUNIQUE_PTR_TESTS -x c++ UniquePtr.h && ./a.out
+
+#include <stdio.h>
+
+static void assert(bool b) {
+ if (!b) {
+ fprintf(stderr, "FAIL\n");
+ abort();
+ }
+ fprintf(stderr, "OK\n");
+}
+static int cCount = 0;
+struct C {
+ C() { ++cCount; }
+ ~C() { --cCount; }
+};
+static bool freed = false;
+struct Freer {
+ void operator()(int* p) {
+ assert(*p == 123);
+ free(p);
+ freed = true;
+ }
+};
+
+int main(int argc, char* argv[]) {
+ //
+ // UniquePtr<T> tests...
+ //
+
+ // Can we free a single object?
+ {
+ UniquePtr<C> c(new C);
+ assert(cCount == 1);
+ }
+ assert(cCount == 0);
+ // Does release work?
+ C* rawC;
+ {
+ UniquePtr<C> c(new C);
+ assert(cCount == 1);
+ rawC = c.release();
+ }
+ assert(cCount == 1);
+ delete rawC;
+ // Does reset work?
+ {
+ UniquePtr<C> c(new C);
+ assert(cCount == 1);
+ c.reset(new C);
+ assert(cCount == 1);
+ }
+ assert(cCount == 0);
+
+ //
+ // UniquePtr<T[]> tests...
+ //
+
+ // Can we free an array?
+ {
+ UniquePtr<C[]> cs(new C[4]);
+ assert(cCount == 4);
+ }
+ assert(cCount == 0);
+ // Does release work?
+ {
+ UniquePtr<C[]> c(new C[4]);
+ assert(cCount == 4);
+ rawC = c.release();
+ }
+ assert(cCount == 4);
+ delete[] rawC;
+ // Does reset work?
+ {
+ UniquePtr<C[]> c(new C[4]);
+ assert(cCount == 4);
+ c.reset(new C[2]);
+ assert(cCount == 2);
+ }
+ assert(cCount == 0);
+
+ //
+ // Custom deleter tests...
+ //
+ assert(!freed);
+ {
+ UniquePtr<int, Freer> i(reinterpret_cast<int*>(malloc(sizeof(int))));
+ *i = 123;
+ }
+ assert(freed);
+ return 0;
+}
+#endif
+
+#endif // UNIQUE_PTR_H_included
diff --git a/json/src/test/java/org/json/AllTests.java b/json/src/test/java/org/json/AllTests.java
new file mode 100644
index 0000000..8261a4d
--- /dev/null
+++ b/json/src/test/java/org/json/AllTests.java
@@ -0,0 +1,30 @@
+/*
+ * 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.json;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class AllTests {
+ public static Test suite() {
+ TestSuite suite = tests.TestSuiteFactory.createTestSuite();
+ suite.addTestSuite(JSONArrayTest.class);
+ suite.addTestSuite(JSONStringerTest.class);
+ suite.addTestSuite(JSONStringerTest.class);
+ return suite;
+ }
+}
diff --git a/json/src/test/java/org/json/JSONArrayTest.java b/json/src/test/java/org/json/JSONArrayTest.java
new file mode 100644
index 0000000..34e5ff6
--- /dev/null
+++ b/json/src/test/java/org/json/JSONArrayTest.java
@@ -0,0 +1,339 @@
+/**
+ * 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.json;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * This black box test was written without inspecting the non-free org.json sourcecode.
+ */
+public class JSONArrayTest extends TestCase {
+
+ public void testEmptyArray() throws JSONException {
+ JSONArray array = new JSONArray();
+ assertEquals(0, array.length());
+ assertEquals("", array.join(" AND "));
+ try {
+ array.get(0);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ array.getBoolean(0);
+ fail();
+ } catch (JSONException e) {
+ }
+
+ assertEquals("[]", array.toString());
+ assertEquals("[]", array.toString(4));
+
+ // out of bounds is co-opted with defaulting
+ assertTrue(array.isNull(0));
+ assertNull(array.opt(0));
+ assertFalse(array.optBoolean(0));
+ assertTrue(array.optBoolean(0, true));
+
+ // bogus (but documented) behaviour: returns null rather than an empty object
+ assertNull(array.toJSONObject(new JSONArray()));
+ }
+
+ public void testEqualsAndHashCode() throws JSONException {
+ JSONArray a = new JSONArray();
+ JSONArray b = new JSONArray();
+ assertTrue(a.equals(b));
+ // bogus behavior: JSONArray overrides equals() but not hashCode().
+ assertEquals(a.hashCode(), b.hashCode());
+
+ a.put(true);
+ a.put(false);
+ b.put(true);
+ b.put(false);
+ assertTrue(a.equals(b));
+ assertEquals(a.hashCode(), b.hashCode());
+
+ b.put(true);
+ assertFalse(a.equals(b));
+ assertTrue(a.hashCode() != b.hashCode());
+ }
+
+ public void testBooleans() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put(true);
+ array.put(false);
+ array.put(2, false);
+ array.put(3, false);
+ array.put(2, true);
+ assertEquals("[true,false,true,false]", array.toString());
+ assertEquals(4, array.length());
+ assertEquals(Boolean.TRUE, array.get(0));
+ assertEquals(Boolean.FALSE, array.get(1));
+ assertEquals(Boolean.TRUE, array.get(2));
+ assertEquals(Boolean.FALSE, array.get(3));
+ assertFalse(array.isNull(0));
+ assertFalse(array.isNull(1));
+ assertFalse(array.isNull(2));
+ assertFalse(array.isNull(3));
+ assertEquals(true, array.optBoolean(0));
+ assertEquals(false, array.optBoolean(1, true));
+ assertEquals(true, array.optBoolean(2, false));
+ assertEquals(false, array.optBoolean(3));
+ assertEquals("true", array.getString(0));
+ assertEquals("false", array.getString(1));
+ assertEquals("true", array.optString(2));
+ assertEquals("false", array.optString(3, "x"));
+ assertEquals("[\n true,\n false,\n true,\n false\n]", array.toString(5));
+
+ JSONArray other = new JSONArray();
+ other.put(true);
+ other.put(false);
+ other.put(true);
+ other.put(false);
+ assertTrue(array.equals(other));
+ other.put(true);
+ assertFalse(array.equals(other));
+
+ other = new JSONArray();
+ other.put("true");
+ other.put("false");
+ other.put("truE");
+ other.put("FALSE");
+ assertFalse(array.equals(other));
+ assertFalse(other.equals(array));
+ assertEquals(true, other.getBoolean(0));
+ assertEquals(false, other.optBoolean(1, true));
+ assertEquals(true, other.optBoolean(2));
+ assertEquals(false, other.getBoolean(3));
+ }
+
+ public void testNulls() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put(3, null);
+ array.put(0, JSONObject.NULL);
+ assertEquals(4, array.length());
+ assertEquals("[null,null,null,null]", array.toString());
+
+ // bogus behaviour: there's 2 ways to represent null; each behaves differently!
+ assertEquals(JSONObject.NULL, array.get(0));
+ try {
+ assertEquals(null, array.get(1));
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ assertEquals(null, array.get(2));
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ assertEquals(null, array.get(3));
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals(JSONObject.NULL, array.opt(0));
+ assertEquals(null, array.opt(1));
+ assertEquals(null, array.opt(2));
+ assertEquals(null, array.opt(3));
+ assertTrue(array.isNull(0));
+ assertTrue(array.isNull(1));
+ assertTrue(array.isNull(2));
+ assertTrue(array.isNull(3));
+ assertEquals("null", array.optString(0));
+ assertEquals("", array.optString(1));
+ assertEquals("", array.optString(2));
+ assertEquals("", array.optString(3));
+ }
+
+ public void testNumbers() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put(Double.MIN_VALUE);
+ array.put(9223372036854775806L);
+ array.put(Double.MAX_VALUE);
+ array.put(-0d);
+ assertEquals(4, array.length());
+
+ // bogus behaviour: toString() and getString(int) return different values for -0d
+ assertEquals("[4.9E-324,9223372036854775806,1.7976931348623157E308,-0]", array.toString());
+
+ assertEquals(Double.MIN_VALUE, array.get(0));
+ assertEquals(9223372036854775806L, array.get(1));
+ assertEquals(Double.MAX_VALUE, array.get(2));
+ assertEquals(-0d, array.get(3));
+ assertEquals(Double.MIN_VALUE, array.getDouble(0));
+ assertEquals(9.223372036854776E18, array.getDouble(1));
+ assertEquals(Double.MAX_VALUE, array.getDouble(2));
+ assertEquals(-0d, array.getDouble(3));
+ assertEquals(0, array.getLong(0));
+ assertEquals(9223372036854775806L, array.getLong(1));
+ assertEquals(Long.MAX_VALUE, array.getLong(2));
+ assertEquals(0, array.getLong(3));
+ assertEquals(0, array.getInt(0));
+ assertEquals(-2, array.getInt(1));
+ assertEquals(Integer.MAX_VALUE, array.getInt(2));
+ assertEquals(0, array.getInt(3));
+ assertEquals(Double.MIN_VALUE, array.opt(0));
+ assertEquals(Double.MIN_VALUE, array.optDouble(0));
+ assertEquals(0, array.optLong(0, 1L));
+ assertEquals(0, array.optInt(0, 1));
+ assertEquals("4.9E-324", array.getString(0));
+ assertEquals("9223372036854775806", array.getString(1));
+ assertEquals("1.7976931348623157E308", array.getString(2));
+ assertEquals("-0.0", array.getString(3));
+
+ JSONArray other = new JSONArray();
+ other.put(Double.MIN_VALUE);
+ other.put(9223372036854775806L);
+ other.put(Double.MAX_VALUE);
+ other.put(-0d);
+ assertTrue(array.equals(other));
+ other.put(0, 0L);
+ assertFalse(array.equals(other));
+ }
+
+ public void testStrings() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put("true");
+ array.put("5.5");
+ array.put("9223372036854775806");
+ array.put("null");
+ array.put("5\"8' tall");
+ assertEquals(5, array.length());
+ assertEquals("[\"true\",\"5.5\",\"9223372036854775806\",\"null\",\"5\\\"8' tall\"]",
+ array.toString());
+
+ // although the documentation doesn't mention it, join() escapes text and wraps
+ // strings in quotes
+ assertEquals("\"true\" \"5.5\" \"9223372036854775806\" \"null\" \"5\\\"8' tall\"",
+ array.join(" "));
+
+ assertEquals("true", array.get(0));
+ assertEquals("null", array.getString(3));
+ assertEquals("5\"8' tall", array.getString(4));
+ assertEquals("true", array.opt(0));
+ assertEquals("5.5", array.optString(1));
+ assertEquals("9223372036854775806", array.optString(2, null));
+ assertEquals("null", array.optString(3, "-1"));
+ assertFalse(array.isNull(0));
+ assertFalse(array.isNull(3));
+
+ assertEquals(true, array.getBoolean(0));
+ assertEquals(true, array.optBoolean(0));
+ assertEquals(true, array.optBoolean(0, false));
+ assertEquals(0, array.optInt(0));
+ assertEquals(-2, array.optInt(0, -2));
+
+ assertEquals(5.5d, array.getDouble(1));
+ assertEquals(5, array.getLong(1));
+ assertEquals(5, array.getInt(1));
+ assertEquals(5, array.optInt(1, 3));
+
+ // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a
+ // double and then converting that to a long. This is consistent with JavaScript.
+ assertEquals(9223372036854775807L, array.getLong(2));
+ assertEquals(9.223372036854776E18, array.getDouble(2));
+ assertEquals(Integer.MAX_VALUE, array.getInt(2));
+
+ assertFalse(array.isNull(3));
+ try {
+ array.getDouble(3);
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals(Double.NaN, array.optDouble(3));
+ assertEquals(-1.0d, array.optDouble(3, -1.0d));
+ }
+
+ public void testToJSONObject() throws JSONException {
+ JSONArray keys = new JSONArray();
+ keys.put("a");
+ keys.put("b");
+
+ JSONArray values = new JSONArray();
+ values.put(5.5d);
+ values.put(false);
+
+ JSONObject object = values.toJSONObject(keys);
+ assertEquals(5.5d, object.get("a"));
+ assertEquals(false, object.get("b"));
+
+ keys.put(0, "a");
+ values.put(0, 11.0d);
+ assertEquals(5.5d, object.get("a"));
+ }
+
+ public void testToJSONObjectWithNulls() throws JSONException {
+ JSONArray keys = new JSONArray();
+ keys.put("a");
+ keys.put("b");
+
+ JSONArray values = new JSONArray();
+ values.put(5.5d);
+ values.put(null);
+
+ // bogus behaviour: null values are stripped
+ JSONObject object = values.toJSONObject(keys);
+ assertEquals(1, object.length());
+ assertFalse(object.has("b"));
+ assertEquals("{\"a\":5.5}", object.toString());
+ }
+
+ public void testPutUnsupportedNumbers() throws JSONException {
+ JSONArray array = new JSONArray();
+
+ try {
+ array.put(Double.NaN);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ array.put(0, Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ array.put(0, Double.POSITIVE_INFINITY);
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testCreateWithUnsupportedNumbers() throws JSONException {
+ JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
+ assertEquals(2, array.length());
+ assertEquals(5.5, array.getDouble(0));
+ assertEquals(Double.NaN, array.getDouble(1));
+ }
+
+ public void testToStringWithUnsupportedNumbers() throws JSONException {
+ // bogus behaviour: when the array contains an unsupported number, toString returns null
+ JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
+ assertNull(array.toString());
+ }
+
+ public void testCreate() throws JSONException {
+ JSONArray array = new JSONArray(Arrays.asList(5.5, true));
+ assertEquals(2, array.length());
+ assertEquals(5.5, array.getDouble(0));
+ assertEquals(true, array.get(1));
+ assertEquals("[5.5,true]", array.toString());
+ }
+
+ public void testParsingConstructor() {
+ fail("TODO");
+ }
+}
diff --git a/json/src/test/java/org/json/JSONStringerTest.java b/json/src/test/java/org/json/JSONStringerTest.java
new file mode 100644
index 0000000..a30df9e
--- /dev/null
+++ b/json/src/test/java/org/json/JSONStringerTest.java
@@ -0,0 +1,356 @@
+/**
+ * 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.json;
+
+import junit.framework.TestCase;
+
+/**
+ * This black box test was written without inspecting the non-free org.json sourcecode.
+ */
+public class JSONStringerTest extends TestCase {
+
+ public void testEmptyStringer() {
+ // bogus behaviour: why isn't this the empty string?
+ assertNull(new JSONStringer().toString());
+ }
+
+ public void testValueJSONNull() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.value(JSONObject.NULL);
+ stringer.endArray();
+ assertEquals("[null]", stringer.toString());
+ }
+
+ public void testEmptyObject() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.object();
+ stringer.endObject();
+ assertEquals("{}", stringer.toString());
+ }
+
+ public void testEmptyArray() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.endArray();
+ assertEquals("[]", stringer.toString());
+ }
+
+ public void testArray() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.value(false);
+ stringer.value(5.0);
+ stringer.value(5L);
+ stringer.value("five");
+ stringer.value(null);
+ stringer.endArray();
+ assertEquals("[false,5,5,\"five\",null]", stringer.toString());
+ }
+
+ public void testKeyValue() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.object();
+ stringer.key("a").value(false);
+ stringer.key("b").value(5.0);
+ stringer.key("c").value(5L);
+ stringer.key("d").value("five");
+ stringer.key("e").value(null);
+ stringer.endObject();
+ assertEquals("{\"a\":false," +
+ "\"b\":5," +
+ "\"c\":5," +
+ "\"d\":\"five\"," +
+ "\"e\":null}", stringer.toString());
+ }
+
+ /**
+ * Test what happens when extreme values are emitted. Such values are likely
+ * to be rounded during parsing.
+ */
+ public void testNumericRepresentations() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.value(Long.MAX_VALUE);
+ stringer.value(Double.MIN_VALUE);
+ stringer.endArray();
+ assertEquals("[9223372036854775807,4.9E-324]", stringer.toString());
+ }
+
+ public void testWeirdNumbers() throws JSONException {
+ try {
+ new JSONStringer().array().value(Double.NaN);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().array().value(Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().array().value(Double.POSITIVE_INFINITY);
+ fail();
+ } catch (JSONException e) {
+ }
+
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.value(-0.0d);
+ stringer.value(0.0d);
+ stringer.endArray();
+ assertEquals("[-0,0]", stringer.toString());
+ }
+
+ public void testMismatchedScopes() {
+ try {
+ new JSONStringer().key("a");
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().value("a");
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().endObject();
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().endArray();
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().array().endObject();
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().object().endArray();
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().object().key("a").key("a");
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONStringer().object().value(false);
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testNullKey() {
+ try {
+ new JSONStringer().object().key(null);
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testRepeatedKey() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.object();
+ stringer.key("a").value(true);
+ stringer.key("a").value(false);
+ stringer.endObject();
+ // JSONStringer doesn't attempt to detect duplicates
+ assertEquals("{\"a\":true,\"a\":false}", stringer.toString());
+ }
+
+ public void testEmptyKey() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.object();
+ stringer.key("").value(false);
+ stringer.endObject();
+ assertEquals("{\"\":false}", stringer.toString()); // legit behaviour!
+ }
+
+ public void testEscaping() throws JSONException {
+ assertEscapedAllWays("a", "a");
+ assertEscapedAllWays("a\"", "a\\\"");
+ assertEscapedAllWays("\"", "\\\"");
+ assertEscapedAllWays(":", ":");
+ assertEscapedAllWays(",", ",");
+ assertEscapedAllWays("\n", "\\n");
+ assertEscapedAllWays("\t", "\\t");
+ assertEscapedAllWays(" ", " ");
+ assertEscapedAllWays("\\", "\\\\");
+ assertEscapedAllWays("{", "{");
+ assertEscapedAllWays("}", "}");
+ assertEscapedAllWays("[", "[");
+ assertEscapedAllWays("]", "]");
+
+ // how does it decide which characters to escape?
+ assertEscapedAllWays("\0", "\\u0000");
+ assertEscapedAllWays("\u0019", "\\u0019");
+ assertEscapedAllWays("\u0020", " ");
+ }
+
+ private void assertEscapedAllWays(String original, String escaped) throws JSONException {
+ assertEquals("{\"" + escaped + "\":false}",
+ new JSONStringer().object().key(original).value(false).endObject().toString());
+ assertEquals("{\"a\":\"" + escaped + "\"}",
+ new JSONStringer().object().key("a").value(original).endObject().toString());
+ assertEquals("[\"" + escaped + "\"]",
+ new JSONStringer().array().value(original).endArray().toString());
+ }
+
+ public void testJSONArrayAsValue() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put(false);
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.value(array);
+ stringer.endArray();
+ assertEquals("[[false]]", stringer.toString());
+ }
+
+ public void testJSONObjectAsValue() throws JSONException {
+ JSONObject object = new JSONObject();
+ object.put("a", false);
+ JSONStringer stringer = new JSONStringer();
+ stringer.object();
+ stringer.key("b").value(object);
+ stringer.endObject();
+ assertEquals("{\"b\":{\"a\":false}}", stringer.toString());
+ }
+
+ public void testArrayNestingMaxDepthIs20() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.array();
+ }
+ for (int i = 0; i < 20; i++) {
+ stringer.endArray();
+ }
+ assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringer.toString());
+
+ stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.array();
+ }
+ try {
+ stringer.array();
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testObjectNestingMaxDepthIs20() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.object();
+ stringer.key("a");
+ }
+ stringer.value(false);
+ for (int i = 0; i < 20; i++) {
+ stringer.endObject();
+ }
+ assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" +
+ "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":false" +
+ "}}}}}}}}}}}}}}}}}}}}", stringer.toString());
+
+ stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.object();
+ stringer.key("a");
+ }
+ try {
+ stringer.object();
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testMixedMaxDepth() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ for (int i = 0; i < 20; i+=2) {
+ stringer.array();
+ stringer.object();
+ stringer.key("a");
+ }
+ stringer.value(false);
+ for (int i = 0; i < 20; i+=2) {
+ stringer.endObject();
+ stringer.endArray();
+ }
+ assertEquals("[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":" +
+ "[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":false" +
+ "}]}]}]}]}]}]}]}]}]}]", stringer.toString());
+
+ stringer = new JSONStringer();
+ for (int i = 0; i < 20; i+=2) {
+ stringer.array();
+ stringer.object();
+ stringer.key("a");
+ }
+ try {
+ stringer.array();
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testMaxDepthWithArrayValue() throws JSONException {
+ JSONArray array = new JSONArray();
+ array.put(false);
+
+ JSONStringer stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.array();
+ }
+ stringer.value(array);
+ for (int i = 0; i < 20; i++) {
+ stringer.endArray();
+ }
+ assertEquals("[[[[[[[[[[[[[[[[[[[[[false]]]]]]]]]]]]]]]]]]]]]", stringer.toString());
+ }
+
+ public void testMaxDepthWithObjectValue() throws JSONException {
+ JSONObject object = new JSONObject();
+ object.put("a", false);
+ JSONStringer stringer = new JSONStringer();
+ for (int i = 0; i < 20; i++) {
+ stringer.object();
+ stringer.key("b");
+ }
+ stringer.value(object);
+ for (int i = 0; i < 20; i++) {
+ stringer.endObject();
+ }
+ assertEquals("{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
+ "{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
+ "{\"a\":false}}}}}}}}}}}}}}}}}}}}}", stringer.toString());
+ }
+
+ public void testMultipleRoots() throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.array();
+ stringer.endArray();
+ try {
+ stringer.object();
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+}
diff --git a/json/src/test/java/org/json/JSONTokenerTest.java b/json/src/test/java/org/json/JSONTokenerTest.java
new file mode 100644
index 0000000..1409a3b
--- /dev/null
+++ b/json/src/test/java/org/json/JSONTokenerTest.java
@@ -0,0 +1,565 @@
+/**
+ * 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.json;
+
+import junit.framework.TestCase;
+
+/**
+ * This black box test was written without inspecting the non-free org.json sourcecode.
+ */
+public class JSONTokenerTest extends TestCase {
+
+ public void testNulls() throws JSONException {
+ // bogus behaviour: JSONTokener accepts null, only to fail later on almost all APIs.
+ new JSONTokener(null).back();
+
+ try {
+ new JSONTokener(null).more();
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).next();
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).next(3);
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).next('A');
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).nextClean();
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).nextString('"');
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).nextTo('A');
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).nextTo("ABC");
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).nextValue();
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).skipPast("ABC");
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ new JSONTokener(null).skipTo('A');
+ fail();
+ } catch (NullPointerException e) {
+ }
+
+ assertEquals("foo! at character 0 of null",
+ new JSONTokener(null).syntaxError("foo!").getMessage());
+
+ assertEquals(" at character 0 of null", new JSONTokener(null).toString());
+ }
+
+ public void testEmptyString() throws JSONException {
+ JSONTokener backTokener = new JSONTokener("");
+ backTokener.back();
+ assertEquals(" at character 0 of ", backTokener.toString());
+ assertFalse(new JSONTokener("").more());
+ assertEquals('\0', new JSONTokener("").next());
+ try {
+ new JSONTokener("").next(3);
+ fail();
+ } catch (JSONException expected) {
+ }
+ try {
+ new JSONTokener("").next('A');
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals('\0', new JSONTokener("").nextClean());
+ try {
+ new JSONTokener("").nextString('"');
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals("", new JSONTokener("").nextTo('A'));
+ assertEquals("", new JSONTokener("").nextTo("ABC"));
+ try {
+ new JSONTokener("").nextValue();
+ fail();
+ } catch (JSONException e) {
+ }
+ new JSONTokener("").skipPast("ABC");
+ assertEquals('\0', new JSONTokener("").skipTo('A'));
+ assertEquals("foo! at character 0 of ",
+ new JSONTokener("").syntaxError("foo!").getMessage());
+ assertEquals(" at character 0 of ", new JSONTokener("").toString());
+ }
+
+ public void testCharacterNavigation() throws JSONException {
+ JSONTokener abcdeTokener = new JSONTokener("ABCDE");
+ assertEquals('A', abcdeTokener.next());
+ assertEquals('B', abcdeTokener.next('B'));
+ assertEquals("CD", abcdeTokener.next(2));
+ try {
+ abcdeTokener.next(2);
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals('E', abcdeTokener.nextClean());
+ assertEquals('\0', abcdeTokener.next());
+ try {
+ // bogus behaviour: returning an empty string should be valid
+ abcdeTokener.next(0);
+ fail();
+ } catch (JSONException e) {
+ }
+ assertFalse(abcdeTokener.more());
+ abcdeTokener.back();
+ assertTrue(abcdeTokener.more());
+ assertEquals('E', abcdeTokener.next());
+ }
+
+ public void testBackNextAndMore() throws JSONException {
+ JSONTokener abcTokener = new JSONTokener("ABC");
+ assertTrue(abcTokener.more());
+ abcTokener.next();
+ abcTokener.next();
+ assertTrue(abcTokener.more());
+ abcTokener.next();
+ assertFalse(abcTokener.more());
+ abcTokener.back();
+ assertTrue(abcTokener.more());
+ abcTokener.next();
+ assertFalse(abcTokener.more());
+ abcTokener.back();
+ abcTokener.back();
+ abcTokener.back();
+ abcTokener.back(); // bogus behaviour: you can back up before the beginning of a String
+ assertEquals('A', abcTokener.next());
+ }
+
+ public void testNextMatching() throws JSONException {
+ JSONTokener abcdTokener = new JSONTokener("ABCD");
+ assertEquals('A', abcdTokener.next('A'));
+ try {
+ abcdTokener.next('C'); // although it failed, this op consumes a character of input
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals('C', abcdTokener.next('C'));
+ assertEquals('D', abcdTokener.next('D'));
+ try {
+ abcdTokener.next('E');
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testNextN() throws JSONException {
+ JSONTokener abcdeTokener = new JSONTokener("ABCDEF");
+ assertEquals("", abcdeTokener.next(0));
+ try {
+ abcdeTokener.next(7);
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals("ABC", abcdeTokener.next(3));
+ try {
+ abcdeTokener.next(4);
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ // bogus behaviour: there should be 3 characters left, but there must be an off-by-one
+ // error in the implementation.
+ assertEquals("DEF", abcdeTokener.next(3));
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals("DE", abcdeTokener.next(2));
+ assertEquals('F', abcdeTokener.next());
+ try {
+ // bogus behaviour: returning an empty string should be valid
+ abcdeTokener.next(0);
+ fail();
+ } catch (JSONException e) {
+ }
+ abcdeTokener.back();
+ abcdeTokener.back();
+ abcdeTokener.back();
+ assertEquals("DE", abcdeTokener.next(2));
+ assertEquals('F', abcdeTokener.next());
+ }
+
+ public void testNextCleanComments() throws JSONException {
+ JSONTokener tokener = new JSONTokener(
+ " A /*XX*/B/*XX//XX\n//XX\nXX*/C//X//X//X\nD/*X*///X\n");
+ assertEquals('A', tokener.nextClean());
+ assertEquals('B', tokener.nextClean());
+ assertEquals('C', tokener.nextClean());
+ assertEquals('D', tokener.nextClean());
+ assertEquals('\0', tokener.nextClean());
+ }
+
+ public void testNextCleanTrailingOpenComment() throws JSONException {
+ try {
+ new JSONTokener(" /* ").nextClean();
+ fail();
+ } catch (JSONException e) {
+ }
+ assertEquals('\0', new JSONTokener(" // ").nextClean());
+ }
+
+ public void testNextCleanNewlineDelimiters() throws JSONException {
+ assertEquals('B', new JSONTokener(" // \r\n B ").nextClean());
+ assertEquals('B', new JSONTokener(" // \n B ").nextClean());
+ assertEquals('B', new JSONTokener(" // \r B ").nextClean());
+ }
+
+ /**
+ * Tests which characters tokener treats as ignorable whitespace. See Kevin Bourrillion's
+ * <a href="https://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">list
+ * of whitespace characters</a>.
+ */
+ public void testNextCleanWhitespace() throws JSONException {
+ // This behaviour contradicts the JSON spec. It claims the only space
+ // characters are space, tab, newline and carriage return. But it treats
+ // many characters like whitespace! These are the same whitespace
+ // characters used by String.trim(), with the exception of '\0'.
+ assertEquals("character tabulation", 'A', new JSONTokener("\u0009A").nextClean());
+ assertEquals("line feed", 'A', new JSONTokener("\nA").nextClean());
+ assertEquals("line tabulation", 'A', new JSONTokener("\u000bA").nextClean());
+ assertEquals("form feed", 'A', new JSONTokener("\u000cA").nextClean());
+ assertEquals("carriage return", 'A', new JSONTokener("\rA").nextClean());
+ assertEquals("information separator 4", 'A', new JSONTokener("\u001cA").nextClean());
+ assertEquals("information separator 3", 'A', new JSONTokener("\u001dA").nextClean());
+ assertEquals("information separator 2", 'A', new JSONTokener("\u001eA").nextClean());
+ assertEquals("information separator 1", 'A', new JSONTokener("\u001fA").nextClean());
+ assertEquals("space", 'A', new JSONTokener("\u0020A").nextClean());
+ for (char c = '\u0002'; c < ' '; c++) {
+ assertEquals('A', new JSONTokener(new String(new char[] { ' ', c, 'A' })).nextClean());
+ }
+
+ // These characters are neither whitespace in the JSON spec nor the implementation
+ assertEquals("null", '\u0000', new JSONTokener("\u0000A").nextClean());
+ assertEquals("next line", '\u0085', new JSONTokener("\u0085A").nextClean());
+ assertEquals("non-breaking space", '\u00a0', new JSONTokener("\u00a0A").nextClean());
+ assertEquals("ogham space mark", '\u1680', new JSONTokener("\u1680A").nextClean());
+ assertEquals("mongolian vowel separator", '\u180e', new JSONTokener("\u180eA").nextClean());
+ assertEquals("en quad", '\u2000', new JSONTokener("\u2000A").nextClean());
+ assertEquals("em quad", '\u2001', new JSONTokener("\u2001A").nextClean());
+ assertEquals("en space", '\u2002', new JSONTokener("\u2002A").nextClean());
+ assertEquals("em space", '\u2003', new JSONTokener("\u2003A").nextClean());
+ assertEquals("three-per-em space", '\u2004', new JSONTokener("\u2004A").nextClean());
+ assertEquals("four-per-em space", '\u2005', new JSONTokener("\u2005A").nextClean());
+ assertEquals("six-per-em space", '\u2006', new JSONTokener("\u2006A").nextClean());
+ assertEquals("figure space", '\u2007', new JSONTokener("\u2007A").nextClean());
+ assertEquals("punctuation space", '\u2008', new JSONTokener("\u2008A").nextClean());
+ assertEquals("thin space", '\u2009', new JSONTokener("\u2009A").nextClean());
+ assertEquals("hair space", '\u200a', new JSONTokener("\u200aA").nextClean());
+ assertEquals("zero-width space", '\u200b', new JSONTokener("\u200bA").nextClean());
+ assertEquals("left-to-right mark", '\u200e', new JSONTokener("\u200eA").nextClean());
+ assertEquals("right-to-left mark", '\u200f', new JSONTokener("\u200fA").nextClean());
+ assertEquals("line separator", '\u2028', new JSONTokener("\u2028A").nextClean());
+ assertEquals("paragraph separator", '\u2029', new JSONTokener("\u2029A").nextClean());
+ assertEquals("narrow non-breaking space", '\u202f', new JSONTokener("\u202fA").nextClean());
+ assertEquals("medium mathematical space", '\u205f', new JSONTokener("\u205fA").nextClean());
+ assertEquals("ideographic space", '\u3000', new JSONTokener("\u3000A").nextClean());
+ }
+
+ public void testNextString() throws JSONException {
+ assertEquals("", new JSONTokener("'").nextString('\''));
+ assertEquals("", new JSONTokener("\"").nextString('\"'));
+ assertEquals("ABC", new JSONTokener("ABC'DEF").nextString('\''));
+ assertEquals("ABC", new JSONTokener("ABC'''DEF").nextString('\''));
+
+ // nextString permits slash-escaping of arbitrary characters!
+ assertEquals("ABC", new JSONTokener("A\\B\\C'DEF").nextString('\''));
+
+ JSONTokener tokener = new JSONTokener(" 'abc' 'def' \"ghi\"");
+ tokener.next();
+ assertEquals('\'', tokener.next());
+ assertEquals("abc", tokener.nextString('\''));
+ tokener.next();
+ assertEquals('\'', tokener.next());
+ assertEquals("def", tokener.nextString('\''));
+ tokener.next();
+ assertEquals('"', tokener.next());
+ assertEquals("ghi", tokener.nextString('\"'));
+ assertFalse(tokener.more());
+ }
+
+ public void testNextStringNoDelimiter() throws JSONException {
+ try {
+ new JSONTokener("").nextString('\'');
+ fail();
+ } catch (JSONException e) {
+ }
+
+ JSONTokener tokener = new JSONTokener(" 'abc");
+ tokener.next();
+ tokener.next();
+ try {
+ tokener.next('\'');
+ fail();
+ } catch (JSONException e) {
+ }
+ }
+
+ public void testNextStringEscapedQuote() throws JSONException {
+ try {
+ new JSONTokener("abc\\").nextString('"');
+ fail();
+ } catch (JSONException e) {
+ }
+
+ // we're mixing Java escaping like \" and JavaScript escaping like \\\"
+ // which makes these tests extra tricky to read!
+ assertEquals("abc\"def", new JSONTokener("abc\\\"def\"ghi").nextString('"'));
+ assertEquals("abc\\def", new JSONTokener("abc\\\\def\"ghi").nextString('"'));
+ assertEquals("abc/def", new JSONTokener("abc\\/def\"ghi").nextString('"'));
+ assertEquals("abc\bdef", new JSONTokener("abc\\bdef\"ghi").nextString('"'));
+ assertEquals("abc\fdef", new JSONTokener("abc\\fdef\"ghi").nextString('"'));
+ assertEquals("abc\ndef", new JSONTokener("abc\\ndef\"ghi").nextString('"'));
+ assertEquals("abc\rdef", new JSONTokener("abc\\rdef\"ghi").nextString('"'));
+ assertEquals("abc\tdef", new JSONTokener("abc\\tdef\"ghi").nextString('"'));
+ }
+
+ public void testNextStringUnicodeEscaped() throws JSONException {
+ // we're mixing Java escaping like \\ and JavaScript escaping like \\u
+ assertEquals("abc def", new JSONTokener("abc\\u0020def\"ghi").nextString('"'));
+ assertEquals("abcU0020def", new JSONTokener("abc\\U0020def\"ghi").nextString('"'));
+
+ // JSON requires 4 hex characters after a unicode escape
+ try {
+ new JSONTokener("abc\\u002\"").nextString('"');
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONTokener("abc\\u").nextString('"');
+ fail();
+ } catch (JSONException e) {
+ }
+ try {
+ new JSONTokener("abc\\u \"").nextString('"');
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ assertEquals("abc\"def", new JSONTokener("abc\\u0022def\"ghi").nextString('"'));
+ try {
+ new JSONTokener("abc\\u000G\"").nextString('"');
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ public void testNextStringNonQuote() throws JSONException {
+ assertEquals("AB", new JSONTokener("ABC").nextString('C'));
+ assertEquals("ABCD", new JSONTokener("AB\\CDC").nextString('C'));
+ assertEquals("AB\nC", new JSONTokener("AB\\nCn").nextString('n'));
+ }
+
+ public void testNextTo() throws JSONException {
+ assertEquals("ABC", new JSONTokener("ABCDEFG").nextTo("DHI"));
+ assertEquals("ABCDEF", new JSONTokener("ABCDEF").nextTo(""));
+
+ JSONTokener tokener = new JSONTokener("ABC\rDEF\nGHI\r\nJKL");
+ assertEquals("ABC", tokener.nextTo("M"));
+ assertEquals('\r', tokener.next());
+ assertEquals("DEF", tokener.nextTo("M"));
+ assertEquals('\n', tokener.next());
+ assertEquals("GHI", tokener.nextTo("M"));
+ assertEquals('\r', tokener.next());
+ assertEquals('\n', tokener.next());
+ assertEquals("JKL", tokener.nextTo("M"));
+
+ tokener = new JSONTokener("ABCDEFGHI");
+ assertEquals("ABC", tokener.nextTo("DEF"));
+ assertEquals("", tokener.nextTo("DEF"));
+ assertEquals('D', tokener.next());
+ assertEquals("", tokener.nextTo("DEF"));
+ assertEquals('E', tokener.next());
+ assertEquals("", tokener.nextTo("DEF"));
+ assertEquals('F', tokener.next());
+ assertEquals("GHI", tokener.nextTo("DEF"));
+ assertEquals("", tokener.nextTo("DEF"));
+
+ tokener = new JSONTokener(" \t \fABC \t DEF");
+ assertEquals("ABC", tokener.nextTo("DEF"));
+ assertEquals('D', tokener.next());
+
+ tokener = new JSONTokener(" \t \fABC \n DEF");
+ assertEquals("ABC", tokener.nextTo("\n"));
+ assertEquals("", tokener.nextTo("\n"));
+
+ // Bogus behaviour: the tokener stops after \0 always
+ tokener = new JSONTokener(" \0\t \fABC \n DEF");
+ assertEquals("", tokener.nextTo("D"));
+ assertEquals('\t', tokener.next());
+ assertEquals("ABC", tokener.nextTo("D"));
+ tokener = new JSONTokener("ABC\0DEF");
+ assertEquals("ABC", tokener.nextTo("\0"));
+ assertEquals("DEF", tokener.nextTo("\0"));
+
+ tokener = new JSONTokener("");
+ try {
+ tokener.nextTo(null);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ public void testSkipPast() {
+ JSONTokener tokener = new JSONTokener("ABCDEF");
+ tokener.skipPast("ABC");
+ assertEquals('D', tokener.next());
+ tokener.skipPast("EF");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABCDEF");
+ tokener.skipPast("ABCDEF");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABCDEF");
+ tokener.skipPast("G");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABC\0ABC");
+ tokener.skipPast("ABC");
+ assertEquals('\0', tokener.next());
+ assertEquals('A', tokener.next());
+
+ tokener = new JSONTokener("\0ABC");
+ tokener.skipPast("ABC");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABC\nDEF");
+ tokener.skipPast("DEF");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABC");
+ tokener.skipPast("ABCDEF");
+ assertEquals('\0', tokener.next());
+
+ tokener = new JSONTokener("ABCDABCDABCD");
+ tokener.skipPast("ABC");
+ assertEquals('D', tokener.next());
+ tokener.skipPast("ABC");
+ assertEquals('D', tokener.next());
+ tokener.skipPast("ABC");
+ assertEquals('D', tokener.next());
+
+ tokener = new JSONTokener("");
+ try {
+ tokener.skipPast(null);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+
+ public void testSkipTo() {
+ JSONTokener tokener = new JSONTokener("ABCDEF");
+ tokener.skipTo('A');
+ assertEquals('A', tokener.next());
+ tokener.skipTo('D');
+ assertEquals('D', tokener.next());
+ tokener.skipTo('G');
+ assertEquals('E', tokener.next());
+ tokener.skipTo('A');
+ assertEquals('F', tokener.next());
+
+ tokener = new JSONTokener("ABC\0DEF");
+ tokener.skipTo('F');
+ // bogus behaviour: skipTo gives up when it sees '\0'
+ assertEquals('A', tokener.next());
+
+ tokener = new JSONTokener("ABC\nDEF");
+ tokener.skipTo('F');
+ assertEquals('F', tokener.next());
+
+ tokener = new JSONTokener("ABCfDEF");
+ tokener.skipTo('F');
+ assertEquals('F', tokener.next());
+
+ tokener = new JSONTokener("ABC/* DEF */");
+ tokener.skipTo('D');
+ assertEquals('D', tokener.next());
+ }
+
+ public void testDehexchar() {
+ assertEquals( 0, JSONTokener.dehexchar('0'));
+ assertEquals( 1, JSONTokener.dehexchar('1'));
+ assertEquals( 2, JSONTokener.dehexchar('2'));
+ assertEquals( 3, JSONTokener.dehexchar('3'));
+ assertEquals( 4, JSONTokener.dehexchar('4'));
+ assertEquals( 5, JSONTokener.dehexchar('5'));
+ assertEquals( 6, JSONTokener.dehexchar('6'));
+ assertEquals( 7, JSONTokener.dehexchar('7'));
+ assertEquals( 8, JSONTokener.dehexchar('8'));
+ assertEquals( 9, JSONTokener.dehexchar('9'));
+ assertEquals(10, JSONTokener.dehexchar('A'));
+ assertEquals(11, JSONTokener.dehexchar('B'));
+ assertEquals(12, JSONTokener.dehexchar('C'));
+ assertEquals(13, JSONTokener.dehexchar('D'));
+ assertEquals(14, JSONTokener.dehexchar('E'));
+ assertEquals(15, JSONTokener.dehexchar('F'));
+ assertEquals(10, JSONTokener.dehexchar('a'));
+ assertEquals(11, JSONTokener.dehexchar('b'));
+ assertEquals(12, JSONTokener.dehexchar('c'));
+ assertEquals(13, JSONTokener.dehexchar('d'));
+ assertEquals(14, JSONTokener.dehexchar('e'));
+ assertEquals(15, JSONTokener.dehexchar('f'));
+
+ for (int c = 0; c <= 0xFFFF; c++) {
+ if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
+ continue;
+ }
+ assertEquals("dehexchar " + c, -1, JSONTokener.dehexchar((char) c));
+ }
+ }
+
+ public void testNextValue() {
+ fail("TODO");
+ }
+}
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/util/NullBenchmarkSuite.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/util/NullBenchmarkSuite.java
deleted file mode 100644
index 52f383c..0000000
--- a/luni/src/test/java/org/apache/harmony/luni/tests/java/util/NullBenchmarkSuite.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2009 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.luni.tests.java.util;
-
-import com.google.caliper.Benchmark;
-import com.google.caliper.SimpleBenchmark;
-
-/**
- * This class exists only to force a dependency from our libraries on Caliper,
- * our micro benchmarking framework.
- */
-public class NullBenchmarkSuite extends SimpleBenchmark {
-
- public void timeNullBenchmark(int trials) throws Exception {
- for (int i = 0; i < trials; i++) {
- // code under test goes here!
- }
- }
-}
diff --git a/luni/src/test/java/tests/AllTests.java b/luni/src/test/java/tests/AllTests.java
index 950156e..864a3ed 100644
--- a/luni/src/test/java/tests/AllTests.java
+++ b/luni/src/test/java/tests/AllTests.java
@@ -66,6 +66,7 @@
suite.addTest(java.util.AllTests.suite());
suite.addTest(javax.xml.parsers.AllTests.suite());
suite.addTest(org.apache.harmony.luni.platform.AllTests.suite());
+ suite.addTest(org.json.AllTests.suite());
suite.addTest(tests.api.org.apache.harmony.kernel.dalvik.AllTests.suite());
return suite;
diff --git a/math/src/main/java/java/math/BigInt.java b/math/src/main/java/java/math/BigInt.java
index 3ba1da2..1eae2e0 100644
--- a/math/src/main/java/java/math/BigInt.java
+++ b/math/src/main/java/java/math/BigInt.java
@@ -82,7 +82,6 @@
public static int consumeErrors(StringBuilder sb) {
int cnt = 0;
int e, reason;
- boolean first = true;
while ((e = NativeBN.ERR_get_error()) != 0) {
reason = e & 255;
if (reason == 103) {
@@ -96,7 +95,6 @@
if (reason == 65) {
throw new OutOfMemoryError();
}
- if (!first) { sb.append(" *** "); first = false; }
sb.append(e).append(": ");
String s = NativeBN.ERR_error_string(e);
sb.append(s);
diff --git a/support/src/test/java/tests/support/resource/Support_Resources.java b/support/src/test/java/tests/support/resource/Support_Resources.java
index 53a8925..67b6001 100644
--- a/support/src/test/java/tests/support/resource/Support_Resources.java
+++ b/support/src/test/java/tests/support/resource/Support_Resources.java
@@ -205,4 +205,20 @@
throw new RuntimeException("Failed to load resource: " + name);
}
}
+
+ public static File resourceToTempFile(String path) throws IOException {
+ File f = File.createTempFile("out", ".xml");
+ f.deleteOnExit();
+ FileOutputStream out = new FileOutputStream(f);
+
+ InputStream xml = Support_Resources.class.getResourceAsStream(path);
+ int b;
+ while ((b = xml.read()) != -1) {
+ out.write(b);
+ }
+ out.flush();
+ out.close();
+ xml.close();
+ return f;
+ }
}
diff --git a/tools/runner/Android.mk b/tools/runner/Android.mk
index bc7c5b7..b208219 100644
--- a/tools/runner/Android.mk
+++ b/tools/runner/Android.mk
@@ -1,69 +1,22 @@
LOCAL_PATH:= $(call my-dir)
+# build DalvikRunner from the source under java/.
include $(CLEAR_VARS)
-
-ext_dirs := \
- ../../../../external/jsr305/ri/src/main/java \
- ../../../../external/guava/src \
- ../../../../external/caliper/src
-
-ext_src_files := $(call all-java-files-under,$(ext_dirs))
-
-LOCAL_SRC_FILES := \
- $(ext_src_files) \
- java/dalvik/runner/Aapt.java \
- java/dalvik/runner/Adb.java \
- java/dalvik/runner/ActivityMode.java \
- java/dalvik/runner/CaliperFinder.java \
- java/dalvik/runner/CaliperRunner.java \
- java/dalvik/runner/Classpath.java \
- java/dalvik/runner/CodeFinder.java \
- java/dalvik/runner/Command.java \
- java/dalvik/runner/CommandFailedException.java \
- java/dalvik/runner/DalvikRunner.java \
- java/dalvik/runner/DeviceDalvikVm.java \
- java/dalvik/runner/Driver.java \
- java/dalvik/runner/Dx.java \
- java/dalvik/runner/Environment.java \
- java/dalvik/runner/EnvironmentDevice.java \
- java/dalvik/runner/EnvironmentHost.java \
- java/dalvik/runner/ExpectedResult.java \
- java/dalvik/runner/JUnitFinder.java \
- java/dalvik/runner/JUnitRunner.java \
- java/dalvik/runner/JavaVm.java \
- java/dalvik/runner/Javac.java \
- java/dalvik/runner/JtregFinder.java \
- java/dalvik/runner/JtregRunner.java \
- java/dalvik/runner/MainFinder.java \
- java/dalvik/runner/MainRunner.java \
- java/dalvik/runner/Mkdir.java \
- java/dalvik/runner/Mode.java \
- java/dalvik/runner/NamingPatternCodeFinder.java \
- java/dalvik/runner/Option.java \
- java/dalvik/runner/OptionParser.java \
- java/dalvik/runner/Result.java \
- java/dalvik/runner/Rm.java \
- java/dalvik/runner/Runner.java \
- java/dalvik/runner/Strings.java \
- java/dalvik/runner/TestProperties.java \
- java/dalvik/runner/TestRun.java \
- java/dalvik/runner/TestRunner.java \
- java/dalvik/runner/Threads.java \
- java/dalvik/runner/Vm.java \
- java/dalvik/runner/XmlReportPrinter.java \
-
+LOCAL_SRC_FILES := $(call all-java-files-under,java)
LOCAL_MODULE:= dalvik_runner
-LOCAL_STATIC_JAVA_LIBRARIES := javatest jh jtreg kxml2-2.3.0
-
+LOCAL_STATIC_JAVA_LIBRARIES := caliper javatest jh jtreg kxml2-2.3.0
# TODO this only works when junit is already built...
LOCAL_JAVA_LIBRARIES := junit
-
LOCAL_JAVACFLAGS := -Werror -Xlint:unchecked
-
include $(BUILD_HOST_JAVA_LIBRARY)
include $(call all-subdir-makefiles)
+# prebuilt caliper.jar
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_JAVA_LIBRARIES := caliper:lib/caliper.jar
+include $(BUILD_HOST_PREBUILT)
+
# prebuilt javatest.jar
include $(CLEAR_VARS)
LOCAL_PREBUILT_JAVA_LIBRARIES := javatest:lib/javatest.jar
diff --git a/tools/runner/java/dalvik/runner/ActivityMode.java b/tools/runner/java/dalvik/runner/ActivityMode.java
index dd7c420..163c72a 100644
--- a/tools/runner/java/dalvik/runner/ActivityMode.java
+++ b/tools/runner/java/dalvik/runner/ActivityMode.java
@@ -19,10 +19,12 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
@@ -36,11 +38,11 @@
private static final String TEST_ACTIVITY_CLASS = "dalvik.runner.TestActivity";
- ActivityMode(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp,
+ ActivityMode(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee, File localTemp,
boolean cleanBefore, boolean cleanAfter, File deviceRunnerDir) {
super(new EnvironmentDevice(cleanBefore, cleanAfter,
debugPort, localTemp, deviceRunnerDir),
- timeoutSeconds, sdkJar);
+ timeoutSeconds, sdkJar, tee);
}
private EnvironmentDevice getEnvironmentDevice() {
@@ -48,7 +50,7 @@
}
@Override protected void prepare(Set<File> testRunnerJava, Classpath testRunnerClasspath) {
- testRunnerJava.add(new File(DalvikRunner.HOME_JAVA, "dalvik/runner/TestActivity.java"));
+ testRunnerJava.add(new File("dalvik/libcore/tools/runner/lib/TestActivity.java"));
super.prepare(testRunnerJava, testRunnerClasspath);
}
@@ -148,11 +150,21 @@
return dex;
}
+ /**
+ * According to android.content.pm.PackageParser, package name
+ * "must have at least one '.' separator" Since the qualified name
+ * may not contain a dot, we prefix containing one to ensure we
+ * are compliant.
+ */
+ private static String packageName(TestRun testRun) {
+ return "DalvikRunner." + testRun.getQualifiedName();
+ }
+
private File createApk (TestRun testRun, File dex) {
String androidManifest =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " package=\"" + testRun.getQualifiedName() + "\">\n" +
+ " package=\"" + packageName(testRun) + "\">\n" +
" <uses-permission android:name=\"android.permission.INTERNET\" />\n" +
" <application>\n" +
" <activity android:name=\"" + TEST_ACTIVITY_CLASS + "\">\n" +
@@ -202,7 +214,7 @@
private void installApk(TestRun testRun, File apkSigned) {
// install the local apk ona the device
- getEnvironmentDevice().adb.uninstall(testRun.getQualifiedName());
+ getEnvironmentDevice().adb.uninstall(packageName(testRun));
getEnvironmentDevice().adb.install(apkSigned);
}
@@ -211,40 +223,20 @@
properties.setProperty(TestProperties.DEVICE_RUNNER_DIR, getEnvironmentDevice().runnerDir.getPath());
}
- @Override protected List<Command> buildCommands(TestRun testRun) {
- List<Command> commands = new ArrayList<Command>();
- commands.add(new Command.Builder()
- .args("adb")
- .args("shell")
- .args("am")
- .args("start")
- .args("-a")
- .args("android.intent.action.MAIN")
- .args("-n")
- .args(testRun.getQualifiedName() + "/" + TEST_ACTIVITY_CLASS).build());
+ @Override protected List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException {
+ new Command(
+ "adb", "shell", "am", "start",
+ "-a","android.intent.action.MAIN",
+ "-n", (packageName(testRun) + "/" + TEST_ACTIVITY_CLASS)).executeWithTimeout(timeoutSeconds);
File resultDir = new File(getEnvironmentDevice().runnerDir, testRun.getQualifiedName());
File resultFile = new File(resultDir, TestProperties.RESULT_FILE);
- /*
- * The follow bash script waits for the result file to
- * exist. It polls once a second to see if it is there with
- * "adb shell ls". The "tr" is to remove the carriage return
- * and newline from the adb output. When it does exist, we
- * "adb shell cat" it so we can see the SUCCESS/FAILURE
- * results that are expected by Mode.runTest.
- */
- // TODO: move loop to Java
- commands.add(new Command.Builder()
- .args("bash")
- .args("-c")
- .args(
- "while [ ! \"`adb shell ls " + resultFile + " | tr -d '\\r\\n'`\" = " +
- " \"" + resultFile + "\" ] ; do " +
- " sleep 1; " +
- "done; " +
- "adb shell cat " + resultFile).build());
-
- return commands;
+ getEnvironmentDevice().adb.waitForFile(resultFile, timeoutSeconds);
+ return new Command.Builder()
+ .args("adb", "shell", "cat", resultFile.getPath())
+ .tee(tee)
+ .build().executeWithTimeout(timeoutSeconds);
}
@Override void cleanup(TestRun testRun) {
diff --git a/tools/runner/java/dalvik/runner/Adb.java b/tools/runner/java/dalvik/runner/Adb.java
index 075ca5f..c982058 100644
--- a/tools/runner/java/dalvik/runner/Adb.java
+++ b/tools/runner/java/dalvik/runner/Adb.java
@@ -58,10 +58,22 @@
}
/**
+ * Loop until we see a file on the device. For example, wait
+ * result.txt appears.
+ */
+ public void waitForFile(File file, long timeoutSeconds) {
+ waitFor(true, file, timeoutSeconds);
+ }
+
+ /**
* Loop until we see a non-empty directory on the device. For
* example, wait until /sdcard is mounted.
*/
- public void waitForNonEmptyDirectory(File path, int timeoutSeconds) {
+ public void waitForNonEmptyDirectory(File path, long timeoutSeconds) {
+ waitFor(false, path, timeoutSeconds);
+ }
+
+ private void waitFor(boolean file, File path, long timeoutSeconds) {
final int millisPerSecond = 1000;
final long start = System.currentTimeMillis();
final long deadline = start + (millisPerSecond * timeoutSeconds);
@@ -69,7 +81,11 @@
while (true) {
final long remainingSeconds = ((deadline - System.currentTimeMillis())
/ millisPerSecond);
- Command command = new Command("adb", "shell", "ls", path.getPath());
+ String pathArgument = path.getPath();
+ if (!file) {
+ pathArgument += "/";
+ }
+ Command command = new Command("adb", "shell", "ls", pathArgument);
List<String> output;
try {
output = command.executeWithTimeout(remainingSeconds);
@@ -82,8 +98,16 @@
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
- if (!output.isEmpty()) {
- return;
+ if (file) {
+ // for files, we expect one line of output that matches the filename
+ if (output.size() == 1 && output.get(0).equals(path.getPath())) {
+ return;
+ }
+ } else {
+ // for a non empty directory, we just want any output
+ if (!output.isEmpty()) {
+ return;
+ }
}
}
}
diff --git a/tools/runner/java/dalvik/runner/CaliperFinder.java b/tools/runner/java/dalvik/runner/CaliperFinder.java
index 094609a..3609471 100644
--- a/tools/runner/java/dalvik/runner/CaliperFinder.java
+++ b/tools/runner/java/dalvik/runner/CaliperFinder.java
@@ -41,15 +41,6 @@
}
public Classpath getRunnerClasspath() {
- return Classpath.of(
- // TODO: we should be able to work with a shipping SDK, not depend on out/...
- // TODO: have a pre-packaged caliper-all.jar in our lib directory, with the jtreg stuff.
- // external/caliper
- new File("out/target/common/obj/JAVA_LIBRARIES/caliper_intermediates/classes.jar").getAbsoluteFile(),
- // external/guava for external/caliper
- new File("out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/classes.jar").getAbsoluteFile(),
- // external/jsr305 for external/guava
- new File("out/target/common/obj/JAVA_LIBRARIES/jsr305_intermediates/classes.jar").getAbsoluteFile());
-
+ return new Classpath();
}
}
diff --git a/tools/runner/java/dalvik/runner/Command.java b/tools/runner/java/dalvik/runner/Command.java
index 4319cf9..88ba38e 100644
--- a/tools/runner/java/dalvik/runner/Command.java
+++ b/tools/runner/java/dalvik/runner/Command.java
@@ -20,6 +20,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -44,6 +45,7 @@
private final List<String> args;
private final File workingDirectory;
private final boolean permitNonZeroExitStatus;
+ private final PrintStream tee;
private Process process;
Command(String... args) {
@@ -54,12 +56,14 @@
this.args = new ArrayList<String>(args);
this.workingDirectory = null;
this.permitNonZeroExitStatus = false;
+ this.tee = null;
}
private Command(Builder builder) {
this.args = new ArrayList<String>(builder.args);
this.workingDirectory = builder.workingDirectory;
this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
+ this.tee = builder.tee;
}
public List<String> getArgs() {
@@ -106,6 +110,9 @@
List<String> outputLines = new ArrayList<String>();
String outputLine;
while ((outputLine = in.readLine()) != null) {
+ if (tee != null) {
+ tee.println(outputLine);
+ }
outputLines.add(outputLine);
}
@@ -167,6 +174,7 @@
private final List<String> args = new ArrayList<String>();
private File workingDirectory;
private boolean permitNonZeroExitStatus = false;
+ private PrintStream tee = null;
public Builder args(Object... objects) {
for (Object object : objects) {
@@ -200,6 +208,11 @@
return this;
}
+ public Builder tee(PrintStream printStream) {
+ tee = printStream;
+ return this;
+ }
+
public Command build() {
return new Command(this);
}
diff --git a/tools/runner/java/dalvik/runner/DalvikRunner.java b/tools/runner/java/dalvik/runner/DalvikRunner.java
index a943be9..c78866e 100644
--- a/tools/runner/java/dalvik/runner/DalvikRunner.java
+++ b/tools/runner/java/dalvik/runner/DalvikRunner.java
@@ -16,8 +16,12 @@
package dalvik.runner;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
@@ -72,6 +76,10 @@
@Option(names = { "--verbose" })
private boolean verbose;
+ @Option(names = { "--tee" })
+ private String teeName;
+ private PrintStream tee;
+
@Option(names = { "--debug" })
private Integer debugPort;
@@ -116,6 +124,9 @@
System.out.println(" --clean: synonym for --clean-before and --clean-after (default).");
System.out.println(" Disable with --no-clean if you want no files removed.");
System.out.println();
+ System.out.println(" --tee <file>: emit test output to file during execution.");
+ System.out.println(" Specify '-' for stdout.");
+ System.out.println();
System.out.println(" --timeout-seconds <seconds>: maximum execution time of each");
System.out.println(" test before the runner aborts it.");
System.out.println(" Default is: " + timeoutSeconds);
@@ -197,10 +208,6 @@
System.out.println("Invalid java home: " + javaHome);
return false;
}
- if (debugPort != null) {
- System.out.println("debug port " + debugPort + " should not be specified for mode " + mode);
- return false;
- }
}
// check vm option consistency
@@ -239,6 +246,19 @@
testFiles.add(new File(testFilename));
}
+ if (teeName != null) {
+ if (teeName.equals("-")) {
+ tee = System.out;
+ } else {
+ try {
+ tee = new PrintStream(new BufferedOutputStream(new FileOutputStream(teeName)));
+ } catch (FileNotFoundException e) {
+ System.out.println("Could not open file teeName: " + e);
+ return false;
+ }
+ }
+ }
+
if (verbose) {
Logger.getLogger("dalvik.runner").setLevel(Level.FINE);
}
@@ -273,6 +293,7 @@
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.vmArgs,
options.cleanBefore,
@@ -283,6 +304,7 @@
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.javaHome,
options.vmArgs,
@@ -293,6 +315,7 @@
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.cleanBefore,
options.cleanAfter,
diff --git a/tools/runner/java/dalvik/runner/DeviceDalvikVm.java b/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
index fec3463..061e374 100644
--- a/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
+++ b/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
@@ -17,6 +17,7 @@
package dalvik.runner;
import java.io.File;
+import java.io.PrintStream;
import java.util.List;
import java.util.logging.Logger;
@@ -24,22 +25,13 @@
* Execute tests on a Dalvik VM using an Android device or emulator.
*/
final class DeviceDalvikVm extends Vm {
-
- // TODO: Don't assume we can put files in /system/framework,
- // so we can run on production devices.
- private static final Classpath RUNTIME_SUPPORT_CLASSPATH = Classpath.of(
- new File("/system/framework/core-tests.jar"),
- new File("/system/framework/caliper.jar"),
- new File("/system/framework/guava.jar"),
- new File("/system/framework/jsr305.jar"));
-
private static final Logger logger = Logger.getLogger(DeviceDalvikVm.class.getName());
- DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar,
+ DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee,
File localTemp, List<String> additionalVmArgs,
boolean cleanBefore, boolean cleanAfter, File runnerDir) {
super(new EnvironmentDevice(cleanBefore, cleanAfter, debugPort, localTemp, runnerDir),
- timeoutSeconds, sdkJar, additionalVmArgs);
+ timeoutSeconds, sdkJar, tee, additionalVmArgs);
}
private EnvironmentDevice getEnvironmentDevice() {
@@ -47,7 +39,24 @@
}
@Override protected void postCompileTestRunner() {
+ // TODO: does this really need to be a special case?
postCompile("testrunner", environment.testRunnerClassesDir());
+
+ // dex everything on the classpath and push it to the device.
+ for (File classpathElement : testClasspath.getElements()) {
+ String name = basenameOfJar(classpathElement);
+ logger.fine("dex and push " + name);
+ // make the local dex (inside a jar)
+ // TODO: this is *really* expensive. we need a cache!
+ File outputFile = getEnvironmentDevice().testDir(name + ".jar");
+ new Dx().dex(outputFile, Classpath.of(classpathElement));
+ // push the local dex to the device
+ getEnvironmentDevice().adb.push(outputFile, deviceDexFile(name));
+ }
+ }
+
+ private String basenameOfJar(File jarFile) {
+ return jarFile.getName().replaceAll("\\.jar$", "");
}
@Override protected void postCompileTest(TestRun testRun) {
@@ -74,8 +83,12 @@
File workingDirectory) {
// ignore the working directory; it's device-local and we can't easily
// set the working directory for commands run via adb shell.
+ // TODO: we only *need* to set ANDROID_DATA on production devices.
+ // We set "user.home" to /sdcard because code might reasonably assume it can write to
+ // that directory.
return new VmCommandBuilder()
- .vmCommand("adb", "shell", "dalvikvm")
+ .vmCommand("adb", "shell", "ANDROID_DATA=/sdcard", "dalvikvm")
+ .vmArgs("-Duser.home=/sdcard")
.vmArgs("-Duser.name=root")
.vmArgs("-Duser.language=en")
.vmArgs("-Duser.region=US")
@@ -87,7 +100,9 @@
Classpath classpath = new Classpath();
classpath.addAll(deviceDexFile(testRun.getQualifiedName()));
classpath.addAll(deviceDexFile("testrunner"));
- classpath.addAll(RUNTIME_SUPPORT_CLASSPATH);
+ for (File testClasspathElement : testClasspath.getElements()) {
+ classpath.addAll(deviceDexFile(basenameOfJar(testClasspathElement)));
+ }
return classpath;
}
}
diff --git a/tools/runner/java/dalvik/runner/Dx.java b/tools/runner/java/dalvik/runner/Dx.java
index 09772bd..393b70d 100644
--- a/tools/runner/java/dalvik/runner/Dx.java
+++ b/tools/runner/java/dalvik/runner/Dx.java
@@ -17,13 +17,25 @@
package dalvik.runner;
import java.io.File;
+import java.util.logging.Logger;
/**
* A dx command.
*/
final class Dx {
+ private static final Logger logger = Logger.getLogger(Dx.class.getName());
+ private static final Md5Cache DEX_CACHE = new Md5Cache("dex");
+ /**
+ * Converts all the .class files on 'classpath' into a dex file written to 'output'.
+ */
public void dex(File output, Classpath classpath) {
+ File key = DEX_CACHE.makeKey(classpath);
+ if (key != null && key.exists()) {
+ logger.fine("dex cache hit for " + classpath);
+ new Command.Builder().args("cp", key, output).execute();
+ return;
+ }
/*
* We pass --core-library so that we can write tests in the
* same package they're testing, even when that's a core
@@ -33,7 +45,7 @@
* yourself.
*
* Memory options pulled from build/core/definitions.mk to
- * handle larged dx input when building dex for APK.
+ * handle large dx input when building dex for APK.
*/
new Command.Builder()
.args("dx")
@@ -44,5 +56,6 @@
.args("--core-library")
.args(Strings.objectsToStrings(classpath.getElements()))
.execute();
+ DEX_CACHE.insert(key, output);
}
}
diff --git a/tools/runner/java/dalvik/runner/EnvironmentDevice.java b/tools/runner/java/dalvik/runner/EnvironmentDevice.java
index c44152d..9ac1c64 100644
--- a/tools/runner/java/dalvik/runner/EnvironmentDevice.java
+++ b/tools/runner/java/dalvik/runner/EnvironmentDevice.java
@@ -41,6 +41,7 @@
}
adb.mkdir(runnerDir);
adb.mkdir(testTemp);
+ adb.mkdir(new File("/sdcard/dalvik-cache")); // TODO: only necessary on production devices.
if (debugPort != null) {
adb.forwardTcp(debugPort, debugPort);
}
diff --git a/tools/runner/java/dalvik/runner/JavaVm.java b/tools/runner/java/dalvik/runner/JavaVm.java
index 561b0ce..38e0386 100644
--- a/tools/runner/java/dalvik/runner/JavaVm.java
+++ b/tools/runner/java/dalvik/runner/JavaVm.java
@@ -17,7 +17,9 @@
package dalvik.runner;
import java.io.File;
+import java.io.PrintStream;
import java.util.List;
+import java.util.Set;
/**
* A local Java virtual machine like Harmony or the RI.
@@ -26,15 +28,16 @@
private final File javaHome;
- JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp,
- File javaHome, List<String> additionalVmArgs,
+ JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee,
+ File localTemp, File javaHome, List<String> additionalVmArgs,
boolean cleanBefore, boolean cleanAfter) {
super(new EnvironmentHost(cleanBefore, cleanAfter, debugPort, localTemp),
- timeoutSeconds, sdkJar, additionalVmArgs);
+ timeoutSeconds, sdkJar, tee, additionalVmArgs);
this.javaHome = javaHome;
}
- @Override protected void postCompileTestRunner() {}
+ @Override protected void postCompileTestRunner() {
+ }
@Override protected void postCompileTest(TestRun testRun) {
}
diff --git a/tools/runner/java/dalvik/runner/Md5Cache.java b/tools/runner/java/dalvik/runner/Md5Cache.java
new file mode 100644
index 0000000..f6ba85d
--- /dev/null
+++ b/tools/runner/java/dalvik/runner/Md5Cache.java
@@ -0,0 +1,115 @@
+/*
+ * 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 dalvik.runner;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.MessageDigest;
+import java.util.logging.Logger;
+
+/**
+ * Caches content by MD5.
+ */
+public final class Md5Cache {
+ private static final Logger logger = Logger.getLogger(Md5Cache.class.getName());
+ private static final File CACHE_ROOT = new File("/tmp/vogar-md5-cache/");
+
+ private final String keyPrefix;
+
+ /**
+ * Creates a new cache accessor. There's only one directory on disk, so 'keyPrefix' is really
+ * just a convenience for humans inspecting the cache.
+ */
+ public Md5Cache(String keyPrefix) {
+ this.keyPrefix = keyPrefix;
+ }
+
+ /**
+ * Returns an ASCII hex representation of the MD5 of the content of 'file'.
+ */
+ private static String md5(File file) {
+ byte[] digest = null;
+ try {
+ MessageDigest digester = MessageDigest.getInstance("MD5");
+ byte[] bytes = new byte[8192];
+ FileInputStream in = new FileInputStream(file);
+ try {
+ int byteCount;
+ while ((byteCount = in.read(bytes)) > 0) {
+ digester.update(bytes, 0, byteCount);
+ }
+ digest = digester.digest();
+ } finally {
+ in.close();
+ }
+ } catch (Exception cause) {
+ throw new RuntimeException("Unable to compute MD5 of \"" + file + "\"", cause);
+ }
+ return (digest == null) ? null : byteArrayToHexString(digest);
+ }
+
+ private static String byteArrayToHexString(byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte b : bytes) {
+ result.append(Integer.toHexString((b >> 4) & 0xf));
+ result.append(Integer.toHexString(b & 0xf));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns the appropriate key for a dex file corresponding to the contents of 'classpath'.
+ * Returns null if we don't think it's possible to cache the given classpath.
+ */
+ public File makeKey(Classpath classpath) {
+ // Do we have it in cache?
+ String key = keyPrefix;
+ for (File element : classpath.getElements()) {
+ // We only cache dexed .jar files, not directories.
+ if (!element.toString().endsWith(".jar")) {
+ return null;
+ }
+ key += "-" + md5(element);
+ }
+ return new File(CACHE_ROOT, key);
+ }
+
+ /**
+ * Copy the file 'content' into the cache with the given 'key'.
+ * This method assumes you're using the appropriate key for the content (and has no way to
+ * check because the key is a function of the inputs that made the content, not the content
+ * itself).
+ * We accept a null so the caller doesn't have to pay attention to whether we think we can
+ * cache the content or not.
+ */
+ public void insert(File key, File content) {
+ if (key == null) {
+ return;
+ }
+ logger.fine("inserting " + key);
+ if (!key.toString().startsWith(CACHE_ROOT.toString())) {
+ throw new IllegalArgumentException("key '" + key + "' not a valid cache key");
+ }
+ // Make sure the cache exists first.
+ new Mkdir().mkdirs(CACHE_ROOT);
+ // Copy it onto the same file system first, then atomically move it into place.
+ // That way, if we fail, we don't leave anything dangerous lying around.
+ File temporary = new File(key + ".tmp");
+ new Command.Builder().args("cp", content, temporary).execute();
+ new Command.Builder().args("mv", temporary, key).execute();
+ }
+}
diff --git a/tools/runner/java/dalvik/runner/Mode.java b/tools/runner/java/dalvik/runner/Mode.java
index c30e023..0ad7172 100644
--- a/tools/runner/java/dalvik/runner/Mode.java
+++ b/tools/runner/java/dalvik/runner/Mode.java
@@ -18,7 +18,9 @@
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FilenameFilter;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -43,6 +45,7 @@
protected final Environment environment;
protected final long timeoutSeconds;
protected final File sdkJar;
+ protected final PrintStream tee;
/**
* Set of Java files needed to built to tun the currently selected
@@ -61,18 +64,21 @@
*/
protected final Classpath testRunnerClasspath = new Classpath();
+ // TODO: this should be an immutable collection.
protected final Classpath testClasspath = Classpath.of(
+ new File("dalvik/libcore/tools/runner/lib/jsr305.jar"),
+ new File("dalvik/libcore/tools/runner/lib/guava.jar"),
+ new File("dalvik/libcore/tools/runner/lib/caliper.jar"),
// TODO: we should be able to work with a shipping SDK, not depend on out/...
- // dalvik/libcore for tests
- new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar").getAbsoluteFile(),
- // framework/base for tests
- new File("out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar").getAbsoluteFile());
+ // dalvik/libcore/**/test/ for junit
+ // TODO: jar up just the junit classes and drop the jar in our lib/ directory.
+ new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar").getAbsoluteFile());
-
- Mode(Environment environment, long timeoutSeconds, File sdkJar) {
+ Mode(Environment environment, long timeoutSeconds, File sdkJar, PrintStream tee) {
this.environment = environment;
this.timeoutSeconds = timeoutSeconds;
this.sdkJar = sdkJar;
+ this.tee = tee;
}
/**
@@ -81,20 +87,37 @@
*/
protected void prepare(Set<File> testRunnerJava, Classpath testRunnerClasspath) {
this.testRunnerJava.add(new File(DalvikRunner.HOME_JAVA, "dalvik/runner/TestRunner.java"));
+ this.testRunnerJava.addAll(dalvikAnnotationSourceFiles());
this.testRunnerJava.addAll(testRunnerJava);
this.testRunnerClasspath.addAll(testRunnerClasspath);
environment.prepare();
compileTestRunner();
}
+ private List<File> dalvikAnnotationSourceFiles() {
+ // Hopefully one day we'll strip the dalvik annotations out, but until then we need to make
+ // them available to javac(1).
+ File sourceDir = new File("dalvik/libcore/dalvik/src/main/java/dalvik/annotation");
+ File[] javaSourceFiles = sourceDir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ return filename.endsWith(".java");
+ }
+ });
+ return Arrays.asList(javaSourceFiles);
+ }
+
private void compileTestRunner() {
logger.fine("build testrunner");
+ Classpath classpath = new Classpath();
+ classpath.addAll(testClasspath);
+ classpath.addAll(testRunnerClasspath);
+
File base = environment.testRunnerClassesDir();
new Mkdir().mkdirs(base);
new Javac()
.bootClasspath(sdkJar)
- .classpath(testRunnerClasspath)
+ .classpath(classpath)
.sourcepath(DalvikRunner.HOME_JAVA)
.destination(base)
.compile(testRunnerJava);
@@ -158,13 +181,17 @@
classpath.addAll(testClasspath);
classpath.addAll(testRun.getRunnerClasspath());
+ Set<File> sourceFiles = new HashSet<File>();
+ sourceFiles.add(testRun.getTestJava());
+ sourceFiles.addAll(dalvikAnnotationSourceFiles());
+
// compile the test case
new Javac()
.bootClasspath(sdkJar)
.classpath(classpath)
.sourcepath(testRun.getTestDirectory())
.destination(testClassesDir)
- .compile(testRun.getTestJava());
+ .compile(sourceFiles);
postCompileTest(testRun);
return true;
}
@@ -194,20 +221,16 @@
throw new IllegalArgumentException();
}
- final List<Command> commands = buildCommands(testRun);
-
- List<String> output = null;
- for (final Command command : commands) {
- try {
- output = command.executeWithTimeout(timeoutSeconds);
- } catch (TimeoutException e) {
- testRun.setResult(Result.EXEC_TIMEOUT,
- Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
- return;
- } catch (Exception e) {
- testRun.setResult(Result.ERROR, e);
- return;
- }
+ List<String> output;
+ try {
+ output = runTestCommand(testRun);
+ } catch (TimeoutException e) {
+ testRun.setResult(Result.EXEC_TIMEOUT,
+ Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
+ return;
+ } catch (Exception e) {
+ testRun.setResult(Result.ERROR, e);
+ return;
}
// we only look at the output of the last command
if (output.isEmpty()) {
@@ -223,9 +246,10 @@
}
/**
- * Returns commands for test execution.
+ * Run the actual test to gather output
*/
- protected abstract List<Command> buildCommands(TestRun testRun);
+ protected abstract List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException;
/**
* Deletes files and releases any resources required for the execution of
diff --git a/tools/runner/java/dalvik/runner/Vm.java b/tools/runner/java/dalvik/runner/Vm.java
index 9f96ec5..8ff5858 100644
--- a/tools/runner/java/dalvik/runner/Vm.java
+++ b/tools/runner/java/dalvik/runner/Vm.java
@@ -19,6 +19,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -27,6 +28,7 @@
import java.util.List;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
/**
@@ -39,22 +41,25 @@
protected final List<String> additionalVmArgs;
Vm(Environment environment, long timeoutSeconds, File sdkJar,
- List<String> additionalVmArgs) {
- super(environment, timeoutSeconds, sdkJar);
+ PrintStream tee, List<String> additionalVmArgs) {
+ super(environment, timeoutSeconds, sdkJar, tee);
this.additionalVmArgs = additionalVmArgs;
}
/**
* Returns a VM for test execution.
*/
- @Override protected List<Command> buildCommands(TestRun testRun) {
- return Collections.singletonList(newVmCommandBuilder(testRun.getUserDir())
+ @Override protected List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException {
+ Command command = newVmCommandBuilder(testRun.getUserDir())
.classpath(getRuntimeSupportClasspath(testRun))
.userDir(testRun.getUserDir())
.debugPort(environment.debugPort)
.vmArgs(additionalVmArgs)
.mainClass(TestRunner.class.getName())
- .build());
+ .output(tee)
+ .build();
+ return command.executeWithTimeout(timeoutSeconds);
}
/**
@@ -78,6 +83,7 @@
private File userDir;
private Integer debugPort;
private String mainClass;
+ private PrintStream output;
private List<String> vmCommand = Collections.singletonList("java");
private List<String> vmArgs = new ArrayList<String>();
@@ -116,6 +122,11 @@
return this;
}
+ public VmCommandBuilder output(PrintStream output) {
+ this.output = output;
+ return this;
+ }
+
public VmCommandBuilder vmArgs(String... vmArgs) {
return vmArgs(Arrays.asList(vmArgs));
}
@@ -146,6 +157,8 @@
builder.args(vmArgs);
builder.args(mainClass);
+ builder.tee(output);
+
return builder.build();
}
}
diff --git a/tools/runner/java/dalvik/runner/TestActivity.java b/tools/runner/lib/TestActivity.java
similarity index 100%
rename from tools/runner/java/dalvik/runner/TestActivity.java
rename to tools/runner/lib/TestActivity.java
diff --git a/tools/runner/lib/caliper.jar b/tools/runner/lib/caliper.jar
new file mode 100644
index 0000000..63a156a
--- /dev/null
+++ b/tools/runner/lib/caliper.jar
Binary files differ
diff --git a/tools/runner/lib/caliper.jar.txt b/tools/runner/lib/caliper.jar.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tools/runner/lib/caliper.jar.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/tools/runner/lib/guava.jar b/tools/runner/lib/guava.jar
new file mode 100644
index 0000000..39adc7f
--- /dev/null
+++ b/tools/runner/lib/guava.jar
Binary files differ
diff --git a/tools/runner/lib/guava.jar.txt b/tools/runner/lib/guava.jar.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tools/runner/lib/guava.jar.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/tools/runner/lib/jsr305.jar b/tools/runner/lib/jsr305.jar
new file mode 100644
index 0000000..57a62c1
--- /dev/null
+++ b/tools/runner/lib/jsr305.jar
Binary files differ
diff --git a/tools/runner/lib/jsr305.jar.txt b/tools/runner/lib/jsr305.jar.txt
new file mode 100644
index 0000000..6736681
--- /dev/null
+++ b/tools/runner/lib/jsr305.jar.txt
@@ -0,0 +1,28 @@
+Copyright (c) 2007-2009, JSR305 expert group
+All rights reserved.
+
+http://www.opensource.org/licenses/bsd-license.php
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of the JSR305 expert group nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
diff --git a/tools/runner/vogar b/tools/runner/vogar
new file mode 100755
index 0000000..e5a6ad0
--- /dev/null
+++ b/tools/runner/vogar
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 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.
+
+# m core-tests junit caliper snod && adb reboot bootloader && fastboot flashall && adb wait-for-device
+# mmm dalvik/libcore/tools/runner
+
+classpath=`dirname $0`/../../../../out/host/linux-x86/framework/dalvik_runner.jar
+exec java -cp $classpath dalvik.runner.DalvikRunner "$@"
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
index 59a8b78..4e689fb 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
@@ -73,7 +73,7 @@
throw new DOMException(DOMException.NAMESPACE_ERR, localName);
}
- if (!document.isXMLIdentifier(localName)) {
+ if (!DocumentImpl.isXMLIdentifier(localName)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, localName);
}
@@ -90,11 +90,11 @@
String prefix = name.substring(0, prefixSeparator);
String localName = name.substring(prefixSeparator + 1);
- if (!document.isXMLIdentifier(prefix) || !document.isXMLIdentifier(localName)) {
+ if (!DocumentImpl.isXMLIdentifier(prefix) || !DocumentImpl.isXMLIdentifier(localName)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
}
} else {
- if (!document.isXMLIdentifier(name)) {
+ if (!DocumentImpl.isXMLIdentifier(name)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
}
}
@@ -108,7 +108,9 @@
}
public String getName() {
- return (prefix != null ? prefix + ":" : "") + localName;
+ return prefix != null
+ ? prefix + ":" + localName
+ : localName;
}
@Override
@@ -148,28 +150,8 @@
}
@Override
- public void setNodeValue(String value) throws DOMException {
- setValue(value);
- }
-
- @Override
public void setPrefix(String prefix) {
- if (!namespaceAware) {
- throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
- }
-
- if (prefix != null) {
- if (namespaceURI == null
- || !DocumentImpl.isXMLIdentifier(prefix)
- || ("xmlns".equals(prefix)
- && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI))
- || ("xml".equals(prefix)
- && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI))) {
- throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
- }
- }
-
- this.prefix = prefix;
+ this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
}
public void setValue(String value) throws DOMException {
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java
index 33e216a..b28c9da 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/CDATASectionImpl.java
@@ -31,7 +31,7 @@
*/
public class CDATASectionImpl extends TextImpl implements CDATASection {
- CDATASectionImpl(DocumentImpl document, String data) {
+ public CDATASectionImpl(DocumentImpl document, String data) {
super(document, data);
}
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java
index c39423c..6354747 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/CharacterDataImpl.java
@@ -51,6 +51,13 @@
return buffer.toString();
}
+ /**
+ * Appends this node's text content to the given builder.
+ */
+ public void appendDataTo(StringBuilder stringBuilder) {
+ stringBuilder.append(buffer);
+ }
+
public int getLength() {
return buffer.length();
}
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
index 861f0a3..1283eeb 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
@@ -31,7 +31,7 @@
* the DOM implementation can easily access them while maintaining the DOM tree
* structure.
*/
-public class DOMImplementationImpl implements DOMImplementation {
+public final class DOMImplementationImpl implements DOMImplementation {
// Singleton instance.
private static DOMImplementationImpl instance;
@@ -50,17 +50,24 @@
}
public boolean hasFeature(String feature, String version) {
- // We claim to support DOM Core Level 1 & 2, nothing else.
-
- // TODO
-
- if ("Core".equalsIgnoreCase(feature) || "XML".equalsIgnoreCase(feature)) {
- if (version == null || "".equals(version) || "1.0".equals(version) || "2.0".equals(version)) {
- return true;
- }
+ boolean anyVersion = version == null || version.length() == 0;
+ if (feature.startsWith("+")) {
+ feature = feature.substring(1);
}
- return false;
+ // TODO: fully implement these APIs:
+ // "LS" (org.w3c.dom.ls) versions "3.0"
+ // "ElementTraversal" (org.w3c.dom.traversal) versions "1.0"
+
+ if (feature.equalsIgnoreCase("Core")) {
+ return anyVersion || version.equals("1.0") || version.equals("2.0") || version.equals("3.0");
+ } else if (feature.equalsIgnoreCase("XML")) {
+ return anyVersion || version.equals("1.0") || version.equals("2.0") || version.equals("3.0");
+ } else if (feature.equalsIgnoreCase("XMLVersion")) {
+ return anyVersion || version.equals("1.0") || version.equals("1.1");
+ } else {
+ return false;
+ }
}
/**
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
index d6d412b..b2f16d1 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
@@ -48,7 +48,7 @@
private DOMImplementation domImplementation;
- DocumentImpl(DOMImplementationImpl impl, String namespaceURI,
+ public DocumentImpl(DOMImplementationImpl impl, String namespaceURI,
String qualifiedName, DocumentType doctype) {
super(null);
@@ -100,6 +100,8 @@
* @return The new node.
*/
Node cloneNode(Node node, boolean deep) throws DOMException {
+ // TODO: callback the UserDataHandler with a NODE_CLONED event
+
Node target;
switch (node.getNodeType()) {
@@ -279,6 +281,7 @@
}
public Node importNode(Node importedNode, boolean deep) throws DOMException {
+ // TODO: callback the UserDataHandler with a NODE_IMPORTED event
return cloneNode(importedNode, deep);
}
@@ -341,6 +344,7 @@
}
public Node adoptNode(Node source) throws DOMException {
+ // TODO: callback the UserDataHandler with a NODE_ADOPTED event
throw new UnsupportedOperationException(); // TODO
}
@@ -354,6 +358,7 @@
public Node renameNode(Node n, String namespaceURI, String qualifiedName)
throws DOMException {
+ // TODO: callback the UserDataHandler with a NODE_RENAMED event
throw new UnsupportedOperationException(); // TODO
}
}
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
index 230e444..df1383d 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
@@ -65,7 +65,7 @@
qualifiedName = qualifiedName.substring(p + 1);
}
- if (!document.isXMLIdentifier(qualifiedName)) {
+ if (!DocumentImpl.isXMLIdentifier(qualifiedName)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, qualifiedName);
}
@@ -82,11 +82,11 @@
String prefix = name.substring(0, p);
String localName = name.substring(p + 1);
- if (!document.isXMLIdentifier(prefix) || !document.isXMLIdentifier(localName)) {
+ if (!DocumentImpl.isXMLIdentifier(prefix) || !DocumentImpl.isXMLIdentifier(localName)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
}
} else {
- if (!document.isXMLIdentifier(name)) {
+ if (!DocumentImpl.isXMLIdentifier(name)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, name);
}
}
@@ -241,7 +241,9 @@
}
public String getTagName() {
- return (prefix != null ? prefix + ":" : "") + localName;
+ return prefix != null
+ ? prefix + ":" + localName
+ : localName;
}
public boolean hasAttribute(String name) {
@@ -281,7 +283,7 @@
throw new DOMException(DOMException.NOT_FOUND_ERR, null);
}
- attributes.remove(oldAttr);
+ attributes.remove(oldAttrImpl);
oldAttrImpl.ownerElement = null;
return oldAttrImpl;
@@ -362,21 +364,7 @@
@Override
public void setPrefix(String prefix) {
- if (!namespaceAware) {
- throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
- }
-
- if (prefix != null) {
- if (namespaceURI == null || !document.isXMLIdentifier(prefix)) {
- throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
- }
-
- if ("xml".equals(prefix) && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI)) {
- throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
- }
- }
-
- this.prefix = prefix;
+ this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
}
public class ElementAttrNamedNodeMapImpl implements NamedNodeMap {
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
index b752506..24ed102 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
@@ -16,24 +16,30 @@
package org.apache.harmony.xml.dom;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CharacterData;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
+import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.UserDataHandler;
+import java.util.ArrayList;
+import java.util.List;
+
/**
- * Provides a straightforward implementation of the corresponding W3C DOM
- * interface. The class is used internally only, thus only notable members that
- * are not in the original interface are documented (the W3C docs are quite
- * extensive). Hope that's ok.
- * <p>
- * Some of the fields may have package visibility, so other classes belonging to
- * the DOM implementation can easily access them while maintaining the DOM tree
- * structure.
- * <p>
- * This class represents a Node that has neither a parent nor children.
+ * A straightforward implementation of the corresponding W3C DOM node.
+ *
+ * <p>Some fields have package visibility so other classes can access them while
+ * maintaining the DOM structure.
+ *
+ * <p>This class represents a Node that has neither a parent nor children.
+ * Subclasses may have either.
+ *
+ * <p>Some code was adapted from Apache Xerces.
*/
public abstract class NodeImpl implements Node {
@@ -135,13 +141,64 @@
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
- public void setNodeValue(String nodeValue) throws DOMException {
+ public final void setNodeValue(String nodeValue) throws DOMException {
+ switch (getNodeType()) {
+ case CDATA_SECTION_NODE:
+ case COMMENT_NODE:
+ case TEXT_NODE:
+ ((CharacterData) this).setData(nodeValue);
+ return;
+
+ case PROCESSING_INSTRUCTION_NODE:
+ ((ProcessingInstruction) this).setData(nodeValue);
+ return;
+
+ case ATTRIBUTE_NODE:
+ ((Attr) this).setValue(nodeValue);
+ return;
+
+ case ELEMENT_NODE:
+ case ENTITY_REFERENCE_NODE:
+ case ENTITY_NODE:
+ case DOCUMENT_NODE:
+ case DOCUMENT_TYPE_NODE:
+ case DOCUMENT_FRAGMENT_NODE:
+ case NOTATION_NODE:
+ return; // do nothing!
+
+ default:
+ throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
+ "Unsupported node type " + getNodeType());
+ }
}
public void setPrefix(String prefix) throws DOMException {
}
/**
+ * Validates the element or attribute namespace prefix on this node.
+ *
+ * @param namespaceAware whether this node is namespace aware
+ * @param namespaceURI this node's namespace URI
+ */
+ protected String validatePrefix(String prefix, boolean namespaceAware, String namespaceURI) {
+ if (!namespaceAware) {
+ throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
+ }
+
+ if (prefix != null) {
+ if (namespaceURI == null
+ || !DocumentImpl.isXMLIdentifier(prefix)
+ || "xml".equals(prefix) && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI)
+ || "xmlns".equals(prefix) && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) {
+ throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
+ }
+ }
+
+ return prefix;
+ }
+
+ /**
* Checks whether a required string matches an actual string. This utility
* method is used for comparing namespaces and such. It takes into account
* null arguments and the "*" special case.
@@ -167,7 +224,6 @@
* account null arguments and the "*" special case.
*
* @param name The required name.
- * @param wildcard TODO
* @return True if and only if the actual name matches the required one.
*/
public boolean matchesName(String name, boolean wildcard) {
@@ -181,7 +237,6 @@
*
* @param namespaceURI The required namespace.
* @param localName The required local name.
- * @param wildcard TODO
* @return True if and only if the actual namespace and local name match
* the required pair of namespace and local name.
*/
@@ -190,7 +245,34 @@
}
public String getBaseURI() {
- return null; // TODO
+ /*
+ * TODO: implement. For reference, here's Xerces' behaviour:
+ *
+ * In all cases, the returned URI should be sanitized before it is
+ * returned. If the URI is malformed, null should be returned instead.
+ *
+ * For document nodes, this should return a member field that's
+ * initialized by the parser.
+ *
+ * For element nodes, this should first look for the xml:base attribute.
+ * if that exists and is absolute, it should be returned.
+ * if that exists and is relative, it should be resolved to the parent's base URI
+ * if it doesn't exist, the parent's baseURI should be returned
+ *
+ * For entity nodes, if a base URI exists that should be returned.
+ * Otherwise the document's base URI should be returned
+ *
+ * For entity references, if a base URI exists that should be returned
+ * otherwise it dereferences the entity (via the document) and uses the
+ * entity's base URI.
+ *
+ * For notations, it returns the base URI field.
+ *
+ * For processing instructions, it returns the parent's base URI.
+ *
+ * For all other node types, it returns null.
+ */
+ return null;
}
public short compareDocumentPosition(Node other)
@@ -209,32 +291,298 @@
}
}
- public void setTextContent(String textContent) throws DOMException {
- throw new UnsupportedOperationException(); // TODO
+ public final void setTextContent(String textContent) throws DOMException {
+ switch (getNodeType()) {
+ case DOCUMENT_TYPE_NODE:
+ case DOCUMENT_NODE:
+ return; // do nothing!
+
+ case ELEMENT_NODE:
+ case ENTITY_NODE:
+ case ENTITY_REFERENCE_NODE:
+ case DOCUMENT_FRAGMENT_NODE:
+ // remove all existing children
+ Node child;
+ while ((child = getFirstChild()) != null) {
+ removeChild(child);
+ }
+ // create a text node to hold the given content
+ if (textContent != null && textContent.length() != 0) {
+ appendChild(getOwnerDocument().createTextNode(textContent));
+ }
+ return;
+
+ case ATTRIBUTE_NODE:
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case COMMENT_NODE:
+ case NOTATION_NODE:
+ setNodeValue(textContent);
+ return;
+
+ default:
+ throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
+ "Unsupported node type " + getNodeType());
+ }
}
public boolean isSameNode(Node other) {
- throw new UnsupportedOperationException(); // TODO
+ return this == other;
}
- public String lookupPrefix(String namespaceURI) {
- throw new UnsupportedOperationException(); // TODO
+ /**
+ * Returns the element whose namespace definitions apply to this node. Use
+ * this element when mapping prefixes to URIs and vice versa.
+ */
+ private NodeImpl getNamespacingElement() {
+ switch (this.getNodeType()) {
+ case ELEMENT_NODE:
+ return this;
+
+ case DOCUMENT_NODE:
+ return (NodeImpl) ((Document) this).getDocumentElement();
+
+ case ENTITY_NODE:
+ case NOTATION_NODE:
+ case DOCUMENT_FRAGMENT_NODE:
+ case DOCUMENT_TYPE_NODE:
+ return null;
+
+ case ATTRIBUTE_NODE:
+ return (NodeImpl) ((Attr) this).getOwnerElement();
+
+ case TEXT_NODE:
+ case CDATA_SECTION_NODE:
+ case ENTITY_REFERENCE_NODE:
+ case PROCESSING_INSTRUCTION_NODE:
+ case COMMENT_NODE:
+ return getContainingElement();
+
+ default:
+ throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
+ "Unsupported node type " + getNodeType());
+ }
}
- public boolean isDefaultNamespace(String namespaceURI) {
- throw new UnsupportedOperationException(); // TODO
+ /**
+ * Returns the nearest ancestor element that contains this node.
+ */
+ private NodeImpl getContainingElement() {
+ for (Node p = getParentNode(); p != null; p = p.getParentNode()) {
+ if (p.getNodeType() == ELEMENT_NODE) {
+ return (NodeImpl) p;
+ }
+ }
+ return null;
}
- public String lookupNamespaceURI(String prefix) {
- throw new UnsupportedOperationException(); // TODO
+ public final String lookupPrefix(String namespaceURI) {
+ if (namespaceURI == null) {
+ return null;
+ }
+
+ // the XML specs define some prefixes (like "xml" and "xmlns") but this
+ // API is explicitly defined to ignore those.
+
+ NodeImpl target = getNamespacingElement();
+ for (NodeImpl node = target; node != null; node = node.getContainingElement()) {
+ // check this element's namespace first
+ if (namespaceURI.equals(node.getNamespaceURI())
+ && target.isPrefixMappedToUri(node.getPrefix(), namespaceURI)) {
+ return node.getPrefix();
+ }
+
+ // search this element for an attribute of this form:
+ // xmlns:foo="http://namespaceURI"
+ if (!node.hasAttributes()) {
+ continue;
+ }
+ NamedNodeMap attributes = node.getAttributes();
+ for (int i = 0, length = attributes.getLength(); i < length; i++) {
+ Node attr = attributes.item(i);
+ if (!"http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI())
+ || !"xmlns".equals(attr.getPrefix())
+ || !namespaceURI.equals(attr.getNodeValue())) {
+ continue;
+ }
+ if (target.isPrefixMappedToUri(attr.getLocalName(), namespaceURI)) {
+ return attr.getLocalName();
+ }
+ }
+ }
+
+ return null;
}
- public boolean isEqualNode(Node arg) {
- throw new UnsupportedOperationException(); // TODO
+ /**
+ * Returns true if the given prefix is mapped to the given URI on this
+ * element. Since child elements can redefine prefixes, this check is
+ * necessary: {@code
+ * <foo xmlns:a="http://good">
+ * <bar xmlns:a="http://evil">
+ * <a:baz />
+ * </bar>
+ * </foo>}
+ *
+ * @param prefix the prefix to find. Nullable.
+ * @param uri the URI to match. Non-null.
+ */
+ boolean isPrefixMappedToUri(String prefix, String uri) {
+ if (prefix == null) {
+ return false;
+ }
+
+ String actual = lookupNamespaceURI(prefix);
+ return uri.equals(actual);
}
- public Object getFeature(String feature, String version) {
- throw new UnsupportedOperationException(); // TODO
+ public final boolean isDefaultNamespace(String namespaceURI) {
+ String actual = lookupNamespaceURI(null); // null yields the default namespace
+ return namespaceURI == null
+ ? actual == null
+ : namespaceURI.equals(actual);
+ }
+
+ public final String lookupNamespaceURI(String prefix) {
+ NodeImpl target = getNamespacingElement();
+ for (NodeImpl node = target; node != null; node = node.getContainingElement()) {
+ // check this element's namespace first
+ String nodePrefix = node.getPrefix();
+ if (node.getNamespaceURI() != null) {
+ if (prefix == null // null => default prefix
+ ? nodePrefix == null
+ : prefix.equals(nodePrefix)) {
+ return node.getNamespaceURI();
+ }
+ }
+
+ // search this element for an attribute of the appropriate form.
+ // default namespace: xmlns="http://resultUri"
+ // non default: xmlns:specifiedPrefix="http://resultUri"
+ if (!node.hasAttributes()) {
+ continue;
+ }
+ NamedNodeMap attributes = node.getAttributes();
+ for (int i = 0, length = attributes.getLength(); i < length; i++) {
+ Node attr = attributes.item(i);
+ if (!"http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI())) {
+ continue;
+ }
+ if (prefix == null // null => default prefix
+ ? "xmlns".equals(attr.getNodeName())
+ : "xmlns".equals(attr.getPrefix()) && prefix.equals(attr.getLocalName())) {
+ String value = attr.getNodeValue();
+ return value.length() > 0 ? value : null;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a list of objects such that two nodes are equal if their lists
+ * are equal. Be careful: the lists may contain NamedNodeMaps and Nodes,
+ * neither of which override Object.equals(). Such values must be compared
+ * manually.
+ */
+ private static List<Object> createEqualityKey(Node node) {
+ List<Object> values = new ArrayList<Object>();
+ values.add(node.getNodeType());
+ values.add(node.getNodeName());
+ values.add(node.getLocalName());
+ values.add(node.getNamespaceURI());
+ values.add(node.getPrefix());
+ values.add(node.getNodeValue());
+ for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
+ values.add(child);
+ }
+
+ switch (node.getNodeType()) {
+ case DOCUMENT_TYPE_NODE:
+ DocumentTypeImpl doctype = (DocumentTypeImpl) node;
+ values.add(doctype.getPublicId());
+ values.add(doctype.getSystemId());
+ values.add(doctype.getInternalSubset());
+ values.add(doctype.getEntities());
+ values.add(doctype.getNotations());
+ break;
+
+ case ELEMENT_NODE:
+ Element element = (Element) node;
+ values.add(element.getAttributes());
+ break;
+ }
+
+ return values;
+ }
+
+ public final boolean isEqualNode(Node arg) {
+ if (arg == this) {
+ return true;
+ }
+
+ List<Object> listA = createEqualityKey(this);
+ List<Object> listB = createEqualityKey(arg);
+
+ if (listA.size() != listB.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < listA.size(); i++) {
+ Object a = listA.get(i);
+ Object b = listB.get(i);
+
+ if (a == b) {
+ continue;
+
+ } else if (a == null || b == null) {
+ return false;
+
+ } else if (a instanceof String || a instanceof Short) {
+ if (!a.equals(b)) {
+ return false;
+ }
+
+ } else if (a instanceof NamedNodeMap) {
+ if (!(b instanceof NamedNodeMap)
+ || !namedNodeMapsEqual((NamedNodeMap) a, (NamedNodeMap) b)) {
+ return false;
+ }
+
+ } else if (a instanceof Node) {
+ if (!(b instanceof Node)
+ || !((Node) a).isEqualNode((Node) b)) {
+ return false;
+ }
+
+ } else {
+ throw new AssertionError(); // unexpected type
+ }
+ }
+
+ return true;
+ }
+
+ private boolean namedNodeMapsEqual(NamedNodeMap a, NamedNodeMap b) {
+ if (a.getLength() != b.getLength()) {
+ return false;
+ }
+ for (int i = 0; i < a.getLength(); i++) {
+ Node aNode = a.item(i);
+ Node bNode = aNode.getLocalName() == null
+ ? b.getNamedItem(aNode.getNodeName())
+ : b.getNamedItemNS(aNode.getNamespaceURI(), aNode.getLocalName());
+ if (bNode == null || !aNode.isEqualNode(bNode)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public final Object getFeature(String feature, String version) {
+ return isSupported(feature, version) ? this : null;
}
public Object setUserData(String key, Object data,
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
index 3905865..3840ef4 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/TextImpl.java
@@ -32,7 +32,7 @@
*/
public class TextImpl extends CharacterDataImpl implements Text {
- TextImpl(DocumentImpl document, String data) {
+ public TextImpl(DocumentImpl document, String data) {
super(document, data);
}
@@ -46,12 +46,7 @@
return Node.TEXT_NODE;
}
- @Override
- public String getNodeValue() {
- return getData();
- }
-
- public Text splitText(int offset) throws DOMException {
+ public final Text splitText(int offset) throws DOMException {
Text newText = getOwnerDocument().createTextNode(
substringData(offset, getLength() - offset));
deleteData(0, offset);
@@ -66,15 +61,83 @@
return this;
}
- public boolean isElementContentWhitespace() {
- throw new UnsupportedOperationException(); // TODO
+ public final boolean isElementContentWhitespace() {
+ // Undefined because we don't validate. Whether whitespace characters
+ // constitute "element content whitespace" is defined by the containing
+ // element's declaration (DTD) and we don't parse that.
+ // TODO: wire this up when we support document validation
+ return false;
}
- public String getWholeText() {
- throw new UnsupportedOperationException(); // TODO
+ public final String getWholeText() {
+ // TODO: support entity references. This code should expand through
+ // the child elements of entity references.
+ // http://code.google.com/p/android/issues/detail?id=6807
+
+ StringBuilder result = new StringBuilder();
+ for (TextImpl n = firstTextNodeInCurrentRun(); n != null; n = n.nextTextNode()) {
+ n.appendDataTo(result);
+ }
+ return result.toString();
}
- public Text replaceWholeText(String content) throws DOMException {
- throw new UnsupportedOperationException(); // TODO
+ public final Text replaceWholeText(String content) throws DOMException {
+ // TODO: support entity references. This code should expand and replace
+ // the child elements of entity references.
+ // http://code.google.com/p/android/issues/detail?id=6807
+
+ Node parent = getParentNode();
+ Text result = null;
+
+ // delete all nodes in the current run of text...
+ for (TextImpl n = firstTextNodeInCurrentRun(); n != null; ) {
+
+ // ...except the current node if we have content for it
+ if (n == this && content != null && content.length() > 0) {
+ setData(content);
+ result = this;
+ n = n.nextTextNode();
+
+ } else {
+ Node toRemove = n; // because removeChild() detaches siblings
+ n = n.nextTextNode();
+ parent.removeChild(toRemove);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the first text or CDATA node in the current sequence of text and
+ * CDATA nodes.
+ */
+ private TextImpl firstTextNodeInCurrentRun() {
+ TextImpl firstTextInCurrentRun = this;
+ for (Node p = getPreviousSibling(); p != null; p = p.getPreviousSibling()) {
+ short nodeType = p.getNodeType();
+ if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
+ firstTextInCurrentRun = (TextImpl) p;
+ } else {
+ break;
+ }
+ }
+ return firstTextInCurrentRun;
+ }
+
+ /**
+ * Returns the next sibling node if it exists and it is text or CDATA.
+ * Otherwise returns null.
+ */
+ private TextImpl nextTextNode() {
+ Node nextSibling = getNextSibling();
+ if (nextSibling == null) {
+ return null;
+ }
+
+ short nodeType = nextSibling.getNodeType();
+ return nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE
+ ? (TextImpl) nextSibling
+ : null;
}
}
diff --git a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
index 5a3c48c..ca2ff98 100644
--- a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
@@ -23,10 +23,14 @@
import javax.xml.parsers.DocumentBuilder;
+import org.apache.harmony.xml.dom.CDATASectionImpl;
+import org.apache.harmony.xml.dom.DocumentImpl;
+import org.apache.harmony.xml.dom.TextImpl;
import org.kxml2.io.KXmlParser;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
@@ -49,7 +53,7 @@
*/
class DocumentBuilderImpl extends DocumentBuilder {
- private static DOMImplementation dom = DOMImplementationImpl.getInstance();
+ private static DOMImplementationImpl dom = DOMImplementationImpl.getInstance();
private boolean coalescing;
@@ -72,25 +76,6 @@
return dom;
}
- /**
- * Reflects whether this DocumentBuilder is configured to ignore comments.
- *
- * @return True if and only if comments are ignored.
- */
- public boolean isIgnoringComments() {
- return ignoreComments;
- }
-
- /**
- * Reflects whether this DocumentBuilder is configured to ignore element
- * content whitespace.
- *
- * @return True if and only if whitespace element content is ignored.
- */
- public boolean isIgnoringElementContentWhitespace() {
- return ignoreElementContentWhitespace;
- }
-
@Override
public boolean isNamespaceAware() {
return namespaceAware;
@@ -112,11 +97,14 @@
throw new IllegalArgumentException();
}
- Document document = newDocument();
+ String namespaceURI = null;
+ String qualifiedName = null;
+ DocumentType doctype = null;
+ DocumentImpl document = new DocumentImpl(dom, namespaceURI, qualifiedName, doctype);
try {
- XmlPullParser parser = new KXmlParser();
-
+ KXmlParser parser = new KXmlParser();
+ parser.keepNamespaceAttributes();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
namespaceAware);
@@ -189,7 +177,7 @@
* @throws XmlPullParserException If a parsing error occurs.
* @throws IOException If a general IO error occurs.
*/
- private void parse(XmlPullParser parser, Document document, Node node,
+ private void parse(XmlPullParser parser, DocumentImpl document, Node node,
int endToken) throws XmlPullParserException, IOException {
int token = parser.getEventType();
@@ -271,7 +259,7 @@
* whitespace at all.
*/
if (!ignoreElementContentWhitespace) {
- appendText(document, node, true, parser.getText());
+ appendText(document, node, token, parser.getText());
}
} else if (token == XmlPullParser.TEXT || token == XmlPullParser.CDSECT) {
/*
@@ -279,7 +267,7 @@
* That's the easiest case. We simply take it and create a new text node,
* or merge with an adjacent text node.
*/
- appendText(document, node, token == XmlPullParser.TEXT, parser.getText());
+ appendText(document, node, token, parser.getText());
} else if (token == XmlPullParser.ENTITY_REF) {
/*
* Found an entity reference. If an entity resolver is
@@ -294,7 +282,7 @@
String replacement = resolveStandardEntity(entity);
if (replacement != null) {
- appendText(document, node, true, replacement);
+ appendText(document, node, token, replacement);
} else {
node.appendChild(document.createEntityReference(entity));
}
@@ -380,17 +368,17 @@
}
/**
- * @param isText true for a normal TextNode, false for a CDATA section.
- * (If we're not coalescing, it matters which kind of node we put into the DOM.)
+ * @param token the XML pull parser token type, such as XmlPullParser.CDSECT
+ * or XmlPullParser.ENTITY_REF.
*/
- private void appendText(Document document, Node node, boolean isText, String text) {
+ private void appendText(DocumentImpl document, Node parent, int token, String text) {
// Ignore empty runs.
if (text.length() == 0) {
return;
}
// Merge with any previous text node if possible.
if (coalescing) {
- Node lastChild = node.getLastChild();
+ Node lastChild = parent.getLastChild();
if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
Text textNode = (Text) lastChild;
textNode.setData(textNode.getNodeValue() + text);
@@ -398,11 +386,9 @@
}
}
// Okay, we really do need a new text node
- if (isText) {
- node.appendChild(document.createTextNode(text));
- } else {
- node.appendChild(document.createCDATASection(text));
- }
+ parent.appendChild(token == XmlPullParser.CDSECT
+ ? new CDATASectionImpl(document, text)
+ : new TextImpl(document, text));
}
@Override
diff --git a/xml/src/main/java/org/kxml2/io/KXmlParser.java b/xml/src/main/java/org/kxml2/io/KXmlParser.java
index c4d8f3d..99eb03b 100644
--- a/xml/src/main/java/org/kxml2/io/KXmlParser.java
+++ b/xml/src/main/java/org/kxml2/io/KXmlParser.java
@@ -45,6 +45,7 @@
private boolean processNsp;
private boolean relaxed;
+ private boolean keepNamespaceAttributes; // android-added
private Hashtable entityMap;
private int depth;
private String[] elementStack = new String[16];
@@ -80,6 +81,14 @@
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
+ * i + 2 = attribute qualified name (may contain ":", as in "html:h1")
+ * i + 3 = attribute value
+ */
private String[] attributes = new String[16];
// private int stackMismatch = 0;
private String error;
@@ -100,6 +109,19 @@
new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128];
}
+ // 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.
+ */
+ 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/"))
return false;
@@ -148,14 +170,23 @@
//System.out.println (prefixMap);
- System.arraycopy(
- attributes,
- i + 4,
- attributes,
- i,
- ((--attributeCount) << 2) - i);
+ // BEGIN android-changed
+ if (keepNamespaceAttributes) {
+ // explicitly set the namespace for unprefixed attributes
+ // such as xmlns="http://foo"
+ attributes[i] = "http://www.w3.org/2000/xmlns/";
+ any = true;
+ } else {
+ System.arraycopy(
+ attributes,
+ i + 4,
+ attributes,
+ i,
+ ((--attributeCount) << 2) - i);
- i -= 4;
+ i -= 4;
+ }
+ // END android-changed
}
}
diff --git a/xml/src/main/java/org/w3c/dom/Attr.java b/xml/src/main/java/org/w3c/dom/Attr.java
index d9ed6ff..bd7267b 100644
--- a/xml/src/main/java/org/w3c/dom/Attr.java
+++ b/xml/src/main/java/org/w3c/dom/Attr.java
@@ -176,7 +176,7 @@
/**
* On retrieval, the value of the attribute is returned as a string.
* Character and general entity references are replaced with their
- * values. See also the method <code>getAttribute</code> on the
+ * values. See also the method <code>getAttribute</code> on the
* <code>Element</code> interface.
* <br>On setting, this creates a <code>Text</code> node with the unparsed
* contents of the string, i.e. any characters that an XML processor
diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
index 02b6d80..ea7abed 100644
--- a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
+++ b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
@@ -34,6 +34,7 @@
import tests.api.org.xml.sax.support.MethodLogger;
import tests.api.org.xml.sax.support.MockHandler;
import tests.api.org.xml.sax.support.MockResolver;
+import tests.support.resource.Support_Resources;
import tests.util.TestEnvironment;
import javax.xml.parsers.DocumentBuilder;
@@ -41,8 +42,6 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -272,7 +271,7 @@
args = {java.io.File.class}
)
public void testGetBaseURI() throws IOException, SAXException {
- File f = resourceToTmpFile("/simple.xml");
+ File f = Support_Resources.resourceToTempFile("/simple.xml");
Document d = db.parse(f);
assertTrue(d.getDocumentElement().getBaseURI().startsWith("file://"));
}
@@ -291,7 +290,7 @@
args = {java.io.File.class}
)
public void test_parseLjava_io_File() throws IOException {
- File f = resourceToTmpFile("/simple.xml");
+ File f = Support_Resources.resourceToTempFile("/simple.xml");
// case 1: Trivial use.
try {
@@ -333,7 +332,7 @@
}
// case 4: Try to parse incorrect xml file
- f = resourceToTmpFile("/wrong.xml");
+ f = Support_Resources.resourceToTempFile("/wrong.xml");
try {
db.parse(f);
fail("Expected SAXException was not thrown");
@@ -344,22 +343,6 @@
}
}
- private File resourceToTmpFile(String path) throws IOException,
- FileNotFoundException {
- File f = File.createTempFile("out", ".xml");
- f.deleteOnExit();
- FileOutputStream out = new FileOutputStream(f);
-
- InputStream xml = getClass().getResourceAsStream(path);
- while (xml.available() > 0) {
- out.write(xml.read());
- }
- out.flush();
- out.close();
- xml.close();
- return f;
- }
-
/**
* @tests javax.xml.parsers.DocumentBuilder#parse(java.io.InputStream)
* Case 1: Try to parse correct xml document.
@@ -579,24 +562,18 @@
method = "parse",
args = {java.lang.String.class}
)
- public void test_parseLjava_lang_String() {
+ public void test_parseLjava_lang_String() throws Exception {
// case 1: Trivial use.
File f = new File(getClass().getResource("/simple.xml").getFile());
- try {
- Document d = db.parse(f.getAbsolutePath());
- assertNotNull(d);
+ Document d = db.parse(f.getAbsolutePath());
+ assertNotNull(d);
// TBD getXmlEncoding() is not supported
// assertEquals("ISO-8859-1", d.getXmlEncoding());
- assertEquals(2, d.getChildNodes().getLength());
- assertEquals("#comment",
- d.getChildNodes().item(0).getNodeName());
- assertEquals("breakfast_menu",
- d.getChildNodes().item(1).getNodeName());
- } catch (IOException ioe) {
- fail("Unexpected IOException " + ioe.toString());
- } catch (SAXException sax) {
- fail("Unexpected SAXException " + sax.toString());
- }
+ assertEquals(2, d.getChildNodes().getLength());
+ assertEquals("#comment",
+ d.getChildNodes().item(0).getNodeName());
+ assertEquals("breakfast_menu",
+ d.getChildNodes().item(1).getNodeName());
// case 2: Try to call parse with null argument
try {
@@ -604,10 +581,6 @@
fail("Expected IllegalArgumentException was not thrown");
} catch (IllegalArgumentException iae) {
// expected
- } catch (IOException ioe) {
- fail("Unexpected IOException " + ioe.toString());
- } catch (SAXException sax) {
- fail("Unexpected SAXException " + sax.toString());
}
// case 3: Try to parse a non-existent uri
@@ -616,8 +589,6 @@
fail("Expected IOException was not thrown");
} catch (IOException ioe) {
// expected
- } catch (SAXException sax) {
- fail("Unexpected SAXException " + sax.toString());
}
// case 4: Try to parse incorrect xml file
@@ -625,8 +596,6 @@
f = new File(getClass().getResource("/wrong.xml").getFile());
db.parse(f.getAbsolutePath());
fail("Expected SAXException was not thrown");
- } catch (IOException ioe) {
- fail("Unexpected IOException " + ioe.toString());
} catch (SAXException sax) {
// expected
}
diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
index ca7cf71..e6d6481 100644
--- a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
+++ b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
@@ -677,8 +677,7 @@
MyDefaultHandler dh = new MyDefaultHandler();
InputStream is = new FileInputStream(list_wf[i]);
parser.parse(is, dh, SAXParserTestSupport.XML_SYSTEM_ID);
- assertTrue(SAXParserTestSupport.equalsMaps(hm,
- dh.createData()));
+ assertEquals(hm, dh.createData());
} catch (IOException ioe) {
fail("Unexpected IOException " + ioe.toString());
} catch (SAXException sax) {
diff --git a/xml/src/test/java/tests/xml/AllTests.java b/xml/src/test/java/tests/xml/AllTests.java
index 45ca18e..597e35e 100644
--- a/xml/src/test/java/tests/xml/AllTests.java
+++ b/xml/src/test/java/tests/xml/AllTests.java
@@ -24,9 +24,10 @@
public static Test suite() {
TestSuite suite = tests.TestSuiteFactory.createTestSuite();
+ suite.addTestSuite(DomTest.class);
suite.addTestSuite(SimpleParserTest.class);
suite.addTestSuite(SimpleBuilderTest.class);
- suite.addTestSuite(NodeTests.class);
+ suite.addTestSuite(NodeTest.class);
//suite.addTest(tests.org.w3c.dom.AllTests.suite());
suite.addTest(tests.api.javax.xml.parsers.AllTests.suite());
diff --git a/xml/src/test/java/tests/xml/DomTest.java b/xml/src/test/java/tests/xml/DomTest.java
new file mode 100644
index 0000000..69e8b37
--- /dev/null
+++ b/xml/src/test/java/tests/xml/DomTest.java
@@ -0,0 +1,748 @@
+/*
+ * 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.xml;
+
+import junit.framework.TestCase;
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Element;
+import org.w3c.dom.Entity;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.Node;
+import org.w3c.dom.Notation;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Construct a DOM and then interrogate it.
+ */
+public class DomTest extends TestCase {
+
+ private Transformer transformer;
+ private DocumentBuilder builder;
+ private DOMImplementation domImplementation;
+
+ private final String xml
+ = "<!DOCTYPE menu ["
+ + " <!ENTITY sp \"Maple Syrup\">"
+ + " <!NOTATION png SYSTEM \"image/png\">"
+ + "]>"
+ + "<menu>\n"
+ + " <item xmlns=\"http://food\" xmlns:a=\"http://addons\">\n"
+ + " <name a:standard=\"strawberry\" deluxe=\"&sp;\">Waffles</name>\n"
+ + " <description xmlns=\"http://marketing\">Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)</description>\n"
+ + " <a:option>Whipped Cream</a:option>\n"
+ + " <a:option>&sp;</a:option>\n"
+ + " <?wafflemaker square shape?>\n"
+ + " <nutrition>\n"
+ + " <a:vitamins xmlns:a=\"http://usda\">\n"
+ + " <!-- add other vitamins? --> \n"
+ + " <a:vitaminc>60%</a:vitaminc>\n"
+ + " </a:vitamins>\n"
+ + " </nutrition>\n"
+ + " </item>\n"
+ + "</menu>";
+
+ private Document document;
+ private DocumentType doctype;
+ private Entity sp;
+ private Notation png;
+ private Element menu;
+ private Element item;
+ private Attr itemXmlns;
+ private Attr itemXmlnsA;
+ private Element name;
+ private Attr standard;
+ private Attr deluxe;
+ private Element description;
+ private Text descriptionText1;
+ private CDATASection descriptionText2;
+ private Text descriptionText3;
+ private Element option1;
+ private Element option2;
+ private Node option2Reference; // resolved to Text on RI, an EntityReference on Dalvik
+ private ProcessingInstruction wafflemaker;
+ private Element nutrition;
+ private Element vitamins;
+ private Attr vitaminsXmlnsA;
+ private Comment comment;
+ private Element vitaminc;
+ private Text vitamincText;
+ private List<Node> allNodes;
+
+ @Override protected void setUp() throws Exception {
+ transformer = TransformerFactory.newInstance().newTransformer();
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ builder = factory.newDocumentBuilder();
+ domImplementation = builder.getDOMImplementation();
+ document = builder.parse(new InputSource(new StringReader(xml)));
+
+ // doctype nodes
+ doctype = document.getDoctype();
+ if (doctype.getEntities() != null) {
+ sp = (Entity) doctype.getEntities().item(0);
+ }
+ if (doctype.getNotations() != null) {
+ png = (Notation) doctype.getNotations().item(0);
+ }
+
+ // document nodes
+ menu = document.getDocumentElement();
+ item = (Element) menu.getChildNodes().item(1);
+ itemXmlns = item.getAttributeNode("xmlns");
+ itemXmlnsA = item.getAttributeNode("xmlns:a");
+ name = (Element) item.getChildNodes().item(1);
+ standard = name.getAttributeNode("a:standard");
+ deluxe = name.getAttributeNode("deluxe");
+ description = (Element) item.getChildNodes().item(3);
+ descriptionText1 = (Text) description.getChildNodes().item(0);
+ descriptionText2 = (CDATASection) description.getChildNodes().item(1);
+ descriptionText3 = (Text) description.getChildNodes().item(2);
+ option1 = (Element) item.getChildNodes().item(5);
+ option2 = (Element) item.getChildNodes().item(7);
+ option2Reference = option2.getChildNodes().item(0);
+ wafflemaker = (ProcessingInstruction) item.getChildNodes().item(9);
+ nutrition = (Element) item.getChildNodes().item(11);
+ vitamins = (Element) nutrition.getChildNodes().item(1);
+ vitaminsXmlnsA = vitamins.getAttributeNode("xmlns:a");
+ comment = (Comment) vitamins.getChildNodes().item(1);
+ vitaminc = (Element) vitamins.getChildNodes().item(3);
+ vitamincText = (Text) vitaminc.getChildNodes().item(0);
+
+ allNodes = new ArrayList<Node>();
+
+ if (sp != null) {
+ allNodes.add(sp);
+ }
+ if (png != null) {
+ allNodes.add(png);
+ }
+
+ allNodes.addAll(Arrays.asList(document, doctype, menu, item, itemXmlns,
+ itemXmlnsA, name, standard, deluxe, description,
+ descriptionText1, descriptionText2, descriptionText3, option1,
+ option2, option2Reference, wafflemaker, nutrition, vitamins,
+ vitaminsXmlnsA, comment, vitaminc, vitamincText));
+ }
+
+ /**
+ * Android's parsed DOM doesn't include entity declarations. These nodes will
+ * only be tested for implementations that support them.
+ */
+ public void testEntityDeclarations() {
+ assertNotNull("This implementation does not parse entity declarations", sp);
+ }
+
+ /**
+ * Android's parsed DOM doesn't include notations. These nodes will only be
+ * tested for implementations that support them.
+ */
+ public void testNotations() {
+ assertNotNull("This implementation does not parse notations", png);
+ }
+
+ public void testLookupNamespaceURIByPrefix() {
+ assertEquals(null, doctype.lookupNamespaceURI("a"));
+ if (sp != null) {
+ assertEquals(null, sp.lookupNamespaceURI("a"));
+ }
+ if (png != null) {
+ assertEquals(null, png.lookupNamespaceURI("a"));
+ }
+ assertEquals(null, document.lookupNamespaceURI("a"));
+ assertEquals(null, menu.lookupNamespaceURI("a"));
+ assertEquals("http://addons", item.lookupNamespaceURI("a"));
+ assertEquals("http://addons", itemXmlns.lookupNamespaceURI("a"));
+ assertEquals("http://addons", itemXmlnsA.lookupNamespaceURI("a"));
+ assertEquals("http://addons", name.lookupNamespaceURI("a"));
+ assertEquals("http://addons", standard.lookupNamespaceURI("a"));
+ assertEquals("http://addons", deluxe.lookupNamespaceURI("a"));
+ assertEquals("http://addons", description.lookupNamespaceURI("a"));
+ assertEquals("http://addons", descriptionText1.lookupNamespaceURI("a"));
+ assertEquals("http://addons", descriptionText2.lookupNamespaceURI("a"));
+ assertEquals("http://addons", descriptionText3.lookupNamespaceURI("a"));
+ assertEquals("http://addons", option1.lookupNamespaceURI("a"));
+ assertEquals("http://addons", option2.lookupNamespaceURI("a"));
+ assertEquals("http://addons", option2Reference.lookupNamespaceURI("a"));
+ assertEquals("http://addons", wafflemaker.lookupNamespaceURI("a"));
+ assertEquals("http://addons", nutrition.lookupNamespaceURI("a"));
+ assertEquals("http://usda", vitamins.lookupNamespaceURI("a"));
+ assertEquals("http://usda", vitaminsXmlnsA.lookupNamespaceURI("a"));
+ assertEquals("http://usda", comment.lookupNamespaceURI("a"));
+ assertEquals("http://usda", vitaminc.lookupNamespaceURI("a"));
+ assertEquals("http://usda", vitamincText.lookupNamespaceURI("a"));
+ }
+
+ public void testLookupNamespaceURIWithNullPrefix() {
+ assertEquals(null, document.lookupNamespaceURI(null));
+ assertEquals(null, doctype.lookupNamespaceURI(null));
+ if (sp != null) {
+ assertEquals(null, sp.lookupNamespaceURI(null));
+ }
+ if (png != null) {
+ assertEquals(null, png.lookupNamespaceURI(null));
+ }
+ assertEquals(null, menu.lookupNamespaceURI(null));
+ assertEquals("http://food", item.lookupNamespaceURI(null));
+ assertEquals("http://food", itemXmlns.lookupNamespaceURI(null));
+ assertEquals("http://food", itemXmlnsA.lookupNamespaceURI(null));
+ assertEquals("http://food", name.lookupNamespaceURI(null));
+ assertEquals("http://food", standard.lookupNamespaceURI(null));
+ assertEquals("http://food", deluxe.lookupNamespaceURI(null));
+ assertEquals("http://marketing", description.lookupNamespaceURI(null));
+ assertEquals("http://marketing", descriptionText1.lookupNamespaceURI(null));
+ assertEquals("http://marketing", descriptionText2.lookupNamespaceURI(null));
+ assertEquals("http://marketing", descriptionText3.lookupNamespaceURI(null));
+ assertEquals("http://food", option1.lookupNamespaceURI(null));
+ assertEquals("http://food", option2.lookupNamespaceURI(null));
+ assertEquals("http://food", option2Reference.lookupNamespaceURI(null));
+ assertEquals("http://food", wafflemaker.lookupNamespaceURI(null));
+ assertEquals("http://food", nutrition.lookupNamespaceURI(null));
+ assertEquals("http://food", vitamins.lookupNamespaceURI(null));
+ assertEquals("http://food", vitaminsXmlnsA.lookupNamespaceURI(null));
+ assertEquals("http://food", comment.lookupNamespaceURI(null));
+ assertEquals("http://food", vitaminc.lookupNamespaceURI(null));
+ assertEquals("http://food", vitamincText.lookupNamespaceURI(null));
+ }
+
+ public void testLookupNamespaceURIWithXmlnsPrefix() {
+ for (Node node : allNodes) {
+ assertEquals(null, node.lookupNamespaceURI("xmlns"));
+ }
+ }
+
+ public void testLookupPrefixWithShadowedUri() {
+ assertEquals(null, document.lookupPrefix("http://addons"));
+ assertEquals(null, doctype.lookupPrefix("http://addons"));
+ if (sp != null) {
+ assertEquals(null, sp.lookupPrefix("http://addons"));
+ }
+ if (png != null) {
+ assertEquals(null, png.lookupPrefix("http://addons"));
+ }
+ assertEquals(null, menu.lookupPrefix("http://addons"));
+ assertEquals("a", item.lookupPrefix("http://addons"));
+ assertEquals("a", itemXmlns.lookupPrefix("http://addons"));
+ assertEquals("a", itemXmlnsA.lookupPrefix("http://addons"));
+ assertEquals("a", name.lookupPrefix("http://addons"));
+ assertEquals("a", standard.lookupPrefix("http://addons"));
+ assertEquals("a", deluxe.lookupPrefix("http://addons"));
+ assertEquals("a", description.lookupPrefix("http://addons"));
+ assertEquals("a", descriptionText1.lookupPrefix("http://addons"));
+ assertEquals("a", descriptionText2.lookupPrefix("http://addons"));
+ assertEquals("a", descriptionText3.lookupPrefix("http://addons"));
+ assertEquals("a", option1.lookupPrefix("http://addons"));
+ assertEquals("a", option2.lookupPrefix("http://addons"));
+ assertEquals("a", option2Reference.lookupPrefix("http://addons"));
+ assertEquals("a", wafflemaker.lookupPrefix("http://addons"));
+ assertEquals("a", nutrition.lookupPrefix("http://addons"));
+ assertEquals(null, vitamins.lookupPrefix("http://addons"));
+ assertEquals(null, vitaminsXmlnsA.lookupPrefix("http://addons"));
+ assertEquals(null, comment.lookupPrefix("http://addons"));
+ assertEquals(null, vitaminc.lookupPrefix("http://addons"));
+ assertEquals(null, vitamincText.lookupPrefix("http://addons"));
+ }
+
+ public void testLookupPrefixWithUnusedUri() {
+ for (Node node : allNodes) {
+ assertEquals(null, node.lookupPrefix("http://unused"));
+ }
+ }
+
+ public void testLookupPrefixWithNullUri() {
+ for (Node node : allNodes) {
+ assertEquals(null, node.lookupPrefix(null));
+ }
+ }
+
+ public void testLookupPrefixWithShadowingUri() {
+ assertEquals(null, document.lookupPrefix("http://usda"));
+ assertEquals(null, doctype.lookupPrefix("http://usda"));
+ if (sp != null) {
+ assertEquals(null, sp.lookupPrefix("http://usda"));
+ }
+ if (png != null) {
+ assertEquals(null, png.lookupPrefix("http://usda"));
+ }
+ assertEquals(null, menu.lookupPrefix("http://usda"));
+ assertEquals(null, item.lookupPrefix("http://usda"));
+ assertEquals(null, itemXmlns.lookupPrefix("http://usda"));
+ assertEquals(null, itemXmlnsA.lookupPrefix("http://usda"));
+ assertEquals(null, name.lookupPrefix("http://usda"));
+ assertEquals(null, standard.lookupPrefix("http://usda"));
+ assertEquals(null, deluxe.lookupPrefix("http://usda"));
+ assertEquals(null, description.lookupPrefix("http://usda"));
+ assertEquals(null, descriptionText1.lookupPrefix("http://usda"));
+ assertEquals(null, descriptionText2.lookupPrefix("http://usda"));
+ assertEquals(null, descriptionText3.lookupPrefix("http://usda"));
+ assertEquals(null, option1.lookupPrefix("http://usda"));
+ assertEquals(null, option2.lookupPrefix("http://usda"));
+ assertEquals(null, option2Reference.lookupPrefix("http://usda"));
+ assertEquals(null, wafflemaker.lookupPrefix("http://usda"));
+ assertEquals(null, nutrition.lookupPrefix("http://usda"));
+ assertEquals("a", vitamins.lookupPrefix("http://usda"));
+ assertEquals("a", vitaminsXmlnsA.lookupPrefix("http://usda"));
+ assertEquals("a", comment.lookupPrefix("http://usda"));
+ assertEquals("a", vitaminc.lookupPrefix("http://usda"));
+ assertEquals("a", vitamincText.lookupPrefix("http://usda"));
+ }
+
+ public void testIsDefaultNamespace() {
+ assertFalse(document.isDefaultNamespace("http://food"));
+ assertFalse(doctype.isDefaultNamespace("http://food"));
+ if (sp != null) {
+ assertFalse(sp.isDefaultNamespace("http://food"));
+ }
+ if (png != null) {
+ assertFalse(png.isDefaultNamespace("http://food"));
+ }
+ assertFalse(menu.isDefaultNamespace("http://food"));
+ assertTrue(item.isDefaultNamespace("http://food"));
+ assertTrue(itemXmlns.isDefaultNamespace("http://food"));
+ assertTrue(itemXmlnsA.isDefaultNamespace("http://food"));
+ assertTrue(name.isDefaultNamespace("http://food"));
+ assertTrue(standard.isDefaultNamespace("http://food"));
+ assertTrue(deluxe.isDefaultNamespace("http://food"));
+ assertFalse(description.isDefaultNamespace("http://food"));
+ assertFalse(descriptionText1.isDefaultNamespace("http://food"));
+ assertFalse(descriptionText2.isDefaultNamespace("http://food"));
+ assertFalse(descriptionText3.isDefaultNamespace("http://food"));
+ assertTrue(option1.isDefaultNamespace("http://food"));
+ assertTrue(option2.isDefaultNamespace("http://food"));
+ assertTrue(option2Reference.isDefaultNamespace("http://food"));
+ assertTrue(wafflemaker.isDefaultNamespace("http://food"));
+ assertTrue(nutrition.isDefaultNamespace("http://food"));
+ assertTrue(vitamins.isDefaultNamespace("http://food"));
+ assertTrue(vitaminsXmlnsA.isDefaultNamespace("http://food"));
+ assertTrue(comment.isDefaultNamespace("http://food"));
+ assertTrue(vitaminc.isDefaultNamespace("http://food"));
+ assertTrue(vitamincText.isDefaultNamespace("http://food"));
+ }
+
+ /**
+ * Xerces fails this test. It returns false always for entity, notation,
+ * document fragment and document type nodes. This contradicts its own
+ * behaviour on lookupNamespaceURI(null).
+ */
+ public void testIsDefaultNamespaceNull_XercesBugs() {
+ String message = "isDefaultNamespace() should be consistent with lookupNamespaceURI(null)";
+ assertTrue(message, doctype.isDefaultNamespace(null));
+ if (sp != null) {
+ assertTrue(message, sp.isDefaultNamespace(null));
+ }
+ if (png != null) {
+ assertTrue(message, png.isDefaultNamespace(null));
+ }
+ }
+
+ public void testIsDefaultNamespaceNull() {
+ assertTrue(document.isDefaultNamespace(null));
+ assertTrue(menu.isDefaultNamespace(null));
+ assertFalse(item.isDefaultNamespace(null));
+ assertFalse(itemXmlns.isDefaultNamespace(null));
+ assertFalse(itemXmlnsA.isDefaultNamespace(null));
+ assertFalse(name.isDefaultNamespace(null));
+ assertFalse(standard.isDefaultNamespace(null));
+ assertFalse(deluxe.isDefaultNamespace(null));
+ assertFalse(description.isDefaultNamespace(null));
+ assertFalse(descriptionText1.isDefaultNamespace(null));
+ assertFalse(descriptionText2.isDefaultNamespace(null));
+ assertFalse(descriptionText3.isDefaultNamespace(null));
+ assertFalse(option1.isDefaultNamespace(null));
+ assertFalse(option2.isDefaultNamespace(null));
+ assertFalse(option2Reference.isDefaultNamespace(null));
+ assertFalse(wafflemaker.isDefaultNamespace(null));
+ assertFalse(nutrition.isDefaultNamespace(null));
+ assertFalse(vitamins.isDefaultNamespace(null));
+ assertFalse(vitaminsXmlnsA.isDefaultNamespace(null));
+ assertFalse(comment.isDefaultNamespace(null));
+ assertFalse(vitaminc.isDefaultNamespace(null));
+ assertFalse(vitamincText.isDefaultNamespace(null));
+ }
+
+ public void testDoctypeSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ doctype.setTextContent("foobar"); // strangely, this is specified to no-op
+ assertEquals(original, domToString(document));
+ }
+
+ public void testDocumentSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ document.setTextContent("foobar"); // strangely, this is specified to no-op
+ assertEquals(original, domToString(document));
+ }
+
+ public void testElementSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ nutrition.setTextContent("foobar");
+ String expected = original.replaceFirst(
+ "(?s)<nutrition>.*</nutrition>", "<nutrition>foobar</nutrition>");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testEntitySetTextContent() throws TransformerException {
+ if (sp == null) {
+ return;
+ }
+ try {
+ sp.setTextContent("foobar");
+ fail(); // is this implementation-specific behaviour?
+ } catch (DOMException e) {
+ }
+ }
+
+ public void testNotationSetTextContent() throws TransformerException {
+ if (png == null) {
+ return;
+ }
+ String original = domToString(document);
+ png.setTextContent("foobar");
+ String expected = original.replace("image/png", "foobar");
+ assertEquals(expected, domToString(document));
+ }
+
+ /**
+ * Tests setTextContent on entity references. Although the other tests can
+ * act on a parsed DOM, this needs to use a programmatically constructed DOM
+ * because the parser may have replaced the entity reference with the
+ * corresponding text.
+ */
+ public void testEntityReferenceSetTextContent() throws TransformerException {
+ document = builder.newDocument();
+ Element root = document.createElement("menu");
+ document.appendChild(root);
+
+ EntityReference entityReference = document.createEntityReference("sp");
+ root.appendChild(entityReference);
+
+ try {
+ entityReference.setTextContent("Lite Syrup");
+ fail();
+ } catch (DOMException e) {
+ }
+ }
+
+ public void testAttributeSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ standard.setTextContent("foobar");
+ String expected = original.replace("standard=\"strawberry\"", "standard=\"foobar\"");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testTextSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ descriptionText1.setTextContent("foobar");
+ String expected = original.replace(">Belgian<!", ">foobar<!");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testCdataSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ descriptionText2.setTextContent("foobar");
+ String expected = original.replace(
+ " waffles & strawberries (< 5g ", "foobar");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testProcessingInstructionSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ wafflemaker.setTextContent("foobar");
+ String expected = original.replace(" square shape?>", " foobar?>");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testCommentSetTextContent() throws TransformerException {
+ String original = domToString(document);
+ comment.setTextContent("foobar");
+ String expected = original.replace("-- add other vitamins? --", "--foobar--");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testCoreFeature() {
+ assertTrue(domImplementation.hasFeature("Core", null));
+ assertTrue(domImplementation.hasFeature("Core", ""));
+ assertTrue(domImplementation.hasFeature("Core", "1.0"));
+ assertTrue(domImplementation.hasFeature("Core", "2.0"));
+ assertTrue(domImplementation.hasFeature("Core", "3.0"));
+ assertTrue(domImplementation.hasFeature("CORE", "3.0"));
+ assertTrue(domImplementation.hasFeature("+Core", "3.0"));
+ assertFalse(domImplementation.hasFeature("Core", "4.0"));
+ }
+
+ public void testXmlFeature() {
+ assertTrue(domImplementation.hasFeature("XML", null));
+ assertTrue(domImplementation.hasFeature("XML", ""));
+ assertTrue(domImplementation.hasFeature("XML", "1.0"));
+ assertTrue(domImplementation.hasFeature("XML", "2.0"));
+ assertTrue(domImplementation.hasFeature("XML", "3.0"));
+ assertTrue(domImplementation.hasFeature("Xml", "3.0"));
+ assertTrue(domImplementation.hasFeature("+XML", "3.0"));
+ assertFalse(domImplementation.hasFeature("XML", "4.0"));
+ }
+
+ /**
+ * The RI fails this test.
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Document3-version
+ */
+ public void testXmlVersionFeature() {
+ String message = "This implementation does not support the XMLVersion feature";
+ assertTrue(message, domImplementation.hasFeature("XMLVersion", null));
+ assertTrue(message, domImplementation.hasFeature("XMLVersion", ""));
+ assertTrue(message, domImplementation.hasFeature("XMLVersion", "1.0"));
+ assertTrue(message, domImplementation.hasFeature("XMLVersion", "1.1"));
+ assertTrue(message, domImplementation.hasFeature("XMLVERSION", "1.1"));
+ assertTrue(message, domImplementation.hasFeature("+XMLVersion", "1.1"));
+ assertFalse(domImplementation.hasFeature("XMLVersion", "1.2"));
+ assertFalse(domImplementation.hasFeature("XMLVersion", "2.0"));
+ assertFalse(domImplementation.hasFeature("XMLVersion", "2.0"));
+ }
+
+ public void testLsFeature() {
+ assertTrue("This implementation does not support the LS feature",
+ domImplementation.hasFeature("LS", "3.0"));
+ }
+
+ public void testElementTraversalFeature() {
+ assertTrue("This implementation does not support the ElementTraversal feature",
+ domImplementation.hasFeature("ElementTraversal", "1.0"));
+ }
+
+ public void testIsSupported() {
+ // we don't independently test the features; instead just assume the
+ // implementation calls through to hasFeature (as tested above)
+ for (Node node : allNodes) {
+ assertTrue(node.isSupported("XML", null));
+ assertTrue(node.isSupported("XML", "3.0"));
+ assertFalse(node.isSupported("foo", null));
+ assertFalse(node.isSupported("foo", "bar"));
+ }
+ }
+
+ public void testGetFeature() {
+ // we don't independently test the features; instead just assume the
+ // implementation calls through to hasFeature (as tested above)
+ for (Node node : allNodes) {
+ assertSame(node, node.getFeature("XML", null));
+ assertSame(node, node.getFeature("XML", "3.0"));
+ assertNull(node.getFeature("foo", null));
+ assertNull(node.getFeature("foo", "bar"));
+ }
+ }
+
+ public void testNodeEqualsPositive() throws Exception {
+ DomTest copy = new DomTest();
+ copy.setUp();
+
+ for (int i = 0; i < allNodes.size(); i++) {
+ Node a = allNodes.get(i);
+ Node b = copy.allNodes.get(i);
+ assertTrue(a.isEqualNode(b));
+ }
+ }
+
+ public void testNodeEqualsNegative() throws Exception {
+ for (Node a : allNodes) {
+ for (Node b : allNodes) {
+ assertEquals(a == b, a.isEqualNode(b));
+ }
+ }
+ }
+
+ public void testNodeEqualsNegativeRecursive() throws Exception {
+ DomTest copy = new DomTest();
+ copy.setUp();
+ copy.vitaminc.setTextContent("55%");
+
+ // changing anything about a node should break equality for all parents
+ assertFalse(document.isEqualNode(copy.document));
+ assertFalse(menu.isEqualNode(copy.menu));
+ assertFalse(item.isEqualNode(copy.item));
+ assertFalse(nutrition.isEqualNode(copy.nutrition));
+ assertFalse(vitamins.isEqualNode(copy.vitamins));
+ assertFalse(vitaminc.isEqualNode(copy.vitaminc));
+
+ // but not siblings
+ assertTrue(doctype.isEqualNode(copy.doctype));
+ assertTrue(description.isEqualNode(copy.description));
+ assertTrue(option1.isEqualNode(copy.option1));
+ }
+
+ public void testNodeEqualsNull() {
+ for (Node node : allNodes) {
+ try {
+ node.isEqualNode(null);
+ fail();
+ } catch (NullPointerException e) {
+ }
+ }
+ }
+
+ public void testIsElementContentWhitespaceWithoutDeclaration() throws Exception {
+ String xml = "<menu> <item/> </menu>";
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Text text = (Text) factory.newDocumentBuilder()
+ .parse(new InputSource(new StringReader(xml)))
+ .getDocumentElement().getChildNodes().item(0);
+ assertFalse(text.isElementContentWhitespace());
+ }
+
+ public void testIsElementContentWhitespaceWithDeclaration() throws Exception {
+ String xml = "<!DOCTYPE menu [\n"
+ + " <!ELEMENT menu (item)*>\n"
+ + " <!ELEMENT item (#PCDATA)>\n"
+ + "]><menu> <item/> </menu>";
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Text text = (Text) factory.newDocumentBuilder()
+ .parse(new InputSource(new StringReader(xml)))
+ .getDocumentElement().getChildNodes().item(0);
+ assertTrue("This implementation does not recognize element content whitespace",
+ text.isElementContentWhitespace());
+ }
+
+ public void testGetWholeTextFirst() {
+ assertEquals("Belgian waffles & strawberries (< 5g of fat)",
+ descriptionText1.getWholeText());
+ }
+
+ public void testGetWholeTextMiddle() {
+ assertEquals("This implementation doesn't include preceding nodes in getWholeText()",
+ "Belgian waffles & strawberries (< 5g of fat)", descriptionText2.getWholeText());
+ }
+
+ public void testGetWholeTextLast() {
+ assertEquals("This implementation doesn't include preceding nodes in getWholeText()",
+ "Belgian waffles & strawberries (< 5g of fat)", descriptionText3.getWholeText());
+ }
+
+ public void testGetWholeTextOnly() {
+ assertEquals("60%", vitamincText.getWholeText());
+ }
+
+ public void testGetWholeTextWithEntityReference() {
+ EntityReference spReference = document.createEntityReference("sp");
+ description.insertBefore(spReference, descriptionText2);
+
+ assertEquals("This implementation doesn't resolve entity references in getWholeText()",
+ "BelgianMaple Syrup waffles & strawberries (< 5g of fat)",
+ descriptionText1.getWholeText());
+ }
+
+ public void testReplaceWholeTextFirst() throws TransformerException {
+ String original = domToString(document);
+ Text replacement = descriptionText1.replaceWholeText("Eggos");
+ assertSame(descriptionText1, replacement);
+ String expected = original.replace(
+ "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextMiddle() throws TransformerException {
+ String original = domToString(document);
+ Text replacement = descriptionText2.replaceWholeText("Eggos");
+ assertSame(descriptionText2, replacement);
+ String expected = original.replace(
+ "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "<![CDATA[Eggos]]>");
+ assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextLast() throws TransformerException {
+ String original = domToString(document);
+ Text replacement = descriptionText3.replaceWholeText("Eggos");
+ assertSame(descriptionText3, replacement);
+ String expected = original.replace(
+ "Belgian<![CDATA[ waffles & strawberries (< 5g ]]>of fat)", "Eggos");
+ assertEquals("This implementation doesn't remove preceding nodes in replaceWholeText()",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextOnly() throws TransformerException {
+ String original = domToString(document);
+ Text replacement = vitamincText.replaceWholeText("70%");
+ assertEquals(Node.TEXT_NODE, replacement.getNodeType());
+ assertSame(vitamincText, replacement);
+ String expected = original.replace("60%", "70%");
+ assertEquals(expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextFirstWithNull() throws TransformerException {
+ String original = domToString(document);
+ assertNull(descriptionText1.replaceWholeText(null));
+ String expected = original.replaceFirst(">.*</description>", "/>");
+ assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextMiddleWithNull() throws TransformerException {
+ String original = domToString(document);
+ assertNull(descriptionText2.replaceWholeText(null));
+ String expected = original.replaceFirst(">.*</description>", "/>");
+ assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextLastWithNull() throws TransformerException {
+ String original = domToString(document);
+ assertNull(descriptionText3.replaceWholeText(null));
+ String expected = original.replaceFirst(">.*</description>", "/>");
+ assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextFirstWithEmptyString() throws TransformerException {
+ String original = domToString(document);
+ assertNull(descriptionText1.replaceWholeText(""));
+ String expected = original.replaceFirst(">.*</description>", "/>");
+ assertEquals("This implementation doesn't remove adjacent nodes in replaceWholeText(null)",
+ expected, domToString(document));
+ }
+
+ public void testReplaceWholeTextOnlyWithEmptyString() throws TransformerException {
+ String original = domToString(document);
+ assertNull(vitamincText.replaceWholeText(""));
+ String expected = original.replaceFirst(">.*</a:vitaminc>", "/>");
+ assertEquals(expected, domToString(document));
+ }
+
+ private String domToString(Document document) throws TransformerException {
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(document), new StreamResult(writer));
+ return writer.toString();
+ }
+}
diff --git a/xml/src/test/java/tests/xml/NodeTest.java b/xml/src/test/java/tests/xml/NodeTest.java
new file mode 100644
index 0000000..dc3a333
--- /dev/null
+++ b/xml/src/test/java/tests/xml/NodeTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 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.xml;
+
+import dalvik.annotation.TestTargetClass;
+import junit.framework.TestCase;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import tests.support.resource.Support_Resources;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+@TestTargetClass(Node.class)
+public class NodeTest extends TestCase {
+
+ /**
+ * For bug 779: Node#getNextSibling throws IndexOutOfBoundsException.
+ */
+ public void test_getNextSibling() throws Exception {
+ // Calling getNextSibling when there is no next sibling should return null.
+ // From http://code.google.com/p/android/issues/detail?id=779.
+ ByteArrayInputStream bis = new ByteArrayInputStream("<root/>".getBytes());
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(bis);
+ Node root = document.getDocumentElement();
+ assertNull(root.getNextSibling());
+ }
+
+ public void testGetBaseUri() throws Exception {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ File file = Support_Resources.resourceToTempFile("/simple.xml");
+ Document document = builder.parse(file);
+
+ String baseUri = "file:" + file.getPath();
+ assertEquals(baseUri, document.getBaseURI());
+
+ Element documentElement = document.getDocumentElement();
+ for (Node node : flattenSubtree(documentElement)) {
+ if (node.getNodeType() == Node.ELEMENT_NODE
+ || node.getNodeType() == Node.DOCUMENT_NODE) {
+ assertEquals("Unexpected base URI for " + node, baseUri, node.getBaseURI());
+ } else {
+ assertNull("Unexpected base URI for " + node, node.getBaseURI());
+ }
+ }
+
+ // TODO: test other node types
+ // TODO: test resolution of relative paths
+ // TODO: test URI santization
+ }
+
+ private List<Node> flattenSubtree(Node subtree) {
+ List<Node> result = new ArrayList<Node>();
+ traverse(subtree, result);
+ return result;
+ }
+
+ private void traverse(Node node, List<Node> sink) {
+ sink.add(node);
+
+ NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ traverse(children.item(i), sink);
+ }
+ }
+}
diff --git a/xml/src/test/java/tests/xml/NodeTests.java b/xml/src/test/java/tests/xml/NodeTests.java
deleted file mode 100644
index e46e216..0000000
--- a/xml/src/test/java/tests/xml/NodeTests.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2009 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.xml;
-
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargetClass;
-
-import junit.framework.TestCase;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.io.ByteArrayInputStream;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-@TestTargetClass(Node.class)
-public class NodeTests extends TestCase {
- @TestTargetNew(
- level = TestLevel.PARTIAL,
- notes = "Issue #779: org.w3c.dom.Node#getNextSibling throws IndexOutOfBoundsException.",
- method = "getNextSibling",
- args = {}
- )
- public void test_getNextSibling() throws Exception {
- // Calling getNextSibling when there is no next sibling should return null.
- // From http://code.google.com/p/android/issues/detail?id=779.
- ByteArrayInputStream bis = new ByteArrayInputStream("<root/>".getBytes());
- Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(bis);
- Node root = document.getDocumentElement();
- assertNull(root.getNextSibling());
- }
-}