blob: c13b0e805a04eb1832347769a8238719103fba6a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation.
* 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:
* Cameron Bateman/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.core.internal.contentassist.el;
import org.eclipse.jface.text.Region;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext;
import org.eclipse.jst.jsf.context.symbol.ISymbol;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAddExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAndExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTChoiceExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTEqualityExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTFunctionInvocation;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTLiteral;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTMultiplyExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTOrExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTRelationalExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTUnaryExpression;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValue;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValuePrefix;
import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValueSuffix;
import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParser;
import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserConstants;
import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserVisitor;
import org.eclipse.jst.jsp.core.internal.java.jspel.ParseException;
import org.eclipse.jst.jsp.core.internal.java.jspel.SimpleNode;
import org.eclipse.jst.jsp.core.internal.java.jspel.Token;
/**
* Consumes an EL expression and converts into a completion prefix
*
* @author cbateman
*
*/
public final class ContentAssistParser
{
/**
* @param relativePosition -- 1-based position in elText (first position is 1)
* @param elText
* @return a content assist strategy for the given position and el expression
* or null if one cannot be determined
*/
public static ContentAssistStrategy getPrefix(final int relativePosition, final String elText)
{
if (elText == null)
{
return null;
}
else if ("".equals(elText.trim())) //$NON-NLS-1$
{
return new IdCompletionStrategy("", ""); //$NON-NLS-1$//$NON-NLS-2$
}
PrefixVisitor visitor = getVisitorForPosition(relativePosition, elText);
return visitor != null? visitor.getPrefix() : null;
}
/**
* Get symbol and symbol region at given position in el string
* @param context - IStructuredDocumentContext
* @param relativePosition - position in el string
* @param elText - el string
* @return SymbolInfo. May be null.
*/
public static SymbolInfo getSymbolInfo(IStructuredDocumentContext context, final int relativePosition, final String elText) {
if (elText == null || "".equals(elText.trim())) //$NON-NLS-1$
{
return null;
}
PrefixVisitor visitor = getVisitorForPosition(relativePosition, elText);
if (visitor != null) {
SymbolInfo symbolInfo = visitor.getSymbolInfo(context);
if (symbolInfo != null) {
Region r = symbolInfo.getRelativeRegion();
if (relativePosition > r.getOffset() && relativePosition <= r.getOffset() + r.getLength()) {
return symbolInfo;
}
}
}
return null;
}
private static PrefixVisitor getVisitorForPosition(final int relativePosition,
final String elText) {
final java.io.StringReader reader = new java.io.StringReader(elText);
final JSPELParser parser = new JSPELParser(reader);
try
{
final ASTExpression expr = parser.Expression();
final PrefixVisitor visitor = new PrefixVisitor(relativePosition, elText);
expr.jjtAccept(visitor, null);
return visitor;
}
catch (ParseException pe)
{
// TODO: handle parser by using current and expected tokens
return null;
}
}
private static String substring(String s, Region r) {
return s.substring(r.getOffset(), r.getOffset() + r.getLength());
}
private static class PrefixVisitor implements JSPELParserVisitor
{
private final int _relativePos;
private final String _fullText;
private String _symbolPrefix; // = null; initialized as tree is visited
private int _prefixType;
private boolean _prefixResolved; // = false; set to true when the prefix is resolved
private int _symbolStartPos = 1; // first char has position 1
private int _symbolEndPos = 0;
PrefixVisitor(final int relativePos, final String fullText)
{
_relativePos = relativePos;
_fullText = fullText;
}
/**
* @return the prefix if resolved or null if not resolved
*/
public ContentAssistStrategy getPrefix()
{
if (_prefixResolved)
{
switch(_prefixType)
{
case ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION:
return new FunctionCompletionStrategy(_symbolPrefix, getProposalStart());
case ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION:
return new IdCompletionStrategy(_symbolPrefix, getProposalStart());
case ContentAssistStrategy.PREFIX_TYPE_EMPTY_EXPRESSION:
return new IdCompletionStrategy("", getProposalStart()); //$NON-NLS-1$
default:
// do nothing; fall-through to return null
}
}
return null;
}
/**
* @param context - IStructuredDocumentContext
* @return symbol and symbol region if resolved, null otherwise
*/
public SymbolInfo getSymbolInfo(IStructuredDocumentContext context) {
if (_prefixResolved && _symbolStartPos < _symbolEndPos) {
Region region = new Region(_symbolStartPos - 1, _symbolEndPos - _symbolStartPos + 1);
ISymbol symbol = null;
switch (_prefixType) {
case ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION:
symbol = SymbolResolveUtil.getSymbolForVariable(context, substring(_fullText, region));
break;
case ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION:
symbol = SymbolResolveUtil.getSymbolForVariableSuffixExpr(context, _symbolPrefix + "." + substring(_fullText, region), _symbolEndPos == _fullText.length()); //$NON-NLS-1$
break;
}
if (symbol != null) {
return new SymbolInfo(symbol, region);
}
}
return null;
}
private String getProposalStart() {
if (_symbolStartPos <= _relativePos) {
return _fullText.substring(_symbolStartPos - 1, _relativePos - 1);
}
return ""; //$NON-NLS-1$
}
public Object visit(ASTAddExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTAndExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTChoiceExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTEqualityExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTFunctionInvocation node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTLiteral node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTMultiplyExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTOrExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTRelationalExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTUnaryExpression node, Object data)
{
return node.childrenAccept(this, data);
}
public Object visit(ASTValue node, Object data)
{
// we're only in this value expr if it contains the cursor
if (testContainsCursor(node))
{
return node.childrenAccept(this, data);
}
return null;
}
public Object visit(ASTValuePrefix node, Object data)
{
// for now, only concern ourselves with simple (identifier) prefixes
if (!_prefixResolved
&& node.jjtGetNumChildren() == 0
&& node.getFirstToken().kind == JSPELParserConstants.IDENTIFIER)
{
_symbolPrefix = node.getFirstToken().image;
if (testContainsCursor(node))
{
// if the cursor is on this id, we don't need to visit
// further since we know both the prefix -- the id -- and
// the type -- it's an id completion
_prefixType = ContentAssistStrategy.PREFIX_TYPE_ID_COMPLETION;
_symbolStartPos = node.getFirstToken().beginColumn;
_symbolEndPos = node.getFirstToken().endColumn;
_prefixResolved = true;
}
}
return node.childrenAccept(this, data);
}
public Object visit(ASTValueSuffix node, Object data)
{
// for now, only deal with the simple .id suffix
Token lastToken = node.getLastToken();
if (node.jjtGetNumChildren() == 0)
{
if (!_prefixResolved
&& node.getFirstToken().kind == JSPELParserConstants.DOT)
{
if (lastToken.kind == JSPELParserConstants.IDENTIFIER)
{
if (testContainsCursor(node))
{
_prefixType = ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION;
int proposalStartLength = _relativePos - lastToken.beginColumn;
if (proposalStartLength < 0) { // Cursor after firstToken start but before lastToken start?
proposalStartLength = 0;
}
_symbolStartPos = lastToken.beginColumn;
_symbolEndPos = lastToken.endColumn;
_prefixResolved = true;
}
// only include this suffix on the path if the cursor is
// further to the right. Thus for x.^y we get a prefix "x"
// and for x.y.^z we get "x.y" since this the part we must
// resolve the prefix for
else
{
_symbolPrefix += node.getFirstToken().image + lastToken.image;
}
}
else if (lastToken == node.getFirstToken())
{
if (testCursorImmediatelyAfter(node))
{
_prefixType = ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION;
_symbolStartPos = lastToken.endColumn + 1;
_symbolEndPos = lastToken.endColumn;
_prefixResolved = true;
}
}
}
return null;
}
if (node.getFirstToken().kind == JSPELParserConstants.LBRACKET)
{
// try to support ca inside the brackets
node.childrenAccept(this, data);
}
Object retValue = node.childrenAccept(this, data);
if (!_prefixResolved)
{
// if we haven't resolved the prefix yet, then we need
// to append this suffix value
_symbolPrefix += _fullText.substring(node.getFirstToken().beginColumn-1, node.getLastToken().endColumn);
}
return retValue;
}
public Object visit(SimpleNode node, Object data)
{
return node.childrenAccept(this, data);
}
private boolean testCursorImmediatelyAfter(SimpleNode node)
{
return node.getLastToken().endColumn == _relativePos-1;
}
/**
* "Containing a cursor" here is deemed to mean that current cursor
* position as indicated by _relativePos, is either directly before, on or
* directly after an expression. For example, in a Value expression like
*
* x x x . y y y . z z z
* ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
* 1 2 3 4 5 6 7 8 9 0 1 2
*
* Position's 1-4 are on xxx, 5-8 are on yyy and 9-12 are on zzz
*
* @param node
* @return true if the node "contains the cursor" (see above)
*/
private boolean testContainsCursor(SimpleNode node)
{
return (node.getFirstToken().beginColumn <= _relativePos
&& node.getLastToken().endColumn+1 >= _relativePos);
}
}
private ContentAssistParser()
{
// utility class; not instantiable
}
}