org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 2006, 2021 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,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
import org.eclipse.persistence.jpa.jpql.utility.iterable.SnapshotCloneListIterable;
/**
* An identification variable followed by the navigation operator (.) and a state field or
* association field is a path expression. The type of the path expression is the type computed as
* the result of navigation; that is, the type of the state field or association field to which the
* expression navigates.
*
* @see CollectionValuedPathExpression
* @see IdentificationVariable
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public abstract class AbstractPathExpression extends AbstractExpression {
/**
* Determines whether the path ends with a dot or not.
*/
private boolean endsWithDot;
/**
* The identification variable that starts the path expression, which can be a sample {@link
* IdentificationVariable identification variable}, an {@link EntryExpression entry expression},
* a {@link ValueExpression value expression} or a {@link KeyExpression key expression}.
*/
private AbstractExpression identificationVariable;
/**
* The state field path in a ordered list of string segments.
*/
private List paths;
/**
* The cached number of segments representing the path expression.
*/
private int pathSize;
/**
* Determines whether the path starts with a dot or not.
*/
private boolean startsWithDot;
/**
* Creates a new AbstractPathExpression
.
*
* @param parent The parent of this expression
* @param identificationVariable The identification variable that was already parsed, which means
* the beginning of the parsing should start with a dot
*/
protected AbstractPathExpression(AbstractExpression parent, AbstractExpression identificationVariable) {
super(parent);
this.pathSize = -1;
this.identificationVariable = identificationVariable;
this.identificationVariable.setParent(this);
}
/**
* Creates a new AbstractPathExpression
.
*
* @param parent The parent of this expression
* @param identificationVariable The identification variable that was already parsed, which means
* the beginning of the parsing should start with a dot
* @param paths The path expression that is following the identification variable
*/
public AbstractPathExpression(AbstractExpression parent,
AbstractExpression identificationVariable,
String paths) {
super(parent, paths);
this.pathSize = -1;
this.identificationVariable = identificationVariable;
this.identificationVariable.setParent(this);
}
/**
* Creates a new AbstractPathExpression
.
*
* @param parent The parent of this expression
* @param paths The path expression
*/
protected AbstractPathExpression(AbstractExpression parent, String paths) {
super(parent, paths);
this.pathSize = -1;
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getIdentificationVariable().accept(visitor);
}
@Override
protected void addChildrenTo(Collection children) {
checkPaths();
children.add(identificationVariable);
}
@Override
protected final void addOrderedChildrenTo(List children) {
checkPaths();
if (!hasVirtualIdentificationVariable()) {
children.add(identificationVariable);
}
children.add(buildStringExpression(getText()));
}
private void checkPaths() {
// Nothing to do
if (paths != null) {
return;
}
paths = new ArrayList<>();
String text = getText();
char character = '\0';
StringBuilder singlePath = new StringBuilder();
// Extract each path from the text
for (int index = 0, count = text.length(); index < count; index++) {
character = text.charAt(index);
// Make sure the identification variable is handled
// correctly if it was passed during instantiation
if (index == 0) {
// No identification variable was passed during instantiation
if (identificationVariable == null) {
// The path starts with '.'
startsWithDot = (character == DOT);
// Start appending to the current single path
if (!startsWithDot) {
singlePath.append(character);
}
}
// The identification variable was passed during instantiation,
// add its parsed text as a path, it's assume the character is a dot
else if (!identificationVariable.isNull() &&
!identificationVariable.isVirtual()) {
paths.add(identificationVariable.toParsedText());
}
// Start appending to the current single path
else {
singlePath.append(character);
}
}
else {
// Append the character and continue
if (character != DOT) {
singlePath.append(character);
}
// Scanning a '.'
else {
// Store the current single path
paths.add(singlePath.toString());
// Clean the buffer
singlePath.setLength(0);
}
}
}
// Check if the last character is a '.'
endsWithDot = (character == DOT);
// Make sure the last path is added to the list
if (singlePath.length() > 0) {
paths.add(singlePath.toString());
}
// Cache the size
pathSize = paths.size();
// The identification variable can never be null
if (identificationVariable == null) {
if (startsWithDot || !endsWithDot && (pathSize == 1)) {
identificationVariable = buildNullExpression();
}
else {
identificationVariable = new IdentificationVariable(this, paths.get(0));
}
}
}
/**
* Determines whether the path ends with a dot or not.
*
* @return true
if the path ends with a dot; false
otherwise
*/
public final boolean endsWithDot() {
checkPaths();
return endsWithDot;
}
@Override
public final JPQLQueryBNF findQueryBNF(Expression expression) {
if ((identificationVariable != null) && identificationVariable.isAncestor(expression)) {
return getQueryBNF(GeneralIdentificationVariableBNF.ID);
}
return super.findQueryBNF(expression);
}
/**
* Returns the identification variable that starts the path expression, which can be a sample
* identification variable, a map value, map key or map entry expression.
*
* @return The root of the path expression
*/
public final Expression getIdentificationVariable() {
checkPaths();
return identificationVariable;
}
/**
* Returns the specified segment of the state field path.
*
* @param index The 0-based segment index
* @return The specified segment
*/
public final String getPath(int index) {
checkPaths();
return paths.get(index);
}
/**
* Determines whether the identification variable was parsed.
*
* @return true
the identification variable was parsed; false
otherwise
*/
public final boolean hasIdentificationVariable() {
checkPaths();
return !identificationVariable.isNull() &&
!identificationVariable.isVirtual();
}
/**
* Determines whether the path's identification variable is virtual or not, meaning it's not part
* of the query but is required for proper navigability.
*
* @return true
if this identification variable was virtually created to fully
* qualify path expression; false
if it was parsed
*/
public final boolean hasVirtualIdentificationVariable() {
checkPaths();
return identificationVariable.isVirtual();
}
@Override
protected final void parse(WordParser wordParser, boolean tolerant) {
wordParser.moveForward(getText());
}
/**
* Returns the segments in the state field path in order.
*
* @return An Iterator
over the segments of the state field path
*/
public final ListIterable paths() {
checkPaths();
return new SnapshotCloneListIterable<>(paths);
}
/**
* Returns the number of segments in the state field path.
*
* @return The number of segments
*/
public final int pathSize() {
checkPaths();
return pathSize;
}
/**
* Sets a virtual identification variable because the abstract schema name was parsed without
* one. This is valid in an UPDATE and DELETE queries.
*
* @param variableName The identification variable that was generated to identify the "root" object
*/
protected final void setVirtualIdentificationVariable(String variableName) {
identificationVariable = new IdentificationVariable(this, variableName, true);
rebuildActualText();
rebuildParsedText();
}
/**
* Determines whether the path starts with a dot or not.
*
* @return true
if the path starts with a dot; false
otherwise
*/
public final boolean startsWithDot() {
return startsWithDot;
}
/**
* Returns a string representation from the given range.
*
* @param startIndex The beginning of the range to create the string representation
* @param stopIndex When to stop creating the string representation, which is exclusive
* @return The string representation of this path expression contained in the given range
* @since 2.4
*/
public String toParsedText(int startIndex, int stopIndex) {
checkPaths();
StringBuilder writer = new StringBuilder();
for (int index = startIndex; index < stopIndex; index++) {
writer.append(paths.get(index));
if (index < stopIndex - 1) {
writer.append(DOT);
}
}
return writer.toString();
}
@Override
protected final void toParsedText(StringBuilder writer, boolean actual) {
checkPaths();
if (startsWithDot) {
writer.append(DOT);
}
for (int index = 0, count = pathSize(); index < count; index++) {
if (index > 0) {
writer.append(DOT);
}
// Make sure to use the identification variable for proper formatting
if ((index == 0) && hasIdentificationVariable()) {
identificationVariable.toParsedText(writer, actual);
}
// Append a single path
else {
writer.append(paths.get(index));
}
}
if (endsWithDot) {
writer.append(DOT);
}
}
}