Home | History | Annotate | Download | only in editors
      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