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 org.eclipse.jgit.dircache.DirCache} as part of a
65   * <code>TreeWalk</code>.
66   * <p>
67   * This is an iterator to adapt a loaded <code>DirCache</code> instance (such as
68   * read from an existing <code>.git/index</code> file) to the tree structure
69   * used by a <code>TreeWalk</code>, making it possible for applications to walk
70   * over any combination of tree objects already in the object database, index
71   * files, or working directories.
72   *
73   * @see org.eclipse.jgit.treewalk.TreeWalk
74   */
75  public class DirCacheIterator extends AbstractTreeIterator {
76  	/** Byte array holding ".gitattributes" string */
77  	private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES
78  			.getBytes();
79  
80  	/** The cache this iterator was created to walk. */
81  	protected final DirCache cache;
82  
83  	/** The tree this iterator is walking. */
84  	private final DirCacheTree tree;
85  
86  	/** First position in this tree. */
87  	private final int treeStart;
88  
89  	/** Last position in this tree. */
90  	private final int treeEnd;
91  
92  	/** Special buffer to hold the ObjectId of {@link #currentSubtree}. */
93  	private final byte[] subtreeId;
94  
95  	/** Index of entry within {@link #cache}. */
96  	protected int ptr;
97  
98  	/** Next subtree to consider within {@link #tree}. */
99  	private int nextSubtreePos;
100 
101 	/** The current file entry from {@link #cache}. */
102 	protected DirCacheEntry currentEntry;
103 
104 	/** The subtree containing {@link #currentEntry} if this is first entry. */
105 	protected DirCacheTree currentSubtree;
106 
107 	/**
108 	 * Create a new iterator for an already loaded DirCache instance.
109 	 * <p>
110 	 * The iterator implementation may copy part of the cache's data during
111 	 * construction, so the cache must be read in prior to creating the
112 	 * iterator.
113 	 *
114 	 * @param dc
115 	 *            the cache to walk. It must be already loaded into memory.
116 	 */
117 	public DirCacheIterator(DirCache dc) {
118 		cache = dc;
119 		tree = dc.getCacheTree(true);
120 		treeStart = 0;
121 		treeEnd = tree.getEntrySpan();
122 		subtreeId = new byte[Constants.OBJECT_ID_LENGTH];
123 		if (!eof())
124 			parseEntry();
125 	}
126 
127 	DirCacheIterator(DirCacheIterator p, DirCacheTree dct) {
128 		super(p, p.path, p.pathLen + 1);
129 		cache = p.cache;
130 		tree = dct;
131 		treeStart = p.ptr;
132 		treeEnd = treeStart + tree.getEntrySpan();
133 		subtreeId = p.subtreeId;
134 		ptr = p.ptr;
135 		parseEntry();
136 	}
137 
138 	/** {@inheritDoc} */
139 	@Override
140 	public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
141 			throws IncorrectObjectTypeException, IOException {
142 		if (currentSubtree == null)
143 			throw new IncorrectObjectTypeException(getEntryObjectId(),
144 					Constants.TYPE_TREE);
145 		return new DirCacheIterator(this, currentSubtree);
146 	}
147 
148 	/** {@inheritDoc} */
149 	@Override
150 	public EmptyTreeIterator createEmptyTreeIterator() {
151 		final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
152 		System.arraycopy(path, 0, n, 0, pathLen);
153 		n[pathLen] = '/';
154 		return new EmptyTreeIterator(this, n, pathLen + 1);
155 	}
156 
157 	/** {@inheritDoc} */
158 	@Override
159 	public boolean hasId() {
160 		if (currentSubtree != null)
161 			return currentSubtree.isValid();
162 		return currentEntry != null;
163 	}
164 
165 	/** {@inheritDoc} */
166 	@Override
167 	public byte[] idBuffer() {
168 		if (currentSubtree != null)
169 			return currentSubtree.isValid() ? subtreeId : zeroid;
170 		if (currentEntry != null)
171 			return currentEntry.idBuffer();
172 		return zeroid;
173 	}
174 
175 	/** {@inheritDoc} */
176 	@Override
177 	public int idOffset() {
178 		if (currentSubtree != null)
179 			return 0;
180 		if (currentEntry != null)
181 			return currentEntry.idOffset();
182 		return 0;
183 	}
184 
185 	/** {@inheritDoc} */
186 	@Override
187 	public void reset() {
188 		if (!first()) {
189 			ptr = treeStart;
190 			nextSubtreePos = 0;
191 			currentEntry = null;
192 			currentSubtree = null;
193 			if (!eof())
194 				parseEntry();
195 		}
196 	}
197 
198 	/** {@inheritDoc} */
199 	@Override
200 	public boolean first() {
201 		return ptr == treeStart;
202 	}
203 
204 	/** {@inheritDoc} */
205 	@Override
206 	public boolean eof() {
207 		return ptr == treeEnd;
208 	}
209 
210 	/** {@inheritDoc} */
211 	@Override
212 	public void next(int delta) {
213 		while (--delta >= 0) {
214 			if (currentSubtree != null)
215 				ptr += currentSubtree.getEntrySpan();
216 			else
217 				ptr++;
218 			if (eof())
219 				break;
220 			parseEntry();
221 		}
222 	}
223 
224 	/** {@inheritDoc} */
225 	@Override
226 	public void back(int delta) {
227 		while (--delta >= 0) {
228 			if (currentSubtree != null)
229 				nextSubtreePos--;
230 			ptr--;
231 			parseEntry(false);
232 			if (currentSubtree != null)
233 				ptr -= currentSubtree.getEntrySpan() - 1;
234 		}
235 	}
236 
237 	private void parseEntry() {
238 		parseEntry(true);
239 	}
240 
241 	private void parseEntry(boolean forward) {
242 		currentEntry = cache.getEntry(ptr);
243 		final byte[] cep = currentEntry.path;
244 
245 		if (!forward) {
246 			if (nextSubtreePos > 0) {
247 				final DirCacheTree p = tree.getChild(nextSubtreePos - 1);
248 				if (p.contains(cep, pathOffset, cep.length)) {
249 					nextSubtreePos--;
250 					currentSubtree = p;
251 				}
252 			}
253 		}
254 		if (nextSubtreePos != tree.getChildCount()) {
255 			final DirCacheTree s = tree.getChild(nextSubtreePos);
256 			if (s.contains(cep, pathOffset, cep.length)) {
257 				// The current position is the first file of this subtree.
258 				// Use the subtree instead as the current position.
259 				//
260 				currentSubtree = s;
261 				nextSubtreePos++;
262 
263 				if (s.isValid())
264 					s.getObjectId().copyRawTo(subtreeId, 0);
265 				mode = FileMode.TREE.getBits();
266 				path = cep;
267 				pathLen = pathOffset + s.nameLength();
268 				return;
269 			}
270 		}
271 
272 		// The current position is a file/symlink/gitlink so we
273 		// do not have a subtree located here.
274 		//
275 		mode = currentEntry.getRawMode();
276 		path = cep;
277 		pathLen = cep.length;
278 		currentSubtree = null;
279 		// Checks if this entry is a .gitattributes file
280 		if (RawParseUtils.match(path, pathOffset, DOT_GIT_ATTRIBUTES_BYTES) == path.length)
281 			attributesNode = new LazyLoadingAttributesNode(
282 					currentEntry.getObjectId());
283 	}
284 
285 	/**
286 	 * Get the DirCacheEntry for the current file.
287 	 *
288 	 * @return the current cache entry, if this iterator is positioned on a
289 	 *         non-tree.
290 	 */
291 	public DirCacheEntry getDirCacheEntry() {
292 		return currentSubtree == null ? currentEntry : null;
293 	}
294 
295 	/**
296 	 * Retrieves the {@link org.eclipse.jgit.attributes.AttributesNode} for the
297 	 * current entry.
298 	 *
299 	 * @param reader
300 	 *            {@link org.eclipse.jgit.lib.ObjectReader} used to parse the
301 	 *            .gitattributes entry.
302 	 * @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
303 	 *         current entry.
304 	 * @throws java.io.IOException
305 	 * @since 3.7
306 	 */
307 	public AttributesNode getEntryAttributesNode(ObjectReader reader)
308 			throws IOException {
309 		if (attributesNode instanceof LazyLoadingAttributesNode)
310 			attributesNode = ((LazyLoadingAttributesNode) attributesNode)
311 					.load(reader);
312 		return attributesNode;
313 	}
314 
315 	/**
316 	 * {@link AttributesNode} implementation that provides lazy loading
317 	 * facilities.
318 	 */
319 	private static class LazyLoadingAttributesNode extends AttributesNode {
320 		final ObjectId objectId;
321 
322 		LazyLoadingAttributesNode(ObjectId objectId) {
323 			super(Collections.<AttributesRule> emptyList());
324 			this.objectId = objectId;
325 
326 		}
327 
328 		AttributesNode load(ObjectReader reader) throws IOException {
329 			AttributesNode r = new AttributesNode();
330 			ObjectLoader loader = reader.open(objectId);
331 			if (loader != null) {
332 				try (InputStream in = loader.openStream()) {
333 					r.parse(in);
334 				}
335 			}
336 			return r.getRules().isEmpty() ? null : r;
337 		}
338 	}
339 
340 }