/******************************************************************************* * Copyright (c) 2012, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation * ******************************************************************************/ package org.eclipse.persistence.jpa.jpql.parser; import java.util.List; import org.eclipse.persistence.jpa.jpql.ExpressionTools; import org.eclipse.persistence.jpa.jpql.WordParser; import org.eclipse.persistence.jpa.jpql.parser.FunctionExpressionFactory.ParameterCount; /** * This expression adds support to call native database functions. * <p> * New to JPA 2.1. * * <div><b>BNF:</b> <code>func_expression ::= <identifier>('function_name' {, func_item}*)</code><p></div> * * @version 2.5 * @since 2.4 * @author James */ public final class FunctionExpression extends AbstractSingleEncapsulatedExpression { /** * The name of the SQL function. */ private String functionName; /** * Determines whether the comma separating the function name and the first expression. */ private boolean hasComma; /** * Determines whether a whitespace is following the comma. */ private boolean hasSpaceAfterComma; /** * The number of {@link ParameterCount parameters} a {@link FunctionExpression} can have. * * @since 2.4 */ private ParameterCount parameterCount; /** * The unique identifier of the {@link JPQLQueryBNF} that will be used to parse the arguments of * the function expression. * * @since 2.4 */ private String parameterQueryBNFId; /** * Creates a new <code>FuncExpression</code>. * * @param parent The parent of this expression * @param identifier The JPQL identifier */ public FunctionExpression(AbstractExpression parent, String identifier) { super(parent, identifier); } /** * Creates a new <code>FunctionExpression</code>. * * @param parent The parent of this expression * @param identifier The JPQL identifier * @param parameterCount The number of {@link ParameterCount parameters} a {@link * FunctionExpression} can have * @param parameterQueryBNFId The unique identifier of the {@link JPQLQueryBNF} that will be used * to parse the arguments of the function expression */ public FunctionExpression(AbstractExpression parent, String identifier, ParameterCount parameterCount, String parameterQueryBNFId) { this(parent, identifier); this.parameterCount = parameterCount; this.parameterQueryBNFId = parameterQueryBNFId; } /** * {@inheritDoc} */ public void accept(ExpressionVisitor visitor) { visitor.visit(this); } /** * {@inheritDoc} */ @Override protected void addOrderedEncapsulatedExpressionTo(List<Expression> children) { children.add(buildStringExpression(functionName)); if (hasComma) { children.add(buildStringExpression(COMMA)); } if (hasSpaceAfterComma) { children.add(buildStringExpression(SPACE)); } super.addOrderedEncapsulatedExpressionTo(children); } /** * {@inheritDoc} */ @Override public String getEncapsulatedExpressionQueryBNFId() { return parameterQueryBNFId; } /** * Returns the name of the SQL function. * * @return The name of the SQL function */ public String getFunctionName() { return functionName; } /** * Returns the number of parameters a {@link FunctionExpression} can have, which will be during * validation. * * @return The number of parameters (encapsulated expressions) allowed by this expression * @since 2.4 */ public ParameterCount getParameterCount() { return parameterCount; } /** * {@inheritDoc} */ public JPQLQueryBNF getQueryBNF() { return getQueryBNF(FunctionExpressionBNF.ID); } /** * Returns the name of the SQL function. * * @return The name of the SQL function */ public String getUnquotedFunctionName() { return ExpressionTools.unquote(functionName); } /** * Determines whether the comma was parsed after the function name. * * @return <code>true</code> if a comma was parsed after the function name and the first * expression; <code>false</code> otherwise */ public boolean hasComma() { return hasComma; } /** * {@inheritDoc} */ @Override public boolean hasEncapsulatedExpression() { return hasFunctionName() || hasComma || super.hasEncapsulatedExpression(); } /** * Determines whether the function name was parsed. * * @return <code>true</code> if the function name was parsed; <code>false</code> otherwise */ public boolean hasFunctionName() { return ExpressionTools.stringIsNotEmpty(functionName); } /** * Determines whether a whitespace was parsed after the comma. * * @return <code>true</code> if there was a whitespace after the comma; <code>false</code> otherwise */ public boolean hasSpaceAfterComma() { return hasSpaceAfterComma; } /** * {@inheritDoc} */ @Override protected void parseEncapsulatedExpression(WordParser wordParser, int whitespaceCount, boolean tolerant) { int count = 0; // Parse the function name outside of a CollectionExpression so we can retrieve it // with getFunctionName() if (wordParser.startsWith(SINGLE_QUOTE)) { functionName = wordParser.word(); wordParser.moveForward(functionName); count = wordParser.skipLeadingWhitespace(); } else { functionName = ExpressionTools.EMPTY_STRING; } hasComma = wordParser.startsWith(COMMA); if (hasComma) { wordParser.moveForward(1); count = wordParser.skipLeadingWhitespace(); hasSpaceAfterComma = count > 0; } // Parse the arguments if (!wordParser.isTail() && wordParser.character() != RIGHT_PARENTHESIS) { super.parseEncapsulatedExpression(wordParser, whitespaceCount, tolerant); } // A space was parsed but no expression, let the space belong to the parent if (count > 0 && !hasExpression() && !wordParser.isTail() && wordParser.character() != RIGHT_PARENTHESIS) { hasSpaceAfterComma = false; wordParser.moveBackward(count); } } /** * {@inheritDoc} */ @Override protected void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) { writer.append(functionName); if (hasComma) { writer.append(COMMA); } if (hasSpaceAfterComma) { writer.append(SPACE); } super.toParsedTextEncapsulatedExpression(writer, actual); } }