diff options
6 files changed, 163 insertions, 21 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java index 423d967..1de2906 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java @@ -53,7 +53,7 @@ import java.util.Map.Entry; public final class AndroidManifestDescriptors implements IDescriptorProvider { private static final String MANIFEST_NODE_NAME = "manifest"; //$NON-NLS-1$ - private static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$ + public static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$ // Public attributes names, attributes descriptors and elements descriptors @@ -345,7 +345,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration. * <p/> * This first creates all the attributes for the given ElementDescriptor. - * It then finds all children of the descriptor, inflate them recursively and set them + * It then finds all children of the descriptor, inflates them recursively and sets them * as child to this ElementDescriptor. * * @param styleMap The input styleable map for manifest elements & attributes. @@ -552,7 +552,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { if (ANDROID_MANIFEST_STYLEABLE.equals(name)) { sb.append(MANIFEST_NODE_NAME); } else { - name = name.replace(ANDROID_MANIFEST_STYLEABLE, ""); //$NON-NLS-1$ + name = name.replace(ANDROID_MANIFEST_STYLEABLE, ""); //$NON-NLS-1$ boolean first_char = true; for (char c : name.toCharArray()) { if (c >= 'A' && c <= 'Z') { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParser.java index c3cd89f..9b6675e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParser.java @@ -19,9 +19,8 @@ package com.android.ide.eclipse.adt.internal.resources; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; import com.android.ide.eclipse.adt.internal.resources.ViewClassInfo.LayoutParamsInfo; -import com.android.sdklib.annotations.VisibleForTesting; -import com.android.sdklib.annotations.VisibleForTesting.Visibility; import org.eclipse.core.runtime.IStatus; import org.w3c.dom.Document; @@ -33,8 +32,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.TreeSet; +import java.util.Map.Entry; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -52,7 +53,8 @@ public final class AttrsXmlParser { private HashMap<String, AttributeInfo> mAttributeMap; /** Map of all attribute names for a given element */ - private HashMap<String, DeclareStyleableInfo> mStyleMap; + private final Map<String, DeclareStyleableInfo> mStyleMap = + new HashMap<String, DeclareStyleableInfo>(); /** 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" @@ -79,9 +81,6 @@ public final class AttrsXmlParser { public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes) { 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>>(); @@ -208,6 +207,9 @@ public final class AttrsXmlParser { * Finds all the <declare-styleable> and <attr> nodes in the top <resources> node. */ private void parseResources(Node res) { + + Map<String, String> unknownParents = new HashMap<String, String>(); + Node lastComment = null; for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { @@ -226,9 +228,12 @@ public final class AttrsXmlParser { if (name != null && !mStyleMap.containsKey(name)) { DeclareStyleableInfo style = parseDeclaredStyleable(name, node); if (parents != null) { - style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$ + String[] parentsArray = + parseStyleableParents(parents, mStyleMap, unknownParents); + style.setParents(parentsArray); //$NON-NLS-1$ } mStyleMap.put(name, style); + unknownParents.remove(name); if (lastComment != null) { style.setJavaDoc(parseJavadoc(lastComment.getNodeValue())); } @@ -241,10 +246,101 @@ public final class AttrsXmlParser { break; } } + + // If we have any unknown parent, re-create synthetic styleable for them. + for (Entry<String, String> entry : unknownParents.entrySet()) { + String name = entry.getKey(); + String parent = entry.getValue(); + + DeclareStyleableInfo style = new DeclareStyleableInfo(name, (AttributeInfo[])null); + if (parent != null) { + style.setParents(new String[] { parent }); + } + mStyleMap.put(name, style); + + // Simplify parents names. See SDK Bug 3125910. + // Implementation detail: that since we want to delete and add to the map, + // we can't just use an iterator. + for (String key : new ArrayList<String>(mStyleMap.keySet())) { + if (key.startsWith(name) && !key.equals(name)) { + // We found a child which name starts with the full name of the + // parent. Simplify the children name. + String newName = AndroidManifestDescriptors.ANDROID_MANIFEST_STYLEABLE + + key.substring(name.length()); + + DeclareStyleableInfo newStyle = + new DeclareStyleableInfo(newName, mStyleMap.get(key)); + mStyleMap.remove(key); + mStyleMap.put(newName, newStyle); + } + } + } + } + + /** + * Parses the "parents" attribute from a <declare-styleable>. + * <p/> + * The syntax is the following: + * <pre> + * parent[.parent]* [[space|,] parent[.parent]* ] + * </pre> + * <p/> + * In English: </br> + * - There can be one or more parents, separated by whitespace or commas. </br> + * - Whitespace is ignored and trimmed. </br> + * - A parent name is actually composed of one or more identifiers joined by a dot. + * <p/> + * Styleables do not usually need to declare their parent chain (e.g. the grand-parents + * of a parent.) Parent names are unique, so in most cases a styleable will only declare + * its immediate parent. + * <p/> + * However it is possible for a styleable's parent to not exist, e.g. if you have a + * styleable "A" that is the root and then styleable "C" declares its parent to be "A.B". + * In this case we record "B" as the parent, even though it is unknown and will never be + * known. Any parent that is currently not in the knownParent map is thus added to the + * unknownParent set. The caller will remove the name from the unknownParent set when it + * sees a declaration for it. + * + * @param parents The parents string to parse. Must not be null or empty. + * @param knownParents The map of all declared styles known so far. + * @param unknownParents A map of all unknown parents collected here. + * @return The array of terminal parent names parsed from the parents string. + */ + private String[] parseStyleableParents(String parents, + Map<String, DeclareStyleableInfo> knownParents, + Map<String, String> unknownParents) { + + ArrayList<String> result = new ArrayList<String>(); + + for (String parent : parents.split("[ \t\n\r\f,|]")) { //$NON-NLS-1$ + parent = parent.trim(); + if (parent.length() == 0) { + continue; + } + if (parent.indexOf('.') >= 0) { + // This is a grand-parent/parent chain. Make sure we know about the + // parents and only record the terminal one. + String last = null; + for (String name : parent.split("\\.")) { //$NON-NLS-1$ + if (name.length() > 0) { + if (!knownParents.containsKey(name)) { + // Record this unknown parent and its grand parent. + unknownParents.put(name, last); + } + last = name; + } + } + parent = last; + } + + result.add(parent); + } + + return result.toArray(new String[result.size()]); } /** - * Parses an <attr> node and convert it into an {@link AttributeInfo} if it is valid. + * Parses an <attr> node and convert it into an {@link AttributeInfo} if it is valid. */ private AttributeInfo parseAttr(Node attrNode, Node lastComment) { AttributeInfo info = null; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/DeclareStyleableInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/DeclareStyleableInfo.java index a412163..5013cf0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/DeclareStyleableInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/DeclareStyleableInfo.java @@ -44,6 +44,34 @@ public class DeclareStyleableInfo { mAttributes = attributes == null ? new AttributeInfo[0] : attributes; } + /** + * Creates a new {@link DeclareStyleableInfo} that has the same attributes + * as an existing one and only differs by name. + * + * @param styleName The name of the style. Should not be empty nor null. + * @param existing The existing {@link DeclareStyleableInfo} to mirror. + */ + public DeclareStyleableInfo(String styleName, DeclareStyleableInfo existing) { + mStyleName = styleName; + + mJavaDoc = existing.getJavaDoc(); + + String[] parents = existing.getParents(); + if (parents != null) { + mParents = new String[parents.length]; + System.arraycopy(parents, 0, mParents, 0, parents.length); + } + + AttributeInfo[] attrs = existing.getAttributes(); + if (attrs == null || attrs.length == 0) { + mAttributes = new AttributeInfo[0]; + } else { + mAttributes = new AttributeInfo[attrs.length]; + System.arraycopy(attrs, 0, mAttributes, 0, attrs.length); + } + } + + /** Returns style name */ public String getStyleName() { return mStyleName; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserManifestTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserManifestTest.java index f1947a3..6ee9223 100755 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserManifestTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserManifestTest.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.adt.internal.resources; -import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.eclipse.tests.AdtTestData; import java.util.Arrays; @@ -48,7 +47,7 @@ public class AttrsXmlParserManifestTest extends TestCase { assertEquals(mFilePath, mParser.getOsAttrsXmlPath()); } - public final void testPreload() throws Exception { + private Map<String, DeclareStyleableInfo> preloadAndGetStyleables() { assertSame(mParser, mParser.preload()); Map<String, DeclareStyleableInfo> styleableList = mParser.getDeclareStyleableList(); @@ -56,19 +55,40 @@ public class AttrsXmlParserManifestTest extends TestCase { if (!(styleableList instanceof TreeMap<?, ?>)) { styleableList = new TreeMap<String, DeclareStyleableInfo>(styleableList); } + return styleableList; + } + + public final void testPreload() throws Exception { + Map<String, DeclareStyleableInfo> styleableList = preloadAndGetStyleables(); assertEquals( "[AndroidManifest, " + "AndroidManifestActivityAlias, " + "AndroidManifestApplication, " + "AndroidManifestNewElement, " + + "AndroidManifestNewParent, " + "AndroidManifestPermission" + - // TODO This is for the next CL: - //"AndroidManifestImplicitParentElement" + - //"AndroidManifestNewElement" + "]", Arrays.toString(styleableList.keySet().toArray())); + } + + /** + * Tests that AndroidManifestNewParentNewElement got renamed to AndroidManifestNewElement + * and a parent named AndroidManifestNewParent was automatically created. + */ + public final void testNewParent() throws Exception { + Map<String, DeclareStyleableInfo> styleableList = preloadAndGetStyleables(); + + DeclareStyleableInfo newElement = styleableList.get("AndroidManifestNewElement"); + assertNotNull(newElement); + assertEquals("AndroidManifestNewElement", newElement.getStyleName()); + assertEquals("[AndroidManifestNewParent]", + Arrays.toString(newElement.getParents())); + + DeclareStyleableInfo newParent = styleableList.get("AndroidManifestNewParent"); + assertNotNull(newParent); + assertEquals("[AndroidManifest]", + Arrays.toString(newParent.getParents())); - // TODO test a bit more the styleable info vlaues. } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserTest.java index 8662259..ae4837e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/AttrsXmlParserTest.java @@ -20,8 +20,6 @@ package com.android.ide.eclipse.adt.internal.resources; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.eclipse.tests.AdtTestData; -import org.w3c.dom.Document; - import java.util.Map; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/mock_manifest_attrs.xml b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/mock_manifest_attrs.xml index 9dcb70b..2335d25 100755 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/mock_manifest_attrs.xml +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/mock_manifest_attrs.xml @@ -170,8 +170,8 @@ <attr name="icon" /> </declare-styleable> - <declare-styleable name="AndroidManifestNewElement" - parent="AndroidManifest.AndroidManifestImplicitParentElement"> + <declare-styleable name="AndroidManifestNewParentNewElement" + parent="AndroidManifest.AndroidManifestNewParent"> <attr name="name" /> <attr name="label" /> <attr name="icon" /> |