470 lines
14 KiB
Java
470 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2008-2009 Marc Blank
|
|
* Licensed to 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.exchange.adapter;
|
|
|
|
import java.io.*;
|
|
import java.util.ArrayList;
|
|
|
|
import com.android.exchange.EasException;
|
|
|
|
import android.content.Context;
|
|
import android.util.Log;
|
|
|
|
/**
|
|
* Extremely fast and lightweight WBXML parser, implementing only the subset of WBXML that
|
|
* EAS uses (as defined in the EAS specification)
|
|
*
|
|
*/
|
|
public abstract class EasParser {
|
|
|
|
private static final String TAG = "EasParser";
|
|
|
|
// The following constants are Wbxml standard
|
|
public static final int START_DOCUMENT = 0;
|
|
public static final int DONE = 1;
|
|
public static final int START = 2;
|
|
public static final int END = 3;
|
|
public static final int TEXT = 4;
|
|
public static final int END_DOCUMENT = 3;
|
|
private static final int NOT_FETCHED = Integer.MIN_VALUE;
|
|
private static final int NOT_ENDED = Integer.MIN_VALUE;
|
|
private static final int EOF_BYTE = -1;
|
|
private boolean debug = false;
|
|
private boolean capture = false;
|
|
|
|
private ArrayList<Integer> captureArray;
|
|
|
|
// The input stream for this parser
|
|
private InputStream in;
|
|
|
|
// The current tag depth
|
|
private int depth;
|
|
|
|
// The upcoming (saved) id from the stream
|
|
private int nextId = NOT_FETCHED;
|
|
|
|
// The current tag table (i.e. the tag table for the current page)
|
|
private String[] tagTable;
|
|
|
|
// An array of tag tables, as defined in EasTags
|
|
static private String[][] tagTables = new String[24][];
|
|
|
|
// The stack of names of tags being processed; used when debug = true
|
|
private String[] nameArray = new String[32];
|
|
|
|
// The stack of tags being processed
|
|
private int[] startTagArray = new int[32];
|
|
|
|
// The following vars are available to all to avoid method calls that represent the state of
|
|
// the parser at any given time
|
|
public int endTag = NOT_ENDED;
|
|
|
|
public int startTag;
|
|
|
|
// The type of the last token read
|
|
public int type;
|
|
|
|
// The current page
|
|
public int page;
|
|
|
|
// The current tag
|
|
public int tag;
|
|
|
|
// The name of the current tag
|
|
public String name;
|
|
|
|
// Whether the current tag is associated with content (a value)
|
|
private boolean noContent;
|
|
|
|
// The value read, as a String. Only one of text or num will be valid, depending on whether the
|
|
// value was requested as a String or an int (to avoid wasted effort in parsing)
|
|
public String text;
|
|
|
|
// The value read, as an int
|
|
public int num;
|
|
|
|
public class EofException extends IOException {
|
|
private static final long serialVersionUID = 1L;
|
|
}
|
|
|
|
public class EodException extends IOException {
|
|
private static final long serialVersionUID = 1L;
|
|
}
|
|
|
|
public class EasParserException extends IOException {
|
|
private static final long serialVersionUID = 1L;
|
|
}
|
|
|
|
public boolean parse() throws IOException, EasException {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Initialize the tag tables; they are constant
|
|
*
|
|
*/
|
|
{
|
|
String[][] pages = EasTags.pages;
|
|
for (int i = 0; i < pages.length; i++) {
|
|
String[] page = pages[i];
|
|
if (page.length > 0) {
|
|
tagTables[i] = page;
|
|
}
|
|
}
|
|
}
|
|
|
|
public EasParser(InputStream in) throws IOException {
|
|
setInput(in);
|
|
}
|
|
|
|
/**
|
|
* Set the debug state of the parser. When debugging is on, every token is logged (Log.v) to
|
|
* the console.
|
|
*
|
|
* @param val the desired state for debug output
|
|
*/
|
|
public void setDebug(boolean val) {
|
|
debug = val;
|
|
}
|
|
|
|
/**
|
|
* Turns on data capture; this is used to create test streams that represent "live" data and
|
|
* can be used against the various parsers.
|
|
*/
|
|
public void captureOn() {
|
|
capture = true;
|
|
captureArray = new ArrayList<Integer>();
|
|
}
|
|
|
|
/**
|
|
* Turns off data capture; writes the captured data to a specified file.
|
|
*/
|
|
public void captureOff(Context context, String file) {
|
|
try {
|
|
FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
|
|
out.write(captureArray.toString().getBytes());
|
|
out.close();
|
|
} catch (FileNotFoundException e) {
|
|
// This is debug code; exceptions aren't interesting.
|
|
} catch (IOException e) {
|
|
// This is debug code; exceptions aren't interesting.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the value of the current tag, as a String
|
|
*
|
|
* @return the String value of the current tag
|
|
* @throws IOException
|
|
*/
|
|
public String getValue() throws IOException {
|
|
// The false argument tells getNext to return the value as a String
|
|
getNext(false);
|
|
// Save the value
|
|
String val = text;
|
|
// Read the next token; it had better be the end of the current tag
|
|
getNext(false);
|
|
// If not, throw an exception
|
|
if (type != END) {
|
|
throw new IOException("No END found!");
|
|
}
|
|
endTag = startTag;
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Return the value of the current tag, as an integer
|
|
*
|
|
* @return the integer value of the current tag
|
|
* @throws IOException
|
|
*/
|
|
public int getValueInt() throws IOException {
|
|
// The true argument to getNext indicates the desire for an integer return value
|
|
getNext(true);
|
|
// Save the value
|
|
int val = num;
|
|
// Read the next token; it had better be the end of the current tag
|
|
getNext(false);
|
|
// If not, throw an exception
|
|
if (type != END) {
|
|
throw new IOException("No END found!");
|
|
}
|
|
endTag = startTag;
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Return the next tag found in the stream; special tags END and END_DOCUMENT are used to
|
|
* mark the end of the current tag and end of document. If we hit end of document without
|
|
* looking for it, generate an EodException. The tag returned consists of the page number
|
|
* shifted PAGE_SHIFT bits OR'd with the tag retrieved from the stream. Thus, all tags returned
|
|
* are unique.
|
|
*
|
|
* @param endingTag the tag that would represent the end of the tag we're processing
|
|
* @return the next tag found
|
|
* @throws IOException
|
|
*/
|
|
public int nextTag(int endingTag) throws IOException {
|
|
// Lose the page information
|
|
endTag = endingTag &= EasTags.PAGE_MASK;
|
|
while (getNext(false) != DONE) {
|
|
// If we're a start, set tag to include the page and return it
|
|
if (type == START) {
|
|
tag = page | startTag;
|
|
return tag;
|
|
// If we're at the ending tag we're looking for, return the END signal
|
|
} else if (type == END && startTag == endTag) {
|
|
return END;
|
|
}
|
|
}
|
|
// We're at end of document here. If we're looking for it, return END_DOCUMENT
|
|
if (endTag == START_DOCUMENT) {
|
|
return END_DOCUMENT;
|
|
}
|
|
// Otherwise, we've prematurely hit end of document, so exception out
|
|
// EodException is a subclass of IOException; this will be treated as an IO error by
|
|
// SyncManager.
|
|
throw new EodException();
|
|
}
|
|
|
|
/**
|
|
* Skip anything found in the stream until the end of the current tag is reached. This can be
|
|
* used to ignore stretches of xml that aren't needed by the parser.
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public void skipTag() throws IOException {
|
|
int thisTag = startTag;
|
|
// Just loop until we hit the end of the current tag
|
|
while (getNext(false) != DONE) {
|
|
if (type == END && startTag == thisTag) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we're at end of document, that's bad
|
|
throw new EofException();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the next token from the input stream
|
|
*
|
|
* @return the token found
|
|
* @throws IOException
|
|
*/
|
|
public int nextToken() throws IOException {
|
|
getNext(false);
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Initializes the parser with an input stream; reads the first 4 bytes (which are always the
|
|
* same in EAS, and then sets the tag table to point to page 0 (by definition, the starting
|
|
* page).
|
|
*
|
|
* @param in the InputStream associated with this parser
|
|
* @throws IOException
|
|
*/
|
|
public void setInput(InputStream in) throws IOException {
|
|
this.in = in;
|
|
readByte(); // version
|
|
readInt(); // ?
|
|
readInt(); // 106 (UTF-8)
|
|
readInt(); // string table length
|
|
tagTable = tagTables[0];
|
|
}
|
|
|
|
/**
|
|
* Return the next piece of data from the stream. The return value indicates the type of data
|
|
* that has been retrieved - START (start of tag), END (end of tag), DONE (end of stream), or
|
|
* TEXT (the value of a tag)
|
|
*
|
|
* @param asInt whether a TEXT value should be parsed as a String or an int.
|
|
* @return the type of data retrieved
|
|
* @throws IOException
|
|
*/
|
|
private final int getNext(boolean asInt) throws IOException {
|
|
if (type == END) {
|
|
depth--;
|
|
} else {
|
|
endTag = NOT_ENDED;
|
|
}
|
|
|
|
if (noContent) {
|
|
type = END;
|
|
noContent = false;
|
|
return type;
|
|
}
|
|
|
|
text = null;
|
|
name = null;
|
|
|
|
int id = nextId ();
|
|
while (id == Wbxml.SWITCH_PAGE) {
|
|
nextId = NOT_FETCHED;
|
|
// Get the new page number
|
|
int pg = readByte();
|
|
// Save the shifted page to add into the startTag in nextTag
|
|
page = pg << EasTags.PAGE_SHIFT;
|
|
// Retrieve the current tag table
|
|
tagTable = tagTables[pg];
|
|
id = nextId();
|
|
}
|
|
nextId = NOT_FETCHED;
|
|
|
|
switch (id) {
|
|
case EOF_BYTE:
|
|
// End of document
|
|
type = DONE;
|
|
break;
|
|
|
|
case Wbxml.END:
|
|
// End of tag
|
|
type = END;
|
|
if (debug) {
|
|
name = nameArray[depth];
|
|
Log.v(TAG, "</" + name + '>');
|
|
}
|
|
// Retrieve the now-current startTag from our stack
|
|
startTag = endTag = startTagArray[depth];
|
|
break;
|
|
|
|
case Wbxml.STR_I:
|
|
// Inline string
|
|
type = TEXT;
|
|
if (asInt) {
|
|
num = readInlineInt();
|
|
} else {
|
|
text = readInlineString();
|
|
}
|
|
if (debug) {
|
|
Log.v(TAG, asInt ? Integer.toString(num) : text);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Start of tag
|
|
type = START;
|
|
// The tag is in the low 6 bits
|
|
startTag = id & 0x3F;
|
|
// If the high bit is set, there is content (a value) to be read
|
|
noContent = (id & 0x40) == 0;
|
|
depth++;
|
|
if (debug) {
|
|
name = tagTable[startTag - 5];
|
|
Log.v(TAG, '<' + name + '>');
|
|
nameArray[depth] = name;
|
|
}
|
|
// Save the startTag to our stack
|
|
startTagArray[depth] = startTag;
|
|
}
|
|
|
|
// Return the type of data we're dealing with
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* Read an int from the input stream, and capture it if necessary for debugging. Seems a small
|
|
* price to pay...
|
|
*
|
|
* @return the int read
|
|
* @throws IOException
|
|
*/
|
|
private int read() throws IOException {
|
|
int i;
|
|
i = in.read();
|
|
if (capture) {
|
|
captureArray.add(i);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
private int nextId() throws IOException {
|
|
if (nextId == NOT_FETCHED) {
|
|
nextId = read();
|
|
}
|
|
return nextId;
|
|
}
|
|
|
|
private int readByte() throws IOException {
|
|
int i = read();
|
|
if (i == EOF_BYTE) {
|
|
throw new EofException();
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Read an integer from the stream; this is called when the parser knows that what follows is
|
|
* an inline string representing an integer (e.g. the Read tag in Email has a value known to
|
|
* be either "0" or "1")
|
|
*
|
|
* @return the integer as parsed from the stream
|
|
* @throws IOException
|
|
*/
|
|
private int readInlineInt() throws IOException {
|
|
int result = 0;
|
|
|
|
while (true) {
|
|
int i = readByte();
|
|
// Inline strings are always terminated with a zero byte
|
|
if (i == 0) {
|
|
return result;
|
|
}
|
|
if (i >= '0' && i <= '9') {
|
|
result = (result * 10) + (i - '0');
|
|
} else {
|
|
throw new IOException("Non integer");
|
|
}
|
|
}
|
|
}
|
|
|
|
private int readInt() throws IOException {
|
|
int result = 0;
|
|
int i;
|
|
|
|
do {
|
|
i = readByte();
|
|
result = (result << 7) | (i & 0x7f);
|
|
} while ((i & 0x80) != 0);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Read an inline string from the stream
|
|
*
|
|
* @return the String as parsed from the stream
|
|
* @throws IOException
|
|
*/
|
|
private String readInlineString() throws IOException {
|
|
StringBuilder sb = new StringBuilder(256);
|
|
|
|
while (true) {
|
|
int i = read();
|
|
if (i == 0) {
|
|
break;
|
|
} else if (i == EOF_BYTE) {
|
|
throw new EofException();
|
|
}
|
|
sb.append((char)i);
|
|
}
|
|
String res = sb.toString();
|
|
return res;
|
|
}
|
|
} |