View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.dircache;
46  
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.util.Collections;
50  
51  import org.eclipse.jgit.attributes.AttributesNode;
52  import org.eclipse.jgit.attributes.AttributesRule;
53  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
54  import org.eclipse.jgit.lib.Constants;
55  import org.eclipse.jgit.lib.FileMode;
56  import org.eclipse.jgit.lib.ObjectId;
57  import org.eclipse.jgit.lib.ObjectLoader;
58  import org.eclipse.jgit.lib.ObjectReader;
59  import org.eclipse.jgit.treewalk.AbstractTreeIterator;
60  import org.eclipse.jgit.treewalk.EmptyTreeIterator;
61  import org.eclipse.jgit.util.RawParseUtils;
62  
63  /**
64   * Iterate a {@link DirCache} as part of a <code>TreeWalk</code>.
65   * <p>
66   * This is an iterator to adapt a loaded <code>DirCache</code> instance (such as
67   * read from an existing <code>.git/index</code> file) to the tree structure
68   * used by a <code>TreeWalk</code>, making it possible for applications to walk
69   * over any combination of tree objects already in the object database, index
70   * files, or working directories.
71   *
72   * @see org.eclipse.jgit.treewalk.TreeWalk
73   */
74  public class DirCacheIterator extends AbstractTreeIterator {
75  	/** Byte array holding ".gitattributes" string */
76  	private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES
77  			.getBytes();
78  
79  	/** The cache this iterator was created to walk. */
80  	protected final DirCache cache;
81  
82  	/** The tree this iterator is walking. */
83  	private final DirCacheTree tree;
84  
85  	/** First position in this tree. */
86  	private final int treeStart;
87  
88  	/** Last position in this tree. */
89  	private final int treeEnd;
90  
91  	/** Special buffer to hold the ObjectId of {@link #currentSubtree}. */
92  	private final byte[] subtreeId;
93  
94  	/** Index of entry within {@link #cache}. */
95  	protected int ptr;
96  
97  	/** Next subtree to consider within {@link #tree}. */
98  	private int nextSubtreePos;
99  
100 	/** The current file entry from {@link #cache}. */
101 	protected DirCacheEntry currentEntry;
102 
103 	/** The subtree containing {@link #currentEntry} if this is first entry. */
104 	protected DirCacheTree currentSubtree;
105 
106 	/**
107 	 * Create a new iterator for an already loaded DirCache instance.
108 	 * <p>
109 	 * The iterator implementation may copy part of the cache's data during
110 	 * construction, so the cache must be read in prior to creating the
111 	 * iterator.
112 	 *
113 	 * @param dc
114 	 *            the cache to walk. It must be already loaded into memory.
115 	 */
116 	public DirCacheIterator(final DirCache dc) {
117 		cache = dc;
118 		tree = dc.getCacheTree(true);
119 		treeStart = 0;
120 		treeEnd = tree.getEntrySpan();
121 		subtreeId = new byte[Constants.OBJECT_ID_LENGTH];
122 		if (!eof())
123 			parseEntry();
124 	}
125 
126 	DirCacheIterator(final DirCacheIterator p, final DirCacheTree dct) {
127 		super(p, p.path, p.pathLen + 1);
128 		cache = p.cache;
129 		tree = dct;
130 		treeStart = p.ptr;
131 		treeEnd = treeStart + tree.getEntrySpan();
132 		subtreeId = p.subtreeId;
133 		ptr = p.ptr;
134 		parseEntry();
135 	}
136 
137 	@Override
138 	public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
139 			throws IncorrectObjectTypeException, IOException {
140 		if (currentSubtree == null)
141 			throw new IncorrectObjectTypeException(getEntryObjectId(),
142 					Constants.TYPE_TREE);
143 		return new DirCacheIterator(this, currentSubtree);
144 	}
145 
146 	@Override
147 	public EmptyTreeIterator createEmptyTreeIterator() {
148 		final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
149 		System.arraycopy(path, 0, n, 0, pathLen);
150 		n[pathLen] = '/';
151 		return new EmptyTreeIterator(this, n, pathLen + 1);
152 	}
153 
154 	@Override
155 	public boolean hasId() {
156 		if (currentSubtree != null)
157 			return currentSubtree.isValid();
158 		return currentEntry != null;
159 	}
160 
161 	@Override
162 	public byte[] idBuffer() {
163 		if (currentSubtree != null)
164 			return currentSubtree.isValid() ? subtreeId : zeroid;
165 		if (currentEntry != null)
166 			return currentEntry.idBuffer();
167 		return zeroid;
168 	}
169 
170 	@Override
171 	public int idOffset() {
172 		if (currentSubtree != null)
173 			return 0;
174 		if (currentEntry != null)
175 			return currentEntry.idOffset();
176 		return 0;
177 	}
178 
179 	@Override
180 	public void reset() {
181 		if (!first()) {
182 			ptr = treeStart;
183 			nextSubtreePos = 0;
184 			currentEntry = null;
185 			currentSubtree = null;
186 			if (!eof())
187 				parseEntry();
188 		}
189 	}
190 
191 	@Override
192 	public boolean first() {
193 		return ptr == treeStart;
194 	}
195 
196 	@Override
197 	public boolean eof() {
198 		return ptr == treeEnd;
199 	}
200 
201 	@Override
202 	public void next(int delta) {
203 		while (--delta >= 0) {
204 			if (currentSubtree != null)
205 				ptr += currentSubtree.getEntrySpan();
206 			else
207 				ptr++;
208 			if (eof())
209 				break;
210 			parseEntry();
211 		}
212 	}
213 
214 	@Override
215 	public void back(int delta) {
216 		while (--delta >= 0) {
217 			if (currentSubtree != null)
218 				nextSubtreePos--;
219 			ptr--;
220 			parseEntry(false);
221 			if (currentSubtree != null)
222 				ptr -= currentSubtree.getEntrySpan() - 1;
223 		}
224 	}
225 
226 	private void parseEntry() {
227 		parseEntry(true);
228 	}
229 
230 	private void parseEntry(boolean forward) {
231 		currentEntry = cache.getEntry(ptr);
232 		final byte[] cep = currentEntry.path;
233 
234 		if (!forward) {
235 			if (nextSubtreePos > 0) {
236 				final DirCacheTree p = tree.getChild(nextSubtreePos - 1);
237 				if (p.contains(cep, pathOffset, cep.length)) {
238 					nextSubtreePos--;
239 					currentSubtree = p;
240 				}
241 			}
242 		}
243 		if (nextSubtreePos != tree.getChildCount()) {
244 			final DirCacheTree s = tree.getChild(nextSubtreePos);
245 			if (s.contains(cep, pathOffset, cep.length)) {
246 				// The current position is the first file of this subtree.
247 				// Use the subtree instead as the current position.
248 				//
249 				currentSubtree = s;
250 				nextSubtreePos++;
251 
252 				if (s.isValid())
253 					s.getObjectId().copyRawTo(subtreeId, 0);
254 				mode = FileMode.TREE.getBits();
255 				path = cep;
256 				pathLen = pathOffset + s.nameLength();
257 				return;
258 			}
259 		}
260 
261 		// The current position is a file/symlink/gitlink so we
262 		// do not have a subtree located here.
263 		//
264 		mode = currentEntry.getRawMode();
265 		path = cep;
266 		pathLen = cep.length;
267 		currentSubtree = null;
268 		// Checks if this entry is a .gitattributes file
269 		if (RawParseUtils.match(path, pathOffset, DOT_GIT_ATTRIBUTES_BYTES) == path.length)
270 			attributesNode = new LazyLoadingAttributesNode(
271 					currentEntry.getObjectId());
272 	}
273 
274 	/**
275 	 * Get the DirCacheEntry for the current file.
276 	 *
277 	 * @return the current cache entry, if this iterator is positioned on a
278 	 *         non-tree.
279 	 */
280 	public DirCacheEntry getDirCacheEntry() {
281 		return currentSubtree == null ? currentEntry : null;
282 	}
283 
284 	/**
285 	 * Retrieves the {@link AttributesNode} for the current entry.
286 	 *
287 	 * @param reader
288 	 *            {@link ObjectReader} used to parse the .gitattributes entry.
289 	 * @return {@link AttributesNode} for the current entry.
290 	 * @throws IOException
291 	 * @since 3.7
292 	 */
293 	public AttributesNode getEntryAttributesNode(ObjectReader reader)
294 			throws IOException {
295 		if (attributesNode instanceof LazyLoadingAttributesNode)
296 			attributesNode = ((LazyLoadingAttributesNode) attributesNode)
297 					.load(reader);
298 		return attributesNode;
299 	}
300 
301 	/**
302 	 * {@link AttributesNode} implementation that provides lazy loading
303 	 * facilities.
304 	 */
305 	private static class LazyLoadingAttributesNode extends AttributesNode {
306 		final ObjectId objectId;
307 
308 		LazyLoadingAttributesNode(ObjectId objectId) {
309 			super(Collections.<AttributesRule> emptyList());
310 			this.objectId = objectId;
311 
312 		}
313 
314 		AttributesNode load(ObjectReader reader) throws IOException {
315 			AttributesNode r = new AttributesNode();
316 			ObjectLoader loader = reader.open(objectId);
317 			if (loader != null) {
318 				InputStream in = loader.openStream();
319 				try {
320 					r.parse(in);
321 				} finally {
322 					in.close();
323 				}
324 			}
325 			return r.getRules().isEmpty() ? null : r;
326 		}
327 	}
328 
329 }