View Javadoc
1   package org.eclipse.jgit.internal.storage.dfs;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.FileNotFoundException;
5   import java.io.IOException;
6   import java.nio.ByteBuffer;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Objects;
13  import java.util.concurrent.ConcurrentHashMap;
14  import java.util.concurrent.ConcurrentMap;
15  import java.util.concurrent.atomic.AtomicInteger;
16  
17  import org.eclipse.jgit.internal.storage.pack.PackExt;
18  import org.eclipse.jgit.lib.ObjectId;
19  import org.eclipse.jgit.lib.ObjectIdRef;
20  import org.eclipse.jgit.lib.Ref;
21  import org.eclipse.jgit.lib.Ref.Storage;
22  import org.eclipse.jgit.lib.SymbolicRef;
23  import org.eclipse.jgit.revwalk.RevWalk;
24  import org.eclipse.jgit.util.RefList;
25  
26  /**
27   * Git repository stored entirely in the local process memory.
28   * <p>
29   * This implementation builds on the DFS repository by storing all reference and
30   * object data in the local process. It is not very efficient and exists only
31   * for unit testing and small experiments.
32   * <p>
33   * The repository is thread-safe. Memory used is released only when this object
34   * is garbage collected. Closing the repository has no impact on its memory.
35   */
36  public class InMemoryRepository extends DfsRepository {
37  	/** Builder for in-memory repositories. */
38  	public static class Builder
39  			extends DfsRepositoryBuilder<Builder, InMemoryRepository> {
40  		@Override
41  		public InMemoryRepository build() throws IOException {
42  			return new InMemoryRepository(this);
43  		}
44  	}
45  
46  	private static final AtomicInteger packId = new AtomicInteger();
47  
48  	private final DfsObjDatabase objdb;
49  
50  	private final DfsRefDatabase refdb;
51  
52  	/**
53  	 * Initialize a new in-memory repository.
54  	 *
55  	 * @param repoDesc
56  	 *            description of the repository.
57  	 * @since 2.0
58  	 */
59  	public InMemoryRepository(DfsRepositoryDescription repoDesc) {
60  		this(new Builder().setRepositoryDescription(repoDesc));
61  	}
62  
63  	private InMemoryRepository(Builder builder) {
64  		super(builder);
65  		objdb = new MemObjDatabase(this);
66  		refdb = new MemRefDatabase();
67  	}
68  
69  	@Override
70  	public DfsObjDatabase getObjectDatabase() {
71  		return objdb;
72  	}
73  
74  	@Override
75  	public DfsRefDatabase getRefDatabase() {
76  		return refdb;
77  	}
78  
79  	private class MemObjDatabase extends DfsObjDatabase {
80  		private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>();
81  
82  		MemObjDatabase(DfsRepository repo) {
83  			super(repo, new DfsReaderOptions());
84  		}
85  
86  		@Override
87  		protected synchronized List<DfsPackDescription> listPacks() {
88  			return packs;
89  		}
90  
91  		@Override
92  		protected DfsPackDescription newPack(PackSource source) {
93  			int id = packId.incrementAndGet();
94  			DfsPackDescription desc = new MemPack(
95  					"pack-" + id + "-" + source.name(), //$NON-NLS-1$ //$NON-NLS-2$
96  					getRepository().getDescription());
97  			return desc.setPackSource(source);
98  		}
99  
100 		@Override
101 		protected synchronized void commitPackImpl(
102 				Collection<DfsPackDescription> desc,
103 				Collection<DfsPackDescription> replace) {
104 			List<DfsPackDescription> n;
105 			n = new ArrayList<DfsPackDescription>(desc.size() + packs.size());
106 			n.addAll(desc);
107 			n.addAll(packs);
108 			if (replace != null)
109 				n.removeAll(replace);
110 			packs = n;
111 		}
112 
113 		@Override
114 		protected void rollbackPack(Collection<DfsPackDescription> desc) {
115 			// Do nothing. Pack is not recorded until commitPack.
116 		}
117 
118 		@Override
119 		protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext)
120 				throws FileNotFoundException, IOException {
121 			MemPack memPack = (MemPack) desc;
122 			byte[] file = memPack.fileMap.get(ext);
123 			if (file == null)
124 				throw new FileNotFoundException(desc.getFileName(ext));
125 			return new ByteArrayReadableChannel(file);
126 		}
127 
128 		@Override
129 		protected DfsOutputStream writeFile(
130 				DfsPackDescription desc, final PackExt ext) throws IOException {
131 			final MemPack memPack = (MemPack) desc;
132 			return new Out() {
133 				@Override
134 				public void flush() {
135 					memPack.fileMap.put(ext, getData());
136 				}
137 			};
138 		}
139 	}
140 
141 	private static class MemPack extends DfsPackDescription {
142 		private final Map<PackExt, byte[]>
143 				fileMap = new HashMap<PackExt, byte[]>();
144 
145 		MemPack(String name, DfsRepositoryDescription repoDesc) {
146 			super(repoDesc, name);
147 		}
148 	}
149 
150 	private abstract static class Out extends DfsOutputStream {
151 		private final ByteArrayOutputStream dst = new ByteArrayOutputStream();
152 
153 		private byte[] data;
154 
155 		@Override
156 		public void write(byte[] buf, int off, int len) {
157 			data = null;
158 			dst.write(buf, off, len);
159 		}
160 
161 		@Override
162 		public int read(long position, ByteBuffer buf) {
163 			byte[] d = getData();
164 			int n = Math.min(buf.remaining(), d.length - (int) position);
165 			if (n == 0)
166 				return -1;
167 			buf.put(d, (int) position, n);
168 			return n;
169 		}
170 
171 		byte[] getData() {
172 			if (data == null)
173 				data = dst.toByteArray();
174 			return data;
175 		}
176 
177 		@Override
178 		public abstract void flush();
179 
180 		@Override
181 		public void close() {
182 			flush();
183 		}
184 
185 	}
186 
187 	private static class ByteArrayReadableChannel implements ReadableChannel {
188 		private final byte[] data;
189 
190 		private int position;
191 
192 		private boolean open = true;
193 
194 		ByteArrayReadableChannel(byte[] buf) {
195 			data = buf;
196 		}
197 
198 		public int read(ByteBuffer dst) {
199 			int n = Math.min(dst.remaining(), data.length - position);
200 			if (n == 0)
201 				return -1;
202 			dst.put(data, position, n);
203 			position += n;
204 			return n;
205 		}
206 
207 		public void close() {
208 			open = false;
209 		}
210 
211 		public boolean isOpen() {
212 			return open;
213 		}
214 
215 		public long position() {
216 			return position;
217 		}
218 
219 		public void position(long newPosition) {
220 			position = (int) newPosition;
221 		}
222 
223 		public long size() {
224 			return data.length;
225 		}
226 
227 		public int blockSize() {
228 			return 0;
229 		}
230 
231 		public void setReadAheadBytes(int b) {
232 			// Unnecessary on a byte array.
233 		}
234 	}
235 
236 	private class MemRefDatabase extends DfsRefDatabase {
237 		private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>();
238 
239 		MemRefDatabase() {
240 			super(InMemoryRepository.this);
241 		}
242 
243 		@Override
244 		protected RefCache scanAllRefs() throws IOException {
245 			RefList.Builder<Ref> ids = new RefList.Builder<Ref>();
246 			RefList.Builder<Ref> sym = new RefList.Builder<Ref>();
247 			for (Ref ref : refs.values()) {
248 				if (ref.isSymbolic())
249 					sym.add(ref);
250 				ids.add(ref);
251 			}
252 			ids.sort();
253 			sym.sort();
254 			return new RefCache(ids.toRefList(), sym.toRefList());
255 		}
256 
257 		@Override
258 		protected boolean compareAndPut(Ref oldRef, Ref newRef)
259 				throws IOException {
260 			ObjectId id = newRef.getObjectId();
261 			if (id != null) {
262 				try (RevWalk rw = new RevWalk(getRepository())) {
263 					// Validate that the target exists in a new RevWalk, as the RevWalk
264 					// from the RefUpdate might be reading back unflushed objects.
265 					rw.parseAny(id);
266 				}
267 			}
268 			String name = newRef.getName();
269 			if (oldRef == null)
270 				return refs.putIfAbsent(name, newRef) == null;
271 
272 			synchronized (refs) {
273 				Ref cur = refs.get(name);
274 				Ref toCompare = cur;
275 				if (toCompare != null) {
276 					if (toCompare.isSymbolic()) {
277 						// Arm's-length dereference symrefs before the compare, since
278 						// DfsRefUpdate#doLink(String) stores them undereferenced.
279 						Ref leaf = toCompare.getLeaf();
280 						if (leaf.getObjectId() == null) {
281 							leaf = refs.get(leaf.getName());
282 							if (leaf.isSymbolic())
283 								// Not supported at the moment.
284 								throw new IllegalArgumentException();
285 							toCompare = new SymbolicRef(
286 									name,
287 									new ObjectIdRef.Unpeeled(
288 											Storage.NEW,
289 											leaf.getName(),
290 											leaf.getObjectId()));
291 						} else
292 							toCompare = toCompare.getLeaf();
293 					}
294 					if (eq(toCompare, oldRef))
295 						return refs.replace(name, cur, newRef);
296 				}
297 			}
298 
299 			if (oldRef.getStorage() == Storage.NEW)
300 				return refs.putIfAbsent(name, newRef) == null;
301 
302 			return false;
303 		}
304 
305 		@Override
306 		protected boolean compareAndRemove(Ref oldRef) throws IOException {
307 			String name = oldRef.getName();
308 			Ref cur = refs.get(name);
309 			if (cur != null && eq(cur, oldRef))
310 				return refs.remove(name, cur);
311 			else
312 				return false;
313 		}
314 
315 		private boolean eq(Ref a, Ref b) {
316 			if (!Objects.equals(a.getName(), b.getName()))
317 				return false;
318 			// Compare leaf object IDs, since the oldRef passed into compareAndPut
319 			// when detaching a symref is an ObjectIdRef.
320 			return Objects.equals(a.getLeaf().getObjectId(),
321 					b.getLeaf().getObjectId());
322 		}
323 	}
324 }