001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.expr.Expression;
005: import net.sf.saxon.expr.ExpressionTool;
006: import net.sf.saxon.expr.UserFunctionCall;
007: import net.sf.saxon.expr.XPathContextMajor;
008: import net.sf.saxon.om.*;
009: import net.sf.saxon.style.StandardNames;
010: import net.sf.saxon.trace.InstructionInfo;
011: import net.sf.saxon.trace.InstructionInfoProvider;
012: import net.sf.saxon.trans.XPathException;
013: import net.sf.saxon.type.Type;
014: import net.sf.saxon.value.SequenceType;
015: import net.sf.saxon.value.Value;
016:
017: import java.util.HashMap;
018:
019: /**
020: * This object represents the compiled form of a user-written function
021: * (the source can be either an XSLT stylesheet function or an XQuery function).
022: *
023: * <p>It is assumed that type-checking, of both the arguments and the results,
024: * has been handled at compile time. That is, the expression supplied as the body
025: * of the function must be wrapped in code to check or convert the result to the
026: * required type, and calls on the function must be wrapped at compile time to check or
027: * convert the supplied arguments.
028: */
029:
030: public final class UserFunction extends Procedure implements
031: InstructionInfoProvider {
032:
033: private int functionNameCode;
034: private boolean memoFunction = false;
035: private boolean tailRecursive = false;
036: // this actually means the function contains tail calls,
037: // they are not necessarily recursive calls
038: private UserFunctionParameter[] parameterDefinitions;
039: private SequenceType resultType;
040: private transient InstructionDetails details = null;
041:
042: public UserFunction() {
043: }
044:
045: public UserFunction(Expression body) {
046: setBody(body);
047: };
048:
049: public void setParameterDefinitions(UserFunctionParameter[] params) {
050: this .parameterDefinitions = params;
051: }
052:
053: public UserFunctionParameter[] getParameterDefinitions() {
054: return parameterDefinitions;
055: }
056:
057: public void setResultType(SequenceType resultType) {
058: this .resultType = resultType;
059: }
060:
061: public void setTailRecursive(boolean tailCalls) {
062: tailRecursive = tailCalls;
063: }
064:
065: /**
066: * Get the type of value returned by this function
067: * @return the declared result type, or the inferred result type
068: * if this is more precise
069: */
070: public SequenceType getResultType() {
071: return resultType;
072: }
073:
074: /**
075: * Get the required types of an argument to this function
076: * @param n identifies the argument in question, starting at 0
077: * @return a SequenceType object, indicating the required type of the argument
078: */
079:
080: public SequenceType getArgumentType(int n) {
081: return parameterDefinitions[n].getRequiredType();
082: }
083:
084: /**
085: * Get the arity of this function
086: * @return the number of arguments
087: */
088:
089: public int getNumberOfArguments() {
090: return parameterDefinitions.length;
091: }
092:
093: /**
094: * Mark this function as a memo function (or not)
095: * @param isMemo true if this is a memo function
096: */
097:
098: public void setMemoFunction(boolean isMemo) {
099: memoFunction = isMemo;
100: }
101:
102: /**
103: * Set the namepool name code of the function
104: * @param nameCode represents the function name
105: */
106:
107: public void setFunctionNameCode(int nameCode) {
108: functionNameCode = nameCode;
109: }
110:
111: /**
112: * Get the namepool name code of the function
113: * @return a name code representing the function name
114: */
115:
116: public int getFunctionNameCode() {
117: return functionNameCode;
118: }
119:
120: /**
121: * Call this function.
122: * @param actualArgs the arguments supplied to the function. These must have the correct
123: * types required by the function signature (it is the caller's responsibility to check this).
124: * It is acceptable to supply a {@link net.sf.saxon.value.Closure} to represent a value whose
125: * evaluation will be delayed until it is needed. The array must be the correct size to match
126: * the number of arguments: again, it is the caller's responsibility to check this.
127: * @param context This provides the run-time context for evaluating the function. It is the caller's
128: * responsibility to allocate a "clean" context for the function to use; the context that is provided
129: * will be overwritten by the function.
130: * @param evaluateTailCalls if true, then any function calls contained in the body of the function
131: * are evaluated in the normal way, whether or not they are marked as tail calls. If the argument
132: * is false, then tail calls are not evaluated, and instead a FunctionCallPackage is returned containing
133: * the information needed to evaluate the function. The caller must then be prepared to deal with this
134: * returned value by evaluating the packaged function call (which may return further packaged function
135: * calls, and so on).
136: * @return a Value representing the result of the function.
137: */
138:
139: public ValueRepresentation call(ValueRepresentation[] actualArgs,
140: XPathContextMajor context, boolean evaluateTailCalls)
141: throws XPathException {
142:
143: // If this is a memo function, see if the result is already known
144: Controller controller = context.getController();
145: if (memoFunction) {
146: ValueRepresentation value = getCachedValue(controller,
147: actualArgs);
148: if (value != null)
149: return value;
150: }
151:
152: // Otherwise evaluate the function
153:
154: context.setStackFrame(getStackFrameMap(), actualArgs);
155: ValueRepresentation result;
156: try {
157: if (tailRecursive || memoFunction) {
158: // If this function contains tail calls, we evaluate it eagerly, because
159: // the caller needs to know whether a tail call was returned or not: if we
160: // return a Closure, the tail call escapes into the wild and can reappear anywhere...
161: // Eager evaluation also makes sense if it's a memo function.
162: result = ExpressionTool.eagerEvaluate(getBody(),
163: context);
164: } else {
165: result = ExpressionTool.lazyEvaluate(getBody(),
166: context, 1);
167: }
168: } catch (XPathException err) {
169: if (err.getLocator() == null) {
170: err.setLocator(this );
171: }
172: throw err;
173: }
174:
175: if (evaluateTailCalls) {
176: while (result instanceof UserFunctionCall.FunctionCallPackage) {
177: result = ((UserFunctionCall.FunctionCallPackage) result)
178: .call();
179: }
180: }
181:
182: // If this is a memo function, save the result in the cache
183: if (memoFunction) {
184: putCachedValue(controller, actualArgs, result);
185: }
186:
187: return result;
188: }
189:
190: /**
191: * Call this function. This method allows an XQuery function to be called directly from a Java
192: * application. It creates the environment needed to achieve this
193: * @param actualArgs the arguments supplied to the function. These must have the correct
194: * types required by the function signature (it is the caller's responsibility to check this).
195: * It is acceptable to supply a {@link net.sf.saxon.value.Closure} to represent a value whose
196: * evaluation will be delayed until it is needed. The array must be the correct size to match
197: * the number of arguments: again, it is the caller's responsibility to check this.
198: * @param controller This provides the run-time context for evaluating the function. A Controller
199: * may be obtained by calling {@link net.sf.saxon.query.XQueryExpression#newController}. This may
200: * be used for a series of calls on functions defined in the same module as the XQueryExpression.
201: * @return a Value representing the result of the function.
202: */
203:
204: public ValueRepresentation call(ValueRepresentation[] actualArgs,
205: Controller controller) throws XPathException {
206: return call(actualArgs, controller.newXPathContext(), true);
207: }
208:
209: /**
210: * For memo functions, get a saved value from the cache.
211: * @return the cached value, or null if no value has been saved for these parameters
212: */
213:
214: private ValueRepresentation getCachedValue(Controller controller,
215: ValueRepresentation[] params) throws XPathException {
216: HashMap map = (HashMap) controller.getUserData(this ,
217: "memo-function-cache");
218: if (map == null) {
219: return null;
220: }
221: String key = getCombinedKey(params);
222: //System.err.println("Used cached value");
223: return (ValueRepresentation) map.get(key);
224: }
225:
226: /**
227: * For memo functions, put the computed value in the cache.
228: */
229:
230: private void putCachedValue(Controller controller,
231: ValueRepresentation[] params, ValueRepresentation value)
232: throws XPathException {
233: HashMap map = (HashMap) controller.getUserData(this ,
234: "memo-function-cache");
235: if (map == null) {
236: map = new HashMap(32);
237: controller.setUserData(this , "memo-function-cache", map);
238: }
239: String key = getCombinedKey(params);
240: map.put(key, value);
241: }
242:
243: /**
244: * Get a key value representing the values of all the supplied arguments
245: */
246:
247: private static String getCombinedKey(ValueRepresentation[] params)
248: throws XPathException {
249: FastStringBuffer sb = new FastStringBuffer(120);
250:
251: for (int i = 0; i < params.length; i++) {
252: ValueRepresentation val = params[i];
253: // TODO: if the argument value is a sequence, use the identity of the sequence, not its content
254: SequenceIterator iter = Value.getIterator(val);
255: while (true) {
256: Item item = iter.next();
257: if (item == null) {
258: break;
259: }
260: if (item instanceof NodeInfo) {
261: NodeInfo node = (NodeInfo) item;
262: //sb.append(""+node.getDocumentRoot().getDocumentNumber());
263: //sb.append('/');
264: sb.append(node.generateId());
265: } else {
266: sb.append("" + Type.displayTypeName(item));
267: sb.append('/');
268: sb.append(item.getStringValueCS());
269: }
270: sb.append('\u0001');
271: }
272: sb.append('\u0002');
273: }
274: return sb.toString();
275: }
276:
277: /**
278: * Get the InstructionInfo details about the construct. This information isn't used for tracing,
279: * but it is available when inspecting the context stack.
280: */
281:
282: public InstructionInfo getInstructionInfo() {
283: if (details == null) {
284: details = new InstructionDetails();
285: details.setSystemId(getSystemId());
286: details.setLineNumber(getLineNumber());
287: details.setConstructType(StandardNames.XSL_FUNCTION);
288: details.setObjectNameCode(functionNameCode);
289: details.setProperty("function", this );
290: }
291: return details;
292: }
293: }
294:
295: //
296: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
297: // you may not use this file except in compliance with the License. You may obtain a copy of the
298: // License at http://www.mozilla.org/MPL/
299: //
300: // Software distributed under the License is distributed on an "AS IS" basis,
301: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
302: // See the License for the specific language governing rights and limitations under the License.
303: //
304: // The Original Code is: all this file.
305: //
306: // The Initial Developer of the Original Code is Michael H. Kay
307: //
308: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
309: //
310: // Contributor(s): none
311: //
|