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