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