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.diff;
45
46 import java.io.BufferedInputStream;
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.io.InputStream;
50
51 import org.eclipse.jgit.errors.LargeObjectException;
52 import org.eclipse.jgit.errors.MissingObjectException;
53 import org.eclipse.jgit.lib.Constants;
54 import org.eclipse.jgit.lib.ObjectId;
55 import org.eclipse.jgit.lib.ObjectLoader;
56 import org.eclipse.jgit.lib.ObjectReader;
57 import org.eclipse.jgit.lib.ObjectStream;
58 import org.eclipse.jgit.treewalk.FileTreeIterator;
59 import org.eclipse.jgit.treewalk.TreeWalk;
60 import org.eclipse.jgit.treewalk.WorkingTreeIterator;
61 import org.eclipse.jgit.treewalk.filter.PathFilter;
62
63 /**
64 * Supplies the content of a file for {@link DiffFormatter}.
65 * <p>
66 * A content source is not thread-safe. Sources may contain state, including
67 * information about the last ObjectLoader they returned. Callers must be
68 * careful to ensure there is no more than one ObjectLoader pending on any
69 * source, at any time.
70 */
71 public abstract class ContentSource {
72 /**
73 * Construct a content source for an ObjectReader.
74 *
75 * @param reader
76 * the reader to obtain blobs from.
77 * @return a source wrapping the reader.
78 */
79 public static ContentSource create(ObjectReader reader) {
80 return new ObjectReaderSource(reader);
81 }
82
83 /**
84 * Construct a content source for a working directory.
85 *
86 * If the iterator is a {@link FileTreeIterator} an optimized version is
87 * used that doesn't require seeking through a TreeWalk.
88 *
89 * @param iterator
90 * the iterator to obtain source files through.
91 * @return a content source wrapping the iterator.
92 */
93 public static ContentSource create(WorkingTreeIterator iterator) {
94 return new WorkingTreeSource(iterator);
95 }
96
97 /**
98 * Determine the size of the object.
99 *
100 * @param path
101 * the path of the file, relative to the root of the repository.
102 * @param id
103 * blob id of the file, if known.
104 * @return the size in bytes.
105 * @throws IOException
106 * the file cannot be accessed.
107 */
108 public abstract long size(String path, ObjectId id) throws IOException;
109
110 /**
111 * Open the object.
112 *
113 * @param path
114 * the path of the file, relative to the root of the repository.
115 * @param id
116 * blob id of the file, if known.
117 * @return a loader that can supply the content of the file. The loader must
118 * be used before another loader can be obtained from this same
119 * source.
120 * @throws IOException
121 * the file cannot be accessed.
122 */
123 public abstract ObjectLoader open(String path, ObjectId id)
124 throws IOException;
125
126 private static class ObjectReaderSource extends ContentSource {
127 private final ObjectReader reader;
128
129 ObjectReaderSource(ObjectReader reader) {
130 this.reader = reader;
131 }
132
133 @Override
134 public long size(String path, ObjectId id) throws IOException {
135 try {
136 return reader.getObjectSize(id, Constants.OBJ_BLOB);
137 } catch (MissingObjectException ignore) {
138 return 0;
139 }
140 }
141
142 @Override
143 public ObjectLoader open(String path, ObjectId id) throws IOException {
144 return reader.open(id, Constants.OBJ_BLOB);
145 }
146 }
147
148 private static class WorkingTreeSource extends ContentSource {
149 private final TreeWalk tw;
150
151 private final WorkingTreeIterator iterator;
152
153 private String current;
154
155 WorkingTreeIterator ptr;
156
157 WorkingTreeSource(WorkingTreeIterator iterator) {
158 this.tw = new TreeWalk((ObjectReader) null);
159 this.tw.setRecursive(true);
160 this.iterator = iterator;
161 }
162
163 @Override
164 public long size(String path, ObjectId id) throws IOException {
165 seek(path);
166 return ptr.getEntryLength();
167 }
168
169 @Override
170 public ObjectLoader open(String path, ObjectId id) throws IOException {
171 seek(path);
172 return new ObjectLoader() {
173 @Override
174 public long getSize() {
175 return ptr.getEntryLength();
176 }
177
178 @Override
179 public int getType() {
180 return ptr.getEntryFileMode().getObjectType();
181 }
182
183 @Override
184 public ObjectStream openStream() throws MissingObjectException,
185 IOException {
186 long contentLength = ptr.getEntryContentLength();
187 InputStream in = ptr.openEntryStream();
188 in = new BufferedInputStream(in);
189 return new ObjectStream.Filter(getType(), contentLength, in);
190 }
191
192 @Override
193 public boolean isLarge() {
194 return true;
195 }
196
197 @Override
198 public byte[] getCachedBytes() throws LargeObjectException {
199 throw new LargeObjectException();
200 }
201 };
202 }
203
204 private void seek(String path) throws IOException {
205 if (!path.equals(current)) {
206 iterator.reset();
207 tw.reset();
208 tw.addTree(iterator);
209 tw.setFilter(PathFilter.create(path));
210 current = path;
211 if (!tw.next())
212 throw new FileNotFoundException(path);
213 ptr = tw.getTree(0, WorkingTreeIterator.class);
214 if (ptr == null)
215 throw new FileNotFoundException(path);
216 }
217 }
218 }
219
220 /** A pair of sources to access the old and new sides of a DiffEntry. */
221 public static final class Pair {
222 private final ContentSource oldSource;
223
224 private final ContentSource newSource;
225
226 /**
227 * Construct a pair of sources.
228 *
229 * @param oldSource
230 * source to read the old side of a DiffEntry.
231 * @param newSource
232 * source to read the new side of a DiffEntry.
233 */
234 public Pair(ContentSource oldSource, ContentSource newSource) {
235 this.oldSource = oldSource;
236 this.newSource = newSource;
237 }
238
239 /**
240 * Determine the size of the object.
241 *
242 * @param side
243 * which side of the entry to read (OLD or NEW).
244 * @param ent
245 * the entry to examine.
246 * @return the size in bytes.
247 * @throws IOException
248 * the file cannot be accessed.
249 */
250 public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
251 switch (side) {
252 case OLD:
253 return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
254 case NEW:
255 return newSource.size(ent.newPath, ent.newId.toObjectId());
256 default:
257 throw new IllegalArgumentException();
258 }
259 }
260
261 /**
262 * Open the object.
263 *
264 * @param side
265 * which side of the entry to read (OLD or NEW).
266 * @param ent
267 * the entry to examine.
268 * @return a loader that can supply the content of the file. The loader
269 * must be used before another loader can be obtained from this
270 * same source.
271 * @throws IOException
272 * the file cannot be accessed.
273 */
274 public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
275 throws IOException {
276 switch (side) {
277 case OLD:
278 return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
279 case NEW:
280 return newSource.open(ent.newPath, ent.newId.toObjectId());
281 default:
282 throw new IllegalArgumentException();
283 }
284 }
285 }
286 }