Expose beginning of iterator indication from AbstractTreeIterator
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / treewalk / WorkingTreeIterator.java
blobc6664f50d1cc62756a9e5620ff23c26cdae2955c
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
21 * written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org.spearce.jgit.treewalk;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.nio.ByteBuffer;
43 import java.nio.CharBuffer;
44 import java.nio.charset.CharacterCodingException;
45 import java.nio.charset.CharsetEncoder;
46 import java.security.MessageDigest;
47 import java.util.Arrays;
48 import java.util.Comparator;
50 import org.spearce.jgit.errors.CorruptObjectException;
51 import org.spearce.jgit.lib.Constants;
52 import org.spearce.jgit.lib.FileMode;
54 /**
55 * Walks a working directory tree as part of a {@link TreeWalk}.
56 * <p>
57 * Most applications will want to use the standard implementation of this
58 * iterator, {@link FileTreeIterator}, as that does all IO through the standard
59 * <code>java.io</code> package. Plugins for a Java based IDE may however wish
60 * to create their own implementations of this class to allow traversal of the
61 * IDE's project space, as well as benefit from any caching the IDE may have.
63 * @see FileTreeIterator
65 public abstract class WorkingTreeIterator extends AbstractTreeIterator {
66 /** An empty entry array, suitable for {@link #init(Entry[])}. */
67 protected static final Entry[] EOF = {};
69 /** Size we perform file IO in if we have to read and hash a file. */
70 private static final int BUFFER_SIZE = 2048;
72 /** The {@link #idBuffer()} for the current entry. */
73 private byte[] contentId;
75 /** Index within {@link #entries} that {@link #contentId} came from. */
76 private int contentIdFromPtr;
78 /** Buffer used to perform {@link #contentId} computations. */
79 private byte[] contentReadBuffer;
81 /** Digest computer for {@link #contentId} computations. */
82 private MessageDigest contentDigest;
84 /** File name character encoder. */
85 private final CharsetEncoder nameEncoder;
87 /** List of entries obtained from the subclass. */
88 private Entry[] entries;
90 /** Total number of entries in {@link #entries} that are valid. */
91 private int entryCnt;
93 /** Current position within {@link #entries}. */
94 private int ptr;
96 /** Create a new iterator with no parent. */
97 protected WorkingTreeIterator() {
98 super();
99 nameEncoder = Constants.CHARSET.newEncoder();
103 * Create a new iterator with no parent and a prefix.
104 * <p>
105 * The prefix path supplied is inserted in front of all paths generated by
106 * this iterator. It is intended to be used when an iterator is being
107 * created for a subsection of an overall repository and needs to be
108 * combined with other iterators that are created to run over the entire
109 * repository namespace.
111 * @param prefix
112 * position of this iterator in the repository tree. The value
113 * may be null or the empty string to indicate the prefix is the
114 * root of the repository. A trailing slash ('/') is
115 * automatically appended if the prefix does not end in '/'.
117 protected WorkingTreeIterator(final String prefix) {
118 super(prefix);
119 nameEncoder = Constants.CHARSET.newEncoder();
123 * Create an iterator for a subtree of an existing iterator.
125 * @param p
126 * parent tree iterator.
128 protected WorkingTreeIterator(final WorkingTreeIterator p) {
129 super(p);
130 nameEncoder = p.nameEncoder;
133 @Override
134 public byte[] idBuffer() {
135 if (contentIdFromPtr == ptr)
136 return contentId;
137 switch (mode & 0170000) {
138 case 0100000: /* normal files */
139 contentIdFromPtr = ptr;
140 return contentId = idBufferBlob(entries[ptr]);
141 case 0120000: /* symbolic links */
142 // Java does not support symbolic links, so we should not
143 // have reached this particular part of the walk code.
145 return zeroid;
146 case 0160000: /* gitlink */
147 // TODO: Support obtaining current HEAD SHA-1 from nested repository
149 return zeroid;
151 return zeroid;
154 private void initializeDigest() {
155 if (contentDigest != null)
156 return;
158 if (parent == null) {
159 contentReadBuffer = new byte[BUFFER_SIZE];
160 contentDigest = Constants.newMessageDigest();
161 } else {
162 final WorkingTreeIterator p = (WorkingTreeIterator) parent;
163 p.initializeDigest();
164 contentReadBuffer = p.contentReadBuffer;
165 contentDigest = p.contentDigest;
169 private static final byte[] digits = { '0', '1', '2', '3', '4', '5', '6',
170 '7', '8', '9' };
172 private static final byte[] hblob = Constants
173 .encodedTypeString(Constants.OBJ_BLOB);
175 private byte[] idBufferBlob(final Entry e) {
176 try {
177 final InputStream is = e.openInputStream();
178 if (is == null)
179 return zeroid;
180 try {
181 initializeDigest();
183 contentDigest.reset();
184 contentDigest.update(hblob);
185 contentDigest.update((byte) ' ');
187 final long blobLength = e.getLength();
188 long sz = blobLength;
189 if (sz == 0) {
190 contentDigest.update((byte) '0');
191 } else {
192 final int bufn = contentReadBuffer.length;
193 int p = bufn;
194 do {
195 contentReadBuffer[--p] = digits[(int) (sz % 10)];
196 sz /= 10;
197 } while (sz > 0);
198 contentDigest.update(contentReadBuffer, p, bufn - p);
200 contentDigest.update((byte) 0);
202 for (;;) {
203 final int r = is.read(contentReadBuffer);
204 if (r <= 0)
205 break;
206 contentDigest.update(contentReadBuffer, 0, r);
207 sz += r;
209 if (sz != blobLength)
210 return zeroid;
211 return contentDigest.digest();
212 } finally {
213 try {
214 is.close();
215 } catch (IOException err2) {
216 // Suppress any error related to closing an input
217 // stream. We don't care, we should not have any
218 // outstanding data to flush or anything like that.
221 } catch (IOException err) {
222 // Can't read the file? Don't report the failure either.
224 return zeroid;
228 @Override
229 public int idOffset() {
230 return 0;
233 @Override
234 public boolean first() {
235 return ptr == 0;
238 @Override
239 public boolean eof() {
240 return ptr == entryCnt;
243 @Override
244 public void next(final int delta) throws CorruptObjectException {
245 ptr += delta;
246 if (!eof())
247 parseEntry();
250 @Override
251 public void back(final int delta) throws CorruptObjectException {
252 ptr -= delta;
253 parseEntry();
256 private void parseEntry() {
257 final Entry e = entries[ptr];
258 mode = e.getMode().getBits();
260 final int nameLen = e.encodedNameLen;
261 while (pathOffset + nameLen > path.length)
262 growPath(pathOffset);
263 System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen);
264 pathLen = pathOffset + nameLen;
268 * Get the byte length of this entry.
270 * @return size of this file, in bytes.
272 public long getEntryLength() {
273 return current().getLength();
277 * Get the last modified time of this entry.
279 * @return last modified time of this file, in milliseconds since the epoch
280 * (Jan 1, 1970 UTC).
282 public long getEntryLastModified() {
283 return current().getLastModified();
286 private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
287 public int compare(final Entry o1, final Entry o2) {
288 final byte[] a = o1.encodedName;
289 final byte[] b = o2.encodedName;
290 final int aLen = o1.encodedNameLen;
291 final int bLen = o2.encodedNameLen;
292 int cPos;
294 for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
295 final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
296 if (cmp != 0)
297 return cmp;
300 if (cPos < aLen) {
301 final int aj = a[cPos] & 0xff;
302 final int lastb = lastPathChar(o2);
303 if (aj < lastb)
304 return -1;
305 else if (aj > lastb)
306 return 1;
307 else if (cPos == aLen - 1)
308 return 0;
309 else
310 return -1;
313 if (cPos < bLen) {
314 final int bk = b[cPos] & 0xff;
315 final int lasta = lastPathChar(o1);
316 if (lasta < bk)
317 return -1;
318 else if (lasta > bk)
319 return 1;
320 else if (cPos == bLen - 1)
321 return 0;
322 else
323 return 1;
326 final int lasta = lastPathChar(o1);
327 final int lastb = lastPathChar(o2);
328 if (lasta < lastb)
329 return -1;
330 else if (lasta > lastb)
331 return 1;
333 if (aLen == bLen)
334 return 0;
335 else if (aLen < bLen)
336 return -1;
337 else
338 return 1;
342 static int lastPathChar(final Entry e) {
343 return e.getMode() == FileMode.TREE ? '/' : '\0';
346 protected void init(final Entry[] list) {
347 // Filter out nulls, . and .. as these are not valid tree entries,
348 // also cache the encoded forms of the path names for efficient use
349 // later on during sorting and iteration.
351 entries = list;
352 int i, o;
354 for (i = 0, o = 0; i < entries.length; i++) {
355 final Entry e = entries[i];
356 if (e == null)
357 continue;
358 final String name = e.getName();
359 if (".".equals(name) || "..".equals(name))
360 continue;
361 if (parent == null && ".git".equals(name))
362 continue;
363 if (i != o)
364 entries[o] = e;
365 e.encodeName(nameEncoder);
366 o++;
368 entryCnt = o;
369 Arrays.sort(entries, 0, entryCnt, ENTRY_CMP);
371 contentIdFromPtr = -1;
372 ptr = 0;
373 if (!eof())
374 parseEntry();
378 * Obtain the current entry from this iterator.
380 * @return the currently selected entry.
382 protected Entry current() {
383 return entries[ptr];
386 /** A single entry within a working directory tree. */
387 protected static abstract class Entry {
388 byte[] encodedName;
390 int encodedNameLen;
392 void encodeName(final CharsetEncoder enc) {
393 final ByteBuffer b;
394 try {
395 b = enc.encode(CharBuffer.wrap(getName()));
396 } catch (CharacterCodingException e) {
397 // This should so never happen.
398 throw new RuntimeException("Unencodeable file: " + getName());
401 encodedNameLen = b.limit();
402 if (b.hasArray() && b.arrayOffset() == 0)
403 encodedName = b.array();
404 else
405 b.get(encodedName = new byte[encodedNameLen]);
408 public String toString() {
409 return getMode().toString() + " " + getName();
413 * Get the type of this entry.
414 * <p>
415 * <b>Note: Efficient implementation required.</b>
416 * <p>
417 * The implementation of this method must be efficient. If a subclass
418 * needs to compute the value they should cache the reference within an
419 * instance member instead.
421 * @return a file mode constant from {@link FileMode}.
423 public abstract FileMode getMode();
426 * Get the byte length of this entry.
427 * <p>
428 * <b>Note: Efficient implementation required.</b>
429 * <p>
430 * The implementation of this method must be efficient. If a subclass
431 * needs to compute the value they should cache the reference within an
432 * instance member instead.
434 * @return size of this file, in bytes.
436 public abstract long getLength();
439 * Get the last modified time of this entry.
440 * <p>
441 * <b>Note: Efficient implementation required.</b>
442 * <p>
443 * The implementation of this method must be efficient. If a subclass
444 * needs to compute the value they should cache the reference within an
445 * instance member instead.
447 * @return time since the epoch (in ms) of the last change.
449 public abstract long getLastModified();
452 * Get the name of this entry within its directory.
453 * <p>
454 * Efficient implementations are not required. The caller will obtain
455 * the name only once and cache it once obtained.
457 * @return name of the entry.
459 public abstract String getName();
462 * Obtain an input stream to read the file content.
463 * <p>
464 * Efficient implementations are not required. The caller will usually
465 * obtain the stream only once per entry, if at all.
466 * <p>
467 * The input stream should not use buffering if the implementation can
468 * avoid it. The caller will buffer as necessary to perform efficient
469 * block IO operations.
470 * <p>
471 * The caller will close the stream once complete.
473 * @return a stream to read from the file.
474 * @throws IOException
475 * the file could not be opened for reading.
477 public abstract InputStream openInputStream() throws IOException;