2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
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
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
;
55 * Walks a working directory tree as part of a {@link TreeWalk}.
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. */
93 /** Current position within {@link #entries}. */
96 /** Create a new iterator with no parent. */
97 protected WorkingTreeIterator() {
99 nameEncoder
= Constants
.CHARSET
.newEncoder();
103 * Create a new iterator with no parent and a prefix.
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.
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
) {
119 nameEncoder
= Constants
.CHARSET
.newEncoder();
123 * Create an iterator for a subtree of an existing iterator.
126 * parent tree iterator.
128 protected WorkingTreeIterator(final WorkingTreeIterator p
) {
130 nameEncoder
= p
.nameEncoder
;
134 public byte[] idBuffer() {
135 if (contentIdFromPtr
== ptr
)
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.
146 case 0160000: /* gitlink */
147 // TODO: Support obtaining current HEAD SHA-1 from nested repository
154 private void initializeDigest() {
155 if (contentDigest
!= null)
158 if (parent
== null) {
159 contentReadBuffer
= new byte[BUFFER_SIZE
];
160 contentDigest
= Constants
.newMessageDigest();
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',
172 private static final byte[] hblob
= Constants
173 .encodedTypeString(Constants
.OBJ_BLOB
);
175 private byte[] idBufferBlob(final Entry e
) {
177 final InputStream is
= e
.openInputStream();
183 contentDigest
.reset();
184 contentDigest
.update(hblob
);
185 contentDigest
.update((byte) ' ');
187 final long blobLength
= e
.getLength();
188 long sz
= blobLength
;
190 contentDigest
.update((byte) '0');
192 final int bufn
= contentReadBuffer
.length
;
195 contentReadBuffer
[--p
] = digits
[(int) (sz
% 10)];
198 contentDigest
.update(contentReadBuffer
, p
, bufn
- p
);
200 contentDigest
.update((byte) 0);
203 final int r
= is
.read(contentReadBuffer
);
206 contentDigest
.update(contentReadBuffer
, 0, r
);
209 if (sz
!= blobLength
)
211 return contentDigest
.digest();
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.
229 public int idOffset() {
234 public boolean first() {
239 public boolean eof() {
240 return ptr
== entryCnt
;
244 public void next(final int delta
) throws CorruptObjectException
{
251 public void back(final int delta
) throws CorruptObjectException
{
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
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
;
294 for (cPos
= 0; cPos
< aLen
&& cPos
< bLen
; cPos
++) {
295 final int cmp
= (a
[cPos
] & 0xff) - (b
[cPos
] & 0xff);
301 return (a
[cPos
] & 0xff) - lastPathChar(o2
);
303 return lastPathChar(o1
) - (b
[cPos
] & 0xff);
304 return lastPathChar(o1
) - lastPathChar(o2
);
308 static int lastPathChar(final Entry e
) {
309 return e
.getMode() == FileMode
.TREE ?
'/' : '\0';
313 * Constructor helper.
316 * files in the subtree of the work tree this iterator operates
319 protected void init(final Entry
[] list
) {
320 // Filter out nulls, . and .. as these are not valid tree entries,
321 // also cache the encoded forms of the path names for efficient use
322 // later on during sorting and iteration.
327 for (i
= 0, o
= 0; i
< entries
.length
; i
++) {
328 final Entry e
= entries
[i
];
331 final String name
= e
.getName();
332 if (".".equals(name
) || "..".equals(name
))
334 if (".git".equals(name
))
338 e
.encodeName(nameEncoder
);
342 Arrays
.sort(entries
, 0, entryCnt
, ENTRY_CMP
);
344 contentIdFromPtr
= -1;
351 * Obtain the current entry from this iterator.
353 * @return the currently selected entry.
355 protected Entry
current() {
359 /** A single entry within a working directory tree. */
360 protected static abstract class Entry
{
365 void encodeName(final CharsetEncoder enc
) {
368 b
= enc
.encode(CharBuffer
.wrap(getName()));
369 } catch (CharacterCodingException e
) {
370 // This should so never happen.
371 throw new RuntimeException("Unencodeable file: " + getName());
374 encodedNameLen
= b
.limit();
375 if (b
.hasArray() && b
.arrayOffset() == 0)
376 encodedName
= b
.array();
378 b
.get(encodedName
= new byte[encodedNameLen
]);
381 public String
toString() {
382 return getMode().toString() + " " + getName();
386 * Get the type of this entry.
388 * <b>Note: Efficient implementation required.</b>
390 * The implementation of this method must be efficient. If a subclass
391 * needs to compute the value they should cache the reference within an
392 * instance member instead.
394 * @return a file mode constant from {@link FileMode}.
396 public abstract FileMode
getMode();
399 * Get the byte length of this entry.
401 * <b>Note: Efficient implementation required.</b>
403 * The implementation of this method must be efficient. If a subclass
404 * needs to compute the value they should cache the reference within an
405 * instance member instead.
407 * @return size of this file, in bytes.
409 public abstract long getLength();
412 * Get the last modified time of this entry.
414 * <b>Note: Efficient implementation required.</b>
416 * The implementation of this method must be efficient. If a subclass
417 * needs to compute the value they should cache the reference within an
418 * instance member instead.
420 * @return time since the epoch (in ms) of the last change.
422 public abstract long getLastModified();
425 * Get the name of this entry within its directory.
427 * Efficient implementations are not required. The caller will obtain
428 * the name only once and cache it once obtained.
430 * @return name of the entry.
432 public abstract String
getName();
435 * Obtain an input stream to read the file content.
437 * Efficient implementations are not required. The caller will usually
438 * obtain the stream only once per entry, if at all.
440 * The input stream should not use buffering if the implementation can
441 * avoid it. The caller will buffer as necessary to perform efficient
442 * block IO operations.
444 * The caller will close the stream once complete.
446 * @return a stream to read from the file.
447 * @throws IOException
448 * the file could not be opened for reading.
450 public abstract InputStream
openInputStream() throws IOException
;