FileDocCategorySizeDatePackage
AttrsXmlParser.javaAPI DocAndroid 1.5 API21141Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.common.resources

AttrsXmlParser

public final class AttrsXmlParser extends Object
Parser for attributes description files.

Fields Summary
private Document
mDocument
private String
mOsAttrsXmlPath
private HashMap
mAttributeMap
private HashMap
mStyleMap
Map of all attribute names for a given element
private Map
mEnumFlagValues
Map of all (constant, value) pairs for attributes of format enum or flag. E.g. for attribute name=gravity, this tells us there's an enum/flag called "center" with value 0x11.
Constructors Summary
public AttrsXmlParser(String osAttrsXmlPath)
Creates a new {@link AttrsXmlParser}, set to load things from the given XML file. Nothing has been parsed yet. Callers should call {@link #preload()} next.

        this(osAttrsXmlPath, null /* inheritableAttributes */);
    
public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes)
Creates a new {@link AttrsXmlParser} set to load things from the given XML file. If inheritableAttributes is non-null, it must point to a preloaded {@link AttrsXmlParser} which attributes will be used for this one. Since already defined attributes are not modifiable, they are thus "inherited".

        mOsAttrsXmlPath = osAttrsXmlPath;

        // styles are not inheritable.
        mStyleMap = new HashMap<String, DeclareStyleableInfo>();

        if (inheritableAttributes == null) {
            mAttributeMap = new HashMap<String, AttributeInfo>();
            mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
        } else {
            mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap);
            mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
                                                             inheritableAttributes.mEnumFlagValues);
        }
    
Methods Summary
public java.util.MapgetDeclareStyleableList()
Returns a list of all decleare-styleable found in the xml file.

        return Collections.unmodifiableMap(mStyleMap);
    
private org.w3c.dom.DocumentgetDocument()
Creates an XML document from the attrs.xml OS path. May return null if the file doesn't exist or cannot be parsed.

        if (mDocument == null) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setIgnoringComments(false);
            try {
                DocumentBuilder builder = factory.newDocumentBuilder();
                mDocument = builder.parse(new File(mOsAttrsXmlPath));
            } catch (ParserConfigurationException e) {
                AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
                        mOsAttrsXmlPath);
            } catch (SAXException e) {
                AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
                        mOsAttrsXmlPath);
            } catch (IOException e) {
                AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
                        mOsAttrsXmlPath);
            }
        }
        return mDocument;
    
public java.util.MapgetEnumFlagValues()
Returns a map of all enum and flag constants sorted by parent attribute name. The map is attribute_name => (constant_name => integer_value).

        return mEnumFlagValues;
    
public java.lang.StringgetOsAttrsXmlPath()

return
The OS path of the attrs.xml file parsed

        return mOsAttrsXmlPath;
    
public voidloadLayoutParamsAttributes(com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo info)
Loads all attributes for the layout data info based on the class name.

        if (getDocument() != null) {
            // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout".
            String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
                    info.getViewLayoutClass().getShortClassName(),
                    info.getShortClassName());
            xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$

            DeclareStyleableInfo style = mStyleMap.get(xmlName);
            if (style != null) {
                info.setAttributes(style.getAttributes());
            }
        }
    
public voidloadViewAttributes(ViewClassInfo info)
Loads all attributes & javadoc for the view class info based on the class name.

        if (getDocument() != null) {
            String xmlName = info.getShortClassName();
            DeclareStyleableInfo style = mStyleMap.get(xmlName);
            if (style != null) {
                info.setAttributes(style.getAttributes());
                info.setJavaDoc(style.getJavaDoc());
            }
        }
    
private com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfoparseAttr(org.w3c.dom.Node attrNode, org.w3c.dom.Node lastComment)
Parses an node and convert it into an {@link AttributeInfo} if it is valid.

        AttributeInfo info = null;
        Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$
        if (nameNode != null) {
            String name = nameNode.getNodeValue();
            if (name != null) {
                info = mAttributeMap.get(name);
                // If the attribute is unknown yet, parse it.
                // If the attribute is know but its format is unknown, parse it too.
                if (info == null || info.getFormats().length == 0) {
                    info = parseAttributeTypes(attrNode, name);
                    if (info != null) {
                        mAttributeMap.put(name, info);
                    }
                } else if (lastComment != null) {
                    info = new AttributeInfo(info);
                }
                if (info != null) {
                    if (lastComment != null) {
                        info.setJavaDoc(parseJavadoc(lastComment.getNodeValue()));
                        info.setDeprecatedDoc(parseDeprecatedDoc(lastComment.getNodeValue()));
                    }
                }
            }
        }
        return info;
    
