1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import org.xmlpull.v1.XmlSerializer; 20 21 import java.io.IOException; 22 import java.io.OutputStream; 23 import java.io.OutputStreamWriter; 24 import java.io.UnsupportedEncodingException; 25 import java.io.Writer; 26 import java.nio.ByteBuffer; 27 import java.nio.CharBuffer; 28 import java.nio.charset.Charset; 29 import java.nio.charset.CharsetEncoder; 30 import java.nio.charset.CoderResult; 31 import java.nio.charset.CodingErrorAction; 32 import java.nio.charset.IllegalCharsetNameException; 33 import java.nio.charset.UnsupportedCharsetException; 34 35 /** 36 * This is a quick and dirty implementation of XmlSerializer that isn't horribly 37 * painfully slow like the normal one. It only does what is needed for the 38 * specific XML files being written with it. 39 */ 40 public class FastXmlSerializer implements XmlSerializer { 41 private static final String ESCAPE_TABLE[] = new String[] { 42 "�", "", "", "", "", "", "", "", // 0-7 43 "", "	", " ", "", "", " ", "", "", // 8-15 44 "", "", "", "", "", "", "", "", // 16-23 45 "", "", "", "", "", "", "", "", // 24-31 46 null, null, """, null, null, null, "&", null, // 32-39 47 null, null, null, null, null, null, null, null, // 40-47 48 null, null, null, null, null, null, null, null, // 48-55 49 null, null, null, null, "<", null, ">", null, // 56-63 50 }; 51 52 private static final int DEFAULT_BUFFER_LEN = 32*1024; 53 54 private static String sSpace = " "; 55 56 private final int mBufferLen; 57 private final char[] mText; 58 private int mPos; 59 60 private Writer mWriter; 61 62 private OutputStream mOutputStream; 63 private CharsetEncoder mCharset; 64 private ByteBuffer mBytes; 65 66 private boolean mIndent = false; 67 private boolean mInTag; 68 69 private int mNesting = 0; 70 private boolean mLineStart = true; 71 FastXmlSerializer()72 public FastXmlSerializer() { 73 this(DEFAULT_BUFFER_LEN); 74 } 75 76 /** 77 * Allocate a FastXmlSerializer with the given internal output buffer size. If the 78 * size is zero or negative, then the default buffer size will be used. 79 * 80 * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use. 81 */ FastXmlSerializer(int bufferSize)82 public FastXmlSerializer(int bufferSize) { 83 mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN; 84 mText = new char[mBufferLen]; 85 mBytes = ByteBuffer.allocate(mBufferLen); 86 } 87 append(char c)88 private void append(char c) throws IOException { 89 int pos = mPos; 90 if (pos >= (mBufferLen-1)) { 91 flush(); 92 pos = mPos; 93 } 94 mText[pos] = c; 95 mPos = pos+1; 96 } 97 append(String str, int i, final int length)98 private void append(String str, int i, final int length) throws IOException { 99 if (length > mBufferLen) { 100 final int end = i + length; 101 while (i < end) { 102 int next = i + mBufferLen; 103 append(str, i, next<end ? mBufferLen : (end-i)); 104 i = next; 105 } 106 return; 107 } 108 int pos = mPos; 109 if ((pos+length) > mBufferLen) { 110 flush(); 111 pos = mPos; 112 } 113 str.getChars(i, i+length, mText, pos); 114 mPos = pos + length; 115 } 116 append(char[] buf, int i, final int length)117 private void append(char[] buf, int i, final int length) throws IOException { 118 if (length > mBufferLen) { 119 final int end = i + length; 120 while (i < end) { 121 int next = i + mBufferLen; 122 append(buf, i, next<end ? mBufferLen : (end-i)); 123 i = next; 124 } 125 return; 126 } 127 int pos = mPos; 128 if ((pos+length) > mBufferLen) { 129 flush(); 130 pos = mPos; 131 } 132 System.arraycopy(buf, i, mText, pos, length); 133 mPos = pos + length; 134 } 135 append(String str)136 private void append(String str) throws IOException { 137 append(str, 0, str.length()); 138 } 139 appendIndent(int indent)140 private void appendIndent(int indent) throws IOException { 141 indent *= 4; 142 if (indent > sSpace.length()) { 143 indent = sSpace.length(); 144 } 145 append(sSpace, 0, indent); 146 } 147 escapeAndAppendString(final String string)148 private void escapeAndAppendString(final String string) throws IOException { 149 final int N = string.length(); 150 final char NE = (char)ESCAPE_TABLE.length; 151 final String[] escapes = ESCAPE_TABLE; 152 int lastPos = 0; 153 int pos; 154 for (pos=0; pos<N; pos++) { 155 char c = string.charAt(pos); 156 if (c >= NE) continue; 157 String escape = escapes[c]; 158 if (escape == null) continue; 159 if (lastPos < pos) append(string, lastPos, pos-lastPos); 160 lastPos = pos + 1; 161 append(escape); 162 } 163 if (lastPos < pos) append(string, lastPos, pos-lastPos); 164 } 165 escapeAndAppendString(char[] buf, int start, int len)166 private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { 167 final char NE = (char)ESCAPE_TABLE.length; 168 final String[] escapes = ESCAPE_TABLE; 169 int end = start+len; 170 int lastPos = start; 171 int pos; 172 for (pos=start; pos<end; pos++) { 173 char c = buf[pos]; 174 if (c >= NE) continue; 175 String escape = escapes[c]; 176 if (escape == null) continue; 177 if (lastPos < pos) append(buf, lastPos, pos-lastPos); 178 lastPos = pos + 1; 179 append(escape); 180 } 181 if (lastPos < pos) append(buf, lastPos, pos-lastPos); 182 } 183 attribute(String namespace, String name, String value)184 public XmlSerializer attribute(String namespace, String name, String value) throws IOException, 185 IllegalArgumentException, IllegalStateException { 186 append(' '); 187 if (namespace != null) { 188 append(namespace); 189 append(':'); 190 } 191 append(name); 192 append("=\""); 193 194 escapeAndAppendString(value); 195 append('"'); 196 mLineStart = false; 197 return this; 198 } 199 cdsect(String text)200 public void cdsect(String text) throws IOException, IllegalArgumentException, 201 IllegalStateException { 202 throw new UnsupportedOperationException(); 203 } 204 comment(String text)205 public void comment(String text) throws IOException, IllegalArgumentException, 206 IllegalStateException { 207 throw new UnsupportedOperationException(); 208 } 209 docdecl(String text)210 public void docdecl(String text) throws IOException, IllegalArgumentException, 211 IllegalStateException { 212 throw new UnsupportedOperationException(); 213 } 214 endDocument()215 public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { 216 flush(); 217 } 218 endTag(String namespace, String name)219 public XmlSerializer endTag(String namespace, String name) throws IOException, 220 IllegalArgumentException, IllegalStateException { 221 mNesting--; 222 if (mInTag) { 223 append(" />\n"); 224 } else { 225 if (mIndent && mLineStart) { 226 appendIndent(mNesting); 227 } 228 append("</"); 229 if (namespace != null) { 230 append(namespace); 231 append(':'); 232 } 233 append(name); 234 append(">\n"); 235 } 236 mLineStart = true; 237 mInTag = false; 238 return this; 239 } 240 entityRef(String text)241 public void entityRef(String text) throws IOException, IllegalArgumentException, 242 IllegalStateException { 243 throw new UnsupportedOperationException(); 244 } 245 flushBytes()246 private void flushBytes() throws IOException { 247 int position; 248 if ((position = mBytes.position()) > 0) { 249 mBytes.flip(); 250 mOutputStream.write(mBytes.array(), 0, position); 251 mBytes.clear(); 252 } 253 } 254 flush()255 public void flush() throws IOException { 256 //Log.i("PackageManager", "flush mPos=" + mPos); 257 if (mPos > 0) { 258 if (mOutputStream != null) { 259 CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); 260 CoderResult result = mCharset.encode(charBuffer, mBytes, true); 261 while (true) { 262 if (result.isError()) { 263 throw new IOException(result.toString()); 264 } else if (result.isOverflow()) { 265 flushBytes(); 266 result = mCharset.encode(charBuffer, mBytes, true); 267 continue; 268 } 269 break; 270 } 271 flushBytes(); 272 mOutputStream.flush(); 273 } else { 274 mWriter.write(mText, 0, mPos); 275 mWriter.flush(); 276 } 277 mPos = 0; 278 } 279 } 280 getDepth()281 public int getDepth() { 282 throw new UnsupportedOperationException(); 283 } 284 getFeature(String name)285 public boolean getFeature(String name) { 286 throw new UnsupportedOperationException(); 287 } 288 getName()289 public String getName() { 290 throw new UnsupportedOperationException(); 291 } 292 getNamespace()293 public String getNamespace() { 294 throw new UnsupportedOperationException(); 295 } 296 getPrefix(String namespace, boolean generatePrefix)297 public String getPrefix(String namespace, boolean generatePrefix) 298 throws IllegalArgumentException { 299 throw new UnsupportedOperationException(); 300 } 301 getProperty(String name)302 public Object getProperty(String name) { 303 throw new UnsupportedOperationException(); 304 } 305 ignorableWhitespace(String text)306 public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, 307 IllegalStateException { 308 throw new UnsupportedOperationException(); 309 } 310 processingInstruction(String text)311 public void processingInstruction(String text) throws IOException, IllegalArgumentException, 312 IllegalStateException { 313 throw new UnsupportedOperationException(); 314 } 315 setFeature(String name, boolean state)316 public void setFeature(String name, boolean state) throws IllegalArgumentException, 317 IllegalStateException { 318 if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { 319 mIndent = true; 320 return; 321 } 322 throw new UnsupportedOperationException(); 323 } 324 setOutput(OutputStream os, String encoding)325 public void setOutput(OutputStream os, String encoding) throws IOException, 326 IllegalArgumentException, IllegalStateException { 327 if (os == null) 328 throw new IllegalArgumentException(); 329 if (true) { 330 try { 331 mCharset = Charset.forName(encoding).newEncoder() 332 .onMalformedInput(CodingErrorAction.REPLACE) 333 .onUnmappableCharacter(CodingErrorAction.REPLACE); 334 } catch (IllegalCharsetNameException e) { 335 throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 336 encoding).initCause(e)); 337 } catch (UnsupportedCharsetException e) { 338 throw (UnsupportedEncodingException) (new UnsupportedEncodingException( 339 encoding).initCause(e)); 340 } 341 mOutputStream = os; 342 } else { 343 setOutput( 344 encoding == null 345 ? new OutputStreamWriter(os) 346 : new OutputStreamWriter(os, encoding)); 347 } 348 } 349 setOutput(Writer writer)350 public void setOutput(Writer writer) throws IOException, IllegalArgumentException, 351 IllegalStateException { 352 mWriter = writer; 353 } 354 setPrefix(String prefix, String namespace)355 public void setPrefix(String prefix, String namespace) throws IOException, 356 IllegalArgumentException, IllegalStateException { 357 throw new UnsupportedOperationException(); 358 } 359 setProperty(String name, Object value)360 public void setProperty(String name, Object value) throws IllegalArgumentException, 361 IllegalStateException { 362 throw new UnsupportedOperationException(); 363 } 364 startDocument(String encoding, Boolean standalone)365 public void startDocument(String encoding, Boolean standalone) throws IOException, 366 IllegalArgumentException, IllegalStateException { 367 append("<?xml version='1.0' encoding='utf-8' standalone='" 368 + (standalone ? "yes" : "no") + "' ?>\n"); 369 mLineStart = true; 370 } 371 startTag(String namespace, String name)372 public XmlSerializer startTag(String namespace, String name) throws IOException, 373 IllegalArgumentException, IllegalStateException { 374 if (mInTag) { 375 append(">\n"); 376 } 377 if (mIndent) { 378 appendIndent(mNesting); 379 } 380 mNesting++; 381 append('<'); 382 if (namespace != null) { 383 append(namespace); 384 append(':'); 385 } 386 append(name); 387 mInTag = true; 388 mLineStart = false; 389 return this; 390 } 391 text(char[] buf, int start, int len)392 public XmlSerializer text(char[] buf, int start, int len) throws IOException, 393 IllegalArgumentException, IllegalStateException { 394 if (mInTag) { 395 append(">"); 396 mInTag = false; 397 } 398 escapeAndAppendString(buf, start, len); 399 if (mIndent) { 400 mLineStart = buf[start+len-1] == '\n'; 401 } 402 return this; 403 } 404 text(String text)405 public XmlSerializer text(String text) throws IOException, IllegalArgumentException, 406 IllegalStateException { 407 if (mInTag) { 408 append(">"); 409 mInTag = false; 410 } 411 escapeAndAppendString(text); 412 if (mIndent) { 413 mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n'); 414 } 415 return this; 416 } 417 418 } 419