• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.xml;
17 
18 import static com.android.sdklib.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.AndroidXmlEditor;
23 import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
24 import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.ResourceLink;
25 import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.XmlResolver;
26 
27 import org.eclipse.core.resources.IFile;
28 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
29 import org.eclipse.jface.text.IDocument;
30 import org.eclipse.jface.text.IRegion;
31 import org.eclipse.jface.text.Region;
32 import org.eclipse.jface.text.hyperlink.IHyperlink;
33 import org.eclipse.jface.text.source.ISourceViewer;
34 import org.eclipse.swt.graphics.Point;
35 import org.eclipse.ui.IEditorPart;
36 import org.eclipse.ui.IWorkbenchPage;
37 import org.eclipse.ui.PlatformUI;
38 import org.eclipse.ui.ide.IDE;
39 import org.eclipse.ui.internal.ErrorEditorPart;
40 import org.eclipse.ui.internal.browser.WebBrowserEditor;
41 import org.eclipse.wst.sse.ui.StructuredTextEditor;
42 import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
43 import org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart;
44 
45 import java.lang.reflect.Field;
46 import java.lang.reflect.Method;
47 
48 @SuppressWarnings("restriction")
49 public class HyperlinksTest extends AdtProjectTest {
50     @Override
testCaseNeedsUniqueProject()51     protected boolean testCaseNeedsUniqueProject() {
52         return true;
53     }
54 
testFqnRegexp()55     public void testFqnRegexp() throws Exception {
56         assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo").matches());
57         assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.pk_g.Foo_Bar1").
58                 matches());
59         assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo$Inner").matches());
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.CLASS_PATTERN.matcher("Foo.bar").matches());
64         assertTrue(Hyperlinks.CLASS_PATTERN.matcher("Foo.bar").matches());
65 
66         assertFalse(Hyperlinks.CLASS_PATTERN.matcher("LinearLayout").matches());
67         assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".").matches());
68         assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".F").matches());
69         assertFalse(Hyperlinks.CLASS_PATTERN.matcher("f.").matches());
70         assertFalse(Hyperlinks.CLASS_PATTERN.matcher("Foo").matches());
71         assertFalse(Hyperlinks.CLASS_PATTERN.matcher("com.android.1Foo").matches());
72         assertFalse(Hyperlinks.CLASS_PATTERN.matcher("1com.Foo").matches());
73     }
74 
testNavigate1()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 
testNavigate2()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 
testNavigate3()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 
testNavigate4()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 
testNavigate5()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 
testNavigate6()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 
testNavigate7()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 
testNavigate8()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 
testNavigate9a()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 
testNavigate10()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 
testNavigate11a()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 
testNavigate11g()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 
testNavigate12()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 
testNavigate13()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 
testNavigate14()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     // Left to test:
183     // onClick handling
184     // class attributes
185     // Test that the correct file is actually opened!
186 
checkXmlNavigation(String basename, String targetPath, String caretLocation)187     private void checkXmlNavigation(String basename, String targetPath,
188             String caretLocation) throws Exception {
189         IFile file = getTestDataFile(getProject(), basename, targetPath, true);
190 
191         // Determine the offset
192         int offset = getCaretOffset(file, caretLocation);
193 
194         // Open file
195         IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
196         assertNotNull(page);
197         IEditorPart editor = IDE.openEditor(page, file);
198         assertTrue(editor.getClass().getName(), editor instanceof AndroidXmlEditor);
199         AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor;
200         ISourceViewer viewer = layoutEditor.getStructuredSourceViewer();
201 
202         XmlResolver resolver = new Hyperlinks.XmlResolver();
203         IHyperlink[] links = resolver.detectHyperlinks(viewer, new Region(offset, 0), true);
204         assertNotNull(links);
205 
206         StringBuilder sb = new StringBuilder(1000);
207         sb.append("Go To Declaration in " + basename + " for " + caretLocation + ":\n");
208         for (IHyperlink link : links) {
209             sb.append(link.getHyperlinkText());
210             sb.append(" : ");
211             sb.append(" [");
212             IRegion region = link.getHyperlinkRegion();
213             sb.append(viewer.getDocument().get(region.getOffset(), region.getLength()));
214             sb.append("]");
215             if (link instanceof Hyperlinks.ResourceLink) {
216                 Hyperlinks.ResourceLink resourceLink = (ResourceLink) link;
217                 sb.append("\n    ");
218                 ResourceFile resourceFile = resourceLink.getFile();
219                 String desc = resourceFile.toString();
220                 desc = desc.replace('\\', '/');
221                 // For files in the SDK folder, strip out file prefix
222                 int dataRes = desc.indexOf("data/res");
223                 if (dataRes != -1) {
224                     desc = desc.substring(dataRes);
225                 }
226                 desc = removeSessionData(desc);
227                 sb.append(desc);
228             }
229             sb.append('\n');
230         }
231 
232         // Open the first link
233         IHyperlink link = links[0];
234         link.open();
235         IEditorPart newEditor = AdtUtils.getActiveEditor();
236         // Ensure that this isn't an invalid file (e.g. opening the SDK platform files
237         // with incorrect content binding could cause this)
238         assertTrue(!(newEditor instanceof ErrorEditorPart));
239 
240         IDocument document = null;
241         Point selection = null;
242 
243         if (newEditor instanceof AndroidXmlEditor) {
244             AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor;
245             document = xmlEditor.getStructuredSourceViewer().getDocument();
246             selection = xmlEditor.getStructuredSourceViewer().getSelectedRange();
247         } else if (newEditor instanceof XMLMultiPageEditorPart) {
248             XMLMultiPageEditorPart xmlEditor = (XMLMultiPageEditorPart) newEditor;
249             Field field = xmlEditor.getClass().getDeclaredField("fTextEditor");
250             field.setAccessible(true);
251             StructuredTextEditor ste = (StructuredTextEditor) field.get(newEditor);
252             if (ste == null) {
253                 Method method = xmlEditor.getClass().getMethod("getTextEditor", new Class[0]);
254                 ste = (StructuredTextEditor) method.invoke(newEditor, new Object[0]);
255             }
256             StructuredTextViewer textViewer = ste.getTextViewer();
257             document = textViewer.getDocument();
258             selection = textViewer.getSelectedRange();
259         } else if (newEditor instanceof WebBrowserEditor) {
260             WebBrowserEditor browser = (WebBrowserEditor) newEditor;
261             Field field = browser.getClass().getDeclaredField("initialURL");
262             field.setAccessible(true);
263             String initialUrl = (String) field.get(newEditor);
264             int index = initialUrl.indexOf("reference");
265             if (index != -1) {
266                 initialUrl = initialUrl.substring(index);
267             }
268             initialUrl = initialUrl.replace('\\', '/');
269             sb.append("\n\nAfter open, a browser is shown with this URL:\n");
270             sb.append("  ");
271             sb.append(initialUrl);
272             sb.append("\n");
273         } else if (newEditor instanceof JavaEditor) {
274             JavaEditor javaEditor = (JavaEditor) newEditor;
275             document = javaEditor.getDocumentProvider().getDocument(javaEditor.getEditorInput());
276             IRegion range = javaEditor.getHighlightRange();
277             selection = new Point(range.getOffset(), range.getLength());
278         } else {
279             fail("Unhandled editor type: " + newEditor.getClass().getName());
280             return;
281         }
282 
283         if (document != null && selection != null) {
284             int lineStart = document.getLineInformationOfOffset(selection.x).getOffset();
285             IRegion lineEndInfo = document.getLineInformationOfOffset(selection.x + selection.y);
286             int lineEnd = lineEndInfo.getOffset() + lineEndInfo.getLength();
287             String text = document.get(lineStart, lineEnd - lineStart);
288             int selectionStart = selection.x - lineStart;
289             int selectionEnd = selectionStart + selection.y;
290             if (selectionEnd > selectionStart) {
291                 // Selection range
292                 text = text.substring(0, selectionStart) + "[^" +
293                    text.substring(selectionStart, selectionEnd) + "]" +
294                    text.substring(selectionEnd);
295             } else {
296                 text = text.substring(0, selectionStart) + "^" +
297                     text.substring(selectionStart);
298             }
299             text = removeSessionData(text);
300 
301             sb.append("\n\nAfter open, the selected text is:\n");
302             sb.append(text);
303             sb.append("\n");
304         }
305 
306         assertEqualsGolden(basename, sb.toString(), "txt");
307     }
308 }
309