private com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfoparseAttributeTypes(org.w3c.dom.Node attrNode, java.lang.String name)
Returns the {@link AttributeInfo} for a specific XML node. This gets the javadoc, the type, the name and the enum/flag values if any.

The XML node is expected to have the following attributes:

  • "name", which is mandatory. The node is skipped if this is missing.
  • "format".
The format may be one type or two types (e.g. "reference|color"). An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute, they are implicitely stated by the presence of sub-nodes or .

By design, nodes of the same name MUST have the same type. Attribute nodes are thus cached by name and reused as much as possible. When reusing a node, it is duplicated and its javadoc reassigned.

        TreeSet<AttributeInfo.Format> formats = new TreeSet<AttributeInfo.Format>();
        String[] enumValues = null;
        String[] flagValues = null;

        Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$
        if (attrFormat != null) {
            for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$
                try {
                    Format format = AttributeInfo.Format.valueOf(f.toUpperCase());
                    // enum and flags are handled differently right below
                    if (format != null &&
                            format != AttributeInfo.Format.ENUM &&
                            format != AttributeInfo.Format.FLAG) {
                        formats.add(format);
                    }
                } catch (IllegalArgumentException e) {
                    AdtPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
                            f, name, getOsAttrsXmlPath());
                }
            }
        }

        // does this <attr> have <enum> children?
        enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$
        if (enumValues != null) {
            formats.add(AttributeInfo.Format.ENUM);
        }

        // does this <attr> have <flag> children?
        flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$
        if (flagValues != null) {
            formats.add(AttributeInfo.Format.FLAG);
        }

        AttributeInfo info = new AttributeInfo(name,
                formats.toArray(new AttributeInfo.Format[formats.size()]));
        info.setEnumValues(enumValues);
        info.setFlagValues(flagValues);
        return info;
    
private DeclareStyleableInfoparseDeclaredStyleable(java.lang.String styleName, org.w3c.dom.Node declareStyleableNode)
Finds all the attributes for a particular style node, e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout".

param
styleName The name of the declare-styleable node
param
declareStyleableNode The declare-styleable node itself

        ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>();
        Node lastComment = null;
        for (Node node = declareStyleableNode.getFirstChild();
             node != null;
             node = node.getNextSibling()) {

            switch (node.getNodeType()) {
            case Node.COMMENT_NODE:
                lastComment = node;
                break;
            case Node.ELEMENT_NODE:
                if (node.getNodeName().equals("attr")) {                       //$NON-NLS-1$
                    AttributeInfo info = parseAttr(node, lastComment);
                    if (info != null) {
                        attrs.add(info);
                    }
                }
                lastComment = null;
                break;
            }
            
        }
        
        return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()]));
    
private java.lang.StringparseDeprecatedDoc(java.lang.String comment)
Parses the javadoc and extract the first @deprecated tag, if any. Returns null if there's no @deprecated tag. The deprecated tag can be of two forms: - {+@deprecated ...text till the next bracket } Note: there should be no space or + between { and @. I need one in this comment otherwise this method will be tagged as deprecated ;-) - @deprecated ...text till the next @tag or end of the comment. In both cases the comment can be multi-line.

        // Skip if we can't even find the tag in the comment.
        if (comment == null) {
            return null;
        }
        
        // sanitize & collapse whitespace
        comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$

        int pos = comment.indexOf("{@deprecated");
        if (pos >= 0) {
            comment = comment.substring(pos + 12 /* len of {@deprecated */);
            comment = comment.replaceFirst("^([^}]*).*", "$1");
        } else if ((pos = comment.indexOf("@deprecated")) >= 0) {
            comment = comment.substring(pos + 11 /* len of @deprecated */);
            comment = comment.replaceFirst("^(.*?)(?:@.*|$)", "$1");
        } else {
            return null;
        }
        
        return comment.trim();
    
private java.lang.String[]parseEnumFlagValues(org.w3c.dom.Node attrNode, java.lang.String filter, java.lang.String attrName)
Given an XML node that represents an node, this method searches if the node has any children nodes named "target" (e.g. "enum" or "flag"). Such nodes must have a "name" attribute.

If "attrNode" is null, look for any that has the given attrNode and the requested children nodes.

This method collects all the possible names of these children nodes and return them.

