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