diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java | 1893 |
1 files changed, 0 insertions, 1893 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java deleted file mode 100644 index 95cec47e6..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java +++ /dev/null @@ -1,1893 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.editors; - -import static com.android.SdkConstants.ANDROID_PKG; -import static com.android.SdkConstants.ANDROID_PREFIX; -import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.ANDROID_THEME_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_CLASS; -import static com.android.SdkConstants.ATTR_CONTEXT; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_ON_CLICK; -import static com.android.SdkConstants.CLASS_ACTIVITY; -import static com.android.SdkConstants.EXT_XML; -import static com.android.SdkConstants.FD_DOCS; -import static com.android.SdkConstants.FD_DOCS_REFERENCE; -import static com.android.SdkConstants.FN_RESOURCE_BASE; -import static com.android.SdkConstants.FN_RESOURCE_CLASS; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.PREFIX_RESOURCE_REF; -import static com.android.SdkConstants.PREFIX_THEME_REF; -import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; -import static com.android.SdkConstants.TAG_RESOURCES; -import static com.android.SdkConstants.TAG_STYLE; -import static com.android.SdkConstants.TOOLS_URI; -import static com.android.SdkConstants.VIEW; -import static com.android.SdkConstants.VIEW_FRAGMENT; -import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME; -import static com.android.xml.AndroidManifest.ATTRIBUTE_PACKAGE; -import static com.android.xml.AndroidManifest.NODE_ACTIVITY; -import static com.android.xml.AndroidManifest.NODE_SERVICE; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.resources.ResourceFile; -import com.android.ide.common.resources.ResourceFolder; -import com.android.ide.common.resources.ResourceRepository; -import com.android.ide.common.resources.ResourceUrl; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; -import com.android.ide.eclipse.adt.internal.sdk.ProjectState; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.ide.eclipse.adt.io.IFileWrapper; -import com.android.ide.eclipse.adt.io.IFolderWrapper; -import com.android.io.FileWrapper; -import com.android.io.IAbstractFile; -import com.android.io.IAbstractFolder; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; -import com.android.sdklib.IAndroidTarget; -import com.android.utils.Pair; - -import org.apache.xerces.parsers.DOMParser; -import org.apache.xerces.xni.Augmentations; -import org.apache.xerces.xni.NamespaceContext; -import org.apache.xerces.xni.QName; -import org.apache.xerces.xni.XMLAttributes; -import org.apache.xerces.xni.XMLLocator; -import org.apache.xerces.xni.XNIException; -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.Path; -import org.eclipse.jdt.core.Flags; -import org.eclipse.jdt.core.ICodeAssist; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IMethod; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.search.IJavaSearchConstants; -import org.eclipse.jdt.core.search.IJavaSearchScope; -import org.eclipse.jdt.core.search.SearchEngine; -import org.eclipse.jdt.core.search.SearchMatch; -import org.eclipse.jdt.core.search.SearchParticipant; -import org.eclipse.jdt.core.search.SearchPattern; -import org.eclipse.jdt.core.search.SearchRequestor; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; -import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; -import org.eclipse.jdt.internal.ui.text.JavaWordFinder; -import org.eclipse.jdt.ui.JavaUI; -import org.eclipse.jdt.ui.actions.SelectionDispatchAction; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IStatusLineManager; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.Region; -import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector; -import org.eclipse.jface.text.hyperlink.IHyperlink; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorReference; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.part.FileEditorInput; -import org.eclipse.ui.part.MultiPageEditorPart; -import org.eclipse.ui.texteditor.ITextEditor; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; -import org.eclipse.wst.sse.ui.StructuredTextEditor; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Class containing hyperlink resolvers for XML and Java files to jump to associated - * resources -- Java Activity and Service classes, XML layout and string declarations, - * image drawables, etc. - */ -@SuppressWarnings("restriction") -public class Hyperlinks { - private static final String CATEGORY = "category"; //$NON-NLS-1$ - private static final String ACTION = "action"; //$NON-NLS-1$ - private static final String PERMISSION = "permission"; //$NON-NLS-1$ - private static final String USES_PERMISSION = "uses-permission"; //$NON-NLS-1$ - private static final String CATEGORY_PKG_PREFIX = "android.intent.category."; //$NON-NLS-1$ - private static final String ACTION_PKG_PREFIX = "android.intent.action."; //$NON-NLS-1$ - private static final String PERMISSION_PKG_PREFIX = "android.permission."; //$NON-NLS-1$ - - private Hyperlinks() { - // Not instantiatable. This is a container class containing shared code - // for the various inner classes that are actual hyperlink resolvers. - } - - /** - * Returns whether a string represents a valid fully qualified name for a view class. - * Does not check for existence. - */ - @VisibleForTesting - static boolean isViewClassName(String name) { - int length = name.length(); - if (length < 2 || name.indexOf('.') == -1) { - return false; - } - - boolean lastWasDot = true; - for (int i = 0; i < length; i++) { - char c = name.charAt(i); - if (lastWasDot) { - if (!Character.isJavaIdentifierStart(c)) { - return false; - } - lastWasDot = false; - } else { - if (c == '.') { - lastWasDot = true; - } else if (!Character.isJavaIdentifierPart(c)) { - return false; - } - } - } - - return !lastWasDot; - } - - /** Determines whether the given attribute <b>name</b> is linkable */ - private static boolean isAttributeNameLink(XmlContext context) { - // We could potentially allow you to link to builtin Android properties: - // ANDROID_URI.equals(attribute.getNamespaceURI()) - // and then jump into the res/values/attrs.xml document that is available - // in the SDK data directory (path found via - // IAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES)). - // - // For now, we're not doing that. - // - // We could also allow to jump into custom attributes in custom view - // classes. Not yet implemented. - - return false; - } - - /** Determines whether the given attribute <b>value</b> is linkable */ - private static boolean isAttributeValueLink(XmlContext context) { - // Everything else here is attribute based - Attr attribute = context.getAttribute(); - if (attribute == null) { - return false; - } - - if (isClassAttribute(context) || isOnClickAttribute(context) - || isManifestName(context) || isStyleAttribute(context)) { - return true; - } - - String value = attribute.getValue(); - if (value.startsWith(NEW_ID_PREFIX)) { - // It's a value -declaration-, nowhere else to jump - // (though we could consider jumping to the R-file; would that - // be helpful?) - return !ATTR_ID.equals(attribute.getLocalName()); - } - - ResourceUrl resource = ResourceUrl.parse(value); - if (resource != null) { - return true; - } - - return false; - } - - /** Determines whether the given element <b>name</b> is linkable */ - private static boolean isElementNameLink(XmlContext context) { - if (isClassElement(context)) { - return true; - } - - return false; - } - - /** - * Returns true if this node/attribute pair corresponds to a manifest reference to - * an activity. - */ - private static boolean isActivity(XmlContext context) { - // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump - // to it - Attr attribute = context.getAttribute(); - String tagName = context.getElement().getTagName(); - if (NODE_ACTIVITY.equals(tagName) && ATTRIBUTE_NAME.equals(attribute.getLocalName()) - && ANDROID_URI.equals(attribute.getNamespaceURI())) { - return true; - } - - return false; - } - - /** - * Returns true if this node/attribute pair corresponds to a manifest android:name reference - */ - private static boolean isManifestName(XmlContext context) { - Attr attribute = context.getAttribute(); - if (attribute != null && ATTRIBUTE_NAME.equals(attribute.getLocalName()) - && ANDROID_URI.equals(attribute.getNamespaceURI())) { - if (getEditor() instanceof ManifestEditor) { - return true; - } - } - - return false; - } - - /** - * Opens the declaration corresponding to an android:name reference in the - * AndroidManifest.xml file - */ - private static boolean openManifestName(IProject project, XmlContext context) { - if (isActivity(context)) { - String fqcn = getActivityClassFqcn(context); - return AdtPlugin.openJavaClass(project, fqcn); - } else if (isService(context)) { - String fqcn = getServiceClassFqcn(context); - return AdtPlugin.openJavaClass(project, fqcn); - } else if (isBuiltinPermission(context)) { - String permission = context.getAttribute().getValue(); - // Mutate something like android.permission.ACCESS_CHECKIN_PROPERTIES - // into relative doc url android/Manifest.permission.html#ACCESS_CHECKIN_PROPERTIES - assert permission.startsWith(PERMISSION_PKG_PREFIX); - String relative = "android/Manifest.permission.html#" //$NON-NLS-1$ - + permission.substring(PERMISSION_PKG_PREFIX.length()); - - URL url = getDocUrl(relative); - if (url != null) { - AdtPlugin.openUrl(url); - return true; - } else { - return false; - } - } else if (isBuiltinIntent(context)) { - String intent = context.getAttribute().getValue(); - // Mutate something like android.intent.action.MAIN into - // into relative doc url android/content/Intent.html#ACTION_MAIN - String relative; - if (intent.startsWith(ACTION_PKG_PREFIX)) { - relative = "android/content/Intent.html#ACTION_" //$NON-NLS-1$ - + intent.substring(ACTION_PKG_PREFIX.length()); - } else if (intent.startsWith(CATEGORY_PKG_PREFIX)) { - relative = "android/content/Intent.html#CATEGORY_" //$NON-NLS-1$ - + intent.substring(CATEGORY_PKG_PREFIX.length()); - } else { - return false; - } - URL url = getDocUrl(relative); - if (url != null) { - AdtPlugin.openUrl(url); - return true; - } else { - return false; - } - } - - return false; - } - - /** Returns true if this represents a style attribute */ - private static boolean isStyleAttribute(XmlContext context) { - String tag = context.getElement().getTagName(); - return TAG_STYLE.equals(tag); - } - - /** - * Returns true if this represents a {@code <view class="foo.bar.Baz">} class - * attribute, or a {@code <fragment android:name="foo.bar.Baz">} class attribute - */ - private static boolean isClassAttribute(XmlContext context) { - Attr attribute = context.getAttribute(); - if (attribute == null) { - return false; - } - String tag = context.getElement().getTagName(); - String attributeName = attribute.getLocalName(); - return ATTR_CLASS.equals(attributeName) && (VIEW.equals(tag) || VIEW_FRAGMENT.equals(tag)) - || ATTR_NAME.equals(attributeName) && VIEW_FRAGMENT.equals(tag) - || (ATTR_CONTEXT.equals(attributeName) - && TOOLS_URI.equals(attribute.getNamespaceURI())); - } - - /** Returns true if this represents an onClick attribute specifying a method handler */ - private static boolean isOnClickAttribute(XmlContext context) { - Attr attribute = context.getAttribute(); - if (attribute == null) { - return false; - } - return ATTR_ON_CLICK.equals(attribute.getLocalName()) && attribute.getValue().length() > 0; - } - - /** Returns true if this represents a {@code <foo.bar.Baz>} custom view class element */ - private static boolean isClassElement(XmlContext context) { - if (context.getAttribute() != null) { - // Don't match the outer element if the user is hovering over a specific attribute - return false; - } - // If the element looks like a fully qualified class name (e.g. it's a custom view - // element) offer it as a link - String tag = context.getElement().getTagName(); - return isViewClassName(tag); - } - - /** Returns the FQCN for a class declaration at the given context */ - private static String getClassFqcn(XmlContext context) { - if (isClassAttribute(context)) { - String value = context.getAttribute().getValue(); - if (!value.isEmpty() && value.charAt(0) == '.') { - IProject project = getProject(); - if (project != null) { - ManifestInfo info = ManifestInfo.get(project); - String pkg = info.getPackage(); - if (pkg != null) { - value = pkg + value; - } - } - } - return value; - } else if (isClassElement(context)) { - return context.getElement().getTagName(); - } - - return null; - } - - /** - * Returns true if this node/attribute pair corresponds to a manifest reference to - * an service. - */ - private static boolean isService(XmlContext context) { - Attr attribute = context.getAttribute(); - Element node = context.getElement(); - - // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it - String nodeName = node.getNodeName(); - if (NODE_SERVICE.equals(nodeName) && ATTRIBUTE_NAME.equals(attribute.getLocalName()) - && ANDROID_URI.equals(attribute.getNamespaceURI())) { - return true; - } - - return false; - } - - /** - * Returns a URL pointing to the Android reference documentation, either installed - * locally or the one on android.com - * - * @param relative a relative url to append to the root url - * @return a URL pointing to the documentation - */ - private static URL getDocUrl(String relative) { - // First try to find locally installed documentation - File sdkLocation = new File(Sdk.getCurrent().getSdkOsLocation()); - File docs = new File(sdkLocation, FD_DOCS + File.separator + FD_DOCS_REFERENCE); - try { - if (docs.exists()) { - String s = docs.toURI().toURL().toExternalForm(); - if (!s.endsWith("/")) { //$NON-NLS-1$ - s += "/"; //$NON-NLS-1$ - } - return new URL(s + relative); - } - // If not, fallback to the online documentation - return new URL("http://developer.android.com/reference/" + relative); //$NON-NLS-1$ - } catch (MalformedURLException e) { - AdtPlugin.log(e, "Can't create URL for %1$s", docs); - return null; - } - } - - /** Returns true if the context is pointing to a permission name reference */ - private static boolean isBuiltinPermission(XmlContext context) { - Attr attribute = context.getAttribute(); - Element node = context.getElement(); - - // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it - String nodeName = node.getNodeName(); - if ((USES_PERMISSION.equals(nodeName) || PERMISSION.equals(nodeName)) - && ATTRIBUTE_NAME.equals(attribute.getLocalName()) - && ANDROID_URI.equals(attribute.getNamespaceURI())) { - String value = attribute.getValue(); - if (value.startsWith(PERMISSION_PKG_PREFIX)) { - return true; - } - } - - return false; - } - - /** Returns true if the context is pointing to an intent reference */ - private static boolean isBuiltinIntent(XmlContext context) { - Attr attribute = context.getAttribute(); - Element node = context.getElement(); - - // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it - String nodeName = node.getNodeName(); - if ((ACTION.equals(nodeName) || CATEGORY.equals(nodeName)) - && ATTRIBUTE_NAME.equals(attribute.getLocalName()) - && ANDROID_URI.equals(attribute.getNamespaceURI())) { - String value = attribute.getValue(); - if (value.startsWith(ACTION_PKG_PREFIX) || value.startsWith(CATEGORY_PKG_PREFIX)) { - return true; - } - } - - return false; - } - - - /** - * Returns the fully qualified class name of an activity referenced by the given - * AndroidManifest.xml node - */ - private static String getActivityClassFqcn(XmlContext context) { - Attr attribute = context.getAttribute(); - Element node = context.getElement(); - StringBuilder sb = new StringBuilder(); - Element root = node.getOwnerDocument().getDocumentElement(); - String pkg = root.getAttribute(ATTRIBUTE_PACKAGE); - String className = attribute.getValue(); - if (className.startsWith(".")) { //$NON-NLS-1$ - sb.append(pkg); - } else if (className.indexOf('.') == -1) { - // According to the <activity> manifest element documentation, this is not - // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) - // but it appears in manifest files and appears to be supported by the runtime - // so handle this in code as well: - sb.append(pkg); - sb.append('.'); - } // else: the class name is already a fully qualified class name - sb.append(className); - return sb.toString(); - } - - /** - * Returns the fully qualified class name of a service referenced by the given - * AndroidManifest.xml node - */ - private static String getServiceClassFqcn(XmlContext context) { - // Same logic - return getActivityClassFqcn(context); - } - - /** - * Returns the XML tag containing an element description for value items of the given - * resource type - * - * @param type the resource type to query the XML tag name for - * @return the tag name used for value declarations in XML of resources of the given - * type - */ - public static String getTagName(ResourceType type) { - if (type == ResourceType.ID) { - // Ids are recorded in <item> tags instead of <id> tags - return SdkConstants.TAG_ITEM; - } - - return type.getName(); - } - - /** - * Computes the actual exact location to jump to for a given XML context. - * - * @param context the XML context to be opened - * @return true if the request was handled successfully - */ - private static boolean open(XmlContext context) { - IProject project = getProject(); - if (project == null) { - return false; - } - - if (isManifestName(context)) { - return openManifestName(project, context); - } else if (isClassElement(context) || isClassAttribute(context)) { - return AdtPlugin.openJavaClass(project, getClassFqcn(context)); - } else if (isOnClickAttribute(context)) { - return openOnClickMethod(project, context.getAttribute().getValue()); - } else { - return false; - } - } - - /** Opens a path (which may not be in the workspace) */ - private static void openPath(IPath filePath, IRegion region, int offset) { - IEditorPart sourceEditor = getEditor(); - IWorkbenchPage page = sourceEditor.getEditorSite().getPage(); - - IFile file = AdtUtils.pathToIFile(filePath); - if (file != null && file.exists()) { - try { - AdtPlugin.openFile(file, region); - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$ - } - } else { - // It's not a path in the workspace; look externally - // (this is probably an @android: path) - if (filePath.isAbsolute()) { - IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath); - if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { - try { - IEditorPart target = IDE.openEditorOnFileStore(page, fileStore); - if (target instanceof MultiPageEditorPart) { - MultiPageEditorPart part = (MultiPageEditorPart) target; - IEditorPart[] editors = part.findEditors(target.getEditorInput()); - if (editors != null) { - for (IEditorPart editor : editors) { - if (editor instanceof StructuredTextEditor) { - StructuredTextEditor ste = (StructuredTextEditor) editor; - part.setActiveEditor(editor); - ste.selectAndReveal(offset, 0); - break; - } - } - } - } - - return; - } catch (PartInitException ex) { - AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$ - } - } - } - } - - // Failed: display message to the user - displayError(String.format("Could not find resource %1$s", filePath)); - } - - private static void displayError(String message) { - // Failed: display message to the user - IEditorSite editorSite = getEditor().getEditorSite(); - IStatusLineManager status = editorSite.getActionBars().getStatusLineManager(); - status.setErrorMessage(message); - } - - /** - * Opens a Java method referenced by the given on click attribute method name - * - * @param project the project containing the click handler - * @param method the method name of the on click handler - * @return true if the method was opened, false otherwise - */ - public static boolean openOnClickMethod(IProject project, String method) { - // Search for the method in the Java index, filtering by the required click handler - // method signature (public and has a single View parameter), and narrowing the scope - // first to Activity classes, then to the whole workspace. - final AtomicBoolean success = new AtomicBoolean(false); - SearchRequestor requestor = new SearchRequestor() { - @Override - public void acceptSearchMatch(SearchMatch match) throws CoreException { - Object element = match.getElement(); - if (element instanceof IMethod) { - IMethod methodElement = (IMethod) element; - String[] parameterTypes = methodElement.getParameterTypes(); - if (parameterTypes != null - && parameterTypes.length == 1 - && ("Qandroid.view.View;".equals(parameterTypes[0]) //$NON-NLS-1$ - || "QView;".equals(parameterTypes[0]))) { //$NON-NLS-1$ - // Check that it's public - if (Flags.isPublic(methodElement.getFlags())) { - JavaUI.openInEditor(methodElement); - success.getAndSet(true); - } - } - } - } - }; - try { - IJavaSearchScope scope = null; - IType activityType = null; - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - if (javaProject != null) { - activityType = javaProject.findType(CLASS_ACTIVITY); - if (activityType != null) { - scope = SearchEngine.createHierarchyScope(activityType); - } - } - if (scope == null) { - scope = SearchEngine.createWorkspaceScope(); - } - - SearchParticipant[] participants = new SearchParticipant[] { - SearchEngine.getDefaultSearchParticipant() - }; - int matchRule = SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE; - SearchPattern pattern = SearchPattern.createPattern("*." + method, - IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, matchRule); - SearchEngine engine = new SearchEngine(); - engine.search(pattern, participants, scope, requestor, new NullProgressMonitor()); - - boolean ok = success.get(); - if (!ok && activityType != null) { - // TODO: Create a project+dependencies scope and search only that scope - - // Try searching again with a complete workspace scope this time - scope = SearchEngine.createWorkspaceScope(); - engine.search(pattern, participants, scope, requestor, new NullProgressMonitor()); - - // TODO: There could be more than one match; add code to consider them all - // and pick the most likely candidate and open only that one. - - ok = success.get(); - } - return ok; - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - return false; - } - - /** - * Returns the current configuration, if the associated UI editor has been initialized - * and has an associated configuration - * - * @return the configuration for this file, or null - */ - private static FolderConfiguration getConfiguration() { - IEditorPart editor = getEditor(); - if (editor != null) { - LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor); - GraphicalEditorPart graphicalEditor = - delegate == null ? null : delegate.getGraphicalEditor(); - - if (graphicalEditor != null) { - return graphicalEditor.getConfiguration(); - } else { - // TODO: Could try a few more things to get the configuration: - // (1) try to look at the file.getPersistentProperty(NAME_CONFIG_STATE) - // which will return previously saved state. This isn't necessary today - // since no editors seem to be lazily initialized. - // (2) attempt to use the configuration from any of the other open - // files, especially files in the same directory as this one. - } - - // Create a configuration from the current file - IProject project = null; - IEditorInput editorInput = editor.getEditorInput(); - if (editorInput instanceof FileEditorInput) { - IFile file = ((FileEditorInput) editorInput).getFile(); - project = file.getProject(); - ProjectResources pr = ResourceManager.getInstance().getProjectResources(project); - IContainer parent = file.getParent(); - if (parent instanceof IFolder) { - ResourceFolder resFolder = pr.getResourceFolder((IFolder) parent); - if (resFolder != null) { - return resFolder.getConfiguration(); - } - } - } - - // Might be editing a Java file, where there is no configuration context. - // Instead look at surrounding files in the workspace and obtain one valid - // configuration. - for (IEditorReference reference : editor.getSite().getPage().getEditorReferences()) { - IEditorPart part = reference.getEditor(false /*restore*/); - - LayoutEditorDelegate refDelegate = LayoutEditorDelegate.fromEditor(part); - if (refDelegate != null) { - IProject refProject = refDelegate.getEditor().getProject(); - if (project == null || project == refProject) { - GraphicalEditorPart refGraphicalEditor = refDelegate.getGraphicalEditor(); - if (refGraphicalEditor != null) { - return refGraphicalEditor.getConfiguration(); - } - } - } - } - } - - return null; - } - - /** Returns the {@link IAndroidTarget} to be used for looking up system resources */ - private static IAndroidTarget getTarget(IProject project) { - IEditorPart editor = getEditor(); - LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor); - if (delegate != null) { - GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor(); - if (graphicalEditor != null) { - return graphicalEditor.getRenderingTarget(); - } - } - - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk == null) { - return null; - } - - return currentSdk.getTarget(project); - } - - /** Return either the project resources or the framework resources (or null) */ - private static ResourceRepository getResources(IProject project, boolean framework) { - if (framework) { - IAndroidTarget target = getTarget(project); - - if (target == null && project == null && framework) { - // No current project: probably jumped into some of the framework XML resource - // files and attempting to jump around. Attempt to figure out which target - // we're dealing with and continue looking within the same framework. - IEditorPart editor = getEditor(); - Sdk sdk = Sdk.getCurrent(); - if (sdk != null && editor instanceof AndroidXmlEditor) { - AndroidTargetData data = ((AndroidXmlEditor) editor).getTargetData(); - if (data != null) { - return data.getFrameworkResources(); - } - } - } - - if (target == null) { - return null; - } - AndroidTargetData data = Sdk.getCurrent().getTargetData(target); - if (data == null) { - return null; - } - return data.getFrameworkResources(); - } else { - return ResourceManager.getInstance().getProjectResources(project); - } - } - - /** - * Finds a definition of an id attribute in layouts. (Ids can also be defined as - * resources; use {@link #findValueInXml} or {@link #findValueInDocument} to locate it there.) - */ - private static Pair<IFile, IRegion> findIdDefinition(IProject project, String id) { - // FIRST look in the same file as the originating request, that's where you usually - // want to jump - IFile self = AdtUtils.getActiveFile(); - if (self != null && EXT_XML.equals(self.getFileExtension())) { - Pair<IFile, IRegion> target = findIdInXml(id, self); - if (target != null) { - return target; - } - } - - // Look in the configuration folder: Search compatible configurations - ResourceRepository resources = getResources(project, false /* isFramework */); - FolderConfiguration configuration = getConfiguration(); - if (configuration != null) { // Not the case when searching from Java files for example - List<ResourceFolder> folders = resources.getFolders(ResourceFolderType.LAYOUT); - if (folders != null) { - for (ResourceFolder folder : folders) { - if (folder.getConfiguration().isMatchFor(configuration)) { - IAbstractFolder wrapper = folder.getFolder(); - if (wrapper instanceof IFolderWrapper) { - IFolder iFolder = ((IFolderWrapper) wrapper).getIFolder(); - Pair<IFile, IRegion> target = findIdInFolder(iFolder, id); - if (target != null) { - return target; - } - } - } - } - return null; - } - } - - // Ugh. Search ALL layout files in the project! - List<ResourceFolder> folders = resources.getFolders(ResourceFolderType.LAYOUT); - if (folders != null) { - for (ResourceFolder folder : folders) { - IAbstractFolder wrapper = folder.getFolder(); - if (wrapper instanceof IFolderWrapper) { - IFolder iFolder = ((IFolderWrapper) wrapper).getIFolder(); - Pair<IFile, IRegion> target = findIdInFolder(iFolder, id); - if (target != null) { - return target; - } - } - } - } - - return null; - } - - /** - * Finds a definition of an id attribute in a particular layout folder. - */ - private static Pair<IFile, IRegion> findIdInFolder(IContainer f, String id) { - try { - // Check XML files in values/ - for (IResource resource : f.members()) { - if (resource.exists() && !resource.isDerived() && resource instanceof IFile) { - IFile file = (IFile) resource; - // Must have an XML extension - if (EXT_XML.equals(file.getFileExtension())) { - Pair<IFile, IRegion> target = findIdInXml(id, file); - if (target != null) { - return target; - } - } - } - } - } catch (CoreException e) { - AdtPlugin.log(e, ""); //$NON-NLS-1$ - } - - return null; - } - - /** Parses the given file and locates a definition of the given resource */ - private static Pair<IFile, IRegion> findValueInXml( - ResourceType type, String name, IFile file) { - IStructuredModel model = null; - try { - model = StructuredModelManager.getModelManager().getExistingModelForRead(file); - if (model == null) { - // There is no open or cached model for the file; see if the file looks - // like it's interesting (content contains the String name we are looking for) - if (AdtPlugin.fileContains(file, name)) { - // Yes, so parse content - model = StructuredModelManager.getModelManager().getModelForRead(file); - } - } - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - Document document = domModel.getDocument(); - return findValueInDocument(type, name, file, document); - } - } catch (IOException e) { - AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$ - } catch (CoreException e) { - AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$ - } finally { - if (model != null) { - model.releaseFromRead(); - } - } - - return null; - } - - /** Looks within an XML DOM document for the given resource name and returns it */ - private static Pair<IFile, IRegion> findValueInDocument( - ResourceType type, String name, IFile file, Document document) { - String targetTag = getTagName(type); - Element root = document.getDocumentElement(); - if (root.getTagName().equals(TAG_RESOURCES)) { - NodeList topLevel = root.getChildNodes(); - Pair<IFile, IRegion> value = findValueInChildren(name, file, targetTag, topLevel); - if (value == null && type == ResourceType.ATTR) { - for (int i = 0, n = topLevel.getLength(); i < n; i++) { - Node child = topLevel.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element)child; - String tagName = element.getTagName(); - if (tagName.equals("declare-styleable")) { - NodeList children = element.getChildNodes(); - value = findValueInChildren(name, file, targetTag, children); - if (value != null) { - return value; - } - } - } - } - } - - return value; - } - - return null; - } - - private static Pair<IFile, IRegion> findValueInChildren(String name, IFile file, - String targetTag, NodeList children) { - for (int i = 0, n = children.getLength(); i < n; i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element)child; - String tagName = element.getTagName(); - if (tagName.equals(targetTag)) { - String elementName = element.getAttribute(ATTR_NAME); - if (elementName.equals(name)) { - IRegion region = null; - if (element instanceof IndexedRegion) { - IndexedRegion r = (IndexedRegion) element; - // IndexedRegion.getLength() returns bogus values - int length = r.getEndOffset() - r.getStartOffset(); - region = new Region(r.getStartOffset(), length); - } - - return Pair.of(file, region); - } - } - } - } - - return null; - } - - /** Parses the given file and locates a definition of the given resource */ - private static Pair<IFile, IRegion> findIdInXml(String id, IFile file) { - IStructuredModel model = null; - try { - model = StructuredModelManager.getModelManager().getExistingModelForRead(file); - if (model == null) { - // There is no open or cached model for the file; see if the file looks - // like it's interesting (content contains the String name we are looking for) - if (AdtPlugin.fileContains(file, id)) { - // Yes, so parse content - model = StructuredModelManager.getModelManager().getModelForRead(file); - } - } - if (model instanceof IDOMModel) { - IDOMModel domModel = (IDOMModel) model; - Document document = domModel.getDocument(); - return findIdInDocument(id, file, document); - } - } catch (IOException e) { - AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$ - } catch (CoreException e) { - AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$ - } finally { - if (model != null) { - model.releaseFromRead(); - } - } - - return null; - } - - /** Looks within an XML DOM document for the given resource name and returns it */ - private static Pair<IFile, IRegion> findIdInDocument(String id, IFile file, - Document document) { - String targetAttribute = NEW_ID_PREFIX + id; - Element root = document.getDocumentElement(); - Pair<IFile, IRegion> result = findIdInElement(root, file, targetAttribute, - true /*requireId*/); - if (result == null) { - result = findIdInElement(root, file, targetAttribute, false /*requireId*/); - } - return result; - } - - private static Pair<IFile, IRegion> findIdInElement( - Element root, IFile file, String targetAttribute, boolean requireIdAttribute) { - NamedNodeMap attributes = root.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node item = attributes.item(i); - if (item instanceof Attr) { - Attr attribute = (Attr) item; - if (requireIdAttribute && !ATTR_ID.equals(attribute.getLocalName())) { - continue; - } - String value = attribute.getValue(); - if (value.equals(targetAttribute)) { - // Select the element -containing- the id rather than the attribute itself - IRegion region = null; - Node element = attribute.getOwnerElement(); - //if (attribute instanceof IndexedRegion) { - if (element instanceof IndexedRegion) { - IndexedRegion r = (IndexedRegion) element; - int length = r.getEndOffset() - r.getStartOffset(); - region = new Region(r.getStartOffset(), length); - } - - return Pair.of(file, region); - } - } - } - - NodeList children = root.getChildNodes(); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element)child; - Pair<IFile, IRegion> result = findIdInElement(element, file, targetAttribute, - requireIdAttribute); - if (result != null) { - return result; - } - } - } - - return null; - } - - /** Parses the given file and locates a definition of the given resource */ - private static Pair<File, Integer> findValueInXml(ResourceType type, String name, File file) { - // We can't use the StructureModelManager on files outside projects - // There is no open or cached model for the file; see if the file looks - // like it's interesting (content contains the String name we are looking for) - if (AdtPlugin.fileContains(file, name)) { - try { - InputSource is = new InputSource(new FileInputStream(file)); - OffsetTrackingParser parser = new OffsetTrackingParser(); - parser.parse(is); - Document document = parser.getDocument(); - - return findValueInDocument(type, name, file, parser, document); - } catch (SAXException e) { - // pass -- ignore files we can't parse - } catch (IOException e) { - // pass -- ignore files we can't parse - } - } - - return null; - } - - /** Looks within an XML DOM document for the given resource name and returns it */ - private static Pair<File, Integer> findValueInDocument(ResourceType type, String name, - File file, OffsetTrackingParser parser, Document document) { - String targetTag = type.getName(); - if (type == ResourceType.ID) { - // Ids are recorded in <item> tags instead of <id> tags - targetTag = "item"; //$NON-NLS-1$ - } - - Pair<File, Integer> result = findTag(name, file, parser, document, targetTag); - if (result == null && type == ResourceType.ATTR) { - // Attributes seem to be defined in <public> tags - targetTag = "public"; //$NON-NLS-1$ - result = findTag(name, file, parser, document, targetTag); - } - return result; - } - - private static Pair<File, Integer> findTag(String name, File file, OffsetTrackingParser parser, - Document document, String targetTag) { - NodeList children = document.getElementsByTagName(targetTag); - for (int i = 0, n = children.getLength(); i < n; i++) { - Node child = children.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) child; - if (element.getTagName().equals(targetTag)) { - String elementName = element.getAttribute(ATTR_NAME); - if (elementName.equals(name)) { - return Pair.of(file, parser.getOffset(element)); - } - } - } - } - - return null; - } - - private static IHyperlink[] getStyleLinks(XmlContext context, IRegion range, String url) { - Attr attribute = context.getAttribute(); - if (attribute != null) { - // Split up theme resource urls to the nearest dot forwards, such that you - // can point to "Theme.Light" by placing the caret anywhere after the dot, - // and point to just "Theme" by pointing before it. - int caret = context.getInnerRegionCaretOffset(); - String value = attribute.getValue(); - int index = value.indexOf('.', caret); - if (index != -1) { - url = url.substring(0, index); - range = new Region(range.getOffset(), - range.getLength() - (value.length() - index)); - } - } - - ResourceUrl resource = ResourceUrl.parse(url); - if (resource == null) { - String androidStyle = ANDROID_STYLE_RESOURCE_PREFIX; - if (url.startsWith(ANDROID_PREFIX)) { - url = androidStyle + url.substring(ANDROID_PREFIX.length()); - } else if (url.startsWith(ANDROID_THEME_PREFIX)) { - url = androidStyle + url.substring(ANDROID_THEME_PREFIX.length()); - } else if (url.startsWith(ANDROID_PKG + ':')) { - url = androidStyle + url.substring(ANDROID_PKG.length() + 1); - } else { - url = STYLE_RESOURCE_PREFIX + url; - } - } - return getResourceLinks(range, url); - } - - private static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url) { - IProject project = Hyperlinks.getProject(); - FolderConfiguration configuration = getConfiguration(); - return getResourceLinks(range, url, project, configuration); - } - - /** - * Computes hyperlinks to resource definitions for resource urls (e.g. - * {@code @android:string/ok} or {@code @layout/foo}. May create multiple links. - * @param range TBD - * @param url the resource url - * @param project the relevant project - * @param configuration the applicable configuration - * @return an array of hyperlinks, or null - */ - @Nullable - public static IHyperlink[] getResourceLinks(@Nullable IRegion range, @NonNull String url, - @Nullable IProject project, @Nullable FolderConfiguration configuration) { - List<IHyperlink> links = new ArrayList<IHyperlink>(); - - ResourceUrl resource = ResourceUrl.parse(url); - if (resource == null) { - return null; - } - ResourceType type = resource.type; - String name = resource.name; - boolean isFramework = resource.framework; - if (project == null) { - // Local reference *within* a framework - isFramework = true; - } - - ResourceRepository resources = getResources(project, isFramework); - if (resources == null) { - return null; - } - List<ResourceFile> sourceFiles = resources.getSourceFiles(type, name, - null /*configuration*/); - if (sourceFiles == null) { - ProjectState projectState = Sdk.getProjectState(project); - if (projectState != null) { - List<IProject> libraries = projectState.getFullLibraryProjects(); - if (libraries != null && !libraries.isEmpty()) { - for (IProject library : libraries) { - resources = ResourceManager.getInstance().getProjectResources(library); - sourceFiles = resources.getSourceFiles(type, name, null /*configuration*/); - if (sourceFiles != null && !sourceFiles.isEmpty()) { - break; - } - } - } - } - } - - ResourceFile best = null; - if (configuration != null && sourceFiles != null && sourceFiles.size() > 0) { - List<ResourceFile> bestFiles = resources.getSourceFiles(type, name, configuration); - if (bestFiles != null && bestFiles.size() > 0) { - best = bestFiles.get(0); - } - } - if (sourceFiles != null) { - List<ResourceFile> matches = new ArrayList<ResourceFile>(); - for (ResourceFile resourceFile : sourceFiles) { - matches.add(resourceFile); - } - - if (matches.size() > 0) { - final ResourceFile fBest = best; - Collections.sort(matches, new Comparator<ResourceFile>() { - @Override - public int compare(ResourceFile rf1, ResourceFile rf2) { - // Sort best item to the front - if (rf1 == fBest) { - return -1; - } else if (rf2 == fBest) { - return 1; - } else { - return getFileName(rf1).compareTo(getFileName(rf2)); - } - } - }); - - // Is this something found in a values/ folder? - boolean valueResource = ResourceHelper.isValueBasedResourceType(type); - - for (ResourceFile file : matches) { - String folderName = file.getFolder().getFolder().getName(); - String label = String.format("Open Declaration in %1$s/%2$s", - folderName, getFileName(file)); - - // Only search for resource type within the file if it's an - // XML file and it is a value resource - ResourceLink link = new ResourceLink(label, range, file, - valueResource ? type : null, name); - links.add(link); - } - } - } - - // Id's are handled specially because they are typically defined - // inline (though they -can- be defined in the values folder above as - // well, in which case we will prefer that definition) - if (!isFramework && type == ResourceType.ID && links.size() == 0) { - // Must compute these lazily... - links.add(new ResourceLink("Open XML Declaration", range, null, type, name)); - } - - if (links.size() > 0) { - return links.toArray(new IHyperlink[links.size()]); - } else { - return null; - } - } - - private static String getFileName(ResourceFile file) { - return file.getFile().getName(); - } - - /** Detector for finding Android references in XML files */ - public static class XmlResolver extends AbstractHyperlinkDetector { - - @Override - public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, - boolean canShowMultipleHyperlinks) { - - if (region == null || textViewer == null) { - return null; - } - - IDocument document = textViewer.getDocument(); - - XmlContext context = XmlContext.find(document, region.getOffset()); - if (context == null) { - return null; - } - - IRegion range = context.getInnerRange(document); - boolean isLinkable = false; - String type = context.getInnerRegion().getType(); - if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) { - if (isAttributeValueLink(context)) { - isLinkable = true; - // Strip out quotes - range = new Region(range.getOffset() + 1, range.getLength() - 2); - - Attr attribute = context.getAttribute(); - if (isStyleAttribute(context)) { - return getStyleLinks(context, range, attribute.getValue()); - } - if (attribute != null - && (attribute.getValue().startsWith(PREFIX_RESOURCE_REF) - || attribute.getValue().startsWith(PREFIX_THEME_REF))) { - // Instantly create links for resources since we can use the existing - // resolved maps for this and offer multiple choices for the user - String url = attribute.getValue(); - return getResourceLinks(range, url); - } - } - } else if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) { - if (isAttributeNameLink(context)) { - isLinkable = true; - } - } else if (type == DOMRegionContext.XML_TAG_NAME) { - if (isElementNameLink(context)) { - isLinkable = true; - } - } else if (type == DOMRegionContext.XML_CONTENT) { - Node parentNode = context.getNode().getParentNode(); - if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) { - // Try to complete resources defined inline as text, such as - // style definitions - ITextRegion outer = context.getElementRegion(); - ITextRegion inner = context.getInnerRegion(); - int innerOffset = outer.getStart() + inner.getStart(); - int caretOffset = innerOffset + context.getInnerRegionCaretOffset(); - try { - IRegion lineInfo = document.getLineInformationOfOffset(caretOffset); - int lineStart = lineInfo.getOffset(); - int lineEnd = Math.min(lineStart + lineInfo.getLength(), - innerOffset + inner.getLength()); - - // Compute the resource URL - int urlStart = -1; - int offset = caretOffset; - while (offset > lineStart) { - char c = document.getChar(offset); - if (c == '@' || c == '?') { - urlStart = offset; - break; - } else if (!isValidResourceUrlChar(c)) { - break; - } - offset--; - } - - if (urlStart != -1) { - offset = caretOffset; - while (offset < lineEnd) { - if (!isValidResourceUrlChar(document.getChar(offset))) { - break; - } - offset++; - } - - int length = offset - urlStart; - String url = document.get(urlStart, length); - range = new Region(urlStart, length); - return getResourceLinks(range, url); - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - } - } - - if (isLinkable) { - IHyperlink hyperlink = new DeferredResolutionLink(context, range); - if (hyperlink != null) { - return new IHyperlink[] { - hyperlink - }; - } - } - - return null; - } - } - - private static boolean isValidResourceUrlChar(char c) { - return Character.isJavaIdentifierPart(c) || c == ':' || c == '/' || c == '.' || c == '+'; - - } - - /** Detector for finding Android references in Java files */ - public static class JavaResolver extends AbstractHyperlinkDetector { - - @Override - public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, - boolean canShowMultipleHyperlinks) { - // Most of this is identical to the builtin JavaElementHyperlinkDetector -- - // everything down to the Android R filtering below - - ITextEditor textEditor = (ITextEditor) getAdapter(ITextEditor.class); - if (region == null || !(textEditor instanceof JavaEditor)) - return null; - - IAction openAction = textEditor.getAction("OpenEditor"); //$NON-NLS-1$ - if (!(openAction instanceof SelectionDispatchAction)) - return null; - - int offset = region.getOffset(); - - IJavaElement input = EditorUtility.getEditorInputJavaElement(textEditor, false); - if (input == null) - return null; - - try { - IDocument document = textEditor.getDocumentProvider().getDocument( - textEditor.getEditorInput()); - IRegion wordRegion = JavaWordFinder.findWord(document, offset); - if (wordRegion == null || wordRegion.getLength() == 0) - return null; - - IJavaElement[] elements = null; - elements = ((ICodeAssist) input).codeSelect(wordRegion.getOffset(), wordRegion - .getLength()); - - // Specific Android R class filtering: - if (elements.length > 0) { - IJavaElement element = elements[0]; - if (element.getElementType() == IJavaElement.FIELD) { - IJavaElement unit = element.getAncestor(IJavaElement.COMPILATION_UNIT); - if (unit == null) { - // Probably in a binary; see if this is an android.R resource - IJavaElement type = element.getAncestor(IJavaElement.TYPE); - if (type != null && type.getParent() != null) { - IJavaElement parentType = type.getParent(); - if (parentType.getElementType() == IJavaElement.CLASS_FILE) { - String pn = parentType.getElementName(); - String prefix = FN_RESOURCE_BASE + "$"; //$NON-NLS-1$ - if (pn.startsWith(prefix)) { - return createTypeLink(element, type, wordRegion, true); - } - } - } - } else if (FN_RESOURCE_CLASS.equals(unit.getElementName())) { - // Yes, we're referencing the project R class. - // Offer hyperlink navigation to XML resource files for - // the various definitions - IJavaElement type = element.getAncestor(IJavaElement.TYPE); - if (type != null) { - return createTypeLink(element, type, wordRegion, false); - } - } - } - - } - return null; - } catch (JavaModelException e) { - return null; - } - } - - private IHyperlink[] createTypeLink(IJavaElement element, IJavaElement type, - IRegion wordRegion, boolean isFrameworkResource) { - String typeName = type.getElementName(); - // typeName will be "id", "layout", "string", etc - if (isFrameworkResource) { - typeName = ANDROID_PKG + ':' + typeName; - } - String elementName = element.getElementName(); - String url = '@' + typeName + '/' + elementName; - return getResourceLinks(wordRegion, url); - } - } - - /** Returns the editor applicable to this hyperlink detection */ - private static IEditorPart getEditor() { - // I would like to be able to find this via getAdapter(TextEditor.class) but - // couldn't find a way to initialize the editor context from - // AndroidSourceViewerConfig#getHyperlinkDetectorTargets (which only has - // a TextViewer, not a TextEditor, instance). - // - // Therefore, for now, use a hack. This hack is reasonable because hyperlink - // resolvers are only run for the front-most visible window in the active - // workbench. - return AdtUtils.getActiveEditor(); - } - - /** Returns the project applicable to this hyperlink detection */ - @Nullable - private static IProject getProject() { - IFile file = AdtUtils.getActiveFile(); - if (file != null) { - return file.getProject(); - } - - return null; - } - - /** - * Hyperlink implementation which delays computing the actual file and offset target - * until it is asked to open the hyperlink - */ - private static class DeferredResolutionLink implements IHyperlink { - private XmlContext mXmlContext; - private IRegion mRegion; - - public DeferredResolutionLink(XmlContext xmlContext, IRegion mRegion) { - super(); - this.mXmlContext = xmlContext; - this.mRegion = mRegion; - } - - @Override - public IRegion getHyperlinkRegion() { - return mRegion; - } - - @Override - public String getHyperlinkText() { - return "Open XML Declaration"; - } - - @Override - public String getTypeLabel() { - return null; - } - - @Override - public void open() { - // Lazily compute the location to open - if (mXmlContext != null && !Hyperlinks.open(mXmlContext)) { - // Failed: display message to the user - displayError("Could not open link"); - } - } - } - - /** - * Hyperlink implementation which provides a link for a resource; the actual file name - * is known, but the value location within XML files is deferred until the link is - * actually opened. - */ - static class ResourceLink implements IHyperlink { - private final String mLinkText; - private final IRegion mLinkRegion; - private final ResourceType mType; - private final String mName; - private final ResourceFile mFile; - - /** - * Constructs a new {@link ResourceLink}. - * - * @param linkText the description of the link to be shown in a popup when there - * is more than one match - * @param linkRegion the region corresponding to the link source highlight - * @param file the target resource file containing the link definition - * @param type the type of resource being linked to - * @param name the name of the resource being linked to - */ - public ResourceLink(String linkText, IRegion linkRegion, ResourceFile file, - ResourceType type, String name) { - super(); - mLinkText = linkText; - mLinkRegion = linkRegion; - mType = type; - mName = name; - mFile = file; - } - - @Override - public IRegion getHyperlinkRegion() { - return mLinkRegion; - } - - @Override - public String getHyperlinkText() { - // return "Open XML Declaration"; - return mLinkText; - } - - @Override - public String getTypeLabel() { - return null; - } - - @Override - public void open() { - // We have to defer computation of ids until the link is clicked since we - // don't have a fast map lookup for these - if (mFile == null && mType == ResourceType.ID) { - // Id's are handled specially because they are typically defined - // inline (though they -can- be defined in the values folder above as well, - // in which case we will prefer that definition) - IProject project = getProject(); - Pair<IFile,IRegion> def = findIdDefinition(project, mName); - if (def != null) { - try { - AdtPlugin.openFile(def.getFirst(), def.getSecond()); - } catch (PartInitException e) { - AdtPlugin.log(e, null); - } - return; - } - - displayError(String.format("Could not find id %1$s", mName)); - return; - } - - IAbstractFile wrappedFile = mFile != null ? mFile.getFile() : null; - if (wrappedFile instanceof IFileWrapper) { - IFile file = ((IFileWrapper) wrappedFile).getIFile(); - try { - // Lazily search for the target? - IRegion region = null; - String extension = file.getFileExtension(); - if (mType != null && mName != null && EXT_XML.equals(extension)) { - Pair<IFile, IRegion> target; - if (mType == ResourceType.ID) { - target = findIdInXml(mName, file); - } else { - target = findValueInXml(mType, mName, file); - } - if (target != null) { - region = target.getSecond(); - } - } - AdtPlugin.openFile(file, region); - } catch (PartInitException e) { - AdtPlugin.log(e, null); - } - } else if (wrappedFile instanceof FileWrapper) { - File file = ((FileWrapper) wrappedFile); - IPath path = new Path(file.getAbsolutePath()); - int offset = 0; - // Lazily search for the target? - if (mType != null && mName != null && EXT_XML.equals(path.getFileExtension())) { - if (file.exists()) { - Pair<File, Integer> target = findValueInXml(mType, mName, file); - if (target != null && target.getSecond() != null) { - offset = target.getSecond(); - } - } - } - openPath(path, null, offset); - } else { - throw new IllegalArgumentException("Invalid link parameters"); - } - } - - ResourceFile getFile() { - return mFile; - } - } - - /** - * XML context containing node, potentially attribute, and text regions surrounding a - * particular caret offset - */ - private static class XmlContext { - private final Node mNode; - private final Element mElement; - private final Attr mAttribute; - private final IStructuredDocumentRegion mOuterRegion; - private final ITextRegion mInnerRegion; - private final int mInnerRegionOffset; - - public XmlContext(Node node, Element element, Attr attribute, - IStructuredDocumentRegion outerRegion, - ITextRegion innerRegion, int innerRegionOffset) { - super(); - mNode = node; - mElement = element; - mAttribute = attribute; - mOuterRegion = outerRegion; - mInnerRegion = innerRegion; - mInnerRegionOffset = innerRegionOffset; - } - - /** - * Gets the current node, never null - * - * @return the surrounding node - */ - public Node getNode() { - return mNode; - } - - - /** - * Gets the current node, may be null - * - * @return the surrounding node - */ - public Element getElement() { - return mElement; - } - - /** - * Returns the current attribute, or null if we are not over an attribute - * - * @return the attribute, or null - */ - public Attr getAttribute() { - return mAttribute; - } - - /** - * Gets the region of the element - * - * @return the region of the surrounding element, never null - */ - public ITextRegion getElementRegion() { - return mOuterRegion; - } - - /** - * Gets the inner region, which can be the tag name, an attribute name, an - * attribute value, or some other portion of an XML element - * @return the inner region, never null - */ - public ITextRegion getInnerRegion() { - return mInnerRegion; - } - - /** - * Gets the caret offset relative to the inner region - * - * @return the offset relative to the inner region - */ - public int getInnerRegionCaretOffset() { - return mInnerRegionOffset; - } - - /** - * Returns a range with suffix whitespace stripped out - * - * @param document the document containing the regions - * @return the range of the inner region, minus any whitespace at the end - */ - public IRegion getInnerRange(IDocument document) { - int start = mOuterRegion.getStart() + mInnerRegion.getStart(); - int length = mInnerRegion.getLength(); - try { - String s = document.get(start, length); - for (int i = s.length() - 1; i >= 0; i--) { - if (Character.isWhitespace(s.charAt(i))) { - length--; - } - } - } catch (BadLocationException e) { - AdtPlugin.log(e, ""); //$NON-NLS-1$ - } - return new Region(start, length); - } - - /** - * Returns the node the cursor is currently on in the document. null if no node is - * selected - */ - private static XmlContext find(IDocument document, int offset) { - // Loosely based on getCurrentNode and getCurrentAttr in the WST's - // XMLHyperlinkDetector. - IndexedRegion inode = null; - IStructuredModel model = null; - try { - model = StructuredModelManager.getModelManager().getExistingModelForRead(document); - if (model != null) { - inode = model.getIndexedRegion(offset); - if (inode == null) { - inode = model.getIndexedRegion(offset - 1); - } - - if (inode instanceof Element) { - Element element = (Element) inode; - Attr attribute = null; - if (element.hasAttributes()) { - NamedNodeMap attrs = element.getAttributes(); - // go through each attribute in node and if attribute contains - // offset, return that attribute - for (int i = 0; i < attrs.getLength(); ++i) { - // assumption that if parent node is of type IndexedRegion, - // then its attributes will also be of type IndexedRegion - IndexedRegion attRegion = (IndexedRegion) attrs.item(i); - if (attRegion.contains(offset)) { - attribute = (Attr) attrs.item(i); - break; - } - } - } - - IStructuredDocument doc = model.getStructuredDocument(); - IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); - if (region != null - && DOMRegionContext.XML_TAG_NAME.equals(region.getType())) { - ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); - if (subRegion == null) { - return null; - } - int regionStart = region.getStartOffset(); - int subregionStart = subRegion.getStart(); - int relativeOffset = offset - (regionStart + subregionStart); - return new XmlContext(element, element, attribute, region, subRegion, - relativeOffset); - } - } else if (inode instanceof Node) { - IStructuredDocument doc = model.getStructuredDocument(); - IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); - if (region != null - && DOMRegionContext.XML_CONTENT.equals(region.getType())) { - ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); - int regionStart = region.getStartOffset(); - int subregionStart = subRegion.getStart(); - int relativeOffset = offset - (regionStart + subregionStart); - return new XmlContext((Node) inode, null, null, region, subRegion, - relativeOffset); - } - - } - } - } finally { - if (model != null) { - model.releaseFromRead(); - } - } - - return null; - } - } - - /** - * DOM parser which records offsets in the element nodes such that it can return - * offsets for elements later - */ - private static final class OffsetTrackingParser extends DOMParser { - - private static final String KEY_OFFSET = "offset"; //$NON-NLS-1$ - - private static final String KEY_NODE = - "http://apache.org/xml/properties/dom/current-element-node"; //$NON-NLS-1$ - - private XMLLocator mLocator; - - public OffsetTrackingParser() throws SAXException { - this.setFeature("http://apache.org/xml/features/dom/defer-node-expansion",//$NON-NLS-1$ - false); - } - - public int getOffset(Node node) { - Integer offset = (Integer) node.getUserData(KEY_OFFSET); - if (offset != null) { - return offset; - } - - return -1; - } - - @Override - public void startElement(QName elementQName, XMLAttributes attrList, Augmentations augs) - throws XNIException { - int offset = mLocator.getCharacterOffset(); - super.startElement(elementQName, attrList, augs); - - try { - Node node = (Node) this.getProperty(KEY_NODE); - if (node != null) { - node.setUserData(KEY_OFFSET, offset, null); - } - } catch (org.xml.sax.SAXException ex) { - AdtPlugin.log(ex, ""); //$NON-NLS-1$ - } - } - - @Override - public void startDocument(XMLLocator locator, String encoding, - NamespaceContext namespaceContext, Augmentations augs) throws XNIException { - super.startDocument(locator, encoding, namespaceContext, augs); - mLocator = locator; - } - } -} |