<NME> KXmlSerializer.java
<BEF> /*
 * Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

package cgeo.org.kxml2.io;

import cgeo.geocaching.utils.Log;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;

import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlSerializer;

@SuppressWarnings("ALL")
public class KXmlSerializer implements XmlSerializer {

    //    static final String UNDEFINED = ":";

    // BEGIN android-added
    /**
     * size (in characters) for the write buffer
     */
    private static final int WRITE_BUFFER_SIZE = 500;
    // END android-added

    // BEGIN android-changed
    // (Guarantee that the writer is always buffered.)
    private BufferedWriter writer;
    // END android-changed

    private boolean pending;
    private int auto;
    private int depth;

    private String[] elementStack = new String[12];
    //nsp/prefix/name
    private int[] nspCounts = new int[4];
    private String[] nspStack = new String[8];
    //prefix/nsp; both empty are ""
    private boolean[] indent = new boolean[4];
    private boolean unicode;
    private String encoding;

    private final void check(boolean close) throws IOException {
        if (!pending) {
            return;
        }

        depth++;
        pending = false;

        if (indent.length <= depth) {
            boolean[] hlp = new boolean[depth + 4];
            System.arraycopy(indent, 0, hlp, 0, depth);
        for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
            writer.write(' ');
            writer.write("xmlns");
            if (!StringUtils.isEmpty(nspStack[i * 2])) {
                writer.write(':');
                writer.write(nspStack[i * 2]);
            }
            else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
                throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
            }
            writer.write("=\"");
                throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
            }
            writer.write("=\"");
            writeEscaped(nspStack[i * 2 + 1], '"');
            writer.write('"');
        }

        if (nspCounts.length <= depth + 1) {
            int[] hlp = new int[depth + 8];
            System.arraycopy(nspCounts, 0, hlp, 0, depth + 1);
            nspCounts = hlp;
        }

        nspCounts[depth + 1] = nspCounts[depth];
        //   nspCounts[depth + 2] = nspCounts[depth];

        writer.write(close ? " />" : ">");
    }

    private final void writeEscaped(String s, int quot) throws IOException {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '\n':
                case '\r':
                case '\t':
                    if (quot == -1) {
                        writer.write(c);
                    } else {
                        writer.write("&#" + ((int) c) + ';');
                    }
                    break;
                case '&':
                    writer.write("&amp;");
                    break;
                case '>':
                    writer.write("&gt;");
                    break;
                case '<':
                    writer.write("&lt;");
                    break;
                default:
                    if (c == quot) {
                        writer.write(c == '"' ? "&quot;" : "&apos;");
                        break;
                    }
                    // BEGIN android-changed: refuse to output invalid characters
                    // See http://www.w3.org/TR/REC-xml/#charsets for definition.
                    // Corrected for c:geo to handle utf-16 codepoint surrogates correctly
                    // See http://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF
                    // Note: tab, newline, and carriage return have already been
                    // handled above.
                    // Check for lead surrogate
                    if (c >= 0xd800 && c <= 0xdbff) {

                        if (i + 1 < s.length()) {
                            writer.write(s.substring(i, i + 1));
                            i++;
                            break;
                        }
                        // if the lead surrogate is at the string end, it's not valid utf-16
                        reportInvalidCharacter(c);
                    }
                    boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
                    if (valid) {
                        if (unicode || c < 127) {
                            writer.write(c);
                        } else {
                            writer.write("&#" + ((int) c) + ";");
                        }
                    } else {
                        reportInvalidCharacter(c);
                    }
                    // END android-changed
            }
        }
    }

    // BEGIN android-added
    private static void reportInvalidCharacter(char ch) {
        Log.w("Skipping illegal character (" + Integer.toHexString((int) ch) + ")");
    }
    // END android-added

    /*
     * private final void writeIndent() throws IOException {
     * writer.write("\r\n");
     * for (int i = 0; i < depth; i++)
     * writer.write(' ');
     * }
     */

    public void docdecl(String dd) throws IOException {
        writer.write("<!DOCTYPE");
        writer.write(dd);
        writer.write(">");
    }

    public void endDocument() throws IOException {
        while (depth > 0) {
            endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]);
        }
        flush();
    }

    public void entityRef(String name) throws IOException {
        check(false);
        writer.write('&');
        writer.write(name);
        writer.write(';');
    }

    public boolean getFeature(String name) {
        //return false;
        return ("http://xmlpull.org/v1/doc/features.html#indent-output"
                .equals(
                        name))
                ? indent[depth]
                : false;
    }

    public String getPrefix(String namespace, boolean create) {
        try {
            return getPrefix(namespace, false, create);
        } catch (IOException e) {
            throw new RuntimeException(e.toString());
        }
    }

    private final String getPrefix(

        for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
            if (nspStack[i + 1].equals(namespace)
                    && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) {
                String cand = nspStack[i];
                for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
                    if (nspStack[j].equals(cand)) {
                    && (includeDefault || StringUtils.isNotEmpty(nspStack[i]))) {
                String cand = nspStack[i];
                for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
                    if (nspStack[j].equals(cand)) {
                        cand = null;
                        break;
                    }
                }
                if (cand != null) {
                    return cand;
                }
            }
        }

        if (!create) {
            return null;
        }

        String prefix;

        if (StringUtils.isEmpty(namespace)) {
            prefix = "";
        } else {
            do {
                prefix = "n" + (auto++);
                for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
                    if (prefix.equals(nspStack[i])) {
                        prefix = null;
                        break;
                    }
                }
            } while (prefix == null);
        }

        boolean p = pending;
        pending = false;
        setPrefix(prefix, namespace);
        pending = p;
        return prefix;
    }

    public Object getProperty(String name) {
        throw new RuntimeException("Unsupported property");
    }

    public void ignorableWhitespace(String s)
            throws IOException {
        text(s);
    }

    public void setFeature(String name, boolean value) {
        if ("http://xmlpull.org/v1/doc/features.html#indent-output"
                .equals(name)) {
            indent[depth] = value;
        } else {
            throw new RuntimeException("Unsupported Feature");
        }
    }

    public void setProperty(String name, Object value) {
        throw new RuntimeException(
                "Unsupported Property:" + value);
    }

    public void setPrefix(String prefix, String namespace)
            throws IOException {

        check(false);
        if (prefix == null) {
            prefix = "";
        }
        if (namespace == null) {
            namespace = "";
        }

        String defined = getPrefix(namespace, true, false);

        // boil out if already defined

        if (prefix.equals(defined)) {
            return;
        }

        int pos = (nspCounts[depth + 1]++) << 1;

        if (nspStack.length < pos + 1) {
            String[] hlp = new String[nspStack.length + 16];
            System.arraycopy(nspStack, 0, hlp, 0, pos);
            nspStack = hlp;
        }

        nspStack[pos++] = prefix;
        nspStack[pos] = namespace;
    }

    public void setOutput(Writer writer) {
        // BEGIN android-changed
        // Guarantee that the writer is always buffered.
        if (writer instanceof BufferedWriter) {
            this.writer = (BufferedWriter) writer;
        } else {
            this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE);
        }
        // END android-changed

        // elementStack = new String[12]; //nsp/prefix/name
        //nspCounts = new int[4];
        //nspStack = new String[8]; //prefix/nsp
        //indent = new boolean[4];

        nspCounts[0] = 2;
        nspCounts[1] = 2;
        nspStack[0] = "";
        nspStack[1] = "";
        nspStack[2] = "xml";
        nspStack[3] = "http://www.w3.org/XML/1998/namespace";
        pending = false;
        auto = 0;
        depth = 0;

        unicode = false;
    }

    public void setOutput(OutputStream os, String encoding)
            throws IOException {
        if (os == null) {
            throw new IllegalArgumentException("os == null");
        }
        setOutput(encoding == null
                ? new OutputStreamWriter(os)
                : new OutputStreamWriter(os, encoding));
        this.encoding = encoding;
        if (encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")) {
            unicode = true;
        }
    }

    public void startDocument(String encoding, Boolean standalone) throws IOException {
        writer.write("<?xml version='1.0' ");

        if (encoding != null) {
            this.encoding = encoding;
            if (encoding.toLowerCase(Locale.US).startsWith("utf")) {
                unicode = true;
            }
        }

        if (this.encoding != null) {
            writer.write("encoding='");
            writer.write(this.encoding);
            writer.write("' ");
        }

        if (standalone != null) {
            writer.write("standalone='");
            writer.write(
                    standalone.booleanValue() ? "yes" : "no");
            writer.write("' ");
        }
        writer.write("?>");
    }

    public XmlSerializer startTag(String namespace, String name)
            throws IOException {
        check(false);

        //        if (namespace == null)
        //            namespace = "";

        if (indent[depth]) {
            writer.write("\r\n");
            for (int i = 0; i < depth; i++) {
                writer.write("  ");
            }
        }

        int esp = depth * 3;

        if (elementStack.length < esp + 3) {
            String[] hlp = new String[elementStack.length + 12];
            System.arraycopy(elementStack, 0, hlp, 0, esp);
            elementStack = hlp;
        }


        if (namespace != null && StringUtils.isEmpty(namespace)) {
            for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) {
                if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
                    throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
                }
            }
                if (StringUtils.isEmpty(nspStack[i * 2]) && StringUtils.isNotEmpty(nspStack[i * 2 + 1])) {
                    throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
                }
            }
        elementStack[esp] = name;

        writer.write('<');
        if (!StringUtils.isEmpty(prefix)) {
            writer.write(prefix);
            writer.write(':');
        }
        if (StringUtils.isNotEmpty(prefix)) {
            writer.write(prefix);
            writer.write(':');
        }

        writer.write(name);

        pending = true;

        return this;
    }

    public XmlSerializer attribute(
            String namespace,
            String name,
            String value)
            throws IOException {
        if (!pending) {
            throw new IllegalStateException("illegal position for attribute");
        }

        //        int cnt = nspCounts[depth];

        if (namespace == null) {
            namespace = "";
        }

        //        depth--;
        //        pending = false;

        String prefix =
                StringUtils.isEmpty(namespace)
                        ? ""
                        : getPrefix(namespace, false, true);

        //        pending = true;
        //        depth++;

        /*
         * if (cnt != nspCounts[depth]) {
         * writer.write(' ');
         * writer.write("xmlns");
         * if (nspStack[cnt * 2] != null) {
         * writer.write(':');
         * writer.write(nspStack[cnt * 2]);
         * }
         */

        writer.write(' ');
        if (!StringUtils.isEmpty(prefix)) {
            writer.write(prefix);
            writer.write(':');
        }
        if (StringUtils.isNotEmpty(prefix)) {
            writer.write(prefix);
            writer.write(':');
        }
        writer.write(name);
        writer.write('=');
        char q = value.indexOf('"') == -1 ? '"' : '\'';
        writer.write(q);
        writeEscaped(value, q);
        writer.write(q);

        return this;
    }

    public void flush() throws IOException {
        check(false);
        writer.flush();
    }

    /*
     * public void close() throws IOException {
     * check();
     * writer.close();
     * }
     */
    public XmlSerializer endTag(String namespace, String name)
            throws IOException {

        if (!pending) {
            depth--;
            //        if (namespace == null)
            //          namespace = "";
        }

        if ((namespace == null
                && elementStack[depth * 3] != null)
                || (namespace != null
                && !namespace.equals(elementStack[depth * 3]))
                || !elementStack[depth * 3 + 2].equals(name)) {
            throw new IllegalArgumentException("</{" + namespace + "}" + name + "> does not match start");
        }

        if (pending) {
            check(true);
            depth--;
        } else {
            if (indent[depth + 1]) {
                writer.write("\r\n");
                for (int i = 0; i < depth; i++) {
                    writer.write("  ");

            writer.write("</");
            String prefix = elementStack[depth * 3 + 1];
            if (!StringUtils.isEmpty(prefix)) {
                writer.write(prefix);
                writer.write(':');
            }
                writer.write(':');
            }
            writer.write(name);
            writer.write('>');
        }

        nspCounts[depth + 1] = nspCounts[depth];
        return this;
    }

    public String getNamespace() {
        return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3];
    }

    public String getName() {
        return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1];
    }

    public int getDepth() {
        return pending ? depth + 1 : depth;
    }

    public XmlSerializer text(String text) throws IOException {
        check(false);
        indent[depth] = false;
        writeEscaped(text, -1);
        return this;
    }

    public XmlSerializer text(char[] text, int start, int len)
            throws IOException {
        text(new String(text, start, len));
        return this;
    }

    public void cdsect(String data) throws IOException {
        check(false);
        // BEGIN android-changed: ]]> is not allowed within a CDATA,
        // so break and start a new one when necessary.
        data = data.replace("]]>", "]]]]><![CDATA[>");
        char[] chars = data.toCharArray();
        // We also aren't allowed any invalid characters.
        for (char ch : chars) {
            boolean valid = (ch >= 0x20 && ch <= 0xd7ff) ||
                    (ch == '\t' || ch == '\n' || ch == '\r') ||
                    (ch >= 0xe000 && ch <= 0xfffd);
            if (!valid) {
                reportInvalidCharacter(ch);
            }
        }
        writer.write("<![CDATA[");
        writer.write(chars, 0, chars.length);
        writer.write("]]>");
        // END android-changed
    }

    public void comment(String comment) throws IOException {
        check(false);
        writer.write("<!--");
        writer.write(comment);
        writer.write("-->");
    }

    public void processingInstruction(String pi)
            throws IOException {
        check(false);
        writer.write("<?");
        writer.write(pi);
        writer.write("?>");
    }
}

