/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.internal.gosu.parser;
import gw.config.CommonServices;
import gw.config.Registry;
import gw.internal.gosu.parser.expressions.BooleanLiteral;
import gw.internal.gosu.parser.expressions.Identifier;
import gw.internal.gosu.parser.expressions.MemberAccess;
import gw.internal.gosu.parser.expressions.QueryExpression;
import gw.internal.gosu.parser.expressions.QueryPathExpression;
import gw.internal.gosu.parser.expressions.TypeLiteral;
import gw.internal.gosu.parser.expressions.WhereClauseConditionalAndExpression;
import gw.internal.gosu.parser.expressions.WhereClauseConditionalOrExpression;
import gw.internal.gosu.parser.expressions.WhereClauseEqualityExpression;
import gw.internal.gosu.parser.expressions.WhereClauseExistsExpression;
import gw.internal.gosu.parser.expressions.WhereClauseRelationalExpression;
import gw.internal.gosu.parser.expressions.WhereClauseUnaryExpression;
import gw.internal.gosu.parser.expressions.WhereClauseParenthesizedExpression;
import gw.internal.gosu.parser.expressions.EvalExpression;
import gw.internal.gosu.parser.expressions.StringLiteral;
import gw.lang.parser.ISymbol;
import gw.lang.parser.Keyword;
import gw.lang.parser.ICapturedSymbol;
import gw.lang.parser.expressions.ITypeVariableDefinition;
import gw.lang.parser.expressions.ILiteralExpression;
import gw.lang.parser.exceptions.ParseException;
import gw.lang.parser.resources.Res;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.java.JavaTypes;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
/**
* <p/>
* <i>query-expression</i>
* <b>find</b> <b>(</b> <identifier> <b>in</b> <query-path-expression> [<b>where</b> <where-clause-expression>] <b>)</b>
* <p/>
* <i>exists-expression</i>
* <b>exists</b> <b>(</b> <identifier> <b>in</b> <query-path-expression> <b>where</b> <where-clause-expression> <b>)</b>
* <p/>
* <i>where-clause-expression</i>
* <where-clause-conditional-expression>
* <p/>
* <i>where-clause-conditional-expression</i>
* <where-clause-conditional-or-expression><BR>
* <p/>
* <i>where-clause-conditional-or-expression</i>
* <where-cluase-conditional-and-expression><BR>
* <where-cluase-conditional-or-expression> <b>||</b> <where-cluase-conditional-and-expression><BR>
* <where-cluase-conditional-or-expression> <b>or</b> <where-cluase-conditional-and-expression><BR>
* <p/>
* <i>where-clause-conditional-and-expression</i>
* <where-clause-equality-expression><BR>
* <where-clause-conditional-and-expression> <b>&&</b> <where-clause-equality-expression><BR>
* <where-clause-conditional-and-expression> <b>and</b> <where-clause-equality-expression><BR>
* <p/>
* <i>where-clause-equality-expression</i>
* <where-clause-relational-expression><BR>
* <where-clause-equality-expression> <b>==</b> <relational-expression><BR>
* <where-clause-equality-expression> <b>!=</b> <relational-expression><BR>
* <where-clause-equality-expression> <b><></b> <relational-expression><BR>
* <p/>
* <i>where-clause-relational-expression</i>
* <where-clause-unary-expression><BR>
* <where-clause-relational-expression> <b><</b> <additive-expression><BR>
* <where-clause-relational-expression> <b>></b> <additive-expression><BR>
* <where-clause-relational-expression> <b><=</b> <additive-expression><BR>
* <where-clause-relational-expression> <b>>=</b> <additive-expression><BR>
* <where-clause-relational-expression> <b>in</b> <where-clause-in-expression><BR>
* <where-clause-relational-expression> <b>startswith</b> <where-clause-in-expression><BR>
* <where-clause-relational-expression> <b>contains</b> <where-clause-in-expression><BR>
* <p/>
* <i>where-clause-in-expression</i>
* <expression><BR>
* <p/>
* <i>where-clause-unary-expression</i>
* <where-clause-primary-expression><BR>
* <b>!</b> <where-clause-unary-expression><BR>
* <b>not</b> <where-clause-unary-expression><BR>
* <p/>
* <i>where-clause-primary-expression</i>
* <exists-expression><BR>
* <query-path-expression><BR>
* <b>(</b> <where-clause-expression> <b>)</b><BR>
* <p/>
*
* @deprecated
*/
class QueryExpressionParser extends ParserBase
{
private QueryExpressionParser _parent;
private IType _type;
private QueryPathExpression _ein;
private QueryPathRootSymbol _symbol;
QueryExpressionParser( GosuParser owner )
{
super( owner );
_blocks = owner.getOwner()._blocks;
}
protected String getScript()
{
return getOwner().getScript();
}
QueryExpressionParser( QueryExpressionParser parent )
{
super( parent.getOwner() );
_parent = parent;
}
IType getEntityType()
{
return _type;
}
QueryExpressionParser getParent()
{
return _parent;
}
boolean parse( Token T )
{
if( !match( T, getParent() != null ? Keyword.KW_exists : Keyword.KW_find ) )
{
return false;
}
QueryExpression qe = getParent() == null
? new QueryExpression()
: new WhereClauseExistsExpression();
List<ICapturedSymbol> captured = new ArrayList<ICapturedSymbol>();
captureAllSymbols( null, getCurrentEnclosingGosuClass(), captured );
qe.setCapturedSymbolsForBytecode( captured );
qe.setCapturedTypeVars( new HashMap<String, ITypeVariableDefinition>( getOwner().getTypeVariables() ) );
// Distinct is only allowed for the root level find clause. I.e. no parent.
if( getParent() == null && match( null, "distinct" ) )
{
qe.setDistinct( true );
}
verify( qe, match( null, '(' ), Res.MSG_EXPECTING_LEFTPAREN_FIND );
match( null, Keyword.KW_var );
int iNamedOffset = getTokenizer().getTokenStart();
if( verify( qe, match( T, SourceCodeTokenizer.TT_WORD ), Res.MSG_EXPECTING_IDENTIFIER_FIND ) )
{
qe.setNameOffset( iNamedOffset, (String)T._strValue );
}
String strIdentifier = T._strValue;
verify( qe, match( null, Keyword.KW_in ), Res.MSG_EXPECTING_IN_FIND );
parseQueryPathExpression();
_ein = (QueryPathExpression)popExpression();
_type = _ein.getRootType();
getSymbolTable().pushScope();
try
{
_symbol = new QueryPathRootSymbol( strIdentifier, _ein.getType(), null );
// getSymbolTable().putSymbol( _symbol ); Don't put this symbol here, it's pushed only when we parse a lhs expression in where-clause
Expression whereExpression = null;
if( match( null, Keyword.KW_where ) )
{
parseWhereClauseExpression();
whereExpression = popExpression();
}
verify( qe, match( null, ')' ), Res.MSG_EXPECTING_RIGHTPAREN_FIND );
qe.setIdentifier( strIdentifier );
qe.setEntityType( _type );
if( _type instanceof ErrorType )
{
// sct: if an ErrorType, set the type on the QueryExpression to prevent ClassCastExceptions
qe.setType( _type );
}
// The QueryExpression type is always a List now because an array type
// requires we load all the results into memory which could get ugly:
// qe.setType( new ArrayType( BeanAccess.sharedInstance().getType( getEntityType() ) ) );
qe.setInExpression( _ein );
qe.setWhereClauseExpression( whereExpression );
pushExpression( qe );
}
finally
{
getSymbolTable().popScope();
}
return true;
}
private void parseWhereClauseExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
parseWhereClauseConditionalExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void parseWhereClauseConditionalExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseConditionalExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseConditionalExpression()
{
// <conditional-or-expression>
parseWhereClauseConditionalOrExpression();
}
void parseWhereClauseConditionalOrExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseConditionalOrExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseConditionalOrExpression()
{
parseWhereClauseConditionalAndExpression();
// <conditional-or-expression2>
do
{
Token T = new Token();
if( match( T, "||", SourceCodeTokenizer.TT_OPERATOR ) ||
match( null, Keyword.KW_or ) )
{
parseWhereClauseConditionalAndExpression();
WhereClauseConditionalOrExpression e = new WhereClauseConditionalOrExpression();
Expression rhs = popExpression();
Expression lhs = popExpression();
e.setLHS( lhs );
e.setRHS( rhs );
pushExpression( e );
}
else
{
// The <null> case
break;
}
}
while( true );
}
void parseWhereClauseConditionalAndExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseConditionalAndExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseConditionalAndExpression()
{
parseWhereClauseEqualityExpression();
// <conditional-and-expression2>
do
{
Token T = new Token();
if( match( T, "&&", SourceCodeTokenizer.TT_OPERATOR ) ||
match( null, Keyword.KW_and ) )
{
parseWhereClauseEqualityExpression();
WhereClauseConditionalAndExpression e = new WhereClauseConditionalAndExpression();
Expression rhs = popExpression();
Expression lhs = popExpression();
e.setLHS( lhs );
e.setRHS( rhs );
pushExpression( e );
}
else
{
// The <null> case
break;
}
}
while( true );
}
void parseWhereClauseEqualityExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseEqualityExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseEqualityExpression()
{
parseWhereClauseRelationalExpression();
// <relational-expression2>
do
{
Token T = new Token();
if( match( T, "==", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, "!=", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, "<>", SourceCodeTokenizer.TT_OPERATOR ) )
{
Expression lhs = popExpression();
WhereClauseEqualityExpression e = new WhereClauseEqualityExpression();
verify( e, lhs instanceof QueryPathExpression, Res.MSG_EXPECTING_QUERY_PATH );
IType type = lhs.getType();
getOwner().pushContextType( type );
try
{
getOwner().parseRelationalExpression();
}
finally
{
getOwner().popContextType();
}
Expression rhs = popExpression();
verifyComparable( lhs.getType(), rhs, true, true );
verifyPropertyVisible( lhs );
e.setLHS( lhs );
e.setRHS( wrapInEvalExpression( rhs ) );
e.setEquals( T._strValue.equals( "==" ) );
pushExpression( e );
}
else
{
Expression lhs = peekExpression();
if( lhs instanceof QueryPathExpression )
{
verify( lhs, lhs.getType() == JavaTypes.BOOLEAN() || lhs.getType() == JavaTypes.pBOOLEAN(), Res.MSG_QUERY_EXPECTED_BOOLEAN_EXPRESSION, lhs.toString() );
QueryPathExpression qpe = (QueryPathExpression)lhs;
if( !qpe.hasParseExceptions() && !isDbProperty( qpe ) )
{
qpe.getDelegate().addParseException( Res.MSG_PROPERTY_NOT_VISIBLE, ((MemberAccess)qpe.getDelegate()).getMemberName() );
}
lhs = popExpression();
WhereClauseEqualityExpression e = new WhereClauseEqualityExpression();
e.setLHS( lhs );
e.setRHS( BooleanLiteral.TRUE.get() );
e.setEquals( true );
pushExpression( e );
}
// else the <null> case
break;
}
}
while( true );
}
private void verifyPropertyVisible( Expression lhs )
{
if( lhs instanceof QueryPathExpression && !lhs.hasParseExceptions() && !(lhs.getType() instanceof ErrorType) &&
!isDbProperty( (QueryPathExpression)lhs ) ) {
((QueryPathExpression)lhs).getDelegate().addParseException( Res.MSG_PROPERTY_NOT_VISIBLE, ((QueryPathExpression)lhs).getMemberName() );
}
}
void parseWhereClauseRelationalExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseRelationalExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseRelationalExpression()
{
// <unary-expression>
parseWhereClauseUnaryExpression();
// <relational-expression2>
do
{
Token T = new Token();
if( match( T, "<", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, ">", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, "<=", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, Keyword.KW_in ) ||
match( T, Keyword.KW_startswith ) ||
match( T, Keyword.KW_contains ) )
{
if( T._strValue.equals( ">" ) && match( null, "=", SourceCodeTokenizer.TT_OPERATOR ) )
{
T._strValue = ">=";
}
Expression lhs = popExpression();
WhereClauseRelationalExpression e = new WhereClauseRelationalExpression();
verify( e, lhs instanceof QueryPathExpression, Res.MSG_EXPECTING_QUERY_PATH );
getOwner().parseAdditiveExpression();
Expression rhs = popExpression();
verifyPropertyVisible( lhs );
if( T._strValue.equals( Keyword.KW_in.toString() ) )
{
limitInOperandToArrayOrQuery( lhs, rhs );
}
else
{
verifyComparable( lhs.getType(), rhs, true, true );
}
e.setLHS( lhs );
e.setRHS( wrapInEvalExpression( rhs ) );
e.setOperator( T._strValue );
pushExpression( e );
}
else
{
// The <null> case
break;
}
}
while( true );
}
private Expression wrapInEvalExpression( Expression rhs )
{
if( rhs instanceof ILiteralExpression )
{
return rhs;
}
EvalExpression evalExpr = new EvalExpression( getOwner().getTypeUsesMap().copy() );
List<ICapturedSymbol> captured = new ArrayList<ICapturedSymbol>();
captureAllSymbols( null, getCurrentEnclosingGosuClass(), captured );
evalExpr.setCapturedSymbolsForBytecode( captured );
evalExpr.setCapturedTypeVars( new HashMap<String, ITypeVariableDefinition>( getOwner().getTypeVariables() ) );
evalExpr.setExpression( new StringLiteral( rhs.toString() ) );
getOwner().findAndWrapLocation( rhs, evalExpr );
return possiblyWrapWithCoercion( evalExpr, rhs.getType(), false );
}
private void limitInOperandToArrayOrQuery( Expression lhs, Expression rhs )
{
if( rhs instanceof QueryExpression )
{
verify( lhs, CommonServices.getEntityAccess().isEntityClass( lhs.getType() ),
Res.MSG_QUERY_IN_LHS_OP_NOT_ENTITY, lhs.toString() );
}
IType intrLhs = lhs.getType();
IType qrsType = JavaTypes.IQUERY_RESULT_SET();
IType componentType = null;
if( qrsType.isAssignableFrom( rhs.getType() ))
{
for (IType iType : rhs.getType().getAllTypesInHierarchy()) {
IType genericType = iType.getGenericType();
if (genericType != null && genericType.equals(qrsType) && iType.isParameterizedType()) {
componentType = iType.getTypeParameters()[0];
}
}
verifyTypesComparable( lhs, intrLhs, componentType == null ? ErrorType.getInstance() : componentType, false, true );
}
else
{
intrLhs = intrLhs.isArray() ? intrLhs : intrLhs.getArrayType();
if( intrLhs == null )
{
verify( lhs, false, Res.MSG_QUERY_IN_LHS_OP_NOT_ARRAY, lhs.getType().toString() );
}
else
{
verifyComparable(intrLhs, rhs, true );
}
}
}
void parseWhereClauseUnaryExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseWhereClauseUnaryExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parseWhereClauseUnaryExpression()
{
Token T = new Token();
if( match( T, "!", SourceCodeTokenizer.TT_OPERATOR ) ||
match( T, Keyword.KW_not ) )
{
parseWhereClauseUnaryExpression();
WhereClauseUnaryExpression ue = new WhereClauseUnaryExpression();
Expression e = popExpression();
ue.setExpression( e );
ue.setType( e.getType() );
pushExpression( ue );
}
else
{
parsePrimaryExpression();
}
}
void parsePrimaryExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parsePrimaryExpression();
setLocation( iOffset, iLineNum, iColumn );
}
void _parsePrimaryExpression()
{
Token T = new Token();
// Parenthesized Expression
if( match( T, '(' ) )
{
parseWhereClauseExpression();
Expression e = popExpression();
WhereClauseParenthesizedExpression expr = new WhereClauseParenthesizedExpression( e );
pushExpression( expr );
verify( e, match( null, ')' ), Res.MSG_EXPECTING_EXPRESSION_CLOSE );
return;
}
// A nested 'exists' query
QueryExpressionParser queryParser = new QueryExpressionParser( this );
if( !queryParser.parse( T ) )
{
// A query path
parseQueryPathExpression();
}
}
void parseQueryPathExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
_parseQueryPathExpression();
setLocation( iOffset, iLineNum, iColumn, true );
}
private void _parseQueryPathExpression()
{
int iOffset = getTokenizer().getTokenStart();
int iLineNum = getTokenizer().getLineNumber();
int iColumn = getTokenizer().getTokenColumn();
Token T = new Token();
QueryPathExpression e = new QueryPathExpression();
// Peek ahead to get the first element in the query path. If this is the in-epxr,
// we're expecting an entity name. We'll create a symbol with this name and type
// so that we can parse a MemberAccess. Otherwise, if this is not an in-expr, in
// which case it must be a lhs expression in a where clause, we don't need to create
// the symbol because it should be there from the in-expr case.
if( !verify( e, match( T, null, SourceCodeTokenizer.TT_WORD, true ), Res.MSG_EXPECTING_QUERY_PATH ) )
{
T._strValue = null;
}
String strRoot = T._strValue == null ? "" : T._strValue;
ISymbol root;
IType type;
TypeLiteral typeLit;
if( _ein != null )
{
getSymbolTable().pushScope();
try
{
getSymbolTable().putSymbol( _symbol );
root = resolveSymbol( e, strRoot, true );
verify( e, root == _symbol, Res.MSG_QUERYPATH_MUST_BEGIN_WITH, _symbol.getName() );
type = root == null ? ErrorType.getInstance( strRoot ) : root.getType();
}
finally
{
getSymbolTable().popScope();
}
}
else
{
try
{
IType intrType = TypeLoaderAccess.instance().getByRelativeName( strRoot, CommonServices.getEntityAccess().getDefaultTypeUses() );
verify( e, intrType != null && CommonServices.getEntityAccess().isEntityClass( intrType ), Res.MSG_EXPECTING_ENTITY_TYPE );
if( CommonServices.getEntityAccess().isViewEntityClass( intrType ) )
{
verify( e, !CommonServices.getEntityAccess().isViewEntityClass( intrType ),
Res.MSG_NO_VIEWS_FOR_YOUS,
CommonServices.getEntityAccess().getPrimaryEntityClass( intrType ).getName() );
}
if( !Registry.instance().isAllowEntityQueires() )
{
verify( e, intrType != null && !intrType.isMutable(), Res.MSG_EXPECTING_READONLY_ENTITY_TYPE );
}
type = intrType;
root = new QueryPathRootSymbol( strRoot, type, null );
}
catch( ClassNotFoundException cnfe )
{
//noinspection ThrowableInstanceNeverThrown
e.addParseException( new ParseException( makeFullParserState(), Res.MSG_EXPECTING_QUERY_PATH ) );
type = ErrorType.getInstance( strRoot );
root = new QueryPathRootSymbol( strRoot, type, null );
}
}
getSymbolTable().pushScope();
try
{
// Create a temporary symbol for the root of the expression so intellisense can handle this as an entity ref and not a type literal
getSymbolTable().putSymbol( root );
IType exprType = type;
getOwner().parsePrimaryExpression();
Expression expr = popExpression();
if( _ein == null )
{
// Note we must make a type-literal-expr here so that the parse tree is consistent e.g.,for the typeinfo db
Token typeLiteralToken = new Token();
typeLiteralToken._strValue = strRoot;
typeLit = getOwner().resolveTypeLiteral( typeLiteralToken );
pushExpression( typeLit );
setLocation( iOffset, iLineNum, iColumn, true );
typeLit.getLocation().setLength( strRoot.length() );
popExpression();
}
if( expr instanceof MemberAccess )
{
MemberAccess ma = (MemberAccess)expr;
IPropertyInfo pi;
try
{
pi = ma.getPropertyInfo();
if( pi instanceof ArrayExpansionPropertyInfo )
{
pi = ((ArrayExpansionPropertyInfo)pi).getDelegate();
}
if( _ein == null )
{
verify( e, CommonServices.getEntityAccess().isEntityClass( pi.getFeatureType() ), Res.MSG_EXPECTING_ENTITY_TYPE );
}
else
{
// verify( e, isDbProperty( pi ), Res.MSG_PROPERTY_NOT_VISIBLE, pi.getName() );
verify( e, ma.getRootExpression() instanceof Identifier, Res.MSG_CANNOT_DEREF_PROPERTIES_IN_WHERE );
}
exprType = pi.getFeatureType();
}
catch( RuntimeException re )
{
// ignore
}
}
if( exprType == null )
{
exprType = ErrorType.getInstance();
}
if( _ein == null )
{
verify( e, (getParent() == null) || (expr != null && expr.toString().toLowerCase().startsWith( getParent()._ein.toString().toLowerCase() )),
Res.MSG_QUERYPATH_MUST_BEGIN_WITH,
getParent() == null ? null : getParent()._ein.toString() );
}
e.setType(exprType);
e.setDelegate( expr );
pushExpression( e );
}
finally
{
getSymbolTable().popScope();
}
}
private boolean isDbProperty( QueryPathExpression qpe )
{
try
{
return CommonServices.getEntityAccess().getQueryExpressionFeatureFilter().acceptFeature( qpe.getRootType(), qpe.getPropertyInfo() );
}
catch( Exception e )
{
return false;
}
}
}