/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.idea.rendering; import com.android.resources.ResourceFolderType; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import org.jetbrains.android.AndroidTestCase; import org.jetbrains.annotations.Nullable; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.StringReader; import java.util.SortedSet; import java.util.TreeSet; import static com.android.SdkConstants.*; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; public class LayoutPsiPullParserTest extends AndroidTestCase { @SuppressWarnings("SpellCheckingInspection") public static final String BASE_PATH = "xmlpull/"; public LayoutPsiPullParserTest() { } public void testDesignAttributes() throws Exception { @SuppressWarnings("SpellCheckingInspection") VirtualFile virtualFile = myFixture.copyFileToProject("xmlpull/designtime.xml", "res/layout/designtime.xml"); assertNotNull(virtualFile); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile); assertTrue(psiFile instanceof XmlFile); XmlFile xmlFile = (XmlFile)psiFile; LayoutPsiPullParser parser = LayoutPsiPullParser.create(xmlFile, new RenderLogger("test", myModule)); assertEquals(START_TAG, parser.nextTag()); assertEquals("LinearLayout", parser.getName()); assertEquals(START_TAG, parser.nextTag()); assertEquals("TextView", parser.getName()); assertEquals("@+id/first", parser.getAttributeValue(ANDROID_URI, ATTR_ID)); assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("TextView", parser.getName()); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, ATTR_LAYOUT_WIDTH)); // auto converted from match_parent assertEquals("wrap_content", parser.getAttributeValue(ANDROID_URI, ATTR_LAYOUT_HEIGHT)); assertEquals("Designtime Text", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); // overriding runtime text attribute assertEquals("@android:color/darker_gray", parser.getAttributeValue(ANDROID_URI, "textColor")); assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("TextView", parser.getName()); assertEquals("@+id/blank", parser.getAttributeValue(ANDROID_URI, ATTR_ID)); assertEquals("", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); // Don't unset when no framework attribute is defined assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("ListView", parser.getName()); assertEquals("@+id/listView", parser.getAttributeValue(ANDROID_URI, ATTR_ID)); assertNull(parser.getAttributeValue(ANDROID_URI, "fastScrollAlwaysVisible")); // Cleared by overriding defined framework attribute } public void testRootFragment() throws Exception { @SuppressWarnings("SpellCheckingInspection") VirtualFile virtualFile = myFixture.copyFileToProject("xmlpull/root_fragment.xml", "res/layout/root_fragment.xml"); assertNotNull(virtualFile); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile); assertTrue(psiFile instanceof XmlFile); XmlFile xmlFile = (XmlFile)psiFile; LayoutPsiPullParser parser = LayoutPsiPullParser.create(xmlFile, new RenderLogger("test", myModule)); assertEquals(START_TAG, parser.nextTag()); assertEquals("FrameLayout", parser.getName()); // Automatically inserted surrounding the <include> assertEquals(7, parser.getAttributeCount()); assertEquals("@+id/item_list", parser.getAttributeValue(ANDROID_URI, ATTR_ID)); assertEquals("com.unit.test.app.ItemListFragment", parser.getAttributeValue(ANDROID_URI, ATTR_NAME)); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_width")); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_height")); assertEquals(START_TAG, parser.nextTag()); assertEquals("include", parser.getName()); assertEquals(null, parser.getAttributeValue(ANDROID_URI, ATTR_ID)); //noinspection ConstantConditions assertEquals("@android:layout/list_content", parser.getAttributeValue(null, ATTR_LAYOUT)); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_width")); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_height")); assertEquals(END_TAG, parser.nextTag()); } public void testFrameLayoutInclude() throws Exception { @SuppressWarnings("SpellCheckingInspection") VirtualFile virtualFile = myFixture.copyFileToProject("xmlpull/frame_tools_layout.xml", "res/layout/frame_tools_layout.xml"); assertNotNull(virtualFile); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile); assertTrue(psiFile instanceof XmlFile); XmlFile xmlFile = (XmlFile)psiFile; LayoutPsiPullParser parser = LayoutPsiPullParser.create(xmlFile, new RenderLogger("test", myModule)); assertEquals(START_TAG, parser.nextTag()); assertEquals("FrameLayout", parser.getName()); // Automatically inserted surrounding the <include> assertEquals(5, parser.getAttributeCount()); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_width")); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_height")); assertEquals(START_TAG, parser.nextTag()); assertEquals("include", parser.getName()); assertEquals(null, parser.getAttributeValue(ANDROID_URI, ATTR_ID)); //noinspection ConstantConditions assertEquals("@android:layout/list_content", parser.getAttributeValue(null, ATTR_LAYOUT)); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_width")); assertEquals("fill_parent", parser.getAttributeValue(ANDROID_URI, "layout_height")); assertEquals(END_TAG, parser.nextTag()); } public void testVisibleChild() throws Exception { @SuppressWarnings("SpellCheckingInspection") VirtualFile virtualFile = myFixture.copyFileToProject("xmlpull/visible_child.xml", "res/layout/visible_child.xml"); assertNotNull(virtualFile); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(virtualFile); assertTrue(psiFile instanceof XmlFile); XmlFile xmlFile = (XmlFile)psiFile; LayoutPsiPullParser parser = LayoutPsiPullParser.create(xmlFile, new RenderLogger("test", myModule)); assertEquals(START_TAG, parser.nextTag()); assertEquals("FrameLayout", parser.getName()); assertEquals(START_TAG, parser.nextTag()); assertEquals("Button", parser.getName()); assertEquals("New Button", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); assertEquals("gone", parser.getAttributeValue(ANDROID_URI, ATTR_VISIBILITY)); assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("CheckBox", parser.getName()); assertEquals("New CheckBox", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); assertEquals("visible", parser.getAttributeValue(ANDROID_URI, ATTR_VISIBILITY)); assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("TextView", parser.getName()); assertEquals("New TextView", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); assertEquals("gone", parser.getAttributeValue(ANDROID_URI, ATTR_VISIBILITY)); assertEquals(END_TAG, parser.nextTag()); assertEquals(START_TAG, parser.nextTag()); assertEquals("Switch", parser.getName()); assertEquals("New Switch", parser.getAttributeValue(ANDROID_URI, ATTR_TEXT)); assertEquals("visible", parser.getAttributeValue(ANDROID_URI, ATTR_VISIBILITY)); assertEquals(END_TAG, parser.nextTag()); } public void test1() throws Exception { checkFile("layout.xml", ResourceFolderType.LAYOUT); } public void test2() throws Exception { checkFile("simple.xml", ResourceFolderType.LAYOUT); } enum NextEventType { NEXT, NEXT_TOKEN, NEXT_TAG } private void compareParsers(PsiFile file, NextEventType nextEventType) throws Exception { assertTrue(file instanceof XmlFile); XmlFile xmlFile = (XmlFile)file; KXmlParser referenceParser = createReferenceParser(file); LayoutPsiPullParser parser = LayoutPsiPullParser.create(xmlFile, new RenderLogger("test", myModule)); assertEquals("Expected " + name(referenceParser.getEventType()) + " but was " + name(parser.getEventType()) + " (at line:column " + describePosition(referenceParser) + ")", referenceParser.getEventType(), parser.getEventType()); while (true) { int expected, next; switch (nextEventType) { case NEXT: expected = referenceParser.next(); next = parser.next(); break; case NEXT_TOKEN: expected = referenceParser.nextToken(); next = parser.nextToken(); break; case NEXT_TAG: { try { expected = referenceParser.nextTag(); } catch (Exception e) { expected = referenceParser.getEventType(); } try { next = parser.nextTag(); } catch (Exception e) { next = parser.getEventType(); } break; } default: fail("Unexpected type"); return; } PsiElement element = null; if (expected == XmlPullParser.START_TAG) { assertNotNull(parser.getViewKey()); assertNotNull(parser.getViewCookie()); assertTrue(parser.getViewCookie() instanceof PsiElement); element = (PsiElement)parser.getViewCookie(); } if (expected == XmlPullParser.START_TAG) { assertEquals(referenceParser.getName(), parser.getName()); if (element != xmlFile.getRootTag()) { // KXmlParser seems to not include xmlns: attributes on the root tag!{ SortedSet<String> referenceAttributes = new TreeSet<String>(); SortedSet<String> attributes = new TreeSet<String>(); for (int i = 0; i < referenceParser.getAttributeCount(); i++) { String s = referenceParser.getAttributePrefix(i) + ':' + referenceParser.getAttributeName(i) + '=' + referenceParser.getAttributeValue(i); referenceAttributes.add(s); } for (int i = 0; i < parser.getAttributeCount(); i++) { String s = parser.getAttributePrefix(i) + ':' + parser.getAttributeName(i) + '=' + parser.getAttributeValue(i); attributes.add(s); if (parser.getAttributeNamespace(i) != null) { //noinspection ConstantConditions assertEquals(normalizeValue(parser.getAttributeValue(i)), normalizeValue(parser.getAttributeValue(parser.getAttributeNamespace(i), parser.getAttributeName(i)))); } } assertEquals(referenceAttributes, attributes); } // We're not correctly implementing this; it turns out Android doesn't need it, so we haven't bothered // pulling out the state correctly to do it //assertEquals(referenceParser.isEmptyElementTag(), parser.isEmptyElementTag()); if (element instanceof XmlTag) { XmlTag tag = (XmlTag)element; for (XmlAttribute attribute : tag.getAttributes()) { String namespace = attribute.getNamespace(); String name = attribute.getLocalName(); if (namespace.isEmpty()) { String prefix = attribute.getNamespacePrefix(); if (!prefix.isEmpty()) { name = prefix + ":" + prefix; } } //noinspection ConstantConditions assertEquals(namespace + ':' + name + " in element " + parser.getName(), normalizeValue(referenceParser.getAttributeValue(namespace, name)), normalizeValue(parser.getAttributeValue(namespace, name))); } } } else if (expected == XmlPullParser.TEXT || expected == XmlPullParser.COMMENT) { assertEquals(StringUtil.notNullize(referenceParser.getText()).trim(), StringUtil.notNullize(parser.getText()).trim()); } if (expected != next) { assertEquals("Expected " + name(expected) + " but was " + name(next) + "(At " + describePosition(referenceParser) + ")", expected, next); } if (expected == XmlPullParser.END_DOCUMENT) { break; } } } @Nullable private static String normalizeValue(String value) { // Some parser translate values; ensure that these are identical if (value != null && value.equals(VALUE_MATCH_PARENT)) { return VALUE_FILL_PARENT; } return value; } private static String name(int event) { return XmlPullParser.TYPES[event]; } private void checkFile(String filename, ResourceFolderType folder) throws Exception { VirtualFile file = myFixture.copyFileToProject(BASE_PATH + filename, "res/" + folder.getName() + "/" + filename); assertNotNull(file); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(file); assertNotNull(psiFile); compareParsers(psiFile, NextEventType.NEXT_TAG); // The LayoutPsiPullParser only supports tags, not text (no text is used in layouts) //compareParsers(psiFile, NextEventType.NEXT); //compareParsers(psiFile, NextEventType.NEXT_TOKEN); } private static KXmlParser createReferenceParser(PsiFile file) throws XmlPullParserException { KXmlParser referenceParser = new KXmlParser(); referenceParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); referenceParser.setInput(new StringReader(file.getText())); return referenceParser; } private static String describePosition(KXmlParser referenceParser) { return referenceParser.getLineNumber() + ":" + referenceParser.getColumnNumber(); } }