<MSG> Use isNotEmpty() instead of !isEmpty().

<DFF> @@ -78,11 +78,11 @@ public class KXmlSerializer implements XmlSerializer {
         for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
             writer.write(' ');
             writer.write("xmlns");
-            if (!StringUtils.isEmpty(nspStack[i * 2])) {
+            if (StringUtils.isNotEmpty(nspStack[i * 2])) {
                 writer.write(':');
                 writer.write(nspStack[i * 2]);
             }
-            else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+            else if (StringUtils.isEmpty(getNamespace()) && StringUtils.isNotEmpty(nspStack[i * 2 + 1])) {
                 throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
             }
             writer.write("=\"");
@@ -219,7 +219,7 @@ public class KXmlSerializer implements XmlSerializer {
 
         for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
             if (nspStack[i + 1].equals(namespace)
-                    && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) {
+                    && (includeDefault || StringUtils.isNotEmpty(nspStack[i]))) {
                 String cand = nspStack[i];
                 for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
                     if (nspStack[j].equals(cand)) {
@@ -410,7 +410,7 @@ public class KXmlSerializer implements XmlSerializer {
 
         if (namespace != null && StringUtils.isEmpty(namespace)) {
             for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) {
-                if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+                if (StringUtils.isEmpty(nspStack[i * 2]) && StringUtils.isNotEmpty(nspStack[i * 2 + 1])) {
                     throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
                 }
             }
@@ -421,7 +421,7 @@ public class KXmlSerializer implements XmlSerializer {
         elementStack[esp] = name;
 
         writer.write('<');
-        if (!StringUtils.isEmpty(prefix)) {
+        if (StringUtils.isNotEmpty(prefix)) {
             writer.write(prefix);
             writer.write(':');
         }
@@ -474,7 +474,7 @@ public class KXmlSerializer implements XmlSerializer {
          */
 
         writer.write(' ');
-        if (!StringUtils.isEmpty(prefix)) {
+        if (StringUtils.isNotEmpty(prefix)) {
             writer.write(prefix);
             writer.write(':');
         }
@@ -531,7 +531,7 @@ public class KXmlSerializer implements XmlSerializer {
 
             writer.write("</");
             String prefix = elementStack[depth * 3 + 1];
-            if (!StringUtils.isEmpty(prefix)) {
+            if (StringUtils.isNotEmpty(prefix)) {
                 writer.write(prefix);
                 writer.write(':');
             }

Use isNotEmpty() instead of !isEmpty().