diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java new file mode 100644 index 000000000..30886e4fa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.common.layout; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX; +import static com.android.SdkConstants.ATTR_ID; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.api.IAttributeInfo; +import com.android.ide.common.api.INode; +import com.android.ide.common.api.INodeHandler; +import com.android.ide.common.api.Margins; +import com.android.ide.common.api.Rect; +import com.android.ide.common.xml.XmlFormatStyle; +import com.android.ide.common.xml.XmlPrettyPrinter; +import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences; +import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.google.common.base.Splitter; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Test/mock implementation of {@link INode} */ +@SuppressWarnings("javadoc") +public class TestNode implements INode { + private TestNode mParent; + + private final List<TestNode> mChildren = new ArrayList<TestNode>(); + + private final String mFqcn; + + private Rect mBounds = new Rect(); // Invalid bounds initially + + private Map<String, IAttribute> mAttributes = new HashMap<String, IAttribute>(); + + private Map<String, IAttributeInfo> mAttributeInfos = new HashMap<String, IAttributeInfo>(); + + private List<String> mAttributeSources; + + public TestNode(String fqcn) { + this.mFqcn = fqcn; + } + + public TestNode bounds(Rect bounds) { + this.mBounds = bounds; + + return this; + } + + public TestNode id(String id) { + return set(ANDROID_URI, ATTR_ID, id); + } + + public TestNode set(String uri, String name, String value) { + setAttribute(uri, name, value); + + return this; + } + + public TestNode add(TestNode child) { + mChildren.add(child); + child.mParent = this; + + return this; + } + + public TestNode add(TestNode... children) { + for (TestNode child : children) { + mChildren.add(child); + child.mParent = this; + } + + return this; + } + + public static TestNode create(String fcqn) { + return new TestNode(fcqn); + } + + public void removeChild(int index) { + TestNode removed = mChildren.remove(index); + removed.mParent = null; + } + + // ==== INODE ==== + + @Override + public @NonNull INode appendChild(@NonNull String viewFqcn) { + return insertChildAt(viewFqcn, mChildren.size()); + } + + @Override + public void editXml(@NonNull String undoName, @NonNull INodeHandler callback) { + callback.handle(this); + } + + public void putAttributeInfo(String uri, String attrName, IAttributeInfo info) { + mAttributeInfos.put(uri + attrName, info); + } + + @Override + public IAttributeInfo getAttributeInfo(@Nullable String uri, @NonNull String attrName) { + return mAttributeInfos.get(uri + attrName); + } + + @Override + public @NonNull Rect getBounds() { + return mBounds; + } + + @Override + public @NonNull INode[] getChildren() { + return mChildren.toArray(new INode[mChildren.size()]); + } + + @Override + public @NonNull IAttributeInfo[] getDeclaredAttributes() { + return mAttributeInfos.values().toArray(new IAttributeInfo[mAttributeInfos.size()]); + } + + @Override + public @NonNull String getFqcn() { + return mFqcn; + } + + @Override + public @NonNull IAttribute[] getLiveAttributes() { + return mAttributes.values().toArray(new IAttribute[mAttributes.size()]); + } + + @Override + public INode getParent() { + return mParent; + } + + @Override + public INode getRoot() { + TestNode curr = this; + while (curr.mParent != null) { + curr = curr.mParent; + } + + return curr; + } + + @Override + public String getStringAttr(@Nullable String uri, @NonNull String attrName) { + IAttribute attr = mAttributes.get(uri + attrName); + if (attr == null) { + return null; + } + + return attr.getValue(); + } + + @Override + public @NonNull INode insertChildAt(@NonNull String viewFqcn, int index) { + TestNode child = new TestNode(viewFqcn); + if (index == -1) { + mChildren.add(child); + } else { + mChildren.add(index, child); + } + child.mParent = this; + return child; + } + + @Override + public void removeChild(@NonNull INode node) { + int index = mChildren.indexOf(node); + if (index != -1) { + removeChild(index); + } + } + + @Override + public boolean setAttribute(@Nullable String uri, @NonNull String localName, + @Nullable String value) { + mAttributes.put(uri + localName, new TestAttribute(uri, localName, value)); + return true; + } + + @Override + public String toString() { + String id = getStringAttr(ANDROID_URI, ATTR_ID); + return "TestNode [id=" + (id != null ? id : "?") + ", fqn=" + mFqcn + ", infos=" + + mAttributeInfos + ", attributes=" + mAttributes + ", bounds=" + mBounds + "]"; + } + + @Override + public int getBaseline() { + return -1; + } + + @Override + public @NonNull Margins getMargins() { + return null; + } + + @Override + public @NonNull List<String> getAttributeSources() { + return mAttributeSources != null ? mAttributeSources : Collections.<String>emptyList(); + } + + public void setAttributeSources(List<String> attributeSources) { + mAttributeSources = attributeSources; + } + + /** Create a test node from the given XML */ + public static TestNode createFromXml(String xml) { + Document document = DomUtilities.parseDocument(xml, false); + assertNotNull(document); + assertNotNull(document.getDocumentElement()); + + return createFromNode(document.getDocumentElement()); + } + + public static String toXml(TestNode node) { + assertTrue("This method only works with nodes constructed from XML", + node instanceof TestXmlNode); + Document document = ((TestXmlNode) node).mElement.getOwnerDocument(); + // Insert new whitespace nodes etc + String xml = dumpDocument(document); + document = DomUtilities.parseDocument(xml, false); + + XmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(EclipseXmlFormatPreferences.create(), + XmlFormatStyle.LAYOUT, "\n"); + StringBuilder sb = new StringBuilder(1000); + sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + printer.prettyPrint(-1, document, null, null, sb, false); + return sb.toString(); + } + + @SuppressWarnings("deprecation") + private static String dumpDocument(Document document) { + // Diagnostics: print out the XML that we're about to render + org.apache.xml.serialize.OutputFormat outputFormat = + new org.apache.xml.serialize.OutputFormat( + "XML", "ISO-8859-1", true); //$NON-NLS-1$ //$NON-NLS-2$ + outputFormat.setIndent(2); + outputFormat.setLineWidth(100); + outputFormat.setIndenting(true); + outputFormat.setOmitXMLDeclaration(true); + outputFormat.setOmitDocumentType(true); + StringWriter stringWriter = new StringWriter(); + // Using FQN here to avoid having an import above, which will result + // in a deprecation warning, and there isn't a way to annotate a single + // import element with a SuppressWarnings. + org.apache.xml.serialize.XMLSerializer serializer = + new org.apache.xml.serialize.XMLSerializer(stringWriter, outputFormat); + serializer.setNamespaces(true); + try { + serializer.serialize(document.getDocumentElement()); + return stringWriter.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private static TestNode createFromNode(Element element) { + String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName(); + TestNode node = new TestXmlNode(fqcn, element); + + for (Element child : DomUtilities.getChildren(element)) { + node.add(createFromNode(child)); + } + + return node; + } + + @Nullable + public static TestNode findById(TestNode node, String id) { + id = BaseLayoutRule.stripIdPrefix(id); + return node.findById(id); + } + + private TestNode findById(String targetId) { + String id = getStringAttr(ANDROID_URI, ATTR_ID); + if (id != null && targetId.equals(BaseLayoutRule.stripIdPrefix(id))) { + return this; + } + + for (TestNode child : mChildren) { + TestNode result = child.findById(targetId); + if (result != null) { + return result; + } + } + + return null; + } + + private static String getTagName(String fqcn) { + return fqcn.substring(fqcn.lastIndexOf('.') + 1); + } + + private static class TestXmlNode extends TestNode { + private final Element mElement; + + public TestXmlNode(String fqcn, Element element) { + super(fqcn); + mElement = element; + } + + @Override + public @NonNull IAttribute[] getLiveAttributes() { + List<IAttribute> result = new ArrayList<IAttribute>(); + + NamedNodeMap attributes = mElement.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + result.add(new TestXmlAttribute(attribute)); + } + return result.toArray(new IAttribute[result.size()]); + } + + @Override + public boolean setAttribute(String uri, String localName, String value) { + if (value == null) { + mElement.removeAttributeNS(uri, localName); + } else { + mElement.setAttributeNS(uri, localName, value); + } + return super.setAttribute(uri, localName, value); + } + + @Override + public INode appendChild(String viewFqcn) { + Element child = mElement.getOwnerDocument().createElement(getTagName(viewFqcn)); + mElement.appendChild(child); + return new TestXmlNode(viewFqcn, child); + } + + @Override + public INode insertChildAt(String viewFqcn, int index) { + if (index == -1) { + return appendChild(viewFqcn); + } + Element child = mElement.getOwnerDocument().createElement(getTagName(viewFqcn)); + List<Element> children = DomUtilities.getChildren(mElement); + if (children.size() >= index) { + Element before = children.get(index); + mElement.insertBefore(child, before); + } else { + fail("Unexpected index"); + mElement.appendChild(child); + } + return new TestXmlNode(viewFqcn, child); + } + + @Override + public String getStringAttr(String uri, String name) { + String value; + if (uri == null) { + value = mElement.getAttribute(name); + } else { + value = mElement.getAttributeNS(uri, name); + } + if (value.isEmpty()) { + value = null; + } + + return value; + } + + @Override + public void removeChild(INode node) { + assert node instanceof TestXmlNode; + mElement.removeChild(((TestXmlNode) node).mElement); + } + + @Override + public void removeChild(int index) { + List<Element> children = DomUtilities.getChildren(mElement); + assertTrue(index < children.size()); + Element oldChild = children.get(index); + mElement.removeChild(oldChild); + } + } + + public static class TestXmlAttribute implements IAttribute { + private Attr mAttribute; + + public TestXmlAttribute(Attr attribute) { + this.mAttribute = attribute; + } + + @Override + public String getUri() { + return mAttribute.getNamespaceURI(); + } + + @Override + public String getName() { + String name = mAttribute.getLocalName(); + if (name == null) { + name = mAttribute.getName(); + } + return name; + } + + @Override + public String getValue() { + return mAttribute.getValue(); + } + } + + // Recursively initialize this node with the bounds specified in the given hierarchy + // dump (from ViewHierarchy's DUMP_INFO flag + public void assignBounds(String bounds) { + Iterable<String> split = Splitter.on('\n').trimResults().split(bounds); + assignBounds(split.iterator()); + } + + private void assignBounds(Iterator<String> iterator) { + assertTrue(iterator.hasNext()); + String desc = iterator.next(); + + Pattern pattern = Pattern.compile("^\\s*(.+)\\s+\\[(.+)\\]\\s*(<.+>)?\\s*(\\S+)?\\s*$"); + Matcher matcher = pattern.matcher(desc); + assertTrue(matcher.matches()); + String fqn = matcher.group(1); + assertEquals(getFqcn(), fqn); + String boundsString = matcher.group(2); + String[] bounds = boundsString.split(","); + assertEquals(boundsString, 4, bounds.length); + try { + int left = Integer.parseInt(bounds[0]); + int top = Integer.parseInt(bounds[1]); + int right = Integer.parseInt(bounds[2]); + int bottom = Integer.parseInt(bounds[3]); + mBounds = new Rect(left, top, right - left, bottom - top); + } catch (NumberFormatException nufe) { + fail(nufe.getLocalizedMessage()); + } + String tag = matcher.group(3); + + for (INode child : getChildren()) { + assertTrue(iterator.hasNext()); + ((TestNode) child).assignBounds(iterator); + } + } +} |