View Javadoc
1   /*
2    * Copyright (C) 2008-2010, 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.treewalk;
46  
47  import java.io.IOException;
48  import java.util.Arrays;
49  
50  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
51  import org.eclipse.jgit.errors.MissingObjectException;
52  import org.eclipse.jgit.lib.AnyObjectId;
53  import org.eclipse.jgit.lib.Constants;
54  import org.eclipse.jgit.lib.FileMode;
55  import org.eclipse.jgit.lib.MutableObjectId;
56  import org.eclipse.jgit.lib.ObjectId;
57  import org.eclipse.jgit.lib.ObjectReader;
58  
59  /** Parses raw Git trees from the canonical semi-text/semi-binary format. */
60  public class CanonicalTreeParser extends AbstractTreeIterator {
61  	private static final byte[] EMPTY = {};
62  
63  	private byte[] raw;
64  
65  	/** First offset within {@link #raw} of the prior entry. */
66  	private int prevPtr;
67  
68  	/** First offset within {@link #raw} of the current entry's data. */
69  	private int currPtr;
70  
71  	/** Offset one past the current entry (first byte of next entry). */
72  	private int nextPtr;
73  
74  	/** Create a new parser. */
75  	public CanonicalTreeParser() {
76  		reset(EMPTY);
77  	}
78  
79  	/**
80  	 * Create a new parser for a tree appearing in a subset of a repository.
81  	 *
82  	 * @param prefix
83  	 *            position of this iterator in the repository tree. The value
84  	 *            may be null or the empty array to indicate the prefix is the
85  	 *            root of the repository. A trailing slash ('/') is
86  	 *            automatically appended if the prefix does not end in '/'.
87  	 * @param reader
88  	 *            reader to load the tree data from.
89  	 * @param treeId
90  	 *            identity of the tree being parsed; used only in exception
91  	 *            messages if data corruption is found.
92  	 * @throws MissingObjectException
93  	 *             the object supplied is not available from the repository.
94  	 * @throws IncorrectObjectTypeException
95  	 *             the object supplied as an argument is not actually a tree and
96  	 *             cannot be parsed as though it were a tree.
97  	 * @throws IOException
98  	 *             a loose object or pack file could not be read.
99  	 */
100 	public CanonicalTreeParser(final byte[] prefix, final ObjectReader reader,
101 			final AnyObjectId treeId) throws IncorrectObjectTypeException,
102 			IOException {
103 		super(prefix);
104 		reset(reader, treeId);
105 	}
106 
107 	private CanonicalTreeParser(final CanonicalTreeParser p) {
108 		super(p);
109 	}
110 
111 	/**
112 	 * @return the parent of this tree parser
113 	 * @deprecated internal use only
114 	 */
115 	@Deprecated
116 	public CanonicalTreeParser getParent() {
117 		return (CanonicalTreeParser) parent;
118 	}
119 
120 	/**
121 	 * Reset this parser to walk through the given tree data.
122 	 *
123 	 * @param treeData
124 	 *            the raw tree content.
125 	 */
126 	public void reset(final byte[] treeData) {
127 		raw = treeData;
128 		prevPtr = -1;
129 		currPtr = 0;
130 		if (eof())
131 			nextPtr = 0;
132 		else
133 			parseEntry();
134 	}
135 
136 	/**
137 	 * Reset this parser to walk through the given tree.
138 	 *
139 	 * @param reader
140 	 *            reader to use during repository access.
141 	 * @param id
142 	 *            identity of the tree being parsed; used only in exception
143 	 *            messages if data corruption is found.
144 	 * @return the root level parser.
145 	 * @throws MissingObjectException
146 	 *             the object supplied is not available from the repository.
147 	 * @throws IncorrectObjectTypeException
148 	 *             the object supplied as an argument is not actually a tree and
149 	 *             cannot be parsed as though it were a tree.
150 	 * @throws IOException
151 	 *             a loose object or pack file could not be read.
152 	 */
153 	public CanonicalTreeParser resetRoot(final ObjectReader reader,
154 			final AnyObjectId id) throws IncorrectObjectTypeException,
155 			IOException {
156 		CanonicalTreeParser p = this;
157 		while (p.parent != null)
158 			p = (CanonicalTreeParser) p.parent;
159 		p.reset(reader, id);
160 		return p;
161 	}
162 
163 	/** @return this iterator, or its parent, if the tree is at eof. */
164 	public CanonicalTreeParser next() {
165 		CanonicalTreeParser p = this;
166 		for (;;) {
167 			if (p.nextPtr == p.raw.length) {
168 				// This parser has reached EOF, return to the parent.
169 				if (p.parent == null) {
170 					p.currPtr = p.nextPtr;
171 					return p;
172 				}
173 				p = (CanonicalTreeParser) p.parent;
174 				continue;
175 			}
176 
177 			p.prevPtr = p.currPtr;
178 			p.currPtr = p.nextPtr;
179 			p.parseEntry();
180 			return p;
181 		}
182 	}
183 
184 	/**
185 	 * Reset this parser to walk through the given tree.
186 	 *
187 	 * @param reader
188 	 *            reader to use during repository access.
189 	 * @param id
190 	 *            identity of the tree being parsed; used only in exception
191 	 *            messages if data corruption is found.
192 	 * @throws MissingObjectException
193 	 *             the object supplied is not available from the repository.
194 	 * @throws IncorrectObjectTypeException
195 	 *             the object supplied as an argument is not actually a tree and
196 	 *             cannot be parsed as though it were a tree.
197 	 * @throws IOException
198 	 *             a loose object or pack file could not be read.
199 	 */
200 	public void reset(final ObjectReader reader, final AnyObjectId id)
201 			throws IncorrectObjectTypeException, IOException {
202 		reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes());
203 	}
204 
205 	@Override
206 	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
207 			final MutableObjectId idBuffer)
208 			throws IncorrectObjectTypeException, IOException {
209 		idBuffer.fromRaw(idBuffer(), idOffset());
210 		if (!FileMode.TREE.equals(mode)) {
211 			final ObjectId me = idBuffer.toObjectId();
212 			throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE);
213 		}
214 		return createSubtreeIterator0(reader, idBuffer);
215 	}
216 
217 	/**
218 	 * Back door to quickly create a subtree iterator for any subtree.
219 	 * <p>
220 	 * Don't use this unless you are ObjectWalk. The method is meant to be
221 	 * called only once the current entry has been identified as a tree and its
222 	 * identity has been converted into an ObjectId.
223 	 *
224 	 * @param reader
225 	 *            reader to load the tree data from.
226 	 * @param id
227 	 *            ObjectId of the tree to open.
228 	 * @return a new parser that walks over the current subtree.
229 	 * @throws IOException
230 	 *             a loose object or pack file could not be read.
231 	 */
232 	public final CanonicalTreeParser createSubtreeIterator0(
233 			final ObjectReader reader, final AnyObjectId id)
234 			throws IOException {
235 		final CanonicalTreeParser p = new CanonicalTreeParser(this);
236 		p.reset(reader, id);
237 		return p;
238 	}
239 
240 	public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader)
241 			throws IncorrectObjectTypeException, IOException {
242 		return createSubtreeIterator(reader, new MutableObjectId());
243 	}
244 
245 	@Override
246 	public boolean hasId() {
247 		return true;
248 	}
249 
250 	@Override
251 	public byte[] idBuffer() {
252 		return raw;
253 	}
254 
255 	@Override
256 	public int idOffset() {
257 		return nextPtr - Constants.OBJECT_ID_LENGTH;
258 	}
259 
260 	@Override
261 	public void reset() {
262 		if (!first())
263 			reset(raw);
264 	}
265 
266 	@Override
267 	public boolean first() {
268 		return currPtr == 0;
269 	}
270 
271 	public boolean eof() {
272 		return currPtr == raw.length;
273 	}
274 
275 	@Override
276 	public void next(int delta) {
277 		if (delta == 1) {
278 			// Moving forward one is the most common case.
279 			//
280 			prevPtr = currPtr;
281 			currPtr = nextPtr;
282 			if (!eof())
283 				parseEntry();
284 			return;
285 		}
286 
287 		// Fast skip over records, then parse the last one.
288 		//
289 		final int end = raw.length;
290 		int ptr = nextPtr;
291 		while (--delta > 0 && ptr != end) {
292 			prevPtr = ptr;
293 			while (raw[ptr] != 0)
294 				ptr++;
295 			ptr += Constants.OBJECT_ID_LENGTH + 1;
296 		}
297 		if (delta != 0)
298 			throw new ArrayIndexOutOfBoundsException(delta);
299 		currPtr = ptr;
300 		if (!eof())
301 			parseEntry();
302 	}
303 
304 	@Override
305 	public void back(int delta) {
306 		if (delta == 1 && 0 <= prevPtr) {
307 			// Moving back one is common in NameTreeWalk, as the average tree
308 			// won't have D/F type conflicts to study.
309 			//
310 			currPtr = prevPtr;
311 			prevPtr = -1;
312 			if (!eof())
313 				parseEntry();
314 			return;
315 		} else if (delta <= 0)
316 			throw new ArrayIndexOutOfBoundsException(delta);
317 
318 		// Fast skip through the records, from the beginning of the tree.
319 		// There is no reliable way to read the tree backwards, so we must
320 		// parse all over again from the beginning. We hold the last "delta"
321 		// positions in a buffer, so we can find the correct position later.
322 		//
323 		final int[] trace = new int[delta + 1];
324 		Arrays.fill(trace, -1);
325 		int ptr = 0;
326 		while (ptr != currPtr) {
327 			System.arraycopy(trace, 1, trace, 0, delta);
328 			trace[delta] = ptr;
329 			while (raw[ptr] != 0)
330 				ptr++;
331 			ptr += Constants.OBJECT_ID_LENGTH + 1;
332 		}
333 		if (trace[1] == -1)
334 			throw new ArrayIndexOutOfBoundsException(delta);
335 		prevPtr = trace[0];
336 		currPtr = trace[1];
337 		parseEntry();
338 	}
339 
340 	private void parseEntry() {
341 		int ptr = currPtr;
342 		byte c = raw[ptr++];
343 		int tmp = c - '0';
344 		for (;;) {
345 			c = raw[ptr++];
346 			if (' ' == c)
347 				break;
348 			tmp <<= 3;
349 			tmp += c - '0';
350 		}
351 		mode = tmp;
352 
353 		tmp = pathOffset;
354 		for (;; tmp++) {
355 			c = raw[ptr++];
356 			if (c == 0)
357 				break;
358 			try {
359 				path[tmp] = c;
360 			} catch (ArrayIndexOutOfBoundsException e) {
361 				growPath(tmp);
362 				path[tmp] = c;
363 			}
364 		}
365 		pathLen = tmp;
366 		nextPtr = ptr + Constants.OBJECT_ID_LENGTH;
367 	}
368 }