View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.diff;
12  
13  import java.io.BufferedInputStream;
14  import java.io.FileNotFoundException;
15  import java.io.IOException;
16  import java.io.InputStream;
17  
18  import org.eclipse.jgit.errors.LargeObjectException;
19  import org.eclipse.jgit.errors.MissingObjectException;
20  import org.eclipse.jgit.lib.Constants;
21  import org.eclipse.jgit.lib.ObjectId;
22  import org.eclipse.jgit.lib.ObjectLoader;
23  import org.eclipse.jgit.lib.ObjectReader;
24  import org.eclipse.jgit.lib.ObjectStream;
25  import org.eclipse.jgit.treewalk.TreeWalk;
26  import org.eclipse.jgit.treewalk.WorkingTreeIterator;
27  import org.eclipse.jgit.treewalk.filter.PathFilter;
28  
29  /**
30   * Supplies the content of a file for
31   * {@link org.eclipse.jgit.diff.DiffFormatter}.
32   * <p>
33   * A content source is not thread-safe. Sources may contain state, including
34   * information about the last ObjectLoader they returned. Callers must be
35   * careful to ensure there is no more than one ObjectLoader pending on any
36   * source, at any time.
37   */
38  public abstract class ContentSource {
39  	/**
40  	 * Construct a content source for an ObjectReader.
41  	 *
42  	 * @param reader
43  	 *            the reader to obtain blobs from.
44  	 * @return a source wrapping the reader.
45  	 */
46  	public static ContentSource create(ObjectReader reader) {
47  		return new ObjectReaderSource(reader);
48  	}
49  
50  	/**
51  	 * Construct a content source for a working directory.
52  	 *
53  	 * If the iterator is a {@link org.eclipse.jgit.treewalk.FileTreeIterator}
54  	 * an optimized version is used that doesn't require seeking through a
55  	 * TreeWalk.
56  	 *
57  	 * @param iterator
58  	 *            the iterator to obtain source files through.
59  	 * @return a content source wrapping the iterator.
60  	 */
61  	public static ContentSource create(WorkingTreeIterator iterator) {
62  		return new WorkingTreeSource(iterator);
63  	}
64  
65  	/**
66  	 * Determine the size of the object.
67  	 *
68  	 * @param path
69  	 *            the path of the file, relative to the root of the repository.
70  	 * @param id
71  	 *            blob id of the file, if known.
72  	 * @return the size in bytes.
73  	 * @throws java.io.IOException
74  	 *             the file cannot be accessed.
75  	 */
76  	public abstract long size(String path, ObjectId id) throws IOException;
77  
78  	/**
79  	 * Open the object.
80  	 *
81  	 * @param path
82  	 *            the path of the file, relative to the root of the repository.
83  	 * @param id
84  	 *            blob id of the file, if known.
85  	 * @return a loader that can supply the content of the file. The loader must
86  	 *         be used before another loader can be obtained from this same
87  	 *         source.
88  	 * @throws java.io.IOException
89  	 *             the file cannot be accessed.
90  	 */
91  	public abstract ObjectLoader open(String path, ObjectId id)
92  			throws IOException;
93  
94  	private static class ObjectReaderSource extends ContentSource {
95  		private final ObjectReader reader;
96  
97  		ObjectReaderSource(ObjectReader reader) {
98  			this.reader = reader;
99  		}
100 
101 		@Override
102 		public long size(String path, ObjectId id) throws IOException {
103 			try {
104 				return reader.getObjectSize(id, Constants.OBJ_BLOB);
105 			} catch (MissingObjectException ignore) {
106 				return 0;
107 			}
108 		}
109 
110 		@Override
111 		public ObjectLoader open(String path, ObjectId id) throws IOException {
112 			return reader.open(id, Constants.OBJ_BLOB);
113 		}
114 	}
115 
116 	private static class WorkingTreeSource extends ContentSource {
117 		private final TreeWalk tw;
118 
119 		private final WorkingTreeIterator iterator;
120 
121 		private String current;
122 
123 		WorkingTreeIterator ptr;
124 
125 		WorkingTreeSource(WorkingTreeIterator iterator) {
126 			this.tw = new TreeWalk((ObjectReader) null);
127 			this.tw.setRecursive(true);
128 			this.iterator = iterator;
129 		}
130 
131 		@Override
132 		public long size(String path, ObjectId id) throws IOException {
133 			seek(path);
134 			return ptr.getEntryLength();
135 		}
136 
137 		@Override
138 		public ObjectLoader open(String path, ObjectId id) throws IOException {
139 			seek(path);
140 			long entrySize = ptr.getEntryContentLength();
141 			return new ObjectLoader() {
142 				@Override
143 				public long getSize() {
144 					return entrySize;
145 				}
146 
147 				@Override
148 				public int getType() {
149 					return ptr.getEntryFileMode().getObjectType();
150 				}
151 
152 				@Override
153 				public ObjectStream openStream() throws MissingObjectException,
154 						IOException {
155 					long contentLength = entrySize;
156 					InputStream in = ptr.openEntryStream();
157 					in = new BufferedInputStream(in);
158 					return new ObjectStream.Filter(getType(), contentLength, in);
159 				}
160 
161 				@Override
162 				public boolean isLarge() {
163 					return true;
164 				}
165 
166 				@Override
167 				public byte[] getCachedBytes() throws LargeObjectException {
168 					throw new LargeObjectException();
169 				}
170 			};
171 		}
172 
173 		private void seek(String path) throws IOException {
174 			if (!path.equals(current)) {
175 				iterator.reset();
176 				tw.reset();
177 				tw.addTree(iterator);
178 				tw.setFilter(PathFilter.create(path));
179 				current = path;
180 				if (!tw.next())
181 					throw new FileNotFoundException(path);
182 				ptr = tw.getTree(0, WorkingTreeIterator.class);
183 				if (ptr == null)
184 					throw new FileNotFoundException(path);
185 			}
186 		}
187 	}
188 
189 	/** A pair of sources to access the old and new sides of a DiffEntry. */
190 	public static final class Pair {
191 		private final ContentSource oldSource;
192 
193 		private final ContentSource newSource;
194 
195 		/**
196 		 * Construct a pair of sources.
197 		 *
198 		 * @param oldSource
199 		 *            source to read the old side of a DiffEntry.
200 		 * @param newSource
201 		 *            source to read the new side of a DiffEntry.
202 		 */
203 		public Pair(ContentSource/../org/eclipse/jgit/diff/ContentSource.html#ContentSource">ContentSource oldSource, ContentSource newSource) {
204 			this.oldSource = oldSource;
205 			this.newSource = newSource;
206 		}
207 
208 		/**
209 		 * Determine the size of the object.
210 		 *
211 		 * @param side
212 		 *            which side of the entry to read (OLD or NEW).
213 		 * @param ent
214 		 *            the entry to examine.
215 		 * @return the size in bytes.
216 		 * @throws IOException
217 		 *             the file cannot be accessed.
218 		 */
219 		public long size(DiffEntry.Side side, DiffEntry ent) throws IOException {
220 			switch (side) {
221 			case OLD:
222 				return oldSource.size(ent.oldPath, ent.oldId.toObjectId());
223 			case NEW:
224 				return newSource.size(ent.newPath, ent.newId.toObjectId());
225 			default:
226 				throw new IllegalArgumentException();
227 			}
228 		}
229 
230 		/**
231 		 * Open the object.
232 		 *
233 		 * @param side
234 		 *            which side of the entry to read (OLD or NEW).
235 		 * @param ent
236 		 *            the entry to examine.
237 		 * @return a loader that can supply the content of the file. The loader
238 		 *         must be used before another loader can be obtained from this
239 		 *         same source.
240 		 * @throws IOException
241 		 *             the file cannot be accessed.
242 		 */
243 		public ObjectLoader open(DiffEntry.Side side, DiffEntry ent)
244 				throws IOException {
245 			switch (side) {
246 			case OLD:
247 				return oldSource.open(ent.oldPath, ent.oldId.toObjectId());
248 			case NEW:
249 				return newSource.open(ent.newPath, ent.newId.toObjectId());
250 			default:
251 				throw new IllegalArgumentException();
252 			}
253 		}
254 	}
255 }