001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.ejb.query;
012:
013: import com.versant.core.metadata.ModelMetaData;
014: import com.versant.core.metadata.FieldMetaData;
015: import com.versant.core.metadata.ClassMetaData;
016: import com.versant.core.common.BindingSupportImpl;
017: import com.versant.core.common.Debug;
018:
019: import java.util.HashMap;
020: import java.util.Map;
021: import java.util.Iterator;
022: import java.util.ArrayList;
023: import java.io.PrintStream;
024:
025: /**
026: * Information collected and used during a Node.resolve call (e.g.
027: * identification variables and so on).
028: */
029: public class ResolveContext {
030:
031: private final ModelMetaData mmd;
032: private final Map idVarMap = new HashMap(16);
033: private NavRoot[] roots = new NavRoot[2];
034: private int rootCount;
035:
036: private Map paramMap; // param name or Integer number -> ParamUsage list
037: private boolean usingPositionalParameters;
038:
039: private ParamUsage[] NO_PARAMS = new ParamUsage[0];
040:
041: /**
042: * Tracks usage of a parameter within the query String. Each time
043: * a parameter is used one of these is added to its list in
044: * paramMap.
045: */
046: public static class ParamUsage {
047:
048: private ParameterNode paramNode;
049: private ParamUsage next;
050: private int index;
051: public Object storeObject;
052:
053: public ParamUsage(ParameterNode paramNode, int index) {
054: this .paramNode = paramNode;
055: this .index = index;
056: }
057:
058: public ParameterNode getParamNode() {
059: return paramNode;
060: }
061:
062: public ParamUsage getNext() {
063: return next;
064: }
065:
066: /**
067: * This defines the order that the parameters where addded in with
068: * the first having index 0.
069: */
070: public int getIndex() {
071: return index;
072: }
073: }
074:
075: public ResolveContext(ModelMetaData mmd) {
076: this .mmd = mmd;
077: }
078:
079: public ModelMetaData getModelMetaData() {
080: return mmd;
081: }
082:
083: /**
084: * Throw a user exception of some kind. This is invoked for user errors
085: * in the query (e.g. duplicate identification variables). The node
086: * parameter is used to provide a line and column reference to the
087: * original query string.
088: */
089: public RuntimeException createUserException(String msg, Node node) {
090: return BindingSupportImpl.getInstance().invalidOperation(msg);
091: }
092:
093: /**
094: * Get the identification variable for the identifier or null if
095: * not found.
096: */
097: public NavBase getIdVar(String identifier) {
098: return (NavBase) idVarMap.get(identifier);
099: }
100:
101: /**
102: * Add an identification variable for an identifier.
103: */
104: public void addIdVar(String identifier, NavBase navBase) {
105: idVarMap.put(identifier, navBase);
106: if (navBase instanceof NavRoot) {
107: if (rootCount == roots.length) {
108: NavRoot[] a = new NavRoot[rootCount * 2];
109: System.arraycopy(roots, 0, a, 0, rootCount);
110: roots = a;
111: }
112: roots[rootCount++] = (NavRoot) navBase;
113: }
114: }
115:
116: /**
117: * Get the number of roots.
118: */
119: public int getRootCount() {
120: return rootCount;
121: }
122:
123: /**
124: * Get the root with index.
125: */
126: public NavRoot getRoot(int index) {
127: if (Debug.DEBUG) {
128: if (index >= rootCount) {
129: throw BindingSupportImpl.getInstance().internal(
130: "index >= rootCount: " + index + " >= "
131: + rootCount);
132: }
133: }
134: return roots[index];
135: }
136:
137: /**
138: * Throw a duplicate ID var exception if identifier already exists.
139: */
140: public void checkIdVarDoesNotExist(String identifier, Node node) {
141: NavBase dup = getIdVar(identifier);
142: if (dup != null) {
143: throw createUserException(
144: "Duplicate identification variable: " + identifier,
145: node);
146: }
147: }
148:
149: /**
150: * Get the identification variable for the identifier or throw an
151: * exception if not found.
152: */
153: public NavBase checkIdVarExists(String identifier, Node node) {
154: NavBase ans = getIdVar(identifier);
155: if (ans == null) {
156: throw createUserException(
157: "Unknown identification variable: " + identifier,
158: node);
159: }
160: return ans;
161: }
162:
163: /**
164: * Resolve a path into a NavField. A new path is created in the tree
165: * starting at the identification variable if necessary.
166: */
167: public NavBase resolveJoinPath(PathNode path, boolean outer,
168: boolean fetch) {
169: return resolveJoinPath(path, outer, fetch, path.size());
170: }
171:
172: /**
173: * Resolve a path sz identifiers deep into a NavBase. A new path is
174: * created in the tree starting at the identification variable if
175: * necessary.
176: */
177: public NavBase resolveJoinPath(PathNode path, boolean outer,
178: boolean fetch, int sz) {
179: String idVarName = path.get(0);
180: NavBase pos = checkIdVarExists(idVarName, path);
181: for (int i = 1; i < sz; i++) {
182: String name = path.get(i);
183: NavField f = pos.findChild(name, outer, fetch);
184: if (f == null) {
185: ClassMetaData cmd = pos.getNavClassMetaData();
186: if (cmd == null) {
187: throw createUserException("Field "
188: + f.getFmd().getQName()
189: + " may not be navigated", path);
190: }
191: FieldMetaData fmd = cmd.getFieldMetaData(name);
192: if (fmd == null) {
193: throw createUserException("No such field '" + name
194: + "' on " + cmd.qname, path);
195: }
196: f = new NavField(fmd, pos, outer, fetch);
197: }
198: pos = f;
199: }
200: return pos;
201: }
202:
203: public ParamUsage addParameterNode(ParameterNode param) {
204: if (paramMap == null) {
205: usingPositionalParameters = param.isPositional();
206: paramMap = new HashMap(8);
207: } else {
208: if (usingPositionalParameters != param.isPositional()) {
209: throw createUserException(
210: "Positional and named parameters may not be mixed",
211: param);
212: }
213: }
214: ParamUsage ans = new ParamUsage(param, paramMap.size());
215: Object key = param.getName();
216: ParamUsage u = (ParamUsage) paramMap.get(key);
217: if (u != null) {
218: for (; u.next != null; u = u.next)
219: ;
220: u.next = ans;
221: } else {
222: paramMap.put(key, ans);
223: }
224: return ans;
225: }
226:
227: /**
228: * Is this query using positional parameters?
229: */
230: public boolean isUsingPositionalParameters() {
231: return usingPositionalParameters;
232: }
233:
234: /**
235: * Get the parameters used in the query sorted in the order that they
236: * appear. Returns empty array if the query uses no parameters.
237: */
238: public ParamUsage[] getParameters() {
239: if (paramMap == null) {
240: return NO_PARAMS;
241: }
242: ParamUsage[] ans = new ParamUsage[paramMap.size()];
243: for (Iterator i = paramMap.entrySet().iterator(); i.hasNext();) {
244: Map.Entry e = (Map.Entry) i.next();
245: ParamUsage u = (ParamUsage) e.getValue();
246: ans[u.index] = u;
247: }
248: return ans;
249: }
250:
251: /**
252: * Find the first usage of a parameter in the query String. Throws
253: * an exception if none found (should not be possible).
254: */
255: public ParamUsage getFirstParamUsage(Object nameOrPosition) {
256: ParamUsage ans = (ParamUsage) paramMap.get(nameOrPosition);
257: if (ans == null) {
258: throw BindingSupportImpl.getInstance().internal(
259: "No usage found for " + nameOrPosition);
260: }
261: return ans;
262: }
263:
264: /**
265: * Dump debugging info.
266: */
267: public void dump(PrintStream out) {
268: out.println("Roots (" + rootCount + ")");
269: for (int i = 0; i < rootCount; i++) {
270: out.println("[" + i + "] " + getRoot(i));
271: }
272: }
273:
274: }
|