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