Fixing namespace+prefix mode in Expat and removing optional fields from callbacks.

The first part is related to bug 6632:
  http://code.google.com/p/android/issues/detail?id=6632

I added these optional fields back when I was originally updating
the XML parser for Froyo. I've decided to remove them to simplify
migrating between Android and the RI. It should also save some
object allocations.

Note that the RI v5 and the RI v6 behave differently for optional
values on attributes; this motivated me to add the otherwise
unfortunate assertOneOf() method to the testcase. (We behave more
like RI v6, which is to supply the values upon request)

Change-Id: Icfa5d29976a86bf194b3ed7c0d9e2275c3bff9dd
diff --git a/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java b/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
index dbe3a3a..d187456 100644
--- a/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
+++ b/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
@@ -244,7 +244,7 @@
     }
 
     public void parse(InputSource input) throws IOException, SAXException {
-        if (processNamespacePrefixes == processNamespaces) {
+        if (processNamespacePrefixes && processNamespaces) {
             /*
              * Expat has XML_SetReturnNSTriplet, but that still doesn't
              * include xmlns attributes like this feature requires. We may
diff --git a/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
index 4721800..b893309 100644
--- a/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
+++ b/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
@@ -628,8 +628,8 @@
     jobject javaParser = parsingContext->object;
 
     ExpatElementName e(env, parsingContext, elementName);
-    jstring uri = e.uri();
-    jstring localName = e.localName();
+    jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
+    jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
     jstring qName = e.qName();
 
     stringStackPush(parsingContext, qName);
diff --git a/xml/src/test/java/tests/xml/AllTests.java b/xml/src/test/java/tests/xml/AllTests.java
index 96b96c5..e9f833f 100644
--- a/xml/src/test/java/tests/xml/AllTests.java
+++ b/xml/src/test/java/tests/xml/AllTests.java
@@ -30,6 +30,7 @@
         suite.addTestSuite(SimpleBuilderTest.class);
         suite.addTestSuite(NodeTest.class);
         suite.addTestSuite(NormalizeTest.class);
+        suite.addTestSuite(SaxTest.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/SaxTest.java b/xml/src/test/java/tests/xml/SaxTest.java
new file mode 100644
index 0000000..2c75a73
--- /dev/null
+++ b/xml/src/test/java/tests/xml/SaxTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Initiate and observe a SAX parse session.
+ */
+public class SaxTest extends TestCase {
+
+    public void testNoPrefixesNoNamespaces() throws Exception {
+        parse(false, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("bar", "", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(false, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("a:bar", "", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    public void testNoPrefixesYesNamespaces() throws Exception {
+        parse(false, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("foo", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(false, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("http://quux", uri);
+                assertEquals("foo", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("http://quux", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    /**
+     * Android's Expat-based SAX parser fails this test because Expat doesn't
+     * supply us with our much desired {@code xmlns="http://..."} attributes.
+     */
+    public void testYesPrefixesYesNamespaces() throws Exception {
+        parse(true, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("foo", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(true, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("http://quux", uri);
+                assertEquals("foo", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(2, attributes.getLength());
+                assertEquals("http://quux", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+                assertEquals("", attributes.getURI(1));
+                assertEquals("", attributes.getLocalName(1));
+                assertEquals("xmlns:a", attributes.getQName(1));
+            }
+        });
+    }
+
+    public void testYesPrefixesNoNamespaces() throws Exception {
+        parse(true, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("bar", "", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(true, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("a:bar", "", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    private void parse(boolean prefixes, boolean namespaces, String xml,
+            ContentHandler handler) throws Exception {
+        SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+        XMLReader reader = parser.getXMLReader();
+        reader.setFeature("http://xml.org/sax/features/namespace-prefixes", prefixes);
+        reader.setFeature("http://xml.org/sax/features/namespaces", namespaces);
+        reader.setContentHandler(handler);
+        reader.parse(new InputSource(new StringReader(xml)));
+    }
+
+    /**
+     * @param expected an optional value that may or may have not been supplied
+     * @param sentinel a marker value that means the expected value was omitted
+     */
+    private void assertOneOf(String expected, String sentinel, String actual) {
+        List<String> optionsList = Arrays.asList(sentinel, expected);
+        assertTrue("Expected one of " + optionsList + " but was " + actual,
+                optionsList.contains(actual));
+    }
+}