| /******************************************************************************* |
| * Copyright (c) 2004, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - Initial API and implementation |
| * Jens Lukowski/Innoopract - initial renaming/restructuring |
| *******************************************************************************/ |
| |
| package org.eclipse.wst.xsd.ui.internal; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.hyperlink.IHyperlink; |
| import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.utils.StringUtils; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| /** |
| * Base class for hyperlinks detectors. Provides a framework and common code for |
| * hyperlink detectors. TODO: Can we pull this class further up the inheritance |
| * hierarchy? |
| */ |
| public abstract class BaseHyperlinkDetector implements IHyperlinkDetector |
| { |
| /* |
| * (non-Javadoc) |
| */ |
| public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) |
| { |
| if (region == null || textViewer == null) |
| { |
| return null; |
| } |
| |
| List hyperlinks = new ArrayList(0); |
| IDocument document = textViewer.getDocument(); |
| int offset = region.getOffset(); |
| |
| IDOMNode node = getCurrentNode(document, offset); |
| |
| // This call allows us to determine whether an attribute is linkable, |
| // without incurring the cost of asking for the target component. |
| |
| if (!isLinkable(node)) |
| { |
| return null; |
| } |
| |
| IRegion hyperlinkRegion = getHyperlinkRegion(node); |
| |
| // createHyperlink is a template method. Derived classes, should override. |
| |
| IHyperlink hyperlink = createHyperlink(document, node, hyperlinkRegion); |
| |
| if (hyperlink != null) |
| { |
| hyperlinks.add(hyperlink); |
| } |
| |
| if (hyperlinks.size() == 0) |
| { |
| return null; |
| } |
| |
| return (IHyperlink[]) hyperlinks.toArray(new IHyperlink[0]); |
| } |
| |
| /** |
| * Determines whether a node is "linkable" that is, the component it refers to |
| * can be the target of a "go to definition" navigation. |
| * |
| * @param node the node to test, must not be null; |
| * @return true if the node is linkable, false otherwise. |
| */ |
| private boolean isLinkable(IDOMNode node) |
| { |
| if (node == null) |
| { |
| return false; |
| } |
| |
| short nodeType = node.getNodeType(); |
| |
| boolean isLinkable = false; |
| |
| if (nodeType == Node.ATTRIBUTE_NODE) |
| { |
| IDOMAttr attr = (IDOMAttr) node; |
| String name = attr.getName(); |
| |
| // isLinkableAttribute is a template method. Derived classes should |
| // override. |
| |
| isLinkable = isLinkableAttribute(name); |
| } |
| |
| return isLinkable; |
| } |
| |
| /** |
| * Determines whether an attribute is "linkable" that is, the component it |
| * points to can be the target of a "go to definition" navigation. Derived |
| * classes should override. |
| * |
| * @param name the attribute name. Must not be null. |
| * @return true if the attribute is linkable, false otherwise. |
| */ |
| protected abstract boolean isLinkableAttribute(String name); |
| |
| /** |
| * Creates a hyperlink based on the selected node. Derived classes should |
| * override. |
| * |
| * @param document the source document. |
| * @param node the node under the cursor. |
| * @param region the text region to use to create the hyperlink. |
| * @return a new IHyperlink for the node or null if one cannot be created. |
| */ |
| protected abstract IHyperlink createHyperlink(IDocument document, IDOMNode node, IRegion region); |
| |
| /** |
| * Locates the attribute node under the cursor. |
| * |
| * @param offset the cursor offset. |
| * @param parent the parent node |
| * @return an IDOMNode representing the attribute if one is found at the |
| * offset or null otherwise. |
| */ |
| protected IDOMNode getAttributeNode(int offset, IDOMNode parent) |
| { |
| IDOMAttr attrNode = null; |
| NamedNodeMap map = parent.getAttributes(); |
| |
| for (int index = 0; index < map.getLength(); index++) |
| { |
| attrNode = (IDOMAttr) map.item(index); |
| boolean located = attrNode.contains(offset); |
| if (located) |
| { |
| if (attrNode.hasNameOnly()) |
| { |
| attrNode = null; |
| } |
| break; |
| } |
| } |
| |
| if (attrNode == null) |
| { |
| return parent; |
| } |
| return attrNode; |
| } |
| |
| /** |
| * Returns the node the cursor is currently on in the document or null if no |
| * node is selected |
| * |
| * @param offset the current cursor offset. |
| * @return IDOMNode either element, doctype, text, attribute or null |
| */ |
| private IDOMNode getCurrentNode(IDocument document, int offset) |
| { |
| IndexedRegion inode = null; |
| IStructuredModel sModel = null; |
| |
| try |
| { |
| sModel = StructuredModelManager.getModelManager().getExistingModelForRead(document); |
| inode = sModel.getIndexedRegion(offset); |
| if (inode == null) |
| inode = sModel.getIndexedRegion(offset - 1); |
| } |
| finally |
| { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| |
| if (inode instanceof IDOMNode) |
| { |
| IDOMNode node = (IDOMNode) inode; |
| |
| if (node.hasAttributes()) |
| { |
| node = getAttributeNode(offset, node); |
| } |
| return node; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get the text region corresponding to an IDOMNode. |
| * |
| * @param node the node for which we want the text region. Must not be null. |
| * @return an IRegion for the node, or null if the node is not recognized. |
| */ |
| protected IRegion getHyperlinkRegion(IDOMNode node) |
| { |
| if (node == null) |
| { |
| return null; |
| } |
| |
| IRegion hyperRegion = null; |
| short nodeType = node.getNodeType(); |
| |
| switch (nodeType) |
| { |
| case Node.ELEMENT_NODE : |
| { |
| hyperRegion = new Region(node.getStartOffset(), node.getEndOffset() - node.getStartOffset()); |
| } |
| break; |
| case Node.ATTRIBUTE_NODE : |
| { |
| IDOMAttr att = (IDOMAttr) node; |
| |
| int regOffset = att.getValueRegionStartOffset(); |
| |
| // ISSUE: We are using a deprecated method here. Is there |
| // a better way to get what we need? |
| |
| ITextRegion valueRegion = att.getValueRegion(); |
| if (valueRegion != null) |
| { |
| int regLength = valueRegion.getTextLength(); |
| String attValue = att.getValueRegionText(); |
| |
| // Do not include quotes in attribute value region. |
| if (StringUtils.isQuoted(attValue)) |
| { |
| regOffset = ++regOffset; |
| regLength = regLength - 2; |
| } |
| hyperRegion = new Region(regOffset, regLength); |
| } |
| } |
| break; |
| default : |
| // Do nothing. |
| break; |
| } |
| |
| return hyperRegion; |
| } |
| } |