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