aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java
diff options
context:
space:
mode:
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.java477
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);
+ }
+ }
+}