package org.eclipse.jgit.storage.dfs; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.util.RefList; /** * Git repository stored entirely in the local process memory. *

* This implementation builds on the DFS repository by storing all reference and * object data in the local process. It is not very efficient and exists only * for unit testing and small experiments. *

* The repository is thread-safe. Memory used is released only when this object * is garbage collected. Closing the repository has no impact on its memory. */ public class InMemoryRepository extends DfsRepository { private final DfsObjDatabase objdb; private final DfsRefDatabase refdb; /** * Initialize a new in-memory repository. * * @param repoDesc * description of the repository. * @since 2.0 */ public InMemoryRepository(DfsRepositoryDescription repoDesc) { super(new DfsRepositoryBuilder() { @Override public InMemoryRepository build() throws IOException { throw new UnsupportedOperationException(); } }.setRepositoryDescription(repoDesc)); objdb = new MemObjDatabase(this); refdb = new MemRefDatabase(); } @Override public DfsObjDatabase getObjectDatabase() { return objdb; } @Override public DfsRefDatabase getRefDatabase() { return refdb; } private class MemObjDatabase extends DfsObjDatabase { private final AtomicInteger packId = new AtomicInteger(); private List packs = new ArrayList(); MemObjDatabase(DfsRepository repo) { super(repo, new DfsReaderOptions()); } @Override protected synchronized List listPacks() { return packs; } @Override protected DfsPackDescription newPack(PackSource source) { int id = packId.incrementAndGet(); return new MemPack("pack-" + id + "-" + source.name(), getRepository().getDescription()); } @Override protected synchronized void commitPackImpl( Collection desc, Collection replace) { List n; n = new ArrayList(desc.size() + packs.size()); n.addAll(desc); n.addAll(packs); if (replace != null) n.removeAll(replace); packs = n; } @Override protected void rollbackPack(Collection desc) { // Do nothing. Pack is not recorded until commitPack. } @Override protected ReadableChannel openPackFile(DfsPackDescription desc) throws FileNotFoundException { MemPack memPack = (MemPack) desc; if (memPack.packFile == null) throw new FileNotFoundException(desc.getPackName()); return new ByteArrayReadableChannel(memPack.packFile); } @Override protected ReadableChannel openPackIndex(DfsPackDescription desc) throws FileNotFoundException { MemPack memPack = (MemPack) desc; if (memPack.packIndex == null) throw new FileNotFoundException(desc.getIndexName()); return new ByteArrayReadableChannel(memPack.packIndex); } @Override protected DfsOutputStream writePackFile(DfsPackDescription desc) { final MemPack memPack = (MemPack) desc; return new Out() { @Override public void flush() { memPack.packFile = getData(); } }; } @Override protected DfsOutputStream writePackIndex(DfsPackDescription desc) { final MemPack memPack = (MemPack) desc; return new Out() { @Override public void flush() { memPack.packIndex = getData(); } }; } } private static class MemPack extends DfsPackDescription { private byte[] packFile; private byte[] packIndex; MemPack(String name, DfsRepositoryDescription repoDesc) { super(repoDesc, name); } } private abstract static class Out extends DfsOutputStream { private final ByteArrayOutputStream dst = new ByteArrayOutputStream(); private byte[] data; @Override public void write(byte[] buf, int off, int len) { data = null; dst.write(buf, off, len); } @Override public int read(long position, ByteBuffer buf) { byte[] d = getData(); int n = Math.min(buf.remaining(), d.length - (int) position); if (n == 0) return -1; buf.put(d, (int) position, n); return n; } byte[] getData() { if (data == null) data = dst.toByteArray(); return data; } @Override public abstract void flush(); @Override public void close() { flush(); } } private static class ByteArrayReadableChannel implements ReadableChannel { private final byte[] data; private int position; private boolean open = true; ByteArrayReadableChannel(byte[] buf) { data = buf; } public int read(ByteBuffer dst) { int n = Math.min(dst.remaining(), data.length - position); if (n == 0) return -1; dst.put(data, position, n); position += n; return n; } public void close() { open = false; } public boolean isOpen() { return open; } public long position() { return position; } public void position(long newPosition) { position = (int) newPosition; } public long size() { return data.length; } public int blockSize() { return 0; } } private class MemRefDatabase extends DfsRefDatabase { private final ConcurrentMap refs = new ConcurrentHashMap(); MemRefDatabase() { super(InMemoryRepository.this); } @Override protected RefCache scanAllRefs() throws IOException { RefList.Builder ids = new RefList.Builder(); RefList.Builder sym = new RefList.Builder(); for (Ref ref : refs.values()) { if (ref.isSymbolic()) sym.add(ref); ids.add(ref); } ids.sort(); sym.sort(); return new RefCache(ids.toRefList(), sym.toRefList()); } @Override protected boolean compareAndPut(Ref oldRef, Ref newRef) throws IOException { String name = newRef.getName(); if (oldRef == null || oldRef.getStorage() == Storage.NEW) return refs.putIfAbsent(name, newRef) == null; Ref cur = refs.get(name); if (cur != null && eq(cur, oldRef)) return refs.replace(name, cur, newRef); else return false; } @Override protected boolean compareAndRemove(Ref oldRef) throws IOException { String name = oldRef.getName(); Ref cur = refs.get(name); if (cur != null && eq(cur, oldRef)) return refs.remove(name, cur); else return false; } private boolean eq(Ref a, Ref b) { if (a.getObjectId() == null && b.getObjectId() == null) return true; if (a.getObjectId() != null) return a.getObjectId().equals(b.getObjectId()); return false; } } }