Home | History | Annotate | Download | only in refactoring
      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.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX;
     19 import static com.android.ide.eclipse.adt.AdtConstants.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 
     59     protected boolean autoFormat() {
     60         return true;
     61     }
     62 
     63     @Override
     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 
     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 
     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 
    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                 if (edit instanceof MultiTextEdit) {
    120                     MultiTextEdit edits = (MultiTextEdit) edit;
    121                     edits.apply(document);
    122                 } else {
    123                     edit.apply(document);
    124                 }
    125             } else {
    126                 System.out.println("Ignoring non-textfilechange in refactoring result");
    127             }
    128         }
    129 
    130         String actual = document.get();
    131 
    132         // Ensure that the document is still valid to make sure the edits don't
    133         // mangle it:
    134         org.w3c.dom.Document doc = DomUtilities.parseDocument(actual, true);
    135         assertNotNull(actual, doc);
    136 
    137         assertEqualsGolden(basename, actual);
    138     }
    139 
    140     protected void checkEdits(List<Change> changes,
    141             Map<IPath, String> fileToGoldenName) throws BadLocationException {
    142         checkEdits(changes, fileToGoldenName, false);
    143     }
    144 
    145     protected void checkEdits(List<Change> changes,
    146             Map<IPath, String> fileToGoldenName, boolean createDiffs) throws BadLocationException {
    147         for (Change change : changes) {
    148             if (change instanceof TextFileChange) {
    149                 TextFileChange tf = (TextFileChange) change;
    150                 IFile file = tf.getFile();
    151                 assertNotNull(file);
    152                 IPath path = file.getProjectRelativePath();
    153                 String goldenName = fileToGoldenName.get(path);
    154                 assertNotNull("Not found: " + path.toString(), goldenName);
    155 
    156                 String xml = readTestFile(goldenName, false);
    157                 if (xml == null) { // New file
    158                     xml = ""; //$NON-NLS-1$
    159                 }
    160                 IDocument document = new Document();
    161                 document.set(xml);
    162 
    163                 String before = document.get();
    164 
    165                 TextEdit edit = tf.getEdit();
    166                 if (edit instanceof MultiTextEdit) {
    167                     MultiTextEdit edits = (MultiTextEdit) edit;
    168                     edits.apply(document);
    169                 } else {
    170                     edit.apply(document);
    171                 }
    172 
    173                 String actual = document.get();
    174 
    175                 if (createDiffs) {
    176                     // Use a diff as the golden file instead of the after
    177                     actual = getDiff(before, actual);
    178                     if (goldenName.endsWith(DOT_XML)) {
    179                         goldenName = goldenName.substring(0,
    180                                 goldenName.length() - DOT_XML.length())
    181                                 + ".diff";
    182                     }
    183                 }
    184 
    185                 assertEqualsGolden(goldenName, actual);
    186             } else {
    187                 System.out.println("Ignoring non-textfilechange in refactoring result");
    188                 assertNull(change.getAffectedObjects());
    189             }
    190         }
    191     }
    192 
    193     protected UiViewElementNode createModel(UiViewElementNode parent, Element element) {
    194         List<Element> children = DomUtilities.getChildren(element);
    195         String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName();
    196         boolean hasChildren = children.size() > 0;
    197         UiViewElementNode node = createNode(parent, fqcn, hasChildren);
    198         node.setXmlNode(element);
    199         for (Element child : children) {
    200             createModel(node, child);
    201         }
    202 
    203         return node;
    204     }
    205 
    206     /**
    207      * Builds up a ViewInfo hierarchy for the given model. This is done by
    208      * reading .info dump files which record the exact pixel sizes of each
    209      * ViewInfo object. These files are assumed to match up exactly with the
    210      * model objects. This is done rather than rendering an actual layout
    211      * hierarchy to insulate the test from pixel difference (in say font size)
    212      * among platforms, as well as tying the test to particulars about relative
    213      * sizes of things which may change with theme adjustments etc.
    214      * <p>
    215      * Each file can be generated by the dump method in the ViewHierarchy.
    216      */
    217     protected ViewInfo createInfos(UiElementNode model, String relativePath) {
    218         String basename = relativePath.substring(0, relativePath.lastIndexOf('.') + 1);
    219         String relative = basename + "info"; //$NON-NLS-1$
    220         String info = readTestFile(relative, true);
    221         // Parse the info file and build up a model from it
    222         // Each line contains a new info.
    223         // If indented it is a child of the parent.
    224         String[] lines = info.split("\n"); //$NON-NLS-1$
    225 
    226         // Iteration order for the info file should match exactly the UI model so
    227         // we can just advance the line index sequentially as we traverse
    228 
    229         return create(model, Arrays.asList(lines).iterator());
    230     }
    231 
    232     protected ViewInfo create(UiElementNode node, Iterator<String> lineIterator) {
    233         // android.widget.LinearLayout [0,36,240,320]
    234         Pattern pattern = Pattern.compile("(\\s*)(\\S+) \\[(\\d+),(\\d+),(\\d+),(\\d+)\\].*");
    235         assertTrue(lineIterator.hasNext());
    236         String description = lineIterator.next();
    237         Matcher matcher = pattern.matcher(description);
    238         assertTrue(matcher.matches());
    239         //String indent = matcher.group(1);
    240         //String fqcn = matcher.group(2);
    241         String left = matcher.group(3);
    242         String top = matcher.group(4);
    243         String right = matcher.group(5);
    244         String bottom = matcher.group(6);
    245 
    246         ViewInfo view = new ViewInfo(node.getXmlNode().getLocalName(), node,
    247                 Integer.parseInt(left), Integer.parseInt(top),
    248                 Integer.parseInt(right), Integer.parseInt(bottom));
    249 
    250         List<UiElementNode> childNodes = node.getUiChildren();
    251         if (childNodes.size() > 0) {
    252             List<ViewInfo> children = new ArrayList<ViewInfo>();
    253             for (UiElementNode child : childNodes) {
    254                 children.add(create(child, lineIterator));
    255             }
    256             view.setChildren(children);
    257         }
    258 
    259         return view;
    260     }
    261 
    262     protected TestContext setupTestContext(IFile file, String relativePath) throws Exception {
    263         IStructuredModel structuredModel = null;
    264         org.w3c.dom.Document domDocument = null;
    265         IStructuredDocument structuredDocument = null;
    266         Element element = null;
    267 
    268         try {
    269             IModelManager modelManager = StructuredModelManager.getModelManager();
    270             structuredModel = modelManager.getModelForRead(file);
    271             if (structuredModel instanceof IDOMModel) {
    272                 IDOMModel domModel = (IDOMModel) structuredModel;
    273                 domDocument = domModel.getDocument();
    274                 element = domDocument.getDocumentElement();
    275                 structuredDocument = structuredModel.getStructuredDocument();
    276             }
    277         } finally {
    278             if (structuredModel != null) {
    279                 structuredModel.releaseFromRead();
    280             }
    281         }
    282 
    283         assertNotNull(structuredModel);
    284         assertNotNull(domDocument);
    285         assertNotNull(element);
    286         assertNotNull(structuredDocument);
    287         assertTrue(element instanceof IndexedRegion);
    288 
    289         UiViewElementNode model = createModel(null, element);
    290         ViewInfo info = createInfos(model, relativePath);
    291         CanvasViewInfo rootView = CanvasViewInfo.create(info, true /* layoutlib5 */).getFirst();
    292         TestLayoutEditor layoutEditor = new TestLayoutEditor(file, structuredDocument, null);
    293 
    294         TestContext testInfo = createTestContext();
    295         testInfo.mFile = file;
    296         testInfo.mStructuredModel = structuredModel;
    297         testInfo.mStructuredDocument = structuredDocument;
    298         testInfo.mElement = element;
    299         testInfo.mDomDocument = domDocument;
    300         testInfo.mUiModel = model;
    301         testInfo.mViewInfo = info;
    302         testInfo.mRootView = rootView;
    303         testInfo.mLayoutEditor = layoutEditor;
    304 
    305         return testInfo;
    306     }
    307 
    308     protected TestContext createTestContext() {
    309         return new TestContext();
    310     }
    311 
    312     protected static class TestContext {
    313         protected IFile mFile;
    314         protected IStructuredModel mStructuredModel;
    315         protected IStructuredDocument mStructuredDocument;
    316         protected org.w3c.dom.Document mDomDocument;
    317         protected Element mElement;
    318         protected UiViewElementNode mUiModel;
    319         protected ViewInfo mViewInfo;
    320         protected CanvasViewInfo mRootView;
    321         protected TestLayoutEditor mLayoutEditor;
    322     }
    323 
    324     @Override
    325     public void testDummy() {
    326         // To avoid JUnit warning that this class contains no tests, even though
    327         // this is an abstract class and JUnit shouldn't try
    328     }
    329 }
    330