1 /* 2 * Copyright (C) 2020 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 static com.android.internal.util.BinaryXmlSerializer.ATTRIBUTE; 20 import static com.android.internal.util.BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0; 21 import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_FALSE; 22 import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_TRUE; 23 import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_BASE64; 24 import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_HEX; 25 import static com.android.internal.util.BinaryXmlSerializer.TYPE_DOUBLE; 26 import static com.android.internal.util.BinaryXmlSerializer.TYPE_FLOAT; 27 import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT; 28 import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT_HEX; 29 import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG; 30 import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG_HEX; 31 import static com.android.internal.util.BinaryXmlSerializer.TYPE_NULL; 32 import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING; 33 import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING_INTERNED; 34 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.text.TextUtils; 38 import android.util.Base64; 39 import android.util.TypedXmlPullParser; 40 import android.util.TypedXmlSerializer; 41 42 import org.xmlpull.v1.XmlPullParser; 43 import org.xmlpull.v1.XmlPullParserException; 44 45 import java.io.EOFException; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.Reader; 49 import java.nio.charset.StandardCharsets; 50 import java.util.Arrays; 51 import java.util.Objects; 52 53 /** 54 * Parser that reads XML documents using a custom binary wire protocol which 55 * benchmarking has shown to be 8.5x faster than {@link Xml.newFastPullParser()} 56 * for a typical {@code packages.xml}. 57 * <p> 58 * The high-level design of the wire protocol is to directly serialize the event 59 * stream, while efficiently and compactly writing strongly-typed primitives 60 * delivered through the {@link TypedXmlSerializer} interface. 61 * <p> 62 * Each serialized event is a single byte where the lower half is a normal 63 * {@link XmlPullParser} token and the upper half is an optional data type 64 * signal, such as {@link #TYPE_INT}. 65 * <p> 66 * This parser has some specific limitations: 67 * <ul> 68 * <li>Only the UTF-8 encoding is supported. 69 * <li>Variable length values, such as {@code byte[]} or {@link String}, are 70 * limited to 65,535 bytes in length. Note that {@link String} values are stored 71 * as UTF-8 on the wire. 72 * <li>Namespaces, prefixes, properties, and options are unsupported. 73 * </ul> 74 */ 75 public final class BinaryXmlPullParser implements TypedXmlPullParser { 76 /** 77 * Default buffer size, which matches {@code FastXmlSerializer}. This should 78 * be kept in sync with {@link BinaryXmlPullParser}. 79 */ 80 private static final int BUFFER_SIZE = 32_768; 81 82 private FastDataInput mIn; 83 84 private int mCurrentToken = START_DOCUMENT; 85 private int mCurrentDepth = 0; 86 private String mCurrentName; 87 private String mCurrentText; 88 89 /** 90 * Pool of attributes parsed for the currently tag. All interactions should 91 * be done via {@link #obtainAttribute()}, {@link #findAttribute(String)}, 92 * and {@link #resetAttributes()}. 93 */ 94 private int mAttributeCount = 0; 95 private Attribute[] mAttributes; 96 97 @Override setInput(InputStream is, String encoding)98 public void setInput(InputStream is, String encoding) throws XmlPullParserException { 99 if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) { 100 throw new UnsupportedOperationException(); 101 } 102 103 mIn = new FastDataInput(is, BUFFER_SIZE); 104 105 mCurrentToken = START_DOCUMENT; 106 mCurrentDepth = 0; 107 mCurrentName = null; 108 mCurrentText = null; 109 110 mAttributeCount = 0; 111 mAttributes = new Attribute[8]; 112 for (int i = 0; i < mAttributes.length; i++) { 113 mAttributes[i] = new Attribute(); 114 } 115 116 try { 117 final byte[] magic = new byte[4]; 118 mIn.readFully(magic); 119 if (!Arrays.equals(magic, PROTOCOL_MAGIC_VERSION_0)) { 120 throw new IOException("Unexpected magic " + bytesToHexString(magic)); 121 } 122 123 // We're willing to immediately consume a START_DOCUMENT if present, 124 // but we're okay if it's missing 125 if (peekNextExternalToken() == START_DOCUMENT) { 126 consumeToken(); 127 } 128 } catch (IOException e) { 129 throw new XmlPullParserException(e.toString()); 130 } 131 } 132 133 @Override setInput(Reader in)134 public void setInput(Reader in) throws XmlPullParserException { 135 throw new UnsupportedOperationException(); 136 } 137 138 @Override next()139 public int next() throws XmlPullParserException, IOException { 140 while (true) { 141 final int token = nextToken(); 142 switch (token) { 143 case START_TAG: 144 case END_TAG: 145 case END_DOCUMENT: 146 return token; 147 case TEXT: 148 consumeAdditionalText(); 149 // Per interface docs, empty text regions are skipped 150 if (mCurrentText == null || mCurrentText.length() == 0) { 151 continue; 152 } else { 153 return TEXT; 154 } 155 } 156 } 157 } 158 159 @Override nextToken()160 public int nextToken() throws XmlPullParserException, IOException { 161 if (mCurrentToken == XmlPullParser.END_TAG) { 162 mCurrentDepth--; 163 } 164 165 int token; 166 try { 167 token = peekNextExternalToken(); 168 consumeToken(); 169 } catch (EOFException e) { 170 token = END_DOCUMENT; 171 } 172 switch (token) { 173 case XmlPullParser.START_TAG: 174 // We need to peek forward to find the next external token so 175 // that we parse all pending INTERNAL_ATTRIBUTE tokens 176 peekNextExternalToken(); 177 mCurrentDepth++; 178 break; 179 } 180 mCurrentToken = token; 181 return token; 182 } 183 184 /** 185 * Peek at the next "external" token without consuming it. 186 * <p> 187 * External tokens, such as {@link #START_TAG}, are expected by typical 188 * {@link XmlPullParser} clients. In contrast, internal tokens, such as 189 * {@link #ATTRIBUTE}, are not expected by typical clients. 190 * <p> 191 * This method consumes any internal events until it reaches the next 192 * external event. 193 */ peekNextExternalToken()194 private int peekNextExternalToken() throws IOException, XmlPullParserException { 195 while (true) { 196 final int token = peekNextToken(); 197 switch (token) { 198 case ATTRIBUTE: 199 consumeToken(); 200 continue; 201 default: 202 return token; 203 } 204 } 205 } 206 207 /** 208 * Peek at the next token in the underlying stream without consuming it. 209 */ peekNextToken()210 private int peekNextToken() throws IOException { 211 return mIn.peekByte() & 0x0f; 212 } 213 214 /** 215 * Parse and consume the next token in the underlying stream. 216 */ consumeToken()217 private void consumeToken() throws IOException, XmlPullParserException { 218 final int event = mIn.readByte(); 219 final int token = event & 0x0f; 220 final int type = event & 0xf0; 221 switch (token) { 222 case ATTRIBUTE: { 223 final Attribute attr = obtainAttribute(); 224 attr.name = mIn.readInternedUTF(); 225 attr.type = type; 226 switch (type) { 227 case TYPE_NULL: 228 case TYPE_BOOLEAN_TRUE: 229 case TYPE_BOOLEAN_FALSE: 230 // Nothing extra to fill in 231 break; 232 case TYPE_STRING: 233 attr.valueString = mIn.readUTF(); 234 break; 235 case TYPE_STRING_INTERNED: 236 attr.valueString = mIn.readInternedUTF(); 237 break; 238 case TYPE_BYTES_HEX: 239 case TYPE_BYTES_BASE64: 240 final int len = mIn.readUnsignedShort(); 241 final byte[] res = new byte[len]; 242 mIn.readFully(res); 243 attr.valueBytes = res; 244 break; 245 case TYPE_INT: 246 case TYPE_INT_HEX: 247 attr.valueInt = mIn.readInt(); 248 break; 249 case TYPE_LONG: 250 case TYPE_LONG_HEX: 251 attr.valueLong = mIn.readLong(); 252 break; 253 case TYPE_FLOAT: 254 attr.valueFloat = mIn.readFloat(); 255 break; 256 case TYPE_DOUBLE: 257 attr.valueDouble = mIn.readDouble(); 258 break; 259 default: 260 throw new IOException("Unexpected data type " + type); 261 } 262 break; 263 } 264 case XmlPullParser.START_DOCUMENT: { 265 mCurrentName = null; 266 mCurrentText = null; 267 if (mAttributeCount > 0) resetAttributes(); 268 break; 269 } 270 case XmlPullParser.END_DOCUMENT: { 271 mCurrentName = null; 272 mCurrentText = null; 273 if (mAttributeCount > 0) resetAttributes(); 274 break; 275 } 276 case XmlPullParser.START_TAG: { 277 mCurrentName = mIn.readInternedUTF(); 278 mCurrentText = null; 279 if (mAttributeCount > 0) resetAttributes(); 280 break; 281 } 282 case XmlPullParser.END_TAG: { 283 mCurrentName = mIn.readInternedUTF(); 284 mCurrentText = null; 285 if (mAttributeCount > 0) resetAttributes(); 286 break; 287 } 288 case XmlPullParser.TEXT: 289 case XmlPullParser.CDSECT: 290 case XmlPullParser.PROCESSING_INSTRUCTION: 291 case XmlPullParser.COMMENT: 292 case XmlPullParser.DOCDECL: 293 case XmlPullParser.IGNORABLE_WHITESPACE: { 294 mCurrentName = null; 295 mCurrentText = mIn.readUTF(); 296 if (mAttributeCount > 0) resetAttributes(); 297 break; 298 } 299 case XmlPullParser.ENTITY_REF: { 300 mCurrentName = mIn.readUTF(); 301 mCurrentText = resolveEntity(mCurrentName); 302 if (mAttributeCount > 0) resetAttributes(); 303 break; 304 } 305 default: { 306 throw new IOException("Unknown token " + token + " with type " + type); 307 } 308 } 309 } 310 311 /** 312 * When the current tag is {@link #TEXT}, consume all subsequent "text" 313 * events, as described by {@link #next}. When finished, the current event 314 * will still be {@link #TEXT}. 315 */ consumeAdditionalText()316 private void consumeAdditionalText() throws IOException, XmlPullParserException { 317 String combinedText = mCurrentText; 318 while (true) { 319 final int token = peekNextExternalToken(); 320 switch (token) { 321 case COMMENT: 322 case PROCESSING_INSTRUCTION: 323 // Quietly consumed 324 consumeToken(); 325 break; 326 case TEXT: 327 case CDSECT: 328 case ENTITY_REF: 329 // Additional text regions collected 330 consumeToken(); 331 combinedText += mCurrentText; 332 break; 333 default: 334 // Next token is something non-text, so wrap things up 335 mCurrentToken = TEXT; 336 mCurrentName = null; 337 mCurrentText = combinedText; 338 return; 339 } 340 } 341 } 342 resolveEntity(@onNull String entity)343 static @NonNull String resolveEntity(@NonNull String entity) 344 throws XmlPullParserException { 345 switch (entity) { 346 case "lt": return "<"; 347 case "gt": return ">"; 348 case "amp": return "&"; 349 case "apos": return "'"; 350 case "quot": return "\""; 351 } 352 if (entity.length() > 1 && entity.charAt(0) == '#') { 353 final char c = (char) Integer.parseInt(entity.substring(1)); 354 return new String(new char[] { c }); 355 } 356 throw new XmlPullParserException("Unknown entity " + entity); 357 } 358 359 @Override require(int type, String namespace, String name)360 public void require(int type, String namespace, String name) 361 throws XmlPullParserException, IOException { 362 if (namespace != null && !namespace.isEmpty()) throw illegalNamespace(); 363 if (mCurrentToken != type || !Objects.equals(mCurrentName, name)) { 364 throw new XmlPullParserException(getPositionDescription()); 365 } 366 } 367 368 @Override nextText()369 public String nextText() throws XmlPullParserException, IOException { 370 if (getEventType() != START_TAG) { 371 throw new XmlPullParserException(getPositionDescription()); 372 } 373 int eventType = next(); 374 if (eventType == TEXT) { 375 String result = getText(); 376 eventType = next(); 377 if (eventType != END_TAG) { 378 throw new XmlPullParserException(getPositionDescription()); 379 } 380 return result; 381 } else if (eventType == END_TAG) { 382 return ""; 383 } else { 384 throw new XmlPullParserException(getPositionDescription()); 385 } 386 } 387 388 @Override nextTag()389 public int nextTag() throws XmlPullParserException, IOException { 390 int eventType = next(); 391 if (eventType == TEXT && isWhitespace()) { 392 eventType = next(); 393 } 394 if (eventType != START_TAG && eventType != END_TAG) { 395 throw new XmlPullParserException(getPositionDescription()); 396 } 397 return eventType; 398 } 399 400 /** 401 * Allocate and return a new {@link Attribute} associated with the tag being 402 * currently processed. This will automatically grow the internal pool as 403 * needed. 404 */ obtainAttribute()405 private @NonNull Attribute obtainAttribute() { 406 if (mAttributeCount == mAttributes.length) { 407 final int before = mAttributes.length; 408 final int after = before + (before >> 1); 409 mAttributes = Arrays.copyOf(mAttributes, after); 410 for (int i = before; i < after; i++) { 411 mAttributes[i] = new Attribute(); 412 } 413 } 414 return mAttributes[mAttributeCount++]; 415 } 416 417 /** 418 * Clear any {@link Attribute} instances that have been allocated by 419 * {@link #obtainAttribute()}, returning them into the pool for recycling. 420 */ resetAttributes()421 private void resetAttributes() { 422 for (int i = 0; i < mAttributeCount; i++) { 423 mAttributes[i].reset(); 424 } 425 mAttributeCount = 0; 426 } 427 428 @Override getAttributeIndex(String namespace, String name)429 public int getAttributeIndex(String namespace, String name) { 430 if (namespace != null && !namespace.isEmpty()) throw illegalNamespace(); 431 for (int i = 0; i < mAttributeCount; i++) { 432 if (Objects.equals(mAttributes[i].name, name)) { 433 return i; 434 } 435 } 436 return -1; 437 } 438 439 @Override getAttributeValue(String namespace, String name)440 public String getAttributeValue(String namespace, String name) { 441 final int index = getAttributeIndex(namespace, name); 442 if (index != -1) { 443 return mAttributes[index].getValueString(); 444 } else { 445 return null; 446 } 447 } 448 449 @Override getAttributeValue(int index)450 public String getAttributeValue(int index) { 451 return mAttributes[index].getValueString(); 452 } 453 454 @Override getAttributeBytesHex(int index)455 public byte[] getAttributeBytesHex(int index) throws XmlPullParserException { 456 return mAttributes[index].getValueBytesHex(); 457 } 458 459 @Override getAttributeBytesBase64(int index)460 public byte[] getAttributeBytesBase64(int index) throws XmlPullParserException { 461 return mAttributes[index].getValueBytesBase64(); 462 } 463 464 @Override getAttributeInt(int index)465 public int getAttributeInt(int index) throws XmlPullParserException { 466 return mAttributes[index].getValueInt(); 467 } 468 469 @Override getAttributeIntHex(int index)470 public int getAttributeIntHex(int index) throws XmlPullParserException { 471 return mAttributes[index].getValueIntHex(); 472 } 473 474 @Override getAttributeLong(int index)475 public long getAttributeLong(int index) throws XmlPullParserException { 476 return mAttributes[index].getValueLong(); 477 } 478 479 @Override getAttributeLongHex(int index)480 public long getAttributeLongHex(int index) throws XmlPullParserException { 481 return mAttributes[index].getValueLongHex(); 482 } 483 484 @Override getAttributeFloat(int index)485 public float getAttributeFloat(int index) throws XmlPullParserException { 486 return mAttributes[index].getValueFloat(); 487 } 488 489 @Override getAttributeDouble(int index)490 public double getAttributeDouble(int index) throws XmlPullParserException { 491 return mAttributes[index].getValueDouble(); 492 } 493 494 @Override getAttributeBoolean(int index)495 public boolean getAttributeBoolean(int index) throws XmlPullParserException { 496 return mAttributes[index].getValueBoolean(); 497 } 498 499 @Override getText()500 public String getText() { 501 return mCurrentText; 502 } 503 504 @Override getTextCharacters(int[] holderForStartAndLength)505 public char[] getTextCharacters(int[] holderForStartAndLength) { 506 final char[] chars = mCurrentText.toCharArray(); 507 holderForStartAndLength[0] = 0; 508 holderForStartAndLength[1] = chars.length; 509 return chars; 510 } 511 512 @Override getInputEncoding()513 public String getInputEncoding() { 514 return StandardCharsets.UTF_8.name(); 515 } 516 517 @Override getDepth()518 public int getDepth() { 519 return mCurrentDepth; 520 } 521 522 @Override getPositionDescription()523 public String getPositionDescription() { 524 // Not very helpful, but it's the best information we have 525 return "Token " + mCurrentToken + " at depth " + mCurrentDepth; 526 } 527 528 @Override getLineNumber()529 public int getLineNumber() { 530 return -1; 531 } 532 533 @Override getColumnNumber()534 public int getColumnNumber() { 535 return -1; 536 } 537 538 @Override isWhitespace()539 public boolean isWhitespace() throws XmlPullParserException { 540 switch (mCurrentToken) { 541 case IGNORABLE_WHITESPACE: 542 return true; 543 case TEXT: 544 case CDSECT: 545 return !TextUtils.isGraphic(mCurrentText); 546 default: 547 throw new XmlPullParserException("Not applicable for token " + mCurrentToken); 548 } 549 } 550 551 @Override getNamespace()552 public String getNamespace() { 553 switch (mCurrentToken) { 554 case START_TAG: 555 case END_TAG: 556 // Namespaces are unsupported 557 return NO_NAMESPACE; 558 default: 559 return null; 560 } 561 } 562 563 @Override getName()564 public String getName() { 565 return mCurrentName; 566 } 567 568 @Override getPrefix()569 public String getPrefix() { 570 // Prefixes are not supported 571 return null; 572 } 573 574 @Override isEmptyElementTag()575 public boolean isEmptyElementTag() throws XmlPullParserException { 576 switch (mCurrentToken) { 577 case START_TAG: 578 try { 579 return (peekNextExternalToken() == END_TAG); 580 } catch (IOException e) { 581 throw new XmlPullParserException(e.toString()); 582 } 583 default: 584 throw new XmlPullParserException("Not at START_TAG"); 585 } 586 } 587 588 @Override getAttributeCount()589 public int getAttributeCount() { 590 return mAttributeCount; 591 } 592 593 @Override getAttributeNamespace(int index)594 public String getAttributeNamespace(int index) { 595 // Namespaces are unsupported 596 return NO_NAMESPACE; 597 } 598 599 @Override getAttributeName(int index)600 public String getAttributeName(int index) { 601 return mAttributes[index].name; 602 } 603 604 @Override getAttributePrefix(int index)605 public String getAttributePrefix(int index) { 606 // Prefixes are not supported 607 return null; 608 } 609 610 @Override getAttributeType(int index)611 public String getAttributeType(int index) { 612 // Validation is not supported 613 return "CDATA"; 614 } 615 616 @Override isAttributeDefault(int index)617 public boolean isAttributeDefault(int index) { 618 // Validation is not supported 619 return false; 620 } 621 622 @Override getEventType()623 public int getEventType() throws XmlPullParserException { 624 return mCurrentToken; 625 } 626 627 @Override getNamespaceCount(int depth)628 public int getNamespaceCount(int depth) throws XmlPullParserException { 629 // Namespaces are unsupported 630 return 0; 631 } 632 633 @Override getNamespacePrefix(int pos)634 public String getNamespacePrefix(int pos) throws XmlPullParserException { 635 // Namespaces are unsupported 636 throw new UnsupportedOperationException(); 637 } 638 639 @Override getNamespaceUri(int pos)640 public String getNamespaceUri(int pos) throws XmlPullParserException { 641 // Namespaces are unsupported 642 throw new UnsupportedOperationException(); 643 } 644 645 @Override getNamespace(String prefix)646 public String getNamespace(String prefix) { 647 // Namespaces are unsupported 648 throw new UnsupportedOperationException(); 649 } 650 651 @Override defineEntityReplacementText(String entityName, String replacementText)652 public void defineEntityReplacementText(String entityName, String replacementText) 653 throws XmlPullParserException { 654 // Custom entities are not supported 655 throw new UnsupportedOperationException(); 656 } 657 658 @Override setFeature(String name, boolean state)659 public void setFeature(String name, boolean state) throws XmlPullParserException { 660 // Features are not supported 661 throw new UnsupportedOperationException(); 662 } 663 664 @Override getFeature(String name)665 public boolean getFeature(String name) { 666 // Features are not supported 667 throw new UnsupportedOperationException(); 668 } 669 670 @Override setProperty(String name, Object value)671 public void setProperty(String name, Object value) throws XmlPullParserException { 672 // Properties are not supported 673 throw new UnsupportedOperationException(); 674 } 675 676 @Override getProperty(String name)677 public Object getProperty(String name) { 678 // Properties are not supported 679 throw new UnsupportedOperationException(); 680 } 681 illegalNamespace()682 private static IllegalArgumentException illegalNamespace() { 683 throw new IllegalArgumentException("Namespaces are not supported"); 684 } 685 686 /** 687 * Holder representing a single attribute. This design enables object 688 * recycling without resorting to autoboxing. 689 * <p> 690 * To support conversion between human-readable XML and binary XML, the 691 * various accessor methods will transparently convert from/to 692 * human-readable values when needed. 693 */ 694 private static class Attribute { 695 public String name; 696 public int type; 697 698 public String valueString; 699 public byte[] valueBytes; 700 public int valueInt; 701 public long valueLong; 702 public float valueFloat; 703 public double valueDouble; 704 reset()705 public void reset() { 706 name = null; 707 valueString = null; 708 valueBytes = null; 709 } 710 getValueString()711 public @Nullable String getValueString() { 712 switch (type) { 713 case TYPE_NULL: 714 return null; 715 case TYPE_STRING: 716 case TYPE_STRING_INTERNED: 717 return valueString; 718 case TYPE_BYTES_HEX: 719 return bytesToHexString(valueBytes); 720 case TYPE_BYTES_BASE64: 721 return Base64.encodeToString(valueBytes, Base64.NO_WRAP); 722 case TYPE_INT: 723 return Integer.toString(valueInt); 724 case TYPE_INT_HEX: 725 return Integer.toString(valueInt, 16); 726 case TYPE_LONG: 727 return Long.toString(valueLong); 728 case TYPE_LONG_HEX: 729 return Long.toString(valueLong, 16); 730 case TYPE_FLOAT: 731 return Float.toString(valueFloat); 732 case TYPE_DOUBLE: 733 return Double.toString(valueDouble); 734 case TYPE_BOOLEAN_TRUE: 735 return "true"; 736 case TYPE_BOOLEAN_FALSE: 737 return "false"; 738 default: 739 // Unknown data type; null is the best we can offer 740 return null; 741 } 742 } 743 getValueBytesHex()744 public @Nullable byte[] getValueBytesHex() throws XmlPullParserException { 745 switch (type) { 746 case TYPE_NULL: 747 return null; 748 case TYPE_BYTES_HEX: 749 case TYPE_BYTES_BASE64: 750 return valueBytes; 751 case TYPE_STRING: 752 case TYPE_STRING_INTERNED: 753 try { 754 return hexStringToBytes(valueString); 755 } catch (Exception e) { 756 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 757 } 758 default: 759 throw new XmlPullParserException("Invalid conversion from " + type); 760 } 761 } 762 getValueBytesBase64()763 public @Nullable byte[] getValueBytesBase64() throws XmlPullParserException { 764 switch (type) { 765 case TYPE_NULL: 766 return null; 767 case TYPE_BYTES_HEX: 768 case TYPE_BYTES_BASE64: 769 return valueBytes; 770 case TYPE_STRING: 771 case TYPE_STRING_INTERNED: 772 try { 773 return Base64.decode(valueString, Base64.NO_WRAP); 774 } catch (Exception e) { 775 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 776 } 777 default: 778 throw new XmlPullParserException("Invalid conversion from " + type); 779 } 780 } 781 getValueInt()782 public int getValueInt() throws XmlPullParserException { 783 switch (type) { 784 case TYPE_INT: 785 case TYPE_INT_HEX: 786 return valueInt; 787 case TYPE_STRING: 788 case TYPE_STRING_INTERNED: 789 try { 790 return Integer.parseInt(valueString); 791 } catch (Exception e) { 792 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 793 } 794 default: 795 throw new XmlPullParserException("Invalid conversion from " + type); 796 } 797 } 798 getValueIntHex()799 public int getValueIntHex() throws XmlPullParserException { 800 switch (type) { 801 case TYPE_INT: 802 case TYPE_INT_HEX: 803 return valueInt; 804 case TYPE_STRING: 805 case TYPE_STRING_INTERNED: 806 try { 807 return Integer.parseInt(valueString, 16); 808 } catch (Exception e) { 809 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 810 } 811 default: 812 throw new XmlPullParserException("Invalid conversion from " + type); 813 } 814 } 815 getValueLong()816 public long getValueLong() throws XmlPullParserException { 817 switch (type) { 818 case TYPE_LONG: 819 case TYPE_LONG_HEX: 820 return valueLong; 821 case TYPE_STRING: 822 case TYPE_STRING_INTERNED: 823 try { 824 return Long.parseLong(valueString); 825 } catch (Exception e) { 826 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 827 } 828 default: 829 throw new XmlPullParserException("Invalid conversion from " + type); 830 } 831 } 832 getValueLongHex()833 public long getValueLongHex() throws XmlPullParserException { 834 switch (type) { 835 case TYPE_LONG: 836 case TYPE_LONG_HEX: 837 return valueLong; 838 case TYPE_STRING: 839 case TYPE_STRING_INTERNED: 840 try { 841 return Long.parseLong(valueString, 16); 842 } catch (Exception e) { 843 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 844 } 845 default: 846 throw new XmlPullParserException("Invalid conversion from " + type); 847 } 848 } 849 getValueFloat()850 public float getValueFloat() throws XmlPullParserException { 851 switch (type) { 852 case TYPE_FLOAT: 853 return valueFloat; 854 case TYPE_STRING: 855 case TYPE_STRING_INTERNED: 856 try { 857 return Float.parseFloat(valueString); 858 } catch (Exception e) { 859 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 860 } 861 default: 862 throw new XmlPullParserException("Invalid conversion from " + type); 863 } 864 } 865 getValueDouble()866 public double getValueDouble() throws XmlPullParserException { 867 switch (type) { 868 case TYPE_DOUBLE: 869 return valueDouble; 870 case TYPE_STRING: 871 case TYPE_STRING_INTERNED: 872 try { 873 return Double.parseDouble(valueString); 874 } catch (Exception e) { 875 throw new XmlPullParserException("Invalid attribute " + name + ": " + e); 876 } 877 default: 878 throw new XmlPullParserException("Invalid conversion from " + type); 879 } 880 } 881 getValueBoolean()882 public boolean getValueBoolean() throws XmlPullParserException { 883 switch (type) { 884 case TYPE_BOOLEAN_TRUE: 885 return true; 886 case TYPE_BOOLEAN_FALSE: 887 return false; 888 case TYPE_STRING: 889 case TYPE_STRING_INTERNED: 890 if ("true".equalsIgnoreCase(valueString)) { 891 return true; 892 } else if ("false".equalsIgnoreCase(valueString)) { 893 return false; 894 } else { 895 throw new XmlPullParserException( 896 "Invalid attribute " + name + ": " + valueString); 897 } 898 default: 899 throw new XmlPullParserException("Invalid conversion from " + type); 900 } 901 } 902 } 903 904 // NOTE: To support unbundled clients, we include an inlined copy 905 // of hex conversion logic from HexDump below 906 private final static char[] HEX_DIGITS = 907 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 908 toByte(char c)909 private static int toByte(char c) { 910 if (c >= '0' && c <= '9') return (c - '0'); 911 if (c >= 'A' && c <= 'F') return (c - 'A' + 10); 912 if (c >= 'a' && c <= 'f') return (c - 'a' + 10); 913 throw new IllegalArgumentException("Invalid hex char '" + c + "'"); 914 } 915 bytesToHexString(byte[] value)916 static String bytesToHexString(byte[] value) { 917 final int length = value.length; 918 final char[] buf = new char[length * 2]; 919 int bufIndex = 0; 920 for (int i = 0; i < length; i++) { 921 byte b = value[i]; 922 buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; 923 buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; 924 } 925 return new String(buf); 926 } 927 hexStringToBytes(String value)928 static byte[] hexStringToBytes(String value) { 929 final int length = value.length(); 930 if (length % 2 != 0) { 931 throw new IllegalArgumentException("Invalid hex length " + length); 932 } 933 byte[] buffer = new byte[length / 2]; 934 for (int i = 0; i < length; i += 2) { 935 buffer[i / 2] = (byte) ((toByte(value.charAt(i)) << 4) 936 | toByte(value.charAt(i + 1))); 937 } 938 return buffer; 939 } 940 } 941