/*******************************************************************************
* Copyright (c) 2007, 2010 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.ui.editor.hyperlink;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.IType;
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.internal.provisional.IndexedRegion;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.core.io.Resource;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.DefaultBeanDefinitionRegistry;
import org.springframework.ide.eclipse.beans.core.internal.model.namespaces.DelegatingNamespaceHandlerResolver;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils;
import org.springframework.ide.eclipse.beans.ui.editor.contentassist.IContentAssistCalculator;
import org.springframework.ide.eclipse.beans.ui.editor.util.BeansEditorUtils;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Support class for implementing custom {@link IHyperlinkDetector}s. Calculation of individual hyperlinks is done via
* {@link IHyperlinkCalculator} strategy interfaces respectively.
* <p>
* Provides the {@link #registerHyperlinkCalculator} methods for registering a {@link IHyperlinkCalculator} to handle a
* specific element.
* @author Christian Dupuis
* @since 2.0.2
*/
@SuppressWarnings("restriction")
public class NamespaceHyperlinkDetectorSupport extends AbstractHyperlinkDetector {
private static final Method FIND_PARSER_FOR_ELEMENT_METHOD;
static {
Method method = null;
try {
method = NamespaceHandlerSupport.class.getDeclaredMethod("findParserForElement", Element.class,
ParserContext.class);
method.setAccessible(true);
}
catch (Exception e) {
}
FIND_PARSER_FOR_ELEMENT_METHOD = method;
}
/**
* Stores the {@link IHyperlinkCalculator} keyed the return value of a call to
* {@link #createRegisteredName(String, String)}.
*/
private Map<String, IHyperlinkCalculator> calculators = new HashMap<String, IHyperlinkCalculator>();
/**
* Calculates hyperlink for the given request by delegating the request to a located {@link IHyperlinkCalculator}
* returned by {@link #locateHyperlinkCalculator(String, String)}.
*/
public IHyperlink createHyperlink(String name, String target, Node node, Node parentNode, IDocument document,
ITextViewer textViewer, IRegion hyperlinkRegion, IRegion cursor) {
String parentNodeName = null;
String parentNamespaceUri = null;
if (parentNode != null) {
parentNodeName = parentNode.getLocalName();
parentNamespaceUri = parentNode.getNamespaceURI();
}
IHyperlinkCalculator calculator = locateHyperlinkCalculator(parentNamespaceUri, parentNodeName,
node.getLocalName(), name);
if (calculator != null) {
return calculator.createHyperlink(name, target, node, parentNode, document, textViewer, hyperlinkRegion,
cursor);
}
return null;
}
/**
* Calculates multiple hyperlinks for the given request by delegating the request to a located
* {@link IHyperlinkCalculator} returned by {@link #locateHyperlinkCalculator(String, String)}.
*/
public IHyperlink[] createHyperlinks(String name, String target, Node node, Node parentNode, IDocument document,
ITextViewer textViewer, IRegion hyperlinkRegion, IRegion cursor) {
String parentNodeName = null;
String parentNamespaceUri = null;
if (parentNode != null) {
parentNodeName = parentNode.getLocalName();
parentNamespaceUri = parentNode.getNamespaceURI();
}
IHyperlinkCalculator calculator = locateHyperlinkCalculator(parentNamespaceUri, parentNodeName,
node.getLocalName(), name);
if (calculator instanceof IMultiHyperlinkCalculator) {
return ((IMultiHyperlinkCalculator) calculator).createHyperlinks(name, target, node, parentNode, document,
textViewer, hyperlinkRegion, cursor);
}
else if (calculator != null) {
return new IHyperlink[] { calculator.createHyperlink(name, target, node, parentNode, document, textViewer,
hyperlinkRegion, cursor) };
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
IHyperlink[] hyperlinks = super.detectHyperlinks(textViewer, region, canShowMultipleHyperlinks);
if (hyperlinks == null) {
hyperlinks = new IHyperlink[0];
}
// Special support opening NamespaceHandlers
IDocument document = textViewer.getDocument();
Node currentNode = BeansEditorUtils.getNodeByOffset(document, region.getOffset());
if ((canShowMultipleHyperlinks || hyperlinks.length == 0)
&& BeansEditorUtils.isElementAtOffset(currentNode, region.getOffset())
&& currentNode instanceof Element) {
hyperlinks = createBeanDefinitionParserHyperlink(hyperlinks, document, currentNode);
}
return hyperlinks;
}
/**
* Empty implementation. To be overridden by subclasses.
*/
public void init() {
}
/**
* Checks if a {@link IHyperlinkCalculator} for the given attribute has been registered.
*/
public boolean isLinkableAttr(Attr attr) {
Node parentNode = attr.getOwnerElement().getParentNode();
String parentNodeName = null;
String parentNamespaceUri = null;
if (parentNode != null) {
parentNodeName = parentNode.getLocalName();
parentNamespaceUri = parentNode.getNamespaceURI();
}
return locateHyperlinkCalculator(parentNamespaceUri, parentNodeName, attr.getOwnerElement().getLocalName(),
attr.getLocalName()) != null;
}
/**
* Locates {@link IHyperlink} instances of open up {@link BeanDefinitionParser}s for selected namespace elements.
*/
private IHyperlink[] createBeanDefinitionParserHyperlink(IHyperlink[] hyperlinks, IDocument document,
Node currentNode) {
String namespaceUri = currentNode.getNamespaceURI();
IFile file = BeansEditorUtils.getFile(document);
IBeansConfig config = BeansCorePlugin.getModel().getConfig(file, true);
// Only search for non-default namespace handlers
if (config != null && namespaceUri != null && !namespaceUri.equals(NamespaceUtils.DEFAULT_NAMESPACE_URI)) {
ClassLoader cl = BeansCorePlugin.getClassLoader();
if (NamespaceUtils.useNamespacesFromClasspath(file.getProject())) {
cl = JdtUtils.getClassLoader(file.getProject(), cl);
}
// Construct NamespaceHandlerResolver using the project's classpath if configured
DelegatingNamespaceHandlerResolver resolver = new DelegatingNamespaceHandlerResolver(cl, config);
NamespaceHandler handler = resolver.resolve(namespaceUri);
if (handler instanceof NamespaceHandlerSupport) {
XmlReaderContext readerContext = new XmlReaderContext((Resource) config.getAdapter(Resource.class),
new NoOpProblemReporter(), null, null, new XmlBeanDefinitionReader(
new DefaultBeanDefinitionRegistry()), resolver);
Object parser = ReflectionUtils.invokeMethod(FIND_PARSER_FOR_ELEMENT_METHOD, handler,
(Element) currentNode, new ParserContext(readerContext, new BeanDefinitionParserDelegate(
readerContext)));
if (parser != null) {
IType type = JdtUtils.getJavaType(file.getProject(), parser.getClass().getName());
if (type != null) {
List<IHyperlink> links = new ArrayList<IHyperlink>(Arrays.asList(hyperlinks));
links.add(new JavaElementHyperlink(
new Region(((IndexedRegion) currentNode).getStartOffset() + 1, currentNode
.getNodeName().length()), type));
hyperlinks = links.toArray(new IHyperlink[links.size()]);
}
}
}
}
return hyperlinks;
}
/**
* Locates a {@link IContentAssistCalculator} in the {@link #calculators} store for the given <code>nodeName</code>
* and <code>attributeName</code>.
*/
private IHyperlinkCalculator locateHyperlinkCalculator(String parentNamespaceUri, String parentNodeName,
String nodeName, String attributeName) {
String key = createRegisteredName(parentNamespaceUri, parentNodeName, nodeName, attributeName);
if (this.calculators.containsKey(key)) {
return this.calculators.get(key);
}
key = createRegisteredName(null, null, nodeName, attributeName);
if (this.calculators.containsKey(key)) {
return this.calculators.get(key);
}
key = createRegisteredName(null, null, null, attributeName);
if (this.calculators.containsKey(key)) {
return this.calculators.get(key);
}
return null;
}
/**
* Creates a name from the <code>nodeName</code> and <code>attributeName</code>.
* @param nodeName the local (non-namespace qualified) name of the element
* @param attributeName the local (non-namespace qualified) name of the attribute
*/
protected String createRegisteredName(String parentNamespaceUri, String parentNodeName, String nodeName,
String attributeName) {
StringBuilder builder = new StringBuilder();
if (StringUtils.hasText(parentNamespaceUri)) {
builder.append("/parentNamespaceUri=");
builder.append(parentNamespaceUri);
}
else {
builder.append("/parentNamespaceUri=");
builder.append("*");
}
if (StringUtils.hasText(parentNodeName)) {
builder.append("/parentNodeName=");
builder.append(parentNodeName);
}
else {
builder.append("/parentNodeName=");
builder.append("*");
}
if (StringUtils.hasText(nodeName)) {
builder.append("/nodeName=");
builder.append(nodeName);
}
else {
builder.append("/nodeName=");
builder.append("*");
}
if (StringUtils.hasText(attributeName)) {
builder.append("/attribute=");
builder.append(attributeName);
}
return builder.toString();
}
/**
* Subclasses can call this to register the supplied {@link IHyperlinkCalculator} to handle the specified attribute.
* The attribute name is the local (non-namespace qualified) name.
*/
protected void registerHyperlinkCalculator(String attributeName, IHyperlinkCalculator calculator) {
registerHyperlinkCalculator(null, attributeName, calculator);
}
/**
* Subclasses can call this to register the supplied {@link IHyperlinkCalculator} to handle the specified attribute
* <b>only</b> for a given element. The attribute name is the local (non-namespace qualified) name.
*/
protected void registerHyperlinkCalculator(String nodeName, String attributeName, IHyperlinkCalculator calculator) {
registerHyperlinkCalculator(null, null, nodeName, attributeName, calculator);
}
/**
* Subclasses can call this to register the supplied {@link IHyperlinkCalculator} to handle the specified attribute
* <b>only</b> for a given element. The attribute name is the local (non-namespace qualified) name.
*/
protected void registerHyperlinkCalculator(String parentNamespaceUri, String parentNodeName, String nodeName,
String attributeName, IHyperlinkCalculator calculator) {
this.calculators.put(createRegisteredName(parentNamespaceUri, parentNodeName, nodeName, attributeName),
calculator);
}
private static final class NoOpProblemReporter implements ProblemReporter {
public void fatal(Problem problem) {
}
public void error(Problem problem) {
}
public void warning(Problem problem) {
}
}
}