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.List;
10  import java.util.concurrent.atomic.AtomicInteger;
11  
12  import org.eclipse.jgit.annotations.Nullable;
13  import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
14  import org.eclipse.jgit.internal.storage.pack.PackExt;
15  import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
16  import org.eclipse.jgit.lib.RefDatabase;
17  
18  /**
19   * Git repository stored entirely in the local process memory.
20   * <p>
21   * This implementation builds on the DFS repository by storing all reference and
22   * object data in the local process. It is not very efficient and exists only
23   * for unit testing and small experiments.
24   * <p>
25   * The repository is thread-safe. Memory used is released only when this object
26   * is garbage collected. Closing the repository has no impact on its memory.
27   */
28  public class InMemoryRepository extends DfsRepository {
29  	/** Builder for in-memory repositories. */
30  	public static class Builder
31  			extends DfsRepositoryBuilder<Builder, InMemoryRepository> {
32  		@Override
33  		public InMemoryRepository build() throws IOException {
34  			return new InMemoryRepository(this);
35  		}
36  	}
37  
38  	static final AtomicInteger packId = new AtomicInteger();
39  
40  	private final MemObjDatabase objdb;
41  	private final MemRefDatabase refdb;
42  	private String gitwebDescription;
43  
44  	/**
45  	 * Initialize a new in-memory repository.
46  	 *
47  	 * @param repoDesc
48  	 *            description of the repository.
49  	 */
50  	public InMemoryRepository(DfsRepositoryDescription repoDesc) {
51  		this(new Builder().setRepositoryDescription(repoDesc));
52  	}
53  
54  	InMemoryRepository(Builder builder) {
55  		super(builder);
56  		objdb = new MemObjDatabase(this);
57  		refdb = new MemRefDatabase();
58  	}
59  
60  	/** {@inheritDoc} */
61  	@Override
62  	public MemObjDatabase getObjectDatabase() {
63  		return objdb;
64  	}
65  
66  	/** {@inheritDoc} */
67  	@Override
68  	public RefDatabase getRefDatabase() {
69  		return refdb;
70  	}
71  
72  	/**
73  	 * Enable (or disable) the atomic reference transaction support.
74  	 * <p>
75  	 * Useful for testing atomic support enabled or disabled.
76  	 *
77  	 * @param atomic
78  	 *            whether to use atomic reference transaction support
79  	 */
80  	public void setPerformsAtomicTransactions(boolean atomic) {
81  		refdb.performsAtomicTransactions = atomic;
82  	}
83  
84  	/** {@inheritDoc} */
85  	@Override
86  	@Nullable
87  	public String getGitwebDescription() {
88  		return gitwebDescription;
89  	}
90  
91  	/** {@inheritDoc} */
92  	@Override
93  	public void setGitwebDescription(@Nullable String d) {
94  		gitwebDescription = d;
95  	}
96  
97  	/** DfsObjDatabase used by InMemoryRepository. */
98  	public static class MemObjDatabase extends DfsObjDatabase {
99  		private List<DfsPackDescription> packs = new ArrayList<>();
100 		private int blockSize;
101 
102 		MemObjDatabase(DfsRepository repo) {
103 			super(repo, new DfsReaderOptions());
104 		}
105 
106 		/**
107 		 * @param blockSize
108 		 *            force a different block size for testing.
109 		 */
110 		public void setReadableChannelBlockSizeForTest(int blockSize) {
111 			this.blockSize = blockSize;
112 		}
113 
114 		@Override
115 		protected synchronized List<DfsPackDescription> listPacks() {
116 			return packs;
117 		}
118 
119 		@Override
120 		protected DfsPackDescription newPack(PackSource source) {
121 			int id = packId.incrementAndGet();
122 			return new MemPack(
123 					"pack-" + id + "-" + source.name(), //$NON-NLS-1$ //$NON-NLS-2$
124 					getRepository().getDescription(),
125 					source);
126 		}
127 
128 		@Override
129 		protected synchronized void commitPackImpl(
130 				Collection<DfsPackDescription> desc,
131 				Collection<DfsPackDescription> replace) {
132 			List<DfsPackDescription> n;
133 			n = new ArrayList<>(desc.size() + packs.size());
134 			n.addAll(desc);
135 			n.addAll(packs);
136 			if (replace != null)
137 				n.removeAll(replace);
138 			packs = n;
139 			clearCache();
140 		}
141 
142 		@Override
143 		protected void rollbackPack(Collection<DfsPackDescription> desc) {
144 			// Do nothing. Pack is not recorded until commitPack.
145 		}
146 
147 		@Override
148 		protected ReadableChannel openFile(DfsPackDescription desc, PackExt ext)
149 				throws FileNotFoundException, IOException {
150 			MemPack memPack = (MemPack) desc;
151 			byte[] file = memPack.get(ext);
152 			if (file == null)
153 				throw new FileNotFoundException(desc.getFileName(ext));
154 			return new ByteArrayReadableChannel(file, blockSize);
155 		}
156 
157 		@Override
158 		protected DfsOutputStream writeFile(DfsPackDescription desc,
159 				PackExt ext) throws IOException {
160 			MemPack memPack = (MemPack) desc;
161 			return new Out() {
162 				@Override
163 				public void flush() {
164 					memPack.put(ext, getData());
165 				}
166 			};
167 		}
168 
169 		@Override
170 		public long getApproximateObjectCount() {
171 			long count = 0;
172 			for (DfsPackDescription p : packs) {
173 				count += p.getObjectCount();
174 			}
175 			return count;
176 		}
177 	}
178 
179 	private static class MemPack extends DfsPackDescription {
180 		final byte[][] fileMap = new byte[PackExt.values().length][];
181 
182 		MemPack(String name, DfsRepositoryDescription repoDesc, PackSource source) {
183 			super(repoDesc, name, source);
184 		}
185 
186 		void put(PackExt ext, byte[] data) {
187 			fileMap[ext.getPosition()] = data;
188 		}
189 
190 		byte[] get(PackExt ext) {
191 			return fileMap[ext.getPosition()];
192 		}
193 	}
194 
195 	private abstract static class Out extends DfsOutputStream {
196 		private final ByteArrayOutputStream dst = new ByteArrayOutputStream();
197 		private byte[] data;
198 
199 		@Override
200 		public void write(byte[] buf, int off, int len) {
201 			data = null;
202 			dst.write(buf, off, len);
203 		}
204 
205 		@Override
206 		public int read(long position, ByteBuffer buf) {
207 			byte[] d = getData();
208 			int n = Math.min(buf.remaining(), d.length - (int) position);
209 			if (n == 0)
210 				return -1;
211 			buf.put(d, (int) position, n);
212 			return n;
213 		}
214 
215 		byte[] getData() {
216 			if (data == null)
217 				data = dst.toByteArray();
218 			return data;
219 		}
220 
221 		@Override
222 		public abstract void flush();
223 
224 		@Override
225 		public void close() {
226 			flush();
227 		}
228 	}
229 
230 	private static class ByteArrayReadableChannel implements ReadableChannel {
231 		private final byte[] data;
232 		private final int blockSize;
233 		private int position;
234 		private boolean open = true;
235 
236 		ByteArrayReadableChannel(byte[] buf, int blockSize) {
237 			data = buf;
238 			this.blockSize = blockSize;
239 		}
240 
241 		@Override
242 		public int read(ByteBuffer dst) {
243 			int n = Math.min(dst.remaining(), data.length - position);
244 			if (n == 0)
245 				return -1;
246 			dst.put(data, position, n);
247 			position += n;
248 			return n;
249 		}
250 
251 		@Override
252 		public void close() {
253 			open = false;
254 		}
255 
256 		@Override
257 		public boolean isOpen() {
258 			return open;
259 		}
260 
261 		@Override
262 		public long position() {
263 			return position;
264 		}
265 
266 		@Override
267 		public void position(long newPosition) {
268 			position = (int) newPosition;
269 		}
270 
271 		@Override
272 		public long size() {
273 			return data.length;
274 		}
275 
276 		@Override
277 		public int blockSize() {
278 			return blockSize;
279 		}
280 
281 		@Override
282 		public void setReadAheadBytes(int b) {
283 			// Unnecessary on a byte array.
284 		}
285 	}
286 
287 	/** DfsRefDatabase used by InMemoryRepository. */
288 	protected class MemRefDatabase extends DfsReftableDatabase {
289 		boolean performsAtomicTransactions = true;
290 
291 		/** Initialize a new in-memory ref database. */
292 		protected MemRefDatabase() {
293 			super(InMemoryRepository.this);
294 		}
295 
296 		@Override
297 		public ReftableConfig getReftableConfig() {
298 			ReftableConfig cfg = new ReftableConfig();
299 			cfg.setAlignBlocks(false);
300 			cfg.setIndexObjects(false);
301 			cfg.fromConfig(getRepository().getConfig());
302 			return cfg;
303 		}
304 
305 		@Override
306 		public boolean performsAtomicTransactions() {
307 			return performsAtomicTransactions;
308 		}
309 	}
310 }