/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jan 31, 2007 */ package com.bigdata.rawstore; import java.io.File; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; import com.bigdata.counters.CounterSet; import com.bigdata.journal.TemporaryRawStore; import com.bigdata.mdi.IResourceMetadata; /** * A purely transient append-only implementation useful when data need to be * buffered in memory. The writes are stored in an {@link ArrayList}. * <p> * Note: it is safe to NOT call {@link #close()} on this implementation. The * implementation does not contain things like {@link ExecutorService}s that * would hang around unless explicitly shutdown. * * @see {@link TemporaryRawStore}, which provides a more scalable solution for * temporary data. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class SimpleMemoryRawStore extends AbstractRawWormStore { private boolean open = true; private final UUID uuid = UUID.randomUUID(); /** * The #of bytes written so far. This is used to generate the address values * returned by {@link #write(ByteBuffer)}. This is necessary in order for * this implementation to assign addresses in the same manner as they would * be assigned by an implementation using an append only byte[] or file. */ protected int nextOffset = 0; /** * Maps an address onto the index in {@link #records} at which the data for * that address was written. */ private final Map<Long,Integer> addrs; /** * The buffered records in the order written. If a record is deleted then * that element in the list will be a [null] value. */ protected final ArrayList<byte[]> records; /** * Uses an initial capacity of 1000 records. */ public SimpleMemoryRawStore() { this(1000); } /** * * @param capacity * The #of records that you expect to store (non-negative). If * the capacity is exceeded then the internal {@link ArrayList} * will be grown as necessary. */ public SimpleMemoryRawStore(int capacity) { /* * Note: The #of offset bits is restricted to 31 since we can not * address more an array having more than 31 unsigned bits of length in * Java's memory model. */ super(31); if (capacity < 0) throw new IllegalArgumentException("capacity is negative"); records = new ArrayList<byte[]>(capacity); // estimate hash table capacity to avoid resizing. addrs = new HashMap<Long,Integer>((int)(capacity*1.25)); } @Override public boolean isOpen() { return open; } @Override public boolean isReadOnly() { if (!open) throw new IllegalArgumentException(); return false; } @Override public boolean isStable() { return false; } @Override public boolean isFullyBuffered() { return true; } @Override public UUID getUUID() { return uuid; } @Override public IResourceMetadata getResourceMetadata() { return new ResourceMetadata(uuid); } /** * Static class since must be {@link Serializable}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ private static class ResourceMetadata implements IResourceMetadata { /** * */ private static final long serialVersionUID = -8333003625527191826L; private final UUID uuid; public ResourceMetadata(UUID uuid) { this.uuid = uuid; } @Override public boolean equals(IResourceMetadata o) { return this == o; } @Override public long getCreateTime() { // does not support commit return 0L; } @Override public long getCommitTime() { // does not support commit return 0L; } @Override public String getFile() { // no backing file. return null; } @Override public UUID getUUID() { return uuid; } @Override public boolean isIndexSegment() { // not index segment. return false; } @Override public boolean isJournal() { // not journal. return false; } // public long size() { // // // #of bytes not available. // return 0L; // // } } /** * {@inheritDoc} * <P> * This always returns <code>null</code>. */ @Override public File getFile() { return null; } @Override public void close() { if( !open ) throw new IllegalStateException(); open = false; // discard all the records. records.clear(); } @Override public void deleteResources() { if(open) throw new IllegalStateException(); // if (LRUNexus.INSTANCE != null) { // // LRUNexus.INSTANCE.deleteCache(getUUID()); // // } } @Override public void destroy() { if(isOpen()) close(); deleteResources(); } @Override public ByteBuffer read(final long addr) { if (addr == 0L) throw new IllegalArgumentException("Address is 0L"); // final int offset = (int)getOffset(addr); final int nbytes = getByteCount(addr); if(nbytes==0) { throw new IllegalArgumentException( "Address encodes record length of zero"); } Integer index = addrs.get(addr); if(index==null) { throw new IllegalArgumentException("Address never written."); } final byte[] b = records.get(index); if(b == null) { throw new IllegalArgumentException("Record was deleted"); } if(b.length != nbytes) { throw new RuntimeException("Bad address / data"); } // return a read-only view onto the data in the store. return ByteBuffer.wrap(b).asReadOnlyBuffer(); } @Override public long write(final ByteBuffer data) { if (data == null) throw new IllegalArgumentException("Buffer is null"); // #of bytes to store. final int nbytes = data.remaining(); if (nbytes == 0) throw new IllegalArgumentException("No bytes remaining in buffer"); // allocate a new array that is an exact fit for the data. final byte[] b = new byte[nbytes]; // copy the data into the array. data.get(b); // the next offset. final int offset = nextOffset; // increment by the #of bytes written. nextOffset += nbytes; // the position in the records[] where this record is stored. final int index = records.size(); // add the record to the records array. records.add(b); // formulate the address that can be used to recover that record. long addr = toAddr(nbytes, offset); // save the mapping from the addr to the records[]. addrs.put(addr,index); return addr; } // public void delete(long addr) { // // if(addr==0L) throw new IllegalArgumentException("Address is 0L"); // //// final long offset = getOffset(addr); // // final int nbytes = getByteCount(addr); // // if(nbytes==0) { // // throw new IllegalArgumentException( // "Address encodes record length of zero"); // // } // // Integer index = addrs.get(addr); // // if(index==null) { // // throw new IllegalArgumentException("Address never written."); // // } // // byte[] b = records.get(index); // // if(b == null) { // // throw new IllegalArgumentException("Record was deleted"); // // } // // if(b.length != nbytes) { // // throw new RuntimeException("Bad address / data"); // // } // // // release that record. // records.set(index, null); // // } @Override public void force(boolean metadata) { // NOP. } @Override public long size() { return nextOffset; } @Override public CounterSet getCounters() { return new CounterSet(); } }