001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 *
012 * $Id$
013 */
014
015package org.nuxeo.ecm.core.api;
016
017import java.util.Iterator;
018import java.util.LinkedList;
019import java.util.NoSuchElementException;
020import java.util.Queue;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025/**
026 * An iterator over a tree of documents
027 * <p>
028 * The tree is traversed from top to bottom and left to right.
029 * <p>
030 * TODO: move this in an utility package
031 *
032 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
033 */
034public class DocumentTreeIterator implements Iterator<DocumentModel> {
035
036    private static final Log log = LogFactory.getLog(DocumentTreeIterator.class);
037
038    /**
039     * The document manager session.
040     */
041    protected final CoreSession session;
042
043    /**
044     * Root document.
045     */
046    protected final DocumentModel root;
047
048    /**
049     * The current sequence.
050     */
051    protected Iterator<DocumentModel> sequence;
052
053    /**
054     * The sequence queue.
055     */
056    protected final Queue<Iterator<DocumentModel>> queue = new LinkedList<Iterator<DocumentModel>>();
057
058    /**
059     * Creates the iterator given the tree root.
060     */
061    public DocumentTreeIterator(CoreSession session, DocumentModel root) {
062        this(session, root, false);
063    }
064
065    public DocumentTreeIterator(CoreSession session, DocumentModel root, boolean excludeRoot) {
066        this.root = root;
067        this.session = session;
068        if (excludeRoot) {
069            sequence = session.getChildrenIterator(root.getRef(), null, null, null);
070        } else {
071            sequence = new OneDocSequence(root);
072        }
073    }
074
075    /**
076     * Gets next non empty sequence from queue.
077     * <p>
078     * This will remove from the queue all traversed sequences (the empty ones and the first not empty sequence found).
079     *
080     * @return the first non empty sequence or null if no one was found
081     */
082    protected Iterator<DocumentModel> getNextNonEmptySequence() {
083        while (true) {
084            Iterator<DocumentModel> seq = queue.poll();
085            if (seq == null) {
086                return null;
087            } else if (seq.hasNext()) {
088                return seq;
089            }
090        }
091    }
092
093    @Override
094    public boolean hasNext() {
095        if (sequence == null || !sequence.hasNext()) {
096            // no current valid sequence
097            sequence = getNextNonEmptySequence();
098            if (sequence == null) {
099                return false;
100            }
101        }
102        // we have a sequence to iterate over
103        return true;
104    }
105
106    @Override
107    public DocumentModel next() {
108        // satisfy iterator contract - throw an exception if no more elements to
109        // iterate
110        if (!hasNext()) {
111            throw new NoSuchElementException("no more documents to iterate over");
112        }
113        // we have a non empty sequence to iterate over
114        DocumentModel doc = sequence.next();
115        if (doc.isFolder()) {
116            // TODO: load children after the document was traversed
117            // update the sequence queue with children from this folder
118            queue.add(session.getChildrenIterator(doc.getRef(), null, null, null));
119        }
120        return doc;
121    }
122
123    @Override
124    public void remove() {
125        throw new UnsupportedOperationException("remove is not yet supported");
126    }
127
128    /**
129     * A sequence of a single doc.
130     *
131     * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
132     */
133    static class OneDocSequence implements Iterator<DocumentModel> {
134        final DocumentModel doc;
135
136        boolean hasNext = true;
137
138        OneDocSequence(DocumentModel doc) {
139            this.doc = doc;
140        }
141
142        @Override
143        public boolean hasNext() {
144            return hasNext;
145        }
146
147        @Override
148        public DocumentModel next() {
149            if (doc == null) {
150                throw new NoSuchElementException("no more documents to iterate over");
151            }
152            hasNext = false;
153            return doc;
154        }
155
156        @Override
157        public void remove() {
158            throw new UnsupportedOperationException("remove is not yet supported");
159        }
160    }
161
162    /**
163     * Resets the iterator back to the tree root and clear any cached data.
164     */
165    public void reset() {
166        sequence = new OneDocSequence(root);
167        queue.clear();
168    }
169
170}