package com.anjlab.eclipse.tapestry5.hyperlink;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import com.anjlab.eclipse.tapestry5.Activator;
import com.anjlab.eclipse.tapestry5.Component;
import com.anjlab.eclipse.tapestry5.EclipseUtils;
import com.anjlab.eclipse.tapestry5.Parameter;
import com.anjlab.eclipse.tapestry5.TapestryContext;
import com.anjlab.eclipse.tapestry5.TapestryContextScope;
import com.anjlab.eclipse.tapestry5.TapestryFile;
import com.anjlab.eclipse.tapestry5.TapestryUtils;
import com.anjlab.eclipse.tapestry5.TextEditorCallback;
import com.anjlab.eclipse.tapestry5.hyperlink.XmlFragment.XmlAtomicFragment;
import com.anjlab.eclipse.tapestry5.hyperlink.XmlFragment.XmlAttributeName;
import com.anjlab.eclipse.tapestry5.hyperlink.XmlFragment.XmlAttributeValue;
import com.anjlab.eclipse.tapestry5.hyperlink.XmlFragment.XmlContextFragment;
import com.anjlab.eclipse.tapestry5.hyperlink.XmlFragment.XmlTagName;
public class TapestryComponentHyperlinkDetector extends AbstractHyperlinkDetector
{
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks)
{
// TODO Cache recent XmlContextFragment?
final XmlContextFragment xmlContextFragment = getXmlFragment(textViewer, region);
final XmlAtomicFragment atomicFragment = xmlContextFragment.getFragmentAt(region.getOffset());
if (atomicFragment == null)
{
return null;
}
final IWorkbenchWindow window = EclipseUtils.getWorkbenchWindow(textViewer.getTextWidget().getShell());
if (window == null)
{
return null;
}
final TapestryContextScope targetScope = findTargetContext(window, atomicFragment);
if (targetScope == null)
{
return null;
}
if (atomicFragment instanceof XmlTagName)
{
// TODO Check if it's a tapestry:parameter
return filesFromContext(atomicFragment, targetScope.context);
}
else if (atomicFragment instanceof XmlAttributeName)
{
if (!StringUtils.isEmpty(atomicFragment.getFQName().prefix))
{
// Ignore prefixed attributes
return null;
}
// XXX This works very slow now:
// - See TapestryProject#findComponentContext(..., ..., ...)
// - Cache parameters in specification once retrieved?
// Find parameter in target context
// TODO Also search between parameters of applied t:mixins
Parameter parameter = targetScope.context.getSpecification()
.getParameter(targetScope.project, atomicFragment.getFQName().name);
return parameter != null
? new IHyperlink[]
{
new MemberHyperlink(atomicFragment,
parameter.getSpecification().getTapestryContext().getJavaFile(),
parameter)
}
: null;
}
else if (atomicFragment instanceof XmlAttributeValue)
{
XmlAttributeName attributeName = ((XmlAttributeValue) atomicFragment).attributeName;
if (TapestryUtils.isTapestryDefaultNamespace(attributeName.xmlTag.resolveNamespacePrefix(attributeName.getFQName().prefix)))
{
// 1) t:type => show files from tapestry context
if (StringUtils.equals("type", attributeName.getFQName().name))
{
return filesFromContext(atomicFragment, targetScope.context);
}
// 2) t:id => open field declaration (if any)
if (StringUtils.equals("id", attributeName.getFQName().name))
{
TapestryContext currentContext = Activator.getDefault().getTapestryContext(window);
if (currentContext == null)
{
return null;
}
// @Component or @InjectComponent
for (Component component : currentContext.getSpecification().getComponents())
{
if (StringUtils.equalsIgnoreCase(atomicFragment.value.value, component.getId()))
{
return new IHyperlink[]
{
new MemberHyperlink(atomicFragment,
component.getSpecification().getTapestryContext().getJavaFile(),
component)
};
}
}
return null;
}
// 3) t:mixins => show separate hyperlink for every referenced mixin
if (StringUtils.equals("mixins", attributeName.getFQName().name))
{
// TODO
return null;
}
}
Parameter parameter = targetScope.context.getSpecification().getParameter(
targetScope.project, attributeName.getFQName().name);
if (parameter == null)
{
return null;
}
// It there's a parameter with in target context and this is its value,
// then find hyperlink target depending on binding prefix (default and actual)
}
return new IHyperlink[]
{
new IHyperlink()
{
@Override
public void open()
{
EclipseUtils.openInformation(window, atomicFragment.toString());
}
@Override
public String getTypeLabel()
{
return atomicFragment.toString();
}
@Override
public String getHyperlinkText()
{
return atomicFragment.toString();
}
@Override
public IRegion getHyperlinkRegion()
{
return atomicFragment.region;
}
}
};
}
private IHyperlink[] filesFromContext(final XmlAtomicFragment atomicFragment, final TapestryContext targetContext)
{
final List<TapestryFile> files = targetContext.getFiles();
IHyperlink[] links = new IHyperlink[files.size()];
for (int i = 0; i < files.size(); i++)
{
final int index = i;
links[index] = new IHyperlink()
{
@Override
public void open()
{
final TapestryFile tapestryFile = files.get(index);
EclipseUtils.openFile(PlatformUI.getWorkbench().getActiveWorkbenchWindow(), tapestryFile, new TextEditorCallback()
{
@Override
public void editorOpened(ITextEditor textEditor)
{
// Highlight Java type when opening Java file for the first time
IRegion highlightedRange = textEditor.getHighlightRange();
if ((highlightedRange == null
|| highlightedRange.getOffset() == 0 && highlightedRange.getLength() == 0)
&& targetContext.getJavaFile() == tapestryFile)
{
IType type = targetContext.getJavaType();
if (type != null)
{
try
{
ISourceRange nameRange = type.getNameRange();
if (nameRange != null)
{
textEditor.selectAndReveal(nameRange.getOffset(),
nameRange.getLength());
}
}
catch (JavaModelException e)
{
// Ignore
}
}
}
}
});
}
@Override
public String getTypeLabel()
{
return files.get(index).getName();
}
@Override
public String getHyperlinkText()
{
return files.get(index).getName();
}
@Override
public IRegion getHyperlinkRegion()
{
return atomicFragment.region;
}
};
}
return links;
}
private TapestryContextScope findTargetContext(IWorkbenchWindow window, XmlAtomicFragment atomicFragment)
{
if (!atomicFragment.hasValue())
{
return null;
}
if (atomicFragment.xmlTag.isComment())
{
return null;
}
String componentName = TapestryUtils.getComponentName(window, new XmlTagNodeAdapter(atomicFragment.xmlTag));
if (componentName == null)
{
return null;
}
return TapestryUtils.getTapestryContext(window, componentName);
}
protected XmlContextFragment getXmlFragment(ITextViewer textViewer, IRegion region)
{
if (region == null || textViewer == null)
{
return null;
}
IDocument document = textViewer.getDocument();
if (document == null)
{
return null;
}
return new XmlContextFragment(document, region);
}
}