1 /* 2 * Copyright (C) 2010 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; 17 18 import static com.android.SdkConstants.FD_SOURCES; 19 20 import com.android.ide.common.resources.ResourceFile; 21 import com.android.ide.eclipse.adt.AdtUtils; 22 import com.android.ide.eclipse.adt.internal.editors.Hyperlinks.ResourceLink; 23 import com.android.ide.eclipse.adt.internal.editors.Hyperlinks.XmlResolver; 24 import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; 25 26 import org.eclipse.core.resources.IFile; 27 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; 28 import org.eclipse.jface.text.IDocument; 29 import org.eclipse.jface.text.IRegion; 30 import org.eclipse.jface.text.Region; 31 import org.eclipse.jface.text.hyperlink.IHyperlink; 32 import org.eclipse.jface.text.source.ISourceViewer; 33 import org.eclipse.swt.graphics.Point; 34 import org.eclipse.ui.IEditorPart; 35 import org.eclipse.ui.IWorkbenchPage; 36 import org.eclipse.ui.PlatformUI; 37 import org.eclipse.ui.ide.IDE; 38 import org.eclipse.ui.internal.ErrorEditorPart; 39 import org.eclipse.ui.internal.browser.WebBrowserEditor; 40 import org.eclipse.wst.sse.ui.StructuredTextEditor; 41 import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; 42 import org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart; 43 44 import java.lang.reflect.Field; 45 import java.lang.reflect.Method; 46 47 @SuppressWarnings({ 48 "restriction", "javadoc" 49 }) 50 public class HyperlinksTest extends AdtProjectTest { 51 @Override 52 protected boolean testCaseNeedsUniqueProject() { 53 return true; 54 } 55 56 public void testFqnRegexp() throws Exception { 57 assertTrue(Hyperlinks.isViewClassName("com.android.Foo")); 58 assertTrue(Hyperlinks.isViewClassName("com.android.pk_g.Foo_Bar1")); 59 assertTrue(Hyperlinks.isViewClassName("com.android.Foo$Inner")); 60 61 // Should we allow non-standard packages and class names? 62 // For now, we're allowing it -- see how this works out in practice. 63 //assertFalse(XmlHyperlinkResolver.isViewClassName("Foo.bar")); 64 assertTrue(Hyperlinks.isViewClassName("Foo.bar")); 65 66 assertFalse(Hyperlinks.isViewClassName("LinearLayout")); 67 assertFalse(Hyperlinks.isViewClassName(".")); 68 assertFalse(Hyperlinks.isViewClassName(".F")); 69 assertFalse(Hyperlinks.isViewClassName("f.")); 70 assertFalse(Hyperlinks.isViewClassName("Foo")); 71 assertFalse(Hyperlinks.isViewClassName("com.android.1Foo")); 72 assertFalse(Hyperlinks.isViewClassName("1com.Foo")); 73 } 74 75 public void testNavigate1() throws Exception { 76 // Check navigating to a local resource 77 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 78 "android:text=\"@string/app^_name\""); 79 } 80 81 public void testNavigate2() throws Exception { 82 // Check navigating to a framework resource 83 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 84 "marginLeft=\"@android:dimen/app_ico^n_size\""); 85 } 86 87 public void testNavigate3() throws Exception { 88 // Check navigating to a style 89 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 90 "style=\"@android:style/Widget.B^utton\""); 91 } 92 93 public void testNavigate4() throws Exception { 94 // Check navigating to resource with many resolutions 95 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 96 "android:text=\"@android:st^ring/ok\""); 97 } 98 99 public void testNavigate5() throws Exception { 100 // Check navigating to styles 101 checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", 102 "parent=\"android:Theme.Li^ght\">"); 103 } 104 105 public void testNavigate6() throws Exception { 106 // Check navigating to a portion of a style (this should pick android:Theme, not 107 // android:Theme.Light 108 checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", 109 "parent=\"android:The^me.Light\">"); 110 } 111 112 public void testNavigate7() throws Exception { 113 // Check navigating to a resource inside text content 114 checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", 115 "popupBackground\">@android:drawable/spinner_dr^opdown_background</item>"); 116 } 117 118 public void testNavigate8() throws Exception { 119 // Check navigating to a resource inside text content where there is space around 120 // the URL 121 checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", 122 "colorBackground\"> @color/cust^om_theme_color </item>"); 123 } 124 125 public void testNavigate9a() throws Exception { 126 // Check navigating to a an activity 127 checkXmlNavigation("manifest.xml", "AndroidManifest.xml", 128 "<activity android:name=\".Test^Activity\""); 129 } 130 131 /* Not yet implemented 132 public void testNavigate9b() throws Exception { 133 // Check navigating to a an activity - clicking on the activity element should 134 // work too 135 checkXmlNavigation("manifest.xml", "AndroidManifest.xml", 136 "<acti^vity android:name=\".TestActivity\""); 137 } 138 */ 139 140 public void testNavigate10() throws Exception { 141 // Check navigating to a permission 142 checkXmlNavigation("manifest.xml", "AndroidManifest.xml", 143 "<uses-permission android:name=\"android.permission.AC^CESS_NETWORK_STATE\" />"); 144 } 145 146 public void testNavigate11a() throws Exception { 147 // Check navigating to an intent 148 checkXmlNavigation("manifest.xml", "AndroidManifest.xml", 149 "<action android:name=\"android.intent.ac^tion.MAIN\" />"); 150 } 151 152 public void testNavigate11g() throws Exception { 153 // Check navigating to an intent 154 checkXmlNavigation("manifest.xml", "AndroidManifest.xml", 155 "<category android:name=\"android.intent.category.LA^UNCHER\" />"); 156 } 157 158 public void testNavigate12() throws Exception { 159 // Check navigating to a custom view class 160 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 161 "<my.Cust^omView></my.CustomView>"); 162 } 163 164 public void testNavigate13() throws Exception { 165 // Check jumping to classes pointed to by fragments 166 167 getTestDataFile(getProject(), "TestFragment.java.txt", 168 FD_SOURCES + "/" + TEST_PROJECT_PACKAGE.replace('.', '/') + "/TestFragment.java"); 169 checkXmlNavigation("fragmentlayout.xml", "res/layout/fragmentlayout.xml", 170 "android:name=\"com.android.ecl^ipse.tests.TestFragment\""); 171 } 172 173 public void testNavigate14() throws Exception { 174 // Check jumping to classes pointed to by fragments 175 176 getTestDataFile(getProject(), "TestFragment.java.txt", 177 FD_SOURCES + "/" + TEST_PROJECT_PACKAGE.replace('.', '/') + "/TestFragment.java"); 178 checkXmlNavigation("fragmentlayout.xml", "res/layout/fragmentlayout.xml", 179 "class=\"com.and^roid.eclipse.tests.TestFragment\""); 180 } 181 182 public void testNavigate15() throws Exception { 183 // Check navigating to a theme resource 184 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 185 "?android:attr/alert^DialogStyle"); 186 } 187 188 public void testNavigate16() throws Exception { 189 // Check navigating to a theme resource 190 checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", 191 "?android:alert^DialogStyle"); 192 } 193 194 // Left to test: 195 // onClick handling 196 // class attributes 197 // Test that the correct file is actually opened! 198 199 private void checkXmlNavigation(String basename, String targetPath, 200 String caretLocation) throws Exception { 201 IFile file = getTestDataFile(getProject(), basename, targetPath, true); 202 203 // Determine the offset 204 int offset = getCaretOffset(file, caretLocation); 205 206 // Open file 207 IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); 208 assertNotNull(page); 209 IEditorPart editor = IDE.openEditor(page, file); 210 assertTrue(editor.getClass().getName(), editor instanceof AndroidXmlEditor); 211 AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; 212 ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); 213 214 XmlResolver resolver = new Hyperlinks.XmlResolver(); 215 IHyperlink[] links = resolver.detectHyperlinks(viewer, new Region(offset, 0), true); 216 assertNotNull(links); 217 218 StringBuilder sb = new StringBuilder(1000); 219 sb.append("Go To Declaration in " + basename + " for " + caretLocation + ":\n"); 220 for (IHyperlink link : links) { 221 sb.append(link.getHyperlinkText()); 222 sb.append(" : "); 223 sb.append(" ["); 224 IRegion region = link.getHyperlinkRegion(); 225 sb.append(viewer.getDocument().get(region.getOffset(), region.getLength())); 226 sb.append("]"); 227 if (link instanceof Hyperlinks.ResourceLink) { 228 Hyperlinks.ResourceLink resourceLink = (ResourceLink) link; 229 sb.append("\n "); 230 ResourceFile resourceFile = resourceLink.getFile(); 231 String desc = resourceFile.toString(); 232 desc = desc.replace('\\', '/'); 233 // For files in the SDK folder, strip out file prefix 234 int dataRes = desc.indexOf("data/res"); 235 if (dataRes != -1) { 236 desc = desc.substring(dataRes); 237 } 238 desc = removeSessionData(desc); 239 sb.append(desc); 240 } 241 sb.append('\n'); 242 } 243 244 // Open the first link 245 IHyperlink link = links[0]; 246 link.open(); 247 IEditorPart newEditor = AdtUtils.getActiveEditor(); 248 // Ensure that this isn't an invalid file (e.g. opening the SDK platform files 249 // with incorrect content binding could cause this) 250 assertTrue(!(newEditor instanceof ErrorEditorPart)); 251 252 IDocument document = null; 253 Point selection = null; 254 255 if (newEditor instanceof AndroidXmlEditor) { 256 AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor; 257 document = xmlEditor.getStructuredSourceViewer().getDocument(); 258 selection = xmlEditor.getStructuredSourceViewer().getSelectedRange(); 259 } else if (newEditor instanceof XMLMultiPageEditorPart) { 260 XMLMultiPageEditorPart xmlEditor = (XMLMultiPageEditorPart) newEditor; 261 Field field = xmlEditor.getClass().getDeclaredField("fTextEditor"); 262 field.setAccessible(true); 263 StructuredTextEditor ste = (StructuredTextEditor) field.get(newEditor); 264 if (ste == null) { 265 Method method = xmlEditor.getClass().getMethod("getTextEditor", new Class[0]); 266 ste = (StructuredTextEditor) method.invoke(newEditor, new Object[0]); 267 } 268 StructuredTextViewer textViewer = ste.getTextViewer(); 269 document = textViewer.getDocument(); 270 selection = textViewer.getSelectedRange(); 271 } else if (newEditor instanceof WebBrowserEditor) { 272 WebBrowserEditor browser = (WebBrowserEditor) newEditor; 273 Field field = browser.getClass().getDeclaredField("initialURL"); 274 field.setAccessible(true); 275 String initialUrl = (String) field.get(newEditor); 276 int index = initialUrl.indexOf("reference"); 277 if (index != -1) { 278 initialUrl = initialUrl.substring(index); 279 } 280 initialUrl = initialUrl.replace('\\', '/'); 281 sb.append("\n\nAfter open, a browser is shown with this URL:\n"); 282 sb.append(" "); 283 sb.append(initialUrl); 284 sb.append("\n"); 285 } else if (newEditor instanceof JavaEditor) { 286 JavaEditor javaEditor = (JavaEditor) newEditor; 287 document = javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput()); 288 IRegion range = javaEditor.getHighlightRange(); 289 selection = new Point(range.getOffset(), range.getLength()); 290 } else { 291 fail("Unhandled editor type: " + newEditor.getClass().getName()); 292 return; 293 } 294 295 if (document != null && selection != null) { 296 int lineStart = document.getLineInformationOfOffset(selection.x).getOffset(); 297 IRegion lineEndInfo = document.getLineInformationOfOffset(selection.x + selection.y); 298 int lineEnd = lineEndInfo.getOffset() + lineEndInfo.getLength(); 299 String text = document.get(lineStart, lineEnd - lineStart); 300 int selectionStart = selection.x - lineStart; 301 int selectionEnd = selectionStart + selection.y; 302 if (selectionEnd > selectionStart) { 303 // Selection range 304 text = text.substring(0, selectionStart) + "[^" + 305 text.substring(selectionStart, selectionEnd) + "]" + 306 text.substring(selectionEnd); 307 } else { 308 text = text.substring(0, selectionStart) + "^" + 309 text.substring(selectionStart); 310 } 311 text = removeSessionData(text); 312 313 sb.append("\n\nAfter open, the selected text is:\n"); 314 sb.append(text); 315 sb.append("\n"); 316 } 317 318 assertEqualsGolden(basename, sb.toString(), "txt"); 319 } 320 } 321