• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008-2009 Marc Blank
3  * Licensed to The Android Open Source Project.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.exchange.adapter;
19 
20 import com.android.exchange.Eas;
21 import com.android.exchange.EasException;
22 import com.android.exchange.utility.FileLogger;
23 
24 import android.content.Context;
25 import android.util.Log;
26 
27 import java.io.ByteArrayOutputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.util.ArrayList;
33 
34 /**
35  * Extremely fast and lightweight WBXML parser, implementing only the subset of WBXML that
36  * EAS uses (as defined in the EAS specification)
37  *
38  */
39 public abstract class Parser {
40 
41     // The following constants are Wbxml standard
42     public static final int START_DOCUMENT = 0;
43     public static final int DONE = 1;
44     public static final int START = 2;
45     public static final int END = 3;
46     public static final int TEXT = 4;
47     public static final int END_DOCUMENT = 3;
48     private static final int NOT_FETCHED = Integer.MIN_VALUE;
49     private static final int NOT_ENDED = Integer.MIN_VALUE;
50     private static final int EOF_BYTE = -1;
51     private boolean logging = false;
52     private boolean capture = false;
53     private String logTag = "EAS Parser";
54 
55     // Where tags start in a page
56     private static final int TAG_BASE = 5;
57 
58     private ArrayList<Integer> captureArray;
59 
60     // The input stream for this parser
61     private InputStream in;
62 
63     // The current tag depth
64     private int depth;
65 
66     // The upcoming (saved) id from the stream
67     private int nextId = NOT_FETCHED;
68 
69     // The current tag table (i.e. the tag table for the current page)
70     private String[] tagTable;
71 
72     // An array of tag tables, as defined in EasTags
73     static private String[][] tagTables = new String[24][];
74 
75     // The stack of names of tags being processed; used when debug = true
76     private String[] nameArray = new String[32];
77 
78     // The stack of tags being processed
79     private int[] startTagArray = new int[32];
80 
81     // The following vars are available to all to avoid method calls that represent the state of
82     // the parser at any given time
83     public int endTag = NOT_ENDED;
84 
85     public int startTag;
86 
87     // The type of the last token read
88     public int type;
89 
90     // The current page
91     public int page;
92 
93     // The current tag
94     public int tag;
95 
96     // The name of the current tag
97     public String name;
98 
99     // Whether the current tag is associated with content (a value)
100     private boolean noContent;
101 
102     // The value read, as a String.  Only one of text or num will be valid, depending on whether the
103     // value was requested as a String or an int (to avoid wasted effort in parsing)
104     public String text;
105 
106     // The value read, as an int
107     public int num;
108 
109     public class EofException extends IOException {
110         private static final long serialVersionUID = 1L;
111     }
112 
113     public class EodException extends IOException {
114         private static final long serialVersionUID = 1L;
115     }
116 
117     public class EasParserException extends IOException {
118         private static final long serialVersionUID = 1L;
119 
EasParserException()120         EasParserException() {
121             super("WBXML format error");
122         }
123 
EasParserException(String reason)124         EasParserException(String reason) {
125             super(reason);
126         }
127     }
128 
parse()129     public boolean parse() throws IOException, EasException {
130         return false;
131     }
132 
133     /**
134      * Initialize the tag tables; they are constant
135      *
136      */
137     {
138         String[][] pages = Tags.pages;
139         for (int i = 0; i < pages.length; i++) {
140             String[] page = pages[i];
141             if (page.length > 0) {
142                 tagTables[i] = page;
143             }
144         }
145     }
146 
Parser(InputStream in)147     public Parser(InputStream in) throws IOException {
148         setInput(in);
149         logging = Eas.PARSER_LOG;
150     }
151 
152     /**
153      * Set the debug state of the parser.  When debugging is on, every token is logged (Log.v) to
154      * the console.
155      *
156      * @param val the desired state for debug output
157      */
setDebug(boolean val)158     public void setDebug(boolean val) {
159         logging = val;
160     }
161 
162     /**
163      * Set the tag used for logging.  When debugging is on, every token is logged (Log.v) to
164      * the console.
165      *
166      * @param val the logging tag
167      */
setLoggingTag(String val)168     public void setLoggingTag(String val) {
169         logTag = val;
170     }
171 
172     /**
173      * Turns on data capture; this is used to create test streams that represent "live" data and
174      * can be used against the various parsers.
175      */
captureOn()176     public void captureOn() {
177         capture = true;
178         captureArray = new ArrayList<Integer>();
179     }
180 
181     /**
182      * Turns off data capture; writes the captured data to a specified file.
183      */
captureOff(Context context, String file)184     public void captureOff(Context context, String file) {
185         try {
186             FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
187             out.write(captureArray.toString().getBytes());
188             out.close();
189         } catch (FileNotFoundException e) {
190             // This is debug code; exceptions aren't interesting.
191         } catch (IOException e) {
192             // This is debug code; exceptions aren't interesting.
193         }
194     }
195 
196     /**
197      * Return the value of the current tag, as a String
198      *
199      * @return the String value of the current tag
200      * @throws IOException
201      */
getValue()202     public String getValue() throws IOException {
203         // The false argument tells getNext to return the value as a String
204         getNext(false);
205         // This means there was no value given, just <Foo/>; we'll return empty string for now
206         if (type == END) {
207             if (logging) {
208                 log("No value for tag: " + tagTable[startTag - TAG_BASE]);
209             }
210             return "";
211         }
212         // Save the value
213         String val = text;
214         // Read the next token; it had better be the end of the current tag
215         getNext(false);
216         // If not, throw an exception
217         if (type != END) {
218             throw new IOException("No END found!");
219         }
220         endTag = startTag;
221         return val;
222     }
223 
224     /**
225      * Return the value of the current tag, as an integer
226      *
227      * @return the integer value of the current tag
228      * @throws IOException
229      */
getValueInt()230    public int getValueInt() throws IOException {
231         // The true argument to getNext indicates the desire for an integer return value
232         getNext(true);
233         if (type == END) {
234             return 0;
235         }
236         // Save the value
237         int val = num;
238         // Read the next token; it had better be the end of the current tag
239         getNext(false);
240         // If not, throw an exception
241         if (type != END) {
242             throw new IOException("No END found!");
243         }
244         endTag = startTag;
245         return val;
246     }
247 
248     /**
249      * Return the next tag found in the stream; special tags END and END_DOCUMENT are used to
250      * mark the end of the current tag and end of document.  If we hit end of document without
251      * looking for it, generate an EodException.  The tag returned consists of the page number
252      * shifted PAGE_SHIFT bits OR'd with the tag retrieved from the stream.  Thus, all tags returned
253      * are unique.
254      *
255      * @param endingTag the tag that would represent the end of the tag we're processing
256      * @return the next tag found
257      * @throws IOException
258      */
nextTag(int endingTag)259     public int nextTag(int endingTag) throws IOException {
260         // Lose the page information
261         endTag = endingTag &= Tags.PAGE_MASK;
262         while (getNext(false) != DONE) {
263             // If we're a start, set tag to include the page and return it
264             if (type == START) {
265                 tag = page | startTag;
266                 return tag;
267             // If we're at the ending tag we're looking for, return the END signal
268             } else if (type == END && startTag == endTag) {
269                 return END;
270             }
271         }
272         // We're at end of document here.  If we're looking for it, return END_DOCUMENT
273         if (endTag == START_DOCUMENT) {
274             return END_DOCUMENT;
275         }
276         // Otherwise, we've prematurely hit end of document, so exception out
277         // EodException is a subclass of IOException; this will be treated as an IO error by
278         // SyncManager.
279         throw new EodException();
280     }
281 
282     /**
283      * Skip anything found in the stream until the end of the current tag is reached.  This can be
284      * used to ignore stretches of xml that aren't needed by the parser.
285      *
286      * @throws IOException
287      */
skipTag()288     public void skipTag() throws IOException {
289         int thisTag = startTag;
290         // Just loop until we hit the end of the current tag
291         while (getNext(false) != DONE) {
292             if (type == END && startTag == thisTag) {
293                 return;
294             }
295         }
296 
297         // If we're at end of document, that's bad
298         throw new EofException();
299     }
300 
301     /**
302      * Retrieve the next token from the input stream
303      *
304      * @return the token found
305      * @throws IOException
306      */
nextToken()307     public int nextToken() throws IOException {
308         getNext(false);
309         return type;
310     }
311 
312     /**
313      * Initializes the parser with an input stream; reads the first 4 bytes (which are always the
314      * same in EAS, and then sets the tag table to point to page 0 (by definition, the starting
315      * page).
316      *
317      * @param in the InputStream associated with this parser
318      * @throws IOException
319      */
setInput(InputStream in)320     public void setInput(InputStream in) throws IOException {
321         this.in = in;
322         readByte(); // version
323         readInt();  // ?
324         readInt();  // 106 (UTF-8)
325         readInt();  // string table length
326         tagTable = tagTables[0];
327     }
328 
resetInput(InputStream in)329     /*package*/ void resetInput(InputStream in) {
330         this.in = in;
331     }
332 
log(String str)333     void log(String str) {
334         int cr = str.indexOf('\n');
335         if (cr > 0) {
336             str = str.substring(0, cr);
337         }
338         Log.v(logTag, str);
339         if (Eas.FILE_LOG) {
340             FileLogger.log(logTag, str);
341         }
342     }
343 
344     /**
345      * Return the next piece of data from the stream.  The return value indicates the type of data
346      * that has been retrieved - START (start of tag), END (end of tag), DONE (end of stream), or
347      * TEXT (the value of a tag)
348      *
349      * @param asInt whether a TEXT value should be parsed as a String or an int.
350      * @return the type of data retrieved
351      * @throws IOException
352      */
getNext(boolean asInt)353     private final int getNext(boolean asInt) throws IOException {
354         int savedEndTag = endTag;
355         if (type == END) {
356             depth--;
357         } else {
358             endTag = NOT_ENDED;
359         }
360 
361         if (noContent) {
362             type = END;
363             noContent = false;
364             endTag = savedEndTag;
365             return type;
366         }
367 
368         text = null;
369         name = null;
370 
371         int id = nextId ();
372         while (id == Wbxml.SWITCH_PAGE) {
373             nextId = NOT_FETCHED;
374             // Get the new page number
375             int pg = readByte();
376             // Save the shifted page to add into the startTag in nextTag
377             page = pg << Tags.PAGE_SHIFT;
378             // Retrieve the current tag table
379             tagTable = tagTables[pg];
380             id = nextId();
381         }
382         nextId = NOT_FETCHED;
383 
384         switch (id) {
385             case EOF_BYTE:
386                 // End of document
387                 type = DONE;
388                 break;
389 
390             case Wbxml.END:
391                 // End of tag
392                 type = END;
393                 if (logging) {
394                     name = nameArray[depth];
395                     //log("</" + name + '>');
396                 }
397                 // Retrieve the now-current startTag from our stack
398                 startTag = endTag = startTagArray[depth];
399                 break;
400 
401             case Wbxml.STR_I:
402                 // Inline string
403                 type = TEXT;
404                 if (asInt) {
405                     num = readInlineInt();
406                 } else {
407                     text = readInlineString();
408                 }
409                 if (logging) {
410                     name = tagTable[startTag - TAG_BASE];
411                     log(name + ": " + (asInt ? Integer.toString(num) : text));
412                 }
413                 break;
414 
415             default:
416                 // Start of tag
417                 type = START;
418                 // The tag is in the low 6 bits
419                 startTag = id & 0x3F;
420                 // If the high bit is set, there is content (a value) to be read
421                 noContent = (id & 0x40) == 0;
422                 depth++;
423                 if (logging) {
424                     name = tagTable[startTag - TAG_BASE];
425                     //log('<' + name + '>');
426                     nameArray[depth] = name;
427                 }
428                 // Save the startTag to our stack
429                 startTagArray[depth] = startTag;
430         }
431 
432         // Return the type of data we're dealing with
433         return type;
434     }
435 
436     /**
437      * Read an int from the input stream, and capture it if necessary for debugging.  Seems a small
438      * price to pay...
439      *
440      * @return the int read
441      * @throws IOException
442      */
read()443     private int read() throws IOException {
444         int i;
445         i = in.read();
446         if (capture) {
447             captureArray.add(i);
448         }
449         return i;
450     }
451 
nextId()452     private int nextId() throws IOException {
453         if (nextId == NOT_FETCHED) {
454             nextId = read();
455         }
456         return nextId;
457     }
458 
readByte()459     private int readByte() throws IOException {
460         int i = read();
461         if (i == EOF_BYTE) {
462             throw new EofException();
463         }
464         return i;
465     }
466 
467     /**
468      * Read an integer from the stream; this is called when the parser knows that what follows is
469      * an inline string representing an integer (e.g. the Read tag in Email has a value known to
470      * be either "0" or "1")
471      *
472      * @return the integer as parsed from the stream
473      * @throws IOException
474      */
readInlineInt()475     private int readInlineInt() throws IOException {
476         int result = 0;
477 
478         while (true) {
479             int i = readByte();
480             // Inline strings are always terminated with a zero byte
481             if (i == 0) {
482                 return result;
483             }
484             if (i >= '0' && i <= '9') {
485                 result = (result * 10) + (i - '0');
486             } else {
487                 throw new IOException("Non integer");
488             }
489         }
490     }
491 
readInt()492     private int readInt() throws IOException {
493         int result = 0;
494         int i;
495 
496         do {
497             i = readByte();
498             result = (result << 7) | (i & 0x7f);
499         } while ((i & 0x80) != 0);
500 
501         return result;
502     }
503 
504     /**
505      * Read an inline string from the stream
506      *
507      * @return the String as parsed from the stream
508      * @throws IOException
509      */
readInlineString()510     private String readInlineString() throws IOException {
511         ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
512         while (true) {
513             int i = read();
514             if (i == 0) {
515                 break;
516             } else if (i == EOF_BYTE) {
517                 throw new EofException();
518             }
519             outputStream.write(i);
520         }
521         outputStream.flush();
522         String res = outputStream.toString("UTF-8");
523         outputStream.close();
524         return res;
525     }
526 }
527