blob: 591553fa1905f67bee0ad74c5f37b58e5bd588ac [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
/*
* Semantic.g
*
* Created on March 8, 2000
*/
header
{
package com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Collection;
import org.glassfish.persistence.common.I18NHelper;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.TypeTable;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.Type;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.ClassType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.FieldInfo;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.NumericType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.NumericWrapperClassType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.SymbolTable;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.Definition;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.TypeName;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.Variable;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.Parameter;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.scope.Field;
}
/**
* This class defines the semantic analysis of the JQL compiler.
* Input of this pass is the AST as produced by the parser,
* that consists of JQLAST nodes.
* The result is a typed JQLAST tree.
*
* @author Michael Bouschen
* @author Shing Wai Chan
* @version 0.1
*/
class Semantic extends TreeParser;
options
{
importVocab = JQL;
buildAST = true;
defaultErrorHandler = false;
ASTLabelType = "JQLAST"; //NOI18N
}
{
/**
* I18N support
*/
protected final static ResourceBundle messages =
I18NHelper.loadBundle(Semantic.class);
/**
* symbol table handling names of fields, variables and parameters
*/
protected SymbolTable symtab;
/**
* symbol table handling type names (candidate class and imported names)
*/
protected SymbolTable typeNames;
/**
* type table
*/
protected TypeTable typetab;
/**
* query parameter table
*/
protected ParameterTable paramtab;
/**
* variable table
*/
protected VariableTable vartab;
/**
*
*/
protected ErrorMsg errorMsg;
/**
* Result class for this query. This class is set by setClass.
*/
protected ClassType candidateClass;
/**
*
*/
public void init(TypeTable typetab, ParameterTable paramtab, ErrorMsg errorMsg)
{
this.symtab = new SymbolTable();
this.typeNames = new SymbolTable();
this.vartab = new VariableTable(errorMsg);
this.typetab = typetab;
this.paramtab = paramtab;
this.errorMsg = errorMsg;
}
/**
*
*/
public void reportError(RecognitionException ex) {
errorMsg.fatal("Error: " + ex); //NOI18N
}
/**
*
*/
public void reportError(String s) {
errorMsg.fatal("Error: " + s); //NOI18N
}
/**
* Combines partial ASTs into one query AST.
*/
public JQLAST createQueryAST(JQLAST candidateClass,
JQLAST importsAST,
JQLAST paramsAST,
JQLAST varsAST,
JQLAST orderingAST,
JQLAST resultAST,
JQLAST filterAST)
{
JQLAST query = new JQLAST(QUERY, "query", null); //NOI18N
if (candidateClass != null)
query.addChild(candidateClass);
if (importsAST != null)
query.addChild(importsAST);
if (paramsAST != null)
query.addChild(paramsAST);
if (varsAST != null)
query.addChild(varsAST);
if (orderingAST != null)
query.addChild(orderingAST);
if (resultAST != null)
query.addChild(resultAST);
if (filterAST != null)
query.addChild(filterAST);
return query;
}
/**
* Creates the CLASS_DEF AST that represents the setClass value.
*/
public JQLAST checkCandidateClass(Class candidateClass)
{
Type type = typetab.checkType(candidateClass);
if (type == null)
{
errorMsg.fatal(I18NHelper.getMessage(messages,
"jqlc.semantic.checkcandidateclass.unknowntype", //NOI18N
String.valueOf(candidateClass)));
}
return new JQLAST(CLASS_DEF, "classDef", type); //NOI18N
}
/**
* This method analyses the expression of a single ordering definition.
* It checks whether the expression
* - is valid (see checkValidOrderingExpr)
* - is of a orderable type
* @param expr the expression of an ordering definition
*/
protected void analyseOrderingExpression(JQLAST expr)
{
checkValidOrderingExpr(expr);
Type exprType = expr.getJQLType();
if (!exprType.isOrderable())
{
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analyseorderingexpression.notorderable", //NOI18N
exprType.getName()));
expr.setJQLType(typetab.errorType);
}
}
/**
* This method checks whether the ordering expression is valid.
* The following expressions are valid:
* - field access using the this object
* - navigation from a field of the this object
* @param expr the ordering definition
*/
private void checkValidOrderingExpr(JQLAST expr)
{
switch(expr.getType())
{
case THIS:
case VARIABLE:
//OK;
break;
case FIELD_ACCESS:
case NAVIGATION:
JQLAST child = (JQLAST)expr.getFirstChild();
if (child != null)
{
// check first part of dot expr
checkValidOrderingExpr(child);
}
break;
default:
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.checkvalidorderingexpr.invalidordering", //NOI18N
expr.getText()));
}
}
/**
* This method checks whether the result expression is valid.
* The following expressions are valid:
* - field access using the this object
* - navigation from a field of the this object
* - variable access
* - distinct expression
* - aggreagte expression
* @param expr the result expression
*/
private void checkValidResultExpr(JQLAST expr)
{
switch(expr.getType())
{
case THIS:
//OK;
break;
case FIELD_ACCESS:
case NAVIGATION:
JQLAST child = (JQLAST)expr.getFirstChild();
if (child != null)
{
// check first part of dot expr
checkValidResultExpr(child);
}
break;
case VARIABLE:
// OK
break;
case DISTINCT:
checkValidResultExpr((JQLAST)expr.getFirstChild());
break;
case AVG:
case SUM:
if (!typetab.isNumberType(expr.getJQLType()) ||
typetab.isCharType(expr.getJQLType())) {
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.checkvalidresultexpr.invalidavgsumexpr", //NOI18N
expr.getJQLType().getName(), expr.getText()));
}
checkValidResultExpr((JQLAST)expr.getFirstChild());
break;
case MAX:
case MIN:
if (!expr.getJQLType().isOrderable()) {
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.checkvalidresultexpr.invalidminmaxexpr", //NOI18N
expr.getJQLType().getName(), expr.getText()));
}
checkValidResultExpr((JQLAST)expr.getFirstChild());
break;
case COUNT:
checkValidResultExpr((JQLAST)expr.getFirstChild());
break;
default:
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.checkvalidresultexpr.invalidresult", //NOI18N
expr.getText()));
}
}
/**
* Checks that result and ordering are compatible.
* If the query result is a field, then it must be the same as ordering
* item. If the query is an object, then ordering expression must
* have the same navigation prefix of the result expression.
*/
private void checkResultOrdering(JQLAST result, JQLAST ordering) {
if (ordering == null) {
return;
}
AST resultReturnAST = result;
boolean hasResultDistinct = false;
if (resultReturnAST == null) { // distinct THIS
resultReturnAST = new JQLAST(THIS, "this", candidateClass);
hasResultDistinct = true;
}
// skip RESULT_DEF node
if (resultReturnAST.getType() == RESULT_DEF) {
resultReturnAST = resultReturnAST.getFirstChild();
}
// skip DISTINCT node
if (resultReturnAST.getType() == DISTINCT) {
resultReturnAST = resultReturnAST.getFirstChild();
hasResultDistinct = true;
}
if (!hasResultDistinct) {
return;
}
if (resultReturnAST.getType() == FIELD_ACCESS) {
StringBuffer buf = new StringBuffer();
genPathExpression(resultReturnAST, buf);
String resultReturnPathExpr = buf.toString();
for (AST sibling = ordering;
sibling != null && sibling.getType() == ORDERING_DEF;
sibling = sibling.getNextSibling()) {
// share buf
buf.setLength(0);
genPathExpression(sibling.getFirstChild().getNextSibling(), buf);
String orderingItemExpr = buf.toString();
if (!orderingItemExpr.equals(resultReturnPathExpr)) {
errorMsg.error(ordering.getLine(), ordering.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.checkresultordering.invalidorderingfordistinctresultfield", //NOI18N
resultReturnPathExpr, orderingItemExpr));
}
}
} else if (resultReturnAST.getType() == NAVIGATION ||
resultReturnAST.getType() == THIS ) {
StringBuffer buf = new StringBuffer();
genPathExpression(resultReturnAST, buf);
String resultReturnPathExpr = buf.toString();
for (AST sibling = ordering;
sibling != null && sibling.getType() == ORDERING_DEF;
sibling = sibling.getNextSibling()) {
// share buf
buf.setLength(0);
genPathExpression(sibling.getFirstChild().getNextSibling().getFirstChild(), buf);
String orderingRootExpr = buf.toString();
if (!orderingRootExpr.equals(resultReturnPathExpr)) {
buf.setLength(0);
genPathExpression(sibling.getFirstChild().getNextSibling(), buf);
errorMsg.error(ordering.getLine(), ordering.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.checkresultordering.invalidorderingfordistinctresult", //NOI18N
resultReturnPathExpr, buf.toString()));
}
}
}
}
/**
* Form a string representation of a dot expression and append to given
* StringBuffer.
* @param ast the AST node representing the root the of the expression
* @param buf the StringBuffer that will have result of path expression
* append
*/
private void genPathExpression(AST ast, StringBuffer buf) {
if (ast == null) {
return;
}
switch (ast.getType()) {
case FIELD_ACCESS:
case STATIC_FIELD_ACCESS:
case NAVIGATION:
AST left = ast.getFirstChild();
AST right = left.getNextSibling();
genPathExpression(left, buf);
buf.append('.');
genPathExpression(right, buf);
break;
default:
buf.append(ast.getText());
break;
}
}
/**
* This method analyses a dot expression of the form expr.ident or
* expr.ident(params) where expr itself can again be a dot expression.
* It checks whether the dot expression is
* - part of a qualified class name specification
* - field access,
* - a method call
* The method returns a temporary single AST node that is defined with a
* specific token type (field access, method call, etc.). This node also
* contains the type of the dot expression.
* @param expr the left hand side of the dot expression
* @param ident the right hand side of the dot expression
* @param args arguments (in the case of a call)
* @return AST node representing the specialized dot expr
*/
protected JQLAST analyseDotExpr(JQLAST dot, JQLAST expr, JQLAST ident, JQLAST args)
{
Type exprType = expr.getJQLType();
String name = ident.getText();
dot.setText(expr.getText() + '.' + name);
if (exprType instanceof ClassType)
{
// left expression is of a class type
ClassType classType = (ClassType)exprType;
if (args == null)
{
// no paranethesis specified => field access
FieldInfo fieldInfo = classType.getFieldInfo(name);
if (fieldInfo == null)
{
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.unknownfield", //NOI18N
ident.getText(), exprType.getName()));
dot.setJQLType(typetab.errorType);
ident.setJQLType(typetab.errorType);
return dot;
}
else if (expr.getType() == TYPENAME)
{
// access of the form: className.staticField
return analyseStaticFieldAccess(dot, expr, ident, classType, fieldInfo);
}
else
{
// access of the form: object.field
return analyseFieldAccess(dot, expr, ident, classType, fieldInfo);
}
}
else
{
// parenthesis specified => method call
if (typetab.isCollectionType(exprType))
{
return analyseCollectionCall(dot, expr, ident, args);
}
else if (exprType.equals(typetab.stringType))
{
return analyseStringCall(dot, expr, ident, args);
}
else if (typetab.isJavaLangMathType(exprType))
{
return analyseMathCall(dot, expr, ident, args);
}
errorMsg.error(dot.getLine(), dot.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
dot.setJQLType(typetab.errorType);
return dot;
}
}
else
{
errorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analysedotexpr.classexprexpected", //NOI18N
ident.getText(), exprType.getName()));
dot.setJQLType(typetab.errorType);
return dot;
}
}
/**
*
*/
protected JQLAST analyseFieldAccess(JQLAST access, JQLAST objectExpr, JQLAST ident,
ClassType classType, FieldInfo fieldInfo)
{
String name = ident.getText();
Type fieldType = fieldInfo.getType();
if (classType.isPersistenceCapable())
{
if (!fieldInfo.isPersistent())
{
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysefieldaccess.nonperistentfield", name, classType.getName())); //NOI18N
}
if (typetab.isPersistenceCapableType(fieldType))
{
access.setType(NAVIGATION);
}
else
{
access.setType(FIELD_ACCESS);
}
}
else
{
if (!fieldInfo.isPublic())
{
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysefieldaccess.nonpublicfield", name, classType.getName())); //NOI18N
}
access.setType(FIELD_ACCESS);
}
access.setText(objectExpr.getText() + '.' + name);
access.setJQLType(fieldType);
ident.setJQLType(fieldType);
access.setFirstChild(objectExpr);
objectExpr.setNextSibling(ident);
return access;
}
/**
*
*/
protected JQLAST analyseStaticFieldAccess(JQLAST access, JQLAST typename, JQLAST ident,
ClassType classType, FieldInfo fieldInfo)
{
String name = ident.getText();
Type fieldType = fieldInfo.getType();
if (!fieldInfo.isStatic())
{
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analysestaticfieldaccess.staticreference", //NOI18N
ident.getText(), classType.getName()));
}
if (!fieldInfo.isPublic())
{
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysestaticfieldaccess.nonpublicfield", name, classType.getName())); //NOI18N
}
access.setType(STATIC_FIELD_ACCESS);
access.setText(typename.getText() + '.' + name);
access.setJQLType(fieldType);
ident.setJQLType(fieldType);
access.setFirstChild(typename);
typename.setNextSibling(ident);
return access;
}
/**
* This method analyses and identifier defined in the current scope:
* - a field, variable or parameter defined in the symbol table
* - a type define in a separate symbol table for type names
* @param ident the identifier AST
* @param def the entry in the symbol table of the type names tables
* @return AST node representing a defined identifier
*/
protected JQLAST analyseDefinedIdentifier(JQLAST ident, Definition def)
{
Type type = def.getType();
if (def instanceof Variable)
{
ident.setType(VARIABLE);
}
else if (def instanceof Parameter)
{
ident.setType(PARAMETER);
}
else if (def instanceof Field)
{
FieldInfo fieldInfo = ((Field)def).getFieldInfo();
JQLAST fieldAccessAST = ident;
JQLAST identAST = new JQLAST(ident);
if (fieldInfo.isStatic())
{
JQLAST typeNameAST = new JQLAST(TYPENAME, candidateClass.getName(), candidateClass);
ident = analyseStaticFieldAccess(fieldAccessAST, typeNameAST,
identAST, candidateClass, fieldInfo);
}
else
{
JQLAST thisAST = new JQLAST(THIS, "this", candidateClass); //NOI18N
ident = analyseFieldAccess(fieldAccessAST, thisAST,
identAST, candidateClass, fieldInfo);
}
}
else if (def instanceof TypeName)
{
ident.setType(TYPENAME);
ident.setText(((TypeName)def).getQualifiedName());
}
else
{
type = typetab.errorType;
errorMsg.fatal(I18NHelper.getMessage(messages,
"jqlc.semantic.analysedefinedidentifier.illegalident", //NOI18N
String.valueOf(def)));
}
ident.setJQLType(type);
return ident;
}
/**
* Analyses a call for an object that implements Collection.
* Currently, contains is the only valid Collection method in a query filter.
*/
protected JQLAST analyseCollectionCall(JQLAST dot, JQLAST collection, JQLAST method, JQLAST args)
{
String methodName = method.getText();
JQLAST firstArg = (JQLAST)args.getFirstChild();
if (methodName.equals("contains")) //NOI18N
{
checkContainsArgs(collection, method, firstArg);
dot.setType(CONTAINS);
dot.setJQLType(typetab.booleanType);
dot.setFirstChild(collection);
collection.setNextSibling(firstArg);
return dot;
}
else if (methodName.equals("isEmpty")) //NOI18N
{
// isEmpty does not take parameters
checkNoArgs(method, firstArg);
dot.setType(IS_EMPTY);
dot.setJQLType(typetab.booleanType);
dot.setFirstChild(collection);
collection.setNextSibling(null);
return dot;
}
errorMsg.error(dot.getLine(), dot.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
dot.setJQLType(typetab.errorType);
return dot;
}
/**
* Analyses a call for an object of type String.
* Currently startsWith and endsWith are the only valid String methods in a query filter
*/
protected JQLAST analyseStringCall(JQLAST dot, JQLAST string, JQLAST method, JQLAST args)
{
String methodName = method.getText();
JQLAST firstArg = (JQLAST)args.getFirstChild();
if (methodName.equals("startsWith")) //NOI18N
{
dot.setType(STARTS_WITH);
checkOneStringArg(method, firstArg);
dot.setJQLType(typetab.booleanType);
dot.setFirstChild(string);
string.setNextSibling(firstArg);
}
else if (methodName.equals("endsWith")) //NOI18N
{
dot.setType(ENDS_WITH);
checkOneStringArg(method, firstArg);
dot.setJQLType(typetab.booleanType);
dot.setFirstChild(string);
string.setNextSibling(firstArg);
}
else if (methodName.equals("like")) //NOI18N
{
checkLikeArgs(method, firstArg);
dot.setType(LIKE);
dot.setJQLType(typetab.booleanType);
dot.setFirstChild(string);
string.setNextSibling(firstArg);
}
else if (methodName.equals("substring")) //NOI18N
{
checkTwoIntArgs(method, firstArg);
dot.setType(SUBSTRING);
dot.setJQLType(typetab.stringType);
dot.setFirstChild(string);
string.setNextSibling(firstArg);
}
else if (methodName.equals("indexOf")) //NOI18N
{
checkIndexOfArgs(method, firstArg);
dot.setType(INDEXOF);
dot.setJQLType(typetab.intType);
dot.setFirstChild(string);
string.setNextSibling(firstArg);
}
else if (methodName.equals("length")) //NOI18N
{
// length does not take parameters
checkNoArgs(method, firstArg);
dot.setType(LENGTH);
dot.setJQLType(typetab.intType);
dot.setFirstChild(string);
string.setNextSibling(null);
}
else
{
errorMsg.error(dot.getLine(), dot.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
dot.setJQLType(typetab.errorType);
}
return dot;
}
/**
* Analyses a java.lang.Math call.
*/
protected JQLAST analyseMathCall(JQLAST dot, JQLAST type, JQLAST method, JQLAST args)
{
String methodName = method.getText();
JQLAST firstArg = (JQLAST)args.getFirstChild();
if (methodName.equals("abs")) //NOI18N
{
checkAbsArgs(method, firstArg);
dot.setType(ABS);
dot.setJQLType(firstArg.getJQLType());
dot.setFirstChild(firstArg);
}
else if (methodName.equals("sqrt")) //NOI18N
{
checkSqrtArgs(method, firstArg);
dot.setType(SQRT);
dot.setJQLType(firstArg.getJQLType());
dot.setFirstChild(firstArg);
}
else
{
errorMsg.error(dot.getLine(), dot.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
dot.setJQLType(typetab.errorType);
}
return dot;
}
/**
* This method checks the specified node (args) representing an empty
* argument list.
*/
protected void checkNoArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg != null)
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
}
/**
* This method checks the specified node (args) representing an argument
* list which consists of a single argument of type String.
*/
protected void checkOneStringArg(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling() != null)
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
Type argType = firstArg.getJQLType();
if (!argType.equals(typetab.stringType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
argType.getName(), typetab.stringType.getName()));
}
}
}
/**
* This method checks the specified node (args) representing a valid contains
* argument list: one argument denoting a variable.
*/
protected void checkContainsArgs(JQLAST collection, JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling() != null)
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getType() != VARIABLE)
{
errorMsg.unsupported(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysecollectioncall.nonvariable")); //NOI18N
}
else
{
FieldInfo collectionFieldInfo = getCollectionField(collection);
if (collectionFieldInfo == null)
{
errorMsg.unsupported(collection.getLine(), collection.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysecollectioncall.unsupportedcollectionexpr", //NOI18N
collection.getText()));
}
else if (!collectionFieldInfo.isRelationship())
{
// check compatibilty of collection element type and type of variable
errorMsg.error(collection.getLine(), collection.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysecollectioncall.relationshipexpected", //NOI18N
collectionFieldInfo.getName()));
}
Type variableType = firstArg.getJQLType();
Type elementType = collectionFieldInfo.getAssociatedClass();
if (!elementType.isCompatibleWith(variableType))
{
errorMsg.error(collection.getLine(), collection.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysecollectioncall.typemismatch", //NOI18N
elementType.getName(), variableType.getName()));
}
}
}
/**
* This method checks the specified node (args) representing a valid like
* argument list: a string argument plus an optional char argument.
*/
protected void checkLikeArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if ((firstArg.getNextSibling() != null) &&
(firstArg.getNextSibling().getNextSibling() != null))
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling().getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
// check type of first arg
Type firstArgType = firstArg.getJQLType();
if (!firstArgType.equals(typetab.stringType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
firstArgType.getName(), typetab.stringType.getName()));
}
// check type of second arg (if available)
JQLAST secondArg = (JQLAST)firstArg.getNextSibling();
if (secondArg != null)
{
Type secondArgType = secondArg.getJQLType();
if (!typetab.isCharType(secondArgType))
{
errorMsg.error(secondArg.getLine(), secondArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
secondArgType.getName(), typetab.charType.getName()));
}
}
}
}
/**
* This method checks the specified node (args) representing an argument
* list which consists of two integer arguments.
*/
protected void checkTwoIntArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
// no args specified
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling() == null)
{
// one arg specified
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling().getNextSibling() != null)
{
// more than two args specified
JQLAST nextArg = (JQLAST)firstArg.getNextSibling().getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
// specified two args
// check type of first arg
Type firstArgType = firstArg.getJQLType();
if (!typetab.isIntType(firstArgType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
firstArgType.getName(), typetab.intType.getName()));
}
// check type of second arg
JQLAST secondArg = (JQLAST)firstArg.getNextSibling();
Type secondArgType = firstArg.getJQLType();
if (!typetab.isIntType(secondArgType))
{
errorMsg.error(secondArg.getLine(), secondArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
secondArgType.getName(), typetab.intType.getName()));
}
}
}
/**
* This method checks the specified node (args) representing a valid indexOf
* argument list: a string argument plus an optional char argument.
*/
protected void checkIndexOfArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if ((firstArg.getNextSibling() != null) &&
(firstArg.getNextSibling().getNextSibling() != null))
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling().getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
// check type of first arg
Type firstArgType = firstArg.getJQLType();
if (!firstArgType.equals(typetab.stringType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
firstArgType.getName(), typetab.stringType.getName()));
}
// check type of second arg (if available)
JQLAST secondArg = (JQLAST)firstArg.getNextSibling();
if (secondArg != null)
{
Type secondArgType = secondArg.getJQLType();
if (!typetab.isIntType(secondArgType))
{
errorMsg.error(secondArg.getLine(), secondArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
secondArgType.getName(), typetab.intType.getName()));
}
}
}
}
/**
* This method checks the specified node (args) representing a valid abs
* argument list: a single number argument.
*/
protected void checkAbsArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling() != null)
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
Type argType = firstArg.getJQLType();
if (!typetab.isNumberType(argType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
argType.getName(), "number type"));
}
}
}
/**
* This method checks the specified node (args) representing a valid sqrt
* argument list: a single argument of type double or Double.
*/
protected void checkSqrtArgs(JQLAST method, JQLAST firstArg)
{
if (firstArg == null)
{
errorMsg.error(method.getLine(), method.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else if (firstArg.getNextSibling() != null)
{
JQLAST nextArg = (JQLAST)firstArg.getNextSibling();
errorMsg.error(nextArg.getLine(), nextArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.numbermismatch")); //NOI18N
}
else
{
Type argType = firstArg.getJQLType();
if (!typetab.isDoubleType(argType))
{
errorMsg.error(firstArg.getLine(), firstArg.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.arguments.typemismatch", //NOI18N
argType.getName(), "double or Double"));
}
}
}
/**
*
*/
protected FieldInfo getCollectionField(JQLAST expr)
{
JQLAST child = (JQLAST)expr.getFirstChild();
switch (expr.getType())
{
case FIELD_ACCESS:
case NAVIGATION:
if ((child != null) && (child.getNextSibling() != null))
{
ClassType classType = (ClassType)child.getJQLType();
String fieldName = child.getNextSibling().getText();
return classType.getFieldInfo(fieldName);
}
errorMsg.fatal(I18NHelper.getMessage(messages, "jqlc.semantic.getcollectionfield.missingchildren")); //NOI18N
break;
case TYPECAST:
if ((child != null) && (child.getNextSibling() != null))
{
return getCollectionField((JQLAST)child.getNextSibling());
}
errorMsg.fatal(I18NHelper.getMessage(messages, "jqlc.semantic.getcollectionfield.missingchildren")); //NOI18N
break;
}
return null;
}
/**
* Analyses a bitwise/logical operation (&, |, ^)
* @param op the bitwise/logical operator
* @param leftAST left operand
* @param rightAST right operand
* @return Type
*/
protected Type analyseBitwiseExpr(JQLAST op, JQLAST leftAST, JQLAST rightAST)
{
Type left = leftAST.getJQLType();
Type right = rightAST.getJQLType();
// handle error type
if (left.equals(typetab.errorType) || right.equals(typetab.errorType))
return typetab.errorType;
switch(op.getType())
{
case BAND:
case BOR:
if (typetab.isBooleanType(left) && typetab.isBooleanType(right))
return typetab.booleanType;
else if (typetab.isIntegralType(left) || typetab.isIntegralType(right))
{
errorMsg.unsupported(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analysebitwiseexpr.integerbitwiseop", //NOI18N
op.getText()));
return typetab.errorType;
}
break;
case BXOR:
if (typetab.isBooleanType(left) && typetab.isBooleanType(right))
{
errorMsg.unsupported(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.analysebitwiseexpr.exclusiveorop")); //NOI18N
return typetab.errorType;
}
else if (typetab.isIntegralType(left) || typetab.isIntegralType(right))
{
errorMsg.unsupported(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analysebitwiseexpr.integerbitwiseop", //NOI18N
op.getText()));
return typetab.errorType;
}
break;
}
// if this code is reached a bitwise operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
* Analyses a boolean conditional operation (&&, ||)
* @param op the conditional operator
* @param leftAST left operand
* @param rightAST right operand
* @return Type
*/
protected Type analyseConditionalExpr(JQLAST op, JQLAST leftAST, JQLAST rightAST)
{
Type left = leftAST.getJQLType();
Type right = rightAST.getJQLType();
// handle error type
if (left.equals(typetab.errorType) || right.equals(typetab.errorType))
return typetab.errorType;
switch(op.getType())
{
case AND:
case OR:
if (typetab.isBooleanType(left) && typetab.isBooleanType(right))
return typetab.booleanType;
break;
}
// if this code is reached a conditional operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
* Analyses a relational operation (<, <=, >, >=, ==, !=)
* @param op the relational operator
* @param leftAST left operand
* @param rightAST right operand
* @return Type
*/
protected Type analyseRelationalExpr(JQLAST op, JQLAST leftAST, JQLAST rightAST)
{
Type left = leftAST.getJQLType();
Type right = rightAST.getJQLType();
// handle error type
if (left.equals(typetab.errorType) || right.equals(typetab.errorType))
return typetab.errorType;
// special check for <, <=, >, >=
// left and right hand types must be orderable
switch(op.getType())
{
case LT:
case LE:
case GT:
case GE:
if (!left.isOrderable())
{
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analyserelationalexpr.notorderable", //NOI18N
left.getName(), op.getText()));
return typetab.errorType;
}
if (!right.isOrderable())
{
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.analyserelationalexpr.notorderable", //NOI18N
right.getName(), op.getText()));
return typetab.errorType;
}
break;
case EQUAL:
case NOT_EQUAL:
if ((leftAST.getType() == CONTAINS) || (rightAST.getType() == CONTAINS))
{
errorMsg.unsupported(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.unsupportedconstraintop", op.getText())); //NOI18N
return typetab.errorType;
}
break;
}
// check for numeric types, numeric wrapper class types and math class types
if (typetab.isNumberType(left) && typetab.isNumberType(right))
return typetab.booleanType;
// check for boolean and java.lang.Boolean
if (typetab.isBooleanType(left) && typetab.isBooleanType(right))
return typetab.booleanType;
if (left.isCompatibleWith(right) || right.isCompatibleWith(left))
return typetab.booleanType;
// if this code is reached a conditional operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
* Analyses a
* @param op the operator
* @param leftAST left operand
* @param rightAST right operand
* @return Type
*/
protected Type analyseBinaryArithmeticExpr(JQLAST op, JQLAST leftAST, JQLAST rightAST)
{
Type left = leftAST.getJQLType();
Type right = rightAST.getJQLType();
// handle error type
if (left.equals(typetab.errorType) || right.equals(typetab.errorType))
return typetab.errorType;
if (typetab.isNumberType(left) && typetab.isNumberType(right))
{
// handle java.math.BigDecimal
if (left.isCompatibleWith(typetab.bigDecimalType))
return left;
if (right.isCompatibleWith(typetab.bigDecimalType))
return right;
// handle java.math.BigInteger
if (left.isCompatibleWith(typetab.bigIntegerType))
{
// if right is floating point return BigDecimal,
// otherwise return BigInteger
return typetab.isFloatingPointType(right) ?
typetab.bigDecimalType : left;
}
if (right.isCompatibleWith(typetab.bigIntegerType))
{
// if left is floating point return BigDecimal,
// otherwise return BigInteger
return typetab.isFloatingPointType(left) ?
typetab.bigDecimalType : right;
}
boolean wrapper = false;
if (left instanceof NumericWrapperClassType)
{
left = ((NumericWrapperClassType)left).getPrimitiveType();
wrapper = true;
}
if (right instanceof NumericWrapperClassType)
{
right = ((NumericWrapperClassType)right).getPrimitiveType();
wrapper = true;
}
// handle numeric types with arbitrary arithmetic operator
if ((left instanceof NumericType) && (right instanceof NumericType))
{
Type promotedType = typetab.binaryNumericPromotion(left, right);
if (wrapper && (promotedType instanceof NumericType))
{
promotedType = ((NumericType)promotedType).getWrapper();
}
return promotedType;
}
}
else if (op.getType() == PLUS)
{
// handle + for strings
// MBO: note, this if matches char + char (which it should'nt),
// but this case is already handled above
if ((left.equals(typetab.stringType) || left.equals(typetab.charType)) &&
(right.equals(typetab.stringType) || right.equals(typetab.charType)))
{
return typetab.stringType;
}
}
// if this code is reached a conditional operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
* Analyses a
* @param op the operator
* @param argAST right operand
* @return Type
*/
protected Type analyseUnaryArithmeticExpr(JQLAST op, JQLAST argAST)
{
Type arg = argAST.getJQLType();
// handle error type
if (arg.equals(typetab.errorType))
return typetab.errorType;
// handle java.math.BigDecimal and java.math.BigInteger
if (arg.isCompatibleWith(typetab.bigDecimalType))
return arg;
// handle java.math.BigInteger
if (arg.isCompatibleWith(typetab.bigIntegerType))
return arg;
boolean wrapper = false;
if (arg instanceof NumericWrapperClassType)
{
arg = ((NumericWrapperClassType)arg).getPrimitiveType();
wrapper = true;
}
if (arg instanceof NumericType)
{
Type promotedType = typetab.unaryNumericPromotion(arg);
if (wrapper && (promotedType instanceof NumericType))
{
promotedType = ((NumericType)promotedType).getWrapper();
}
return promotedType;
}
// if this code is reached a conditional operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
* Analyses a
* @param op the operator
* @param argAST right operand
* @return Type
*/
protected Type analyseComplementExpr(JQLAST op, JQLAST argAST)
{
Type arg = argAST.getJQLType();
// handle error type
if (arg.equals(typetab.errorType))
return typetab.errorType;
switch(op.getType())
{
case BNOT:
if (typetab.isIntegralType(arg))
{
return arg;
}
break;
case LNOT:
if (typetab.isBooleanType(arg))
{
if (argAST.getType() == CONTAINS)
{
errorMsg.unsupported(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.semantic.generic.unsupportedconstraintop", op.getText())); //NOI18N
return typetab.errorType;
}
return arg;
}
break;
}
// if this code is reached a conditional operator was used with invalid arguments
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.arguments.invalid", //NOI18N
op.getText()));
return typetab.errorType;
}
/**
*
*/
protected void checkConstraints(JQLAST ast, VariableTable tab)
{
checkConstraints(ast, null, tab);
}
/**
*
*/
protected void checkConstraints(JQLAST ast, String dependentVariable, VariableTable tab)
{
if (ast == null) return;
switch (ast.getType())
{
case VARIABLE:
tab.markUsed(ast, dependentVariable);
break;
case CONTAINS:
JQLAST expr = (JQLAST)ast.getFirstChild();
JQLAST var = (JQLAST)expr.getNextSibling();
checkConstraints(expr, var.getText(), tab);
tab.markConstraint(var, expr);
break;
case BOR:
case BXOR:
case OR:
JQLAST left = (JQLAST)ast.getFirstChild();
JQLAST right = (JQLAST)left.getNextSibling();
// prepare tab copy for right hand side and merge the right hand side copy into vartab
VariableTable copy = new VariableTable(tab);
checkConstraints(left, dependentVariable, tab);
checkConstraints(right, dependentVariable, copy);
tab.merge(copy);
break;
default:
for (JQLAST node = (JQLAST)ast.getFirstChild(); node != null; node = (JQLAST)node.getNextSibling())
{
checkConstraints(node, dependentVariable, tab);
}
break;
}
}
}
// rules
query
: #( QUERY
{
symtab.enterScope();
typeNames.enterScope();
}
candidateClass
imports
{
// enter new scope for variable and parameter names
symtab.enterScope();
}
parameters
variables
o:ordering
r:result
filter
{
typeNames.leaveScope();
// leaves variable and parameter name scope
symtab.leaveScope();
// leaves global scope
symtab.leaveScope();
}
)
{
checkResultOrdering(#r, #o);
}
;
// ----------------------------------
// rules: candidate class
// ----------------------------------
candidateClass
{
errorMsg.setContext("setClass"); //NOI18N
}
: c:CLASS_DEF
{
// check persistence capable
candidateClass = (ClassType)#c.getJQLType();
String className = candidateClass.getName();
if (!candidateClass.isPersistenceCapable())
{
errorMsg.unsupported(#c.getLine(), #c.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.candidateclass.nonpc", //NOI18N
className));
}
// get base name
int index = className.lastIndexOf('.');
String identName = index>0 ? className.substring(index+1) : className;
typeNames.declare(identName, new TypeName(candidateClass, className));
// init symbol table with field names of the candidate class
FieldInfo[] fieldInfos = candidateClass.getFieldInfos();
for (int i = 0; i < fieldInfos.length; i++)
{
FieldInfo fieldInfo = fieldInfos[i];
symtab.declare(fieldInfo.getName(), new Field(fieldInfo));
}
}
;
// ----------------------------------
// rules: import declaration
// ----------------------------------
imports!
{
errorMsg.setContext("declareImports"); //NOI18N
}
: ( declareImport )*
;
declareImport
{ String name = null; }
: #( i:IMPORT_DEF name = qualifiedName )
{
Type type = typetab.checkType(name);
if (type == null)
{
errorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.unknowntype", name)); //NOI18N
}
// get base name
int index = name.lastIndexOf('.');
String identName = index>0 ? name.substring(index+1) : name;
Definition old = typeNames.declare(identName, new TypeName(type, name));
if (old != null)
{
errorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.alreadydeclared", //NOI18N
identName, old.getName()));
}
}
;
// ----------------------------------
// rules: parameter declaration
// ----------------------------------
parameters
{
errorMsg.setContext("declareParameters"); //NOI18N
}
: ( declareParameter )*
;
declareParameter
: #( PARAMETER_DEF t:type i:IDENT )
{
String name = #i.getText();
Type type = #t.getJQLType();
Definition old = symtab.declare(name, new Parameter(type));
if (old != null)
{
errorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.alreadydeclared", //NOI18N
name, old.getName()));
}
#i.setJQLType(type);
paramtab.add(name, type);
}
;
// ----------------------------------
// rules: variable declaration
// ----------------------------------
variables
{
errorMsg.setContext("declareVariables"); //NOI18N
}
: ( declareVariable )*
;
declareVariable
: #( VARIABLE_DEF t:type i:IDENT )
{
String name = #i.getText();
Type type = #t.getJQLType();
Definition old = symtab.declare(name, new Variable(type));
if (old != null)
{
errorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.alreadydeclared", //NOI18N
name, old.getName()));
}
vartab.add(name);
#i.setJQLType(type);
}
;
// ----------------------------------
// rules: ordering specification
// ----------------------------------
ordering
{
errorMsg.setContext("setOrdering"); //NOI18N
}
: ( orderSpec )*
;
orderSpec
: #( ORDERING_DEF (ASCENDING | DESCENDING) e:expression )
{
analyseOrderingExpression(#e);
checkConstraints(#e, vartab);
}
;
// ----------------------------------
// rules: result expression
// ----------------------------------
result
{
errorMsg.setContext("setResult"); //NOI18N
}
: #( r:RESULT_DEF e:resultExpr )
{
checkValidResultExpr(#e);
#r.setJQLType(#e.getJQLType());
checkConstraints(#e, vartab);
}
| // empty rule
;
resultExpr
: #( d:DISTINCT e0:resultExpr )
{
#d.setJQLType(#e0.getJQLType());
}
| #( a:AVG e1:resultExpr )
{
#a.setJQLType(typetab.getAvgReturnType(#e1.getJQLType()));
}
| #( max:MAX e2:resultExpr )
{
#max.setJQLType(typetab.getMinMaxReturnType(#e2.getJQLType()));
}
| #( min:MIN e3:resultExpr )
{
#min.setJQLType(typetab.getMinMaxReturnType(#e3.getJQLType()));
}
| #( s:SUM e4:resultExpr )
{
#s.setJQLType(typetab.getSumReturnType(#e4.getJQLType()));
}
| #( c:COUNT resultExpr )
{
#c.setJQLType(typetab.longType);
}
| expression
;
// ----------------------------------
// rules: filer expression
// ----------------------------------
filter
{
errorMsg.setContext("setFilter"); //NOI18N
}
// There is always a filter defined and it is the last node of the query tree.
// Otherwise all the remaining subtrees after the CLASS_DEF subtree are empty
// which results in a ClassCastException antlr.ASTNullType when analysis
// the (non existsent) subtrees
: #( FILTER_DEF e:expression )
{
Type exprType = #e.getJQLType();
if (!(typetab.isBooleanType(exprType) || exprType.equals(typetab.errorType)))
{
// filter expression must have the type boolean or java.lang.Boolean
errorMsg.error(#e.getLine(), #e.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.filter.booleanexpected", exprType)); //NOI18N
}
checkConstraints(#e, vartab);
vartab.checkConstraints();
}
;
expression
{ String repr; }
: repr = e:exprNoCheck[false]
{
if (repr != null)
{
#e.setJQLType(typetab.errorType);
errorMsg.error(#e.getLine(), #e.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.expression.undefined", repr)); //NOI18N
}
}
;
exprNoCheck [boolean insideDotExpr] returns [String repr]
{ repr = null; } // repr is used to get the text of identifier
// inside a dot expression
: bitwiseExpr
| conditionalExpr
| relationalExpr
| binaryArithmeticExpr
| unaryArithmeticExpr
| complementExpr
| repr = primary[insideDotExpr]
;
bitwiseExpr
: #( op1:BAND left1:expression right1:expression )
{
#op1.setJQLType(analyseBitwiseExpr(#op1, #left1, #right1));
}
| #( op2:BOR left2:expression right2:expression )
{
#op2.setJQLType(analyseBitwiseExpr(#op2, #left2, #right2));
}
| #( op3:BXOR left3:expression right3:expression )
{
#op3.setJQLType(analyseBitwiseExpr(#op3, #left3, #right3));
}
;
conditionalExpr
: #( op1:AND left1:expression right1:expression )
{
#op1.setJQLType(analyseConditionalExpr(#op1, #left1, #right1));
}
| #( op2:OR left2:expression right2:expression )
{
#op2.setJQLType(analyseConditionalExpr(#op2, #left2, #right2));
}
;
relationalExpr
{
Type left = null;
Type right = null;
}
: #( op1:EQUAL left1:expression right1:expression )
{
#op1.setJQLType(analyseRelationalExpr(#op1, #left1, #right1));
left = #left1.getJQLType();
right = #right1.getJQLType();
if (typetab.isPersistenceCapableType(left) || typetab.isPersistenceCapableType(right))
{
#op1.setType(OBJECT_EQUAL);
}
else if (typetab.isCollectionType(left) || typetab.isCollectionType(right))
{
#op1.setType(COLLECTION_EQUAL);
}
}
| #( op2:NOT_EQUAL left2:expression right2:expression )
{
#op2.setJQLType(analyseRelationalExpr(#op2, #left2, #right2));
left = #left2.getJQLType();
right = #right2.getJQLType();
if (typetab.isPersistenceCapableType(left) || typetab.isPersistenceCapableType(right))
{
#op2.setType(OBJECT_NOT_EQUAL);
}
else if (typetab.isCollectionType(left) || typetab.isCollectionType(right))
{
#op2.setType(COLLECTION_NOT_EQUAL);
}
}
| #( op3:LT left3:expression right3:expression )
{
#op3.setJQLType(analyseRelationalExpr(#op3, #left3, #right3));
}
| #( op4:GT left4:expression right4:expression )
{
#op4.setJQLType(analyseRelationalExpr(#op4, #left4, #right4));
}
| #( op5:LE left5:expression right5:expression )
{
#op5.setJQLType(analyseRelationalExpr(#op5, #left5, #right5));
}
| #( op6:GE left6:expression right6:expression )
{
#op6.setJQLType(analyseRelationalExpr(#op6, #left6, #right6));
}
;
binaryArithmeticExpr
: #( op1:PLUS left1:expression right1:expression )
{
#op1.setJQLType(analyseBinaryArithmeticExpr(#op1, #left1, #right1));
if (#op1.getJQLType().equals(typetab.stringType))
{
// change the operator from PLUS to CONCAT in the case of string concatenation
#op1.setType(CONCAT);
}
}
| #( op2:MINUS left2:expression right2:expression )
{
#op2.setJQLType(analyseBinaryArithmeticExpr(#op2, #left2, #right2));
}
| #( op3:STAR left3:expression right3:expression )
{
#op3.setJQLType(analyseBinaryArithmeticExpr(#op3, #left3, #right3));
}
| #( op4:DIV left4:expression right4:expression )
{
#op4.setJQLType(analyseBinaryArithmeticExpr(#op4, #left4, #right4));
}
| #( op5:MOD left5:expression right5:expression )
{
#op5.setJQLType(analyseBinaryArithmeticExpr(#op5, #left5, #right5));
}
;
unaryArithmeticExpr
: #( op1:UNARY_PLUS arg1:expression )
{
#op1.setJQLType(analyseUnaryArithmeticExpr(#op1, #arg1));
}
| #( op2:UNARY_MINUS arg2:expression )
{
#op2.setJQLType(analyseUnaryArithmeticExpr(#op2, #arg2));
}
;
complementExpr
: #( op1:BNOT arg1:expression )
{
#op1.setJQLType(analyseComplementExpr(#op1, #arg1));
}
| #( op2:LNOT arg2:expression )
{
#op2.setJQLType(analyseComplementExpr(#op2, #arg2));
}
;
primary [boolean insideDotExpr] returns [String repr]
{ repr = null; }
: #( c:TYPECAST t:type e:expression )
{
Type type = #t.getJQLType();
Type exprType = #e.getJQLType();
if (!(type.isCompatibleWith(exprType) || exprType.isCompatibleWith(type)))
{
errorMsg.error(#c.getLine(), #c.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.primary.invalidcast", //NOI18N
exprType.getName(), type.getName()));
type = typetab.errorType;
}
#c.setJQLType(type);
}
| literal
| i:THIS
{ #i.setJQLType(candidateClass); }
| repr = dotExpr
| repr = identifier [insideDotExpr]
;
dotExpr returns [String repr]
{
repr = null;
}
: #( dot:DOT
repr = expr:exprNoCheck[true] ident:IDENT ( args:argList )?
)
{
Type type = null;
if (repr != null) // possible package name
{
String qualifiedName = repr + '.' + #ident.getText();
type = typetab.checkType(qualifiedName);
if (type == null)
{
// name does not define a valid class => return qualifiedName
repr = qualifiedName;
}
else if (#args == null)
{
// found valid class name and NO arguments specified
// => use of the class name
repr = null;
#dot.setType(TYPENAME);
#dot.setText(qualifiedName);
#dot.setFirstChild(null);
}
else
{
// found valid class name and arguments specified =>
// looks like constructor call
repr = null;
errorMsg.error(dot.getLine(), dot.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
}
#dot.setJQLType(type);
#dot.setText(#expr.getText() + '.' + #ident.getText());
}
else // no string repr of left hand side => expression is defined
{
#dotExpr = analyseDotExpr(#dot, #expr, #ident, #args);
}
}
;
argList
: #( ARG_LIST (expression)* )
;
identifier [boolean insideDotExpr] returns [String repr]
{
repr = null; // repr is set when ident is part of a package name spec
}
: ident:IDENT ( args:argList ) ?
{
String name = #ident.getText();
Definition def = symtab.getDefinition(name);
// check args, if defined => invalid method call
if (#args != null)
{
#ident.setJQLType(typetab.errorType);
errorMsg.error(#ident.getLine(), #ident.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.invalidmethodcall")); //NOI18N
}
else if (def != null)
{
#ident = analyseDefinedIdentifier(#ident, def);
}
else if (insideDotExpr)
{
Definition typedef = typeNames.getDefinition(name);
if (typedef != null)
{
#ident = analyseDefinedIdentifier(#ident, typedef);
}
else
{
repr = #ident.getText();
}
}
else
{
#ident.setJQLType(typetab.errorType);
errorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.identifier.undefined", //NOI18N
ident.getText()));
}
}
;
literal
: b1:TRUE { #b1.setJQLType(typetab.booleanType); }
| b2:FALSE { #b2.setJQLType(typetab.booleanType); }
| i:INT_LITERAL { #i.setJQLType(typetab.intType); }
| l:LONG_LITERAL { #l.setJQLType(typetab.longType); }
| f:FLOAT_LITERAL { #f.setJQLType(typetab.floatType); }
| d:DOUBLE_LITERAL { #d.setJQLType(typetab.doubleType); }
| c:CHAR_LITERAL { #c.setJQLType(typetab.charType); }
| s:STRING_LITERAL { #s.setJQLType(typetab.stringType); }
| n:NULL { #n.setJQLType(typetab.nullType); }
;
qualifiedName returns [String name]
{ name = null; }
: id1:IDENT
{
name = #id1.getText();
}
| #( d:DOT
name = qualifiedName
id2:IDENT
{
name += (#d.getText() + #id2.getText());
}
)
;
type
{ String name = null; }
: name = qn:qualifiedName
{
Type type = null;
if (typeNames.isDeclared(name))
{
Definition def = typeNames.getDefinition(name);
if (def instanceof TypeName)
{
type = def.getType();
}
else
{
errorMsg.error(#qn.getLine(), #qn.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.type.notype", //NOI18N
name, def.getName()));
}
}
else
{
type = typetab.checkType(name);
if ((type == null) && (name.indexOf('.') == -1))
{
// ckeck java.lang class without package name
type = typetab.checkType("java.lang." + name); //NOI18N
}
if (type == null)
{
errorMsg.error(#qn.getLine(), #qn.getColumn(),
I18NHelper.getMessage(messages, "jqlc.semantic.generic.unknowntype", name)); //NOI18N
}
}
// change AST to a single node that represents the full class name
#qn.setType(TYPENAME);
#qn.setText(name);
#qn.setFirstChild(null);
#qn.setJQLType(type);
}
| p:primitiveType
{
#p.setJQLType(typetab.checkType(#p.getText()));
#p.setType(TYPENAME);
}
;
primitiveType
: BOOLEAN
| BYTE
| CHAR
| SHORT
| INT
| FLOAT
| LONG
| DOUBLE
;