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  	/** Create an empty formatter with a default buffer size. */
98  	public TreeFormatter() {
99  		this(8192);
100 	}
101 
102 	/**
103 	 * Create an empty formatter with the specified buffer size.
104 	 *
105 	 * @param size
106 	 *            estimated size of the tree, in bytes. Callers can use
107 	 *            {@link #entrySize(FileMode, int)} to estimate the size of each
108 	 *            entry in advance of allocating the formatter.
109 	 */
110 	public TreeFormatter(int size) {
111 		buf = new byte[size];
112 	}
113 
114 	/**
115 	 * Add a link to a submodule commit, mode is {@link FileMode#GITLINK}.
116 	 *
117 	 * @param name
118 	 *            name of the entry.
119 	 * @param commit
120 	 *            the ObjectId to store in this entry.
121 	 */
122 	public void append(String name, RevCommit commit) {
123 		append(name, GITLINK, commit);
124 	}
125 
126 	/**
127 	 * Add a subtree, mode is {@link FileMode#TREE}.
128 	 *
129 	 * @param name
130 	 *            name of the entry.
131 	 * @param tree
132 	 *            the ObjectId to store in this entry.
133 	 */
134 	public void append(String name, RevTree tree) {
135 		append(name, TREE, tree);
136 	}
137 
138 	/**
139 	 * Add a regular file, mode is {@link FileMode#REGULAR_FILE}.
140 	 *
141 	 * @param name
142 	 *            name of the entry.
143 	 * @param blob
144 	 *            the ObjectId to store in this entry.
145 	 */
146 	public void append(String name, RevBlob blob) {
147 		append(name, REGULAR_FILE, blob);
148 	}
149 
150 	/**
151 	 * Append any entry to the tree.
152 	 *
153 	 * @param name
154 	 *            name of the entry.
155 	 * @param mode
156 	 *            mode describing the treatment of {@code id}.
157 	 * @param id
158 	 *            the ObjectId to store in this entry.
159 	 */
160 	public void append(String name, FileMode mode, AnyObjectId id) {
161 		append(encode(name), mode, id);
162 	}
163 
164 	/**
165 	 * Append any entry to the tree.
166 	 *
167 	 * @param name
168 	 *            name of the entry. The name should be UTF-8 encoded, but file
169 	 *            name encoding is not a well defined concept in Git.
170 	 * @param mode
171 	 *            mode describing the treatment of {@code id}.
172 	 * @param id
173 	 *            the ObjectId to store in this entry.
174 	 */
175 	public void append(byte[] name, FileMode mode, AnyObjectId id) {
176 		append(name, 0, name.length, mode, id);
177 	}
178 
179 	/**
180 	 * Append any entry to the tree.
181 	 *
182 	 * @param nameBuf
183 	 *            buffer holding the name of the entry. The name should be UTF-8
184 	 *            encoded, but file name encoding is not a well defined concept
185 	 *            in Git.
186 	 * @param namePos
187 	 *            first position within {@code nameBuf} of the name data.
188 	 * @param nameLen
189 	 *            number of bytes from {@code nameBuf} to use as the name.
190 	 * @param mode
191 	 *            mode describing the treatment of {@code id}.
192 	 * @param id
193 	 *            the ObjectId to store in this entry.
194 	 */
195 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
196 			AnyObjectId id) {
197 		append(nameBuf, namePos, nameLen, mode, id, false);
198 	}
199 
200 	/**
201 	 * Append any entry to the tree.
202 	 *
203 	 * @param nameBuf
204 	 *            buffer holding the name of the entry. The name should be UTF-8
205 	 *            encoded, but file name encoding is not a well defined concept
206 	 *            in Git.
207 	 * @param namePos
208 	 *            first position within {@code nameBuf} of the name data.
209 	 * @param nameLen
210 	 *            number of bytes from {@code nameBuf} to use as the name.
211 	 * @param mode
212 	 *            mode describing the treatment of {@code id}.
213 	 * @param id
214 	 *            the ObjectId to store in this entry.
215 	 * @param allowEmptyName
216 	 *            allow an empty filename (creating a corrupt tree)
217 	 * @since 4.6
218 	 */
219 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
220 			AnyObjectId id, boolean allowEmptyName) {
221 		if (nameLen == 0 && !allowEmptyName) {
222 			throw new IllegalArgumentException(
223 					JGitText.get().invalidTreeZeroLengthName);
224 		}
225 		if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
226 			id.copyRawTo(buf, ptr);
227 			ptr += OBJECT_ID_LENGTH;
228 
229 		} else {
230 			try {
231 				fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
232 				id.copyRawTo(overflowBuffer);
233 			} catch (IOException badBuffer) {
234 				// This should never occur.
235 				throw new RuntimeException(badBuffer);
236 			}
237 		}
238 	}
239 
240 	/**
241 	 * Append any entry to the tree.
242 	 *
243 	 * @param nameBuf
244 	 *            buffer holding the name of the entry. The name should be UTF-8
245 	 *            encoded, but file name encoding is not a well defined concept
246 	 *            in Git.
247 	 * @param namePos
248 	 *            first position within {@code nameBuf} of the name data.
249 	 * @param nameLen
250 	 *            number of bytes from {@code nameBuf} to use as the name.
251 	 * @param mode
252 	 *            mode describing the treatment of {@code id}.
253 	 * @param idBuf
254 	 *            buffer holding the raw ObjectId of the entry.
255 	 * @param idPos
256 	 *            first position within {@code idBuf} to copy the id from.
257 	 */
258 	public void append(byte[] nameBuf, int namePos, int nameLen, FileMode mode,
259 			byte[] idBuf, int idPos) {
260 		if (fmtBuf(nameBuf, namePos, nameLen, mode)) {
261 			System.arraycopy(idBuf, idPos, buf, ptr, OBJECT_ID_LENGTH);
262 			ptr += OBJECT_ID_LENGTH;
263 
264 		} else {
265 			try {
266 				fmtOverflowBuffer(nameBuf, namePos, nameLen, mode);
267 				overflowBuffer.write(idBuf, idPos, OBJECT_ID_LENGTH);
268 			} catch (IOException badBuffer) {
269 				// This should never occur.
270 				throw new RuntimeException(badBuffer);
271 			}
272 		}
273 	}
274 
275 	private boolean fmtBuf(byte[] nameBuf, int namePos, int nameLen,
276 			FileMode mode) {
277 		if (buf == null || buf.length < ptr + entrySize(mode, nameLen))
278 			return false;
279 
280 		mode.copyTo(buf, ptr);
281 		ptr += mode.copyToLength();
282 		buf[ptr++] = ' ';
283 
284 		System.arraycopy(nameBuf, namePos, buf, ptr, nameLen);
285 		ptr += nameLen;
286 		buf[ptr++] = 0;
287 		return true;
288 	}
289 
290 	private void fmtOverflowBuffer(byte[] nameBuf, int namePos, int nameLen,
291 			FileMode mode) throws IOException {
292 		if (buf != null) {
293 			overflowBuffer = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
294 			overflowBuffer.write(buf, 0, ptr);
295 			buf = null;
296 		}
297 
298 		mode.copyTo(overflowBuffer);
299 		overflowBuffer.write((byte) ' ');
300 		overflowBuffer.write(nameBuf, namePos, nameLen);
301 		overflowBuffer.write((byte) 0);
302 	}
303 
304 	/**
305 	 * Insert this tree and obtain its ObjectId.
306 	 *
307 	 * @param ins
308 	 *            the inserter to store the tree.
309 	 * @return computed ObjectId of the tree
310 	 * @throws IOException
311 	 *             the tree could not be stored.
312 	 */
313 	public ObjectId insertTo(ObjectInserter ins) throws IOException {
314 		if (buf != null)
315 			return ins.insert(OBJ_TREE, buf, 0, ptr);
316 
317 		final long len = overflowBuffer.length();
318 		return ins.insert(OBJ_TREE, len, overflowBuffer.openInputStream());
319 	}
320 
321 	/**
322 	 * Compute the ObjectId for this tree
323 	 *
324 	 * @param ins
325 	 * @return ObjectId for this tree
326 	 */
327 	public ObjectId computeId(ObjectInserter ins) {
328 		if (buf != null)
329 			return ins.idFor(OBJ_TREE, buf, 0, ptr);
330 
331 		final long len = overflowBuffer.length();
332 		try {
333 			return ins.idFor(OBJ_TREE, len, overflowBuffer.openInputStream());
334 		} catch (IOException e) {
335 			// this should never happen
336 			throw new RuntimeException(e);
337 		}
338 	}
339 
340 	/**
341 	 * Copy this formatter's buffer into a byte array.
342 	 *
343 	 * This method is not efficient, as it needs to create a copy of the
344 	 * internal buffer in order to supply an array of the correct size to the
345 	 * caller. If the buffer is just to pass to an ObjectInserter, consider
346 	 * using {@link ObjectInserter#insert(TreeFormatter)} instead.
347 	 *
348 	 * @return a copy of this formatter's buffer.
349 	 */
350 	public byte[] toByteArray() {
351 		if (buf != null) {
352 			byte[] r = new byte[ptr];
353 			System.arraycopy(buf, 0, r, 0, ptr);
354 			return r;
355 		}
356 
357 		try {
358 			return overflowBuffer.toByteArray();
359 		} catch (IOException err) {
360 			// This should never happen, its read failure on a byte array.
361 			throw new RuntimeException(err);
362 		}
363 	}
364 
365 	@SuppressWarnings("nls")
366 	@Override
367 	public String toString() {
368 		byte[] raw = toByteArray();
369 
370 		CanonicalTreeParser p = new CanonicalTreeParser();
371 		p.reset(raw);
372 
373 		StringBuilder r = new StringBuilder();
374 		r.append("Tree={");
375 		if (!p.eof()) {
376 			r.append('\n');
377 			try {
378 				new ObjectChecker().checkTree(raw);
379 			} catch (CorruptObjectException error) {
380 				r.append("*** ERROR: ").append(error.getMessage()).append("\n");
381 				r.append('\n');
382 			}
383 		}
384 		while (!p.eof()) {
385 			final FileMode mode = p.getEntryFileMode();
386 			r.append(mode);
387 			r.append(' ');
388 			r.append(Constants.typeString(mode.getObjectType()));
389 			r.append(' ');
390 			r.append(p.getEntryObjectId().name());
391 			r.append(' ');
392 			r.append(p.getEntryPathString());
393 			r.append('\n');
394 			p.next();
395 		}
396 		r.append("}");
397 		return r.toString();
398 	}
399 }