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