All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.value.MemoClosure Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.value;

import net.sf.saxon.Controller;
import net.sf.saxon.event.SequenceOutputter;
import net.sf.saxon.event.SequenceReceiver;
import net.sf.saxon.event.TeeOutputter;
import net.sf.saxon.expr.LastPositionFinder;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.XPathContextMajor;
import net.sf.saxon.expr.parser.ExplicitLocation;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.tree.iter.GroundedIterator;
import net.sf.saxon.tree.iter.ListIterator;
import net.sf.saxon.tree.iter.SingletonIterator;

import java.util.ArrayList;
import java.util.List;

/**
 * A MemoClosure represents a value that has not yet been evaluated: the value is represented
 * by an expression, together with saved values of all the context variables that the
 * expression depends on.
 * 

*

The MemoClosure is designed for use when the value is only read several times. The * value is saved on the first evaluation and remembered for later use.

*

*

The MemoClosure maintains a reservoir containing those items in the value that have * already been read. When a new iterator is requested to read the value, this iterator * first examines and returns any items already placed in the reservoir by previous * users of the MemoClosure. When the reservoir is exhausted, it then uses an underlying * Input Iterator to read further values of the underlying expression. If the value is * not read to completion (for example, if the first user did exists($expr), then the * Input Iterator is left positioned where this user abandoned it. The next user will read * any values left in the reservoir by the first user, and then pick up iterating the * base expression where the first user left off. Eventually, all the values of the * expression will find their way into the reservoir, and future users simply iterate * over the reservoir contents. Alternatively, of course, the values may be left unread.

*

*

Delayed evaluation is used only for expressions with a static type that allows * more than one item, so the evaluateItem() method will not normally be used, but it is * supported for completeness.

*

*

The expression may depend on local variables and on the context item; these values * are held in the saved XPathContext object that is kept as part of the Closure, and they * will always be read from that object. The expression may also depend on global variables; * these are unchanging, so they can be read from the Bindery in the normal way. Expressions * that depend on other contextual information, for example the values of position(), last(), * current(), current-group(), should not be evaluated using this mechanism: they should * always be evaluated eagerly. This means that the Closure does not need to keep a copy * of these context variables.

*

*

In Saxon-EE, a for-each loop can be multithreaded. If a variable declared outside * the loop is evaluated as a MemoClosure, then a reference to the variable within the * loop can result in concurrent attempts to evaluate the variable incrementally. This * is prevented by synchronizing the evaluation methods.

*/ public class MemoClosure extends Closure { /*@Nullable*/ private List reservoir = null; protected int state; // State in which no items have yet been read private static final int UNREAD = 0; // State in which zero or more items are in the reservoir and it is not known // whether more items exist private static final int MAYBE_MORE = 1; // State in which all the items are in the reservoir private static final int ALL_READ = 3; // State in which we are getting the base iterator. If the closure is called in this state, // it indicates a recursive entry, which is only possible on an error path private static final int BUSY = 4; // State in which we know that the value is an empty sequence protected static final int EMPTY = 5; /** * Constructor should not be called directly, instances should be made using the make() method. */ //private static int closureCount = 0; public MemoClosure() { //System.err.println("************** Creating MemoClosure " + closureCount); //closureCount++; if ((closureCount % 1000) == 0) System.err.println("MemoClosures: " + closureCount); } /** * Evaluate the expression in a given context to return an iterator over a sequence */ /*@NotNull*/ public synchronized SequenceIterator iterate() throws XPathException { switch (state) { case UNREAD: state = BUSY; SequenceIterator in = expression.iterate(savedXPathContext); if (in instanceof EmptyIterator) { state = EMPTY; return inputIterator = EmptyIterator.emptyIterator(); } else if ((in.getProperties() & SequenceIterator.GROUNDED) != 0) { GroundedValue value = ((GroundedIterator)in).materialize(); reservoir = new ArrayList(value.getLength()); Item it; while ((it = in.next()) != null) { reservoir.add(it); } state = ALL_READ; return new ListIterator(reservoir); } else { inputIterator = in; reservoir = new ArrayList(50); state = MAYBE_MORE; return new ProgressiveIterator(); } case MAYBE_MORE: return new ProgressiveIterator(); case ALL_READ: switch (reservoir.size()) { case 0: state = EMPTY; return EmptyIterator.emptyIterator(); case 1: return SingletonIterator.makeIterator(reservoir.get(0)); default: return new ListIterator(reservoir); } case BUSY: // recursive entry: can happen if there is a circularity involving variable and function definitions // Can also happen if variable evaluation is attempted in a debugger, hence the cautious message XPathException de = new XPathException("Attempt to access a variable while it is being evaluated"); de.setErrorCode("XTDE0640"); //de.setXPathContext(context); throw de; case EMPTY: return EmptyIterator.emptyIterator(); default: throw new IllegalStateException("Unknown iterator state"); } } /** * Process the expression by writing the value to the current Receiver * * @param context The dynamic context, giving access to the current node, * the current variables, etc. */ public synchronized void process(/*@NotNull*/ XPathContext context) throws XPathException { // To evaluate the closure in push mode, we need to use the original context of the // expression for everything except the current output destination, which is taken from the // context supplied at evaluation time if (state == EMPTY) { return; // we know there is nothing to do } else if (state == BUSY) { // recursive entry: can happen if there is a circularity involving variable and function definitions XPathException de = new XPathException("Attempt to access a variable while it is being evaluated"); de.setErrorCode("XTDE0640"); de.setXPathContext(context); throw de; } if (reservoir != null) { SequenceIterator iter = iterate(); SequenceReceiver out = context.getReceiver(); Item it; while ((it = iter.next()) != null) { out.append(it, ExplicitLocation.UNKNOWN_LOCATION, NodeInfo.ALL_NAMESPACES); } } else { state = BUSY; Controller controller = context.getController(); XPathContextMajor c2 = savedXPathContext.newContext(); // Fork the output: one copy goes to a SequenceOutputter which remembers the contents for // use next time the variable is referenced; another copy goes to the current output destination. SequenceOutputter seq = controller.allocateSequenceOutputter(20); seq.open(); TeeOutputter tee = new TeeOutputter(context.getReceiver(), seq); tee.setPipelineConfiguration(controller.makePipelineConfiguration()); c2.setReceiver(tee); c2.setTemporaryOutputState(StandardNames.XSL_VARIABLE); expression.process(c2); seq.close(); List list = seq.getList(); if (list.isEmpty()) { state = EMPTY; } else { reservoir = list; state = ALL_READ; } // give unwanted stuff to the garbage collector savedXPathContext = null; seq.reset(); } } /** * Get the n'th item in the sequence (starting from 0). This is defined for all * SequenceValues, but its real benefits come for a SequenceValue stored extensionally */ /** * Append an item to the reservoir * * @param item the item to be added */ private void append(Item item) { assert reservoir != null; reservoir.add(item); } /** * Release unused space in the reservoir (provided the amount of unused space is worth reclaiming) */ private void condense() { if (reservoir != null) { ((ArrayList) reservoir).trimToSize(); } // give unwanted stuff to the garbage collector savedXPathContext = null; // inputIterator = null; // expression = null; } /** * Determine whether the contents of the MemoClosure have been fully read * * @return true if the contents have been fully read */ public boolean isFullyRead() { return state == EMPTY || state == ALL_READ; } /** * Get the n'th item in the sequence, zero-based * * @param n the index of the required item, starting from zero * @return the item at the relevant position * @throws XPathException if an error occurs reading the base iterator */ public synchronized Item itemAt(int n) throws XPathException { if (n < 0) { return null; } if (reservoir != null && n < reservoir.size()) { return reservoir.get(n); } if (state == ALL_READ || state == EMPTY) { return null; } if (state == UNREAD) { if (inputIterator == null) { state = BUSY; inputIterator = expression.iterate(savedXPathContext); } Item item = inputIterator.next(); if (item == null) { state = EMPTY; return null; } else { state = MAYBE_MORE; reservoir = new ArrayList(50); append(item); if (n == 0) { return item; } } } // We have read some items from the input sequence but not enough. Read as many more as are needed. int diff = n - reservoir.size() + 1; while (diff-- > 0) { Item i = inputIterator.next(); if (i == null) { state = ALL_READ; condense(); return null; } append(i); state = MAYBE_MORE; } //noinspection ConstantConditions return reservoir.get(n); } /** * Return a value containing all the items in the sequence returned by this * SequenceIterator * * @return the corresponding value * @throws net.sf.saxon.trans.XPathException * if a failure occurs reading the input */ /*@Nullable*/ public GroundedValue reduce() throws XPathException { if (state == ALL_READ) { return new SequenceExtent(reservoir); } else if (state == EMPTY) { return EmptySequence.getInstance(); } return new SequenceExtent(iterate()); } /** * A ProgressiveIterator starts by reading any items already held in the reservoir; * when the reservoir is exhausted, it reads further items from the inputIterator, * copying them into the reservoir as they are read. */ public final class ProgressiveIterator implements SequenceIterator, LastPositionFinder, GroundedIterator { int position = -1; // zero-based position in the reservoir of the // item most recently read /** * Create a ProgressiveIterator */ public ProgressiveIterator() { } public MemoClosure getMemoClosure() { return MemoClosure.this; } /*@Nullable*/ public Item next() throws XPathException { synchronized (MemoClosure.this) { // synchronized "just in case" the same variable is being referenced and evaluated by code in // multiple threads. The code in LetExpression attempts to ensure that eager evaluation is used // in such cases (because otherwise, the lock acquired here can potentially be held for a long time), // but we synchronize anyway for safety. if (position == -2) { // means we've already returned null once, keep doing so if called again. return null; } if (++position < reservoir.size()) { return reservoir.get(position); } else if (state == ALL_READ) { // someone else has read the input to completion in the meantime position = -2; return null; } else { assert inputIterator != null; Item i = inputIterator.next(); if (i == null) { state = ALL_READ; condense(); position = -2; return null; } position = reservoir.size(); append(i); state = MAYBE_MORE; return i; } } } public void close() { } /*@NotNull*/ public ProgressiveIterator getAnother() { return new ProgressiveIterator(); } /** * Get the last position (that is, the number of items in the sequence) */ public int getLength() throws XPathException { if (state == ALL_READ) { return reservoir.size(); } else if (state == EMPTY) { return 0; } else { // save the current position int savePos = position; // fill the reservoir while (true) { Item item = next(); if (item == null) { break; } } // reset the current position position = savePos; // return the total number of items return reservoir.size(); } } /** * Return a value containing all the items in the sequence returned by this * SequenceIterator * * @return the corresponding value */ /*@Nullable*/ public GroundedValue materialize() throws XPathException { if (state == ALL_READ) { assert reservoir != null; return new SequenceExtent(reservoir); } else if (state == EMPTY) { return EmptySequence.getInstance(); } else { return new SequenceExtent(iterate()); //throw new IllegalStateException("Progressive iterator is not grounded until all items are read"); } } /** * Get properties of this iterator, as a bit-significant integer. * * @return the properties of this iterator. This will be some combination of * properties such as {@link #GROUNDED} and {@link #LAST_POSITION_FINDER}. It is always * acceptable to return the value zero, indicating that there are no known special properties. */ public int getProperties() { // bug 1740 shows that it is better to report the iterator as grounded even though this // may trigger eager evaluation of the underlying sequence. //if (state == EMPTY || state == ALL_READ) { return GROUNDED | LAST_POSITION_FINDER; //} else { // return 0; //} } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy