1 /* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining a copy 4 * of this software and associated documentation files (the "Software"), to deal 5 * in the Software without restriction, including without limitation the rights 6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 * sell copies of the Software, and to permit persons to whom the Software is 8 * furnished to do so, subject to the following conditions: 9 * 10 * The above copyright notice and this permission notice shall be included in 11 * all copies or substantial portions of the Software. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 * IN THE SOFTWARE. */ 20 21 //Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian 22 // Greatly simplified for Google, Inc. by Marc Blank 23 24 package com.android.exchange.adapter; 25 26 import android.content.ContentValues; 27 28 import com.android.exchange.Eas; 29 import com.android.exchange.utility.FileLogger; 30 import com.android.mail.utils.LogUtils; 31 import com.google.common.annotations.VisibleForTesting; 32 33 import java.io.ByteArrayOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.io.OutputStream; 37 38 public class Serializer { 39 private static final String TAG = Eas.LOG_TAG; 40 private static final int BUFFER_SIZE = 16*1024; 41 private static final int NOT_PENDING = -1; 42 43 private final OutputStream mOutput; 44 private int mPendingTag = NOT_PENDING; 45 private int mDepth; 46 private String[] mNameStack = new String[20]; 47 private int mTagPage = 0; 48 private boolean mLogging = LogUtils.isLoggable(TAG, LogUtils.VERBOSE); 49 50 public Serializer() throws IOException { 51 this(new ByteArrayOutputStream(), true); 52 } 53 54 public Serializer(OutputStream os) throws IOException { 55 this(os, true); 56 } 57 58 @VisibleForTesting 59 public Serializer(boolean startDocument) throws IOException { 60 this(new ByteArrayOutputStream(), startDocument); 61 } 62 63 /** 64 * Base constructor 65 * @param outputStream the stream we're serializing to 66 * @param startDocument whether or not to start a document 67 * @param _logging whether or not to log our output 68 * @throws IOException 69 */ 70 public Serializer(OutputStream outputStream, boolean startDocument) throws IOException { 71 super(); 72 mOutput = outputStream; 73 if (startDocument) { 74 startDocument(); 75 } else { 76 mOutput.write(0); 77 } 78 } 79 80 void log(String str) { 81 int cr = str.indexOf('\n'); 82 if (cr > 0) { 83 str = str.substring(0, cr); 84 } 85 LogUtils.v(TAG, str); 86 if (Eas.FILE_LOG) { 87 FileLogger.log(TAG, str); 88 } 89 } 90 91 public void done() throws IOException { 92 if (mDepth != 0) { 93 throw new IOException("Done received with unclosed tags"); 94 } 95 mOutput.flush(); 96 } 97 98 public void startDocument() throws IOException{ 99 mOutput.write(0x03); // version 1.3 100 mOutput.write(0x01); // unknown or missing public identifier 101 mOutput.write(106); // UTF-8 102 mOutput.write(0); // 0 length string array 103 } 104 105 public void checkPendingTag(boolean degenerated) throws IOException { 106 if (mPendingTag == NOT_PENDING) 107 return; 108 109 int page = mPendingTag >> Tags.PAGE_SHIFT; 110 int tag = mPendingTag & Tags.PAGE_MASK; 111 if (page != mTagPage) { 112 mTagPage = page; 113 mOutput.write(Wbxml.SWITCH_PAGE); 114 mOutput.write(page); 115 } 116 117 mOutput.write(degenerated ? tag : tag | Wbxml.WITH_CONTENT); 118 if (mLogging) { 119 String name = Tags.pages[page][tag - 5]; 120 mNameStack[mDepth] = name; 121 log("<" + name + '>'); 122 } 123 mPendingTag = NOT_PENDING; 124 } 125 126 public Serializer start(int tag) throws IOException { 127 checkPendingTag(false); 128 mPendingTag = tag; 129 mDepth++; 130 return this; 131 } 132 133 public Serializer end() throws IOException { 134 if (mPendingTag >= 0) { 135 checkPendingTag(true); 136 } else { 137 mOutput.write(Wbxml.END); 138 if (mLogging) { 139 log("</" + mNameStack[mDepth] + '>'); 140 } 141 } 142 mDepth--; 143 return this; 144 } 145 146 public Serializer tag(int t) throws IOException { 147 start(t); 148 end(); 149 return this; 150 } 151 152 public Serializer data(int tag, String value) throws IOException { 153 if (value == null) { 154 LogUtils.e(TAG, "Writing null data for tag: " + tag); 155 } 156 start(tag); 157 text(value); 158 end(); 159 return this; 160 } 161 162 public Serializer text(String text) throws IOException { 163 if (text == null) { 164 LogUtils.e(TAG, "Writing null text for pending tag: " + mPendingTag); 165 } 166 checkPendingTag(false); 167 mOutput.write(Wbxml.STR_I); 168 writeLiteralString(mOutput, text); 169 if (mLogging) { 170 log(text); 171 } 172 return this; 173 } 174 175 public Serializer opaque(InputStream is, int length) throws IOException { 176 checkPendingTag(false); 177 mOutput.write(Wbxml.OPAQUE); 178 writeInteger(mOutput, length); 179 if (mLogging) { 180 log("Opaque, length: " + length); 181 } 182 // Now write out the opaque data in batches 183 byte[] buffer = new byte[BUFFER_SIZE]; 184 while (length > 0) { 185 int bytesRead = is.read(buffer, 0, Math.min(BUFFER_SIZE, length)); 186 if (bytesRead == -1) { 187 break; 188 } 189 mOutput.write(buffer, 0, bytesRead); 190 length -= bytesRead; 191 } 192 return this; 193 } 194 195 public Serializer opaqueWithoutData(int length) throws IOException { 196 checkPendingTag(false); 197 mOutput.write(Wbxml.OPAQUE); 198 writeInteger(mOutput, length); 199 return this; 200 } 201 202 void writeInteger(OutputStream out, int i) throws IOException { 203 byte[] buf = new byte[5]; 204 int idx = 0; 205 206 do { 207 buf[idx++] = (byte) (i & 0x7f); 208 i = i >> 7; 209 } while (i != 0); 210 211 while (idx > 1) { 212 out.write(buf[--idx] | 0x80); 213 } 214 out.write(buf[0]); 215 if (mLogging) { 216 log(Integer.toString(i)); 217 } 218 } 219 220 void writeLiteralString(OutputStream out, String s) throws IOException { 221 byte[] data = s.getBytes("UTF-8"); 222 out.write(data); 223 out.write(0); 224 } 225 226 public void writeStringValue (ContentValues cv, String key, int tag) throws IOException { 227 String value = cv.getAsString(key); 228 if (value != null && value.length() > 0) { 229 data(tag, value); 230 } else { 231 tag(tag); 232 } 233 } 234 235 @Override 236 public String toString() { 237 if (mOutput instanceof ByteArrayOutputStream) { 238 return ((ByteArrayOutputStream)mOutput).toString(); 239 } 240 throw new IllegalStateException(); 241 } 242 243 public byte[] toByteArray() { 244 if (mOutput instanceof ByteArrayOutputStream) { 245 return ((ByteArrayOutputStream)mOutput).toByteArray(); 246 } 247 throw new IllegalStateException(); 248 } 249 250 } 251