blob: 5d431615c5ea945d8c6075a3b4cb0af1771fa721 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 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.editor;
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.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
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 extends AbstractHyperlinkDetector
{
/*
* (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 and only
// underline the actual value, not the quotes.
if (StringUtils.isQuoted(attValue))
{
regLength = regLength - 2;
regOffset++;
}
hyperRegion = new Region(regOffset, regLength);
}
}
break;
default :
// Do nothing.
break;
}
return hyperRegion;
}
}