| /* |
| * 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 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; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.JPQLException; |
| |
| /** |
| * INTERNAL |
| * <p><b>Purpose</b>: The ParseTreeContext holds and manages context information for the parse tree for validation. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Associate schema names with variables |
| * <li> Associate identifier with nodes |
| * <li> Answer an alias for a variable name |
| * <li> Answer a class for a variable name |
| * <li> Answer a class loader |
| * <li> Answer true if there is a class for a variable name |
| * <li> Answer a node for a given identifier |
| * <li> Print the context on a string |
| * </ul> |
| * @see ParseTree |
| * @author Jon Driscoll and Joel Lucuik |
| * @since TopLink 4.0 |
| */ |
| public class ParseTreeContext { |
| private Map<String, VariableDecl> variableDecls; |
| private String baseVariable; |
| private int currentScope; |
| private Set<String> outerScopeVariables; |
| private Map<String, List<Node>> fetchJoins; |
| private TypeHelper typeHelper; |
| private Map<String, Object> parameterTypes; |
| private List<String> 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<Map.Entry<String, VariableDecl>> i = variableDecls.entrySet().iterator(); i.hasNext(); ) { |
| Map.Entry<String, VariableDecl> 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<String> 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<String> variables) { |
| outerScopeVariables = variables; |
| } |
| |
| /** |
| * Associate the given variableName with the given node representing a |
| * JOIN FETCH node. |
| */ |
| public void registerFetchJoin(String variableName, Node node) { |
| List<Node> 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<Node> 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<String> getUnusedVariables() { |
| Set<String> unused = new HashSet<>(); |
| for (Iterator<Map.Entry<String, VariableDecl>> i = variableDecls.entrySet().iterator(); i.hasNext();) { |
| Map.Entry<String, VariableDecl> 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<String, String> typeNamesToVariables = new HashMap<>(); |
| int nrOfRangeVariables = 0; |
| //Map the Aliases to the variable names, then check the count |
| for (Iterator<Map.Entry<String, VariableDecl>> i = variableDecls.entrySet().iterator(); i.hasNext(); ) { |
| Map.Entry<String, VariableDecl> 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<String, String> typeNamesToVariables = new HashMap<>(); |
| for (Iterator<Map.Entry<String, VariableDecl>> i = variableDecls.entrySet().iterator(); i.hasNext(); ) { |
| Map.Entry<String, VariableDecl> 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<String> 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; |
| } |
| } |
| } |