1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
17 
18 import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
19 import static com.android.SdkConstants.DOT_XML;
20 
21 import com.android.ide.common.rendering.api.ViewInfo;
22 import com.android.ide.eclipse.adt.AdtPlugin;
23 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
24 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
25 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
26 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
27 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
28 
29 import org.eclipse.core.resources.IFile;
30 import org.eclipse.core.runtime.IPath;
31 import org.eclipse.jface.preference.IPreferenceStore;
32 import org.eclipse.jface.text.BadLocationException;
33 import org.eclipse.jface.text.Document;
34 import org.eclipse.jface.text.IDocument;
35 import org.eclipse.ltk.core.refactoring.Change;
36 import org.eclipse.ltk.core.refactoring.TextFileChange;
37 import org.eclipse.text.edits.MultiTextEdit;
38 import org.eclipse.text.edits.TextEdit;
39 import org.eclipse.wst.sse.core.StructuredModelManager;
40 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
41 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
42 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
43 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
44 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
45 import org.w3c.dom.Element;
46 
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.regex.Matcher;
54 import java.util.regex.Pattern;
55 
56 @SuppressWarnings("restriction")
57 public class RefactoringTest extends AdtProjectTest {
58 
autoFormat()59     protected boolean autoFormat() {
60         return true;
61     }
62 
63     @Override
setUp()64     protected void setUp() throws Exception {
65 
66         // Ensure that the defaults are initialized so for example formatting options are
67         // initialized properly
68         IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
69         AdtPrefs.init(store);
70         AdtPrefs prefs = AdtPrefs.getPrefs();
71         prefs.initializeStoreWithDefaults(store);
72 
73         store.setValue(AdtPrefs.PREFS_FORMAT_GUI_XML, autoFormat());
74 
75         prefs.loadValues(null);
76 
77         super.setUp();
78     }
79 
findElementById(Element root, String id)80     protected static Element findElementById(Element root, String id) {
81         if (id.equals(VisualRefactoring.getId(root))) {
82             return root;
83         }
84 
85         for (Element child : DomUtilities.getChildren(root)) {
86             Element result = findElementById(child, id);
87             if (result != null) {
88                 return result;
89             }
90         }
91 
92         return null;
93     }
94 
getElements(Element root, String... ids)95     protected static List<Element> getElements(Element root, String... ids) {
96         List<Element> selectedElements = new ArrayList<Element>();
97         for (String id : ids) {
98             Element element = findElementById(root, id);
99             assertNotNull(element);
100             selectedElements.add(element);
101         }
102         return selectedElements;
103     }
104 
checkEdits(String basename, List<Change> changes)105     protected void checkEdits(String basename, List<Change> changes) throws BadLocationException,
106             IOException {
107         IDocument document = new Document();
108 
109         String xml = readTestFile(basename, false);
110         if (xml == null) { // New file
111             xml = ""; //$NON-NLS-1$
112         }
113         document.set(xml);
114 
115         for (Change change : changes) {
116             if (change instanceof TextFileChange) {
117                 TextFileChange tf = (TextFileChange) change;
118                 TextEdit edit = tf.getEdit();
119                 IFile file = tf.getFile();
120                 String contents = AdtPlugin.readFile(file);
121                 assertEquals(contents, xml);
122                 if (edit instanceof MultiTextEdit) {
123                     MultiTextEdit edits = (MultiTextEdit) edit;
124                     edits.apply(document);
125                 } else {
126                     edit.apply(document);
127                 }
128             } else {
129                 System.out.println("Ignoring non-textfilechange in refactoring result");
130             }
131         }
132 
133         String actual = document.get();
134 
135         // Ensure that the document is still valid to make sure the edits don't
136         // mangle it:
137         org.w3c.dom.Document doc = DomUtilities.parseDocument(actual, true);
138         assertNotNull(actual, doc);
139 
140         assertEqualsGolden(basename, actual);
141     }
142 
checkEdits(List<Change> changes, Map<IPath, String> fileToGoldenName)143     protected void checkEdits(List<Change> changes,
144             Map<IPath, String> fileToGoldenName) throws BadLocationException, IOException {
145         checkEdits(changes, fileToGoldenName, false);
146     }
147 
checkEdits(List<Change> changes, Map<IPath, String> fileToGoldenName, boolean createDiffs)148     protected void checkEdits(List<Change> changes,
149             Map<IPath, String> fileToGoldenName, boolean createDiffs)
150                     throws BadLocationException, IOException {
151         for (Change change : changes) {
152             if (change instanceof TextFileChange) {
153                 TextFileChange tf = (TextFileChange) change;
154                 IFile file = tf.getFile();
155                 assertNotNull(file);
156                 IPath path = file.getProjectRelativePath();
157                 String goldenName = fileToGoldenName.get(path);
158                 assertNotNull("Not found: " + path.toString(), goldenName);
159 
160                 String xml = readTestFile(goldenName, false);
161                 if (xml == null) { // New file
162                     xml = ""; //$NON-NLS-1$
163                 }
164                 IDocument document = new Document();
165                 document.set(xml);
166 
167                 String before = document.get();
168 
169                 TextEdit edit = tf.getEdit();
170                 if (edit instanceof MultiTextEdit) {
171                     MultiTextEdit edits = (MultiTextEdit) edit;
172                     edits.apply(document);
173                 } else {
174                     edit.apply(document);
175                 }
176 
177                 String actual = document.get();
178 
179                 if (createDiffs) {
180                     // Use a diff as the golden file instead of the after
181                     actual = getDiff(before, actual);
182                     if (goldenName.endsWith(DOT_XML)) {
183                         goldenName = goldenName.substring(0,
184                                 goldenName.length() - DOT_XML.length())
185                                 + ".diff";
186                     }
187                 }
188 
189                 assertEqualsGolden(goldenName, actual);
190             } else {
191                 System.out.println("Ignoring non-textfilechange in refactoring result");
192                 assertNull(change.getAffectedObjects());
193             }
194         }
195     }
196 
createModel(UiViewElementNode parent, Element element)197     protected UiViewElementNode createModel(UiViewElementNode parent, Element element) {
198         List<Element> children = DomUtilities.getChildren(element);
199         String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName();
200         boolean hasChildren = children.size() > 0;
201         UiViewElementNode node = createNode(parent, fqcn, hasChildren);
202         node.setXmlNode(element);
203         for (Element child : children) {
204             createModel(node, child);
205         }
206 
207         return node;
208     }
209 
210     /**
211      * Builds up a ViewInfo hierarchy for the given model. This is done by
212      * reading .info dump files which record the exact pixel sizes of each
213      * ViewInfo object. These files are assumed to match up exactly with the
214      * model objects. This is done rather than rendering an actual layout
215      * hierarchy to insulate the test from pixel difference (in say font size)
216      * among platforms, as well as tying the test to particulars about relative
217      * sizes of things which may change with theme adjustments etc.
218      * <p>
219      * Each file can be generated by the dump method in the ViewHierarchy.
220      */
createInfos(UiElementNode model, String relativePath)221     protected ViewInfo createInfos(UiElementNode model, String relativePath) throws IOException {
222         String basename = relativePath.substring(0, relativePath.lastIndexOf('.') + 1);
223         String relative = basename + "info"; //$NON-NLS-1$
224         String info = readTestFile(relative, true);
225         // Parse the info file and build up a model from it
226         // Each line contains a new info.
227         // If indented it is a child of the parent.
228         String[] lines = info.split("\n"); //$NON-NLS-1$
229 
230         // Iteration order for the info file should match exactly the UI model so
231         // we can just advance the line index sequentially as we traverse
232 
233         return create(model, Arrays.asList(lines).iterator());
234     }
235 
create(UiElementNode node, Iterator<String> lineIterator)236     protected ViewInfo create(UiElementNode node, Iterator<String> lineIterator) {
237         // android.widget.LinearLayout [0,36,240,320]
238         Pattern pattern = Pattern.compile("(\\s*)(\\S+) \\[(\\d+),(\\d+),(\\d+),(\\d+)\\].*");
239         assertTrue(lineIterator.hasNext());
240         String description = lineIterator.next();
241         Matcher matcher = pattern.matcher(description);
242         assertTrue(matcher.matches());
243         //String indent = matcher.group(1);
244         //String fqcn = matcher.group(2);
245         String left = matcher.group(3);
246         String top = matcher.group(4);
247         String right = matcher.group(5);
248         String bottom = matcher.group(6);
249 
250         ViewInfo view = new ViewInfo(node.getXmlNode().getLocalName(), node,
251                 Integer.parseInt(left), Integer.parseInt(top),
252                 Integer.parseInt(right), Integer.parseInt(bottom));
253 
254         List<UiElementNode> childNodes = node.getUiChildren();
255         if (childNodes.size() > 0) {
256             List<ViewInfo> children = new ArrayList<ViewInfo>();
257             for (UiElementNode child : childNodes) {
258                 children.add(create(child, lineIterator));
259             }
260             view.setChildren(children);
261         }
262 
263         return view;
264     }
265 
setupTestContext(IFile file, String relativePath)266     protected TestContext setupTestContext(IFile file, String relativePath) throws Exception {
267         IStructuredModel structuredModel = null;
268         org.w3c.dom.Document domDocument = null;
269         IStructuredDocument structuredDocument = null;
270         Element element = null;
271 
272         try {
273             IModelManager modelManager = StructuredModelManager.getModelManager();
274             structuredModel = modelManager.getModelForRead(file);
275             if (structuredModel instanceof IDOMModel) {
276                 IDOMModel domModel = (IDOMModel) structuredModel;
277                 domDocument = domModel.getDocument();
278                 element = domDocument.getDocumentElement();
279                 structuredDocument = structuredModel.getStructuredDocument();
280             }
281         } finally {
282             if (structuredModel != null) {
283                 structuredModel.releaseFromRead();
284             }
285         }
286 
287         assertNotNull(structuredModel);
288         assertNotNull(domDocument);
289         assertNotNull(element);
290         assertNotNull(structuredDocument);
291         assertTrue(element instanceof IndexedRegion);
292 
293         UiViewElementNode model = createModel(null, element);
294         ViewInfo info = createInfos(model, relativePath);
295         CanvasViewInfo rootView = CanvasViewInfo.create(info, true /* layoutlib5 */).getFirst();
296         TestLayoutEditorDelegate layoutEditor =
297             new TestLayoutEditorDelegate(file, structuredDocument, null);
298 
299         TestContext testInfo = createTestContext();
300         testInfo.mFile = file;
301         testInfo.mStructuredModel = structuredModel;
302         testInfo.mStructuredDocument = structuredDocument;
303         testInfo.mElement = element;
304         testInfo.mDomDocument = domDocument;
305         testInfo.mUiModel = model;
306         testInfo.mViewInfo = info;
307         testInfo.mRootView = rootView;
308         testInfo.mLayoutEditorDelegate = layoutEditor;
309 
310         return testInfo;
311     }
312 
createTestContext()313     protected TestContext createTestContext() {
314         return new TestContext();
315     }
316 
317     protected static class TestContext {
318         protected IFile mFile;
319         protected IStructuredModel mStructuredModel;
320         protected IStructuredDocument mStructuredDocument;
321         protected org.w3c.dom.Document mDomDocument;
322         protected Element mElement;
323         protected UiViewElementNode mUiModel;
324         protected ViewInfo mViewInfo;
325         protected CanvasViewInfo mRootView;
326         protected TestLayoutEditorDelegate mLayoutEditorDelegate;
327     }
328 
329     @Override
testDummy()330     public void testDummy() {
331         // To avoid JUnit warning that this class contains no tests, even though
332         // this is an abstract class and JUnit shouldn't try
333     }
334 }
335