Home | History | Annotate | Download | only in platform
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.ide.common.resources.platform;
     18 
     19 import static com.android.SdkConstants.DOT_LAYOUT_PARAMS;
     20 import static com.android.ide.eclipse.adt.AdtConstants.DOC_HIDE;
     21 
     22 import com.android.ide.common.api.IAttributeInfo.Format;
     23 import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo;
     24 import com.android.ide.eclipse.adt.AdtUtils;
     25 import com.android.utils.ILogger;
     26 import com.google.common.collect.Maps;
     27 
     28 import org.w3c.dom.Document;
     29 import org.w3c.dom.Node;
     30 import org.xml.sax.SAXException;
     31 
     32 import java.io.File;
     33 import java.io.IOException;
     34 import java.util.ArrayList;
     35 import java.util.Collections;
     36 import java.util.EnumSet;
     37 import java.util.HashMap;
     38 import java.util.Locale;
     39 import java.util.Map;
     40 import java.util.Map.Entry;
     41 
     42 import javax.xml.parsers.DocumentBuilder;
     43 import javax.xml.parsers.DocumentBuilderFactory;
     44 import javax.xml.parsers.ParserConfigurationException;
     45 
     46 /**
     47  * Parser for attributes description files.
     48  */
     49 public final class AttrsXmlParser {
     50 
     51     public static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest";  //$NON-NLS-1$
     52 
     53     private Document mDocument;
     54     private String mOsAttrsXmlPath;
     55 
     56     // all attributes that have the same name are supposed to have the same
     57     // parameters so we'll keep a cache of them to avoid processing them twice.
     58     private Map<String, AttributeInfo> mAttributeMap;
     59 
     60     /** Map of all attribute names for a given element */
     61     private final Map<String, DeclareStyleableInfo> mStyleMap =
     62         new HashMap<String, DeclareStyleableInfo>();
     63 
     64     /** Map from format name (lower case) to the uppercase version */
     65     private Map<String, Format> mFormatNames = new HashMap<String, Format>(10);
     66 
     67     /**
     68      * Map of all (constant, value) pairs for attributes of format enum or flag.
     69      * E.g. for attribute name=gravity, this tells us there's an enum/flag called "center"
     70      * with value 0x11.
     71      */
     72     private Map<String, Map<String, Integer>> mEnumFlagValues;
     73 
     74     /**
     75      * A logger object. Must not be null.
     76      */
     77     private final ILogger mLog;
     78 
     79     /**
     80      * Creates a new {@link AttrsXmlParser}, set to load things from the given
     81      * XML file. Nothing has been parsed yet. Callers should call {@link #preload()}
     82      * next.
     83      *
     84      * @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse.
     85      *              Must not be null. Should point to an existing valid XML document.
     86      * @param log A logger object. Must not be null.
     87      * @param expectedAttributeCount expected number of attributes in the file
     88      */
     89     public AttrsXmlParser(String osAttrsXmlPath, ILogger log, int expectedAttributeCount) {
     90         this(osAttrsXmlPath, null /* inheritableAttributes */, log, expectedAttributeCount);
     91     }
     92 
     93     /**
     94      * Returns the parsed map of attribute infos
     95      *
     96      * @return a map from string name to {@link AttributeInfo}
     97      */
     98     public Map<String, AttributeInfo> getAttributeMap() {
     99         return mAttributeMap;
    100     }
    101 
    102     /**
    103      * Creates a new {@link AttrsXmlParser} set to load things from the given
    104      * XML file.
    105      * <p/>
    106      * If inheritableAttributes is non-null, it must point to a preloaded
    107      * {@link AttrsXmlParser} which attributes will be used for this one. Since
    108      * already defined attributes are not modifiable, they are thus "inherited".
    109      *
    110      * @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse.
    111      *              Must not be null. Should point to an existing valid XML document.
    112      * @param inheritableAttributes An optional parser with attributes to inherit. Can be null.
    113      *              If not null, the parser must have had its {@link #preload()} method
    114      *              invoked prior to being used here.
    115      * @param log A logger object. Must not be null.
    116      * @param expectedAttributeCount expected number of attributes in the file
    117      */
    118     public AttrsXmlParser(
    119             String osAttrsXmlPath,
    120             AttrsXmlParser inheritableAttributes,
    121             ILogger log,
    122             int expectedAttributeCount) {
    123         mOsAttrsXmlPath = osAttrsXmlPath;
    124         mLog = log;
    125 
    126         assert osAttrsXmlPath != null;
    127         assert log != null;
    128 
    129         mAttributeMap = Maps.newHashMapWithExpectedSize(expectedAttributeCount);
    130         if (inheritableAttributes == null) {
    131             mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
    132         } else {
    133             mAttributeMap.putAll(inheritableAttributes.mAttributeMap);
    134             mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
    135                                                          inheritableAttributes.mEnumFlagValues);
    136         }
    137 
    138         // Pre-compute the set of format names such that we don't have to compute the uppercase
    139         // version of the same format string names again and again
    140         for (Format f : Format.values()) {
    141             mFormatNames.put(f.name().toLowerCase(Locale.US), f);
    142         }
    143     }
    144 
    145     /**
    146      * Returns the OS path of the attrs.xml file parsed.
    147      */
    148     public String getOsAttrsXmlPath() {
    149         return mOsAttrsXmlPath;
    150     }
    151 
    152     /**
    153      * Preloads the document, parsing all attributes and declared styles.
    154      *
    155      * @return Self, for command chaining.
    156      */
    157     public AttrsXmlParser preload() {
    158         Document doc = getDocument();
    159 
    160         if (doc == null) {
    161             mLog.warning("Failed to find %1$s", //$NON-NLS-1$
    162                     mOsAttrsXmlPath);
    163             return this;
    164         }
    165 
    166         Node res = doc.getFirstChild();
    167         while (res != null &&
    168                 res.getNodeType() != Node.ELEMENT_NODE &&
    169                 !res.getNodeName().equals("resources")) { //$NON-NLS-1$
    170             res = res.getNextSibling();
    171         }
    172 
    173         if (res == null) {
    174             mLog.warning("Failed to find a <resources> node in %1$s", //$NON-NLS-1$
    175                     mOsAttrsXmlPath);
    176             return this;
    177         }
    178 
    179         parseResources(res);
    180         return this;
    181     }
    182 
    183     /**
    184      * Loads all attributes & javadoc for the view class info based on the class name.
    185      */
    186     public void loadViewAttributes(ViewClassInfo info) {
    187         if (getDocument() != null) {
    188             String xmlName = info.getShortClassName();
    189             DeclareStyleableInfo style = mStyleMap.get(xmlName);
    190             if (style != null) {
    191                 String definedBy = info.getFullClassName();
    192                 AttributeInfo[] attributes = style.getAttributes();
    193                 for (AttributeInfo attribute : attributes) {
    194                     if (attribute.getDefinedBy() == null) {
    195                         attribute.setDefinedBy(definedBy);
    196                     }
    197                 }
    198                 info.setAttributes(attributes);
    199                 info.setJavaDoc(style.getJavaDoc());
    200             }
    201         }
    202     }
    203 
    204     /**
    205      * Loads all attributes for the layout data info based on the class name.
    206      */
    207     public void loadLayoutParamsAttributes(LayoutParamsInfo info) {
    208         if (getDocument() != null) {
    209             // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout".
    210             ViewClassInfo viewLayoutClass = info.getViewLayoutClass();
    211             String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
    212                     viewLayoutClass.getShortClassName(),
    213                     info.getShortClassName());
    214             xmlName = AdtUtils.stripSuffix(xmlName, "Params"); //$NON-NLS-1$
    215 
    216             DeclareStyleableInfo style = mStyleMap.get(xmlName);
    217             if (style != null) {
    218                 // For defined by, use the actual class name, e.g.
    219                 //   android.widget.LinearLayout.LayoutParams
    220                 String definedBy = viewLayoutClass.getFullClassName() + DOT_LAYOUT_PARAMS;
    221                 AttributeInfo[] attributes = style.getAttributes();
    222                 for (AttributeInfo attribute : attributes) {
    223                     if (attribute.getDefinedBy() == null) {
    224                         attribute.setDefinedBy(definedBy);
    225                     }
    226                 }
    227                 info.setAttributes(attributes);
    228             }
    229         }
    230     }
    231 
    232     /**
    233      * Returns a list of all <code>declare-styleable</code> found in the XML file.
    234      */
    235     public Map<String, DeclareStyleableInfo> getDeclareStyleableList() {
    236         return Collections.unmodifiableMap(mStyleMap);
    237     }
    238 
    239     /**
    240      * Returns a map of all enum and flag constants sorted by parent attribute name.
    241      * The map is attribute_name => (constant_name => integer_value).
    242      */
    243     public Map<String, Map<String, Integer>> getEnumFlagValues() {
    244         return mEnumFlagValues;
    245     }
    246 
    247     //-------------------------
    248 
    249     /**
    250      * Creates an XML document from the attrs.xml OS path.
    251      * May return null if the file doesn't exist or cannot be parsed.
    252      */
    253     private Document getDocument() {
    254         if (mDocument == null) {
    255             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    256             factory.setIgnoringComments(false);
    257             try {
    258                 DocumentBuilder builder = factory.newDocumentBuilder();
    259                 mDocument = builder.parse(new File(mOsAttrsXmlPath));
    260             } catch (ParserConfigurationException e) {
    261                 mLog.error(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
    262                         mOsAttrsXmlPath);
    263             } catch (SAXException e) {
    264                 mLog.error(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
    265                         mOsAttrsXmlPath);
    266             } catch (IOException e) {
    267                 mLog.error(e, "Failed to read XML document %1$s", //$NON-NLS-1$
    268                         mOsAttrsXmlPath);
    269             }
    270         }
    271         return mDocument;
    272     }
    273 
    274     /**
    275      * Finds all the &lt;declare-styleable&gt; and &lt;attr&gt; nodes
    276      * in the top &lt;resources&gt; node.
    277      */
    278     private void parseResources(Node res) {
    279 
    280         Map<String, String> unknownParents = new HashMap<String, String>();
    281 
    282         Node lastComment = null;
    283         for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) {
    284             switch (node.getNodeType()) {
    285             case Node.COMMENT_NODE:
    286                 lastComment = node;
    287                 break;
    288             case Node.ELEMENT_NODE:
    289                 if (node.getNodeName().equals("declare-styleable")) {          //$NON-NLS-1$
    290                     Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
    291                     if (nameNode != null) {
    292                         String name = nameNode.getNodeValue();
    293 
    294                         Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$
    295                         String parents = parentNode == null ? null : parentNode.getNodeValue();
    296 
    297                         if (name != null && !mStyleMap.containsKey(name)) {
    298                             DeclareStyleableInfo style = parseDeclaredStyleable(name, node);
    299                             if (parents != null) {
    300                                 String[] parentsArray =
    301                                     parseStyleableParents(parents, mStyleMap, unknownParents);
    302                                 style.setParents(parentsArray);
    303                             }
    304                             mStyleMap.put(name, style);
    305                             unknownParents.remove(name);
    306                             if (lastComment != null) {
    307                                 String nodeValue = lastComment.getNodeValue();
    308                                 if (nodeValue.contains(DOC_HIDE)) {
    309                                     mStyleMap.remove(name);
    310                                 } else {
    311                                     style.setJavaDoc(parseJavadoc(nodeValue));
    312                                 }
    313                             }
    314                         }
    315                     }
    316                 } else if (node.getNodeName().equals("attr")) {                //$NON-NLS-1$
    317                     parseAttr(node, lastComment);
    318                 }
    319                 lastComment = null;
    320                 break;
    321             }
    322         }
    323 
    324         // If we have any unknown parent, re-create synthetic styleable for them.
    325         for (Entry<String, String> entry : unknownParents.entrySet()) {
    326             String name = entry.getKey();
    327             String parent = entry.getValue();
    328 
    329             DeclareStyleableInfo style = new DeclareStyleableInfo(name, (AttributeInfo[])null);
    330             if (parent != null) {
    331                 style.setParents(new String[] { parent });
    332             }
    333             mStyleMap.put(name, style);
    334 
    335             // Simplify parents names. See SDK Bug 3125910.
    336             // Implementation detail: that since we want to delete and add to the map,
    337             // we can't just use an iterator.
    338             for (String key : new ArrayList<String>(mStyleMap.keySet())) {
    339                 if (key.startsWith(name) && !key.equals(name)) {
    340                     // We found a child which name starts with the full name of the
    341                     // parent. Simplify the children name.
    342                     String newName = ANDROID_MANIFEST_STYLEABLE + key.substring(name.length());
    343 
    344                     DeclareStyleableInfo newStyle =
    345                         new DeclareStyleableInfo(newName, mStyleMap.get(key));
    346                     mStyleMap.remove(key);
    347                     mStyleMap.put(newName, newStyle);
    348                 }
    349             }
    350         }
    351     }
    352 
    353     /**
    354      * Parses the "parents" attribute from a &lt;declare-styleable&gt;.
    355      * <p/>
    356      * The syntax is the following:
    357      * <pre>
    358      *   parent[.parent]* [[space|,] parent[.parent]* ]
    359      * </pre>
    360      * <p/>
    361      * In English: </br>
    362      * - There can be one or more parents, separated by whitespace or commas. </br>
    363      * - Whitespace is ignored and trimmed. </br>
    364      * - A parent name is actually composed of one or more identifiers joined by a dot.
    365      * <p/>
    366      * Styleables do not usually need to declare their parent chain (e.g. the grand-parents
    367      * of a parent.) Parent names are unique, so in most cases a styleable will only declare
    368      * its immediate parent.
    369      * <p/>
    370      * However it is possible for a styleable's parent to not exist, e.g. if you have a
    371      * styleable "A" that is the root and then styleable "C" declares its parent to be "A.B".
    372      * In this case we record "B" as the parent, even though it is unknown and will never be
    373      * known. Any parent that is currently not in the knownParent map is thus added to the
    374      * unknownParent set. The caller will remove the name from the unknownParent set when it
    375      * sees a declaration for it.
    376      *
    377      * @param parents The parents string to parse. Must not be null or empty.
    378      * @param knownParents The map of all declared styles known so far.
    379      * @param unknownParents A map of all unknown parents collected here.
    380      * @return The array of terminal parent names parsed from the parents string.
    381      */
    382     private String[] parseStyleableParents(String parents,
    383             Map<String, DeclareStyleableInfo> knownParents,
    384             Map<String, String> unknownParents) {
    385 
    386         ArrayList<String> result = new ArrayList<String>();
    387 
    388         for (String parent : parents.split("[ \t\n\r\f,|]")) {          //$NON-NLS-1$
    389             parent = parent.trim();
    390             if (parent.length() == 0) {
    391                 continue;
    392             }
    393             if (parent.indexOf('.') >= 0) {
    394                 // This is a grand-parent/parent chain. Make sure we know about the
    395                 // parents and only record the terminal one.
    396                 String last = null;
    397                 for (String name : parent.split("\\.")) {          //$NON-NLS-1$
    398                     if (name.length() > 0) {
    399                         if (!knownParents.containsKey(name)) {
    400                             // Record this unknown parent and its grand parent.
    401                             unknownParents.put(name, last);
    402                         }
    403                         last = name;
    404                     }
    405                 }
    406                 parent = last;
    407             }
    408 
    409             result.add(parent);
    410         }
    411 
    412         return result.toArray(new String[result.size()]);
    413     }
    414 
    415     /**
    416      * Parses an &lt;attr&gt; node and convert it into an {@link AttributeInfo} if it is valid.
    417      */
    418     private AttributeInfo parseAttr(Node attrNode, Node lastComment) {
    419         AttributeInfo info = null;
    420         Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$
    421         if (nameNode != null) {
    422             String name = nameNode.getNodeValue();
    423             if (name != null) {
    424                 info = mAttributeMap.get(name);
    425                 // If the attribute is unknown yet, parse it.
    426                 // If the attribute is know but its format is unknown, parse it too.
    427                 if (info == null || info.getFormats().size() == 0) {
    428                     info = parseAttributeTypes(attrNode, name);
    429                     if (info != null) {
    430                         mAttributeMap.put(name, info);
    431                     }
    432                 } else if (lastComment != null) {
    433                     info = new AttributeInfo(info);
    434                 }
    435                 if (info != null) {
    436                     if (lastComment != null) {
    437                         String nodeValue = lastComment.getNodeValue();
    438                         if (nodeValue.contains(DOC_HIDE)) {
    439                             return null;
    440                         }
    441                         info.setJavaDoc(parseJavadoc(nodeValue));
    442                         info.setDeprecatedDoc(parseDeprecatedDoc(nodeValue));
    443                     }
    444                 }
    445             }
    446         }
    447         return info;
    448     }
    449 
    450     /**
    451      * Finds all the attributes for a particular style node,
    452      * e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout".
    453      *
    454      * @param styleName The name of the declare-styleable node
    455      * @param declareStyleableNode The declare-styleable node itself
    456      */
    457     private DeclareStyleableInfo parseDeclaredStyleable(String styleName,
    458             Node declareStyleableNode) {
    459         ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>();
    460         Node lastComment = null;
    461         for (Node node = declareStyleableNode.getFirstChild();
    462              node != null;
    463              node = node.getNextSibling()) {
    464 
    465             switch (node.getNodeType()) {
    466             case Node.COMMENT_NODE:
    467                 lastComment = node;
    468                 break;
    469             case Node.ELEMENT_NODE:
    470                 if (node.getNodeName().equals("attr")) {                       //$NON-NLS-1$
    471                     AttributeInfo info = parseAttr(node, lastComment);
    472                     if (info != null) {
    473                         attrs.add(info);
    474                     }
    475                 }
    476                 lastComment = null;
    477                 break;
    478             }
    479 
    480         }
    481 
    482         return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()]));
    483     }
    484 
    485     /**
    486      * Returns the {@link AttributeInfo} for a specific <attr> XML node.
    487      * This gets the javadoc, the type, the name and the enum/flag values if any.
    488      * <p/>
    489      * The XML node is expected to have the following attributes:
    490      * <ul>
    491      * <li>"name", which is mandatory. The node is skipped if this is missing.</li>
    492      * <li>"format".</li>
    493      * </ul>
    494      * The format may be one type or two types (e.g. "reference|color").
    495      * An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute,
    496      * they are implicitly stated by the presence of sub-nodes <enum> or <flag>.
    497      * <p/>
    498      * By design, attr nodes of the same name MUST have the same type.
    499      * Attribute nodes are thus cached by name and reused as much as possible.
    500      * When reusing a node, it is duplicated and its javadoc reassigned.
    501      */
    502     private AttributeInfo parseAttributeTypes(Node attrNode, String name) {
    503         EnumSet<Format> formats = null;
    504         String[] enumValues = null;
    505         String[] flagValues = null;
    506 
    507         Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$
    508         if (attrFormat != null) {
    509             for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$
    510                 Format format = mFormatNames.get(f);
    511                 if (format == null) {
    512                     mLog.info(
    513                         "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
    514                         f, name, getOsAttrsXmlPath());
    515                 } else if (format != AttributeInfo.Format.ENUM &&
    516                         format != AttributeInfo.Format.FLAG) {
    517                     if (formats == null) {
    518                         formats = format.asSet();
    519                     } else {
    520                         if (formats.size() == 1) {
    521                             formats = EnumSet.copyOf(formats);
    522                         }
    523                         formats.add(format);
    524                     }
    525                 }
    526             }
    527         }
    528 
    529         // does this <attr> have <enum> children?
    530         enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$
    531         if (enumValues != null) {
    532             if (formats == null) {
    533                 formats = Format.ENUM_SET;
    534             } else {
    535                 if (formats.size() == 1) {
    536                     formats = EnumSet.copyOf(formats);
    537                 }
    538                 formats.add(Format.ENUM);
    539             }
    540         }
    541 
    542         // does this <attr> have <flag> children?
    543         flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$
    544         if (flagValues != null) {
    545             if (formats == null) {
    546                 formats = Format.FLAG_SET;
    547             } else {
    548                 if (formats.size() == 1) {
    549                     formats = EnumSet.copyOf(formats);
    550                 }
    551                 formats.add(Format.FLAG);
    552             }
    553         }
    554 
    555         if (formats == null) {
    556             formats = Format.NONE;
    557         }
    558 
    559         AttributeInfo info = new AttributeInfo(name, formats);
    560         info.setEnumValues(enumValues);
    561         info.setFlagValues(flagValues);
    562         return info;
    563     }
    564 
    565     /**
    566      * Given an XML node that represents an <attr> node, this method searches
    567      * if the node has any children nodes named "target" (e.g. "enum" or "flag").
    568      * Such nodes must have a "name" attribute.
    569      * <p/>
    570      * If "attrNode" is null, look for any <attr> that has the given attrNode
    571      * and the requested children nodes.
    572      * <p/>
    573      * This method collects all the possible names of these children nodes and
    574      * return them.
    575      *
    576      * @param attrNode The <attr> XML node
    577      * @param filter The child node to look for, either "enum" or "flag".
    578      * @param attrName The value of the name attribute of <attr>
    579      *
    580      * @return Null if there are no such children nodes, otherwise an array of length >= 1
    581      *         of all the names of these children nodes.
    582      */
    583     private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) {
    584         ArrayList<String> names = null;
    585         for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) {
    586             if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
    587                 Node nameNode = child.getAttributes().getNamedItem("name");  //$NON-NLS-1$
    588                 if (nameNode == null) {
    589                     mLog.warning(
    590                             "Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
    591                             attrName, filter);
    592                 } else {
    593                     if (names == null) {
    594                         names = new ArrayList<String>();
    595                     }
    596                     String name = nameNode.getNodeValue();
    597                     names.add(name);
    598 
    599                     Node valueNode = child.getAttributes().getNamedItem("value");  //$NON-NLS-1$
    600                     if (valueNode == null) {
    601                         mLog.warning(
    602                             "Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
    603                             attrName, filter, name);
    604                     } else {
    605                         String value = valueNode.getNodeValue();
    606                         try {
    607                             // Integer.decode cannot handle "ffffffff", see JDK issue 6624867
    608                             int i = (int) (long) Long.decode(value);
    609 
    610                             Map<String, Integer> map = mEnumFlagValues.get(attrName);
    611                             if (map == null) {
    612                                 map = new HashMap<String, Integer>();
    613                                 mEnumFlagValues.put(attrName, map);
    614                             }
    615                             map.put(name, Integer.valueOf(i));
    616 
    617                         } catch(NumberFormatException e) {
    618                             mLog.error(e,
    619                                     "Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
    620                                     attrName, filter, name, value);
    621                         }
    622                     }
    623                 }
    624             }
    625         }
    626         return names == null ? null : names.toArray(new String[names.size()]);
    627     }
    628 
    629     /**
    630      * Parses the javadoc comment.
    631      * Only keeps the first sentence.
    632      * <p/>
    633      * This does not remove nor simplify links and references.
    634      */
    635     private String parseJavadoc(String comment) {
    636         if (comment == null) {
    637             return null;
    638         }
    639 
    640         // sanitize & collapse whitespace
    641         comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
    642 
    643         // Explicitly remove any @deprecated tags since they are handled separately.
    644         comment = comment.replaceAll("(?:\\{@deprecated[^}]*\\}|@deprecated[^@}]*)", "");
    645 
    646         // take everything up to the first dot that is followed by a space or the end of the line.
    647         // I love regexps :-). For the curious, the regexp is:
    648         // - start of line
    649         // - ignore whitespace
    650         // - group:
    651         //   - everything, not greedy
    652         //   - non-capturing group (?: )
    653         //      - end of string
    654         //      or
    655         //      - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" )
    656         //                            (<! non-capturing zero-width negative look-behind)
    657         //      - a dot
    658         //      - followed by a space (?= non-capturing zero-width positive look-ahead)
    659         // - anything else is ignored
    660         comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
    661 
    662         return comment;
    663     }
    664 
    665 
    666     /**
    667      * Parses the javadoc and extract the first @deprecated tag, if any.
    668      * Returns null if there's no @deprecated tag.
    669      * The deprecated tag can be of two forms:
    670      * - {+@deprecated ...text till the next bracket }
    671      *   Note: there should be no space or + between { and @. I need one in this comment otherwise
    672      *   this method will be tagged as deprecated ;-)
    673      * - @deprecated ...text till the next @tag or end of the comment.
    674      * In both cases the comment can be multi-line.
    675      */
    676     private String parseDeprecatedDoc(String comment) {
    677         // Skip if we can't even find the tag in the comment.
    678         if (comment == null) {
    679             return null;
    680         }
    681 
    682         // sanitize & collapse whitespace
    683         comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
    684 
    685         int pos = comment.indexOf("{@deprecated");
    686         if (pos >= 0) {
    687             comment = comment.substring(pos + 12 /* len of {@deprecated */);
    688             comment = comment.replaceFirst("^([^}]*).*", "$1");
    689         } else if ((pos = comment.indexOf("@deprecated")) >= 0) {
    690             comment = comment.substring(pos + 11 /* len of @deprecated */);
    691             comment = comment.replaceFirst("^(.*?)(?:@.*|$)", "$1");
    692         } else {
    693             return null;
    694         }
    695 
    696         return comment.trim();
    697     }
    698 }
    699