| /* |
| * Copyright (C) 2011 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.eclipse.adt.internal.editors.layout.refactoring; |
| |
| import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX; |
| import static com.android.SdkConstants.DOT_XML; |
| |
| import com.android.ide.common.rendering.api.ViewInfo; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; |
| import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; |
| import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; |
| import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.ltk.core.refactoring.Change; |
| import org.eclipse.ltk.core.refactoring.TextFileChange; |
| import org.eclipse.text.edits.MultiTextEdit; |
| import org.eclipse.text.edits.TextEdit; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.w3c.dom.Element; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| @SuppressWarnings("restriction") |
| public class RefactoringTest extends AdtProjectTest { |
| |
| protected boolean autoFormat() { |
| return true; |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| |
| // Ensure that the defaults are initialized so for example formatting options are |
| // initialized properly |
| IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); |
| AdtPrefs.init(store); |
| AdtPrefs prefs = AdtPrefs.getPrefs(); |
| prefs.initializeStoreWithDefaults(store); |
| |
| store.setValue(AdtPrefs.PREFS_FORMAT_GUI_XML, autoFormat()); |
| |
| prefs.loadValues(null); |
| |
| super.setUp(); |
| } |
| |
| protected static Element findElementById(Element root, String id) { |
| if (id.equals(VisualRefactoring.getId(root))) { |
| return root; |
| } |
| |
| for (Element child : DomUtilities.getChildren(root)) { |
| Element result = findElementById(child, id); |
| if (result != null) { |
| return result; |
| } |
| } |
| |
| return null; |
| } |
| |
| protected static List<Element> getElements(Element root, String... ids) { |
| List<Element> selectedElements = new ArrayList<Element>(); |
| for (String id : ids) { |
| Element element = findElementById(root, id); |
| assertNotNull(element); |
| selectedElements.add(element); |
| } |
| return selectedElements; |
| } |
| |
| protected void checkEdits(String basename, List<Change> changes) throws BadLocationException, |
| IOException { |
| IDocument document = new Document(); |
| |
| String xml = readTestFile(basename, false); |
| if (xml == null) { // New file |
| xml = ""; //$NON-NLS-1$ |
| } |
| document.set(xml); |
| |
| for (Change change : changes) { |
| if (change instanceof TextFileChange) { |
| TextFileChange tf = (TextFileChange) change; |
| TextEdit edit = tf.getEdit(); |
| IFile file = tf.getFile(); |
| String contents = AdtPlugin.readFile(file); |
| assertEquals(contents, xml); |
| if (edit instanceof MultiTextEdit) { |
| MultiTextEdit edits = (MultiTextEdit) edit; |
| edits.apply(document); |
| } else { |
| edit.apply(document); |
| } |
| } else { |
| System.out.println("Ignoring non-textfilechange in refactoring result"); |
| } |
| } |
| |
| String actual = document.get(); |
| |
| // Ensure that the document is still valid to make sure the edits don't |
| // mangle it: |
| org.w3c.dom.Document doc = DomUtilities.parseDocument(actual, true); |
| assertNotNull(actual, doc); |
| |
| assertEqualsGolden(basename, actual); |
| } |
| |
| protected void checkEdits(List<Change> changes, |
| Map<IPath, String> fileToGoldenName) throws BadLocationException, IOException { |
| checkEdits(changes, fileToGoldenName, false); |
| } |
| |
| protected void checkEdits(List<Change> changes, |
| Map<IPath, String> fileToGoldenName, boolean createDiffs) |
| throws BadLocationException, IOException { |
| for (Change change : changes) { |
| if (change instanceof TextFileChange) { |
| TextFileChange tf = (TextFileChange) change; |
| IFile file = tf.getFile(); |
| assertNotNull(file); |
| IPath path = file.getProjectRelativePath(); |
| String goldenName = fileToGoldenName.get(path); |
| assertNotNull("Not found: " + path.toString(), goldenName); |
| |
| String xml = readTestFile(goldenName, false); |
| if (xml == null) { // New file |
| xml = ""; //$NON-NLS-1$ |
| } |
| IDocument document = new Document(); |
| document.set(xml); |
| |
| String before = document.get(); |
| |
| TextEdit edit = tf.getEdit(); |
| if (edit instanceof MultiTextEdit) { |
| MultiTextEdit edits = (MultiTextEdit) edit; |
| edits.apply(document); |
| } else { |
| edit.apply(document); |
| } |
| |
| String actual = document.get(); |
| |
| if (createDiffs) { |
| // Use a diff as the golden file instead of the after |
| actual = getDiff(before, actual); |
| if (goldenName.endsWith(DOT_XML)) { |
| goldenName = goldenName.substring(0, |
| goldenName.length() - DOT_XML.length()) |
| + ".diff"; |
| } |
| } |
| |
| assertEqualsGolden(goldenName, actual); |
| } else { |
| System.out.println("Ignoring non-textfilechange in refactoring result"); |
| assertNull(change.getAffectedObjects()); |
| } |
| } |
| } |
| |
| protected UiViewElementNode createModel(UiViewElementNode parent, Element element) { |
| List<Element> children = DomUtilities.getChildren(element); |
| String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName(); |
| boolean hasChildren = children.size() > 0; |
| UiViewElementNode node = createNode(parent, fqcn, hasChildren); |
| node.setXmlNode(element); |
| for (Element child : children) { |
| createModel(node, child); |
| } |
| |
| return node; |
| } |
| |
| /** |
| * Builds up a ViewInfo hierarchy for the given model. This is done by |
| * reading .info dump files which record the exact pixel sizes of each |
| * ViewInfo object. These files are assumed to match up exactly with the |
| * model objects. This is done rather than rendering an actual layout |
| * hierarchy to insulate the test from pixel difference (in say font size) |
| * among platforms, as well as tying the test to particulars about relative |
| * sizes of things which may change with theme adjustments etc. |
| * <p> |
| * Each file can be generated by the dump method in the ViewHierarchy. |
| */ |
| protected ViewInfo createInfos(UiElementNode model, String relativePath) throws IOException { |
| String basename = relativePath.substring(0, relativePath.lastIndexOf('.') + 1); |
| String relative = basename + "info"; //$NON-NLS-1$ |
| String info = readTestFile(relative, true); |
| // Parse the info file and build up a model from it |
| // Each line contains a new info. |
| // If indented it is a child of the parent. |
| String[] lines = info.split("\n"); //$NON-NLS-1$ |
| |
| // Iteration order for the info file should match exactly the UI model so |
| // we can just advance the line index sequentially as we traverse |
| |
| return create(model, Arrays.asList(lines).iterator()); |
| } |
| |
| protected ViewInfo create(UiElementNode node, Iterator<String> lineIterator) { |
| // android.widget.LinearLayout [0,36,240,320] |
| Pattern pattern = Pattern.compile("(\\s*)(\\S+) \\[(\\d+),(\\d+),(\\d+),(\\d+)\\].*"); |
| assertTrue(lineIterator.hasNext()); |
| String description = lineIterator.next(); |
| Matcher matcher = pattern.matcher(description); |
| assertTrue(matcher.matches()); |
| //String indent = matcher.group(1); |
| //String fqcn = matcher.group(2); |
| String left = matcher.group(3); |
| String top = matcher.group(4); |
| String right = matcher.group(5); |
| String bottom = matcher.group(6); |
| |
| ViewInfo view = new ViewInfo(node.getXmlNode().getLocalName(), node, |
| Integer.parseInt(left), Integer.parseInt(top), |
| Integer.parseInt(right), Integer.parseInt(bottom)); |
| |
| List<UiElementNode> childNodes = node.getUiChildren(); |
| if (childNodes.size() > 0) { |
| List<ViewInfo> children = new ArrayList<ViewInfo>(); |
| for (UiElementNode child : childNodes) { |
| children.add(create(child, lineIterator)); |
| } |
| view.setChildren(children); |
| } |
| |
| return view; |
| } |
| |
| protected TestContext setupTestContext(IFile file, String relativePath) throws Exception { |
| IStructuredModel structuredModel = null; |
| org.w3c.dom.Document domDocument = null; |
| IStructuredDocument structuredDocument = null; |
| Element element = null; |
| |
| try { |
| IModelManager modelManager = StructuredModelManager.getModelManager(); |
| structuredModel = modelManager.getModelForRead(file); |
| if (structuredModel instanceof IDOMModel) { |
| IDOMModel domModel = (IDOMModel) structuredModel; |
| domDocument = domModel.getDocument(); |
| element = domDocument.getDocumentElement(); |
| structuredDocument = structuredModel.getStructuredDocument(); |
| } |
| } finally { |
| if (structuredModel != null) { |
| structuredModel.releaseFromRead(); |
| } |
| } |
| |
| assertNotNull(structuredModel); |
| assertNotNull(domDocument); |
| assertNotNull(element); |
| assertNotNull(structuredDocument); |
| assertTrue(element instanceof IndexedRegion); |
| |
| UiViewElementNode model = createModel(null, element); |
| ViewInfo info = createInfos(model, relativePath); |
| CanvasViewInfo rootView = CanvasViewInfo.create(info, true /* layoutlib5 */).getFirst(); |
| TestLayoutEditorDelegate layoutEditor = |
| new TestLayoutEditorDelegate(file, structuredDocument, null); |
| |
| TestContext testInfo = createTestContext(); |
| testInfo.mFile = file; |
| testInfo.mStructuredModel = structuredModel; |
| testInfo.mStructuredDocument = structuredDocument; |
| testInfo.mElement = element; |
| testInfo.mDomDocument = domDocument; |
| testInfo.mUiModel = model; |
| testInfo.mViewInfo = info; |
| testInfo.mRootView = rootView; |
| testInfo.mLayoutEditorDelegate = layoutEditor; |
| |
| return testInfo; |
| } |
| |
| protected TestContext createTestContext() { |
| return new TestContext(); |
| } |
| |
| protected static class TestContext { |
| protected IFile mFile; |
| protected IStructuredModel mStructuredModel; |
| protected IStructuredDocument mStructuredDocument; |
| protected org.w3c.dom.Document mDomDocument; |
| protected Element mElement; |
| protected UiViewElementNode mUiModel; |
| protected ViewInfo mViewInfo; |
| protected CanvasViewInfo mRootView; |
| protected TestLayoutEditorDelegate mLayoutEditorDelegate; |
| } |
| |
| @Override |
| public void testDummy() { |
| // To avoid JUnit warning that this class contains no tests, even though |
| // this is an abstract class and JUnit shouldn't try |
| } |
| } |