/******************************************************************************* * Copyright (c) 2006, 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.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<String> 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 <code>AbstractPathExpression</code>. * * @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 <code>AbstractPathExpression</code>. * * @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 <code>AbstractPathExpression</code>. * * @param parent The parent of this expression * @param paths The path expression */ protected AbstractPathExpression(AbstractExpression parent, String paths) { super(parent, paths); this.pathSize = -1; } /** * {@inheritDoc} */ public void acceptChildren(ExpressionVisitor visitor) { getIdentificationVariable().accept(visitor); } /** * {@inheritDoc} */ @Override protected void addChildrenTo(Collection<Expression> children) { checkPaths(); children.add(identificationVariable); } /** * {@inheritDoc} */ @Override protected final void addOrderedChildrenTo(List<Expression> 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>(); 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 <code>true</code> if the path ends with a dot; <code>false</code> otherwise */ public final boolean endsWithDot() { checkPaths(); return endsWithDot; } /** * {@inheritDoc} */ @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 <code>true</code> the identification variable was parsed; <code>false</code> 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 <code>true</code> if this identification variable was virtually created to fully * qualify path expression; <code>false</code> if it was parsed */ public final boolean hasVirtualIdentificationVariable() { checkPaths(); return identificationVariable.isVirtual(); } /** * {@inheritDoc} */ @Override protected final void parse(WordParser wordParser, boolean tolerant) { wordParser.moveForward(getText()); } /** * Returns the segments in the state field path in order. * * @return An <code>Iterator</code> over the segments of the state field path */ public final ListIterable<String> paths() { checkPaths(); return new SnapshotCloneListIterable<String>(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 <b>UPDATE</b> and <b>DELETE</b> 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 <code>true</code> if the path starts with a dot; <code>false</code> 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(); } /** * {@inheritDoc} */ @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); } } }