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.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 
     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                 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 
    143     protected void checkEdits(List<Change> changes,
    144             Map<IPath, String> fileToGoldenName) throws BadLocationException, IOException {
    145         checkEdits(changes, fileToGoldenName, false);
    146     }
    147 
    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 
    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      */
    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 
    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 
    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 
    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
    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