1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
2045fed27dbea77923779f6142c0341b3c80a77eddMarc Blankimport android.content.Context;
2145fed27dbea77923779f6142c0341b3c80a77eddMarc Blank
227c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport com.android.exchange.Eas;
23ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport com.android.exchange.EasException;
243c193c425ff383dbc94514d055ae6778821d8471Martin Hibdonimport com.android.exchange.service.EasService;
25c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blankimport com.android.exchange.utility.FileLogger;
26942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedyimport com.android.mail.utils.LogUtils;
2745fed27dbea77923779f6142c0341b3c80a77eddMarc Blankimport com.google.common.annotations.VisibleForTesting;
28ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
2961563f075e07b9961ede4000d815865edc0f2497Marc Blankimport java.io.ByteArrayOutputStream;
307c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport java.io.FileNotFoundException;
317c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport java.io.FileOutputStream;
327c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport java.io.IOException;
337c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport java.io.InputStream;
343d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shraunerimport java.util.ArrayDeque;
357c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankimport java.util.ArrayList;
363d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shraunerimport java.util.Arrays;
373d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shraunerimport java.util.Deque;
387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank
39ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
40ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Extremely fast and lightweight WBXML parser, implementing only the subset of WBXML that
413d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner * EAS uses (as defined in the EAS specification).
423d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *
433d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner * Supports:
443d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      WBXML tokens to encode XML tags
453d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      WBXML code pages to support multiple XML namespaces
463d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      Inline strings
473d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      Opaque data
483d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *
493d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner * Does not support: (throws EasParserException)
503d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      String tables
513d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      Entities
523d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      Processing instructions
533d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner *      Attribute encoding
54ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
55ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
567c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic abstract class Parser {
5734d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank    private static final boolean LOG_VERBOSE = false;
5834d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank
59110837ebff288a75f9bda067c38e2c46797d99b5Alon Albert    private static final String LOG_TAG = Eas.LOG_TAG;
60ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu
61ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // The following constants are Wbxml standard
62ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public static final int START_DOCUMENT = 0;
635bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner    public static final int END_DOCUMENT = 1;
645bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner    private static final int DONE = 1;
655bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner    private static final int START = 2;
66ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public static final int END = 3;
675bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner    private static final int TEXT = 4;
685bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner    private static final int OPAQUE = 5;
69ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private static final int NOT_ENDED = Integer.MIN_VALUE;
70ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private static final int EOF_BYTE = -1;
7126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
72ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu    private boolean capture = false;
73ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu
74ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private ArrayList<Integer> captureArray;
75ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
76ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // The input stream for this parser
77ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private InputStream in;
78ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
79ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // The stack of names of tags being processed; used when debug = true
80ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private String[] nameArray = new String[32];
81ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
823d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    public class Tag {
833d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        private final int mPage;
843d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        private final int mIndex;
853d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        // Whether the tag is associated with content (a value)
863d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        public final boolean mNoContent;
873d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        private final String mName;
883d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
893d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        public Tag(final int page, final int id) {
903d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            mPage = page;
913d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            // The tag is in the low 6 bits
923d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            mIndex = id & Tags.PAGE_MASK;
933d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            // If the high bit is set, there is content (a value) to be read
943d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            mNoContent = (id & Wbxml.WITH_CONTENT) == 0;
953d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            if (Tags.isGlobalTag(mIndex)) {
963d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                mName = "unsupported-WBXML";
973d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            } else if (!Tags.isValidTag(mPage, mIndex)) {
983d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                mName = "unknown";
993d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            } else {
1003d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                mName = Tags.getTagName(mPage, mIndex);
1013d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            }
1023d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        }
1033d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner
1043d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        public int getTagNum() {
1054d07dd49522f0573cb6ea1920f072bf677ed60f9Jay Shrauner            if (Tags.isGlobalTag(mIndex)) {
1064d07dd49522f0573cb6ea1920f072bf677ed60f9Jay Shrauner                return mIndex;
1074d07dd49522f0573cb6ea1920f072bf677ed60f9Jay Shrauner            }
1083d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            return (mPage << Tags.PAGE_SHIFT) | mIndex;
1093d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        }
1103d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner
1113d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        @Override
1123d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        public String toString() {
1133d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            return mName;
1143d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        }
1153d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    }
1163d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
117ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // The stack of tags being processed
1183d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    private final Deque<Tag> startTagArray = new ArrayDeque<Tag>();
119ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1203d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private Tag startTag;
121ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1223d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    // The type of the last token read (eg, TEXT, OPAQUE, END, etc).
1233d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private int type;
124ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1253d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    // The current page. As of EAS 14.1, this is a value 0-24.
1263d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private int page;
127ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1283d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    // The current tag. The low order 6 bits contain the tag index and the
1293d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    // higher order bits the page number. The format matches that used for
1303d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    // the tag enums defined in Tags.java.
131ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public int tag;
132ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
133ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    // Whether the current tag is associated with content (a value)
134ae073cce37583f350ee4f1df4847c9c26eadb404Marc Blank    public boolean noContent;
135ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1363d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    // The value read, as a String
1373d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private String text;
138ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
13977186bb1a174432ef272584374942d8b9228e39cMarc Blank    // The value read, as bytes
1403d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private byte[] bytes;
14177186bb1a174432ef272584374942d8b9228e39cMarc Blank
142ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu    // TODO: Define a new parse exception type rather than lumping these in as IOExceptions.
143ff7e02603bc8196f411c0c491d74a42e747b7dc5Yu Ping Hu
144bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    /**
145bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank     * Generated when the parser comes to EOF prematurely during parsing (i.e. in error)
146bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank     */
147ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public class EofException extends IOException {
148ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private static final long serialVersionUID = 1L;
149ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
150ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
151bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    /**
152bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank     * An EmptyStreamException is an EofException that occurs reading the first byte in the parser's
153bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank     * input stream; in other words, the stream had no content.
154bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank     */
155bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    public class EmptyStreamException extends EofException {
156bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank        private static final long serialVersionUID = 1L;
157bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank    }
158bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank
159ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public class EodException extends IOException {
160ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private static final long serialVersionUID = 1L;
161ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
162ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
163ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public class EasParserException extends IOException {
164ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private static final long serialVersionUID = 1L;
1651431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank
1661431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank        EasParserException() {
1671431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            super("WBXML format error");
1681431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank        }
1691431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank
1703d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        EasParserException(final String reason) {
1711431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank            super(reason);
1721431215b5fc40d0d6498b0fe602ad4d1b8a66ff3Marc Blank        }
173ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
174ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
175ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public boolean parse() throws IOException, EasException {
176ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return false;
177ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
178ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1793d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    public Parser(final InputStream in) throws IOException {
18026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        setInput(in, true);
18126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    }
18226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
18326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    /**
18426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * Constructor for use when switching parsers within a input stream
18526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * @param parser an existing, initialized parser
18626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     * @throws IOException
18726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank     */
1883d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    public Parser(final Parser parser) throws IOException {
18926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        setInput(parser.in, false);
190ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
191ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
192bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank    protected InputStream getInput() {
193bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank        return in;
194bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank    }
195bb12673b0aa36ff0751ddcffe02223c6100f424eMarc Blank
196ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
197ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Turns on data capture; this is used to create test streams that represent "live" data and
198ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * can be used against the various parsers.
199ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void captureOn() {
201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        capture = true;
202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        captureArray = new ArrayList<Integer>();
203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
206ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Turns off data capture; writes the captured data to a specified file.
207ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
2083d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    public void captureOff(final Context context, final String file) {
209ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
2103d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            final FileOutputStream out = context.openFileOutput(file,
2113d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                    Context.MODE_WORLD_WRITEABLE);
212ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            out.write(captureArray.toString().getBytes());
213ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            out.close();
214ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (FileNotFoundException e) {
215ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // This is debug code; exceptions aren't interesting.
216ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } catch (IOException e) {
217ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // This is debug code; exceptions aren't interesting.
218ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
219ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
220ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
221ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
2223d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * Return the value of the current tag, as a byte array. Throws EasParserException
2233d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * if neither opaque nor text data is present. Never returns null--returns
2243d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * an empty byte[] array for empty data.
22577186bb1a174432ef272584374942d8b9228e39cMarc Blank     *
22677186bb1a174432ef272584374942d8b9228e39cMarc Blank     * @return the byte array value of the current tag
22777186bb1a174432ef272584374942d8b9228e39cMarc Blank     * @throws IOException
22877186bb1a174432ef272584374942d8b9228e39cMarc Blank     */
22977186bb1a174432ef272584374942d8b9228e39cMarc Blank    public byte[] getValueBytes() throws IOException {
2303d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final String name = startTag.toString();
2313d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
2323d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        getNext();
2333d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        // This means there was no value given, just <Foo/>; we'll return empty array
2343d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        if (type == END) {
2353d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            log("No value for tag: " + name);
2363d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            return new byte[0];
2373d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        } else if (type != OPAQUE && type != TEXT) {
2383d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            throw new EasParserException("Expected OPAQUE or TEXT data for tag " + name);
2393d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        }
2403d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
2413d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        // Save the value
2423d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        final byte[] val = type == OPAQUE ? bytes : text.getBytes("UTF-8");
2433d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        // Read the next token; it had better be the end of the current tag
2443d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        getNext();
2453d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        // If not, throw an exception
2463d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        if (type != END) {
2473d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            throw new EasParserException("No END found for tag " + name);
2483d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        }
2493d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        return val;
25077186bb1a174432ef272584374942d8b9228e39cMarc Blank    }
25177186bb1a174432ef272584374942d8b9228e39cMarc Blank
25277186bb1a174432ef272584374942d8b9228e39cMarc Blank    /**
2533d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * Return the value of the current tag, as a String. Throws EasParserException
2543d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * for non-text data. Never returns null--returns an empty string if no data.
255ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
256ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the String value of the current tag
257ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
258ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
259ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String getValue() throws IOException {
2603d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final String name = startTag.toString();
2613d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
2623d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        getNext();
263377230593dca7cb01483bfaf93959e5821f5f028Marc Blank        // This means there was no value given, just <Foo/>; we'll return empty string for now
264377230593dca7cb01483bfaf93959e5821f5f028Marc Blank        if (type == END) {
2653d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            log("No value for tag: " + name);
266377230593dca7cb01483bfaf93959e5821f5f028Marc Blank            return "";
2673d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        } else if (type != TEXT) {
2683d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            throw new EasParserException("Expected TEXT data for tag " + name);
269377230593dca7cb01483bfaf93959e5821f5f028Marc Blank        }
2703d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
271ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Save the value
2723d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        final String val = text;
273ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Read the next token; it had better be the end of the current tag
2743d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        getNext();
275ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // If not, throw an exception
276ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (type != END) {
2773d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            throw new EasParserException("No END found for tag " + name);
278ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
279ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return val;
280ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
281ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
282ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
2833d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * Return the value of the current tag, as an integer. Throws EasParserException
2843d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * for non text data, and text data that doesn't parse as an integer. Returns
2853d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner     * 0 for empty data.
286ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
287ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the integer value of the current tag
288ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
289ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
2903d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    public int getValueInt() throws IOException {
2913d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final String val = getValue();
2923d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        if (val.length() == 0) {
293377230593dca7cb01483bfaf93959e5821f5f028Marc Blank            return 0;
294377230593dca7cb01483bfaf93959e5821f5f028Marc Blank        }
2953d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
2963d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        int num;
2973d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        try {
2983d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            num = Integer.parseInt(val);
2993d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        } catch (NumberFormatException e) {
3003d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            throw new EasParserException("Tag " + startTag + ": " + e.getMessage());
301ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
3023d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        return num;
303ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
304ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
305ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
306ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Return the next tag found in the stream; special tags END and END_DOCUMENT are used to
307ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * mark the end of the current tag and end of document.  If we hit end of document without
308ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * looking for it, generate an EodException.  The tag returned consists of the page number
309ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * shifted PAGE_SHIFT bits OR'd with the tag retrieved from the stream.  Thus, all tags returned
310ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * are unique.
311ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
312ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param endingTag the tag that would represent the end of the tag we're processing
313ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the next tag found
314ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
315ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
3163d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    public int nextTag(final int endingTag) throws IOException {
3173d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        while (getNext() != DONE) {
318ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // If we're a start, set tag to include the page and return it
319ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (type == START) {
3203d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                tag = startTag.getTagNum();
321ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return tag;
322ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // If we're at the ending tag we're looking for, return the END signal
3233d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            } else if (type == END && startTag.getTagNum() == endingTag) {
324ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return END;
325ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
326ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
327ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // We're at end of document here.  If we're looking for it, return END_DOCUMENT
3283d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        if (endingTag == START_DOCUMENT) {
329ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return END_DOCUMENT;
330ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
331ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Otherwise, we've prematurely hit end of document, so exception out
332ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // EodException is a subclass of IOException; this will be treated as an IO error by
333bb0141b49e7eff978fa445249dc888461ea581e3Martin Hibdon        // EasService
334ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throw new EodException();
335ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
336ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
337ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
338ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Skip anything found in the stream until the end of the current tag is reached.  This can be
339ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * used to ignore stretches of xml that aren't needed by the parser.
340ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
341ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
342ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
343ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public void skipTag() throws IOException {
3443d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final int thisTag = startTag.getTagNum();
345ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Just loop until we hit the end of the current tag
3463d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        while (getNext() != DONE) {
3473d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            if (type == END && startTag.getTagNum() == thisTag) {
348ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                return;
349ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
350ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
351ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
352ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // If we're at end of document, that's bad
353ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        throw new EofException();
354ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
355ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
356ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
357ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Initializes the parser with an input stream; reads the first 4 bytes (which are always the
358ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * same in EAS, and then sets the tag table to point to page 0 (by definition, the starting
359ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * page).
360ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
361ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @param in the InputStream associated with this parser
362ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
363ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
3643d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    public void setInput(final InputStream in, final boolean initialize) throws IOException {
365ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        this.in = in;
3669f551ed0c2794a4ebb775df0dc5f714cf07f29b7Marc Blank        if ((in != null) && initialize) {
367bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            // If we fail on the very first byte, report an empty stream
368bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            try {
3693d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                final int version = readByte(); // version
370bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            } catch (EofException e) {
371bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank                throw new EmptyStreamException();
372bc2c2bd71a2026ac4bb54e6bf82f02df585f8a87Marc Blank            }
3733d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            readInt();  // public identifier
37426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            readInt();  // 106 (UTF-8)
3753d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            final int stringTableLength = readInt();  // string table length
3763d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            if (stringTableLength != 0) {
3773d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                throw new EasParserException("WBXML string table unsupported");
3783d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            }
37926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
380ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
381ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
38245fed27dbea77923779f6142c0341b3c80a77eddMarc Blank    @VisibleForTesting
3833d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    void resetInput(final InputStream in) {
38402e3436296ad5a638a1b6f349555e39964c6d13dMarc Blank        this.in = in;
38545fed27dbea77923779f6142c0341b3c80a77eddMarc Blank        try {
38645fed27dbea77923779f6142c0341b3c80a77eddMarc Blank            // Read leading zero
38745fed27dbea77923779f6142c0341b3c80a77eddMarc Blank            read();
38845fed27dbea77923779f6142c0341b3c80a77eddMarc Blank        } catch (IOException e) {
38945fed27dbea77923779f6142c0341b3c80a77eddMarc Blank        }
39002e3436296ad5a638a1b6f349555e39964c6d13dMarc Blank    }
39126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
3923d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    void log(final String str) {
3933c193c425ff383dbc94514d055ae6778821d8471Martin Hibdon        if (!EasService.getProtocolLogging()) {
3943d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            return;
3953d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        }
3963d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final String logStr;
397f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank        int cr = str.indexOf('\n');
398f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank        if (cr > 0) {
3993d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            logStr = str.substring(0, cr);
4003d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        } else {
4013d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            logStr = str;
402f9423affa52661c2f552df35f0b9ddeecd8fa8feMarc Blank        }
4033d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        final char [] charArray = new char[startTagArray.size() * 2];
4043d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        Arrays.fill(charArray, ' ');
4053d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        final String indent = new String(charArray);
4063c193c425ff383dbc94514d055ae6778821d8471Martin Hibdon        LogUtils.d(LOG_TAG, "%s", indent + logStr);
4073c193c425ff383dbc94514d055ae6778821d8471Martin Hibdon        if (EasService.getFileLogging()) {
4083d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            FileLogger.log(LOG_TAG, logStr);
409c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank        }
410c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank    }
411c1e79c036cd2a40e8a6e66b8ea4d37d121d355baMarc Blank
4123d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    void logVerbose(final String str) {
4133d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        if (LOG_VERBOSE) {
4143d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            log(str);
4153d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        }
4163d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    }
4173d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
4183d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    protected void pushTag(final int id) {
4193d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        page = id >>> Tags.PAGE_SHIFT;
42026d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        push(id);
42126d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    }
42226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
42340038a7f263c5463f8a0c08ffa21111b3e483694Martin Hibdon    protected void pop() {
42426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        // Retrieve the now-current startTag from our stack
4255bad4cfbe9a6401d5b4cde5bb8b4eec8d5fab82fJay Shrauner        startTag = startTagArray.removeFirst();
4263d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        log("</" + startTag + '>');
42726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    }
42826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
4293d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    private void push(final int id) {
4303d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        startTag = new Tag(page, id);
4313d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        noContent = startTag.mNoContent;
4323d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        log("<" + startTag + (noContent ? '/' : "") + '>');
43326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        // Save the startTag to our stack
4343d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        startTagArray.addFirst(startTag);
43526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank    }
43626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
437ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
438ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Return the next piece of data from the stream.  The return value indicates the type of data
439ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * that has been retrieved - START (start of tag), END (end of tag), DONE (end of stream), or
440ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * TEXT (the value of a tag)
441ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
442ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the type of data retrieved
443ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
444ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
4453d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner    private final int getNext() throws IOException {
4463d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        bytes = null;
4473d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        text = null;
4483d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner
449ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (noContent) {
4503d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            startTagArray.removeFirst();
451ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            type = END;
452ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            noContent = false;
453ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return type;
454ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
455ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
4563d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        int id = read();
457ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (id == Wbxml.SWITCH_PAGE) {
458ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Get the new page number
4593d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            page = readByte();
460ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Retrieve the current tag table
4613d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            if (!Tags.isValidPage(page)) {
4623d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                // Unknown code page. These seem to happen mostly because of
4633d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                // invalid data from the server so throw an exception here.
4643d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                throw new EasParserException("Unknown code page " + page);
4653d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            }
4663d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            logVerbose("Page: " + page);
4673d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner            id = read();
468ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
469ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
470ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        switch (id) {
471ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            case EOF_BYTE:
472ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // End of document
473ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                type = DONE;
474ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                break;
475ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
476ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            case Wbxml.END:
477ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                type = END;
47826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank                pop();
479ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                break;
480ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
481ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            case Wbxml.STR_I:
482ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Inline string
483ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                type = TEXT;
4843d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                text = readInlineString();
4853d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                log(startTag + ": " + text);
486ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                break;
487ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
48877186bb1a174432ef272584374942d8b9228e39cMarc Blank            case Wbxml.OPAQUE:
48977186bb1a174432ef272584374942d8b9228e39cMarc Blank                // Integer length + opaque data
4903d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                type = OPAQUE;
4913d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                final int length = readInt();
49277186bb1a174432ef272584374942d8b9228e39cMarc Blank                bytes = new byte[length];
49377186bb1a174432ef272584374942d8b9228e39cMarc Blank                for (int i = 0; i < length; i++) {
49477186bb1a174432ef272584374942d8b9228e39cMarc Blank                    bytes[i] = (byte)readByte();
49577186bb1a174432ef272584374942d8b9228e39cMarc Blank                }
4963d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                log(startTag + ": (opaque:" + length + ") ");
49777186bb1a174432ef272584374942d8b9228e39cMarc Blank                break;
49877186bb1a174432ef272584374942d8b9228e39cMarc Blank
499ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            default:
5003d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                if (Tags.isGlobalTag(id & Tags.PAGE_MASK)) {
5013d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                    throw new EasParserException(String.format(
5023d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                                    "Unhandled WBXML global token 0x%02X", id));
5033d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                }
5043d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                if ((id & Wbxml.WITH_ATTRIBUTES) != 0) {
5053d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                    throw new EasParserException(String.format(
5063d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                                    "Attributes unsupported, tag 0x%02X", id));
5073d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner                }
508ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                type = START;
50926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank                push(id);
510ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
511ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
512ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Return the type of data we're dealing with
513ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return type;
514ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
515ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
516ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
517ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Read an int from the input stream, and capture it if necessary for debugging.  Seems a small
518ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * price to pay...
519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
520ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the int read
521ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
522ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
523ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private int read() throws IOException {
524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int i;
525ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        i = in.read();
526ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (capture) {
527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            captureArray.add(i);
528ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
5293d3a9d01938c0b25f629985473fcef8f7d4455eeJay Shrauner        logVerbose("Byte: " + i);
530ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return i;
531ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
532ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
533ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private int readByte() throws IOException {
534ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int i = read();
535ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        if (i == EOF_BYTE) {
536ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            throw new EofException();
537ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
538ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return i;
539ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
540ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5413d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner    /**
5423d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner     * Throws EasParserException if detects integer encoded with more than 5
5433d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner     * bytes. A uint_32 needs 5 bytes to fully encode 32 bits so if the high
5443d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner     * bit is set for more than 4 bytes, something is wrong with the data
5453d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner     * stream.
5463d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner     */
547ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private int readInt() throws IOException {
548ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int result = 0;
549ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        int i;
5503d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        int numBytes = 0;
551ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
552ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        do {
5533d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            if (++numBytes > 5) {
5543d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner                throw new EasParserException("Invalid integer encoding, too many bytes");
5553d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            }
556ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            i = readByte();
557ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            result = (result << 7) | (i & 0x7f);
558ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } while ((i & 0x80) != 0);
559ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
560ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return result;
561ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
562ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
563ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    /**
564ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * Read an inline string from the stream
565ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     *
566ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @return the String as parsed from the stream
567ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     * @throws IOException
568ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank     */
569ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    private String readInlineString() throws IOException {
5703d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
571ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        while (true) {
5723d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner            final int i = read();
573ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (i == 0) {
574ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                break;
575ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            } else if (i == EOF_BYTE) {
576ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                throw new EofException();
577ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
57861563f075e07b9961ede4000d815865edc0f2497Marc Blank            outputStream.write(i);
579ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
58061563f075e07b9961ede4000d815865edc0f2497Marc Blank        outputStream.flush();
5813d52ff729b82a20802bb6d5a8952040f4d961cefJay Shrauner        final String res = outputStream.toString("UTF-8");
58261563f075e07b9961ede4000d815865edc0f2497Marc Blank        outputStream.close();
583ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return res;
584ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
585dabb41d364a54a60df0e9d3750cb37faed626743Elliott Hughes}
586