001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.event.SequenceOutputter;
005: import net.sf.saxon.event.SequenceReceiver;
006: import net.sf.saxon.event.TeeOutputter;
007: import net.sf.saxon.expr.LastPositionFinder;
008: import net.sf.saxon.expr.XPathContext;
009: import net.sf.saxon.om.*;
010: import net.sf.saxon.trans.DynamicError;
011: import net.sf.saxon.trans.XPathException;
012:
013: import java.util.List;
014:
015: /**
016: * A MemoClosure represents a value that has not yet been evaluated: the value is represented
017: * by an expression, together with saved values of all the context variables that the
018: * expression depends on.
019: * <p/>
020: * <p>The MemoClosure is designed for use when the value is only read several times. The
021: * value is saved on the first evaluation and remembered for later use.</p>
022: * <p/>
023: * <p>The MemoClosure maintains a reservoir containing those items in the value that have
024: * already been read. When a new iterator is requested to read the value, this iterator
025: * first examines and returns any items already placed in the reservoir by previous
026: * users of the MemoClosure. When the reservoir is exhausted, it then uses an underlying
027: * Input Iterator to read further values of the underlying expression. If the value is
028: * not read to completion (for example, if the first user did exists($expr), then the
029: * Input Iterator is left positioned where this user abandoned it. The next user will read
030: * any values left in the reservoir by the first user, and then pick up iterating the
031: * base expression where the first user left off. Eventually, all the values of the
032: * expression will find their way into the reservoir, and future users simply iterate
033: * over the reservoir contents. Alternatively, of course, the values may be left unread.</p>
034: * <p/>
035: * <p>Delayed evaluation is used only for expressions with a static type that allows
036: * more than one item, so the evaluateItem() method will not normally be used, but it is
037: * supported for completeness.</p>
038: * <p/>
039: * <p>The expression may depend on local variables and on the context item; these values
040: * are held in the saved XPathContext object that is kept as part of the Closure, and they
041: * will always be read from that object. The expression may also depend on global variables;
042: * these are unchanging, so they can be read from the Bindery in the normal way. Expressions
043: * that depend on other contextual information, for example the values of position(), last(),
044: * current(), current-group(), should not be evaluated using this mechanism: they should
045: * always be evaluated eagerly. This means that the Closure does not need to keep a copy
046: * of these context variables.</p>
047: */
048:
049: public class MemoClosure extends Closure {
050:
051: private Item[] reservoir = null;
052: private int used;
053: protected int state;
054:
055: // State in which no items have yet been read
056: private static final int UNREAD = 0;
057:
058: // State in which zero or more items are in the reservoir and it is not known
059: // whether more items exist
060: private static final int MAYBE_MORE = 1;
061:
062: // State in which all the items are in the reservoir
063: private static final int ALL_READ = 3;
064:
065: // State in which we are getting the base iterator. If the closure is called in this state,
066: // it indicates a recursive entry, which is only possible on an error path
067: private static final int BUSY = 4;
068:
069: // State in which we know that the value is an empty sequence
070: protected static final int EMPTY = 5;
071:
072: /**
073: * Constructor should not be called directly, instances should be made using the make() method.
074: */
075:
076: public MemoClosure() {
077: }
078:
079: /**
080: * Evaluate the expression in a given context to return an iterator over a sequence
081: *
082: * @param context the evaluation context. This is ignored; we use the context saved
083: * as part of the Closure instead.
084: */
085:
086: public SequenceIterator iterate(XPathContext context)
087: throws XPathException {
088:
089: switch (state) {
090: case UNREAD:
091: state = BUSY;
092: inputIterator = expression.iterate(savedXPathContext);
093: if (inputIterator instanceof EmptyIterator) {
094: state = EMPTY;
095: return inputIterator;
096: }
097: // TODO: following optimization looks OK, but it throws func20 into an infinite loop
098: // if (inputIterator instanceof GroundedIterator) {
099: // state = UNREAD;
100: // return inputIterator.getAnother();
101: // }
102: reservoir = new Item[50];
103: used = 0;
104: state = MAYBE_MORE;
105: return new ProgressiveIterator();
106:
107: case MAYBE_MORE:
108: return new ProgressiveIterator();
109:
110: case ALL_READ:
111: return new ArrayIterator(reservoir, 0, used);
112:
113: case BUSY:
114: // this indicates a recursive entry, probably on an error path while printing diagnostics
115: throw new DynamicError(
116: "Attempt to access a lazily-evaluated variable while it is being evaluated");
117:
118: case EMPTY:
119: return EmptyIterator.getInstance();
120:
121: default:
122: throw new IllegalStateException("Unknown iterator state");
123:
124: }
125: }
126:
127: /**
128: * Process the instruction, without returning any tail calls
129: *
130: * @param context The dynamic context, giving access to the current node,
131: * the current variables, etc.
132: */
133:
134: public void process(XPathContext context) throws XPathException {
135: // To evaluate the closure in push mode, we need to use the original context of the
136: // expression for everything except the current output destination, which is taken from the
137: // context supplied at evaluation time
138: if (state == EMPTY) {
139: return; // we know there is nothing to do
140: }
141: if (reservoir != null) {
142: SequenceIterator iter = iterate(context);
143: SequenceReceiver out = context.getReceiver();
144: while (true) {
145: Item it = iter.next();
146: if (it == null)
147: break;
148: out.append(it, 0, NodeInfo.ALL_NAMESPACES);
149: }
150: } else {
151: Controller controller = context.getController();
152: XPathContext c2 = savedXPathContext.newMinorContext();
153: //c2.setOrigin(this);
154: // Fork the output: one copy goes to a SequenceOutputter which remembers the contents for
155: // use next time the variable is referenced; another copy goes to the current output destination.
156: SequenceOutputter seq = new SequenceOutputter();
157: seq.setPipelineConfiguration(controller
158: .makePipelineConfiguration());
159: seq.open();
160: TeeOutputter tee = new TeeOutputter(context.getReceiver(),
161: seq);
162: tee.setPipelineConfiguration(controller
163: .makePipelineConfiguration());
164: c2.setTemporaryReceiver(tee);
165:
166: expression.process(c2);
167:
168: seq.close();
169: List list = seq.getList();
170: if (list.size() == 0) {
171: state = EMPTY;
172: } else {
173: reservoir = new Item[list.size()];
174: reservoir = (Item[]) list.toArray(reservoir);
175: used = list.size();
176: state = ALL_READ;
177: }
178: // give unwanted stuff to the garbage collector
179: savedXPathContext = null;
180: // inputIterator = null;
181: // expression = null;
182: }
183:
184: }
185:
186: /**
187: * Get the n'th item in the sequence (starting from 0). This is defined for all
188: * SequenceValues, but its real benefits come for a SequenceValue stored extensionally
189: */
190:
191: public Item itemAt(int n) throws XPathException {
192: if (n < 0) {
193: return null;
194: }
195: if (reservoir != null && n < used) {
196: return reservoir[n];
197: }
198: if (state == ALL_READ || state == EMPTY) {
199: return null;
200: }
201: if (state == UNREAD) {
202: return super .itemAt(n);
203: // this will read from the start of the sequence
204: }
205: // We have read some items from the input sequence but not enough. Read as many more as are needed.
206: int diff = n - used + 1;
207: while (diff-- > 0) {
208: Item i = inputIterator.next();
209: if (i == null) {
210: state = ALL_READ;
211: condense();
212: return itemAt(n);
213: }
214: append(i);
215: state = MAYBE_MORE;
216: }
217: return reservoir[n];
218: }
219:
220: /**
221: * Get the length of the sequence
222: */
223:
224: public int getLength() throws XPathException {
225: if (state == ALL_READ) {
226: return used;
227: } else if (state == EMPTY) {
228: return 0;
229: } else {
230: return super .getLength();
231: }
232: }
233:
234: /**
235: * Append an item to the reservoir
236: */
237:
238: private void append(Item item) {
239: if (used >= reservoir.length) {
240: Item[] r2 = new Item[used * 2];
241: System.arraycopy(reservoir, 0, r2, 0, used);
242: reservoir = r2;
243: }
244: reservoir[used++] = item;
245: }
246:
247: /**
248: * Release unused space in the reservoir (provided the amount of unused space is worth reclaiming)
249: */
250:
251: private void condense() {
252: if (reservoir.length - used > 30) {
253: Item[] r2 = new Item[used];
254: System.arraycopy(reservoir, 0, r2, 0, used);
255: reservoir = r2;
256: }
257: // give unwanted stuff to the garbage collector
258: savedXPathContext = null;
259: // inputIterator = null;
260: // expression = null;
261: }
262:
263: /**
264: * Determine whether the contents of the MemoClosure have been fully read
265: */
266:
267: public boolean isFullyRead() {
268: return state == EMPTY || state == ALL_READ;
269: }
270:
271: /**
272: * Return a value containing all the items in the sequence returned by this
273: * SequenceIterator
274: *
275: * @return the corresponding value
276: */
277:
278: public Value materialize() throws XPathException {
279: if (state == ALL_READ) {
280: return new SequenceExtent(reservoir);
281: } else if (state == EMPTY) {
282: return EmptySequence.getInstance();
283: }
284: return new SequenceExtent(iterate(null));
285: }
286:
287: /**
288: * A ProgressiveIterator starts by reading any items already held in the reservoir;
289: * when the reservoir is exhausted, it reads further items from the inputIterator,
290: * copying them into the reservoir as they are read.
291: */
292:
293: public final class ProgressiveIterator implements SequenceIterator,
294: LastPositionFinder, GroundedIterator {
295:
296: int position = -1; // zero-based position in the reservoir of the
297:
298: // item most recently read
299:
300: public ProgressiveIterator() {
301:
302: }
303:
304: public Item next() throws XPathException {
305: if (position == -2) { // means we've already return null once, keep doing so if called again.
306: return null;
307: }
308: if (++position < used) {
309: return reservoir[position];
310: // } else if (state == ALL_READ) {
311: // return null;
312: } else {
313: Item i = inputIterator.next();
314: if (i == null) {
315: state = ALL_READ;
316: condense();
317: //position--; // leave position at last item
318: position = -2;
319: return null;
320: }
321: position = used;
322: append(i);
323: state = MAYBE_MORE;
324: return i;
325: }
326: }
327:
328: public Item current() {
329: if (position < 0) {
330: return null;
331: }
332: return reservoir[position];
333: }
334:
335: public int position() {
336: return position + 1; // return one-based position
337: }
338:
339: public SequenceIterator getAnother() {
340: return new ProgressiveIterator();
341: }
342:
343: /**
344: * Get the last position (that is, the number of items in the sequence)
345: */
346:
347: public int getLastPosition() throws XPathException {
348: if (state == ALL_READ) {
349: return used;
350: } else if (state == EMPTY) {
351: return 0;
352: } else {
353: // save the current position
354: int savePos = position;
355: // fill the reservoir
356: while (true) {
357: Item item = next();
358: if (item == null) {
359: break;
360: }
361: }
362: // reset the current position
363: position = savePos;
364: // return the total number of items
365: return used;
366: }
367: }
368:
369: /**
370: * Return a value containing all the items in the sequence returned by this
371: * SequenceIterator
372: *
373: * @return the corresponding value
374: */
375:
376: public Value materialize() throws XPathException {
377: if (state == ALL_READ) {
378: return new SequenceExtent(reservoir);
379: } else if (state == EMPTY) {
380: return EmptySequence.getInstance();
381: }
382: return new SequenceExtent(iterate(null));
383:
384: }
385:
386: /**
387: * Get properties of this iterator, as a bit-significant integer.
388: *
389: * @return the properties of this iterator. This will be some combination of
390: * properties such as {@link GROUNDED} and {@link LAST_POSITION_FINDER}. It is always
391: * acceptable to return the value zero, indicating that there are no known special properties.
392: */
393:
394: public int getProperties() {
395: if (state == EMPTY || state == ALL_READ) {
396: return GROUNDED | LAST_POSITION_FINDER;
397: } else {
398: return 0;
399: }
400: }
401: }
402:
403: }
404:
405: //
406: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
407: // you may not use this file except in compliance with the License. You may obtain a copy of the
408: // License at http://www.mozilla.org/MPL/
409: //
410: // Software distributed under the License is distributed on an "AS IS" basis,
411: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
412: // See the License for the specific language governing rights and limitations under the License.
413: //
414: // The Original Code is: all this file.
415: //
416: // The Initial Developer of the Original Code is Michael H. Kay.
417: //
418: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
419: //
420: // Contributor(s): none.
421: //
|