package dk.statsbiblioteket.medieplatform.autonomous.iterator; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.AttributeParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.DataFileNodeBeginsParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.DataFileNodeEndsParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.DelegatingTreeIterator; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.NodeBeginsParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.NodeEndParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.ParsingEvent; import dk.statsbiblioteket.medieplatform.autonomous.iterator.common.TreeIterator; import java.io.IOException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.regex.Pattern; /** * The abstract iterator, meant as a common superclass for implementations for different backends. * The system is designed as a series of Iterators. Each iterator represents a non-leaf node in the tree. * It has an ID of type T and iterators for attributes and children. * When iterating, first the attributes are encountered. When all attributes are used, iteration through the children * begin. A delegate Iterator is initialised with the id of the first child. When this iterator runs out, the delegate * is initialised to the second child. * As such, the abstract iterator will often be a long chain of iterators. The "Current" iterator will always be the * iterator with no delegate. * * @param <T> The type of identifier used for the nodes. */ public abstract class AbstractIterator<T> implements DelegatingTreeIterator { private Iterator<DelegatingTreeIterator> childrenIterator; private Iterator<T> attributeIterator; protected final T id; private String dataFilePattern; private DelegatingTreeIterator delegate = null; private boolean done = false; private boolean begun = false; protected AbstractIterator(T id, String dataFilePattern) { this.id = id; this.dataFilePattern = dataFilePattern; } @Override public final boolean hasNext() { // We are done with this node and all subnodes. return !done; } /** * This iterator iterates over the current node. It lists first all attributes of the current node and then begins * iterating over the child nodes. It has the side effect that once it is finished iterating over attributes, it * will create the first delegate (ie first child node to be iterated over). The delegate is set back to null when * the * last child has been read. Therefore calls to this method will have the side effect of changing subsequent calls * to * getDelegate() from null to non-null, and back to null again at various points in the iteration cycle. * * @return the next ParsingEvent * @throws NoSuchElementException if we are already finished iterating this object. */ @Override public final ParsingEvent next() { if (!hasNext()) {//general catch-all, we know there is something to come from this iterator throw new NoSuchElementException("The iterator is out of objects"); } //So now we must figure out what kind of event is next //We have not sent the "NodeBeginEvent" yet, so get that done before anything else if (!begun) { //!begun implied delegate==null ParsingEvent parsingEvent = createNodeBeginsParsingEvent(); begun = true; return parsingEvent; } //After the NodeBeginsEvent, iterate the AttributeEvents if (getAttributeIterator().hasNext()) { //begun==true implies attributeIterator!=null T attributeID = getAttributeIterator().next(); return makeAttributeEvent(id, attributeID); } //We are now finished iterating the attributes, and we check if we have a delegate //If we have one, forward the request to the delegate if (delegate != null) { //delegate != null implied attributeIterator is empty if (delegate.hasNext()) {//forward the request the delegate return delegate.next(); } else { //The delegate is out of objects, so ditch it delegate = null; } } //If we got to here delegate==null, ie. we either have never had a delegate or we just exhausted the one we had if (getChildrenIterator().hasNext()) { delegate = getChildrenIterator().next(); return delegate.next(); } else { //Okay, so we have exhausted the children iterator also. if (done) { //And we have given a "NodeEndEvent" throw new NoSuchElementException("Iterator for id " + id + " exhausted"); } else { //We have not given the "NodeEndEvent" so return this. done = true; return createNodeEndsParsingEvent(); } } } /** * Utility method to create teh nodeEnds event. A method so that subclasses can override it to return their * own specialisations of this event. In this implementation the location is set to the String representation of * the node id. * * @return a node ends event */ protected NodeEndParsingEvent createNodeEndsParsingEvent() { if (isDataFile(getIdOfNode())) { return new DataFileNodeEndsParsingEvent(getIdOfNode(), id.toString()); } return new NodeEndParsingEvent(getIdOfNode(), id.toString()); } private boolean isDataFile(String idOfNode) { return Pattern.matches(dataFilePattern, idOfNode); } /** * Utility method to create the nodeBegins event. A methods so that subclasses can override it to return their own * specialisations of this event. In this implementation the location is set to the String representation of * the node id. * * @return a node begins event */ protected NodeBeginsParsingEvent createNodeBeginsParsingEvent() { if (isDataFile(getIdOfNode())) { return new DataFileNodeBeginsParsingEvent(getIdOfNode(), id.toString()); } return new NodeBeginsParsingEvent(getIdOfNode(), id.toString()); } /** * Get the children iterator, initilised lazily. * * @return the children iterator */ protected synchronized Iterator<DelegatingTreeIterator> getChildrenIterator() { if (childrenIterator == null) { childrenIterator = initializeChildrenIterator(); } return childrenIterator; } /** * This is a factory method which creates an iterator over all the children of this element. Typically it will * just call a constructor defined in an implementation of this class for each child and return an iterator of the * resulting objects. * * @return the children iterator */ protected abstract Iterator<DelegatingTreeIterator> initializeChildrenIterator(); /** * Get the iterator over attributes of the current element, initializing if needed. * * @return the attribute iterator */ protected synchronized Iterator<T> getAttributeIterator() { if (attributeIterator == null) { try { attributeIterator = initilizeAttributeIterator(); } catch (IOException e) { throw new RuntimeException(e); } } return attributeIterator; } /** * This is a factory method which creates an iterator over all attributes of this element, for example all files * in a directory. * * @return */ protected abstract Iterator<T> initilizeAttributeIterator() throws IOException; /** * Returns an instance of a concrete subclass of AttributeEvent appropriate for this attribute. There could be * different kinds of attribute in a given element and these could be identified and given different behaviours. * * @param nodeID the identifier of the node that the attribute resides in * @param attributeID the identifier of the attribute. * * @return an AttributeParsingEvent */ protected abstract AttributeParsingEvent makeAttributeEvent(T nodeID, T attributeID); protected String getIdOfNode() { return id.toString(); } @Override public void remove() { throw new UnsupportedOperationException("Remove not supported"); } @Override /** * This method recursively follows the chain of delegates down from the node it was called on to the node currently * being processed. It identifies the node currently being processed as being that node which has a delegate, but * whose delegate has no delegate. */ public TreeIterator skipToNextSibling() { if (delegate == null) { //we have no delegate. The only way this can happen is if this method have been called on the //root node. We would never recurse to a node without a delegate. //Return this, as there is not very much else to do return this; } //Okay, so we are not the root node, as we have a delegate. //If our delegate also have a delegate, we know that we are not the current node either if (delegate.getDelegate() != null) { //, so proceed downwards return delegate.skipToNextSibling(); } //Okay, so we know that we have a delegate, and this delegate does not have a delegate. We are in //the correct location //Save our delegate DelegatingTreeIterator oldDelegate = delegate; //disconnect it. If the delegate is null, the children iterator will be next'ed to get a next child as //delegate on the following next operation delegate = null; //reset the disconnected delegate, as we are probably somewhat inside it oldDelegate.reset(); //return it return oldDelegate; } /** Reset this iterator/node, so that iteration from here will start fresh. */ @Override public void reset() { delegate = null; done = false; begun = false; childrenIterator = null; attributeIterator = null; } @Override public final TreeIterator getDelegate() { return delegate; } public String getDataFilePattern() { return dataFilePattern; } }