/*******************************************************************************
* Copyright (c) 2008-2012 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.seam.text.ext.hyperlink;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.jboss.tools.common.el.core.model.ELArgumentInvocation;
import org.jboss.tools.common.el.core.model.ELExpression;
import org.jboss.tools.common.el.core.model.ELInvocationExpression;
import org.jboss.tools.common.el.core.model.ELPropertyInvocation;
import org.jboss.tools.common.el.core.resolver.ElVarSearcher;
import org.jboss.tools.common.el.core.resolver.Var;
import org.jboss.tools.common.text.ext.hyperlink.AbstractHyperlinkPartitioner;
import org.jboss.tools.common.text.ext.hyperlink.HyperlinkRegion;
import org.jboss.tools.common.text.ext.hyperlink.IHyperLinkPartitionPriority;
import org.jboss.tools.common.text.ext.hyperlink.IHyperlinkPartitionRecognizer;
import org.jboss.tools.common.text.ext.hyperlink.IHyperlinkRegion;
import org.jboss.tools.common.text.ext.util.StructuredModelWrapper;
import org.jboss.tools.common.text.ext.util.Utils;
import org.jboss.tools.jst.web.ui.internal.text.ext.hyperlink.jsp.JSPRootHyperlinkPartitioner;
import org.jboss.tools.seam.core.ISeamContextVariable;
import org.jboss.tools.seam.core.ISeamMessages;
import org.jboss.tools.seam.core.ISeamProject;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.internal.core.el.SeamELCompletionEngine;
import org.jboss.tools.seam.text.ext.SeamExtPlugin;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
/**
* @author Jeremy
*/
public class SeamBeanHyperlinkPartitioner extends AbstractHyperlinkPartitioner implements IHyperlinkPartitionRecognizer, IHyperLinkPartitionPriority {
public static final String SEAM_MESSAGES_BEAN_PARTITION = "org.jboss.tools.seam.text.ext.SEAM_MESSAGES_BEAN";
/**
* @see com.ibm.sse.editor.hyperlink.AbstractHyperlinkPartitioner#parse(org.eclipse.jface.text.IDocument, com.ibm.sse.editor.extensions.hyperlink.IHyperlinkRegion)
*/
protected IHyperlinkRegion parse(IDocument document, int offset, IHyperlinkRegion superRegion) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
if (!recognize(document, offset, superRegion)) return null;
IHyperlinkRegion r = getRegion(document, offset);
if (r == null) return null;
r = getWordRegion(document, offset);
if (r == null) return null;
Map<String, ISeamMessages> messages = findMessagesComponents(document, offset);
if (messages != null && !messages.isEmpty()) {
String axis = getAxis(document, offset);
String contentType = superRegion.getContentType();
String type = SEAM_MESSAGES_BEAN_PARTITION;
return new HyperlinkRegion(r.getOffset(), r.getLength(), axis, contentType, type);
}
return null;
} finally {
smw.dispose();
}
}
protected String getAxis(IDocument document, int offset) {
return JSPRootHyperlinkPartitioner.computeAxis(document, offset) + "/";
}
public static IHyperlinkRegion getWordRegion (IDocument document, final int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
Node n = Utils.findNodeForOffset(xmlDocument, offset);
if (n == null || !(n instanceof Attr || n instanceof Text)) return null;
int start = Utils.getValueStart(n);
int end = Utils.getValueEnd(n);
if(start < 0 || start > end || start > offset) return null;
String attrText = document.get(start, end - start);
StringBuffer sb = new StringBuffer(attrText);
//find start of bean property
int bStart = offset - start;
while (bStart >= 0) {
if (!Character.isJavaIdentifierPart(sb.charAt(bStart))) {
bStart++;
break;
}
if (bStart == 0) break;
bStart--;
}
// find end of bean property
int bEnd = offset - start;
while (bEnd < sb.length()) {
if (!Character.isJavaIdentifierPart(sb.charAt(bEnd)))
break;
bEnd++;
}
int propStart = bStart + start;
int propLength = bEnd - bStart;
if (propStart > offset || propStart + propLength < offset) return null;
IHyperlinkRegion region = new HyperlinkRegion(propStart, propLength, null, null, null);
return region;
} catch (BadLocationException x) {
SeamExtPlugin.getPluginLog().logError(x);
return null;
} finally {
smw.dispose();
}
}
public static IHyperlinkRegion getRegionPart(IDocument document, final int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
Node n = Utils.findNodeForOffset(xmlDocument, offset);
if (n == null || !(n instanceof Attr || n instanceof Text)) return null;
int start = Utils.getValueStart(n);
int end = Utils.getValueEnd(n);
if(start < 0 || start > end || start > offset) return null;
String attrText = document.get(start, end - start);
StringBuffer sb = new StringBuffer(attrText);
//find start of bean property
int bStart = offset - start;
while (bStart >= 0) {
if (!Character.isJavaIdentifierPart(sb.charAt(bStart)) &&
sb.charAt(bStart) != '.' && sb.charAt(bStart) != '[' && sb.charAt(bStart) != ']') {
bStart++;
break;
}
if (bStart == 0) break;
bStart--;
}
// find end of bean property
int bEnd = offset - start;
while (bEnd < sb.length()) {
if (!Character.isJavaIdentifierPart(sb.charAt(bEnd)))
break;
bEnd++;
}
int propStart = bStart + start;
int propLength = bEnd - bStart;
if (propStart > offset || propStart + propLength < offset) return null;
return new HyperlinkRegion(propStart, propLength, null, null, null);
} catch (BadLocationException x) {
SeamExtPlugin.getPluginLog().logError(x);
return null;
} finally {
smw.dispose();
}
}
public static IHyperlinkRegion getRegion(IDocument document, final int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
Node n = Utils.findNodeForOffset(xmlDocument, offset);
if (n == null || !(n instanceof Attr || n instanceof Text)) return null;
int start = 0;
int end = document.getLength();
if(n instanceof IDOMNode) {
start = ((IDOMNode)n).getStartOffset();
end = ((IDOMNode)n).getEndOffset();
}
//TODO do we have and need seam project here?
SeamELCompletionEngine engine = new SeamELCompletionEngine();
ELInvocationExpression tokens = engine.findExpressionAtOffset(document, offset, start, end);
if (tokens == null /*|| tokens.size() == 0*/)
return null; // No EL Operand found
int propStart = tokens.getStartPosition();
int propLength = tokens.getEndPosition() - propStart;
if (propStart > offset || propStart + propLength < offset) return null;
IHyperlinkRegion region = new HyperlinkRegion(propStart, propLength);
return region;
} finally {
smw.dispose();
}
}
/**
* @see com.ibm.sse.editor.extensions.hyperlink.IHyperlinkPartitionRecognizer#recognize(org.eclipse.jface.text.IDocument, com.ibm.sse.editor.extensions.hyperlink.IHyperlinkRegion)
*/
public boolean recognize(IDocument document, int offset, IHyperlinkRegion region) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return false;
Map<String, ISeamMessages> messages = findMessagesComponents(document, offset);
if (messages != null && !messages.isEmpty()) {
return true;
}
List<IJavaElement> javaElements = findJavaElements(document, offset);
if (javaElements != null && !javaElements.isEmpty()) {
return true;
}
return false;
} finally {
smw.dispose();
}
}
public static List<IJavaElement> findJavaElements(IDocument document, int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
IHyperlinkRegion r = getRegion(document, offset);
if (r == null) return null;
String propText = document.get(r.getOffset(), r.getLength());
IFile file = smw.getFile();
IProject project = (file == null ? null : file.getProject());
ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
if (seamProject == null)
return null;
SeamELCompletionEngine engine = new SeamELCompletionEngine();
String prefix = propText;
ELExpression expr = engine.parseOperand(prefix);
if (!(expr instanceof ELInvocationExpression))
return null; // No EL Operand found
expr.getModel().shift(r.getOffset() - expr.getFirstToken().getStart());
List<IJavaElement> javaElements = null;
try {
javaElements = engine.getJavaElementsForELOperandTokens(seamProject, file, (ELInvocationExpression)expr);
} catch (StringIndexOutOfBoundsException e) {
SeamExtPlugin.getPluginLog().logError(e);
return null;
} catch (BadLocationException e) {
SeamExtPlugin.getPluginLog().logError(e);
return null;
}
//Do not need it, vars handled in getJavaElementsForELOperandTokens
if (javaElements == null || javaElements.isEmpty()) {
// Try to find a local Var (a pair of variable-value attributes)
ElVarSearcher varSearcher = new ElVarSearcher(file, engine);
// Find a Var in the EL
int start = expr.getStartPosition();
int end = expr.getEndPosition();
if (expr.getText().length() == 0)
return null;
List<Var> allVars= varSearcher.findAllVars(file, start);
Var var = varSearcher.findVarForEl(expr.getText(), null, allVars, true);
if (var == null) {
// Find a Var in the current offset assuming that it's a node with var/value attribute pair
var = varSearcher.findVar(file, start);
}
if (var == null)
return null;
String resolvedValue = var.getValue();
if (resolvedValue == null || resolvedValue.length() == 0)
return null;
if (resolvedValue.startsWith("#{") || resolvedValue.startsWith("${"))
resolvedValue = resolvedValue.substring(2);
if (resolvedValue.endsWith("}"))
resolvedValue = resolvedValue.substring(0, resolvedValue.lastIndexOf("}"));
// Replace the Var with its resolved value in tokens (Var is always the first token)
StringBuffer elText = new StringBuffer();
elText.append(resolvedValue);
String app = expr.getText();
int i = app.indexOf('.');
app = app.substring(i + 1);
elText.append('.').append(app);
javaElements = engine.getJavaElementsForExpression(
seamProject, file, elText.toString(), offset);
}
return javaElements;
} catch (BadLocationException x) {
SeamExtPlugin.getPluginLog().logError(x);
return null;
} finally {
smw.dispose();
}
}
public static Map<String, ISeamMessages> findMessagesComponents(IDocument document, int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
IHyperlinkRegion r = getRegion(document, offset);
if (r == null) return null;
String propText = document.get(r.getOffset(), r.getLength());
IFile file = smw.getFile();
IProject project = (file == null ? null : file.getProject());
ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
if (seamProject == null)
return null;
SeamELCompletionEngine engine= new SeamELCompletionEngine();
String prefix = propText;
ELExpression exp = engine.parseOperand(prefix);
if (exp == null)
return null; // No EL Operand found
Map<ELInvocationExpression, List<ISeamContextVariable>> map = new HashMap<ELInvocationExpression, List<ISeamContextVariable>>();
exp.getModel().shift(r.getOffset() - exp.getFirstToken().getStart());
if ( !(exp instanceof ELInvocationExpression) &&
!(exp instanceof ELPropertyInvocation) &&
!(exp instanceof ELArgumentInvocation))
return null;
String propertyName = null;
if (exp instanceof ELPropertyInvocation) {
propertyName = ((ELPropertyInvocation)exp).getMemberName();
} else if (exp instanceof ELArgumentInvocation) {
propertyName = Utils.trimQuotes(
((ELArgumentInvocation)exp).getArgument().getArgument().getText());
}
if (propertyName == null)
return null;
// ScopeType scope = SeamELCompletionEngine.getScope(seamProject, file);
ELInvocationExpression expr = (ELInvocationExpression)exp;
ELInvocationExpression left = expr;
if (expr.getLeft() != null) {
while (left != null) {
List<ISeamContextVariable> resolvedVars = new ArrayList<ISeamContextVariable>();
resolvedVars = engine.resolveVariables(seamProject, file, left,
left == expr, true);
if (resolvedVars != null && !resolvedVars.isEmpty()) {
map.put(left, resolvedVars);
break;
}
left = (ELInvocationExpression) left.getLeft();
}
}
// At the moment map contains the resolved EL parts mapped to their vars
// And now we need to extract SeamXmlFactory vars to the real ones
// and filter out all non-SeamMessagesComponent vars
// Next we need to map the SeamMessagesComponent vars to properties
Map<String, ISeamMessages> messages = new HashMap<String, ISeamMessages>();
if (map != null && !map.isEmpty()) {
for (ELInvocationExpression l : map.keySet()) {
List<ISeamContextVariable> variables = map.get(l);
for (ISeamContextVariable variable : variables) {
ISeamMessages messagesVariable = SeamELCompletionEngine.getSeamMessagesComponentVariable(variable);
if (messagesVariable != null) {
messages.put(propertyName, messagesVariable);
}
}
}
}
return messages;
} catch (BadLocationException x) {
SeamExtPlugin.getPluginLog().logError(x);
return null;
} finally {
smw.dispose();
}
}
public static IHyperlinkRegion getMessagesPropertyRegion(IDocument document, int offset) {
StructuredModelWrapper smw = new StructuredModelWrapper();
try {
smw.init(document);
Document xmlDocument = smw.getDocument();
if (xmlDocument == null) return null;
IHyperlinkRegion r = getRegion(document, offset);
if (r == null) return null;
String propText = document.get(r.getOffset(), r.getLength());
IFile file = smw.getFile();
IProject project = (file == null ? null : file.getProject());
ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
if (seamProject == null)
return null;
SeamELCompletionEngine engine= new SeamELCompletionEngine();
String prefix = propText;
ELExpression exp = engine.parseOperand(prefix);
if (exp == null)
return null; // No EL Operand found
Map<ELInvocationExpression, List<ISeamContextVariable>> map = new HashMap<ELInvocationExpression, List<ISeamContextVariable>>();
exp.getModel().shift(r.getOffset() - exp.getFirstToken().getStart());
if ( !(exp instanceof ELInvocationExpression) &&
!(exp instanceof ELPropertyInvocation) &&
!(exp instanceof ELArgumentInvocation))
return null;
String propertyName = null;
if (exp instanceof ELPropertyInvocation) {
propertyName = ((ELPropertyInvocation)exp).getMemberName();
int start = ((ELPropertyInvocation)exp).getLastToken().getStart();
int length = ((ELPropertyInvocation)exp).getLastToken().getLength();
return new HyperlinkRegion(start, length);
} else if (exp instanceof ELArgumentInvocation) {
propertyName = Utils.trimQuotes(
((ELArgumentInvocation)exp).getArgument().getArgument().getText());
int start = ((ELArgumentInvocation)exp).getArgument().getArgument().getStartPosition();
int length = ((ELArgumentInvocation)exp).getArgument().getArgument().getEndPosition() -
((ELArgumentInvocation)exp).getArgument().getArgument().getStartPosition();
return new HyperlinkRegion(start, length);
}
return null;
} catch (BadLocationException x) {
SeamExtPlugin.getPluginLog().logError(x);
return null;
} finally {
smw.dispose();
}
}
public int getPriority() {
return 0; // to be first
}
}