blob: 1ad17da533457f3ce941dac54268630f87e9558e [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 November 19, 2001
*/
header
{
package com.sun.jdo.spi.persistence.support.ejb.ejbqlc;
import java.util.ResourceBundle;
import java.lang.reflect.Method;
import org.glassfish.persistence.common.I18NHelper;
import com.sun.jdo.spi.persistence.support.ejb.ejbc.MethodHelper;
}
/**
* This class defines the semantic analysis of the EJBQL compiler.
* Input of this pass is the AST as produced by the parser,
* that consists of EJBQLAST nodes.
* The result is a typed EJBQLAST tree.
*
* @author Michael Bouschen
* @author Shing Wai Chan
*/
class Semantic extends TreeParser;
options
{
importVocab = EJBQL;
buildAST = true;
defaultErrorHandler = false;
ASTLabelType = "EJBQLAST"; //NOI18N
}
{
/** Name of the property to disable order by validation. */
public static final String DISABLE_ORDERBY_VALIDATION_PROPERTY =
"com.sun.jdo.spi.persistence.support.ejb.ejbqlc.DISABLE_ORDERBY_VALIDATION"; // NOI18N
/**
* Property to disable order by validation.
* Note, the default is false, meaning the compiler checks that select
* clause and orderby clause are compatible.
*/
private static final boolean DISABLE_ORDERBY_VALIDATION =
Boolean.getBoolean(DISABLE_ORDERBY_VALIDATION_PROPERTY);
/** Symbol table handling names of variables and parameters. */
protected SymbolTable symtab;
/** Type info access helper. */
protected TypeSupport typeSupport;
/** Parameter info helper. */
protected ParameterSupport paramSupport;
/** The Method instance of the finder/selector method. */
protected Method method;
/** result-type-mapping element from the DD. */
protected int resultTypeMapping;
/** Flag indicating finder or selector. */
protected boolean finderNotSelector;
/** Flag indicating have aggregate function or not. */
protected boolean isAggregate = false;
/** The ejb-name. */
protected String ejbName;
/** I18N support. */
protected final static ResourceBundle msgs = I18NHelper.loadBundle(
Semantic.class);
/**
* Initializes the semantic analysis.
* @param typeSupport type info access helper.
* @param paramSupport parameter info helper.
* @param method method instance of the finder/selector method.
* @param resultTypeMapping result-type-mapping element from the DD
* @param finderNotSelector <code>true</code> for finder;
* <code>false</code> for selector
* @param ejbName the ejb name of the finder/selector method.
*/
public void init(TypeSupport typeSupport, ParameterSupport paramSupport,
Method method, int resultTypeMapping,
boolean finderNotSelector, String ejbName)
{
this.symtab = new SymbolTable();
this.typeSupport = typeSupport;
this.paramSupport = paramSupport;
this.method = method;
this.resultTypeMapping = resultTypeMapping;
this.finderNotSelector = finderNotSelector;
this.ejbName = ejbName;
}
/** */
public void reportError(RecognitionException ex) {
ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_SemanticError"), ex); //NOI18N
}
/** */
public void reportError(String s) {
ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_SemanticError") + s); //NOI18N
}
//========= Internal helper methods ==========
/**
* Checks the return type and the type of the select clause expression
* of a finder method.
* <p>
* The return type of a finder must be one of the following:
* <ul>
* <li>java.util.Collection (multi-object finder)
* <li>java.util.Enumeration (EJB 1.1 multi-object finder)
* <li>the entity bean's remote interface (single-object finder)
* <li>the entity bean's local interface (single-object finder)
* </ul>
* The type of the select clause expression of a finder must be
* the entity bean's local or remote interface.
* @param returnType the return type of the finder/selector method object
* @param selectClauseTypeInfo the type info of the select clause
* expression.
*/
private void checkFinderReturnType(
Class returnType, Object selectClauseTypeInfo)
{
String selectClauseTypeName = typeSupport.getTypeName(selectClauseTypeInfo);
Object returnTypeInfo = typeSupport.getTypeInfo(returnType);
// The return type of a finder must be Collection or Enumeration or
// the entity bean's remote or local interface
if ((returnType != java.util.Collection.class) &&
(returnType != java.util.Enumeration.class) &&
(!typeSupport.isRemoteInterfaceOfEjb(returnTypeInfo, ejbName)) &&
(!typeSupport.isLocalInterfaceOfEjb(returnTypeInfo, ejbName))) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidFinderReturnType", returnType.getName())); //NOI18N
}
// The type of the select clause expression must be the ejb name
// of this bean.
if (!selectClauseTypeName.equals(this.ejbName)) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidFinderSelectClauseType", selectClauseTypeName)); //NOI18N
}
}
/**
* Implements type compatibility for selector. The method returns
* <code>true</code> if returnTypeInfo is compatible with
* selectClauseTypeInfo.
*/
private boolean isCompatibleSelectorSelectorReturnType(
Object returnTypeInfo, Object selectClauseTypeInfo)
{
if (isAggregate) {
return getCommonOperandType(selectClauseTypeInfo, returnTypeInfo) != TypeSupport.errorType;
} else {
return typeSupport.isCompatibleWith(selectClauseTypeInfo, returnTypeInfo);
}
}
/**
* Checks the return type and the type of the select clause expression
* of a selector method.
* <p>
* The return type of a selector must be one of the following:
* <ul>
* <li>java.util.Collection (multi-object selector)
* <li>java.util.Set (multi-object selector)
* <li>assignable from the type of the select clause expression
* (single-object selector)
* </ul>
* @param returnType the return type of the finder/selector method object
* @param selectClauseTypeInfo the type info of the select clause
* expression.
*/
private void checkSelectorReturnType(
Class returnType, Object selectClauseTypeInfo)
{
String selectClauseTypeName = typeSupport.getTypeName(selectClauseTypeInfo);
Object returnTypeInfo = typeSupport.getTypeInfo(returnType);
// The return type of a selector must be Collection or Set or
// assingable from the type of the select clause expression
if ((returnType != java.util.Collection.class) &&
(returnType != java.util.Set.class) &&
!isCompatibleSelectorSelectorReturnType(returnTypeInfo,
selectClauseTypeInfo)) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidSelectorReturnType", //NOI18N
typeSupport.getTypeName(returnTypeInfo), selectClauseTypeName));
}
}
/**
* Checks the result-type-mapping element setting in the case of a finder
* method. Finder must not specify result-type-mapping.
*/
private void checkFinderResultTypeMapping()
{
if (resultTypeMapping != MethodHelper.NO_RETURN) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidResultTypeMappingForFinder")); //NOI18N
}
}
/**
* Checks the setting of the result-type-mapping element for a
* selector. Only selectors returning a entity object may
* specify this.
* <p>
* The method checks the following error cases:
* <ul>
* <li>result-type-mapping is specified as Remote,
* but bean does not have remote interface
* <li>result-type-mapping is specified as Local,
* but bean does not have local interface
* <li>single-object selector returns remote interface,
* but result-type-mapping is not specified as Remote
* <li>single-object selector returns local interface,
* but result-type-mapping is specified as Remote
* <li>result-type-mapping is specified for a selector returning
* non-entity objects.
* </ul>
* @param returnType the return type of the finder/selector method object
* @param selectClauseTypeInfo the type info of the select clause.
*/
private void checkSelectorResultTypeMapping(
Class returnType, Object selectClauseTypeInfo)
{
Object returnTypeInfo = typeSupport.getTypeInfo(returnType);
// case: multi-object selector returning entity objects
if (typeSupport.isCollectionType(returnTypeInfo) &&
typeSupport.isEjbName(selectClauseTypeInfo)) {
if (resultTypeMapping == MethodHelper.REMOTE_RETURN) {
// result-type-mapping is Remote =>
// bean must have remote interface
if (!typeSupport.hasRemoteInterface(selectClauseTypeInfo)) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidRemoteResultTypeMappingForMultiSelector", //NOI18N
selectClauseTypeInfo));
}
}
else {
// result-type-mapping is Local or not specified =>
// bean must have local interface
if (!typeSupport.hasLocalInterface(selectClauseTypeInfo)) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidLocalResultTypeMappingForMultiSelector", //NOI18N
selectClauseTypeInfo));
}
}
}
// case: single-object selector returning remote interface
else if (typeSupport.isRemoteInterface(returnTypeInfo)) {
// result-type-mapping must be Remote
if (resultTypeMapping != MethodHelper.REMOTE_RETURN) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidLocalResultTypeMappingForSingleSelector")); //NOI18N
}
}
// case: single-object selector returning local interface
else if (typeSupport.isLocalInterface(returnTypeInfo)) {
// result-type-mapping must be Local or not specified
if (resultTypeMapping == MethodHelper.REMOTE_RETURN) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidRemoteResultTypeMappingForSingleSelector")); //NOI18N
}
}
// cases: single-object and multi-object selector
// returning non-enity object(s)
else if (resultTypeMapping != MethodHelper.NO_RETURN) {
// result-type-mapping must not be specified
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidResultTypeMappingForSelector", //NOI18N
selectClauseTypeInfo));
}
}
/**
* Checks that select clause and orderby clause are compatible.
* <p>
* The method checks the following error cases:
* <ul>
* <li>if the select clause is an identification variable or
* a single valued cmr path expression, then the orderby item
* must be a cmp field of the entity bean abstract schema
* type value returned by the SELECT clause
* <li>if the select clause is a cmp field, then
* orderby item must be empty or the same cmp field.
* </ul>
* @param select the select clause of the query
* @param orderby the orderby clause of the query
*/
private void checkSelectOrderbyClause(EJBQLAST select, EJBQLAST orderby)
{
// nothing to check if no orderby clause or
// if orderby validation is disabled
if ((orderby == null) || DISABLE_ORDERBY_VALIDATION) {
return;
}
AST selectReturnAST = select.getFirstChild();
// skip DISTINCT node, so selectReturnAST should be one of the following:
// Object(x), cmr-field, cmp-field
// it is illegal to be an aggregate function node
if (selectReturnAST.getType() == DISTINCT) {
selectReturnAST = selectReturnAST.getNextSibling();
}
if (selectReturnAST.getType() == CMP_FIELD_ACCESS) {
StringBuffer buf = new StringBuffer();
genPathExpression(selectReturnAST, buf);
String selectReturnPathExpr = buf.toString();
for (AST sibling = orderby.getFirstChild();
sibling != null;
sibling = sibling.getNextSibling().getNextSibling()) {
// share buf
buf.setLength(0);
genPathExpression(sibling, buf);
String siblingPathExpr = buf.toString();
if (!selectReturnPathExpr.equals(siblingPathExpr)) {
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidOrderbyItemForCMPSelect", //NOI18N
siblingPathExpr));
}
}
} else {
AST abstractSchemaAST = null;
if (selectReturnAST.getType() == SINGLE_CMR_FIELD_ACCESS) {
abstractSchemaAST = selectReturnAST;
} else if (selectReturnAST.getType() == OBJECT) {
abstractSchemaAST = selectReturnAST.getFirstChild();
} else { // it must be an aggregate function node
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidAggregateOrderby" //NOI18N
));
}
StringBuffer buf = new StringBuffer();
genPathExpression(abstractSchemaAST, buf);
String abstractSchemaExpr = buf.toString();
for (AST sibling = orderby.getFirstChild();
sibling != null;
sibling = sibling.getNextSibling().getNextSibling()) {
// share buf
buf.setLength(0);
genPathExpression(sibling.getFirstChild(), buf);
String siblingRootExpr = buf.toString();
if (!abstractSchemaExpr.equals(siblingRootExpr)) {
buf.setLength(0);
genPathExpression(sibling, buf);
ErrorMsg.error(I18NHelper.getMessage(msgs,
"EXC_InvalidOrderbyItem", //NOI18N
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
*/
//SW: We can write this method without recursion. Michael suggests to use
//recursion for readability.
private void genPathExpression(AST ast, StringBuffer buf) {
if (ast == null) {
return;
}
switch (ast.getType()) {
case CMP_FIELD_ACCESS:
case COLLECTION_CMR_FIELD_ACCESS:
case SINGLE_CMR_FIELD_ACCESS:
AST left = ast.getFirstChild();
AST right = left.getNextSibling();
genPathExpression(left, buf);
buf.append('.');
genPathExpression(right, buf);
break;
default:
buf.append(ast.getText());
break;
}
}
/**
* Analyses a logical operation AND, OR
* @param op the logical operator
* @param leftAST left operand
* @param rightAST right operand
* @return the type info of the operator
*/
private Object analyseConditionalExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
{
Object left = leftAST.getTypeInfo();
Object right = rightAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right))
return typeSupport.errorType;
if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
Object common = typeSupport.booleanType;
return common;
}
// if this code is reached a bitwise operator was used with invalid arguments
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
return typeSupport.errorType;
}
/**
* Analyses a equality operation (==, <>)
* @param op the relational operator
* @param leftAST left operand
* @param rightAST right operand
* @return the type info of the operator
*/
private Object analyseEqualityExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
{
Object left = leftAST.getTypeInfo();
Object right = rightAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
return typeSupport.errorType;
}
// check left hand side for literals and input params
if (isLiteral(leftAST)) {
ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidLHSLiteral", //NOI18N
leftAST.getText(), op.getText()));
return typeSupport.errorType;
}
else if (isInputParameter(leftAST)) {
ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidLHSParameter", //NOI18N
leftAST.getText(), op.getText()));
return typeSupport.errorType;
}
// check operand types
if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
return typeSupport.booleanType;
}
else if (typeSupport.isStringType(left) && typeSupport.isStringType(right)) {
return typeSupport.booleanType;
}
else if (typeSupport.isDateTimeType(left) && typeSupport.isDateTimeType(right)) {
return typeSupport.booleanType;
}
else if (isEntityBeanValue(leftAST) && isEntityBeanValue(rightAST) &&
(typeSupport.isCompatibleWith(left, right) ||
typeSupport.isCompatibleWith(right, left))) {
String leftEjbName = (String)leftAST.getTypeInfo();
// the input parameter must be on right hand side of an equality
// expression ('?1' = e.department is not supported)
return analyseParameterEjbName(rightAST, leftEjbName);
}
else if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
return typeSupport.booleanType;
}
// if this code is reached a conditional operator was used with invalid arguments
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
return typeSupport.errorType;
}
/**
* Analyses a relational operation (<, <=, >, >=)
* @param op the relational operator
* @param leftAST left operand
* @param rightAST right operand
* @return the type info of the operator
*/
private Object analyseRelationalExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
{
Object left = leftAST.getTypeInfo();
Object right = rightAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
return typeSupport.errorType;
}
// check left hand side for literals and input params
if (isLiteral(leftAST)) {
ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidLHSLiteral", //NOI18N
leftAST.getText(), op.getText()));
return typeSupport.errorType;
}
else if (isInputParameter(leftAST)) {
ErrorMsg.error(leftAST.getLine(), leftAST.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidLHSParameter", //NOI18N
leftAST.getText(), op.getText()));
return typeSupport.errorType;
}
// check operand types
if ((typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) ||
(typeSupport.isDateTimeType(left) && typeSupport.isDateTimeType(right)) ||
(typeSupport.isStringType(left) && typeSupport.isStringType(right))) {
return typeSupport.booleanType;
}
// if this code is reached a conditional operator was used with invalid arguments
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
return typeSupport.errorType;
}
/**
* Analyses a binary arithmetic expression +, -, *, /.
* @param op the operator
* @param leftAST left operand
* @param rightAST right operand
* @return the type info of the operator
*/
private Object analyseBinaryArithmeticExpr(EJBQLAST op, EJBQLAST leftAST, EJBQLAST rightAST)
{
Object left = leftAST.getTypeInfo();
Object right = rightAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(left) || typeSupport.isErrorType(right)) {
return typeSupport.errorType;
}
if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
Object common = getCommonOperandType(left, right);
if (!typeSupport.isErrorType(common))
return common;
}
// if this code is reached a conditional operator was used with invalid arguments
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
return typeSupport.errorType;
}
/**
* Returns the common type info for the specified operand types.
* This includes binary numeric promotion as specified in Java.
* @param left type info of left operand
* @param right type info of right operand
* @return the type info of the operator
*/
private Object getCommonOperandType(Object left, Object right)
{
if (typeSupport.isNumberType(left) && typeSupport.isNumberType(right)) {
boolean wrapper = false;
// handle java.math.BigDecimal:
if (typeSupport.bigDecimalType.equals(left)) {
return left;
}
if (typeSupport.bigDecimalType.equals(right)) {
return right;
}
// handle java.math.BigInteger
if (typeSupport.bigIntegerType.equals(left)) {
// if right is floating point return BigDecimal,
// otherwise return BigInteger
return typeSupport.isFloatingPointType(right) ?
typeSupport.bigDecimalType : left;
}
if (typeSupport.bigIntegerType.equals(right)) {
// if left is floating point return BigDecimal,
// otherwise return BigInteger
return typeSupport.isFloatingPointType(left) ?
typeSupport.bigDecimalType : right;
}
if (typeSupport.isNumericWrapperType(left)) {
left = typeSupport.getPrimitiveType(left);
wrapper = true;
}
if (typeSupport.isNumericWrapperType(right)) {
right = typeSupport.getPrimitiveType(right);
wrapper = true;
}
// handle numeric types with arbitrary arithmetic operator
if (typeSupport.isNumericType(left) && typeSupport.isNumericType(right)) {
Object promotedType = typeSupport.binaryNumericPromotion(left, right);
if (wrapper)
promotedType = typeSupport.getWrapperType(promotedType);
return promotedType;
}
}
else if (typeSupport.isBooleanType(left) && typeSupport.isBooleanType(right)) {
// check for boolean wrapper class: if one of the operands has the
// type Boolean return Boolean, otherwise return boolean.
if (left.equals(typeSupport.booleanClassType) ||
right.equals(typeSupport.booleanClassType))
return typeSupport.booleanClassType;
else
return typeSupport.booleanType;
}
else if (typeSupport.isCompatibleWith(left, right)) {
return right;
}
else if (typeSupport.isCompatibleWith(right, left)) {
return left;
}
// not compatible types => return errorType
return typeSupport.errorType;
}
/**
* Analyses a unary expression (+ and -).
* @param op the operator
* @param argASTleftAST left operand
* @param rightAST right operand
* @return the type info of the operator
*/
private Object analyseUnaryArithmeticExpr(EJBQLAST op, EJBQLAST argAST)
{
Object arg = argAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(arg))
return arg;
if (typeSupport.isNumberType(arg)) {
boolean wrapper = false;
if (typeSupport.isNumericWrapperType(arg)) {
arg = typeSupport.getPrimitiveType(arg);
wrapper = true;
}
Object promotedType = typeSupport.unaryNumericPromotion(arg);
if (wrapper)
promotedType = typeSupport.getWrapperType(promotedType);
return promotedType;
}
// if this code is reached a conditional operator was used with invalid arguments
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", op.getText())); //NOI18N
return typeSupport.errorType;
}
/**
* Analyses a expression node that is expected to access a collection
* valued CMR field. It returns the element type of the collection valued
* CMR field.
* @param fieldAccess the field access node
* @return the type info of the operator
*/
private Object analyseCollectionValuedCMRField(EJBQLAST fieldAccess)
{
if (fieldAccess.getType() != COLLECTION_CMR_FIELD_ACCESS) {
ErrorMsg.fatal(I18NHelper.getMessage(msgs, "ERR_InvalidPathExpr")); //NOI18N
return typeSupport.errorType;
}
EJBQLAST classExpr = (EJBQLAST)fieldAccess.getFirstChild();
EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
Object fieldInfo =
typeSupport.getFieldInfo(classExpr.getTypeInfo(), field.getText());
return typeSupport.getElementType(fieldInfo);
}
/**
* Analyses a MEMBER OF operation.
* @param op the MEMBER OF operator
* @param value node representing the value to be tested
* @param col the collection
* @return the type info of the operator
*/
private Object analyseMemberExpr(EJBQLAST op, EJBQLAST value, EJBQLAST col)
{
Object valueTypeInfo = value.getTypeInfo();
Object elementTypeInfo = analyseCollectionValuedCMRField(col);
// handle error type
if (typeSupport.isErrorType(valueTypeInfo) ||
typeSupport.isErrorType(elementTypeInfo)) {
return typeSupport.errorType;
}
// check compatibility
if (typeSupport.isCompatibleWith(valueTypeInfo, elementTypeInfo) ||
typeSupport.isCompatibleWith(elementTypeInfo, valueTypeInfo)) {
return analyseParameterEjbName(value, (String)elementTypeInfo);
}
// if this code is reached there is a compatibility problem
// with the value and the collection expr
ErrorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(msgs, "EXC_CollectionElementTypeMismatch", //NOI18N
typeSupport.getTypeName(elementTypeInfo),
typeSupport.getTypeName(valueTypeInfo)));
return typeSupport.errorType;
}
/**
* Analyses the type of the element to be compatible with the type of the
* value expression in the sense that element type can be cast into value
* type without losing precision.
* For instance, element type can be a double and value type can be an
* integer.
* @param elementAST given element
* @param valueTypeInfo the type to be check for compatibility
* @return the type info of the elementAST or typeSupport.errorType
*/
private Object analyseInCollectionElement(EJBQLAST elementAST,
Object valueTypeInfo)
{
Object elementTypeInfo = elementAST.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(valueTypeInfo) ||
typeSupport.isErrorType(elementTypeInfo)) {
return typeSupport.errorType;
}
Object common = getCommonOperandType(elementTypeInfo, valueTypeInfo);
if (!typeSupport.isErrorType(common) &&
elementTypeInfo.equals(common)) {
return common;
}
// if this code is reached there is a compatibility problem
// with the value and the collection expr
ErrorMsg.error(elementAST.getLine(), elementAST.getColumn(),
I18NHelper.getMessage(msgs, "EXC_CollectionElementTypeMismatch", //NOI18N
typeSupport.getTypeName(valueTypeInfo),
typeSupport.getTypeName(elementTypeInfo)));
return typeSupport.errorType;
}
/**
* Analyses whether paramAST can be associated to a ejbName.
* @param paramAST AST node corresponds to a PARAMETER
* @param ejbName name to be check with paramAST
* @return the type info of typeSupport.booleanType or typeSupport.errorType
*/
private Object analyseParameterEjbName(EJBQLAST paramAST, String ejbName)
{
if (isInputParameter(paramAST)) {
String paramName = paramAST.getText();
String paramEjbName = paramSupport.getParameterEjbName(paramName);
if (paramEjbName != null && !paramEjbName.equals(ejbName)) {
ErrorMsg.error(paramAST.getLine(), paramAST.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_MultipleEJBNameParameter", // NOI18N
paramName, ejbName, paramEjbName));
return typeSupport.errorType;
} else {
paramSupport.setParameterEjbName(paramName, ejbName);
}
}
return typeSupport.booleanType;
}
/**
* Returns <code>true</code> if ast denotes a entity bena value.
*/
private boolean isEntityBeanValue(EJBQLAST ast)
{
switch(ast.getType()) {
case SINGLE_CMR_FIELD_ACCESS:
case IDENTIFICATION_VAR:
return true;
case INPUT_PARAMETER:
Object typeInfo = ast.getTypeInfo();
return typeSupport.isEjbOrInterfaceName(typeInfo);
}
return false;
}
/**
* Returns <code>true</code> if ast denotes a literal.
*/
private boolean isLiteral(EJBQLAST ast)
{
int tokenType = ast.getType();
return ((tokenType == INT_LITERAL) ||
(tokenType == LONG_LITERAL) ||
(tokenType == STRING_LITERAL) ||
(tokenType == FLOAT_LITERAL) ||
(tokenType == DOUBLE_LITERAL) ||
(tokenType == TRUE) ||
(tokenType == FALSE));
}
/**
* Returns <code>true</code> if ast denotes a input parameter access.
*/
private boolean isInputParameter(EJBQLAST ast)
{
return ast.getType() == INPUT_PARAMETER;
}
/**
* The method checks the specified node being an expression of type String.
* @param expr the expression to be checked
* @return <code>true</code> if the specified expression has the type String.
*/
private boolean isStringExpr(EJBQLAST expr)
{
Object exprType = expr.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(exprType))
return true;
// expr must have the type String
if (!typeSupport.isStringType(exprType)) {
ErrorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(msgs, "EXC_StringExprExpected", //NOI18N
typeSupport.getTypeName(exprType)));
return false;
}
// everything is ok => return true;
return true;
}
/**
* The method checks the specified node being an expression of
* type int or java.lang.Integer.
* @param expr the expression to be checked
* @return <code>true</code> if the specified expression has the type
* int or java.lang.Integer.
*/
private boolean isIntExpr(EJBQLAST expr)
{
Object exprType = expr.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(exprType))
return true;
// expr must have the type int or Integer
if (!typeSupport.isIntType(exprType)) {
ErrorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(msgs, "EXC_IntExprExpected", //NOI18N
typeSupport.getTypeName(exprType)));
return false;
}
// everything is ok => return true;
return true;
}
/**
* The method checks the specified node being an expression of
* type double or java.lang.Double.
* @param expr the expression to be checked
* @return <code>true</code> if the specified expression has the type
* double or java.lang.Double.
*/
private boolean isDoubleExpr(EJBQLAST expr)
{
Object exprType = expr.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(exprType))
return true;
// expr must have the type double or Double
if (!typeSupport.isDoubleType(exprType)) {
ErrorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(msgs, "EXC_DoubleExprExpected", //NOI18N
typeSupport.getTypeName(exprType)));
return false;
}
// everything is ok => return true;
return true;
}
/**
* The method checks the specified node being an expression of a number type
* (a numeric type or a number wrapper class).
* @param expr the expression to be checked
* @return <code>true</code> if the specified expression has a number type.
*/
private boolean isNumberExpr(EJBQLAST expr)
{
Object exprType = expr.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(exprType))
return true;
// expr must have a number type
if (!typeSupport.isNumberType(exprType)) {
ErrorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(msgs, "EXC_NumberExprExpected", //NOI18N
typeSupport.getTypeName(exprType)));
return false;
}
// everything is ok => return true;
return true;
}
/**
* The method checks the specified node being an expression of a number type
* (a numeric type or a number wrapper class).
* @param expr the expression to be checked
* @return <code>true</code> if the specified expression has a number or
* String type
*/
private boolean isNumberOrStringExpr(EJBQLAST expr)
{
Object exprType = expr.getTypeInfo();
// handle error type
if (typeSupport.isErrorType(exprType))
return true;
// expr must have a number type
if (!typeSupport.isNumberType(exprType) &&
!typeSupport.isStringType(exprType)) {
ErrorMsg.error(expr.getLine(), expr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_NumberOrStringExprExpected", //NOI18N
typeSupport.getTypeName(exprType)));
return false;
}
// everything is ok => return true;
return true;
}
/**
* The method checks whether the specified node denotes a valid abstract
* schema type.
* @param ident the node to be checked
* @return the type info for the abstract bean class of the specified
* abstract schema type.
*/
private Object checkAbstractSchemaType(EJBQLAST ident)
{
String name = ident.getText();
Object typeInfo =
typeSupport.getTypeInfoForAbstractSchema(name);
if (typeInfo == null) {
ErrorMsg.error(ident.getLine(), ident.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_AbstractSchemNameExpected", name)); //NOI18N
typeInfo = typeSupport.errorType;
}
return typeInfo;
}
/**
* Returns true if the specified text is a string literal consisting of a
* single char. Escaped chars are counted as a single char such as \ uxxxx.
*/
private boolean isSingleCharacterStringLiteral(String text)
{
int i = 0;
int length = text.length();
if (length == 0) {
// empty string
return false;
}
if (text.charAt(i) == '\\')
{
i++;
if (i == length) {
// string literal was '\'
return true;
}
// escaped char => check the next char
if (text.charAt(i) == 'u') {
// unicode
i +=5;
}
else if (('0' <= text.charAt(i)) && (text.charAt(i) <= '3')) {
i++;
if ((i < length) && isOctalDigit(text.charAt(i))) {
i++;
if ((i < length) && isOctalDigit(text.charAt(i))) {
i++;
}
}
}
else if (isOctalDigit(text.charAt(i))) {
i++;
if ((i < length) && isOctalDigit(text.charAt(i))) {
i++;
}
}
else {
i++;
}
}
else if (text.charAt(i) == '\''){
// check special EJBQL single quote char
i++;
if ((i < length) && (text.charAt(i) == '\'')) {
i++;
}
}
else {
i++;
}
// reached end of text?
return (i == length);
}
/** Returns true if the specified char is an octal digit */
private boolean isOctalDigit(char c)
{
return ('0' <= c && c <= '7');
}
}
// rules
query
: #(QUERY fromClause s:selectClause whereClause o:orderbyClause)
{
checkSelectOrderbyClause(#s, #o);
}
;
// ----------------------------------
// rules: from clause
// ----------------------------------
fromClause
: #( FROM ( identificationVarDecl )+ )
;
identificationVarDecl
: collectionMemberDecl
| rangeVarDecl
;
collectionMemberDecl
: #(IN p:collectionValuedPathExpression var:IDENT)
{
Object typeInfo = analyseCollectionValuedCMRField(#p);
String name = #var.getText();
Object identVar = new IdentificationVariable(name, typeInfo);
if (symtab.declare(name, identVar) != null) {
ErrorMsg.error(#var.getLine(), #var.getColumn(),
I18NHelper.getMessage(msgs, "EXC_MultipleDeclaration", name)); //NOI18N
}
#var.setType(IDENTIFICATION_VAR_DECL);
#var.setTypeInfo(typeInfo);
}
;
rangeVarDecl
: #(RANGE abstractSchemaName:ABSTRACT_SCHEMA_NAME var:IDENT)
{
// check abstract schema name
Object typeInfo =
checkAbstractSchemaType(#abstractSchemaName);
#abstractSchemaName.setTypeInfo(typeInfo);
// check identification variable
String name = #var.getText();
Object identVar = new IdentificationVariable(name, typeInfo);
if (symtab.declare(name, identVar) != null) {
ErrorMsg.error(#var.getLine(), #var.getColumn(),
I18NHelper.getMessage(msgs, "EXC_MultipleDeclaration", name)); //NOI18N
}
#var.setType(IDENTIFICATION_VAR_DECL);
#var.setTypeInfo(typeInfo);
}
;
// ----------------------------------
// rules: select clause
// ----------------------------------
selectClause
: #( SELECT distinct p:projection )
{
Object selectClauseTypeInfo = #p.getTypeInfo();
Class returnType = method.getReturnType();
if (finderNotSelector) {
checkFinderReturnType(returnType, selectClauseTypeInfo);
checkFinderResultTypeMapping();
}
else {
checkSelectorReturnType(returnType, selectClauseTypeInfo);
checkSelectorResultTypeMapping(returnType,
selectClauseTypeInfo);
}
}
;
distinct
: DISTINCT
| // empty rule
{
// Insert DISTINCT keyword, in the case of a multi-object selector
// having java.util.Set as return type
if (!finderNotSelector &&
(method.getReturnType() == java.util.Set.class)) {
#distinct = #[DISTINCT,"distinct"];
}
}
;
projection
: singleValuedPathExpression
| #( o:OBJECT var:IDENT )
{
String name = #var.getText();
Object decl = symtab.getDeclaration(name);
Object typeInfo = null;
if ((decl != null) &&
(decl instanceof IdentificationVariable)) {
#var.setType(IDENTIFICATION_VAR);
typeInfo = ((IdentificationVariable)decl).getTypeInfo();
}
else {
ErrorMsg.error(#var.getLine(), #var.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_IdentificationVariableExcepted", name)); //NOI18N
}
#var.setTypeInfo(typeInfo);
#o.setTypeInfo(typeInfo);
}
| #( sum:SUM ( DISTINCT )? sumExpr:cmpPathExpression )
{
// check numeric type
Object typeInfo = #sumExpr.getTypeInfo();
if (!typeSupport.isNumberType(typeInfo) ||
typeSupport.isCharType(typeInfo)) {
ErrorMsg.error(#sumExpr.getLine(), #sumExpr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_NumberExprExpected", //NO18N
typeSupport.getTypeName(typeInfo)));
}
#sum.setTypeInfo(typeSupport.getSumReturnType(typeInfo));
isAggregate = true;
}
| #( avg:AVG ( DISTINCT )? avgExpr:cmpPathExpression )
{
// check numeric type
Object typeInfo = #avgExpr.getTypeInfo();
if (!typeSupport.isNumberType(typeInfo) ||
typeSupport.isCharType(typeInfo)) {
ErrorMsg.error(#avgExpr.getLine(), #avgExpr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_NumberExprExpected", //NO18N
typeSupport.getTypeName(typeInfo)));
}
#avg.setTypeInfo(typeSupport.getAvgReturnType(typeInfo));
isAggregate = true;
}
| #( min:MIN ( DISTINCT )? minExpr:cmpPathExpression )
{
// check orderable type
Object typeInfo = #minExpr.getTypeInfo();
if (!typeSupport.isOrderableType(typeInfo)) {
ErrorMsg.error(#minExpr.getLine(), #minExpr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_OrderableExpected", //NO18N
typeSupport.getTypeName(typeInfo)));
}
#min.setTypeInfo(typeSupport.getMinMaxReturnType(typeInfo));
isAggregate = true;
}
| #( max:MAX ( DISTINCT )? maxExpr:cmpPathExpression )
{
// check orderable type
Object typeInfo = #maxExpr.getTypeInfo();
if (!typeSupport.isOrderableType(typeInfo)) {
ErrorMsg.error(#maxExpr.getLine(), #maxExpr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_OrderableExpected", //NO18N
typeSupport.getTypeName(typeInfo)));
}
#max.setTypeInfo(typeSupport.getMinMaxReturnType(typeInfo));
isAggregate = true;
}
| #( c:COUNT ( DISTINCT )? countExpr )
{
#c.setTypeInfo(typeSupport.longClassType);
isAggregate = true;
}
;
countExpr
: v:IDENT
{
String name = #v.getText();
Object decl = symtab.getDeclaration(name);
Object typeInfo = null;
if ((decl != null) &&
(decl instanceof IdentificationVariable)) {
#v.setType(IDENTIFICATION_VAR);
typeInfo = ((IdentificationVariable)decl).getTypeInfo();
}
else {
ErrorMsg.error(#v.getLine(), #v.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_IdentificationVariableExcepted", name)); //NOI18N
}
#v.setTypeInfo(typeInfo);
}
| singleValuedPathExpression
;
// ----------------------------------
// rules: where clause
// ----------------------------------
whereClause
: #( WHERE e:expression )
{
Object typeInfo = #e.getTypeInfo();
if (!typeSupport.isBooleanType(typeInfo)) {
ErrorMsg.error(#e.getLine(), #e.getColumn(),
I18NHelper.getMessage(msgs, "EXC_BooleanWhereClauseExpected", //NOI18N
typeSupport.getTypeName(typeInfo)));
}
}
;
// ----------------------------------
// rules: order by clause
// ----------------------------------
orderbyClause
: #( ORDER ( orderbyItem )+ )
| // empty rule
;
orderbyItem
: expr:cmpPathExpression ( ASC | DESC )
{
// check orderable type
Object typeInfo = #expr.getTypeInfo();
if (!typeSupport.isOrderableType(typeInfo)) {
ErrorMsg.error(#expr.getLine(), #expr.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_OrderableOrderbyClauseExpected", //NO18N
typeSupport.getTypeName(typeInfo)));
}
}
;
// ----------------------------------
// rules: expression
// ----------------------------------
expression
: conditionalExpr
| relationalExpr
| binaryArithmeticExpr
| unaryExpr
| betweenExpr
| likeExpr
| inExpr
| nullComparisonExpr
| emptyCollectionComparisonExpr
| collectionMemberExpr
| function
| primary
;
conditionalExpr
: #( op1:AND left1:expression right1:expression )
{
#op1.setTypeInfo(analyseConditionalExpr(#op1, #left1, #right1));
}
| #( op2:OR left2:expression right2:expression )
{
#op2.setTypeInfo(analyseConditionalExpr(#op2, #left2, #right2));
}
;
relationalExpr
: #( op1:EQUAL left1:expression right1:expression )
{
#op1.setTypeInfo(analyseEqualityExpr(#op1, #left1, #right1));
}
| #( op2:NOT_EQUAL left2:expression right2:expression )
{
#op2.setTypeInfo(analyseEqualityExpr(#op2, #left2, #right2));
}
| #( op3:LT left3:expression right3:expression )
{
#op3.setTypeInfo(analyseRelationalExpr(#op3, #left3, #right3));
}
| #( op4:LE left4:expression right4:expression )
{
#op4.setTypeInfo(analyseRelationalExpr(#op4, #left4, #right4));
}
| #( op5:GT left5:expression right5:expression )
{
#op5.setTypeInfo(analyseRelationalExpr(#op5, #left5, #right5));
}
| #( op6:GE left6:expression right6:expression )
{
#op6.setTypeInfo(analyseRelationalExpr(#op6, #left6, #right6));
}
;
binaryArithmeticExpr
: #( op1:PLUS left1:expression right1:expression )
{
#op1.setTypeInfo(analyseBinaryArithmeticExpr(#op1, #left1, #right1));
}
| #( op2:MINUS left2:expression right2:expression )
{
#op2.setTypeInfo(analyseBinaryArithmeticExpr(#op2, #left2, #right2));
}
| #( op3:STAR left3:expression right3:expression )
{
#op3.setTypeInfo(analyseBinaryArithmeticExpr(#op3, #left3, #right3));
}
| #( op4:DIV left4:expression right4:expression )
{
#op4.setTypeInfo(analyseBinaryArithmeticExpr(#op4, #left4, #right4));
}
;
unaryExpr
: #( op1:UNARY_PLUS arg1:expression )
{
#op1.setTypeInfo(analyseUnaryArithmeticExpr(#op1, #arg1));
}
| #( op2:UNARY_MINUS arg2:expression )
{
#op2.setTypeInfo(analyseUnaryArithmeticExpr(#op2, #arg2));
}
| #( op3:NOT arg3:expression )
{
Object typeInfo = typeSupport.errorType;
Object arg = #arg3.getTypeInfo();
if (typeSupport.isErrorType(arg))
typeInfo = typeSupport.errorType;
else if (typeSupport.isBooleanType(arg))
typeInfo = arg;
else {
ErrorMsg.error(#op3.getLine(), #op3.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidArguments", //NOI18N
#op3.getText()));
}
#op3.setTypeInfo(typeInfo);
}
;
betweenExpr
: #( op1:BETWEEN expr1:expression lower1:expression upper1:expression )
{
#op1.setTypeInfo((isNumberExpr(#expr1) && isNumberExpr(#lower1) && isNumberExpr(#upper1)) ?
typeSupport.booleanType : typeSupport.errorType);
}
| #( op2:NOT_BETWEEN expr2:expression lower2:expression upper2:expression )
{
#op2.setTypeInfo((isNumberExpr(#expr2) && isNumberExpr(#lower2) && isNumberExpr(#upper2)) ?
typeSupport.booleanType : typeSupport.errorType);
}
;
likeExpr
: #( op1:LIKE expr1:cmpPathExpression pattern escape )
{
#op1.setTypeInfo(isStringExpr(#expr1) ?
typeSupport.booleanType : typeSupport.errorType);
}
| #( op2:NOT_LIKE expr2:cmpPathExpression pattern escape )
{
#op2.setTypeInfo(isStringExpr(#expr2) ?
typeSupport.booleanType : typeSupport.errorType);
}
;
pattern
: STRING_LITERAL
| p:inputParameter
{
if (!typeSupport.isStringType(#p.getTypeInfo())) {
ErrorMsg.error(#p.getLine(), #p.getColumn(),
I18NHelper.getMessage(msgs, "EXC_InvalidPatternDefinition",
#p.getText())); //NOI18N
}
}
;
escape
: #( ESCAPE escapeCharacter )
| // empty rule
;
escapeCharacter
: s:STRING_LITERAL
{
String literal = #s.getText();
// String must be single charater string literal =>
// either '<char>' or ''''
if (!isSingleCharacterStringLiteral(#s.getText())) {
ErrorMsg.error(#s.getLine(), #s.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_InvalidEscapeDefinition", #s.getText())); //NOI18N
}
}
| p:inputParameter
{
Object paramType = #p.getTypeInfo();
if (!typeSupport.isCharType(paramType)) {
ErrorMsg.error(#p.getLine(), #p.getColumn(),
I18NHelper.getMessage(msgs,
"EXC_InvalidEscapeParameterDefinition", #p.getText())); //NOI18N
}
}
;
inExpr
: #( op1:IN expr1:cmpPathExpression inCollection[#expr1.getTypeInfo()] )
{
#op1.setTypeInfo(isNumberOrStringExpr(#expr1) ?
typeSupport.booleanType : typeSupport.errorType);
}
| #( op2:NOT_IN expr2:cmpPathExpression inCollection[#expr2.getTypeInfo()] )
{
#op2.setTypeInfo(isNumberOrStringExpr(#expr2) ?
typeSupport.booleanType : typeSupport.errorType);
}
;
nullComparisonExpr
: #( op1:NULL ( singleValuedPathExpression | inputParameter ) )
{
#op1.setTypeInfo(typeSupport.booleanType);
}
| #( op2:NOT_NULL ( singleValuedPathExpression | inputParameter ) )
{
#op2.setTypeInfo(typeSupport.booleanType);
}
;
emptyCollectionComparisonExpr
{
Object elementTypeInfo = null;
}
: #( op1:EMPTY col1:collectionValuedPathExpression )
{
elementTypeInfo = analyseCollectionValuedCMRField(#col1);
#op1.setTypeInfo(typeSupport.isErrorType(elementTypeInfo) ?
typeSupport.errorType : typeSupport.booleanType );
}
| #( op2:NOT_EMPTY col2:collectionValuedPathExpression )
{
elementTypeInfo = analyseCollectionValuedCMRField(#col2);
#op2.setTypeInfo(typeSupport.isErrorType(elementTypeInfo) ?
typeSupport.errorType : typeSupport.booleanType );
}
;
collectionMemberExpr
: #( op1:MEMBER value1:member col1:collectionValuedPathExpression )
{
#op1.setTypeInfo(analyseMemberExpr(#op1, #value1, #col1));
}
| #( op2:NOT_MEMBER value2:member col2:collectionValuedPathExpression )
{
#op2.setTypeInfo(analyseMemberExpr(#op2, #value2, #col2));
}
;
member
: identificationVariable
| inputParameter
| singleValuedCmrPathExpression
;
function
: concat
| substring
| length
| locate
| abs
| sqrt
| mod
;
concat
: #( op:CONCAT arg1:expression arg2:expression )
{
#op.setTypeInfo((isStringExpr(#arg1) && isStringExpr(#arg2)) ?
typeSupport.stringType : typeSupport.errorType);
}
;
substring
: #( op:SUBSTRING arg1:expression arg2:expression arg3:expression )
{
#op.setTypeInfo((isStringExpr(#arg1) && isIntExpr(#arg2) && isIntExpr(#arg3)) ?
typeSupport.stringType : typeSupport.errorType);
}
;
length
: #( op:LENGTH arg:expression )
{
#op.setTypeInfo(isStringExpr(#arg) ?
typeSupport.intType : typeSupport.errorType);
}
;
locate
: #( op:LOCATE arg1:expression arg2:expression ( arg3:expression )? )
{
#op.setTypeInfo((isStringExpr(#arg1) && isStringExpr(#arg2) &&
((#arg3 == null) || isIntExpr(#arg3))) ?
typeSupport.intType : typeSupport.errorType);
}
;
abs
: #( op:ABS expr:expression )
{
#op.setTypeInfo(isNumberExpr(#expr) ?
#expr.getTypeInfo() : typeSupport.errorType);
}
;
sqrt
: #( op:SQRT expr:expression )
{
#op.setTypeInfo(isDoubleExpr(#expr) ?
#expr.getTypeInfo() : typeSupport.errorType);
}
;
mod
: #( op:MOD arg1:expression arg2:expression )
{
#op.setTypeInfo((isIntExpr(#arg1) && isIntExpr(#arg2)) ?
typeSupport.intType : typeSupport.errorType);
}
;
primary
: literal
| singleValuedPathExpression
| identificationVariable
| inputParameter
;
literal
: b1:TRUE { #b1.setTypeInfo(typeSupport.booleanType); }
| b2:FALSE { #b2.setTypeInfo(typeSupport.booleanType); }
| s:STRING_LITERAL { #s.setTypeInfo(typeSupport.stringType); }
| i:INT_LITERAL { #i.setTypeInfo(typeSupport.intType); }
| l:LONG_LITERAL { #l.setTypeInfo(typeSupport.longType); }
| f:FLOAT_LITERAL { #f.setTypeInfo(typeSupport.floatType); }
| d:DOUBLE_LITERAL { #d.setTypeInfo(typeSupport.doubleType); }
;
pathExpression
: #( dot:DOT o:objectDenoter i:IDENT )
{
String fieldName = #i.getText();
Object typeInfo = #o.getTypeInfo();
Object fieldTypeInfo =
typeSupport.getFieldType(typeInfo, fieldName);
if (fieldTypeInfo == null) {
// field is not known
ErrorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(msgs, "EXC_UnknownField", fieldName, //NOI18N
typeSupport.getAbstractSchemaForTypeInfo(typeInfo)));
fieldTypeInfo = typeSupport.errorType;
}
else {
Object fieldInfo = typeSupport.getFieldInfo(typeInfo, fieldName);
if (fieldInfo == null) {
ErrorMsg.fatal(I18NHelper.getMessage(msgs,
"ERR_MissingFieldInfo", //NOI18N
fieldName, typeSupport.getTypeName(typeInfo)));
}
if (!typeSupport.isRelationship(fieldInfo)) {
// field is not a relationship => cmp field
#i.setType(CMP_FIELD);
#dot.setType(CMP_FIELD_ACCESS);
}
else if (typeSupport.isCollectionType(fieldTypeInfo)) {
// field is a relationship of a collection type =>
// collection valued cmr field
#i.setType(COLLECTION_CMR_FIELD);
#dot.setType(COLLECTION_CMR_FIELD_ACCESS);
}
else {
// field is a relationship of a non collection type =>
// single valued cmr field
#i.setType(SINGLE_CMR_FIELD);
#dot.setType(SINGLE_CMR_FIELD_ACCESS);
}
}
#dot.setTypeInfo(fieldTypeInfo);
#i.setTypeInfo(fieldTypeInfo);
}
;
objectDenoter
: identificationVariable
| singleValuedCmrPathExpression
;
identificationVariable
: i:IDENT
{
String name = #i.getText();
Object decl = symtab.getDeclaration(name);
// check for identification variables
if ((decl != null) && (decl instanceof IdentificationVariable)) {
#i.setType(IDENTIFICATION_VAR);
#i.setTypeInfo(((IdentificationVariable)decl).getTypeInfo());
}
else {
#i.setTypeInfo(typeSupport.errorType);
ErrorMsg.error(#i.getLine(), #i.getColumn(),
I18NHelper.getMessage(msgs, "EXC_UndefinedIdentifier", name)); //NOI18N
}
}
;
singleValuedPathExpression
: p:pathExpression
{
int fieldTokenType = #p.getType();
if ((fieldTokenType != SINGLE_CMR_FIELD_ACCESS) &&
(fieldTokenType != CMP_FIELD_ACCESS)) {
EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
ErrorMsg.error(field.getLine(), field.getColumn(),
I18NHelper.getMessage(msgs, "EXC_SingleValuedCMROrCMPFieldExpected", //NOI18N
field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
#p.setType(SINGLE_CMR_FIELD_ACCESS);
}
}
;
cmpPathExpression
: p:pathExpression
{
int fieldTokenType = #p.getType();
if ((fieldTokenType != CMP_FIELD_ACCESS)) {
EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
ErrorMsg.error(field.getLine(), field.getColumn(),
I18NHelper.getMessage(msgs, "EXC_CMPFieldExpected", //NOI18N
field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
#p.setType(CMP_FIELD_ACCESS);
}
}
;
singleValuedCmrPathExpression
: p:pathExpression
{
int fieldTokenType = #p.getType();
if (fieldTokenType != SINGLE_CMR_FIELD_ACCESS) {
EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
ErrorMsg.error(field.getLine(), field.getColumn(),
I18NHelper.getMessage(msgs, "EXC_SingleValuedCMRFieldExpected", //NOI18N
field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
#p.setType(COLLECTION_CMR_FIELD_ACCESS);
}
}
;
collectionValuedPathExpression
: p:pathExpression
{
int fieldTokenType = #p.getType();
if (fieldTokenType != COLLECTION_CMR_FIELD_ACCESS) {
EJBQLAST classExpr = (EJBQLAST)#p.getFirstChild();
EJBQLAST field = (EJBQLAST)classExpr.getNextSibling();
ErrorMsg.error(field.getLine(), field.getColumn(),
I18NHelper.getMessage(msgs, "EXC_CollectionValuedCMRFieldExpected", //NOI18N
field.getText(), typeSupport.getTypeName(field.getTypeInfo())));
#p.setType(COLLECTION_CMR_FIELD_ACCESS);
}
}
;
inCollection [Object valueExprTypeInfo]
: ( inCollectionElement[valueExprTypeInfo] )+
;
inCollectionElement [Object valueExprTypeInfo]
: l:literal
{
l.setTypeInfo(analyseInCollectionElement(#l, valueExprTypeInfo));
}
| i:inputParameter
{
i.setTypeInfo(analyseInCollectionElement(#i, valueExprTypeInfo));
}
;
inputParameter
: param:INPUT_PARAMETER
{
Object typeInfo = typeSupport.getTypeInfo(
paramSupport.getParameterType(#param.getText()));
#param.setTypeInfo(typeInfo);
}
;