/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.storage;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* This is an in-memory byte-based data structure with the possibility to be stored on flush().
* Thread safe.
* <p>
*
* @author Peter Karich
*/
public class RAMDataAccess extends AbstractDataAccess {
private byte[][] segments = new byte[0][];
private boolean store;
RAMDataAccess(String name, String location, boolean store, ByteOrder order) {
super(name, location, order);
this.store = store;
}
/**
* @param store true if in-memory data should be saved when calling flush
*/
public RAMDataAccess store(boolean store) {
this.store = store;
return this;
}
@Override
public boolean isStoring() {
return store;
}
@Override
public DataAccess copyTo(DataAccess da) {
if (da instanceof RAMDataAccess) {
copyHeader(da);
RAMDataAccess rda = (RAMDataAccess) da;
// TODO PERFORMANCE we could reuse rda segments!
rda.segments = new byte[segments.length][];
for (int i = 0; i < segments.length; i++) {
byte[] area = segments[i];
rda.segments[i] = Arrays.copyOf(area, area.length);
}
rda.setSegmentSize(segmentSizeInBytes);
// leave id, store and close unchanged
return da;
} else {
return super.copyTo(da);
}
}
@Override
public RAMDataAccess create(long bytes) {
if (segments.length > 0)
throw new IllegalThreadStateException("already created");
// initialize transient values
setSegmentSize(segmentSizeInBytes);
ensureCapacity(Math.max(10 * 4, bytes));
return this;
}
@Override
public boolean ensureCapacity(long bytes) {
if (bytes < 0)
throw new IllegalArgumentException("new capacity has to be strictly positive");
long cap = getCapacity();
long newBytes = bytes - cap;
if (newBytes <= 0)
return false;
int segmentsToCreate = (int) (newBytes / segmentSizeInBytes);
if (newBytes % segmentSizeInBytes != 0)
segmentsToCreate++;
try {
byte[][] newSegs = Arrays.copyOf(segments, segments.length + segmentsToCreate);
for (int i = segments.length; i < newSegs.length; i++) {
newSegs[i] = new byte[1 << segmentSizePower];
}
segments = newSegs;
} catch (OutOfMemoryError err) {
throw new OutOfMemoryError(err.getMessage() + " - problem when allocating new memory. Old capacity: "
+ cap + ", new bytes:" + newBytes + ", segmentSizeIntsPower:" + segmentSizePower
+ ", new segments:" + segmentsToCreate + ", existing:" + segments.length);
}
return true;
}
@Override
public boolean loadExisting() {
if (segments.length > 0)
throw new IllegalStateException("already initialized");
if (isClosed())
throw new IllegalStateException("already closed");
if (!store)
return false;
File file = new File(getFullName());
if (!file.exists() || file.length() == 0)
return false;
try {
RandomAccessFile raFile = new RandomAccessFile(getFullName(), "r");
try {
long byteCount = readHeader(raFile) - HEADER_OFFSET;
if (byteCount < 0)
return false;
raFile.seek(HEADER_OFFSET);
// raFile.readInt() <- too slow
int segmentCount = (int) (byteCount / segmentSizeInBytes);
if (byteCount % segmentSizeInBytes != 0)
segmentCount++;
segments = new byte[segmentCount][];
for (int s = 0; s < segmentCount; s++) {
byte[] bytes = new byte[segmentSizeInBytes];
int read = raFile.read(bytes);
if (read <= 0)
throw new IllegalStateException("segment " + s + " is empty? " + toString());
segments[s] = bytes;
}
return true;
} finally {
raFile.close();
}
} catch (IOException ex) {
throw new RuntimeException("Problem while loading " + getFullName(), ex);
}
}
@Override
public void flush() {
if (closed)
throw new IllegalStateException("already closed");
if (!store)
return;
try {
RandomAccessFile raFile = new RandomAccessFile(getFullName(), "rw");
try {
long len = getCapacity();
writeHeader(raFile, len, segmentSizeInBytes);
raFile.seek(HEADER_OFFSET);
// raFile.writeInt() <- too slow, so copy into byte array
for (int s = 0; s < segments.length; s++) {
byte area[] = segments[s];
raFile.write(area);
}
} finally {
raFile.close();
}
} catch (Exception ex) {
throw new RuntimeException("Couldn't store bytes to " + toString(), ex);
}
}
@Override
public final void setInt(long bytePos, int value) {
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
assert index + 4 <= segmentSizeInBytes : "integer cannot be distributed over two segments";
bitUtil.fromInt(segments[bufferIndex], value, index);
}
@Override
public final int getInt(long bytePos) {
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
assert index + 4 <= segmentSizeInBytes : "integer cannot be distributed over two segments";
if (bufferIndex > segments.length) {
LoggerFactory.getLogger(getClass()).error(getName() + ", segments:" + segments.length
+ ", bufIndex:" + bufferIndex + ", bytePos:" + bytePos
+ ", segPower:" + segmentSizePower);
}
return bitUtil.toInt(segments[bufferIndex], index);
}
@Override
public final void setShort(long bytePos, short value) {
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
assert index + 2 <= segmentSizeInBytes : "integer cannot be distributed over two segments";
bitUtil.fromShort(segments[bufferIndex], value, index);
}
@Override
public final short getShort(long bytePos) {
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
assert index + 2 <= segmentSizeInBytes : "integer cannot be distributed over two segments";
return bitUtil.toShort(segments[bufferIndex], index);
}
@Override
public void setBytes(long bytePos, byte[] values, int length) {
assert length <= segmentSizeInBytes : "the length has to be smaller or equal to the segment size: " + length + " vs. " + segmentSizeInBytes;
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
byte[] seg = segments[bufferIndex];
int delta = index + length - segmentSizeInBytes;
if (delta > 0) {
length -= delta;
System.arraycopy(values, 0, seg, index, length);
seg = segments[bufferIndex + 1];
System.arraycopy(values, length, seg, 0, delta);
} else {
System.arraycopy(values, 0, seg, index, length);
}
}
@Override
public void getBytes(long bytePos, byte[] values, int length) {
assert length <= segmentSizeInBytes : "the length has to be smaller or equal to the segment size: " + length + " vs. " + segmentSizeInBytes;
assert segmentSizePower > 0 : "call create or loadExisting before usage!";
int bufferIndex = (int) (bytePos >>> segmentSizePower);
int index = (int) (bytePos & indexDivisor);
byte[] seg = segments[bufferIndex];
int delta = index + length - segmentSizeInBytes;
if (delta > 0) {
length -= delta;
System.arraycopy(seg, index, values, 0, length);
seg = segments[bufferIndex + 1];
System.arraycopy(seg, 0, values, length, delta);
} else {
System.arraycopy(seg, index, values, 0, length);
}
}
@Override
public void close() {
super.close();
segments = new byte[0][];
closed = true;
}
@Override
public long getCapacity() {
return (long) getSegments() * segmentSizeInBytes;
}
@Override
public int getSegments() {
return segments.length;
}
@Override
public void trimTo(long capacity) {
if (capacity > getCapacity()) {
throw new IllegalStateException("Cannot increase capacity (" + getCapacity() + ") to " + capacity
+ " via trimTo. Use ensureCapacity instead. ");
}
if (capacity < segmentSizeInBytes)
capacity = segmentSizeInBytes;
int remainingSegments = (int) (capacity / segmentSizeInBytes);
if (capacity % segmentSizeInBytes != 0) {
remainingSegments++;
}
segments = Arrays.copyOf(segments, remainingSegments);
}
@Override
public void rename(String newName) {
if (!checkBeforeRename(newName)) {
return;
}
if (store) {
super.rename(newName);
}
// in every case set the name
name = newName;
}
@Override
public DAType getType() {
if (isStoring())
return DAType.RAM_STORE;
return DAType.RAM;
}
}