1 package org.robolectric.android; 2 3 import static org.robolectric.res.AttributeResource.ANDROID_RES_NS_PREFIX; 4 import static org.robolectric.res.AttributeResource.RES_AUTO_NS_URI; 5 6 import android.content.res.Resources; 7 import android.content.res.XmlResourceParser; 8 import com.android.internal.util.XmlUtils; 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.io.Reader; 12 import java.nio.file.Path; 13 import java.util.Arrays; 14 import java.util.List; 15 import org.robolectric.res.AttributeResource; 16 import org.robolectric.res.Fs; 17 import org.robolectric.res.ResName; 18 import org.robolectric.res.ResourceTable; 19 import org.robolectric.res.StringResources; 20 import org.w3c.dom.Document; 21 import org.w3c.dom.Element; 22 import org.w3c.dom.NamedNodeMap; 23 import org.w3c.dom.Node; 24 import org.xmlpull.v1.XmlPullParserException; 25 26 /** 27 * Concrete implementation of the {@link XmlResourceParser}. 28 * 29 * Clients expects a pull parser while the resource loader 30 * initialise this object with a {@link Document}. 31 * This implementation navigates the dom and emulates a pull 32 * parser by raising all the opportune events. 33 * 34 * Note that the original android implementation is based on 35 * a set of native methods calls. Here those methods are 36 * re-implemented in java when possible. 37 */ 38 public class XmlResourceParserImpl implements XmlResourceParser { 39 40 /** 41 * All the parser features currently supported by Android. 42 */ 43 public static final String[] AVAILABLE_FEATURES = { 44 XmlResourceParser.FEATURE_PROCESS_NAMESPACES, 45 XmlResourceParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES 46 }; 47 /** 48 * All the parser features currently NOT supported by Android. 49 */ 50 public static final String[] UNAVAILABLE_FEATURES = { 51 XmlResourceParser.FEATURE_PROCESS_DOCDECL, 52 XmlResourceParser.FEATURE_VALIDATION 53 }; 54 55 private final Document document; 56 private final Path fileName; 57 private final String packageName; 58 private final ResourceTable resourceTable; 59 private final String applicationNamespace; 60 61 private Node currentNode; 62 63 private boolean mStarted = false; 64 private boolean mDecNextDepth = false; 65 private int mDepth = 0; 66 private int mEventType = START_DOCUMENT; 67 68 /** 69 * @deprecated use {@link XmlResourceParserImpl#XmlResourceParserImpl(Document, Path, String, 70 * String, ResourceTable)} instead. 71 */ 72 @Deprecated XmlResourceParserImpl( Document document, String fileName, String packageName, String applicationPackageName, ResourceTable resourceTable)73 public XmlResourceParserImpl( 74 Document document, 75 String fileName, 76 String packageName, 77 String applicationPackageName, 78 ResourceTable resourceTable) { 79 this(document, Fs.fromUrl(fileName), packageName, applicationPackageName, resourceTable); 80 } 81 XmlResourceParserImpl( Document document, Path fileName, String packageName, String applicationPackageName, ResourceTable resourceTable)82 public XmlResourceParserImpl( 83 Document document, 84 Path fileName, 85 String packageName, 86 String applicationPackageName, 87 ResourceTable resourceTable) { 88 this.document = document; 89 this.fileName = fileName; 90 this.packageName = packageName; 91 this.resourceTable = resourceTable; 92 this.applicationNamespace = ANDROID_RES_NS_PREFIX + applicationPackageName; 93 } 94 95 @Override setFeature(String name, boolean state)96 public void setFeature(String name, boolean state) 97 throws XmlPullParserException { 98 if (isAndroidSupportedFeature(name) && state) { 99 return; 100 } 101 throw new XmlPullParserException("Unsupported feature: " + name); 102 } 103 104 @Override getFeature(String name)105 public boolean getFeature(String name) { 106 return isAndroidSupportedFeature(name); 107 } 108 109 @Override setProperty(String name, Object value)110 public void setProperty(String name, Object value) 111 throws XmlPullParserException { 112 throw new XmlPullParserException("setProperty() not supported"); 113 } 114 115 @Override getProperty(String name)116 public Object getProperty(String name) { 117 // Properties are not supported. Android returns null 118 // instead of throwing an XmlPullParserException. 119 return null; 120 } 121 122 @Override setInput(Reader in)123 public void setInput(Reader in) throws XmlPullParserException { 124 throw new XmlPullParserException("setInput() not supported"); 125 } 126 127 @Override setInput(InputStream inputStream, String inputEncoding)128 public void setInput(InputStream inputStream, String inputEncoding) 129 throws XmlPullParserException { 130 throw new XmlPullParserException("setInput() not supported"); 131 } 132 133 @Override defineEntityReplacementText( String entityName, String replacementText)134 public void defineEntityReplacementText( 135 String entityName, String replacementText) 136 throws XmlPullParserException { 137 throw new XmlPullParserException( 138 "defineEntityReplacementText() not supported"); 139 } 140 141 @Override getNamespacePrefix(int pos)142 public String getNamespacePrefix(int pos) 143 throws XmlPullParserException { 144 throw new XmlPullParserException( 145 "getNamespacePrefix() not supported"); 146 } 147 148 @Override getInputEncoding()149 public String getInputEncoding() { 150 return null; 151 } 152 153 @Override getNamespace(String prefix)154 public String getNamespace(String prefix) { 155 throw new RuntimeException( 156 "getNamespaceCount() not supported"); 157 } 158 159 @Override getNamespaceCount(int depth)160 public int getNamespaceCount(int depth) 161 throws XmlPullParserException { 162 throw new XmlPullParserException( 163 "getNamespaceCount() not supported"); 164 } 165 166 @Override getPositionDescription()167 public String getPositionDescription() { 168 return "XML file " + fileName + " line #" + getLineNumber() + " (sorry, not yet implemented)"; 169 } 170 171 @Override getNamespaceUri(int pos)172 public String getNamespaceUri(int pos) 173 throws XmlPullParserException { 174 throw new XmlPullParserException( 175 "getNamespaceUri() not supported"); 176 } 177 178 @Override getColumnNumber()179 public int getColumnNumber() { 180 // Android always returns -1 181 return -1; 182 } 183 184 @Override getDepth()185 public int getDepth() { 186 return mDepth; 187 } 188 189 @Override getText()190 public String getText() { 191 if (currentNode == null) { 192 return ""; 193 } 194 return StringResources.processStringResources(currentNode.getTextContent()); 195 } 196 197 @Override getLineNumber()198 public int getLineNumber() { 199 // TODO(msama): The current implementation is 200 // unable to return line numbers. 201 return -1; 202 } 203 204 @Override getEventType()205 public int getEventType() 206 throws XmlPullParserException { 207 return mEventType; 208 } 209 210 /*package*/ isWhitespace(String text)211 public boolean isWhitespace(String text) 212 throws XmlPullParserException { 213 if (text == null) { 214 return false; 215 } 216 return text.split("\\s").length == 0; 217 } 218 219 @Override isWhitespace()220 public boolean isWhitespace() 221 throws XmlPullParserException { 222 // Note: in android whitespaces are automatically stripped. 223 // Here we have to skip them manually 224 return isWhitespace(getText()); 225 } 226 227 @Override getPrefix()228 public String getPrefix() { 229 throw new RuntimeException("getPrefix not supported"); 230 } 231 232 @Override getTextCharacters(int[] holderForStartAndLength)233 public char[] getTextCharacters(int[] holderForStartAndLength) { 234 String txt = getText(); 235 char[] chars = null; 236 if (txt != null) { 237 holderForStartAndLength[0] = 0; 238 holderForStartAndLength[1] = txt.length(); 239 chars = new char[txt.length()]; 240 txt.getChars(0, txt.length(), chars, 0); 241 } 242 return chars; 243 } 244 245 @Override getNamespace()246 public String getNamespace() { 247 String namespace = currentNode != null ? currentNode.getNamespaceURI() : null; 248 if (namespace == null) { 249 return ""; 250 } 251 252 return maybeReplaceNamespace(namespace); 253 } 254 255 @Override getName()256 public String getName() { 257 if (currentNode == null) { 258 return null; 259 } 260 return currentNode.getNodeName(); 261 } 262 getAttributeAt(int index)263 Node getAttributeAt(int index) { 264 if (currentNode == null) { 265 throw new IndexOutOfBoundsException(String.valueOf(index)); 266 } 267 NamedNodeMap map = currentNode.getAttributes(); 268 if (index >= map.getLength()) { 269 throw new IndexOutOfBoundsException(String.valueOf(index)); 270 } 271 return map.item(index); 272 } 273 getAttribute(String namespace, String name)274 public String getAttribute(String namespace, String name) { 275 if (currentNode == null) { 276 return null; 277 } 278 279 Element element = (Element) currentNode; 280 if (element.hasAttributeNS(namespace, name)) { 281 return element.getAttributeNS(namespace, name).trim(); 282 } else if (applicationNamespace.equals(namespace) 283 && element.hasAttributeNS(AttributeResource.RES_AUTO_NS_URI, name)) { 284 return element.getAttributeNS(AttributeResource.RES_AUTO_NS_URI, name).trim(); 285 } 286 287 return null; 288 } 289 290 @Override getAttributeNamespace(int index)291 public String getAttributeNamespace(int index) { 292 Node attr = getAttributeAt(index); 293 if (attr == null) { 294 return ""; 295 } 296 return maybeReplaceNamespace(attr.getNamespaceURI()); 297 } 298 maybeReplaceNamespace(String namespace)299 private String maybeReplaceNamespace(String namespace) { 300 if (namespace == null) { 301 return ""; 302 } else if (namespace.equals(applicationNamespace)) { 303 return AttributeResource.RES_AUTO_NS_URI; 304 } else { 305 return namespace; 306 } 307 } 308 309 @Override getAttributeName(int index)310 public String getAttributeName(int index) { 311 Node attr = getAttributeAt(index); 312 String name = attr.getLocalName(); 313 return name == null ? attr.getNodeName() : name; 314 } 315 316 @Override getAttributePrefix(int index)317 public String getAttributePrefix(int index) { 318 throw new RuntimeException("getAttributePrefix not supported"); 319 } 320 321 @Override isEmptyElementTag()322 public boolean isEmptyElementTag() throws XmlPullParserException { 323 // In Android this method is left unimplemented. 324 // This implementation is mirroring that. 325 return false; 326 } 327 328 @Override getAttributeCount()329 public int getAttributeCount() { 330 if (currentNode == null) { 331 return -1; 332 } 333 return currentNode.getAttributes().getLength(); 334 } 335 336 @Override getAttributeValue(int index)337 public String getAttributeValue(int index) { 338 return qualify(getAttributeAt(index).getNodeValue()); 339 } 340 341 // for testing only... qualify(String value)342 public String qualify(String value) { 343 if (value == null) return null; 344 if (AttributeResource.isResourceReference(value)) { 345 return "@" + ResName.qualifyResourceName(value.trim().substring(1).replace("+", ""), packageName, "attr"); 346 } else if (AttributeResource.isStyleReference(value)) { 347 return "?" + ResName.qualifyResourceName(value.trim().substring(1), packageName, "attr"); 348 } else { 349 return StringResources.processStringResources(value); 350 } 351 } 352 353 @Override getAttributeType(int index)354 public String getAttributeType(int index) { 355 // Android always returns CDATA even if the 356 // node has no attribute. 357 return "CDATA"; 358 } 359 360 @Override isAttributeDefault(int index)361 public boolean isAttributeDefault(int index) { 362 // The android implementation always returns false 363 return false; 364 } 365 366 @Override nextToken()367 public int nextToken() throws XmlPullParserException, IOException { 368 return next(); 369 } 370 371 @Override getAttributeValue(String namespace, String name)372 public String getAttributeValue(String namespace, String name) { 373 return qualify(getAttribute(namespace, name)); 374 } 375 376 @Override next()377 public int next() throws XmlPullParserException, IOException { 378 if (!mStarted) { 379 mStarted = true; 380 return START_DOCUMENT; 381 } 382 if (mEventType == END_DOCUMENT) { 383 return END_DOCUMENT; 384 } 385 int ev = nativeNext(); 386 if (mDecNextDepth) { 387 mDepth--; 388 mDecNextDepth = false; 389 } 390 switch (ev) { 391 case START_TAG: 392 mDepth++; 393 break; 394 case END_TAG: 395 mDecNextDepth = true; 396 break; 397 } 398 mEventType = ev; 399 if (ev == END_DOCUMENT) { 400 // Automatically close the parse when we reach the end of 401 // a document, since the standard XmlPullParser interface 402 // doesn't have such an API so most clients will leave us 403 // dangling. 404 close(); 405 } 406 return ev; 407 } 408 409 /** 410 * A twin implementation of the native android nativeNext(status) 411 * 412 * @throws XmlPullParserException 413 */ nativeNext()414 private int nativeNext() throws XmlPullParserException { 415 switch (mEventType) { 416 case (CDSECT): { 417 throw new IllegalArgumentException( 418 "CDSECT is not handled by Android"); 419 } 420 case (COMMENT): { 421 throw new IllegalArgumentException( 422 "COMMENT is not handled by Android"); 423 } 424 case (DOCDECL): { 425 throw new IllegalArgumentException( 426 "DOCDECL is not handled by Android"); 427 } 428 case (ENTITY_REF): { 429 throw new IllegalArgumentException( 430 "ENTITY_REF is not handled by Android"); 431 } 432 case (END_DOCUMENT): { 433 // The end document event should have been filtered 434 // from the invoker. This should never happen. 435 throw new IllegalArgumentException( 436 "END_DOCUMENT should not be found here."); 437 } 438 case (END_TAG): { 439 return navigateToNextNode(currentNode); 440 } 441 case (IGNORABLE_WHITESPACE): { 442 throw new IllegalArgumentException( 443 "IGNORABLE_WHITESPACE"); 444 } 445 case (PROCESSING_INSTRUCTION): { 446 throw new IllegalArgumentException( 447 "PROCESSING_INSTRUCTION"); 448 } 449 case (START_DOCUMENT): { 450 currentNode = document.getDocumentElement(); 451 return START_TAG; 452 } 453 case (START_TAG): { 454 if (currentNode.hasChildNodes()) { 455 // The node has children, navigate down 456 return processNextNodeType( 457 currentNode.getFirstChild()); 458 } else { 459 // The node has no children 460 return END_TAG; 461 } 462 } 463 case (TEXT): { 464 return navigateToNextNode(currentNode); 465 } 466 default: { 467 // This can only happen if mEventType is 468 // assigned with an unmapped integer. 469 throw new RuntimeException( 470 "Robolectric-> Uknown XML event type: " + mEventType); 471 } 472 } 473 474 } 475 processNextNodeType(Node node)476 /*protected*/ int processNextNodeType(Node node) 477 throws XmlPullParserException { 478 switch (node.getNodeType()) { 479 case (Node.ATTRIBUTE_NODE): { 480 throw new IllegalArgumentException("ATTRIBUTE_NODE"); 481 } 482 case (Node.CDATA_SECTION_NODE): { 483 return navigateToNextNode(node); 484 } 485 case (Node.COMMENT_NODE): { 486 return navigateToNextNode(node); 487 } 488 case (Node.DOCUMENT_FRAGMENT_NODE): { 489 throw new IllegalArgumentException("DOCUMENT_FRAGMENT_NODE"); 490 } 491 case (Node.DOCUMENT_NODE): { 492 throw new IllegalArgumentException("DOCUMENT_NODE"); 493 } 494 case (Node.DOCUMENT_TYPE_NODE): { 495 throw new IllegalArgumentException("DOCUMENT_TYPE_NODE"); 496 } 497 case (Node.ELEMENT_NODE): { 498 currentNode = node; 499 return START_TAG; 500 } 501 case (Node.ENTITY_NODE): { 502 throw new IllegalArgumentException("ENTITY_NODE"); 503 } 504 case (Node.ENTITY_REFERENCE_NODE): { 505 throw new IllegalArgumentException("ENTITY_REFERENCE_NODE"); 506 } 507 case (Node.NOTATION_NODE): { 508 throw new IllegalArgumentException("DOCUMENT_TYPE_NODE"); 509 } 510 case (Node.PROCESSING_INSTRUCTION_NODE): { 511 throw new IllegalArgumentException("DOCUMENT_TYPE_NODE"); 512 } 513 case (Node.TEXT_NODE): { 514 if (isWhitespace(node.getNodeValue())) { 515 // Skip whitespaces 516 return navigateToNextNode(node); 517 } else { 518 currentNode = node; 519 return TEXT; 520 } 521 } 522 default: { 523 throw new RuntimeException( 524 "Robolectric -> Unknown node type: " + 525 node.getNodeType() + "."); 526 } 527 } 528 } 529 530 /** 531 * Navigate to the next node after a node and all of his 532 * children have been explored. 533 * 534 * If the node has unexplored siblings navigate to the 535 * next sibling. Otherwise return to its parent. 536 * 537 * @param node the node which was just explored. 538 * @return {@link XmlPullParserException#START_TAG} if the given 539 * node has siblings, {@link XmlPullParserException#END_TAG} 540 * if the node has no unexplored siblings or 541 * {@link XmlPullParserException#END_DOCUMENT} if the explored 542 * was the root document. 543 * @throws XmlPullParserException if the parser fails to 544 * parse the next node. 545 */ navigateToNextNode(Node node)546 int navigateToNextNode(Node node) 547 throws XmlPullParserException { 548 Node nextNode = node.getNextSibling(); 549 if (nextNode != null) { 550 // Move to the next siblings 551 return processNextNodeType(nextNode); 552 } else { 553 // Goes back to the parent 554 if (document.getDocumentElement().equals(node)) { 555 currentNode = null; 556 return END_DOCUMENT; 557 } 558 currentNode = node.getParentNode(); 559 return END_TAG; 560 } 561 } 562 563 @Override require(int type, String namespace, String name)564 public void require(int type, String namespace, String name) 565 throws XmlPullParserException, IOException { 566 if (type != getEventType() 567 || (namespace != null && !namespace.equals(getNamespace())) 568 || (name != null && !name.equals(getName()))) { 569 throw new XmlPullParserException( 570 "expected " + TYPES[type] + getPositionDescription()); 571 } 572 } 573 574 @Override nextText()575 public String nextText() throws XmlPullParserException, IOException { 576 if (getEventType() != START_TAG) { 577 throw new XmlPullParserException( 578 getPositionDescription() 579 + ": parser must be on START_TAG to read next text", this, null); 580 } 581 int eventType = next(); 582 if (eventType == TEXT) { 583 String result = getText(); 584 eventType = next(); 585 if (eventType != END_TAG) { 586 throw new XmlPullParserException( 587 getPositionDescription() 588 + ": event TEXT it must be immediately followed by END_TAG", this, null); 589 } 590 return result; 591 } else if (eventType == END_TAG) { 592 return ""; 593 } else { 594 throw new XmlPullParserException( 595 getPositionDescription() 596 + ": parser must be on START_TAG or TEXT to read text", this, null); 597 } 598 } 599 600 @Override nextTag()601 public int nextTag() throws XmlPullParserException, IOException { 602 int eventType = next(); 603 if (eventType == TEXT && isWhitespace()) { // skip whitespace 604 eventType = next(); 605 } 606 if (eventType != START_TAG && eventType != END_TAG) { 607 throw new XmlPullParserException( 608 "Expected start or end tag. Found: " + eventType, this, null); 609 } 610 return eventType; 611 } 612 613 @Override getAttributeNameResource(int index)614 public int getAttributeNameResource(int index) { 615 String attributeNamespace = getAttributeNamespace(index); 616 if (attributeNamespace.equals(RES_AUTO_NS_URI)) { 617 attributeNamespace = packageName; 618 } else if (attributeNamespace.startsWith(ANDROID_RES_NS_PREFIX)) { 619 attributeNamespace = attributeNamespace.substring(ANDROID_RES_NS_PREFIX.length()); 620 } 621 return getResourceId(getAttributeName(index), attributeNamespace, "attr"); 622 } 623 624 @Override getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)625 public int getAttributeListValue(String namespace, String attribute, 626 String[] options, int defaultValue) { 627 String attr = getAttribute(namespace, attribute); 628 if (attr == null) { 629 return 0; 630 } 631 List<String> optList = Arrays.asList(options); 632 int index = optList.indexOf(attr); 633 if (index == -1) { 634 return defaultValue; 635 } 636 return index; 637 } 638 639 @Override getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)640 public boolean getAttributeBooleanValue(String namespace, String attribute, 641 boolean defaultValue) { 642 String attr = getAttribute(namespace, attribute); 643 if (attr == null) { 644 return defaultValue; 645 } 646 return Boolean.parseBoolean(attr); 647 } 648 649 @Override getAttributeResourceValue(String namespace, String attribute, int defaultValue)650 public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { 651 String attr = getAttribute(namespace, attribute); 652 if (attr != null && attr.startsWith("@") && !AttributeResource.isNull(attr)) { 653 return getResourceId(attr, packageName, null); 654 } 655 return defaultValue; 656 } 657 658 @Override getAttributeIntValue(String namespace, String attribute, int defaultValue)659 public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { 660 return XmlUtils.convertValueToInt(this.getAttributeValue(namespace, attribute), defaultValue); 661 } 662 663 @Override getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)664 public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) { 665 int value = getAttributeIntValue(namespace, attribute, defaultValue); 666 if (value < 0) { 667 return defaultValue; 668 } 669 return value; 670 } 671 672 @Override getAttributeFloatValue(String namespace, String attribute, float defaultValue)673 public float getAttributeFloatValue(String namespace, String attribute, 674 float defaultValue) { 675 String attr = getAttribute(namespace, attribute); 676 if (attr == null) { 677 return defaultValue; 678 } 679 try { 680 return Float.parseFloat(attr); 681 } catch (NumberFormatException ex) { 682 return defaultValue; 683 } 684 } 685 686 @Override getAttributeListValue( int idx, String[] options, int defaultValue)687 public int getAttributeListValue( 688 int idx, String[] options, int defaultValue) { 689 try { 690 String value = getAttributeValue(idx); 691 List<String> optList = Arrays.asList(options); 692 int index = optList.indexOf(value); 693 if (index == -1) { 694 return defaultValue; 695 } 696 return index; 697 } catch (IndexOutOfBoundsException ex) { 698 return defaultValue; 699 } 700 } 701 702 @Override getAttributeBooleanValue( int idx, boolean defaultValue)703 public boolean getAttributeBooleanValue( 704 int idx, boolean defaultValue) { 705 try { 706 return Boolean.parseBoolean(getAttributeValue(idx)); 707 } catch (IndexOutOfBoundsException ex) { 708 return defaultValue; 709 } 710 } 711 712 @Override getAttributeResourceValue(int idx, int defaultValue)713 public int getAttributeResourceValue(int idx, int defaultValue) { 714 String attributeValue = getAttributeValue(idx); 715 if (attributeValue != null && attributeValue.startsWith("@")) { 716 int resourceId = getResourceId(attributeValue.substring(1), packageName, null); 717 if (resourceId != 0) { 718 return resourceId; 719 } 720 } 721 return defaultValue; 722 } 723 724 @Override getAttributeIntValue(int idx, int defaultValue)725 public int getAttributeIntValue(int idx, int defaultValue) { 726 try { 727 return Integer.parseInt(getAttributeValue(idx)); 728 } catch (NumberFormatException ex) { 729 return defaultValue; 730 } catch (IndexOutOfBoundsException ex) { 731 return defaultValue; 732 } 733 } 734 735 @Override getAttributeUnsignedIntValue(int idx, int defaultValue)736 public int getAttributeUnsignedIntValue(int idx, int defaultValue) { 737 int value = getAttributeIntValue(idx, defaultValue); 738 if (value < 0) { 739 return defaultValue; 740 } 741 return value; 742 } 743 744 @Override getAttributeFloatValue(int idx, float defaultValue)745 public float getAttributeFloatValue(int idx, float defaultValue) { 746 try { 747 return Float.parseFloat(getAttributeValue(idx)); 748 } catch (NumberFormatException ex) { 749 return defaultValue; 750 } catch (IndexOutOfBoundsException ex) { 751 return defaultValue; 752 } 753 } 754 755 @Override getIdAttribute()756 public String getIdAttribute() { 757 return getAttribute(null, "id"); 758 } 759 760 @Override getClassAttribute()761 public String getClassAttribute() { 762 return getAttribute(null, "class"); 763 } 764 765 @Override getIdAttributeResourceValue(int defaultValue)766 public int getIdAttributeResourceValue(int defaultValue) { 767 return getAttributeResourceValue(null, "id", defaultValue); 768 } 769 770 @Override getStyleAttribute()771 public int getStyleAttribute() { 772 String attr = getAttribute(null, "style"); 773 if (attr == null || 774 (!AttributeResource.isResourceReference(attr) && !AttributeResource.isStyleReference(attr))) { 775 return 0; 776 } 777 778 int style = getResourceId(attr, packageName, "style"); 779 if (style == 0) { 780 // try again with underscores... 781 style = getResourceId(attr.replace('.', '_'), packageName, "style"); 782 } 783 return style; 784 } 785 786 @Override close()787 public void close() { 788 // Nothing to do 789 } 790 791 @Override finalize()792 protected void finalize() throws Throwable { 793 close(); 794 } 795 getResourceId(String possiblyQualifiedResourceName, String defaultPackageName, String defaultType)796 private int getResourceId(String possiblyQualifiedResourceName, String defaultPackageName, String defaultType) { 797 798 if (AttributeResource.isNull(possiblyQualifiedResourceName)) return 0; 799 800 if (AttributeResource.isStyleReference(possiblyQualifiedResourceName)) { 801 ResName styleReference = AttributeResource.getStyleReference(possiblyQualifiedResourceName, defaultPackageName, "attr"); 802 Integer resourceId = resourceTable.getResourceId(styleReference); 803 if (resourceId == null) { 804 throw new Resources.NotFoundException(styleReference.getFullyQualifiedName()); 805 } 806 return resourceId; 807 } 808 809 if (AttributeResource.isResourceReference(possiblyQualifiedResourceName)) { 810 ResName resourceReference = AttributeResource.getResourceReference(possiblyQualifiedResourceName, defaultPackageName, defaultType); 811 Integer resourceId = resourceTable.getResourceId(resourceReference); 812 if (resourceId == null) { 813 throw new Resources.NotFoundException(resourceReference.getFullyQualifiedName()); 814 } 815 return resourceId; 816 } 817 possiblyQualifiedResourceName = removeLeadingSpecialCharsIfAny(possiblyQualifiedResourceName); 818 ResName resName = ResName.qualifyResName(possiblyQualifiedResourceName, defaultPackageName, defaultType); 819 Integer resourceId = resourceTable.getResourceId(resName); 820 return resourceId == null ? 0 : resourceId; 821 } 822 removeLeadingSpecialCharsIfAny(String name)823 private static String removeLeadingSpecialCharsIfAny(String name){ 824 if (name.startsWith("@+")) { 825 return name.substring(2); 826 } 827 if (name.startsWith("@")) { 828 return name.substring(1); 829 } 830 return name; 831 } 832 833 /** 834 * Tell is a given feature is supported by android. 835 * 836 * @param name Feature name. 837 * @return True if the feature is supported. 838 */ isAndroidSupportedFeature(String name)839 private static boolean isAndroidSupportedFeature(String name) { 840 if (name == null) { 841 return false; 842 } 843 for (String feature : AVAILABLE_FEATURES) { 844 if (feature.equals(name)) { 845 return true; 846 } 847 } 848 return false; 849 } 850 } 851