| /* |
| * 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 |
| ; |
| |