| /******************************************************************************* |
| * Copyright (c) 2006 Oracle Corporation. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Cameron Bateman/Oracle - initial API and implementation |
| * |
| ********************************************************************************/ |
| |
| package org.eclipse.jst.jsf.validation.internal.el; |
| |
| import java.io.IOException; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.emf.common.util.Diagnostic; |
| import org.eclipse.jst.jsf.common.internal.types.BooleanLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.FloatLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.IntegerLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.LiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.MethodType; |
| import org.eclipse.jst.jsf.common.internal.types.NullLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.SignatureBasedType; |
| import org.eclipse.jst.jsf.common.internal.types.StringLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.ValueType; |
| import org.eclipse.jst.jsf.context.resolver.structureddocument.IStructuredDocumentContextResolverFactory; |
| import org.eclipse.jst.jsf.context.resolver.structureddocument.IWorkspaceContextResolver; |
| import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext; |
| import org.eclipse.jst.jsf.context.symbol.IInstanceSymbol; |
| import org.eclipse.jst.jsf.context.symbol.IPropertySymbol; |
| import org.eclipse.jst.jsf.context.symbol.ISymbol; |
| import org.eclipse.jst.jsf.context.symbol.internal.util.IObjectSymbolBasedValueType; |
| import org.eclipse.jst.jsf.core.internal.JSFCorePlugin; |
| import org.eclipse.jst.jsf.designtime.resolver.IStructuredDocumentSymbolResolverFactory; |
| import org.eclipse.jst.jsf.designtime.resolver.ISymbolContextResolver; |
| import org.eclipse.jst.jsf.validation.internal.IJSFViewValidator.IValidationReporter; |
| import org.eclipse.jst.jsf.validation.internal.el.diagnostics.DiagnosticFactory; |
| import org.eclipse.jst.jsf.validation.internal.el.operators.BinaryOperator; |
| import org.eclipse.jst.jsf.validation.internal.el.operators.BracketOperator; |
| import org.eclipse.jst.jsf.validation.internal.el.operators.DotOperator; |
| import org.eclipse.jst.jsf.validation.internal.el.operators.TernaryChoiceOperator; |
| import org.eclipse.jst.jsf.validation.internal.el.operators.UnaryOperator; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAddExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTAndExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTChoiceExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTEqualityExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTFunctionInvocation; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTLiteral; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTMultiplyExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTOperatorExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTOrExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTRelationalExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTUnaryExpression; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValue; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValuePrefix; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ASTValueSuffix; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParser; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserConstants; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.JSPELParserVisitor; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.ParseException; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.SimpleNode; |
| import org.eclipse.jst.jsp.core.internal.java.jspel.Token; |
| |
| |
| class ASTSemanticValidator implements JSPELParserVisitor, IExpressionSemanticValidator |
| { |
| private final IFile _targetFile; |
| private final ASTExpression _expr; |
| private final IStructuredDocumentContext _context; |
| private final ISymbolContextResolver _symbolResolver; |
| // private final List<IMessage> _messages; |
| private final EvaluationTracker _tracker; |
| private final DiagnosticFactory _diagnosticFactory; |
| private final IValidationReporter _reporter; |
| |
| ASTSemanticValidator(final ASTExpression expr, |
| final IStructuredDocumentContext context, |
| final IStructuredDocumentSymbolResolverFactory symbolResolverFactory, |
| final IValidationReporter reporter) |
| { |
| final IWorkspaceContextResolver resolver = IStructuredDocumentContextResolverFactory.INSTANCE |
| .getWorkspaceContextResolver(context); |
| |
| if (resolver != null) |
| { |
| _targetFile = (IFile) resolver.getResource(); |
| } |
| else |
| { |
| _targetFile = null; |
| } |
| |
| _expr = expr; |
| _context = context; |
| _symbolResolver = symbolResolverFactory.getSymbolContextResolver(context); |
| _tracker = new EvaluationTracker(); |
| _diagnosticFactory = new DiagnosticFactory(); |
| _reporter = reporter; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsf.validation.internal.el.IExpressionSemanticValidator#validate() |
| */ |
| public void validate() |
| { |
| _expr.jjtAccept(this, _tracker); |
| } |
| |
| public Object visit(final ASTAddExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTAndExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTChoiceExpression node, final Object data) |
| { |
| if (node.jjtGetNumChildren() != 3) |
| { |
| throw new AssertionError("Binary operators should always have two sub-expressions"); //$NON-NLS-1$ |
| } |
| |
| // evaluate choice argument |
| node.jjtGetChild(0).jjtAccept(this, data); |
| final ValueType choiceArg = ((EvaluationTracker)data).getValueType(); |
| // evaluate when true argument |
| node.jjtGetChild(1).jjtAccept(this, data); |
| final ValueType whenTrueArg = ((EvaluationTracker)data).getValueType(); |
| //evaluate when false argument |
| node.jjtGetChild(2).jjtAccept(this, data); |
| final ValueType whenFalseArg = ((EvaluationTracker)data).getValueType(); |
| |
| |
| if (choiceArg != null && whenTrueArg != null && whenFalseArg != null) |
| { |
| final TernaryChoiceOperator operator = |
| new TernaryChoiceOperator(_diagnosticFactory); |
| |
| final Diagnostic diagnostic = |
| operator.validate(choiceArg/* whenTrueArg, whenFalseArg*/); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| final Token firstToken = node.getFirstToken(); |
| final int offset = _context.getDocumentPosition() + firstToken.beginColumn - 1; |
| final int length = node.getLastToken().endColumn - firstToken.beginColumn+1; |
| _reporter.report(diagnostic, offset, length); |
| } |
| |
| ((EvaluationTracker)data).setType(operator.perform(choiceArg, whenTrueArg, whenFalseArg)); |
| } |
| else |
| { |
| ((EvaluationTracker)data).setType(null); |
| } |
| |
| return data; |
| } |
| |
| public Object visit(final ASTEqualityExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTExpression node, final Object data) { |
| return node.childrenAccept(this, data); |
| } |
| |
| public Object visit(final ASTFunctionInvocation node, final Object data) |
| { |
| // when we see a function invocation, null the type |
| // we do not validate function invocations currently |
| final Object retVal = node.childrenAccept(this, data); |
| ((EvaluationTracker)data).setType(null); |
| return retVal; |
| } |
| |
| public Object visit(final ASTLiteral node, final Object data) |
| { |
| // note, there is an implicit assumption here that literals |
| // are all terminals (leafs in the tree) |
| if (node.jjtGetNumChildren() > 0) |
| { |
| throw new AssertionError("Literals should be terminal"); //$NON-NLS-1$ |
| } |
| |
| LiteralType type = null; |
| |
| final Token literalToken = node.getFirstToken(); |
| |
| switch (literalToken.kind) |
| { |
| case JSPELParserConstants.STRING_LITERAL: |
| type = new StringLiteralType(stripQuotes(literalToken.image)); |
| break; |
| |
| case JSPELParserConstants.INTEGER_LITERAL: |
| type = new IntegerLiteralType(Long.parseLong(literalToken.image)); |
| break; |
| |
| case JSPELParserConstants.FLOATING_POINT_LITERAL: |
| type = new FloatLiteralType(Double.parseDouble(literalToken.image)); |
| break; |
| |
| case JSPELParserConstants.FALSE: |
| type = BooleanLiteralType.FALSE; |
| break; |
| |
| case JSPELParserConstants.TRUE: |
| type = BooleanLiteralType.TRUE; |
| break; |
| |
| case JSPELParserConstants.NULL: |
| type = NullLiteralType.SINGLETON; |
| break; |
| |
| default: |
| JSFCorePlugin.log("Unknown EL literal: " +literalToken.toString(), new Throwable("This throwable simply used to mark a stack trace")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| ((EvaluationTracker)data).setType(type); |
| return data; |
| } |
| |
| private String stripQuotes(final String stringLiteral) |
| { |
| if (stringLiteral.startsWith("'") //$NON-NLS-1$ |
| || stringLiteral.startsWith("\"")) //$NON-NLS-1$ |
| |
| { |
| if (stringLiteral.length() > 2) |
| { |
| // take 'literal' -> literal |
| return stringLiteral.substring(1, stringLiteral.length()-1); |
| } |
| // if only two characters, then the empty string |
| return ""; //$NON-NLS-1$ |
| } |
| |
| return stringLiteral; |
| } |
| |
| public Object visit(final ASTMultiplyExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTOrExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTRelationalExpression node, final Object data) |
| { |
| performBinaryEvaluation(node, (EvaluationTracker)data); |
| return data; |
| } |
| |
| public Object visit(final ASTUnaryExpression node, final Object data) |
| { |
| // assertion here is that this expression decomposes: |
| // UnaryExpr -> Value |
| // UnaryExpr -> UnaryOp UnaryExpression |
| // since UnaryOp is a terminal (-,!,not,empty) node will |
| // always have exactly one child |
| node.childrenAccept(this, data); |
| final SignatureBasedType type = ((EvaluationTracker)data).getType(); |
| |
| if (type != null) |
| { |
| final Token firstToken = node.getFirstToken(); |
| if (UnaryOperator.isUnaryOperator(firstToken)) |
| { |
| if (type instanceof ValueType) |
| { |
| final UnaryOperator unaryOp = UnaryOperator.createUnaryOperator(firstToken, _diagnosticFactory); |
| final Diagnostic diagnostic = unaryOp.validate((ValueType)type); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| final int offset = _context.getDocumentPosition() + firstToken.beginColumn - 1; |
| final int length = node.getLastToken().endColumn - firstToken.beginColumn+1; |
| _reporter.report(diagnostic, offset, length); |
| } |
| |
| ((EvaluationTracker)data). |
| setType(unaryOp.performOperation ((ValueType)type)); |
| } |
| // cannot apply operations to method bindings |
| else |
| { |
| final int offset = _context.getDocumentPosition() + |
| firstToken.beginColumn - 1; |
| final int length = node.getLastToken().endColumn - |
| firstToken.beginColumn+1; |
| |
| Diagnostic diagnostic = |
| _diagnosticFactory.create_CANNOT_APPLY_OPERATOR_TO_METHOD_BINDING(); |
| |
| _reporter.report(diagnostic, offset, length); |
| } |
| } |
| } |
| |
| return data; |
| } |
| |
| public Object visit(final ASTValue node, final Object data) { |
| final ValueExpressionTracker tracker = new ValueExpressionTracker(); |
| |
| ((EvaluationTracker)data).setValueTracker(tracker); |
| |
| node.childrenAccept(this, data); |
| |
| final SignatureBasedType type = ((EvaluationTracker)data).getType(); |
| |
| // now check the tracker. If the last property in the expression |
| // is non-null (i.e. the value has one or more suffices) then we |
| // to very the leaf node (i.e. 'z' in #{x.y.z}) is more than just |
| // an intermediate value used to get to other properties |
| if (type instanceof IObjectSymbolBasedValueType |
| && ((IObjectSymbolBasedValueType)type).getSymbol() instanceof IPropertySymbol |
| && ((IPropertySymbol)((IObjectSymbolBasedValueType)type).getSymbol()).isIntermediate()) |
| { |
| final int offset = tracker.getCurPropertySymbolOffset(); |
| final int length = tracker.getCurPropertySymbolLength(); |
| final Diagnostic diagnostic = |
| _diagnosticFactory.create_MEMBER_IS_INTERMEDIATE( |
| ((IPropertySymbol)((IObjectSymbolBasedValueType)type).getSymbol()).getName()); |
| _reporter.report(diagnostic, offset, length); |
| } |
| |
| return data; |
| } |
| |
| public Object visit(final ASTValuePrefix node, final Object data) |
| { |
| if (node.jjtGetNumChildren() == 0) |
| { |
| final Token token = node.getFirstToken(); |
| final String image = token.image; |
| |
| final ISymbol symbol = _symbolResolver.getVariable(image); |
| |
| if (symbol == null) |
| { |
| final int offset = |
| _context.getDocumentPosition() + token.beginColumn - 1; |
| final int length = token.endColumn - token.beginColumn + 1; |
| |
| final Diagnostic diagnostic = |
| _diagnosticFactory.create_VARIABLE_NOT_FOUND(image); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| _reporter.report(diagnostic, offset, length); |
| } |
| } |
| else if (symbol instanceof IInstanceSymbol) |
| { |
| final IObjectSymbolBasedValueType symbolType = |
| IObjectSymbolBasedValueType.getInstance(symbol); |
| ((EvaluationTracker) data).setType(symbolType); |
| } |
| } |
| |
| return node.childrenAccept(this, data); |
| } |
| |
| public Object visit(final ASTValueSuffix node, final Object data) |
| { |
| final ValueExpressionTracker tracker = ((EvaluationTracker) data).getValueTracker(); |
| final SignatureBasedType type = ((EvaluationTracker) data).getType(); |
| |
| if (type instanceof IObjectSymbolBasedValueType) |
| { |
| final IObjectSymbolBasedValueType symbolType = |
| (IObjectSymbolBasedValueType) type; |
| final Token firstToken = node.getFirstToken(); |
| |
| if (node.jjtGetNumChildren() == 0 |
| && firstToken.kind == JSPELParserConstants.DOT) |
| { |
| final Token dotId = node.getLastToken(); |
| |
| final int offset = |
| _context.getDocumentPosition() + dotId.beginColumn - 1; |
| final int length = dotId.endColumn - dotId.beginColumn + 1; |
| |
| final DotOperator dotOp = new DotOperator(_diagnosticFactory, _targetFile, _symbolResolver); |
| |
| final StringLiteralType suffixLiteral = new StringLiteralType(dotId.image); |
| final Diagnostic diagnostic = |
| dotOp.validate(symbolType, |
| suffixLiteral); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| _reporter.report(diagnostic, offset, length); |
| ((EvaluationTracker) data).setType(null); |
| } |
| else |
| { |
| // // if the base (value-a) is a map, then using the bracket value-a['y'] type |
| // // syntax is recommended. Note that we do this here instead of |
| // // DotOperator so that we don't tie the default property resolver |
| // // behaviour to that operator class. If someone changes the rules |
| // // of how the prop resolver interprets the base, then they may want to |
| // // write their own validator that doesn't do this |
| // if (symbolType.getSymbol().supportsCoercion(TypeConstants.TYPE_MAP)) |
| // { |
| // _messages.add(ValidationMessageFactory.createFromDiagnostic( |
| // DiagnosticFactory.create_BINARY_OP_DOT_WITH_VALUEA_MAP_SHOULD_USE_ARRAY |
| // (symbolType.getSymbol().getName(), dotId.image), |
| // startOffset, length, _targetFile)); |
| // } |
| |
| ((EvaluationTracker) data).setType(dotOp.performOperation(symbolType, |
| suffixLiteral)); |
| tracker.setCurMemberSymbol(offset, length); |
| } |
| |
| // we finished with the single dot suffix here |
| return data; |
| } |
| else if (firstToken.kind == JSPELParserConstants.LBRACKET) |
| { |
| final EvaluationTracker subExprTracker = new EvaluationTracker(); |
| node.childrenAccept(this, subExprTracker); |
| |
| final SignatureBasedType subExprType = subExprTracker.getType(); |
| |
| if (subExprType instanceof ValueType) |
| { |
| final Token lastToken = node.getLastToken(); |
| final int offset = |
| _context.getDocumentPosition() + firstToken.beginColumn - 1; |
| final int length = lastToken.endColumn - firstToken.beginColumn + 1; |
| |
| final BracketOperator bracketOperator = new BracketOperator(_diagnosticFactory, _targetFile, _symbolResolver); |
| |
| final Diagnostic diagnostic = |
| bracketOperator.validate(symbolType, |
| (ValueType)subExprType); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| _reporter.report(diagnostic, offset, length); |
| ((EvaluationTracker) data).setType(null); |
| } |
| else |
| { |
| ((EvaluationTracker) data).setType(bracketOperator.performOperation(symbolType, |
| (ValueType)subExprType)); |
| tracker.setCurMemberSymbol(offset, length); |
| } |
| } |
| // we are finished with the bracketed suffix at this point. |
| return data; |
| } |
| } |
| |
| // don't bother to accept children, since if we haven't done |
| // something above, there's not much sensible we can do with it |
| // clear the type first though |
| ((EvaluationTracker) data).setType(null); |
| return data; //node.childrenAccept(this, data); |
| } |
| |
| public Object visit(final SimpleNode node, final Object data) { |
| return node.childrenAccept(this, data); |
| } |
| |
| /** |
| * Copies stored messages into the validation reporter |
| * @param validator |
| * @param reporter |
| */ |
| // public void reportFindings(final IValidator validator, final IReporter reporter) |
| // { |
| // for (final IMessage message : _messages) |
| // { |
| // // don't report messages that have no severity. |
| // if ((message.getSeverity() & IMessage.ALL_MESSAGES) != 0) |
| // { |
| // reporter.addMessage(validator, message); |
| // } |
| // } |
| // } |
| |
| private void performBinaryEvaluation(final ASTOperatorExpression node, final EvaluationTracker tracker) |
| { |
| if (node.jjtGetNumChildren() < 2) |
| { |
| throw new AssertionError("Binary operators should always have at least two sub-expressions"); //$NON-NLS-1$ |
| } |
| else if (node.getOperatorTokens().size() != node.jjtGetNumChildren()-1) |
| { |
| throw new AssertionError("Binary operators should always have one operator token less than number of sub-expressions"); //$NON-NLS-1$ |
| } |
| |
| // evaluate left-most argument |
| node.jjtGetChild(0).jjtAccept(this, tracker); |
| |
| ValueType curType = getValueTypeForBinaryOperation(tracker.getType(), (SimpleNode) node.jjtGetChild(0)); |
| |
| for (int child = 1; child < node.jjtGetNumChildren(); child++) |
| { |
| // evaluate next argument running left-to-right |
| node.jjtGetChild(child).jjtAccept(this, tracker); |
| final ValueType secondType = |
| getValueTypeForBinaryOperation(tracker.getType(), (SimpleNode) node.jjtGetChild(child)); |
| |
| if (curType != null && secondType != null) |
| { |
| final BinaryOperator operator = |
| BinaryOperator.getBinaryOperator((Token)node.getOperatorTokens().get(child-1), _diagnosticFactory, _context); |
| |
| final Diagnostic diagnostic = operator.validate(curType, secondType); |
| |
| if (diagnostic.getSeverity() != Diagnostic.OK) |
| { |
| final Token firstToken = node.getFirstToken(); |
| final int offset = _context.getDocumentPosition() + firstToken.beginColumn - 1; |
| final int length = node.getLastToken().endColumn - firstToken.beginColumn+1; |
| _reporter.report(diagnostic, offset, length); |
| } |
| |
| curType = operator.performOperation(curType, secondType); |
| } |
| } |
| |
| tracker.setType(curType); |
| } |
| |
| private ValueType getValueTypeForBinaryOperation(final SignatureBasedType type, final SimpleNode node) |
| { |
| if (type instanceof ValueType) |
| { |
| return (ValueType) type; |
| } |
| else if (type instanceof MethodType) |
| { |
| final int offset = _context.getDocumentPosition() + node.getFirstToken().beginColumn - 1; |
| final int length = node.getLastToken().endColumn - node.getFirstToken().beginColumn+1; |
| final Diagnostic diagnostic = _diagnosticFactory.create_CANNOT_APPLY_OPERATOR_TO_METHOD_BINDING(); |
| _reporter.report(diagnostic, offset, length); |
| } |
| |
| return null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsf.validation.internal.el.IExpressionSemanticValidator#getMessages() |
| */ |
| // public List getMessages() |
| // { |
| // if (!_validatorHasBeenCalled) |
| // { |
| // throw new AssertionError("Should not call getMessages before validate has been called"); |
| // } |
| // return _messages; |
| // } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsf.validation.internal.el.IExpressionSemanticValidator#getExpressionType() |
| */ |
| public SignatureBasedType getExpressionType() |
| { |
| return _tracker.getType(); |
| } |
| |
| /** |
| * @param args |
| * @throws IOException |
| * @throws ParseException |
| */ |
| public static void main(final String[] args) throws IOException, ParseException |
| { |
| String elText = ""; //$NON-NLS-1$ |
| int nextCharacter; |
| |
| while(((nextCharacter = System.in.read()) != -1)) |
| { |
| final char nextChar = (char) nextCharacter; |
| |
| if (nextChar == '\n') |
| { |
| final JSPELParser parser = JSPELParser.createParser(elText); |
| final ASTExpression expr = parser.Expression(); |
| expr.dump(""); //$NON-NLS-1$ |
| |
| elText = ""; //$NON-NLS-1$ |
| } |
| else |
| { |
| elText += nextChar; |
| } |
| } |
| } |
| } |