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 
     17 package com.android.ide.eclipse.adt.internal.editors;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
     21 import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME;
     22 import static com.android.ide.common.layout.LayoutConstants.ATTR_ON_CLICK;
     23 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
     24 import static com.android.ide.common.layout.LayoutConstants.VIEW;
     25 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF;
     26 import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
     27 import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
     28 import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
     29 import static com.android.ide.eclipse.adt.AdtConstants.FN_RESOURCE_BASE;
     30 import static com.android.ide.eclipse.adt.AdtConstants.FN_RESOURCE_CLASS;
     31 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_FRAGMENT;
     32 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.NAME_ATTR;
     33 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.ROOT_ELEMENT;
     34 import static com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors.STYLE_ELEMENT;
     35 import static com.android.sdklib.SdkConstants.FD_DOCS;
     36 import static com.android.sdklib.SdkConstants.FD_DOCS_REFERENCE;
     37 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME;
     38 import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
     39 import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
     40 import static com.android.sdklib.xml.AndroidManifest.NODE_SERVICE;
     41 
     42 import com.android.annotations.VisibleForTesting;
     43 import com.android.ide.common.resources.ResourceFile;
     44 import com.android.ide.common.resources.ResourceFolder;
     45 import com.android.ide.common.resources.ResourceRepository;
     46 import com.android.ide.common.resources.configuration.FolderConfiguration;
     47 import com.android.ide.eclipse.adt.AdtPlugin;
     48 import com.android.ide.eclipse.adt.AdtUtils;
     49 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     50 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
     51 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
     52 import com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors;
     53 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     54 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     55 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     56 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     57 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     58 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     59 import com.android.ide.eclipse.adt.io.IFileWrapper;
     60 import com.android.ide.eclipse.adt.io.IFolderWrapper;
     61 import com.android.io.FileWrapper;
     62 import com.android.io.IAbstractFile;
     63 import com.android.io.IAbstractFolder;
     64 import com.android.resources.ResourceFolderType;
     65 import com.android.resources.ResourceType;
     66 import com.android.sdklib.IAndroidTarget;
     67 import com.android.sdklib.SdkConstants;
     68 import com.android.util.Pair;
     69 
     70 import org.apache.xerces.parsers.DOMParser;
     71 import org.apache.xerces.xni.Augmentations;
     72 import org.apache.xerces.xni.NamespaceContext;
     73 import org.apache.xerces.xni.QName;
     74 import org.apache.xerces.xni.XMLAttributes;
     75 import org.apache.xerces.xni.XMLLocator;
     76 import org.apache.xerces.xni.XNIException;
     77 import org.eclipse.core.filesystem.EFS;
     78 import org.eclipse.core.filesystem.IFileStore;
     79 import org.eclipse.core.resources.IContainer;
     80 import org.eclipse.core.resources.IFile;
     81 import org.eclipse.core.resources.IFolder;
     82 import org.eclipse.core.resources.IProject;
     83 import org.eclipse.core.resources.IResource;
     84 import org.eclipse.core.runtime.CoreException;
     85 import org.eclipse.core.runtime.IPath;
     86 import org.eclipse.core.runtime.NullProgressMonitor;
     87 import org.eclipse.core.runtime.Path;
     88 import org.eclipse.jdt.core.Flags;
     89 import org.eclipse.jdt.core.ICodeAssist;
     90 import org.eclipse.jdt.core.IJavaElement;
     91 import org.eclipse.jdt.core.IJavaProject;
     92 import org.eclipse.jdt.core.IMethod;
     93 import org.eclipse.jdt.core.IType;
     94 import org.eclipse.jdt.core.JavaModelException;
     95 import org.eclipse.jdt.core.search.IJavaSearchConstants;
     96 import org.eclipse.jdt.core.search.IJavaSearchScope;
     97 import org.eclipse.jdt.core.search.SearchEngine;
     98 import org.eclipse.jdt.core.search.SearchMatch;
     99 import org.eclipse.jdt.core.search.SearchParticipant;
    100 import org.eclipse.jdt.core.search.SearchPattern;
    101 import org.eclipse.jdt.core.search.SearchRequestor;
    102 import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
    103 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
    104 import org.eclipse.jdt.internal.ui.text.JavaWordFinder;
    105 import org.eclipse.jdt.ui.JavaUI;
    106 import org.eclipse.jdt.ui.actions.SelectionDispatchAction;
    107 import org.eclipse.jface.action.IAction;
    108 import org.eclipse.jface.action.IStatusLineManager;
    109 import org.eclipse.jface.text.BadLocationException;
    110 import org.eclipse.jface.text.IDocument;
    111 import org.eclipse.jface.text.IRegion;
    112 import org.eclipse.jface.text.ITextViewer;
    113 import org.eclipse.jface.text.Region;
    114 import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
    115 import org.eclipse.jface.text.hyperlink.IHyperlink;
    116 import org.eclipse.ui.IEditorInput;
    117 import org.eclipse.ui.IEditorPart;
    118 import org.eclipse.ui.IEditorReference;
    119 import org.eclipse.ui.IEditorSite;
    120 import org.eclipse.ui.IWorkbenchPage;
    121 import org.eclipse.ui.PartInitException;
    122 import org.eclipse.ui.ide.IDE;
    123 import org.eclipse.ui.part.FileEditorInput;
    124 import org.eclipse.ui.part.MultiPageEditorPart;
    125 import org.eclipse.ui.texteditor.ITextEditor;
    126 import org.eclipse.wst.sse.core.StructuredModelManager;
    127 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
    128 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
    129 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
    130 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
    131 import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
    132 import org.eclipse.wst.sse.ui.StructuredTextEditor;
    133 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
    134 import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
    135 import org.w3c.dom.Attr;
    136 import org.w3c.dom.Document;
    137 import org.w3c.dom.Element;
    138 import org.w3c.dom.NamedNodeMap;
    139 import org.w3c.dom.Node;
    140 import org.w3c.dom.NodeList;
    141 import org.xml.sax.InputSource;
    142 import org.xml.sax.SAXException;
    143 
    144 import java.io.File;
    145 import java.io.FileInputStream;
    146 import java.io.IOException;
    147 import java.net.MalformedURLException;
    148 import java.net.URL;
    149 import java.util.ArrayList;
    150 import java.util.Collections;
    151 import java.util.Comparator;
    152 import java.util.List;
    153 import java.util.concurrent.atomic.AtomicBoolean;
    154 import java.util.regex.Pattern;
    155 
    156 /**
    157  * Class containing hyperlink resolvers for XML and Java files to jump to associated
    158  * resources -- Java Activity and Service classes, XML layout and string declarations,
    159  * image drawables, etc.
    160  */
    161 @SuppressWarnings("restriction")
    162 public class Hyperlinks {
    163     private static final String CATEGORY = "category";                            //$NON-NLS-1$
    164     private static final String ACTION = "action";                                //$NON-NLS-1$
    165     private static final String PERMISSION = "permission";                        //$NON-NLS-1$
    166     private static final String USES_PERMISSION = "uses-permission";              //$NON-NLS-1$
    167     private static final String CATEGORY_PKG_PREFIX = "android.intent.category."; //$NON-NLS-1$
    168     private static final String ACTION_PKG_PREFIX = "android.intent.action.";     //$NON-NLS-1$
    169     private static final String PERMISSION_PKG_PREFIX = "android.permission.";    //$NON-NLS-1$
    170 
    171     private Hyperlinks() {
    172         // Not instantiatable. This is a container class containing shared code
    173         // for the various inner classes that are actual hyperlink resolvers.
    174     }
    175 
    176     /** Regular expression matching a FQCN for a view class */
    177     @VisibleForTesting
    178     /* package */ static final Pattern CLASS_PATTERN = Pattern.compile(
    179         "(([a-zA-Z_\\$][a-zA-Z0-9_\\$]*)+\\.)+[a-zA-Z_\\$][a-zA-Z0-9_\\$]*"); //$NON-NLS-1$
    180 
    181     /** Determines whether the given attribute <b>name</b> is linkable */
    182     private static boolean isAttributeNameLink(XmlContext context) {
    183         // We could potentially allow you to link to builtin Android properties:
    184         //   ANDROID_URI.equals(attribute.getNamespaceURI())
    185         // and then jump into the res/values/attrs.xml document that is available
    186         // in the SDK data directory (path found via
    187         // IAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES)).
    188         //
    189         // For now, we're not doing that.
    190         //
    191         // We could also allow to jump into custom attributes in custom view
    192         // classes. Not yet implemented.
    193 
    194         return false;
    195     }
    196 
    197     /** Determines whether the given attribute <b>value</b> is linkable */
    198     private static boolean isAttributeValueLink(XmlContext context) {
    199         // Everything else here is attribute based
    200         Attr attribute = context.getAttribute();
    201         if (attribute == null) {
    202             return false;
    203         }
    204 
    205         if (isClassAttribute(context) || isOnClickAttribute(context)
    206                 || isManifestName(context) || isStyleAttribute(context)) {
    207             return true;
    208         }
    209 
    210         String value = attribute.getValue();
    211         if (value.startsWith("@+")) { //$NON-NLS-1$
    212             // It's a value -declaration-, nowhere else to jump
    213             // (though we could consider jumping to the R-file; would that
    214             // be helpful?)
    215             return false;
    216         }
    217 
    218         Pair<ResourceType,String> resource = ResourceHelper.parseResource(value);
    219         if (resource != null) {
    220             ResourceType type = resource.getFirst();
    221             if (type != null) {
    222                 return true;
    223             }
    224         }
    225 
    226         return false;
    227     }
    228 
    229     /** Determines whether the given element <b>name</b> is linkable */
    230     private static boolean isElementNameLink(XmlContext context) {
    231         if (isClassElement(context)) {
    232             return true;
    233         }
    234 
    235         return false;
    236     }
    237 
    238     /**
    239      * Returns true if this node/attribute pair corresponds to a manifest reference to
    240      * an activity.
    241      */
    242     private static boolean isActivity(XmlContext context) {
    243         // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump
    244         // to it
    245         Attr attribute = context.getAttribute();
    246         String tagName = context.getElement().getTagName();
    247         if (NODE_ACTIVITY.equals(tagName) && ATTRIBUTE_NAME.equals(attribute.getLocalName())
    248                 && ANDROID_URI.equals(attribute.getNamespaceURI())) {
    249             return true;
    250         }
    251 
    252         return false;
    253     }
    254 
    255     /**
    256      * Returns true if this node/attribute pair corresponds to a manifest android:name reference
    257      */
    258     private static boolean isManifestName(XmlContext context) {
    259         Attr attribute = context.getAttribute();
    260         if (attribute != null && ATTRIBUTE_NAME.equals(attribute.getLocalName())
    261                 && ANDROID_URI.equals(attribute.getNamespaceURI())) {
    262             if (getEditor() instanceof ManifestEditor) {
    263                 return true;
    264             }
    265         }
    266 
    267         return false;
    268     }
    269 
    270     /**
    271      * Opens the declaration corresponding to an android:name reference in the
    272      * AndroidManifest.xml file
    273      */
    274     private static boolean openManifestName(IProject project, XmlContext context) {
    275         if (isActivity(context)) {
    276             String fqcn = getActivityClassFqcn(context);
    277             return AdtPlugin.openJavaClass(project, fqcn);
    278         } else if (isService(context)) {
    279             String fqcn = getServiceClassFqcn(context);
    280             return AdtPlugin.openJavaClass(project, fqcn);
    281         } else if (isBuiltinPermission(context)) {
    282             String permission = context.getAttribute().getValue();
    283             // Mutate something like android.permission.ACCESS_CHECKIN_PROPERTIES
    284             // into relative doc url android/Manifest.permission.html#ACCESS_CHECKIN_PROPERTIES
    285             assert permission.startsWith(PERMISSION_PKG_PREFIX);
    286             String relative = "android/Manifest.permission.html#" //$NON-NLS-1$
    287                     + permission.substring(PERMISSION_PKG_PREFIX.length());
    288 
    289             URL url = getDocUrl(relative);
    290             if (url != null) {
    291                 AdtPlugin.openUrl(url);
    292                 return true;
    293             } else {
    294                 return false;
    295             }
    296         } else if (isBuiltinIntent(context)) {
    297             String intent = context.getAttribute().getValue();
    298             // Mutate something like android.intent.action.MAIN into
    299             // into relative doc url android/content/Intent.html#ACTION_MAIN
    300             String relative;
    301             if (intent.startsWith(ACTION_PKG_PREFIX)) {
    302                 relative = "android/content/Intent.html#ACTION_" //$NON-NLS-1$
    303                         + intent.substring(ACTION_PKG_PREFIX.length());
    304             } else if (intent.startsWith(CATEGORY_PKG_PREFIX)) {
    305                 relative = "android/content/Intent.html#CATEGORY_" //$NON-NLS-1$
    306                         + intent.substring(CATEGORY_PKG_PREFIX.length());
    307             } else {
    308                 return false;
    309             }
    310             URL url = getDocUrl(relative);
    311             if (url != null) {
    312                 AdtPlugin.openUrl(url);
    313                 return true;
    314             } else {
    315                 return false;
    316             }
    317         }
    318 
    319         return false;
    320     }
    321 
    322     /** Returns true if this represents a style attribute */
    323     private static boolean isStyleAttribute(XmlContext context) {
    324         String tag = context.getElement().getTagName();
    325         return STYLE_ELEMENT.equals(tag);
    326     }
    327 
    328     /**
    329      * Returns true if this represents a {@code <view class="foo.bar.Baz">} class
    330      * attribute, or a {@code <fragment android:name="foo.bar.Baz">} class attribute
    331      */
    332     private static boolean isClassAttribute(XmlContext context) {
    333         Attr attribute = context.getAttribute();
    334         if (attribute == null) {
    335             return false;
    336         }
    337         String tag = context.getElement().getTagName();
    338         String attributeName = attribute.getLocalName();
    339         return ATTR_CLASS.equals(attributeName) && (VIEW.equals(tag) || VIEW_FRAGMENT.equals(tag))
    340                 || ATTR_NAME.equals(attributeName) && VIEW_FRAGMENT.equals(tag);
    341     }
    342 
    343     /** Returns true if this represents an onClick attribute specifying a method handler */
    344     private static boolean isOnClickAttribute(XmlContext context) {
    345         Attr attribute = context.getAttribute();
    346         if (attribute == null) {
    347             return false;
    348         }
    349         return ATTR_ON_CLICK.equals(attribute.getLocalName()) && attribute.getValue().length() > 0;
    350     }
    351 
    352     /** Returns true if this represents a {@code <foo.bar.Baz>} custom view class element */
    353     private static boolean isClassElement(XmlContext context) {
    354         if (context.getAttribute() != null) {
    355             // Don't match the outer element if the user is hovering over a specific attribute
    356             return false;
    357         }
    358         // If the element looks like a fully qualified class name (e.g. it's a custom view
    359         // element) offer it as a link
    360         String tag = context.getElement().getTagName();
    361         return (tag.indexOf('.') != -1 && CLASS_PATTERN.matcher(tag).matches());
    362     }
    363 
    364     /** Returns the FQCN for a class declaration at the given context */
    365     private static String getClassFqcn(XmlContext context) {
    366         if (isClassAttribute(context)) {
    367             return context.getAttribute().getValue();
    368         } else if (isClassElement(context)) {
    369             return context.getElement().getTagName();
    370         }
    371 
    372         return null;
    373     }
    374 
    375     /**
    376      * Returns true if this node/attribute pair corresponds to a manifest reference to
    377      * an service.
    378      */
    379     private static boolean isService(XmlContext context) {
    380         Attr attribute = context.getAttribute();
    381         Element node = context.getElement();
    382 
    383         // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it
    384         String nodeName = node.getNodeName();
    385         if (NODE_SERVICE.equals(nodeName) && ATTRIBUTE_NAME.equals(attribute.getLocalName())
    386                 && ANDROID_URI.equals(attribute.getNamespaceURI())) {
    387             return true;
    388         }
    389 
    390         return false;
    391     }
    392 
    393     /**
    394      * Returns a URL pointing to the Android reference documentation, either installed
    395      * locally or the one on android.com
    396      *
    397      * @param relative a relative url to append to the root url
    398      * @return a URL pointing to the documentation
    399      */
    400     private static URL getDocUrl(String relative) {
    401         // First try to find locally installed documentation
    402         File sdkLocation = new File(Sdk.getCurrent().getSdkLocation());
    403         File docs = new File(sdkLocation, FD_DOCS + File.separator + FD_DOCS_REFERENCE);
    404         try {
    405             if (docs.exists()) {
    406                 String s = docs.toURI().toURL().toExternalForm();
    407                 if (!s.endsWith("/")) { //$NON-NLS-1$
    408                     s += "/";           //$NON-NLS-1$
    409                 }
    410                 return new URL(s + relative);
    411             }
    412             // If not, fallback to the online documentation
    413             return new URL("http://developer.android.com/reference/" + relative); //$NON-NLS-1$
    414         } catch (MalformedURLException e) {
    415             AdtPlugin.log(e, "Can't create URL for %1$s", docs);
    416             return null;
    417         }
    418     }
    419 
    420     /** Returns true if the context is pointing to a permission name reference */
    421     private static boolean isBuiltinPermission(XmlContext context) {
    422         Attr attribute = context.getAttribute();
    423         Element node = context.getElement();
    424 
    425         // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it
    426         String nodeName = node.getNodeName();
    427         if ((USES_PERMISSION.equals(nodeName) || PERMISSION.equals(nodeName))
    428                 && ATTRIBUTE_NAME.equals(attribute.getLocalName())
    429                 && ANDROID_URI.equals(attribute.getNamespaceURI())) {
    430             String value = attribute.getValue();
    431             if (value.startsWith(PERMISSION_PKG_PREFIX)) {
    432                 return true;
    433             }
    434         }
    435 
    436         return false;
    437     }
    438 
    439     /** Returns true if the context is pointing to an intent reference */
    440     private static boolean isBuiltinIntent(XmlContext context) {
    441         Attr attribute = context.getAttribute();
    442         Element node = context.getElement();
    443 
    444         // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it
    445         String nodeName = node.getNodeName();
    446         if ((ACTION.equals(nodeName) || CATEGORY.equals(nodeName))
    447                 && ATTRIBUTE_NAME.equals(attribute.getLocalName())
    448                 && ANDROID_URI.equals(attribute.getNamespaceURI())) {
    449             String value = attribute.getValue();
    450             if (value.startsWith(ACTION_PKG_PREFIX) || value.startsWith(CATEGORY_PKG_PREFIX)) {
    451                 return true;
    452             }
    453         }
    454 
    455         return false;
    456     }
    457 
    458 
    459     /**
    460      * Returns the fully qualified class name of an activity referenced by the given
    461      * AndroidManifest.xml node
    462      */
    463     private static String getActivityClassFqcn(XmlContext context) {
    464         Attr attribute = context.getAttribute();
    465         Element node = context.getElement();
    466         StringBuilder sb = new StringBuilder();
    467         Element root = node.getOwnerDocument().getDocumentElement();
    468         String pkg = root.getAttribute(ATTRIBUTE_PACKAGE);
    469         String className = attribute.getValue();
    470         if (className.startsWith(".")) { //$NON-NLS-1$
    471             sb.append(pkg);
    472         } else if (className.indexOf('.') == -1) {
    473             // According to the <activity> manifest element documentation, this is not
    474             // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
    475             // but it appears in manifest files and appears to be supported by the runtime
    476             // so handle this in code as well:
    477             sb.append(pkg);
    478             sb.append('.');
    479         } // else: the class name is already a fully qualified class name
    480         sb.append(className);
    481         return sb.toString();
    482     }
    483 
    484     /**
    485      * Returns the fully qualified class name of a service referenced by the given
    486      * AndroidManifest.xml node
    487      */
    488     private static String getServiceClassFqcn(XmlContext context) {
    489         // Same logic
    490         return getActivityClassFqcn(context);
    491     }
    492 
    493     /**
    494      * Returns the XML tag containing an element description for value items of the given
    495      * resource type
    496      *
    497      * @param type the resource type to query the XML tag name for
    498      * @return the tag name used for value declarations in XML of resources of the given
    499      *         type
    500      */
    501     public static String getTagName(ResourceType type) {
    502         if (type == ResourceType.ID) {
    503             // Ids are recorded in <item> tags instead of <id> tags
    504             return ValuesDescriptors.ITEM_TAG;
    505         }
    506 
    507         return type.getName();
    508     }
    509 
    510     /**
    511      * Computes the actual exact location to jump to for a given XML context.
    512      *
    513      * @param context the XML context to be opened
    514      * @return true if the request was handled successfully
    515      */
    516     private static boolean open(XmlContext context) {
    517         IProject project = getProject();
    518         if (project == null) {
    519             return false;
    520         }
    521 
    522         if (isManifestName(context)) {
    523             return openManifestName(project, context);
    524         } else if (isClassElement(context) || isClassAttribute(context)) {
    525             return AdtPlugin.openJavaClass(project, getClassFqcn(context));
    526         } else if (isOnClickAttribute(context)) {
    527             return openOnClickMethod(project, context.getAttribute().getValue());
    528         } else {
    529             return false;
    530         }
    531     }
    532 
    533     /** Opens a path (which may not be in the workspace) */
    534     private static void openPath(IPath filePath, IRegion region, int offset) {
    535         IEditorPart sourceEditor = getEditor();
    536         IWorkbenchPage page = sourceEditor.getEditorSite().getPage();
    537 
    538         IFile file = AdtUtils.pathToIFile(filePath);
    539         if (file != null && file.exists()) {
    540             try {
    541                 AdtPlugin.openFile(file, region);
    542                 return;
    543             } catch (PartInitException ex) {
    544                 AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$
    545             }
    546         } else {
    547             // It's not a path in the workspace; look externally
    548             // (this is probably an @android: path)
    549             if (filePath.isAbsolute()) {
    550                 IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath);
    551                 if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) {
    552                     try {
    553                         IEditorPart target = IDE.openEditorOnFileStore(page, fileStore);
    554                         if (target instanceof MultiPageEditorPart) {
    555                             MultiPageEditorPart part = (MultiPageEditorPart) target;
    556                             IEditorPart[] editors = part.findEditors(target.getEditorInput());
    557                             if (editors != null) {
    558                                 for (IEditorPart editor : editors) {
    559                                     if (editor instanceof StructuredTextEditor) {
    560                                         StructuredTextEditor ste = (StructuredTextEditor) editor;
    561                                         part.setActiveEditor(editor);
    562                                         ste.selectAndReveal(offset, 0);
    563                                         break;
    564                                     }
    565                                 }
    566                             }
    567                         }
    568 
    569                         return;
    570                     } catch (PartInitException ex) {
    571                         AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$
    572                     }
    573                 }
    574             }
    575         }
    576 
    577         // Failed: display message to the user
    578         displayError(String.format("Could not find resource %1$s", filePath));
    579     }
    580 
    581     private static void displayError(String message) {
    582         // Failed: display message to the user
    583         IEditorSite editorSite = getEditor().getEditorSite();
    584         IStatusLineManager status = editorSite.getActionBars().getStatusLineManager();
    585         status.setErrorMessage(message);
    586     }
    587 
    588     /**
    589      * Opens a Java method referenced by the given on click attribute method name
    590      *
    591      * @param project the project containing the click handler
    592      * @param method the method name of the on click handler
    593      * @return true if the method was opened, false otherwise
    594      */
    595     public static boolean openOnClickMethod(IProject project, String method) {
    596         // Search for the method in the Java index, filtering by the required click handler
    597         // method signature (public and has a single View parameter), and narrowing the scope
    598         // first to Activity classes, then to the whole workspace.
    599         final AtomicBoolean success = new AtomicBoolean(false);
    600         SearchRequestor requestor = new SearchRequestor() {
    601             @Override
    602             public void acceptSearchMatch(SearchMatch match) throws CoreException {
    603                 Object element = match.getElement();
    604                 if (element instanceof IMethod) {
    605                     IMethod methodElement = (IMethod) element;
    606                     String[] parameterTypes = methodElement.getParameterTypes();
    607                     if (parameterTypes != null
    608                             && parameterTypes.length == 1
    609                             && ("Qandroid.view.View;".equals(parameterTypes[0]) //$NON-NLS-1$
    610                                     || "QView;".equals(parameterTypes[0]))) {   //$NON-NLS-1$
    611                         // Check that it's public
    612                         if (Flags.isPublic(methodElement.getFlags())) {
    613                             JavaUI.openInEditor(methodElement);
    614                             success.getAndSet(true);
    615                         }
    616                     }
    617                 }
    618             }
    619         };
    620         try {
    621             IJavaSearchScope scope = null;
    622             IType activityType = null;
    623             IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    624             if (javaProject != null) {
    625                 activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY);
    626                 if (activityType != null) {
    627                     scope = SearchEngine.createHierarchyScope(activityType);
    628                 }
    629             }
    630             if (scope == null) {
    631                 scope = SearchEngine.createWorkspaceScope();
    632             }
    633 
    634             SearchParticipant[] participants = new SearchParticipant[] {
    635                 SearchEngine.getDefaultSearchParticipant()
    636             };
    637             int matchRule = SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE;
    638             SearchPattern pattern = SearchPattern.createPattern("*." + method,
    639                     IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, matchRule);
    640             SearchEngine engine = new SearchEngine();
    641             engine.search(pattern, participants, scope, requestor, new NullProgressMonitor());
    642 
    643             boolean ok = success.get();
    644             if (!ok && activityType != null) {
    645                 // TODO: Create a project+dependencies scope and search only that scope
    646 
    647                 // Try searching again with a complete workspace scope this time
    648                 scope = SearchEngine.createWorkspaceScope();
    649                 engine.search(pattern, participants, scope, requestor, new NullProgressMonitor());
    650 
    651                 // TODO: There could be more than one match; add code to consider them all
    652                 // and pick the most likely candidate and open only that one.
    653 
    654                 ok = success.get();
    655             }
    656             return ok;
    657         } catch (CoreException e) {
    658             AdtPlugin.log(e, null);
    659         }
    660         return false;
    661     }
    662 
    663     /**
    664      * Returns the current configuration, if the associated UI editor has been initialized
    665      * and has an associated configuration
    666      *
    667      * @return the configuration for this file, or null
    668      */
    669     private static FolderConfiguration getConfiguration() {
    670         IEditorPart editor = getEditor();
    671         if (editor != null) {
    672             LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor);
    673             GraphicalEditorPart graphicalEditor =
    674                 delegate == null ? null : delegate.getGraphicalEditor();
    675 
    676             if (graphicalEditor != null) {
    677                 return graphicalEditor.getConfiguration();
    678             } else {
    679                 // TODO: Could try a few more things to get the configuration:
    680                 // (1) try to look at the file.getPersistentProperty(NAME_CONFIG_STATE)
    681                 //    which will return previously saved state. This isn't necessary today
    682                 //    since no editors seem to be lazily initialized.
    683                 // (2) attempt to use the configuration from any of the other open
    684                 //    files, especially files in the same directory as this one.
    685             }
    686 
    687             // Create a configuration from the current file
    688             IProject project = null;
    689             IEditorInput editorInput = editor.getEditorInput();
    690             if (editorInput instanceof FileEditorInput) {
    691                 IFile file = ((FileEditorInput) editorInput).getFile();
    692                 project = file.getProject();
    693                 ProjectResources pr = ResourceManager.getInstance().getProjectResources(project);
    694                 IContainer parent = file.getParent();
    695                 if (parent instanceof IFolder) {
    696                     ResourceFolder resFolder = pr.getResourceFolder((IFolder) parent);
    697                     if (resFolder != null) {
    698                         return resFolder.getConfiguration();
    699                     }
    700                 }
    701             }
    702 
    703             // Might be editing a Java file, where there is no configuration context.
    704             // Instead look at surrounding files in the workspace and obtain one valid
    705             // configuration.
    706             for (IEditorReference reference : editor.getSite().getPage().getEditorReferences()) {
    707                 IEditorPart part = reference.getEditor(false /*restore*/);
    708 
    709                 LayoutEditorDelegate refDelegate = LayoutEditorDelegate.fromEditor(part);
    710                 if (refDelegate != null) {
    711                     IProject refProject = refDelegate.getEditor().getProject();
    712                     if (project == null || project == refProject) {
    713                         GraphicalEditorPart refGraphicalEditor = refDelegate.getGraphicalEditor();
    714                         if (refGraphicalEditor != null) {
    715                             return refGraphicalEditor.getConfiguration();
    716                         }
    717                     }
    718                 }
    719             }
    720         }
    721 
    722         return null;
    723     }
    724 
    725     /** Returns the {@link IAndroidTarget} to be used for looking up system resources */
    726     private static IAndroidTarget getTarget(IProject project) {
    727         IEditorPart editor = getEditor();
    728         LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor);
    729         if (delegate != null) {
    730             GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor();
    731             if (graphicalEditor != null) {
    732                 return graphicalEditor.getRenderingTarget();
    733             }
    734         }
    735 
    736         Sdk currentSdk = Sdk.getCurrent();
    737         if (currentSdk == null) {
    738             return null;
    739         }
    740 
    741         return currentSdk.getTarget(project);
    742     }
    743 
    744     /** Return either the project resources or the framework resources (or null) */
    745     private static ResourceRepository getResources(IProject project, boolean framework) {
    746         if (framework) {
    747             IAndroidTarget target = getTarget(project);
    748 
    749             if (target == null && project == null && framework) {
    750                 // No current project: probably jumped into some of the framework XML resource
    751                 // files and attempting to jump around. Attempt to figure out which target
    752                 // we're dealing with and continue looking within the same framework.
    753                 IEditorPart editor = getEditor();
    754                 Sdk sdk = Sdk.getCurrent();
    755                 if (sdk != null && editor instanceof AndroidXmlEditor) {
    756                     AndroidTargetData data = ((AndroidXmlEditor) editor).getTargetData();
    757                     if (data != null) {
    758                         return data.getFrameworkResources();
    759                     }
    760                 }
    761             }
    762 
    763             if (target == null) {
    764                 return null;
    765             }
    766             AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
    767             if (data == null) {
    768                 return null;
    769             }
    770             return data.getFrameworkResources();
    771         } else {
    772             return ResourceManager.getInstance().getProjectResources(project);
    773         }
    774     }
    775 
    776     /**
    777      * Finds a definition of an id attribute in layouts. (Ids can also be defined as
    778      * resources; use {@link #findValueInXml} or {@link #findValueInDocument} to locate it there.)
    779      */
    780     private static Pair<IFile, IRegion> findIdDefinition(IProject project, String id) {
    781         // FIRST look in the same file as the originating request, that's where you usually
    782         // want to jump
    783         IFile self = AdtUtils.getActiveFile();
    784         if (self != null && EXT_XML.equals(self.getFileExtension())) {
    785             Pair<IFile, IRegion> target = findIdInXml(id, self);
    786             if (target != null) {
    787                 return target;
    788             }
    789         }
    790 
    791         // Look in the configuration folder: Search compatible configurations
    792         ResourceRepository resources = getResources(project, false /* isFramework */);
    793         FolderConfiguration configuration = getConfiguration();
    794         if (configuration != null) { // Not the case when searching from Java files for example
    795             List<ResourceFolder> folders = resources.getFolders(ResourceFolderType.LAYOUT);
    796             if (folders != null) {
    797                 for (ResourceFolder folder : folders) {
    798                     if (folder.getConfiguration().isMatchFor(configuration)) {
    799                         IAbstractFolder wrapper = folder.getFolder();
    800                         if (wrapper instanceof IFolderWrapper) {
    801                             IFolder iFolder = ((IFolderWrapper) wrapper).getIFolder();
    802                             Pair<IFile, IRegion> target = findIdInFolder(iFolder, id);
    803                             if (target != null) {
    804                                 return target;
    805                             }
    806                         }
    807                     }
    808                 }
    809                 return null;
    810             }
    811         }
    812 
    813         // Ugh. Search ALL layout files in the project!
    814         List<ResourceFolder> folders = resources.getFolders(ResourceFolderType.LAYOUT);
    815         if (folders != null) {
    816             for (ResourceFolder folder : folders) {
    817                 IAbstractFolder wrapper = folder.getFolder();
    818                 if (wrapper instanceof IFolderWrapper) {
    819                     IFolder iFolder = ((IFolderWrapper) wrapper).getIFolder();
    820                     Pair<IFile, IRegion> target = findIdInFolder(iFolder, id);
    821                     if (target != null) {
    822                         return target;
    823                     }
    824                 }
    825             }
    826         }
    827 
    828         return null;
    829     }
    830 
    831     /**
    832      * Finds a definition of an id attribute in a particular layout folder.
    833      */
    834     private static Pair<IFile, IRegion> findIdInFolder(IContainer f, String id) {
    835         try {
    836             // Check XML files in values/
    837             for (IResource resource : f.members()) {
    838                 if (resource.exists() && !resource.isDerived() && resource instanceof IFile) {
    839                     IFile file = (IFile) resource;
    840                     // Must have an XML extension
    841                     if (EXT_XML.equals(file.getFileExtension())) {
    842                         Pair<IFile, IRegion> target = findIdInXml(id, file);
    843                         if (target != null) {
    844                             return target;
    845                         }
    846                     }
    847                 }
    848             }
    849         } catch (CoreException e) {
    850             AdtPlugin.log(e, ""); //$NON-NLS-1$
    851         }
    852 
    853         return null;
    854     }
    855 
    856     /** Parses the given file and locates a definition of the given resource */
    857     private static Pair<IFile, IRegion> findValueInXml(
    858             ResourceType type, String name, IFile file) {
    859         IStructuredModel model = null;
    860         try {
    861             model = StructuredModelManager.getModelManager().getExistingModelForRead(file);
    862             if (model == null) {
    863                 // There is no open or cached model for the file; see if the file looks
    864                 // like it's interesting (content contains the String name we are looking for)
    865                 if (AdtPlugin.fileContains(file, name)) {
    866                     // Yes, so parse content
    867                     model = StructuredModelManager.getModelManager().getModelForRead(file);
    868                 }
    869             }
    870             if (model instanceof IDOMModel) {
    871                 IDOMModel domModel = (IDOMModel) model;
    872                 Document document = domModel.getDocument();
    873                 return findValueInDocument(type, name, file, document);
    874             }
    875         } catch (IOException e) {
    876             AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$
    877         } catch (CoreException e) {
    878             AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$
    879         } finally {
    880             if (model != null) {
    881                 model.releaseFromRead();
    882             }
    883         }
    884 
    885         return null;
    886     }
    887 
    888     /** Looks within an XML DOM document for the given resource name and returns it */
    889     private static Pair<IFile, IRegion> findValueInDocument(
    890             ResourceType type, String name, IFile file, Document document) {
    891         String targetTag = getTagName(type);
    892         Element root = document.getDocumentElement();
    893         if (root.getTagName().equals(ROOT_ELEMENT)) {
    894             NodeList children = root.getChildNodes();
    895             for (int i = 0, n = children.getLength(); i < n; i++) {
    896                 Node child = children.item(i);
    897                 if (child.getNodeType() == Node.ELEMENT_NODE) {
    898                     Element element = (Element)child;
    899                     if (element.getTagName().equals(targetTag)) {
    900                         String elementName = element.getAttribute(NAME_ATTR);
    901                         if (elementName.equals(name)) {
    902                             IRegion region = null;
    903                             if (element instanceof IndexedRegion) {
    904                                 IndexedRegion r = (IndexedRegion) element;
    905                                 // IndexedRegion.getLength() returns bogus values
    906                                 int length = r.getEndOffset() - r.getStartOffset();
    907                                 region = new Region(r.getStartOffset(), length);
    908                             }
    909 
    910                             return Pair.of(file, region);
    911                         }
    912                     }
    913                 }
    914             }
    915         }
    916 
    917         return null;
    918     }
    919 
    920     /** Parses the given file and locates a definition of the given resource */
    921     private static Pair<IFile, IRegion> findIdInXml(String id, IFile file) {
    922         IStructuredModel model = null;
    923         try {
    924             model = StructuredModelManager.getModelManager().getExistingModelForRead(file);
    925             if (model == null) {
    926                 // There is no open or cached model for the file; see if the file looks
    927                 // like it's interesting (content contains the String name we are looking for)
    928                 if (AdtPlugin.fileContains(file, id)) {
    929                     // Yes, so parse content
    930                     model = StructuredModelManager.getModelManager().getModelForRead(file);
    931                 }
    932             }
    933             if (model instanceof IDOMModel) {
    934                 IDOMModel domModel = (IDOMModel) model;
    935                 Document document = domModel.getDocument();
    936                 return findIdInDocument(id, file, document);
    937             }
    938         } catch (IOException e) {
    939             AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$
    940         } catch (CoreException e) {
    941             AdtPlugin.log(e, "Can't parse %1$s", file); //$NON-NLS-1$
    942         } finally {
    943             if (model != null) {
    944                 model.releaseFromRead();
    945             }
    946         }
    947 
    948         return null;
    949     }
    950 
    951     /** Looks within an XML DOM document for the given resource name and returns it */
    952     private static Pair<IFile, IRegion> findIdInDocument(String id, IFile file,
    953             Document document) {
    954         String targetAttribute = NEW_ID_PREFIX + id;
    955         return findIdInElement(document.getDocumentElement(), file, targetAttribute);
    956     }
    957 
    958     private static Pair<IFile, IRegion> findIdInElement(
    959             Element root, IFile file, String targetAttribute) {
    960         NamedNodeMap attributes = root.getAttributes();
    961         for (int i = 0, n = attributes.getLength(); i < n; i++) {
    962             Node item = attributes.item(i);
    963             if (item instanceof Attr) {
    964                 Attr attribute = (Attr)item;
    965                 String value = attribute.getValue();
    966                 if (value.equals(targetAttribute)) {
    967                     // Select the element -containing- the id rather than the attribute itself
    968                     IRegion region = null;
    969                     Node element = attribute.getOwnerElement();
    970                     //if (attribute instanceof IndexedRegion) {
    971                     if (element instanceof IndexedRegion) {
    972                         IndexedRegion r = (IndexedRegion) element;
    973                         int length = r.getEndOffset() - r.getStartOffset();
    974                         region = new Region(r.getStartOffset(), length);
    975                     }
    976 
    977                     return Pair.of(file, region);
    978                 }
    979             }
    980         }
    981 
    982         NodeList children = root.getChildNodes();
    983         for (int i = 0, n = children.getLength(); i < n; i++) {
    984             Node child = children.item(i);
    985             if (child.getNodeType() == Node.ELEMENT_NODE) {
    986                 Element element = (Element)child;
    987                 Pair<IFile, IRegion> result = findIdInElement(element, file, targetAttribute);
    988                 if (result != null) {
    989                     return result;
    990                 }
    991             }
    992         }
    993 
    994         return null;
    995     }
    996 
    997     /** Parses the given file and locates a definition of the given resource */
    998     private static Pair<File, Integer> findValueInXml(ResourceType type, String name, File file) {
    999         // We can't use the StructureModelManager on files outside projects
   1000         // There is no open or cached model for the file; see if the file looks
   1001         // like it's interesting (content contains the String name we are looking for)
   1002         if (AdtPlugin.fileContains(file, name)) {
   1003             try {
   1004                 InputSource is = new InputSource(new FileInputStream(file));
   1005                 OffsetTrackingParser parser = new OffsetTrackingParser();
   1006                 parser.parse(is);
   1007                 Document document = parser.getDocument();
   1008 
   1009                 return findValueInDocument(type, name, file, parser, document);
   1010             } catch (SAXException e) {
   1011                 // pass -- ignore files we can't parse
   1012             } catch (IOException e) {
   1013                 // pass -- ignore files we can't parse
   1014             }
   1015         }
   1016 
   1017         return null;
   1018     }
   1019 
   1020     /** Looks within an XML DOM document for the given resource name and returns it */
   1021     private static Pair<File, Integer> findValueInDocument(ResourceType type, String name,
   1022             File file, OffsetTrackingParser parser, Document document) {
   1023         String targetTag = type.getName();
   1024         if (type == ResourceType.ID) {
   1025             // Ids are recorded in <item> tags instead of <id> tags
   1026             targetTag = "item"; //$NON-NLS-1$
   1027         } else if (type == ResourceType.ATTR) {
   1028             // Attributes seem to be defined in <public> tags
   1029             targetTag = "public"; //$NON-NLS-1$
   1030         }
   1031         Element root = document.getDocumentElement();
   1032         if (root.getTagName().equals(ROOT_ELEMENT)) {
   1033             NodeList children = root.getChildNodes();
   1034             for (int i = 0, n = children.getLength(); i < n; i++) {
   1035                 Node child = children.item(i);
   1036                 if (child.getNodeType() == Node.ELEMENT_NODE) {
   1037                     Element element = (Element) child;
   1038                     if (element.getTagName().equals(targetTag)) {
   1039                         String elementName = element.getAttribute(NAME_ATTR);
   1040                         if (elementName.equals(name)) {
   1041 
   1042                             return Pair.of(file, parser.getOffset(element));
   1043                         }
   1044                     }
   1045                 }
   1046             }
   1047         }
   1048 
   1049         return null;
   1050     }
   1051 
   1052     private static IHyperlink[] getStyleLinks(XmlContext context, IRegion range, String url) {
   1053         Attr attribute = context.getAttribute();
   1054         if (attribute != null) {
   1055             // Split up theme resource urls to the nearest dot forwards, such that you
   1056             // can point to "Theme.Light" by placing the caret anywhere after the dot,
   1057             // and point to just "Theme" by pointing before it.
   1058             int caret = context.getInnerRegionCaretOffset();
   1059             String value = attribute.getValue();
   1060             int index = value.indexOf('.', caret);
   1061             if (index != -1) {
   1062                 url = url.substring(0, index);
   1063                 range = new Region(range.getOffset(),
   1064                         range.getLength() - (value.length() - index));
   1065             }
   1066         }
   1067 
   1068         Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
   1069         if (resource == null) {
   1070             String androidStyle = "@android:style/"; //$NON-NLS-1$
   1071             if (url.startsWith(PREFIX_ANDROID_RESOURCE_REF)) {
   1072                 url = androidStyle + url.substring(PREFIX_ANDROID_RESOURCE_REF.length());
   1073             } else if (url.startsWith(ANDROID_PKG + ':')) {
   1074                 url = androidStyle + url.substring(ANDROID_PKG.length() + 1);
   1075             } else {
   1076                 url = "@style/" + url; //$NON-NLS-1$
   1077             }
   1078         }
   1079         return getResourceLinks(range, url);
   1080     }
   1081 
   1082     /**
   1083      * Computes hyperlinks to resource definitions for resource urls (e.g.
   1084      * {@code @android:string/ok} or {@code @layout/foo}. May create multiple links.
   1085      */
   1086     private static IHyperlink[] getResourceLinks(IRegion range, String url) {
   1087         List<IHyperlink> links = new ArrayList<IHyperlink>();
   1088         IProject project = Hyperlinks.getProject();
   1089         FolderConfiguration configuration = getConfiguration();
   1090 
   1091         Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
   1092         if (resource == null || resource.getFirst() == null) {
   1093             return null;
   1094         }
   1095         ResourceType type = resource.getFirst();
   1096         String name = resource.getSecond();
   1097 
   1098         boolean isFramework = url.startsWith("@android"); //$NON-NLS-1$
   1099         if (project == null) {
   1100             // Local reference *within* a framework
   1101             isFramework = true;
   1102         }
   1103 
   1104         ResourceRepository resources = getResources(project, isFramework);
   1105         if (resources == null) {
   1106             return null;
   1107         }
   1108         List<ResourceFile> sourceFiles = resources.getSourceFiles(type, name,
   1109                 null /*configuration*/);
   1110         ResourceFile best = null;
   1111         if (configuration != null && sourceFiles != null && sourceFiles.size() > 0) {
   1112             List<ResourceFile> bestFiles = resources.getSourceFiles(type, name, configuration);
   1113             if (bestFiles != null && bestFiles.size() > 0) {
   1114                 best = bestFiles.get(0);
   1115             }
   1116         }
   1117         if (sourceFiles != null) {
   1118             List<ResourceFile> matches = new ArrayList<ResourceFile>();
   1119             for (ResourceFile resourceFile : sourceFiles) {
   1120                 matches.add(resourceFile);
   1121             }
   1122 
   1123             if (matches.size() > 0) {
   1124                 final ResourceFile fBest = best;
   1125                 Collections.sort(matches, new Comparator<ResourceFile>() {
   1126                     @Override
   1127                     public int compare(ResourceFile rf1, ResourceFile rf2) {
   1128                         // Sort best item to the front
   1129                         if (rf1 == fBest) {
   1130                             return -1;
   1131                         } else if (rf2 == fBest) {
   1132                             return 1;
   1133                         } else {
   1134                             return getFileName(rf1).compareTo(getFileName(rf2));
   1135                         }
   1136                     }
   1137                 });
   1138 
   1139                 // Is this something found in a values/ folder?
   1140                 boolean valueResource = ResourceHelper.isValueBasedResourceType(type);
   1141 
   1142                 for (ResourceFile file : matches) {
   1143                     String folderName = file.getFolder().getFolder().getName();
   1144                     String label = String.format("Open Declaration in %1$s/%2$s",
   1145                             folderName, getFileName(file));
   1146 
   1147                     // Only search for resource type within the file if it's an
   1148                     // XML file and it is a value resource
   1149                     ResourceLink link = new ResourceLink(label, range, file,
   1150                             valueResource ? type : null, name);
   1151                     links.add(link);
   1152                 }
   1153             }
   1154         }
   1155 
   1156         // Id's are handled specially because they are typically defined
   1157         // inline (though they -can- be defined in the values folder above as
   1158         // well, in which case we will prefer that definition)
   1159         if (!isFramework && type == ResourceType.ID && links.size() == 0) {
   1160             // Must compute these lazily...
   1161             links.add(new ResourceLink("Open XML Declaration", range, null, type, name));
   1162         }
   1163 
   1164         if (links.size() > 0) {
   1165             return links.toArray(new IHyperlink[links.size()]);
   1166         } else {
   1167             return null;
   1168         }
   1169     }
   1170 
   1171     private static String getFileName(ResourceFile file) {
   1172         return file.getFile().getName();
   1173     }
   1174 
   1175     /** Detector for finding Android references in XML files */
   1176    public static class XmlResolver extends AbstractHyperlinkDetector {
   1177 
   1178         @Override
   1179         public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region,
   1180                 boolean canShowMultipleHyperlinks) {
   1181 
   1182             if (region == null || textViewer == null) {
   1183                 return null;
   1184             }
   1185 
   1186             IDocument document = textViewer.getDocument();
   1187 
   1188             XmlContext context = XmlContext.find(document, region.getOffset());
   1189             if (context == null) {
   1190                 return null;
   1191             }
   1192 
   1193             IRegion range = context.getInnerRange(document);
   1194             boolean isLinkable = false;
   1195             String type = context.getInnerRegion().getType();
   1196             if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {
   1197                 if (isAttributeValueLink(context)) {
   1198                     isLinkable = true;
   1199                     // Strip out quotes
   1200                     range = new Region(range.getOffset() + 1, range.getLength() - 2);
   1201 
   1202                     Attr attribute = context.getAttribute();
   1203                     if (isStyleAttribute(context)) {
   1204                         return getStyleLinks(context, range, attribute.getValue());
   1205                     }
   1206                     if (attribute != null
   1207                             && attribute.getValue().startsWith(PREFIX_RESOURCE_REF)) {
   1208                         // Instantly create links for resources since we can use the existing
   1209                         // resolved maps for this and offer multiple choices for the user
   1210 
   1211                         String url = attribute.getValue();
   1212                         return getResourceLinks(range, url);
   1213                     }
   1214                 }
   1215             } else if (type == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
   1216                 if (isAttributeNameLink(context)) {
   1217                     isLinkable = true;
   1218                 }
   1219             } else if (type == DOMRegionContext.XML_TAG_NAME) {
   1220                 if (isElementNameLink(context)) {
   1221                     isLinkable = true;
   1222                 }
   1223             } else if (type == DOMRegionContext.XML_CONTENT) {
   1224                 Node parentNode = context.getNode().getParentNode();
   1225                 if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) {
   1226                     // Try to complete resources defined inline as text, such as
   1227                     // style definitions
   1228                     ITextRegion outer = context.getElementRegion();
   1229                     ITextRegion inner = context.getInnerRegion();
   1230                     int innerOffset = outer.getStart() + inner.getStart();
   1231                     int caretOffset = innerOffset + context.getInnerRegionCaretOffset();
   1232                     try {
   1233                         IRegion lineInfo = document.getLineInformationOfOffset(caretOffset);
   1234                         int lineStart = lineInfo.getOffset();
   1235                         int lineEnd = Math.min(lineStart + lineInfo.getLength(),
   1236                                 innerOffset + inner.getLength());
   1237 
   1238                         // Compute the resource URL
   1239                         int urlStart = -1;
   1240                         int offset = caretOffset;
   1241                         while (offset > lineStart) {
   1242                             char c = document.getChar(offset);
   1243                             if (c == '@') {
   1244                                 urlStart = offset;
   1245                                 break;
   1246                             } else if (!isValidResourceUrlChar(c)) {
   1247                                 break;
   1248                             }
   1249                             offset--;
   1250                         }
   1251 
   1252                         if (urlStart != -1) {
   1253                             offset = caretOffset;
   1254                             while (offset < lineEnd) {
   1255                                 if (!isValidResourceUrlChar(document.getChar(offset))) {
   1256                                     break;
   1257                                 }
   1258                                 offset++;
   1259                             }
   1260 
   1261                             int length = offset - urlStart;
   1262                             String url = document.get(urlStart, length);
   1263                             range = new Region(urlStart, length);
   1264                             return getResourceLinks(range, url);
   1265                         }
   1266                     } catch (BadLocationException e) {
   1267                         AdtPlugin.log(e, null);
   1268                     }
   1269                 }
   1270             }
   1271 
   1272             if (isLinkable) {
   1273                 IHyperlink hyperlink = new DeferredResolutionLink(context, range);
   1274                 if (hyperlink != null) {
   1275                     return new IHyperlink[] {
   1276                         hyperlink
   1277                     };
   1278                 }
   1279             }
   1280 
   1281             return null;
   1282         }
   1283     }
   1284 
   1285     private static boolean isValidResourceUrlChar(char c) {
   1286         return Character.isJavaIdentifierPart(c) || c == ':' || c == '/' || c == '.' || c == '+';
   1287 
   1288     }
   1289 
   1290     /** Detector for finding Android references in Java files */
   1291     public static class JavaResolver extends AbstractHyperlinkDetector {
   1292 
   1293         @Override
   1294         public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region,
   1295                 boolean canShowMultipleHyperlinks) {
   1296             // Most of this is identical to the builtin JavaElementHyperlinkDetector --
   1297             // everything down to the Android R filtering below
   1298 
   1299             ITextEditor textEditor = (ITextEditor) getAdapter(ITextEditor.class);
   1300             if (region == null || !(textEditor instanceof JavaEditor))
   1301                 return null;
   1302 
   1303             IAction openAction = textEditor.getAction("OpenEditor"); //$NON-NLS-1$
   1304             if (!(openAction instanceof SelectionDispatchAction))
   1305                 return null;
   1306 
   1307             int offset = region.getOffset();
   1308 
   1309             IJavaElement input = EditorUtility.getEditorInputJavaElement(textEditor, false);
   1310             if (input == null)
   1311                 return null;
   1312 
   1313             try {
   1314                 IDocument document = textEditor.getDocumentProvider().getDocument(
   1315                         textEditor.getEditorInput());
   1316                 IRegion wordRegion = JavaWordFinder.findWord(document, offset);
   1317                 if (wordRegion == null || wordRegion.getLength() == 0)
   1318                     return null;
   1319 
   1320                 IJavaElement[] elements = null;
   1321                 elements = ((ICodeAssist) input).codeSelect(wordRegion.getOffset(), wordRegion
   1322                         .getLength());
   1323 
   1324                 // Specific Android R class filtering:
   1325                 if (elements.length > 0) {
   1326                     IJavaElement element = elements[0];
   1327                     if (element.getElementType() == IJavaElement.FIELD) {
   1328                         IJavaElement unit = element.getAncestor(IJavaElement.COMPILATION_UNIT);
   1329                         if (unit == null) {
   1330                             // Probably in a binary; see if this is an android.R resource
   1331                             IJavaElement type = element.getAncestor(IJavaElement.TYPE);
   1332                             if (type != null && type.getParent() != null) {
   1333                                 IJavaElement parentType = type.getParent();
   1334                                 if (parentType.getElementType() == IJavaElement.CLASS_FILE) {
   1335                                     String pn = parentType.getElementName();
   1336                                     String prefix = FN_RESOURCE_BASE + "$"; //$NON-NLS-1$
   1337                                     if (pn.startsWith(prefix)) {
   1338                                         return createTypeLink(element, type, wordRegion, true);
   1339                                     }
   1340                                 }
   1341                             }
   1342                         } else if (FN_RESOURCE_CLASS.equals(unit.getElementName())) {
   1343                             // Yes, we're referencing the project R class.
   1344                             // Offer hyperlink navigation to XML resource files for
   1345                             // the various definitions
   1346                             IJavaElement type = element.getAncestor(IJavaElement.TYPE);
   1347                             if (type != null) {
   1348                                 return createTypeLink(element, type, wordRegion, false);
   1349                             }
   1350                         }
   1351                     }
   1352 
   1353                 }
   1354                 return null;
   1355             } catch (JavaModelException e) {
   1356                 return null;
   1357             }
   1358         }
   1359 
   1360         private IHyperlink[] createTypeLink(IJavaElement element, IJavaElement type,
   1361                 IRegion wordRegion, boolean isFrameworkResource) {
   1362             String typeName = type.getElementName();
   1363             // typeName will be "id", "layout", "string", etc
   1364             if (isFrameworkResource) {
   1365                 typeName = ANDROID_PKG + ':' + typeName;
   1366             }
   1367             String elementName = element.getElementName();
   1368             String url = '@' + typeName + '/' + elementName;
   1369             return getResourceLinks(wordRegion, url);
   1370         }
   1371     }
   1372 
   1373     /** Returns the editor applicable to this hyperlink detection */
   1374     private static IEditorPart getEditor() {
   1375         // I would like to be able to find this via getAdapter(TextEditor.class) but
   1376         // couldn't find a way to initialize the editor context from
   1377         // AndroidSourceViewerConfig#getHyperlinkDetectorTargets (which only has
   1378         // a TextViewer, not a TextEditor, instance).
   1379         //
   1380         // Therefore, for now, use a hack. This hack is reasonable because hyperlink
   1381         // resolvers are only run for the front-most visible window in the active
   1382         // workbench.
   1383         return AdtUtils.getActiveEditor();
   1384     }
   1385 
   1386     /** Returns the project applicable to this hyperlink detection */
   1387     private static IProject getProject() {
   1388         IFile file = AdtUtils.getActiveFile();
   1389         if (file != null) {
   1390             return file.getProject();
   1391         }
   1392 
   1393         return null;
   1394     }
   1395 
   1396     /**
   1397      * Hyperlink implementation which delays computing the actual file and offset target
   1398      * until it is asked to open the hyperlink
   1399      */
   1400     private static class DeferredResolutionLink implements IHyperlink {
   1401         private XmlContext mXmlContext;
   1402         private IRegion mRegion;
   1403 
   1404         public DeferredResolutionLink(XmlContext xmlContext, IRegion mRegion) {
   1405             super();
   1406             this.mXmlContext = xmlContext;
   1407             this.mRegion = mRegion;
   1408         }
   1409 
   1410         @Override
   1411         public IRegion getHyperlinkRegion() {
   1412             return mRegion;
   1413         }
   1414 
   1415         @Override
   1416         public String getHyperlinkText() {
   1417             return "Open XML Declaration";
   1418         }
   1419 
   1420         @Override
   1421         public String getTypeLabel() {
   1422             return null;
   1423         }
   1424 
   1425         @Override
   1426         public void open() {
   1427             // Lazily compute the location to open
   1428             if (mXmlContext != null && !Hyperlinks.open(mXmlContext)) {
   1429                 // Failed: display message to the user
   1430                 displayError("Could not open link");
   1431             }
   1432         }
   1433     }
   1434 
   1435     /**
   1436      * Hyperlink implementation which provides a link for a resource; the actual file name
   1437      * is known, but the value location within XML files is deferred until the link is
   1438      * actually opened.
   1439      */
   1440     static class ResourceLink implements IHyperlink {
   1441         private final String mLinkText;
   1442         private final IRegion mLinkRegion;
   1443         private final ResourceType mType;
   1444         private final String mName;
   1445         private final ResourceFile mFile;
   1446 
   1447         /**
   1448          * Constructs a new {@link ResourceLink}.
   1449          *
   1450          * @param linkText the description of the link to be shown in a popup when there
   1451          *            is more than one match
   1452          * @param linkRegion the region corresponding to the link source highlight
   1453          * @param file the target resource file containing the link definition
   1454          * @param type the type of resource being linked to
   1455          * @param name the name of the resource being linked to
   1456          */
   1457         public ResourceLink(String linkText, IRegion linkRegion, ResourceFile file,
   1458                 ResourceType type, String name) {
   1459             super();
   1460             mLinkText = linkText;
   1461             mLinkRegion = linkRegion;
   1462             mType = type;
   1463             mName = name;
   1464             mFile = file;
   1465         }
   1466 
   1467         @Override
   1468         public IRegion getHyperlinkRegion() {
   1469             return mLinkRegion;
   1470         }
   1471 
   1472         @Override
   1473         public String getHyperlinkText() {
   1474             // return "Open XML Declaration";
   1475             return mLinkText;
   1476         }
   1477 
   1478         @Override
   1479         public String getTypeLabel() {
   1480             return null;
   1481         }
   1482 
   1483         @Override
   1484         public void open() {
   1485             // We have to defer computation of ids until the link is clicked since we
   1486             // don't have a fast map lookup for these
   1487             if (mFile == null && mType == ResourceType.ID) {
   1488                 // Id's are handled specially because they are typically defined
   1489                 // inline (though they -can- be defined in the values folder above as well,
   1490                 // in which case we will prefer that definition)
   1491                 IProject project = getProject();
   1492                 Pair<IFile,IRegion> def = findIdDefinition(project, mName);
   1493                 if (def != null) {
   1494                     try {
   1495                         AdtPlugin.openFile(def.getFirst(), def.getSecond());
   1496                     } catch (PartInitException e) {
   1497                         AdtPlugin.log(e, null);
   1498                     }
   1499                     return;
   1500                 }
   1501 
   1502                 displayError(String.format("Could not find id %1$s", mName));
   1503                 return;
   1504             }
   1505 
   1506             IAbstractFile wrappedFile = mFile != null ? mFile.getFile() : null;
   1507             if (wrappedFile instanceof IFileWrapper) {
   1508                 IFile file = ((IFileWrapper) wrappedFile).getIFile();
   1509                 try {
   1510                     // Lazily search for the target?
   1511                     IRegion region = null;
   1512                     String extension = file.getFileExtension();
   1513                     if (mType != null && mName != null && EXT_XML.equals(extension)) {
   1514                         Pair<IFile, IRegion> target;
   1515                         if (mType == ResourceType.ID) {
   1516                             target = findIdInXml(mName, file);
   1517                         } else {
   1518                             target = findValueInXml(mType, mName, file);
   1519                         }
   1520                         if (target != null) {
   1521                             region = target.getSecond();
   1522                         }
   1523                     }
   1524                     AdtPlugin.openFile(file, region);
   1525                 } catch (PartInitException e) {
   1526                     AdtPlugin.log(e, null);
   1527                 }
   1528             } else if (wrappedFile instanceof FileWrapper) {
   1529                 File file = ((FileWrapper) wrappedFile);
   1530                 IPath path = new Path(file.getAbsolutePath());
   1531                 int offset = 0;
   1532                 // Lazily search for the target?
   1533                 if (mType != null && mName != null && EXT_XML.equals(path.getFileExtension())) {
   1534                     if (file.exists()) {
   1535                         Pair<File, Integer> target = findValueInXml(mType, mName, file);
   1536                         if (target != null && target.getSecond() != null) {
   1537                             offset = target.getSecond();
   1538                         }
   1539                     }
   1540                 }
   1541                 openPath(path, null, offset);
   1542             } else {
   1543                 throw new IllegalArgumentException("Invalid link parameters");
   1544             }
   1545         }
   1546 
   1547         ResourceFile getFile() {
   1548             return mFile;
   1549         }
   1550     }
   1551 
   1552     /**
   1553      * XML context containing node, potentially attribute, and text regions surrounding a
   1554      * particular caret offset
   1555      */
   1556     private static class XmlContext {
   1557         private final Node mNode;
   1558         private final Element mElement;
   1559         private final Attr mAttribute;
   1560         private final IStructuredDocumentRegion mOuterRegion;
   1561         private final ITextRegion mInnerRegion;
   1562         private final int mInnerRegionOffset;
   1563 
   1564         public XmlContext(Node node, Element element, Attr attribute,
   1565                 IStructuredDocumentRegion outerRegion,
   1566                 ITextRegion innerRegion, int innerRegionOffset) {
   1567             super();
   1568             mNode = node;
   1569             mElement = element;
   1570             mAttribute = attribute;
   1571             mOuterRegion = outerRegion;
   1572             mInnerRegion = innerRegion;
   1573             mInnerRegionOffset = innerRegionOffset;
   1574         }
   1575 
   1576         /**
   1577          * Gets the current node, never null
   1578          *
   1579          * @return the surrounding node
   1580          */
   1581         public Node getNode() {
   1582             return mNode;
   1583         }
   1584 
   1585 
   1586         /**
   1587          * Gets the current node, may be null
   1588          *
   1589          * @return the surrounding node
   1590          */
   1591         public Element getElement() {
   1592             return mElement;
   1593         }
   1594 
   1595         /**
   1596          * Returns the current attribute, or null if we are not over an attribute
   1597          *
   1598          * @return the attribute, or null
   1599          */
   1600         public Attr getAttribute() {
   1601             return mAttribute;
   1602         }
   1603 
   1604         /**
   1605          * Gets the region of the element
   1606          *
   1607          * @return the region of the surrounding element, never null
   1608          */
   1609         public ITextRegion getElementRegion() {
   1610             return mOuterRegion;
   1611         }
   1612 
   1613         /**
   1614          * Gets the inner region, which can be the tag name, an attribute name, an
   1615          * attribute value, or some other portion of an XML element
   1616          * @return the inner region, never null
   1617          */
   1618         public ITextRegion getInnerRegion() {
   1619             return mInnerRegion;
   1620         }
   1621 
   1622         /**
   1623          * Gets the caret offset relative to the inner region
   1624          *
   1625          * @return the offset relative to the inner region
   1626          */
   1627         public int getInnerRegionCaretOffset() {
   1628             return mInnerRegionOffset;
   1629         }
   1630 
   1631         /**
   1632          * Returns a range with suffix whitespace stripped out
   1633          *
   1634          * @param document the document containing the regions
   1635          * @return the range of the inner region, minus any whitespace at the end
   1636          */
   1637         public IRegion getInnerRange(IDocument document) {
   1638             int start = mOuterRegion.getStart() + mInnerRegion.getStart();
   1639             int length = mInnerRegion.getLength();
   1640             try {
   1641                 String s = document.get(start, length);
   1642                 for (int i = s.length() - 1; i >= 0; i--) {
   1643                     if (Character.isWhitespace(s.charAt(i))) {
   1644                         length--;
   1645                     }
   1646                 }
   1647             } catch (BadLocationException e) {
   1648                 AdtPlugin.log(e, ""); //$NON-NLS-1$
   1649             }
   1650             return new Region(start, length);
   1651         }
   1652 
   1653         /**
   1654          * Returns the node the cursor is currently on in the document. null if no node is
   1655          * selected
   1656          */
   1657         private static XmlContext find(IDocument document, int offset) {
   1658             // Loosely based on getCurrentNode and getCurrentAttr in the WST's
   1659             // XMLHyperlinkDetector.
   1660             IndexedRegion inode = null;
   1661             IStructuredModel model = null;
   1662             try {
   1663                 model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
   1664                 if (model != null) {
   1665                     inode = model.getIndexedRegion(offset);
   1666                     if (inode == null) {
   1667                         inode = model.getIndexedRegion(offset - 1);
   1668                     }
   1669 
   1670                     if (inode instanceof Element) {
   1671                         Element element = (Element) inode;
   1672                         Attr attribute = null;
   1673                         if (element.hasAttributes()) {
   1674                             NamedNodeMap attrs = element.getAttributes();
   1675                             // go through each attribute in node and if attribute contains
   1676                             // offset, return that attribute
   1677                             for (int i = 0; i < attrs.getLength(); ++i) {
   1678                                 // assumption that if parent node is of type IndexedRegion,
   1679                                 // then its attributes will also be of type IndexedRegion
   1680                                 IndexedRegion attRegion = (IndexedRegion) attrs.item(i);
   1681                                 if (attRegion.contains(offset)) {
   1682                                     attribute = (Attr) attrs.item(i);
   1683                                     break;
   1684                                 }
   1685                             }
   1686                         }
   1687 
   1688                         IStructuredDocument doc = model.getStructuredDocument();
   1689                         IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset);
   1690                         if (region != null
   1691                                 && DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
   1692                             ITextRegion subRegion = region.getRegionAtCharacterOffset(offset);
   1693                             if (subRegion == null) {
   1694                                 return null;
   1695                             }
   1696                             int regionStart = region.getStartOffset();
   1697                             int subregionStart = subRegion.getStart();
   1698                             int relativeOffset = offset - (regionStart + subregionStart);
   1699                             return new XmlContext(element, element, attribute, region, subRegion,
   1700                                     relativeOffset);
   1701                         }
   1702                     } else if (inode instanceof Node) {
   1703                         IStructuredDocument doc = model.getStructuredDocument();
   1704                         IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset);
   1705                         if (region != null
   1706                                 && DOMRegionContext.XML_CONTENT.equals(region.getType())) {
   1707                             ITextRegion subRegion = region.getRegionAtCharacterOffset(offset);
   1708                             int regionStart = region.getStartOffset();
   1709                             int subregionStart = subRegion.getStart();
   1710                             int relativeOffset = offset - (regionStart + subregionStart);
   1711                             return new XmlContext((Node) inode, null, null, region, subRegion,
   1712                                     relativeOffset);
   1713                         }
   1714 
   1715                     }
   1716                 }
   1717             } finally {
   1718                 if (model != null) {
   1719                     model.releaseFromRead();
   1720                 }
   1721             }
   1722 
   1723             return null;
   1724         }
   1725     }
   1726 
   1727     /**
   1728      * DOM parser which records offsets in the element nodes such that it can return
   1729      * offsets for elements later
   1730      */
   1731     private static final class OffsetTrackingParser extends DOMParser {
   1732 
   1733         private static final String KEY_OFFSET = "offset"; //$NON-NLS-1$
   1734 
   1735         private static final String KEY_NODE =
   1736             "http://apache.org/xml/properties/dom/current-element-node"; //$NON-NLS-1$
   1737 
   1738         private XMLLocator mLocator;
   1739 
   1740         public OffsetTrackingParser() throws SAXException {
   1741             this.setFeature("http://apache.org/xml/features/dom/defer-node-expansion",//$NON-NLS-1$
   1742                     false);
   1743         }
   1744 
   1745         public int getOffset(Node node) {
   1746             Integer offset = (Integer) node.getUserData(KEY_OFFSET);
   1747             if (offset != null) {
   1748                 return offset;
   1749             }
   1750 
   1751             return -1;
   1752         }
   1753 
   1754         @Override
   1755         public void startElement(QName elementQName, XMLAttributes attrList, Augmentations augs)
   1756                 throws XNIException {
   1757             int offset = mLocator.getCharacterOffset();
   1758             super.startElement(elementQName, attrList, augs);
   1759 
   1760             try {
   1761                 Node node = (Node) this.getProperty(KEY_NODE);
   1762                 if (node != null) {
   1763                     node.setUserData(KEY_OFFSET, offset, null);
   1764                 }
   1765             } catch (org.xml.sax.SAXException ex) {
   1766                 AdtPlugin.log(ex, ""); //$NON-NLS-1$
   1767             }
   1768         }
   1769 
   1770         @Override
   1771         public void startDocument(XMLLocator locator, String encoding,
   1772                 NamespaceContext namespaceContext, Augmentations augs) throws XNIException {
   1773             super.startDocument(locator, encoding, namespaceContext, augs);
   1774             mLocator = locator;
   1775         }
   1776     }
   1777 }
   1778