summaryrefslogtreecommitdiffstats
path: root/core/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/com')
-rw-r--r--core/java/com/android/internal/net/DNParser.java450
-rw-r--r--core/java/com/android/internal/net/DomainNameValidator.java277
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java2
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java365
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java797
5 files changed, 1890 insertions, 1 deletions
diff --git a/core/java/com/android/internal/net/DNParser.java b/core/java/com/android/internal/net/DNParser.java
new file mode 100644
index 0000000..5254207
--- /dev/null
+++ b/core/java/com/android/internal/net/DNParser.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A simple distinguished name(DN) parser.
+ *
+ * <p>This class is based on org.apache.harmony.security.x509.DNParser. It's customized to remove
+ * external references which are unnecessary for our requirements.
+ *
+ * <p>This class is only meant for extracting a string value from a DN. e.g. it doesn't support
+ * values in the hex-string style.
+ *
+ * <p>This class is used by {@link DomainNameValidator} only. However, in order to make this
+ * class visible from unit tests, it's made public.
+ *
+ * @hide
+ */
+public final class DNParser {
+ private static final String TAG = "DNParser";
+
+ /** DN to be parsed. */
+ private final String dn;
+
+ // length of distinguished name string
+ private final int length;
+
+ private int pos, beg, end;
+
+ // tmp vars to store positions of the currently parsed item
+ private int cur;
+
+ // distinguished name chars
+ private char[] chars;
+
+ /**
+ * Exception message thrown when we failed to parse DN, which shouldn't happen because we
+ * only handle DNs that {@link X500Principal#getName} returns, which shouldn't be malformed.
+ */
+ private static final String ERROR_PARSE_ERROR = "Failed to parse DN";
+
+ /**
+ * Constructor.
+ *
+ * @param principal - {@link X500Principal} to be parsed
+ */
+ public DNParser(X500Principal principal) {
+ this.dn = principal.getName(X500Principal.RFC2253);
+ this.length = dn.length();
+ }
+
+ // gets next attribute type: (ALPHA 1*keychar) / oid
+ private String nextAT() throws IOException {
+
+ // skip preceding space chars, they can present after
+ // comma or semicolon (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ if (pos == length) {
+ return null; // reached the end of DN
+ }
+
+ // mark the beginning of attribute type
+ beg = pos;
+
+ // attribute type chars
+ pos++;
+ for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+ // we don't follow exact BNF syntax here:
+ // accept any char except space and '='
+ }
+ if (pos >= length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // mark the end of attribute type
+ end = pos;
+
+ // skip trailing space chars between attribute type and '='
+ // (compatibility with RFC 1779)
+ if (chars[pos] == ' ') {
+ for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+ }
+
+ if (chars[pos] != '=' || pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+
+ pos++; //skip '=' char
+
+ // skip space chars between '=' and attribute value
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ // in case of oid attribute type skip its prefix: "oid." or "OID."
+ // (compatibility with RFC 1779)
+ if ((end - beg > 4) && (chars[beg + 3] == '.')
+ && (chars[beg] == 'O' || chars[beg] == 'o')
+ && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+ && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+ beg += 4;
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+ private String quotedAV() throws IOException {
+
+ pos++;
+ beg = pos;
+ end = beg;
+ while (true) {
+
+ if (pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ if (chars[pos] == '"') {
+ // enclosing quotation was found
+ pos++;
+ break;
+ } else if (chars[pos] == '\\') {
+ chars[end] = getEscaped();
+ } else {
+ // shift char: required for string with escaped chars
+ chars[end] = chars[pos];
+ }
+ pos++;
+ end++;
+ }
+
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets hex string attribute value: "#" hexstring
+ private String hexAV() throws IOException {
+
+ if (pos + 4 >= length) {
+ // encoded byte array must be not less then 4 c
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ beg = pos; // store '#' position
+ pos++;
+ while (true) {
+
+ // check for end of attribute value
+ // looks for space and component separators
+ if (pos == length || chars[pos] == '+' || chars[pos] == ','
+ || chars[pos] == ';') {
+ end = pos;
+ break;
+ }
+
+ if (chars[pos] == ' ') {
+ end = pos;
+ pos++;
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ break;
+ } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+ chars[pos] += 32; //to low case
+ }
+
+ pos++;
+ }
+
+ // verify length of hex string
+ // encoded byte array must be not less then 4 and must be even number
+ int hexLen = end - beg; // skip first '#' char
+ if (hexLen < 5 || (hexLen & 1) == 0) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // get byte encoding from string representation
+ byte[] encoded = new byte[hexLen / 2];
+ for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+ encoded[i] = (byte) getByte(p);
+ }
+
+ return new String(chars, beg, hexLen);
+ }
+
+ // gets string attribute value: *( stringchar / pair )
+ private String escapedAV() throws IOException {
+
+ beg = pos;
+ end = pos;
+ while (true) {
+
+ if (pos >= length) {
+ // the end of DN has been found
+ return new String(chars, beg, end - beg);
+ }
+
+ switch (chars[pos]) {
+ case '+':
+ case ',':
+ case ';':
+ // separator char has beed found
+ return new String(chars, beg, end - beg);
+ case '\\':
+ // escaped char
+ chars[end++] = getEscaped();
+ pos++;
+ break;
+ case ' ':
+ // need to figure out whether space defines
+ // the end of attribute value or not
+ cur = end;
+
+ pos++;
+ chars[end++] = ' ';
+
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ chars[end++] = ' ';
+ }
+ if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+ || chars[pos] == ';') {
+ // separator char or the end of DN has beed found
+ return new String(chars, beg, cur - beg);
+ }
+ break;
+ default:
+ chars[end++] = chars[pos];
+ pos++;
+ }
+ }
+ }
+
+ // returns escaped char
+ private char getEscaped() throws IOException {
+
+ pos++;
+ if (pos == length) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ case '\\':
+ case ',':
+ case '=':
+ case '+':
+ case '<':
+ case '>':
+ case '#':
+ case ';':
+ case ' ':
+ case '*':
+ case '%':
+ case '_':
+ //FIXME: escaping is allowed only for leading or trailing space char
+ return chars[pos];
+ default:
+ // RFC doesn't explicitly say that escaped hex pair is
+ // interpreted as UTF-8 char. It only contains an example of such DN.
+ return getUTF8();
+ }
+ }
+
+ // decodes UTF-8 char
+ // see http://www.unicode.org for UTF-8 bit distribution table
+ private char getUTF8() throws IOException {
+
+ int res = getByte(pos);
+ pos++; //FIXME tmp
+
+ if (res < 128) { // one byte: 0-7F
+ return (char) res;
+ } else if (res >= 192 && res <= 247) {
+
+ int count;
+ if (res <= 223) { // two bytes: C0-DF
+ count = 1;
+ res = res & 0x1F;
+ } else if (res <= 239) { // three bytes: E0-EF
+ count = 2;
+ res = res & 0x0F;
+ } else { // four bytes: F0-F7
+ count = 3;
+ res = res & 0x07;
+ }
+
+ int b;
+ for (int i = 0; i < count; i++) {
+ pos++;
+ if (pos == length || chars[pos] != '\\') {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ pos++;
+
+ b = getByte(pos);
+ pos++; //FIXME tmp
+ if ((b & 0xC0) != 0x80) {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+
+ res = (res << 6) + (b & 0x3F);
+ }
+ return (char) res;
+ } else {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ }
+
+ // Returns byte representation of a char pair
+ // The char pair is composed of DN char in
+ // specified 'position' and the next char
+ // According to BNF syntax:
+ // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+ // / "a" / "b" / "c" / "d" / "e" / "f"
+ private int getByte(int position) throws IOException {
+
+ if ((position + 1) >= length) {
+ // to avoid ArrayIndexOutOfBoundsException
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ int b1, b2;
+
+ b1 = chars[position];
+ if (b1 >= '0' && b1 <= '9') {
+ b1 = b1 - '0';
+ } else if (b1 >= 'a' && b1 <= 'f') {
+ b1 = b1 - 87; // 87 = 'a' - 10
+ } else if (b1 >= 'A' && b1 <= 'F') {
+ b1 = b1 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ b2 = chars[position + 1];
+ if (b2 >= '0' && b2 <= '9') {
+ b2 = b2 - '0';
+ } else if (b2 >= 'a' && b2 <= 'f') {
+ b2 = b2 - 87; // 87 = 'a' - 10
+ } else if (b2 >= 'A' && b2 <= 'F') {
+ b2 = b2 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ return (b1 << 4) + b2;
+ }
+
+ /**
+ * Parses the DN and returns the attribute value for an attribute type.
+ *
+ * @param attributeType attribute type to look for (e.g. "ca")
+ * @return value of the attribute that first found, or null if none found
+ */
+ public String find(String attributeType) {
+ try {
+ // Initialize internal state.
+ pos = 0;
+ beg = 0;
+ end = 0;
+ cur = 0;
+ chars = dn.toCharArray();
+
+ String attType = nextAT();
+ if (attType == null) {
+ return null;
+ }
+ while (true) {
+ String attValue = "";
+
+ if (pos == length) {
+ return null;
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ attValue = quotedAV();
+ break;
+ case '#':
+ attValue = hexAV();
+ break;
+ case '+':
+ case ',':
+ case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+ //empty attribute value
+ break;
+ default:
+ attValue = escapedAV();
+ }
+
+ if (attributeType.equalsIgnoreCase(attType)) {
+ return attValue;
+ }
+
+ if (pos >= length) {
+ return null;
+ }
+
+ if (chars[pos] == ',' || chars[pos] == ';') {
+ } else if (chars[pos] != '+') {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ pos++;
+ attType = nextAT();
+ if (attType == null) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+ } catch (IOException e) {
+ // Parse error shouldn't happen, because we only handle DNs that
+ // X500Principal.getName() returns, which shouldn't be malformed.
+ Log.e(TAG, "Failed to parse DN: " + dn);
+ return null;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/net/DomainNameValidator.java b/core/java/com/android/internal/net/DomainNameValidator.java
new file mode 100644
index 0000000..dbd5019
--- /dev/null
+++ b/core/java/com/android/internal/net/DomainNameValidator.java
@@ -0,0 +1,277 @@
+/*
+ * 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 com.android.internal.net;
+
+
+import android.util.Config;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.security.auth.x500.X500Principal;
+
+/** @hide */
+public class DomainNameValidator {
+ private final static String TAG = "DomainNameValidator";
+
+ private static final boolean DEBUG = false;
+ private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+ private static Pattern QUICK_IP_PATTERN;
+ static {
+ try {
+ QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$");
+ } catch (PatternSyntaxException e) {}
+ }
+
+ private static final int ALT_DNS_NAME = 2;
+ private static final int ALT_IPA_NAME = 7;
+
+ /**
+ * Checks the site certificate against the domain name of the site being visited
+ * @param certificate The certificate to check
+ * @param thisDomain The domain name of the site being visited
+ * @return True iff if there is a domain match as specified by RFC2818
+ */
+ public static boolean match(X509Certificate certificate, String thisDomain) {
+ if (certificate == null || thisDomain == null || thisDomain.length() == 0) {
+ return false;
+ }
+
+ thisDomain = thisDomain.toLowerCase();
+ if (!isIpAddress(thisDomain)) {
+ return matchDns(certificate, thisDomain);
+ } else {
+ return matchIpAddress(certificate, thisDomain);
+ }
+ }
+
+ /**
+ * @return True iff the domain name is specified as an IP address
+ */
+ private static boolean isIpAddress(String domain) {
+ boolean rval = (domain != null && domain.length() != 0);
+ if (rval) {
+ try {
+ // do a quick-dirty IP match first to avoid DNS lookup
+ rval = QUICK_IP_PATTERN.matcher(domain).matches();
+ if (rval) {
+ rval = domain.equals(
+ InetAddress.getByName(domain).getHostAddress());
+ }
+ } catch (UnknownHostException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "unknown host exception";
+ }
+
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.isIpAddress(): " + errorMessage);
+ }
+
+ rval = false;
+ }
+ }
+
+ return rval;
+ }
+
+ /**
+ * Checks the site certificate against the IP domain name of the site being visited
+ * @param certificate The certificate to check
+ * @param thisDomain The DNS domain name of the site being visited
+ * @return True iff if there is a domain match as specified by RFC2818
+ */
+ private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchIpAddress(): this domain: " + thisDomain);
+ }
+
+ try {
+ Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+ if (subjectAltNames != null) {
+ Iterator i = subjectAltNames.iterator();
+ while (i.hasNext()) {
+ List altNameEntry = (List)(i.next());
+ if (altNameEntry != null && 2 <= altNameEntry.size()) {
+ Integer altNameType = (Integer)(altNameEntry.get(0));
+ if (altNameType != null) {
+ if (altNameType.intValue() == ALT_IPA_NAME) {
+ String altName = (String)(altNameEntry.get(1));
+ if (altName != null) {
+ if (LOG_ENABLED) {
+ Log.v(TAG, "alternative IP: " + altName);
+ }
+ if (thisDomain.equalsIgnoreCase(altName)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (CertificateParsingException e) {}
+
+ return false;
+ }
+
+ /**
+ * Checks the site certificate against the DNS domain name of the site being visited
+ * @param certificate The certificate to check
+ * @param thisDomain The DNS domain name of the site being visited
+ * @return True iff if there is a domain match as specified by RFC2818
+ */
+ private static boolean matchDns(X509Certificate certificate, String thisDomain) {
+ boolean hasDns = false;
+ try {
+ Collection subjectAltNames = certificate.getSubjectAlternativeNames();
+ if (subjectAltNames != null) {
+ Iterator i = subjectAltNames.iterator();
+ while (i.hasNext()) {
+ List altNameEntry = (List)(i.next());
+ if (altNameEntry != null && 2 <= altNameEntry.size()) {
+ Integer altNameType = (Integer)(altNameEntry.get(0));
+ if (altNameType != null) {
+ if (altNameType.intValue() == ALT_DNS_NAME) {
+ hasDns = true;
+ String altName = (String)(altNameEntry.get(1));
+ if (altName != null) {
+ if (matchDns(thisDomain, altName)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (CertificateParsingException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "failed to parse certificate";
+ }
+
+ Log.w(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+ return false;
+ }
+
+ if (!hasDns) {
+ final String cn = new DNParser(certificate.getSubjectX500Principal())
+ .find("cn");
+ if (LOG_ENABLED) {
+ Log.v(TAG, "Validating subject: DN:"
+ + certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL)
+ + " CN:" + cn);
+ }
+ if (cn != null) {
+ return matchDns(thisDomain, cn);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param thisDomain The domain name of the site being visited
+ * @param thatDomain The domain name from the certificate
+ * @return True iff thisDomain matches thatDomain as specified by RFC2818
+ */
+ // not private for testing
+ public static boolean matchDns(String thisDomain, String thatDomain) {
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchDns():" +
+ " this domain: " + thisDomain +
+ " that domain: " + thatDomain);
+ }
+
+ if (thisDomain == null || thisDomain.length() == 0 ||
+ thatDomain == null || thatDomain.length() == 0) {
+ return false;
+ }
+
+ thatDomain = thatDomain.toLowerCase();
+
+ // (a) domain name strings are equal, ignoring case: X matches X
+ boolean rval = thisDomain.equals(thatDomain);
+ if (!rval) {
+ String[] thisDomainTokens = thisDomain.split("\\.");
+ String[] thatDomainTokens = thatDomain.split("\\.");
+
+ int thisDomainTokensNum = thisDomainTokens.length;
+ int thatDomainTokensNum = thatDomainTokens.length;
+
+ // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X
+ if (thisDomainTokensNum >= thatDomainTokensNum) {
+ for (int i = thatDomainTokensNum - 1; i >= 0; --i) {
+ rval = thisDomainTokens[i].equals(thatDomainTokens[i]);
+ if (!rval) {
+ // (c) OR we have a special *-match:
+ // *.Y.X matches Z.Y.X but *.X doesn't match Z.Y.X
+ rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum);
+ if (rval) {
+ rval = thatDomainTokens[0].equals("*");
+ if (!rval) {
+ // (d) OR we have a *-component match:
+ // f*.com matches foo.com but not bar.com
+ rval = domainTokenMatch(
+ thisDomainTokens[0], thatDomainTokens[0]);
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ // (e) OR thatHost has a '*.'-prefix of thisHost:
+ // *.Y.X matches Y.X
+ rval = thatDomain.equals("*." + thisDomain);
+ }
+ }
+
+ return rval;
+ }
+
+ /**
+ * @param thisDomainToken The domain token from the current domain name
+ * @param thatDomainToken The domain token from the certificate
+ * @return True iff thisDomainToken matches thatDomainToken, using the
+ * wildcard match as specified by RFC2818-3.1. For example, f*.com must
+ * match foo.com but not bar.com
+ */
+ private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) {
+ if (thisDomainToken != null && thatDomainToken != null) {
+ int starIndex = thatDomainToken.indexOf('*');
+ if (starIndex >= 0) {
+ if (thatDomainToken.length() - 1 <= thisDomainToken.length()) {
+ String prefix = thatDomainToken.substring(0, starIndex);
+ String suffix = thatDomainToken.substring(starIndex + 1);
+
+ return thisDomainToken.startsWith(prefix) && thisDomainToken.endsWith(suffix);
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 9e5bdff..2369d25 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -20,7 +20,7 @@ package com.android.internal.os;
import android.content.Context;
import android.content.res.XmlResourceParser;
-import com.android.common.XmlUtils;
+import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
new file mode 100644
index 0000000..592a8fa
--- /dev/null
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2006 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 com.android.internal.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+
+/**
+ * This is a quick and dirty implementation of XmlSerializer that isn't horribly
+ * painfully slow like the normal one. It only does what is needed for the
+ * specific XML files being written with it.
+ */
+public class FastXmlSerializer implements XmlSerializer {
+ private static final String ESCAPE_TABLE[] = new String[] {
+ null, null, null, null, null, null, null, null, // 0-7
+ null, null, null, null, null, null, null, null, // 8-15
+ null, null, null, null, null, null, null, null, // 16-23
+ null, null, null, null, null, null, null, null, // 24-31
+ null, null, "&quot;", null, null, null, "&amp;", null, // 32-39
+ null, null, null, null, null, null, null, null, // 40-47
+ null, null, null, null, null, null, null, null, // 48-55
+ null, null, null, null, "&lt;", null, "&gt;", null, // 56-63
+ };
+
+ private static final int BUFFER_LEN = 8192;
+
+ private final char[] mText = new char[BUFFER_LEN];
+ private int mPos;
+
+ private Writer mWriter;
+
+ private OutputStream mOutputStream;
+ private CharsetEncoder mCharset;
+ private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
+
+ private boolean mInTag;
+
+ private void append(char c) throws IOException {
+ int pos = mPos;
+ if (pos >= (BUFFER_LEN-1)) {
+ flush();
+ pos = mPos;
+ }
+ mText[pos] = c;
+ mPos = pos+1;
+ }
+
+ private void append(String str, int i, final int length) throws IOException {
+ if (length > BUFFER_LEN) {
+ final int end = i + length;
+ while (i < end) {
+ int next = i + BUFFER_LEN;
+ append(str, i, next<end ? BUFFER_LEN : (end-i));
+ i = next;
+ }
+ return;
+ }
+ int pos = mPos;
+ if ((pos+length) > BUFFER_LEN) {
+ flush();
+ pos = mPos;
+ }
+ str.getChars(i, i+length, mText, pos);
+ mPos = pos + length;
+ }
+
+ private void append(char[] buf, int i, final int length) throws IOException {
+ if (length > BUFFER_LEN) {
+ final int end = i + length;
+ while (i < end) {
+ int next = i + BUFFER_LEN;
+ append(buf, i, next<end ? BUFFER_LEN : (end-i));
+ i = next;
+ }
+ return;
+ }
+ int pos = mPos;
+ if ((pos+length) > BUFFER_LEN) {
+ flush();
+ pos = mPos;
+ }
+ System.arraycopy(buf, i, mText, pos, length);
+ mPos = pos + length;
+ }
+
+ private void append(String str) throws IOException {
+ append(str, 0, str.length());
+ }
+
+ private void escapeAndAppendString(final String string) throws IOException {
+ final int N = string.length();
+ final char NE = (char)ESCAPE_TABLE.length;
+ final String[] escapes = ESCAPE_TABLE;
+ int lastPos = 0;
+ int pos;
+ for (pos=0; pos<N; pos++) {
+ char c = string.charAt(pos);
+ if (c >= NE) continue;
+ String escape = escapes[c];
+ if (escape == null) continue;
+ if (lastPos < pos) append(string, lastPos, pos-lastPos);
+ lastPos = pos + 1;
+ append(escape);
+ }
+ if (lastPos < pos) append(string, lastPos, pos-lastPos);
+ }
+
+ private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
+ final char NE = (char)ESCAPE_TABLE.length;
+ final String[] escapes = ESCAPE_TABLE;
+ int end = start+len;
+ int lastPos = start;
+ int pos;
+ for (pos=start; pos<end; pos++) {
+ char c = buf[pos];
+ if (c >= NE) continue;
+ String escape = escapes[c];
+ if (escape == null) continue;
+ if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+ lastPos = pos + 1;
+ append(escape);
+ }
+ if (lastPos < pos) append(buf, lastPos, pos-lastPos);
+ }
+
+ public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ append(' ');
+ if (namespace != null) {
+ append(namespace);
+ append(':');
+ }
+ append(name);
+ append("=\"");
+
+ escapeAndAppendString(value);
+ append('"');
+ return this;
+ }
+
+ public void cdsect(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void comment(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void docdecl(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
+ flush();
+ }
+
+ public XmlSerializer endTag(String namespace, String name) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ if (mInTag) {
+ append(" />\n");
+ } else {
+ append("</");
+ if (namespace != null) {
+ append(namespace);
+ append(':');
+ }
+ append(name);
+ append(">\n");
+ }
+ mInTag = false;
+ return this;
+ }
+
+ public void entityRef(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ private void flushBytes() throws IOException {
+ int position;
+ if ((position = mBytes.position()) > 0) {
+ mBytes.flip();
+ mOutputStream.write(mBytes.array(), 0, position);
+ mBytes.clear();
+ }
+ }
+
+ public void flush() throws IOException {
+ //Log.i("PackageManager", "flush mPos=" + mPos);
+ if (mPos > 0) {
+ if (mOutputStream != null) {
+ CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
+ CoderResult result = mCharset.encode(charBuffer, mBytes, true);
+ while (true) {
+ if (result.isError()) {
+ throw new IOException(result.toString());
+ } else if (result.isOverflow()) {
+ flushBytes();
+ result = mCharset.encode(charBuffer, mBytes, true);
+ continue;
+ }
+ break;
+ }
+ flushBytes();
+ mOutputStream.flush();
+ } else {
+ mWriter.write(mText, 0, mPos);
+ mWriter.flush();
+ }
+ mPos = 0;
+ }
+ }
+
+ public int getDepth() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean getFeature(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getName() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getNamespace() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPrefix(String namespace, boolean generatePrefix)
+ throws IllegalArgumentException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getProperty(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void processingInstruction(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setFeature(String name, boolean state) throws IllegalArgumentException,
+ IllegalStateException {
+ if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+ return;
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ public void setOutput(OutputStream os, String encoding) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ if (os == null)
+ throw new IllegalArgumentException();
+ if (true) {
+ try {
+ mCharset = Charset.forName(encoding).newEncoder();
+ } catch (IllegalCharsetNameException e) {
+ throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+ encoding).initCause(e));
+ } catch (UnsupportedCharsetException e) {
+ throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
+ encoding).initCause(e));
+ }
+ mOutputStream = os;
+ } else {
+ setOutput(
+ encoding == null
+ ? new OutputStreamWriter(os)
+ : new OutputStreamWriter(os, encoding));
+ }
+ }
+
+ public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ mWriter = writer;
+ }
+
+ public void setPrefix(String prefix, String namespace) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setProperty(String name, Object value) throws IllegalArgumentException,
+ IllegalStateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void startDocument(String encoding, Boolean standalone) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ append("<?xml version='1.0' encoding='utf-8' standalone='"
+ + (standalone ? "yes" : "no") + "' ?>\n");
+ }
+
+ public XmlSerializer startTag(String namespace, String name) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ if (mInTag) {
+ append(">\n");
+ }
+ append('<');
+ if (namespace != null) {
+ append(namespace);
+ append(':');
+ }
+ append(name);
+ mInTag = true;
+ return this;
+ }
+
+ public XmlSerializer text(char[] buf, int start, int len) throws IOException,
+ IllegalArgumentException, IllegalStateException {
+ if (mInTag) {
+ append(">");
+ mInTag = false;
+ }
+ escapeAndAppendString(buf, start, len);
+ return this;
+ }
+
+ public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
+ IllegalStateException {
+ if (mInTag) {
+ append(">");
+ mInTag = false;
+ }
+ escapeAndAppendString(text);
+ return this;
+ }
+
+}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
new file mode 100644
index 0000000..8d8df16
--- /dev/null
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2006 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 com.android.internal.util;
+
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.util.Xml;
+
+/** {@hide} */
+public class XmlUtils
+{
+
+ public static void skipCurrentTag(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ }
+ }
+
+ public static final int
+ convertValueToList(CharSequence value, String[] options, int defaultValue)
+ {
+ if (null != value) {
+ for (int i = 0; i < options.length; i++) {
+ if (value.equals(options[i]))
+ return i;
+ }
+ }
+
+ return defaultValue;
+ }
+
+ public static final boolean
+ convertValueToBoolean(CharSequence value, boolean defaultValue)
+ {
+ boolean result = false;
+
+ if (null == value)
+ return defaultValue;
+
+ if (value.equals("1")
+ || value.equals("true")
+ || value.equals("TRUE"))
+ result = true;
+
+ return result;
+ }
+
+ public static final int
+ convertValueToInt(CharSequence charSeq, int defaultValue)
+ {
+ if (null == charSeq)
+ return defaultValue;
+
+ String nm = charSeq.toString();
+
+ // XXX This code is copied from Integer.decode() so we don't
+ // have to instantiate an Integer!
+
+ int value;
+ int sign = 1;
+ int index = 0;
+ int len = nm.length();
+ int base = 10;
+
+ if ('-' == nm.charAt(0)) {
+ sign = -1;
+ index++;
+ }
+
+ if ('0' == nm.charAt(index)) {
+ // Quick check for a zero by itself
+ if (index == (len - 1))
+ return 0;
+
+ char c = nm.charAt(index + 1);
+
+ if ('x' == c || 'X' == c) {
+ index += 2;
+ base = 16;
+ } else {
+ index++;
+ base = 8;
+ }
+ }
+ else if ('#' == nm.charAt(index))
+ {
+ index++;
+ base = 16;
+ }
+
+ return Integer.parseInt(nm.substring(index), base) * sign;
+ }
+
+ public static final int
+ convertValueToUnsignedInt(String value, int defaultValue)
+ {
+ if (null == value)
+ return defaultValue;
+
+ return parseUnsignedIntAttribute(value);
+ }
+
+ public static final int
+ parseUnsignedIntAttribute(CharSequence charSeq)
+ {
+ String value = charSeq.toString();
+
+ long bits;
+ int index = 0;
+ int len = value.length();
+ int base = 10;
+
+ if ('0' == value.charAt(index)) {
+ // Quick check for zero by itself
+ if (index == (len - 1))
+ return 0;
+
+ char c = value.charAt(index + 1);
+
+ if ('x' == c || 'X' == c) { // check for hex
+ index += 2;
+ base = 16;
+ } else { // check for octal
+ index++;
+ base = 8;
+ }
+ } else if ('#' == value.charAt(index)) {
+ index++;
+ base = 16;
+ }
+
+ return (int) Long.parseLong(value.substring(index), base);
+ }
+
+ /**
+ * Flatten a Map into an output stream as XML. The map can later be
+ * read back with readMapXml().
+ *
+ * @param val The map to be flattened.
+ * @param out Where to write the XML data.
+ *
+ * @see #writeMapXml(Map, String, XmlSerializer)
+ * @see #writeListXml
+ * @see #writeValueXml
+ * @see #readMapXml
+ */
+ public static final void writeMapXml(Map val, OutputStream out)
+ throws XmlPullParserException, java.io.IOException {
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ writeMapXml(val, null, serializer);
+ serializer.endDocument();
+ }
+
+ /**
+ * Flatten a List into an output stream as XML. The list can later be
+ * read back with readListXml().
+ *
+ * @param val The list to be flattened.
+ * @param out Where to write the XML data.
+ *
+ * @see #writeListXml(List, String, XmlSerializer)
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readListXml
+ */
+ public static final void writeListXml(List val, OutputStream out)
+ throws XmlPullParserException, java.io.IOException
+ {
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ writeListXml(val, null, serializer);
+ serializer.endDocument();
+ }
+
+ /**
+ * Flatten a Map into an XmlSerializer. The map can later be read back
+ * with readThisMapXml().
+ *
+ * @param val The map to be flattened.
+ * @param name Name attribute to include with this list's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the map into.
+ *
+ * @see #writeMapXml(Map, OutputStream)
+ * @see #writeListXml
+ * @see #writeValueXml
+ * @see #readMapXml
+ */
+ public static final void writeMapXml(Map val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException
+ {
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ Set s = val.entrySet();
+ Iterator i = s.iterator();
+
+ out.startTag(null, "map");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ while (i.hasNext()) {
+ Map.Entry e = (Map.Entry)i.next();
+ writeValueXml(e.getValue(), (String)e.getKey(), out);
+ }
+
+ out.endTag(null, "map");
+ }
+
+ /**
+ * Flatten a List into an XmlSerializer. The list can later be read back
+ * with readThisListXml().
+ *
+ * @param val The list to be flattened.
+ * @param name Name attribute to include with this list's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the list into.
+ *
+ * @see #writeListXml(List, OutputStream)
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readListXml
+ */
+ public static final void writeListXml(List val, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException
+ {
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "list");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ int N = val.size();
+ int i=0;
+ while (i < N) {
+ writeValueXml(val.get(i), null, out);
+ i++;
+ }
+
+ out.endTag(null, "list");
+ }
+
+ /**
+ * Flatten a byte[] into an XmlSerializer. The list can later be read back
+ * with readThisByteArrayXml().
+ *
+ * @param val The byte array to be flattened.
+ * @param name Name attribute to include with this array's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the array into.
+ *
+ * @see #writeMapXml
+ * @see #writeValueXml
+ */
+ public static final void writeByteArrayXml(byte[] val, String name,
+ XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "byte-array");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ final int N = val.length;
+ out.attribute(null, "num", Integer.toString(N));
+
+ StringBuilder sb = new StringBuilder(val.length*2);
+ for (int i=0; i<N; i++) {
+ int b = val[i];
+ int h = b>>4;
+ sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+ h = b&0xff;
+ sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+ }
+
+ out.text(sb.toString());
+
+ out.endTag(null, "byte-array");
+ }
+
+ /**
+ * Flatten an int[] into an XmlSerializer. The list can later be read back
+ * with readThisIntArrayXml().
+ *
+ * @param val The int array to be flattened.
+ * @param name Name attribute to include with this array's tag, or null for
+ * none.
+ * @param out XmlSerializer to write the array into.
+ *
+ * @see #writeMapXml
+ * @see #writeValueXml
+ * @see #readThisIntArrayXml
+ */
+ public static final void writeIntArrayXml(int[] val, String name,
+ XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException {
+
+ if (val == null) {
+ out.startTag(null, "null");
+ out.endTag(null, "null");
+ return;
+ }
+
+ out.startTag(null, "int-array");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+
+ final int N = val.length;
+ out.attribute(null, "num", Integer.toString(N));
+
+ for (int i=0; i<N; i++) {
+ out.startTag(null, "item");
+ out.attribute(null, "value", Integer.toString(val[i]));
+ out.endTag(null, "item");
+ }
+
+ out.endTag(null, "int-array");
+ }
+
+ /**
+ * Flatten an object's value into an XmlSerializer. The value can later
+ * be read back with readThisValueXml().
+ *
+ * Currently supported value types are: null, String, Integer, Long,
+ * Float, Double Boolean, Map, List.
+ *
+ * @param v The object to be flattened.
+ * @param name Name attribute to include with this value's tag, or null
+ * for none.
+ * @param out XmlSerializer to write the object into.
+ *
+ * @see #writeMapXml
+ * @see #writeListXml
+ * @see #readValueXml
+ */
+ public static final void writeValueXml(Object v, String name, XmlSerializer out)
+ throws XmlPullParserException, java.io.IOException
+ {
+ String typeStr;
+ if (v == null) {
+ out.startTag(null, "null");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+ out.endTag(null, "null");
+ return;
+ } else if (v instanceof String) {
+ out.startTag(null, "string");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+ out.text(v.toString());
+ out.endTag(null, "string");
+ return;
+ } else if (v instanceof Integer) {
+ typeStr = "int";
+ } else if (v instanceof Long) {
+ typeStr = "long";
+ } else if (v instanceof Float) {
+ typeStr = "float";
+ } else if (v instanceof Double) {
+ typeStr = "double";
+ } else if (v instanceof Boolean) {
+ typeStr = "boolean";
+ } else if (v instanceof byte[]) {
+ writeByteArrayXml((byte[])v, name, out);
+ return;
+ } else if (v instanceof int[]) {
+ writeIntArrayXml((int[])v, name, out);
+ return;
+ } else if (v instanceof Map) {
+ writeMapXml((Map)v, name, out);
+ return;
+ } else if (v instanceof List) {
+ writeListXml((List)v, name, out);
+ return;
+ } else if (v instanceof CharSequence) {
+ // XXX This is to allow us to at least write something if
+ // we encounter styled text... but it means we will drop all
+ // of the styling information. :(
+ out.startTag(null, "string");
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+ out.text(v.toString());
+ out.endTag(null, "string");
+ return;
+ } else {
+ throw new RuntimeException("writeValueXml: unable to write value " + v);
+ }
+
+ out.startTag(null, typeStr);
+ if (name != null) {
+ out.attribute(null, "name", name);
+ }
+ out.attribute(null, "value", v.toString());
+ out.endTag(null, typeStr);
+ }
+
+ /**
+ * Read a HashMap from an InputStream containing XML. The stream can
+ * previously have been written by writeMapXml().
+ *
+ * @param in The InputStream from which to read.
+ *
+ * @return HashMap The resulting map.
+ *
+ * @see #readListXml
+ * @see #readValueXml
+ * @see #readThisMapXml
+ * #see #writeMapXml
+ */
+ public static final HashMap readMapXml(InputStream in)
+ throws XmlPullParserException, java.io.IOException
+ {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ return (HashMap)readValueXml(parser, new String[1]);
+ }
+
+ /**
+ * Read an ArrayList from an InputStream containing XML. The stream can
+ * previously have been written by writeListXml().
+ *
+ * @param in The InputStream from which to read.
+ *
+ * @return HashMap The resulting list.
+ *
+ * @see #readMapXml
+ * @see #readValueXml
+ * @see #readThisListXml
+ * @see #writeListXml
+ */
+ public static final ArrayList readListXml(InputStream in)
+ throws XmlPullParserException, java.io.IOException
+ {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ return (ArrayList)readValueXml(parser, new String[1]);
+ }
+
+ /**
+ * Read a HashMap object from an XmlPullParser. The XML data could
+ * previously have been generated by writeMapXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the map.
+ *
+ * @param parser The XmlPullParser from which to read the map data.
+ * @param endTag Name of the tag that will end the map, usually "map".
+ * @param name An array of one string, used to return the name attribute
+ * of the map's tag.
+ *
+ * @return HashMap The newly generated map.
+ *
+ * @see #readMapXml
+ */
+ public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException
+ {
+ HashMap map = new HashMap();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ Object val = readThisValueXml(parser, name);
+ if (name[0] != null) {
+ //System.out.println("Adding to map: " + name + " -> " + val);
+ map.put(name[0], val);
+ } else {
+ throw new XmlPullParserException(
+ "Map value without name attribute: " + parser.getName());
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return map;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read an ArrayList object from an XmlPullParser. The XML data could
+ * previously have been generated by writeListXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return HashMap The newly generated list.
+ *
+ * @see #readListXml
+ */
+ public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException
+ {
+ ArrayList list = new ArrayList();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ Object val = readThisValueXml(parser, name);
+ list.add(val);
+ //System.out.println("Adding to list: " + val);
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return list;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read an int[] object from an XmlPullParser. The XML data could
+ * previously have been generated by writeIntArrayXml(). The XmlPullParser
+ * must be positioned <em>after</em> the tag that begins the list.
+ *
+ * @param parser The XmlPullParser from which to read the list data.
+ * @param endTag Name of the tag that will end the list, usually "list".
+ * @param name An array of one string, used to return the name attribute
+ * of the list's tag.
+ *
+ * @return Returns a newly generated int[].
+ *
+ * @see #readListXml
+ */
+ public static final int[] readThisIntArrayXml(XmlPullParser parser,
+ String endTag, String[] name)
+ throws XmlPullParserException, java.io.IOException {
+
+ int num;
+ try {
+ num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException(
+ "Need num attribute in byte-array");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Not a number in num attribute in byte-array");
+ }
+
+ int[] array = new int[num];
+ int i = 0;
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ try {
+ array[i] = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } catch (NullPointerException e) {
+ throw new XmlPullParserException(
+ "Need value attribute in item");
+ } catch (NumberFormatException e) {
+ throw new XmlPullParserException(
+ "Not a number in value attribute in item");
+ }
+ } else {
+ throw new XmlPullParserException(
+ "Expected item tag at: " + parser.getName());
+ }
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return array;
+ } else if (parser.getName().equals("item")) {
+ i++;
+ } else {
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: "
+ + parser.getName());
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Read a flattened object from an XmlPullParser. The XML data could
+ * previously have been written with writeMapXml(), writeListXml(), or
+ * writeValueXml(). The XmlPullParser must be positioned <em>at</em> the
+ * tag that defines the value.
+ *
+ * @param parser The XmlPullParser from which to read the object.
+ * @param name An array of one string, used to return the name attribute
+ * of the value's tag.
+ *
+ * @return Object The newly generated value object.
+ *
+ * @see #readMapXml
+ * @see #readListXml
+ * @see #writeValueXml
+ */
+ public static final Object readValueXml(XmlPullParser parser, String[] name)
+ throws XmlPullParserException, java.io.IOException
+ {
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ return readThisValueXml(parser, name);
+ } else if (eventType == parser.END_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected end tag at: " + parser.getName());
+ } else if (eventType == parser.TEXT) {
+ throw new XmlPullParserException(
+ "Unexpected text: " + parser.getText());
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Unexpected end of document");
+ }
+
+ private static final Object readThisValueXml(XmlPullParser parser, String[] name)
+ throws XmlPullParserException, java.io.IOException
+ {
+ final String valueName = parser.getAttributeValue(null, "name");
+ final String tagName = parser.getName();
+
+ //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName);
+
+ Object res;
+
+ if (tagName.equals("null")) {
+ res = null;
+ } else if (tagName.equals("string")) {
+ String value = "";
+ int eventType;
+ while ((eventType = parser.next()) != parser.END_DOCUMENT) {
+ if (eventType == parser.END_TAG) {
+ if (parser.getName().equals("string")) {
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + value);
+ return value;
+ }
+ throw new XmlPullParserException(
+ "Unexpected end tag in <string>: " + parser.getName());
+ } else if (eventType == parser.TEXT) {
+ value += parser.getText();
+ } else if (eventType == parser.START_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected start tag in <string>: " + parser.getName());
+ }
+ }
+ throw new XmlPullParserException(
+ "Unexpected end of document in <string>");
+ } else if (tagName.equals("int")) {
+ res = Integer.parseInt(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("long")) {
+ res = Long.valueOf(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("float")) {
+ res = new Float(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("double")) {
+ res = new Double(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("boolean")) {
+ res = Boolean.valueOf(parser.getAttributeValue(null, "value"));
+ } else if (tagName.equals("int-array")) {
+ parser.next();
+ res = readThisIntArrayXml(parser, "int-array", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ } else if (tagName.equals("map")) {
+ parser.next();
+ res = readThisMapXml(parser, "map", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ } else if (tagName.equals("list")) {
+ parser.next();
+ res = readThisListXml(parser, "list", name);
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ } else {
+ throw new XmlPullParserException(
+ "Unknown tag: " + tagName);
+ }
+
+ // Skip through to end tag.
+ int eventType;
+ while ((eventType = parser.next()) != parser.END_DOCUMENT) {
+ if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(tagName)) {
+ name[0] = valueName;
+ //System.out.println("Returning value for " + valueName + ": " + res);
+ return res;
+ }
+ throw new XmlPullParserException(
+ "Unexpected end tag in <" + tagName + ">: " + parser.getName());
+ } else if (eventType == parser.TEXT) {
+ throw new XmlPullParserException(
+ "Unexpected text in <" + tagName + ">: " + parser.getName());
+ } else if (eventType == parser.START_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected start tag in <" + tagName + ">: " + parser.getName());
+ }
+ }
+ throw new XmlPullParserException(
+ "Unexpected end of document in <" + tagName + ">");
+ }
+
+ public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException
+ {
+ int type;
+ while ((type=parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+ ", expected " + firstElementName);
+ }
+ }
+
+ public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
+ {
+ int type;
+ while ((type=parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ ;
+ }
+ }
+}