001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
005: *
006: *
007: * The contents of this file are subject to the terms of either the GNU
008: * General Public License Version 2 only ("GPL") or the Common Development
009: * and Distribution License("CDDL") (collectively, the "License"). You
010: * may not use this file except in compliance with the License. You can obtain
011: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
012: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
013: * language governing permissions and limitations under the License.
014: *
015: * When distributing the software, include this License Header Notice in each
016: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
017: * Sun designates this particular file as subject to the "Classpath" exception
018: * as provided by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the License
020: * Header, with the fields enclosed by brackets [] replaced by your own
021: * identifying information: "Portions Copyrighted [year]
022: * [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * If you wish your version of this file to be governed by only the CDDL or
027: * only the GPL Version 2, indicate your decision by adding "[Contributor]
028: * elects to include this software in this distribution under the [CDDL or GPL
029: * Version 2] license." If you don't indicate a single choice of license, a
030: * recipient has the option to distribute your version of this file under
031: * either the CDDL, the GPL Version 2 or to extend the choice of license to
032: * its licensees as provided above. However, if you add GPL Version 2 code
033: * and therefore, elected the GPL Version 2 license, then the option applies
034: * only if the new code is made subject to such option by the copyright
035: * holder.
036: */
037: package oracle.toplink.essentials.internal.parsing;
038:
039: import java.util.*;
040: import oracle.toplink.essentials.internal.localization.*;
041: import oracle.toplink.essentials.descriptors.ClassDescriptor;
042: import oracle.toplink.essentials.exceptions.EJBQLException;
043:
044: /**
045: * INTERNAL
046: * <p><b>Purpose</b>: The ParseTreeContext holds and manages context information for the parse tree for validation.
047: * <p><b>Responsibilities</b>:<ul>
048: * <li> Associate schema names with variables
049: * <li> Associate identifier with nodes
050: * <li> Answer an alias for a variable name
051: * <li> Answer a class for a variable name
052: * <li> Answer a class loader
053: * <li> Answer true if there is a class for a variable name
054: * <li> Answer a node for a given identifier
055: * <li> Print the context on a string
056: * </ul>
057: * @see ParseTree
058: * @author Jon Driscoll and Joel Lucuik
059: * @since TopLink 4.0
060: */
061: public class ParseTreeContext {
062: private Map variableDecls;
063: private String baseVariable;
064: private int currentScope;
065: private Set outerScopeVariables;
066: private Map fetchJoins;
067: private TypeHelper typeHelper;
068: private Map parameterTypes;
069: private List parameterNames;
070: private NodeFactory nodeFactory;
071: private String queryInfo;
072:
073: /**
074: * INTERNAL
075: * Return a new initialized ParseTreeContext
076: */
077: public ParseTreeContext(NodeFactory nodeFactory, String queryInfo) {
078: super ();
079: variableDecls = new HashMap();
080: currentScope = 0;
081: fetchJoins = new HashMap();
082: typeHelper = null;
083: parameterTypes = new HashMap();
084: parameterNames = new ArrayList();
085: this .nodeFactory = nodeFactory;
086: this .queryInfo = queryInfo;
087: }
088:
089: /**
090: * INTERNAL
091: * Associate the given schema with the given variable.
092: */
093: public void registerSchema(String variable, String schema,
094: int line, int column) {
095: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
096: if (decl == null) {
097: decl = new VariableDecl(variable, schema);
098: variableDecls.put(variable, decl);
099: } else {
100: String text = decl.isRangeVariable ? decl.schema
101: : decl.path.getAsString();
102: throw EJBQLException.multipleVariableDeclaration(
103: getQueryInfo(), line, column, variable, text);
104: }
105: }
106:
107: /**
108: * INTERNAL
109: * Associate the given path with the given variable.
110: */
111: public void registerJoinVariable(String variable, Node path,
112: int line, int column) {
113: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
114: if (decl == null) {
115: decl = new VariableDecl(variable, path);
116: variableDecls.put(variable, decl);
117: } else {
118: String text = decl.isRangeVariable ? decl.schema
119: : decl.path.getAsString();
120: throw EJBQLException.multipleVariableDeclaration(
121: getQueryInfo(), line, column, variable, text);
122: }
123: }
124:
125: /**
126: * INTERNAL
127: */
128: public void unregisterVariable(String variable) {
129: variableDecls.remove(variable);
130: }
131:
132: /**
133: * INTERNAL
134: * Returns true if the specified string denotes a variable.
135: */
136: public boolean isVariable(String variable) {
137: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
138: return decl != null;
139: }
140:
141: /**
142: * INTERNAL
143: * Returns true if the specified string denotes a range variable.
144: */
145: /** */
146: public boolean isRangeVariable(String variable) {
147: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
148: return (decl != null) && decl.isRangeVariable;
149: }
150:
151: /**
152: * INTERNAL
153: * Returns the abstract schema name if the specified string denotes a
154: * range variable.
155: */
156: public String schemaForVariable(String variable) {
157: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
158: return (decl != null) ? decl.schema : null;
159: }
160:
161: /**
162: * INTERNAL
163: * Answer the class associated with the provided schema name
164: */
165: public Class classForSchemaName(String schemaName,
166: GenerationContext context) {
167: ClassDescriptor descriptor = context.getSession()
168: .getDescriptorForAlias(schemaName);
169: if (descriptor == null) {
170: throw EJBQLException.unknownAbstractSchemaType(
171: getQueryInfo(), schemaName);
172: }
173: Class theClass = descriptor.getJavaClass();
174: if (theClass == null) {
175: throw EJBQLException.resolutionClassNotFoundException(
176: getQueryInfo(), schemaName);
177: }
178: return theClass;
179: }
180:
181: /**
182: * INTERNAL
183: * getVariableNameForClass():
184: * Answer the name mapped to the specified class. Answer null if none found.
185: * SELECT OBJECT (emp) FROM Employee emp
186: * getVariableNameForClass(Employee.class) => "emp"
187: */
188: public String getVariableNameForClass(Class theClass,
189: GenerationContext context) {
190: for (Iterator i = variableDecls.entrySet().iterator(); i
191: .hasNext();) {
192: Map.Entry entry = (Map.Entry) i.next();
193: String nextVariable = (String) entry.getKey();
194: VariableDecl decl = (VariableDecl) entry.getValue();
195: if ((decl.schema != null)
196: && (theClass == this .classForSchemaName(
197: decl.schema, context))) {
198: return nextVariable;
199: }
200: }
201: return null;
202: }
203:
204: /**
205: * INTERNAL
206: * Returns the path if the specified string denotes a join or collection
207: * member variable.
208: */
209: public Node pathForVariable(String variable) {
210: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
211: return (decl != null) ? decl.path : null;
212: }
213:
214: /** */
215: public String getBaseVariable() {
216: return baseVariable;
217: }
218:
219: /** */
220: public void setBaseVariable(String variable) {
221: this .baseVariable = variable;
222: }
223:
224: /** */
225: public NodeFactory getNodeFactory() {
226: return nodeFactory;
227: }
228:
229: /** */
230: public String getQueryInfo() {
231: return queryInfo;
232: }
233:
234: /**
235: * INTERNAL
236: * Returns true if the specified string denotes a variable declared in an
237: * outer scope.
238: */
239: public boolean isDeclaredInOuterScope(String variable) {
240: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
241: return (decl != null) ? (decl.scope < currentScope) : false;
242: }
243:
244: /**
245: * INTERNAL
246: * Sets the scope of the specified variable to the current scope.
247: */
248: public void setScopeOfVariable(String variable) {
249: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
250: if (decl != null) {
251: decl.scope = currentScope;
252: }
253: }
254:
255: /**
256: * INTERNAL
257: * Enters a new scope. This initializes the set of outer scope variables.
258: */
259: public void enterScope() {
260: currentScope++;
261: resetOuterScopeVariables();
262: }
263:
264: /**
265: * INTERNAL
266: * Leaves the current scope.
267: */
268: public void leaveScope() {
269: currentScope--;
270: }
271:
272: /**
273: * INTERNAL
274: * Adds the specified variable to the set of outer scope variables.
275: */
276: public void registerOuterScopeVariable(String variable) {
277: outerScopeVariables.add(variable);
278: }
279:
280: /**
281: * INTERNAL
282: * Returns the set of outer scope variables.
283: */
284: public Set getOuterScopeVariables() {
285: return outerScopeVariables;
286: }
287:
288: /**
289: * INTERNAL
290: * Resets the set of outer scope variables.
291: */
292: public void resetOuterScopeVariables() {
293: outerScopeVariables = new HashSet();
294: }
295:
296: /**
297: * INTERNAL
298: * Resets the set of outer scope variables.
299: */
300: public void resetOuterScopeVariables(Set variables) {
301: outerScopeVariables = variables;
302: }
303:
304: /**
305: * Associate the given variableName with the given node representating a
306: * JOIN FETCH node.
307: */
308: public void registerFetchJoin(String variableName, Node node) {
309: List joins = (List) fetchJoins.get(variableName);
310: if (joins == null) {
311: joins = new ArrayList();
312: fetchJoins.put(variableName, joins);
313: }
314: joins.add(node);
315: }
316:
317: /** Returns alist of FETCH JOIN nodes for the specified attached to the
318: * specified variable. */
319: public List getFetchJoins(String variableName) {
320: return (List) fetchJoins.get(variableName);
321: }
322:
323: /** Mark the specified variable as used if it is declared in the current
324: * scope. */
325: public void usedVariable(String variable) {
326: VariableDecl decl = (VariableDecl) variableDecls.get(variable);
327: if ((decl != null) && (decl.scope == currentScope)) {
328: decl.used = true;
329: }
330: }
331:
332: /** Returns s set of variables that are declared in the current scope,
333: * but not used in the query. */
334: public Set getUnusedVariables() {
335: Set unused = new HashSet();
336: for (Iterator i = variableDecls.entrySet().iterator(); i
337: .hasNext();) {
338: Map.Entry entry = (Map.Entry) i.next();
339: String variable = (String) entry.getKey();
340: VariableDecl decl = (VariableDecl) entry.getValue();
341: if ((decl.scope == currentScope) && !decl.used) {
342: unused.add(variable);
343: }
344: }
345: return unused;
346: }
347:
348: //answer true if two or more variables are mapped to the same type name in variableTypes
349: //true => "SELECT OBJECT (emp1) FROM Employee emp1, Employee emp2 WHERE ..."
350: //false => "SELECT OBJECT (emp) FROM Employee emp WHERE ..."
351: public boolean hasMoreThanOneVariablePerType() {
352: Map typeNamesToVariables = new HashMap();
353: int nrOfRangeVariables = 0;
354: //Map the Aliases to the variable names, then check the count
355: for (Iterator i = variableDecls.entrySet().iterator(); i
356: .hasNext();) {
357: Map.Entry entry = (Map.Entry) i.next();
358: String variable = (String) entry.getKey();
359: VariableDecl decl = (VariableDecl) entry.getValue();
360: if (decl.isRangeVariable) {
361: nrOfRangeVariables++;
362: typeNamesToVariables.put(decl.schema, variable);
363: }
364: }
365: return typeNamesToVariables.size() != nrOfRangeVariables;
366: }
367:
368: //answer true if two or more aliases are involved in the FROM (different types)
369: //true => "SELECT OBJECT (emp1) FROM Employee emp1, Address addr1 WHERE ..."
370: //false => "SELECT OBJECT (emp) FROM Employee emp WHERE ..."
371: //false => "SELECT OBJECT (emp1) FROM Employee emp1, Employee emp2 WHERE ..."
372: public boolean hasMoreThanOneAliasInFrom() {
373: Map typeNamesToVariables = new HashMap();
374: for (Iterator i = variableDecls.entrySet().iterator(); i
375: .hasNext();) {
376: Map.Entry entry = (Map.Entry) i.next();
377: String variable = (String) entry.getKey();
378: VariableDecl decl = (VariableDecl) entry.getValue();
379: if (decl.isRangeVariable) {
380: typeNamesToVariables.put(decl.schema, variable);
381: }
382: }
383: return typeNamesToVariables.size() > 1;
384: }
385:
386: /**
387: * INTERNAL
388: * Returns the type helper stored in this context.
389: */
390: public TypeHelper getTypeHelper() {
391: return typeHelper;
392: }
393:
394: /**
395: * INTERNAL
396: * Stores the specified type helper in this context.
397: */
398: public void setTypeHelper(TypeHelper typeHelper) {
399: this .typeHelper = typeHelper;
400: }
401:
402: /**
403: * INTERNAL
404: * Add a parameter.
405: */
406: public void addParameter(String parameterName) {
407: if (!parameterNames.contains(parameterName)) {
408: parameterNames.add(parameterName);
409: }
410: }
411:
412: /**
413: * INTERNAL
414: * Defines the type of the parameter with the specified name.
415: */
416: public void defineParameterType(String parameterName,
417: Object parameterType, int line, int column) {
418: if (parameterTypes.containsKey(parameterName)) {
419: // existing entry
420: Object oldType = parameterTypes.get(parameterName);
421: if (typeHelper.isAssignableFrom(oldType, parameterType)) {
422: // OK
423: } else if (typeHelper.isAssignableFrom(parameterType,
424: oldType)) {
425: // new parameter type is more general
426: parameterTypes.put(parameterName, parameterType);
427: } else {
428: // error case old usage and new usage do not match type
429: throw EJBQLException.invalidMultipleUseOfSameParameter(
430: getQueryInfo(), line, column, parameterName,
431: typeHelper.getTypeName(oldType), typeHelper
432: .getTypeName(parameterType));
433: }
434: } else {
435: // new entry
436: parameterTypes.put(parameterName, parameterType);
437: }
438: }
439:
440: /**
441: * INTERNAL
442: * Returns true if the query has at least one parameter.
443: */
444: public boolean hasParameters() {
445: return !parameterNames.isEmpty();
446: }
447:
448: /**
449: * INTERNAL
450: * Return the type of the specified parameter.
451: */
452: public Object getParameterType(String parameter) {
453: return parameterTypes.get(parameter);
454: }
455:
456: /**
457: * INTERNAL
458: * Return the parameter names.
459: */
460: public List getParameterNames() {
461: return parameterNames;
462: }
463:
464: /**
465: * INTERNAL
466: * Class defining the type of the values the variableDecls map.
467: * It holds the following values:
468: * variable - the name of the variable
469: * isRangeVariable - true if the variable is declared as range variable
470: * schema - the abstract for a range variable
471: * path - the path for join or collection member variable
472: * scope - the scope of teh variable
473: * used - true if the variable is used in any of the clauses
474: */
475: static class VariableDecl {
476: public final String variable;
477: public final boolean isRangeVariable;
478: public final String schema;
479: public final Node path;
480: public int scope;
481: public boolean used;
482:
483: public VariableDecl(String variable, String schema) {
484: this .variable = variable;
485: this .isRangeVariable = true;
486: this .schema = schema;
487: this .path = null;
488: this .used = false;
489: }
490:
491: public VariableDecl(String variable, Node path) {
492: this .variable = variable;
493: this .isRangeVariable = false;
494: this .schema = null;
495: this .path = path;
496: this .used = false;
497: }
498: }
499: }
|