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