param
attrNode The XML node
param
filter The child node to look for, either "enum" or "flag".
param
attrName The value of the name attribute of
return
Null if there are no such children nodes, otherwise an array of length >= 1 of all the names of these children nodes.

        ArrayList<String> names = null;
        for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
                Node nameNode = child.getAttributes().getNamedItem("name");  //$NON-NLS-1$
                if (nameNode == null) {
                    AdtPlugin.log(IStatus.WARNING,
                            "Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
                            attrName, filter);
                } else {
                    if (names == null) {
                        names = new ArrayList<String>();
                    }
                    String name = nameNode.getNodeValue();
                    names.add(name);
                    
                    Node valueNode = child.getAttributes().getNamedItem("value");  //$NON-NLS-1$
                    if (valueNode == null) {
                        AdtPlugin.log(IStatus.WARNING,
                                "Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
                                attrName, filter, name);
                    } else {
                        String value = valueNode.getNodeValue();
                        try {
                            int i = value.startsWith("0x") ?
                                    Integer.parseInt(value.substring(2), 16 /* radix */) :
                                    Integer.parseInt(value);
                            
                            Map<String, Integer> map = mEnumFlagValues.get(attrName);
                            if (map == null) {
                                map = new HashMap<String, Integer>();
                                mEnumFlagValues.put(attrName, map);
                            }
                            map.put(name, Integer.valueOf(i));
                            
                        } catch(NumberFormatException e) {
                            AdtPlugin.log(e,
                                    "Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
                                    attrName, filter, name, value);
                        }
                    }
                }
            }
        }
        return names == null ? null : names.toArray(new String[names.size()]);
    
private java.lang.StringparseJavadoc(java.lang.String comment)
Parses the javadoc comment. Only keeps the first sentence.

This does not remove nor simplify links and references. Such a transformation is done later at "display" time in {@link DescriptorsUtils#formatTooltip(String)} and co.

        if (comment == null) {
            return null;
        }
        
        // sanitize & collapse whitespace
        comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$

        // Explicitly remove any @deprecated tags since they are handled separately.
        comment = comment.replaceAll("(?:\\{@deprecated[^}]*\\}|@deprecated[^@}]*)", "");

        // take everything up to the first dot that is followed by a space or the end of the line.
        // I love regexps :-). For the curious, the regexp is:
        // - start of line
        // - ignore whitespace
        // - group:
        //   - everything, not greedy
        //   - non-capturing group (?: )
        //      - end of string
        //      or
        //      - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" )
        //                            (<! non-capturing zero-width negative look-behind)
        //      - a dot
        //      - followed by a space (?= non-capturing zero-width positive look-ahead)
        // - anything else is ignored
        comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
        
        return comment;
    
private voidparseResources(org.w3c.dom.Node res)
Finds all the and nodes in the top node.

        Node lastComment = null;
        for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) {
            switch (node.getNodeType()) {
            case Node.COMMENT_NODE:
                lastComment = node;
                break;
            case Node.ELEMENT_NODE:
                if (node.getNodeName().equals("declare-styleable")) {          //$NON-NLS-1$
                    Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
                    if (nameNode != null) {
                        String name = nameNode.getNodeValue();
                        
                        Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$
                        String parents = parentNode == null ? null : parentNode.getNodeValue();
                        
                        if (name != null && !mStyleMap.containsKey(name)) {
                            DeclareStyleableInfo style = parseDeclaredStyleable(name, node);
                            if (parents != null) {
                                style.setParents(parents.split("[ ,|]"));  //$NON-NLS-1$
                            }
                            mStyleMap.put(name, style);
                            if (lastComment != null) {
                                style.setJavaDoc(parseJavadoc(lastComment.getNodeValue()));
                            }
                        }
                    }
                } else if (node.getNodeName().equals("attr")) {                //$NON-NLS-1$
                    parseAttr(node, lastComment);
                }
                lastComment = null;
                break;
            }
        }
    
public com.android.ide.eclipse.common.resources.AttrsXmlParserpreload()
Preloads the document, parsing all attributes and declared styles.

return
Self, for command chaining.

        Document doc = getDocument();

        if (doc == null) {
            AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
                    mOsAttrsXmlPath);
            return this;
        }

        Node res = doc.getFirstChild();
        while (res != null &&
                res.getNodeType() != Node.ELEMENT_NODE &&
                !res.getNodeName().equals("resources")) { //$NON-NLS-1$
            res = res.getNextSibling();
        }
        
        if (res == null) {
            AdtPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
                    mOsAttrsXmlPath);
            return this;
        }
        
        parseResources(res);
        return this;