org.eclipse.persistence.internal.jpa.parsing.ParseTreeContext 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
The newest version!
/*
* Copyright (c) 1998, 2023 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.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.JPQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* INTERNAL
* Purpose: The ParseTreeContext holds and manages context information for the parse tree for validation.
*
Responsibilities:
* - Associate schema names with variables
*
- Associate identifier with nodes
*
- Answer an alias for a variable name
*
- Answer a class for a variable name
*
- Answer a class loader
*
- Answer true if there is a class for a variable name
*
- Answer a node for a given identifier
*
- Print the context on a string
*
* @see ParseTree
* @author Jon Driscoll and Joel Lucuik
* @since TopLink 4.0
*/
public class ParseTreeContext {
private Map variableDecls;
private String baseVariable;
private int currentScope;
private Set outerScopeVariables;
private Map> fetchJoins;
private TypeHelper typeHelper;
private Map parameterTypes;
private List parameterNames;
private NodeFactory nodeFactory;
private String queryInfo;
/**
* INTERNAL
* Return a new initialized ParseTreeContext
*/
public ParseTreeContext(NodeFactory nodeFactory, String queryInfo) {
super();
variableDecls = new HashMap<>();
currentScope = 0;
fetchJoins = new HashMap<>();
typeHelper = null;
parameterTypes = new HashMap<>();
parameterNames = new ArrayList<>();
this.nodeFactory = nodeFactory;
this.queryInfo = queryInfo;
}
/**
* INTERNAL
* Associate the given schema with the given variable.
*/
public void registerSchema(String variable, String schema, int line, int column) {
VariableDecl decl = variableDecls.get(variable);
if (decl == null) {
decl = new VariableDecl(variable, schema);
variableDecls.put(variable, decl);
} else {
String text = decl.isRangeVariable ? decl.schema : decl.path.getAsString();
throw JPQLException.multipleVariableDeclaration(
getQueryInfo(), line, column, variable, text);
}
}
/**
* INTERNAL
* Associate the given path with the given variable.
*/
public void registerJoinVariable(String variable, Node path, int line, int column) {
VariableDecl decl = variableDecls.get(variable);
if (decl == null) {
decl = new VariableDecl(variable, path);
variableDecls.put(variable, decl);
} else {
String text = decl.isRangeVariable ? decl.schema : decl.path.getAsString();
throw JPQLException.multipleVariableDeclaration(
getQueryInfo(), line, column, variable, text);
}
}
/**
* INTERNAL
*/
public void unregisterVariable(String variable) {
variableDecls.remove(variable);
}
/**
* INTERNAL
* Returns true if the specified string denotes a variable.
*/
public boolean isVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
return decl != null;
}
/**
* INTERNAL
* Returns true if the specified string denotes a range variable.
*/
public boolean isRangeVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
return (decl != null) && decl.isRangeVariable;
}
/**
* INTERNAL
* Returns the abstract schema name if the specified string denotes a
* range variable.
*/
public String schemaForVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
return (decl != null) ? decl.schema : null;
}
/**
* INTERNAL
* Answer the class associated with the provided schema name
*/
public Class> classForSchemaName(String schemaName, GenerationContext context) {
ClassDescriptor descriptor = context.getSession().getDescriptorForAlias(schemaName);
if (descriptor == null) {
throw JPQLException.entityTypeNotFound(getQueryInfo(), schemaName);
}
Class> theClass = descriptor.getJavaClass();
if (theClass == null) {
throw JPQLException.resolutionClassNotFoundException(getQueryInfo(), schemaName);
}
return theClass;
}
/**
* INTERNAL
* getVariableNameForClass():
* Answer the name mapped to the specified class. Answer null if none found.
* SELECT OBJECT (emp) FROM Employee emp
* getVariableNameForClass(Employee.class) => "emp"
*/
public String getVariableNameForClass(Class> theClass, GenerationContext context) {
for (Iterator> i = variableDecls.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = i.next();
String nextVariable = entry.getKey();
VariableDecl decl = entry.getValue();
if ((decl.schema != null) &&
(theClass == this.classForSchemaName(decl.schema, context))) {
return nextVariable;
}
}
return null;
}
/**
* INTERNAL
* Returns the path if the specified string denotes a join or collection
* member variable.
*/
public Node pathForVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
return (decl != null) ? decl.path : null;
}
/** */
public String getBaseVariable() {
return baseVariable;
}
/** */
public void setBaseVariable(String variable) {
this.baseVariable = variable;
}
/** */
public NodeFactory getNodeFactory() {
return nodeFactory;
}
/** */
public String getQueryInfo() {
return queryInfo;
}
/**
* INTERNAL
* Returns true if the specified string denotes a variable declared in an
* outer scope.
*/
public boolean isDeclaredInOuterScope(String variable) {
VariableDecl decl = variableDecls.get(variable);
return decl != null && (decl.scope < currentScope);
}
/**
* INTERNAL
* Sets the scope of the specified variable to the current scope.
*/
public void setScopeOfVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
if (decl != null) {
decl.scope = currentScope;
}
}
/**
* INTERNAL
* Enters a new scope. This initializes the set of outer scope variables.
*/
public void enterScope() {
currentScope++;
resetOuterScopeVariables();
}
/**
* INTERNAL
* Leaves the current scope.
*/
public void leaveScope() {
currentScope--;
}
/**
* INTERNAL
* Adds the specified variable to the set of outer scope variables.
*/
public void registerOuterScopeVariable(String variable) {
outerScopeVariables.add(variable);
}
/**
* INTERNAL
* Returns the set of outer scope variables.
*/
public Set getOuterScopeVariables() {
return outerScopeVariables;
}
/**
* INTERNAL
* Resets the set of outer scope variables.
*/
public void resetOuterScopeVariables() {
outerScopeVariables = new HashSet<>();
}
/**
* INTERNAL
* Resets the set of outer scope variables.
*/
public void resetOuterScopeVariables(Set variables) {
outerScopeVariables = variables;
}
/**
* Associate the given variableName with the given node representing a
* JOIN FETCH node.
*/
public void registerFetchJoin(String variableName, Node node) {
List joins = fetchJoins.get(variableName);
if (joins == null) {
joins = new ArrayList<>();
fetchJoins.put(variableName, joins);
}
joins.add(node);
}
/** Returns a list of FETCH JOIN nodes for the specified attached to the
* specified variable.
*/
public List getFetchJoins(String variableName) {
return fetchJoins.get(variableName);
}
/** Mark the specified variable as used if it is declared in the current
* scope. */
public void usedVariable(String variable) {
VariableDecl decl = variableDecls.get(variable);
if ((decl != null) && (decl.scope == currentScope)) {
decl.used = true;
}
}
/** Returns s set of variables that are declared in the current scope,
* but not used in the query.
*/
public Set getUnusedVariables() {
Set unused = new HashSet<>();
for (Iterator> i = variableDecls.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = i.next();
String variable = entry.getKey();
VariableDecl decl = entry.getValue();
if ((decl.scope == currentScope) && !decl.used) {
unused.add(variable);
}
}
return unused;
}
//answer true if two or more variables are mapped to the same type name in variableTypes
//true => "SELECT OBJECT (emp1) FROM Employee emp1, Employee emp2 WHERE ..."
//false => "SELECT OBJECT (emp) FROM Employee emp WHERE ..."
public boolean hasMoreThanOneVariablePerType() {
Map typeNamesToVariables = new HashMap<>();
int nrOfRangeVariables = 0;
//Map the Aliases to the variable names, then check the count
for (Iterator> i = variableDecls.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = i.next();
String variable = entry.getKey();
VariableDecl decl = entry.getValue();
if (decl.isRangeVariable) {
nrOfRangeVariables++;
typeNamesToVariables.put(decl.schema, variable);
}
}
return typeNamesToVariables.size() != nrOfRangeVariables;
}
//answer true if two or more aliases are involved in the FROM (different types)
//true => "SELECT OBJECT (emp1) FROM Employee emp1, Address addr1 WHERE ..."
//false => "SELECT OBJECT (emp) FROM Employee emp WHERE ..."
//false => "SELECT OBJECT (emp1) FROM Employee emp1, Employee emp2 WHERE ..."
public boolean hasMoreThanOneAliasInFrom() {
Map typeNamesToVariables = new HashMap<>();
for (Iterator> i = variableDecls.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = i.next();
String variable = entry.getKey();
VariableDecl decl = entry.getValue();
if (decl.isRangeVariable) {
typeNamesToVariables.put(decl.schema, variable);
}
}
return typeNamesToVariables.size() > 1;
}
/**
* INTERNAL
* Returns the type helper stored in this context.
*/
public TypeHelper getTypeHelper() {
return typeHelper;
}
/**
* INTERNAL
* Stores the specified type helper in this context.
*/
public void setTypeHelper(TypeHelper typeHelper) {
this.typeHelper = typeHelper;
}
/**
* INTERNAL
* Add a parameter.
*/
public void addParameter(String parameterName) {
if (!parameterNames.contains(parameterName)){
parameterNames.add(parameterName);
}
}
/**
* INTERNAL
* Defines the type of the parameter with the specified name.
*/
public void defineParameterType(String parameterName, Object parameterType,
int line, int column) {
if (parameterTypes.containsKey(parameterName)) {
// existing entry
Object oldType = parameterTypes.get(parameterName);
if (typeHelper.isAssignableFrom(oldType, parameterType)) {
// OK
} else if (typeHelper.isAssignableFrom(parameterType, oldType)) {
// new parameter type is more general
parameterTypes.put(parameterName, parameterType);
} else {
// error case old usage and new usage do not match type
throw JPQLException.invalidMultipleUseOfSameParameter(
getQueryInfo(), line, column, parameterName,
typeHelper.getTypeName(oldType),
typeHelper.getTypeName(parameterType));
}
} else {
// new entry
parameterTypes.put(parameterName, parameterType);
}
}
/**
* INTERNAL
* Returns true if the query has at least one parameter.
*/
public boolean hasParameters() {
return !parameterNames.isEmpty();
}
/**
* INTERNAL
* Return the type of the specified parameter.
*/
public Object getParameterType(String parameter) {
return parameterTypes.get(parameter);
}
/**
* INTERNAL
* Return the parameter names.
*/
public List getParameterNames() {
return parameterNames;
}
/**
* INTERNAL
* Class defining the type of the values the variableDecls map.
* It holds the following values:
* variable - the name of the variable
* isRangeVariable - true if the variable is declared as range variable
* schema - the abstract for a range variable
* path - the path for join or collection member variable
* scope - the scope of the variable
* used - true if the variable is used in any of the clauses
*/
static class VariableDecl {
public final String variable;
public final boolean isRangeVariable;
public final String schema;
public final Node path;
public int scope;
public boolean used;
public VariableDecl(String variable, String schema) {
this.variable = variable;
this.isRangeVariable = true;
this.schema = schema;
this.path = null;
this.used = false;
}
public VariableDecl(String variable, Node path) {
this.variable = variable;
this.isRangeVariable = false;
this.schema = null;
this.path = path;
this.used = false;
}
}
}