View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.lib;
45  
46  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
47  import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
48  import static org.eclipse.jgit.lib.Constants.encode;
49  import static org.eclipse.jgit.lib.FileMode.GITLINK;
50  import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE;
51  import static org.eclipse.jgit.lib.FileMode.TREE;
52  
53  import java.io.IOException;
54  
55  import org.eclipse.jgit.errors.CorruptObjectException;
56  import org.eclipse.jgit.internal.JGitText;
57  import org.eclipse.jgit.revwalk.RevBlob;
58  import org.eclipse.jgit.revwalk.RevCommit;
59  import org.eclipse.jgit.revwalk.RevTree;
60  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
61  import org.eclipse.jgit.util.TemporaryBuffer;
62  
63  /**
64   * Mutable formatter to construct a single tree object.
65   *
66   * This formatter does not process subtrees. Callers must handle creating each
67   * subtree on their own.
68   *
69   * To maintain good performance for bulk operations, this formatter does not
70   * validate its input. Callers are responsible for ensuring the resulting tree
71   * object is correctly well formed by writing entries in the correct order.
72   */
73  public class TreeFormatter {
74  	/**
75  	 * Compute the size of a tree entry record.
76  	 *
77  	 * This method can be used to estimate the correct size of a tree prior to
78  	 * allocating a formatter. Getting the size correct at allocation time
79  	 * ensures the internal buffer is sized correctly, reducing copying.
80  	 *
81  	 * @param mode
82  	 *            the mode the entry will have.
83  	 * @param nameLen
84  	 *            the length of the name, in bytes.
85  	 * @return the length of the record.
86  	 */
87  	public static int entrySize(FileMode mode, int nameLen) {
88  		return mode.copyToLength() + nameLen + OBJECT_ID_LENGTH + 2;
89  	}
90  
91  	private byte[] buf;
92  
93  	private int ptr;
94  
95  	private TemporaryBuffer.Heap overflowBuffer;
96  
97  	/**
98  	 * Create an empty formatter with a default buffer size.
99  	 */
100 	public TreeFormatter() {
101 		this(8192);
102 	}
103 
104 	/**
105 	 * Create an empty formatter with the specified buffer size.
106 	 *
107 	 * @param size
108 	 *            estimated size of the tree, in bytes. Callers can use
109 	 *            {@link #entrySize(FileMode, int)} to estimate the size of each
110 	 *            entry in advance of allocating the formatter.
111 	 */
112 	public TreeFormatter(int size) {
113 		buf = new byte[size];
114 	}
115 
116 	/**
117 	 * Add a link to a submodule commit, mode is {@link org.eclipse.jgit.lib.FileMode#GITLINK}.
118 	 *
119 	 * @param name
120 	 *            name of the entry.
121 	 * @param commit
122 	 *            the ObjectId to store in this entry.
123 	 */
124 	public void append(String name, RevCommit commit) {
125 		append(name, GITLINK, commit);
126 	}
127 
128 	/**
129 	 * Add a subtree, mode is {@link org.eclipse.jgit.lib.FileMode#TREE}.
130 	 *
131 	 * @param name
132 	 *            name of the entry.
133 	 * @param tree
134 	 *            the ObjectId to store in this entry.
135 	 */
136 	public void append(String name, RevTree tree) {
137 		append(name, TREE, tree);
138 	}
139 
140 	/**
141 	 * Add a regular file, mode is {@link org.eclipse.jgit.lib.FileMode#REGULAR_FILE}.
142 	 *
143 	 * @param name
144 	 *            name of the entry.
145 	 * @param blob
146 	 *            the ObjectId to store in this entry.
147 	 */
148 	public void append(String name, RevBlob blob) {
149 		append(name, REGULAR_FILE, blob);
150 	}
151 
152 	/**
153 	 * Append any entry to the tree.
154 	 *
155 	 * @param name
156 	 *            name of the entry.
157 	 * @param mode
158 	 *            mode describing the treatment of {@code id}.
159 	 * @param id
160 	 *            the ObjectId to store in this entry.
161 	 */
162 	public void append(String name, FileMode mode, AnyObjectId id) {
163 		append(encode(name), mode, id);
164 	}
165 
166 	/**
167 	 * Append any entry to the tree.
168 	 *
169 	 * @param name
170 	 *            name of the entry. The name should be UTF-8 encoded, but file
171 	 *            name encoding is not a well defined concept in Git.
172 	 * @param mode
173 	 *            mode describing the treatment of {@code id}.
174 	 * @param id
175 	 *            the ObjectId to store in this entry.
176 	 */
177 	public void append(byte[] name, FileMode mode, AnyObjectId id) {
178 		append(name, 0, name.length, mode, id);
179 	}
180 
181 	/**
182 	 * Append any entry to the tree.
183 	 *
184 	 * @param nameBuf
185 	 *            buffer holding the name of the entry. The name should be UTF-8
186 	 *            encoded, but file name encoding is not a well defined concept
187 	 *            in Git.
188 	 * @param namePos
189 	 *            first position within {@code nameBuf} of the name data.
190 	 * @param nameLen
191 	 *            number of bytes from {@code nameBuf} to use as the name.
192 	 * @param mode
193 	 *            mode describing the treatment of {@code id}.
194 	 * @param id
195 	 *            the ObjectId to store in this entry.
196 	 */
197 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
198 			AnyObjectId id) {
199 		append(nameBuf, namePos, nameLen, mode, id, false);
200 	}
201 
202 	/**
203 	 * Append any entry to the tree.
204 	 *
205 	 * @param nameBuf
206 	 *            buffer holding the name of the entry. The name should be UTF-8
207 	 *            encoded, but file name encoding is not a well defined concept
208 	 *            in Git.
209 	 * @param namePos
210 	 *            first position within {@code nameBuf} of the name data.
211 	 * @param nameLen
212 	 *            number of bytes from {@code nameBuf} to use as the name.
213 	 * @param mode
214 	 *            mode describing the treatment of {@code id}.
215 	 * @param id
216 	 *            the ObjectId to store in this entry.
217 	 * @param allowEmptyName
218 	 *            allow an empty filename (creating a corrupt tree)
219 	 * @since 4.6
220 	 */
221 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
222 			AnyObjectId id, boolean allowEmptyName) {
223 		if (nameLen == 0 && !allowEmptyName) {
224 			throw new IllegalArgumentException(
225 					JGitText.get().invalidTreeZeroLengthName);
226 		}
227 		if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
228 			id.copyRawTo(buf, ptr);
229 			ptr += OBJECT_ID_LENGTH;
230 
231 		} else {
232 			try {
233 				fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
234 				id.copyRawTo(overflowBuffer);
235 			} catch (IOException badBuffer) {
236 				// This should never occur.
237 				throw new RuntimeException(badBuffer);
238 			}
239 		}
240 	}
241 
242 	/**
243 	 * Append any entry to the tree.
244 	 *
245 	 * @param nameBuf
246 	 *            buffer holding the name of the entry. The name should be UTF-8
247 	 *            encoded, but file name encoding is not a well defined concept
248 	 *            in Git.
249 	 * @param namePos
250 	 *            first position within {@code nameBuf} of the name data.
251 	 * @param nameLen
252 	 *            number of bytes from {@code nameBuf} to use as the name.
253 	 * @param mode
254 	 *            mode describing the treatment of {@code id}.
255 	 * @param idBuf
256 	 *            buffer holding the raw ObjectId of the entry.
257 	 * @param idPos
258 	 *            first position within {@code idBuf} to copy the id from.
259 	 */
260 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
261 			byte[] idBuf, int idPos) {
262 		if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
263 			System.arraycopy(idBuf, idPos, buf, ptr, OBJECT_ID_LENGTH);
264 			ptr += OBJECT_ID_LENGTH;
265 
266 		} else {
267 			try {
268 				fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
269 				overflowBuffer.write(idBuf, idPos, OBJECT_ID_LENGTH);
270 			} catch (IOException badBuffer) {
271 				// This should never occur.
272 				throw new RuntimeException(badBuffer);
273 			}
274 		}
275 	}
276 
277 	private boolean fmtBuf(byte[] nameBuf, int namePos, int nameLen,
278 			FileMode mode) {
279 		if (buf == null || buf.length < ptr + entrySize(mode, nameLen))
280 			return false;
281 
282 		mode.copyTo(buf, ptr);
283 		ptr += mode.copyToLength();
284 		buf[ptr++] = ' ';
285 
286 		System.arraycopy(nameBuf, namePos, buf, ptr, nameLen);
287 		ptr += nameLen;
288 		buf[ptr++] = 0;
289 		return true;
290 	}
291 
292 	private void fmtOverflowBuffer(byte[] nameBuf, int namePos, int nameLen,
293 			FileMode mode) throws IOException {
294 		if (buf != null) {
295 			overflowBuffer = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
296 			overflowBuffer.write(buf, 0, ptr);
297 			buf = null;
298 		}
299 
300 		mode.copyTo(overflowBuffer);
301 		overflowBuffer.write((byte) ' ');
302 		overflowBuffer.write(nameBuf, namePos, nameLen);
303 		overflowBuffer.write((byte) 0);
304 	}
305 
306 	/**
307 	 * Insert this tree and obtain its ObjectId.
308 	 *
309 	 * @param ins
310 	 *            the inserter to store the tree.
311 	 * @return computed ObjectId of the tree
312 	 * @throws java.io.IOException
313 	 *             the tree could not be stored.
314 	 */
315 	public ObjectId insertTo(ObjectInserter ins) throws IOException {
316 		if (buf != null)
317 			return ins.insert(OBJ_TREE, buf, 0, ptr);
318 
319 		final long len = overflowBuffer.length();
320 		return ins.insert(OBJ_TREE, len, overflowBuffer.openInputStream());
321 	}
322 
323 	/**
324 	 * Compute the ObjectId for this tree
325 	 *
326 	 * @param ins a {@link org.eclipse.jgit.lib.ObjectInserter} object.
327 	 * @return ObjectId for this tree
328 	 */
329 	public ObjectId computeId(ObjectInserter ins) {
330 		if (buf != null)
331 			return ins.idFor(OBJ_TREE, buf, 0, ptr);
332 
333 		final long len = overflowBuffer.length();
334 		try {
335 			return ins.idFor(OBJ_TREE, len, overflowBuffer.openInputStream());
336 		} catch (IOException e) {
337 			// this should never happen
338 			throw new RuntimeException(e);
339 		}
340 	}
341 
342 	/**
343 	 * Copy this formatter's buffer into a byte array.
344 	 *
345 	 * This method is not efficient, as it needs to create a copy of the
346 	 * internal buffer in order to supply an array of the correct size to the
347 	 * caller. If the buffer is just to pass to an ObjectInserter, consider
348 	 * using {@link org.eclipse.jgit.lib.ObjectInserter#insert(TreeFormatter)}
349 	 * instead.
350 	 *
351 	 * @return a copy of this formatter's buffer.
352 	 */
353 	public byte[] toByteArray() {
354 		if (buf != null) {
355 			byte[] r = new byte[ptr];
356 			System.arraycopy(buf, 0, r, 0, ptr);
357 			return r;
358 		}
359 
360 		try {
361 			return overflowBuffer.toByteArray();
362 		} catch (IOException err) {
363 			// This should never happen, its read failure on a byte array.
364 			throw new RuntimeException(err);
365 		}
366 	}
367 
368 	/** {@inheritDoc} */
369 	@SuppressWarnings("nls")
370 	@Override
371 	public String toString() {
372 		byte[] raw = toByteArray();
373 
374 		CanonicalTreeParser p = new CanonicalTreeParser();
375 		p.reset(raw);
376 
377 		StringBuilder r = new StringBuilder();
378 		r.append("Tree={");
379 		if (!p.eof()) {
380 			r.append('\n');
381 			try {
382 				new ObjectChecker().checkTree(raw);
383 			} catch (CorruptObjectException error) {
384 				r.append("*** ERROR: ").append(error.getMessage()).append("\n");
385 				r.append('\n');
386 			}
387 		}
388 		while (!p.eof()) {
389 			final FileMode mode = p.getEntryFileMode();
390 			r.append(mode);
391 			r.append(' ');
392 			r.append(Constants.typeString(mode.getObjectType()));
393 			r.append(' ');
394 			r.append(p.getEntryObjectId().name());
395 			r.append(' ');
396 			r.append(p.getEntryPathString());
397 			r.append('\n');
398 			p.next();
399 		}
400 		r.append("}");
401 		return r.toString();
402 	}
403 }