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}