org.eclipse.persistence.internal.jpa.parsing.DotNode 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) 1998, 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 from Oracle TopLink
package org.eclipse.persistence.internal.jpa.parsing;
import org.eclipse.persistence.exceptions.JPQLException;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.internal.expressions.*;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReportQuery;
/**
* INTERNAL
* Purpose: This node represents an 'DOT' (i.e. '.') on the input
* stream. The left and right will depend on the input stream.
*
Responsibilities:
*
* @author Jon Driscoll and Joel Lucuik
* @since TopLink 4.0
*/
public class DotNode extends LogicalOperatorNode implements AliasableNode {
private Object enumConstant;
/**
* INTERNAL
* Apply this node to the passed query
*/
@Override
public void applyToQuery(ObjectLevelReadQuery theQuery, GenerationContext context) {
if (theQuery.isReportQuery()){
ReportQuery reportQuery = (ReportQuery)theQuery;
reportQuery.addAttribute(resolveAttribute(), generateExpression(context));
reportQuery.dontRetrievePrimaryKeys();
}
}
/**
* INTERNAL
* Check the left child node for an unqualified field access. The method
* delegates to the left most expression of multi-navigation path
* expression.
*/
@Override
public Node qualifyAttributeAccess(ParseTreeContext context) {
if (getLeft() != null) {
setLeft(getLeft().qualifyAttributeAccess(context));
}
return this;
}
/**
* INTERNAL
* Validate node and calculate its type.
* Check for enum literals.
*/
@Override
public void validate(ParseTreeContext context) {
TypeHelper typeHelper = context.getTypeHelper();
String name = ((AttributeNode)right).getAttributeName();
// check for fully qualified type names
Node leftMost = getLeftMostNode();
if (isDeclaredVariable(leftMost, context)) {
left.validate(context);
checkNavigation(left, context);
Object type = null;
if (left.isVariableNode()){
Node path = context.pathForVariable(((VariableNode)left).getVariableName());
if (path != null){
type = path.getType();
type = typeHelper.resolveAttribute(type, name);
}
}
if (type == null){
type = typeHelper.resolveAttribute(left.getType(), name);
}
if (type == null) {
// could not resolve attribute
throw JPQLException.unknownAttribute(
context.getQueryInfo(), right.getLine(), right.getColumn(),
name, typeHelper.getTypeName(left.getType()));
}
if (right.isAttributeNode()){
type = ((AttributeNode)right).computeActualType(type, typeHelper);
((AttributeNode)right).checkForQueryKey(left.getType(), typeHelper);
}
setType(type);
right.setType(type);
} else {
// Check for enum literal access
String typeName = left.getAsString();
Object type = resolveEnumTypeName(typeName, typeHelper);
if ((type != null) && typeHelper.isEnumType(type)) {
enumConstant = typeHelper.resolveEnumConstant(type, name);
if (enumConstant == null) {
throw JPQLException.invalidEnumLiteral(context.getQueryInfo(),
right.getLine(), right.getColumn(), typeName, name);
}
} else {
// left most node is not an identification variable and
// dot expression does not denote an enum literal access =>
// unknown identification variable
throw JPQLException.aliasResolutionException(
context.getQueryInfo(), leftMost.getLine(),
leftMost.getColumn(), leftMost.getAsString());
}
setType(type);
right.setType(type);
}
}
/**
* INTERNAL
* Checks whether the left hand side of this dot node is navigable.
*/
private void checkNavigation(Node node, ParseTreeContext context) {
TypeHelper typeHelper = context.getTypeHelper();
// Checks whether the type of the dot node allows a navigation.
Object type = node.getType();
if (!typeHelper.isEntityClass(type) &&
!typeHelper.isEmbeddable(type) &&
!typeHelper.isEnumType(type)) {
throw JPQLException.invalidNavigation(
context.getQueryInfo(), node.getLine(), node.getColumn(),
this.getAsString(), node.getAsString(),
typeHelper.getTypeName(type));
}
// Special check to disallow collection valued relationships
if (node.isDotNode()) {
Node left = node.getLeft();
AttributeNode right = (AttributeNode)node.getRight();
if (typeHelper.isCollectionValuedRelationship(
left.getType(), right.getAttributeName())) {
throw JPQLException.invalidCollectionNavigation(
context.getQueryInfo(), right.getLine(), right.getColumn(),
this.getAsString(), right.getAttributeName());
}
}
}
/** */
private boolean isDeclaredVariable(Node node, ParseTreeContext context) {
if (node.isVariableNode()) {
String name = ((VariableNode)node).getCanonicalVariableName();
return context.isVariable(name);
}
return false;
}
/**
* INTERNAL
* Return a TopLink expression by getting the required variables using the
* left and right nodes
* "emp.address.city" = builder.get("address").get("city")
*/
@Override
public Expression generateExpression(GenerationContext context) {
Node right = getRight();
if (enumConstant != null) {
// enum literal access
return new ConstantExpression(enumConstant, new ExpressionBuilder());
} else {
// Get the left expression
Expression whereClause = getLeft().generateExpression(context);
// Calculate the mapping and pass it to the right expression
if (right.isAttributeNode()) {
((AttributeNode)right).setMapping(resolveMapping(context));
}
// Or it with whatever the right expression is
whereClause = right.addToExpression(whereClause, context);
if (alias != null){
context.addExpression(whereClause, alias);
}
// and return the expression...
return whereClause;
}
}
/**
* INTERNAL
* Yes, this is a dot node
*/
@Override
public boolean isDotNode() {
return true;
}
/**
* INTERNAL
* ():
* Answer true if the SELECTed node has a left and right, and the right represents
* a direct-to-field mapping.
*/
public boolean endsWithDirectToField(GenerationContext context) {
DatabaseMapping mapping = resolveMapping(context);
return (mapping != null) && mapping.isDirectToFieldMapping();
}
/**
* INTERNAL
* Returns the attribute type if the right represents a direct-to-field mapping.
*/
public Class getTypeOfDirectToField(GenerationContext context) {
DatabaseMapping mapping = resolveMapping(context);
if ((mapping != null) && mapping.isDirectToFieldMapping()) {
return mapping.getAttributeClassification();
}
return null;
}
public Object getTypeForMapKey(ParseTreeContext context){
Object type = null;
String name = ((AttributeNode)right).getAttributeName();
Node leftMost = getLeftMostNode();
if (isDeclaredVariable(leftMost, context)) {
type = context.getTypeHelper().resolveMapKey(left.getType(), name);
}
return type;
}
/**
* INTERNAL
* ():
* Answer true if the node has a left and right, and the right represents
* a collection mapping.
*/
public boolean endsWithCollectionField(GenerationContext context) {
DatabaseMapping mapping = resolveMapping(context);
return (mapping != null) && mapping.isCollectionMapping();
}
/**
* INTERNAL
* Answer the name of the attribute which is represented by the receiver's
* right node.
*/
@Override
public String resolveAttribute() {
return ((AttributeNode)getRight()).getAttributeName();
}
/**
* INTERNAL
* Answer the mapping resulting from traversing the receiver's nodes
*/
@Override
public DatabaseMapping resolveMapping(GenerationContext context) {
Class leftClass = getLeft().resolveClass(context);
return getRight().resolveMapping(context, leftClass);
}
/**
* resolveClass: Answer the class which results from traversing the mappings for the receiver's nodes
*/
@Override
public Class resolveClass(GenerationContext context) {
Class leftClass = getLeft().resolveClass(context);
return getRight().resolveClass(context, leftClass);
}
/**
* INTERNAL
* Get the string representation of this node.
*/
@Override
public String getAsString() {
return left.getAsString() + "." + right.getAsString();
}
/**
* INTERNAL
* Return the left most node of a dot expr, so return 'a' for 'a.b.c'.
*/
public Node getLeftMostNode() {
if (left.isDotNode()){
return ((DotNode)left).getLeftMostNode();
} else if (left.isMapKeyNode()){
return ((MapKeyNode)left).getLeftMostNode();
}
return left;
}
/**
* INTERNAL
* Return the right most node of a dot expr, so return 'c' for 'a.b.c'.
*/
public Node getRightMostNode() {
if (right.isDotNode()){
return ((DotNode)right).getRightMostNode();
}
return right;
}
/**
* INTERNAL
* Returns the type representation for the specified type name. The method
* looks for inner classes if it cannot resolve the type name.
*/
private Object resolveEnumTypeName(String name, TypeHelper helper) {
Object type = helper.resolveTypeName(name);
if (type == null) {
// check for inner enum type
int index = name.lastIndexOf('.');
if (index != -1) {
name = name.substring(0, index) + '$' + name.substring(index+1);
type = helper.resolveTypeName(name);
}
}
return type;
}
@Override
public boolean isAliasableNode(){
return true;
}
}