You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2017/09/28 11:09:04 UTC
[48/50] [abbrv] jena git commit: JENA-1396: Merge //github/afs/mantis
as subdirectory jena-db/
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileRandomAccess.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileRandomAccess.java
index 0000000,0000000..c3738ad
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileRandomAccess.java
@@@ -1,0 -1,0 +1,148 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import java.io.IOException ;
++import java.io.RandomAccessFile ;
++
++import org.apache.jena.atlas.RuntimeIOException ;
++import org.apache.jena.atlas.io.IO ;
++
++/** Implementation of {@link BinaryDataFile} using {@link RandomAccessFile}.
++ *
++ * <li>No buffering of reads or writes provided.
++ * <li>Not thread-safe.
++ *
++ * @see BinaryDataFileWriteBuffered
++ */
++public class BinaryDataFileRandomAccess implements BinaryDataFile {
++ // On OpenJDK, RandomAccessFile and FileChannelImpl both
++ // dive into native code.
++ protected RandomAccessFile file ;
++ protected boolean readMode ;
++ protected long readPosition ;
++ protected long writePosition ;
++ private final String filename ;
++
++ public BinaryDataFileRandomAccess(String filename) {
++ this.filename = filename ;
++ }
++
++ @Override
++ public void open() {
++ if ( file != null )
++ throw new RuntimeIOException("Already open") ;
++ try {
++ file = new RandomAccessFile(filename, "rw") ;
++ writePosition = file.length() ;
++ readPosition = 0 ;
++ readMode = true ;
++ }
++ catch (IOException e) { IO.exception(e); }
++ }
++
++ @Override
++ public boolean isOpen() {
++ return file != null ;
++ }
++
++ @Override
++ public int read(long posn, byte[] b, int start, int length) {
++ checkOpen() ;
++ switchToReadMode() ;
++ seek(posn) ;
++ try {
++ int x = file.read(b, start, length) ;
++ readPosition += x ;
++ return x ;
++ }
++ catch (IOException ex) { IO.exception(ex); return -1 ; }
++ }
++
++ @Override
++ public long write(byte[] b, int start, int length) {
++ checkOpen() ;
++ switchToWriteMode() ;
++ long x = writePosition ;
++ try {
++ file.write(b, start, length) ;
++ writePosition += length ;
++ }
++ catch (IOException ex) { IO.exception(ex) ; }
++ return x ;
++ }
++
++
++ // Move the RandomAccess file pointer.
++ private void seek(long posn) {
++ try { file.seek(posn) ; }
++ catch (IOException ex) { IO.exception(ex) ; }
++ }
++
++ @Override
++ public void truncate(long length) {
++ checkOpen() ;
++ switchToWriteMode() ;
++ try { file.setLength(length); }
++ catch (IOException ex) { IO.exception(ex) ; }
++ }
++
++ @Override
++ public void sync() {
++ checkOpen() ;
++ flush$() ;
++ }
++
++ protected void flush$() {
++ try { file.getFD().sync() ; }
++ catch (IOException ex) { IO.exception(ex) ; }
++ }
++
++ @Override
++ public void close() {
++ if ( ! isOpen() )
++ return ;
++ try { file.close(); }
++ catch (IOException ex) { IO.exception(ex) ; }
++ file = null ;
++ }
++
++ @Override
++ public long length() {
++ try { return file.length() ;}
++ catch (IOException ex) { IO.exception(ex) ; return -1 ;}
++ }
++
++ protected void switchToReadMode() {
++ if ( ! readMode )
++ readMode = true ;
++ }
++
++ protected void switchToWriteMode() {
++ if ( readMode ) {
++ readMode = false ;
++ seek(writePosition);
++ }
++ }
++
++ protected void checkOpen() {
++ if ( ! isOpen() )
++ throw new RuntimeIOException("Not open") ;
++ }
++}
++
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileSync.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileSync.java
index 0000000,0000000..bb5c1b1
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileSync.java
@@@ -1,0 -1,0 +1,82 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++
++/** Add synchronization to all BinaryDataFile operations.
++ * This gives the correct thread-safe operation
++ * but isn'tnecessarily the best way to do it.
++ */
++public class BinaryDataFileSync implements BinaryDataFile {
++ private final BinaryDataFile other ;
++
++ public BinaryDataFileSync(BinaryDataFile other) {
++ this.other = other ;
++ }
++
++ @Override
++ synchronized
++ public void open() { other.open() ; }
++
++ @Override
++ synchronized
++ public boolean isOpen() { return other.isOpen() ; }
++
++ @Override
++ synchronized
++ public int read(long posn, byte[] b) {
++ return other.read(posn, b) ;
++ }
++
++ @Override
++ synchronized
++ public int read(long posn, byte[] b, int start, int length) {
++ return other.read(posn, b, start, length) ;
++ }
++
++ @Override
++ synchronized
++ public long write(byte[] b) {
++ return other.write(b) ;
++ }
++
++ @Override
++ synchronized
++ public long write(byte[] b, int start, int length) {
++ return other.write(b, start, length) ;
++ }
++
++ @Override
++ synchronized
++ public long length() {
++ return other.length() ;
++ }
++
++ @Override
++ synchronized
++ public void truncate(long length) { other.truncate(length); }
++
++ @Override
++ synchronized
++ public void sync() { other.sync(); }
++
++ @Override
++ synchronized
++ public void close() { other.close(); }
++}
++
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWrapper.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWrapper.java
index 0000000,0000000..b970284
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWrapper.java
@@@ -1,0 -1,0 +1,67 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++public class BinaryDataFileWrapper implements BinaryDataFile {
++ private final BinaryDataFile other ;
++
++ public BinaryDataFileWrapper(BinaryDataFile other) {
++ this.other = other ;
++ }
++
++ @Override
++ public void open() { other.open() ; }
++
++ @Override
++ public boolean isOpen() { return other.isOpen() ; }
++
++ @Override
++ public int read(long posn, byte[] b) {
++ return other.read(posn, b) ;
++ }
++
++ @Override
++ public int read(long posn, byte[] b, int start, int length) {
++ return other.read(posn, b, start, length) ;
++ }
++
++ @Override
++ public long write(byte[] b) {
++ return other.write(b) ;
++ }
++
++ @Override
++ public long write(byte[] b, int start, int length) {
++ return other.write(b, start, length) ;
++ }
++
++ @Override
++ public long length() {
++ return other.length() ;
++ }
++
++ @Override
++ public void truncate(long length) { other.truncate(length); }
++
++ @Override
++ public void sync() { other.sync(); }
++
++ @Override
++ public void close() { other.close(); }
++}
++
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWriteBuffered.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWriteBuffered.java
index 0000000,0000000..da25ce1
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BinaryDataFileWriteBuffered.java
@@@ -1,0 -1,0 +1,162 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import org.apache.jena.atlas.RuntimeIOException ;
++
++/** Implementation of {@link BinaryDataFile} adding write buffering to another
++ * {@link BinaryDataFile} file such as a {@link BinaryDataFileRandomAccess}.
++ * <li>Thread-safe.
++ * <li>No read buffering provided.
++ * <li>The write buffer is flushed when switching to read.
++ */
++
++public class BinaryDataFileWriteBuffered implements BinaryDataFile {
++ private static final int SIZE = 128*1024 ;
++ private final Object sync = new Object() ;
++ private byte[] buffer ;
++ private int bufferLength ;
++ private boolean pendingOutput ;
++ private final BinaryDataFile other ;
++
++ public BinaryDataFileWriteBuffered(BinaryDataFile other) {
++ this(other, SIZE) ;
++ }
++
++ public BinaryDataFileWriteBuffered(BinaryDataFile other, int bufferSize) {
++ this.other = other ;
++ buffer = new byte[bufferSize] ;
++ }
++
++ @Override
++ public void open() {
++ synchronized(sync) {
++ other.open() ;
++ bufferLength = 0 ;
++ pendingOutput = false ;
++ }
++ }
++
++ @Override
++ public void close() {
++ synchronized(sync) {
++ if ( ! isOpen() )
++ return ;
++ writeBuffer();
++ other.close() ;
++ }
++ }
++
++ @Override
++ public boolean isOpen() {
++ synchronized(sync) {
++ return other.isOpen() ;
++ }
++ }
++
++ @Override
++ public long length() {
++ synchronized(sync) {
++ return other.length()+bufferLength ;
++ }
++ }
++
++ @Override
++ public void truncate(long posn) {
++ synchronized(sync) {
++ checkOpen() ;
++ if ( pendingOutput && posn >= other.length() )
++ writeBuffer() ;
++ other.truncate(posn) ;
++ }
++ }
++
++ private void checkOpen() {
++ if ( ! other.isOpen() )
++ throw new RuntimeIOException("Not open") ;
++ }
++
++ @Override
++ public int read(long posn, byte[] b, int start, int length) {
++ synchronized(sync) {
++ // Overlap with buffered area
++ // We flush the write buffer for a read so no need to check.
++ checkOpen() ;
++ switchToReadMode() ;
++ return other.read(posn, b, start, length) ;
++ }
++ }
++
++ @Override
++ public long write(byte[] buf, int off, int len) {
++ synchronized(sync) {
++ checkOpen() ;
++ switchToWriteMode() ;
++ long x = length() ;
++
++// if ( false ) {
++// // No buffering
++// try { file.write(buf, off, len) ; }
++// catch (IOException e) { IO.exception(e); }
++// bufferLength = 0 ;
++// return ;
++// }
++
++ // No room.
++ if ( bufferLength + len >= SIZE )
++ writeBuffer() ;
++
++ if ( bufferLength + len < SIZE ) {
++ // Room to buffer
++ System.arraycopy(buf, off, buffer, bufferLength, len);
++ bufferLength += len ;
++ pendingOutput = true ;
++ return x ;
++ }
++ // Larger than the buffer space. Write directly.
++ other.write(buf, off, len) ;
++ return x ;
++ }
++ }
++
++ @Override
++ public void sync() {
++ synchronized(sync) {
++ writeBuffer() ;
++ other.sync();
++ }
++ }
++
++ private void writeBuffer() {
++ if ( pendingOutput ) {
++ pendingOutput = false ;
++ other.write(buffer, 0, bufferLength) ;
++ bufferLength = 0 ;
++ }
++ }
++
++ // Inside synchronization
++ protected void switchToWriteMode() {
++ }
++
++ // Inside synchronization
++ protected void switchToReadMode() {
++ writeBuffer() ;
++ }
++}
++
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccess.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccess.java
index 0000000,0000000..ba8d491
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccess.java
@@@ -1,0 -1,0 +1,64 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import org.apache.jena.atlas.lib.Closeable ;
++import org.apache.jena.atlas.lib.Sync ;
++import org.seaborne.dboe.base.block.Block ;
++
++/**
++ * Interface to concrete storage - read and write Blocks, addressed by id.
++ * Suitable for memory mapped I/O (returns
++ * internally allocated space for read, not provided from outside; write() can
++ * insist the block written comes from allocate()).
++ * This interfce can also be backed by an in-memory implemntation
++ * ({@link BlockAccessMem}, {@link BlockAccessByteArray}).
++ *
++ * This is wrapped in a BlockMgr to provide a higher level abstraction.
++ *
++ * @see BufferChannel
++ */
++public interface BlockAccess extends Sync, Closeable
++{
++ public Block allocate(int size) ;
++
++ public Block read(long id) ;
++
++ public void write(Block block) ;
++
++ public void overwrite(Block block) ;
++
++ public boolean isEmpty() ;
++
++ /* The limit of the current allocation space.
++ * Allocated blocks have ids in [0, allocBoundary).
++ * Allocation units need not be in +1 increments.
++ */
++ public long allocBoundary() ;
++
++ /** Reset the allocation space (i.e. truncate).
++ * The boundary should be a number obtained from a previous
++ * call of allocLimit. It can not be greater than the current
++ * allocation boundary.
++ */
++ public void resetAllocBoundary(long boundary) ;
++
++ public boolean valid(long id) ;
++
++ public String getLabel() ;
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessBase.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessBase.java
index 0000000,0000000..1244cd8
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessBase.java
@@@ -1,0 -1,0 +1,175 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import static java.lang.String.format ;
++
++import java.nio.ByteBuffer ;
++import java.nio.channels.FileChannel ;
++import java.util.concurrent.atomic.AtomicLong ;
++
++import org.seaborne.dboe.base.block.Block ;
++import org.seaborne.dboe.base.block.BlockException ;
++import org.seaborne.dboe.sys.FileLib ;
++import org.seaborne.dboe.sys.Sys;
++import org.slf4j.Logger ;
++
++/** Support for a disk file backed FileAccess */
++public abstract class BlockAccessBase implements BlockAccess {
++ protected final int blockSize ;
++ protected FileChannel file ;
++ protected final String filename ;
++
++ protected final String label ;
++ // Does this need to be tread safe?
++ // Only changes in a write transaction
++
++ // Don't overload use of this!
++ protected final AtomicLong seq ;
++ protected long numFileBlocks = -1 ;
++
++ public BlockAccessBase(String filename, int blockSize) {
++ this.filename = filename ;
++ this.file = FileLib.openManaged(filename) ;
++ this.blockSize = blockSize ;
++ this.label = label(filename) ;
++ // This is not related to used file length in mapped mode.
++ long filesize = FileLib.size(file) ;
++ long longBlockSize = blockSize ;
++
++ numFileBlocks = filesize / longBlockSize ; // This is not related to
++ // used file length in mapped
++ // mode.
++ seq = new AtomicLong(numFileBlocks) ;
++
++ if ( numFileBlocks > Integer.MAX_VALUE )
++ getLog().warn(format("File size (%d) exceeds tested block number limits (%d)", filesize, blockSize)) ;
++
++ if ( filesize % longBlockSize != 0 )
++ throw new BlockException(format("File size (%d) not a multiple of blocksize (%d)", filesize, blockSize)) ;
++ }
++
++ /** Find path compoent, with extension */
++ private static String label(String filename) {
++ int j = filename.lastIndexOf('/') ;
++ if ( j < 0 )
++ j = filename.lastIndexOf('\\') ;
++ String fn = (j >= 0) ? filename.substring(j + 1) : filename ;
++ return fn ;
++ }
++
++ protected abstract Logger getLog() ;
++
++ @Override
++ final public boolean isEmpty() {
++ return numFileBlocks <= 0 ;
++ }
++
++ final protected void writeNotification(Block block) {}
++
++ final protected void overwriteNotification(Block block) {
++ // Write at end => extend
++ if ( block.getId() >= numFileBlocks ) {
++ numFileBlocks = block.getId() + 1 ;
++ seq.set(numFileBlocks) ;
++ }
++ }
++
++ final protected int allocateId() {
++ checkIfClosed() ;
++ int id = (int)seq.getAndIncrement() ;
++ numFileBlocks++ ; // TODO Fix this when proper freeblock management is
++ // introduced.
++ return id ;
++ }
++
++ @Override
++ final public long allocBoundary() {
++ checkIfClosed() ;
++ return seq.get() ;
++ // Underlying area is untouched.
++ }
++
++ @Override
++ final public void resetAllocBoundary(long boundary) {
++ checkIfClosed() ;
++ seq.set(boundary) ;
++ _resetAllocBoundary(boundary) ;
++ }
++
++ protected abstract void _resetAllocBoundary(long boundary) ;
++
++ @Override
++ final synchronized public boolean valid(long id) {
++ if ( id >= numFileBlocks )
++ return false ;
++ if ( id < 0 )
++ return false ;
++ return true ;
++ }
++
++ final protected void check(long id) {
++ if ( id > Integer.MAX_VALUE )
++ throw new BlockException(format("BlockAccessBase: Id (%d) too large", id)) ;
++
++ // Access to numFileBlocks not synchronized - it's only a check
++ if ( id < 0 || id >= numFileBlocks ) {
++ // Do it properly!
++ synchronized (this) {
++ if ( id < 0 || id >= numFileBlocks )
++ throw new BlockException(format("BlockAccessBase: Bounds exception: %s: (%d,%d)", filename, id, numFileBlocks)) ;
++ }
++ }
++ }
++
++ final protected void check(Block block) {
++ check(block.getId()) ;
++ ByteBuffer bb = block.getByteBuffer() ;
++ if ( bb.capacity() != blockSize )
++ throw new BlockException(format("BlockMgrFile: Wrong size block. Expected=%d : actual=%d", blockSize, bb.capacity())) ;
++ if ( bb.order() != Sys.NetworkOrder )
++ throw new BlockException("BlockMgrFile: Wrong byte order") ;
++ }
++
++ protected void force() {
++ FileLib.sync(file) ;
++ }
++
++ // @Override
++ final public boolean isClosed() {
++ return file == null ;
++ }
++
++ protected final void checkIfClosed() {
++ if ( isClosed() )
++ getLog().error("File has been closed") ;
++ }
++
++ protected abstract void _close() ;
++
++ @Override
++ final public void close() {
++ _close() ;
++ file = null ;
++ }
++
++ @Override
++ public String getLabel() {
++ return label ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessByteArray.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessByteArray.java
index 0000000,0000000..45d932c
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessByteArray.java
@@@ -1,0 -1,0 +1,129 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++import java.nio.ByteBuffer ;
++
++import org.apache.jena.atlas.lib.ByteBufferLib ;
++import org.seaborne.dboe.base.block.Block ;
++import static org.seaborne.dboe.sys.Sys.SizeOfInt ;
++
++/**
++ * FileAccess interface backed by a byte array.
++ */
++public class BlockAccessByteArray implements BlockAccess {
++ private ByteBuffer bytes ;
++ private long length ; // Bytes in use: 0 to length-1
++ private long alloc ; // Bytes allocated - next allocation number.
++ private final String label ;
++
++ public BlockAccessByteArray(String label) {
++ bytes = ByteBuffer.allocate(1024) ;
++ length = 0 ;
++ alloc = 0 ;
++ this.label = label ;
++ }
++
++ @Override
++ public String getLabel() {
++ return label ;
++ }
++
++ @Override
++ public Block allocate(int size) {
++ long addr = alloc ;
++ ByteBuffer bb = ByteBuffer.allocate(size) ;
++ alloc += (size + SizeOfInt) ;
++ return new Block((int)addr, bb) ;
++ }
++
++ @Override
++ public long allocBoundary() {
++ return alloc ;
++ }
++
++ @Override
++ public void resetAllocBoundary(long boundary) {
++ ByteBufferLib.fill(bytes, (int)boundary, (int)length, (byte)0);
++ length = boundary ;
++ alloc = boundary ;
++ bytes.limit((int)alloc) ;
++
++ }
++
++ @Override
++ public Block read(long id) {
++ // Variable length blocks.
++ if ( id < 0 || id >= length || id >= bytes.capacity() )
++ throw new FileException("Bad id (read): " + id) ;
++ bytes.position((int)id) ;
++ int len = bytes.getInt() ;
++ ByteBuffer bb = ByteBuffer.allocate(len) ;
++ // Copy out the bytes - copy for safety.
++ bytes.get(bb.array(), 0, len) ;
++ return new Block(id, bb) ;
++ }
++
++ @Override
++ public void write(Block block) {
++ // Variable length blocks.
++ long loc = block.getId() ;
++ if ( loc < 0 || loc > length ) // Can be equal => append.
++ throw new FileException("Bad id (write): " + loc + " (" + alloc + "," + length + ")") ;
++ ByteBuffer bb = block.getByteBuffer() ;
++ int len = bb.capacity() ;
++
++ if ( loc == length ) {
++ if ( bytes.capacity() - length < len ) {
++ int cap2 = bytes.capacity() + 1024 ;
++ while (bytes.capacity() - length < len)
++ cap2 += 1024 ;
++
++ ByteBuffer bytes2 = ByteBuffer.allocate(cap2) ;
++ bytes2.position(0) ;
++ bytes2.put(bytes) ;
++ bytes = bytes2 ;
++ }
++ length += len + SizeOfInt ;
++ }
++ bytes.position((int)loc) ;
++ bytes.putInt(len) ;
++ bytes.put(bb.array(), 0, bb.capacity()) ;
++ }
++
++ @Override
++ public void overwrite(Block block) {
++ write(block) ;
++ }
++
++ @Override
++ public boolean isEmpty() {
++ return length == 0 ;
++ }
++
++ @Override
++ public boolean valid(long id) {
++ return (id >= 0 && id < length) ;
++ }
++
++ @Override
++ public void sync() {}
++
++ @Override
++ public void close() {}
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessDirect.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessDirect.java
index 0000000,0000000..62b4f43
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessDirect.java
@@@ -1,0 -1,0 +1,130 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import static java.lang.String.format ;
++
++import java.io.IOException ;
++import java.nio.ByteBuffer ;
++
++import org.apache.jena.atlas.lib.FileOps ;
++import org.seaborne.dboe.base.block.Block ;
++import org.seaborne.dboe.sys.FileLib ;
++import org.slf4j.Logger ;
++import org.slf4j.LoggerFactory ;
++
++public class BlockAccessDirect extends BlockAccessBase
++{
++ // Maybe layer BlockAccess on BufferChannel - retrofitting.
++ // but need separate for memory mapped files anyway.
++
++ private static Logger log = LoggerFactory.getLogger(BlockAccessDirect.class) ;
++
++ public BlockAccessDirect(String filename, int blockSize)
++ {
++ super(filename, blockSize) ;
++ }
++
++ @Override
++ public Block allocate(int blkSize)
++ {
++ if ( blkSize > 0 && blkSize != this.blockSize )
++ throw new FileException("Fixed blocksize only: request= "+blkSize+"fixed size="+this.blockSize) ;
++ int x = allocateId() ;
++ ByteBuffer bb = ByteBuffer.allocate(blkSize) ;
++ Block block = new Block(x, bb) ;
++ return block;
++ }
++
++ @Override
++ public Block read(long id)
++ {
++ check(id) ;
++ checkIfClosed() ;
++ ByteBuffer bb = ByteBuffer.allocate(blockSize) ;
++ readByteBuffer(id, bb) ;
++ bb.rewind() ;
++ Block block = new Block(id, bb) ;
++ return block ;
++ }
++
++ private void readByteBuffer(long id, ByteBuffer dst)
++ {
++ try {
++ int len = file.read(dst, filePosition(id)) ;
++ if ( len != blockSize )
++ throw new FileException(format("get: short read (%d, not %d)", len, blockSize)) ;
++ } catch (IOException ex)
++ { throw new FileException("FileAccessDirect", ex) ; }
++ }
++
++ private final long filePosition(long id)
++ {
++ return id*blockSize ;
++ }
++
++ @Override
++ public void write(Block block)
++ {
++ check(block) ;
++ checkIfClosed() ;
++ ByteBuffer bb = block.getByteBuffer() ;
++ // This .clear() except the javadoc suggests this is not the correct use of .clear()
++ // and the name does
++ bb.limit(bb.capacity()) ; // It shouldn't have been changed.
++ bb.rewind() ;
++ try {
++ int len = file.write(bb, filePosition(block.getId())) ;
++ if ( len != blockSize )
++ throw new FileException(format("write: short write (%d, not %d)", len, blockSize)) ;
++ } catch (IOException ex)
++ { throw new FileException("FileAccessDirect", ex) ; }
++ writeNotification(block) ;
++ }
++
++ @Override
++ public void overwrite(Block block)
++ {
++ overwriteNotification(block) ;
++ write(block) ;
++ }
++
++ @Override
++ protected void _resetAllocBoundary(long boundary) {
++ FileLib.truncate(file, filePosition(boundary)) ;
++ }
++
++ @Override
++ public void sync()
++ {
++ force() ;
++ }
++
++ @Override
++ protected void _close()
++ { super.force() ; }
++
++ @Override
++ protected Logger getLog()
++ {
++ return log ;
++ }
++
++ @Override
++ public String toString() { return "Direct:"+FileOps.basename(filename) ; }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMapped.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMapped.java
index 0000000,0000000..636d8f6
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMapped.java
@@@ -1,0 -1,0 +1,267 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import static java.lang.String.format ;
++
++import java.io.IOException ;
++import java.nio.ByteBuffer ;
++import java.nio.MappedByteBuffer ;
++import java.nio.channels.FileChannel.MapMode ;
++import java.util.Arrays ;
++
++import org.seaborne.dboe.base.block.Block ;
++import org.seaborne.dboe.sys.SystemIndex ;
++import org.slf4j.Logger ;
++import org.slf4j.LoggerFactory ;
++
++/** FileAccess for a file, using memory mapped I/O */
++final
++public class BlockAccessMapped extends BlockAccessBase
++{
++ /* Blocks are addressed by positive ints -
++ * Is that a limit?
++ * One billion is 2^30
++ * If a block is 8K, the 2^31*2^13 = 2^44 bits or 2^14 billion = 16K Billion. = 16 trillion bytes.
++ * No limit at the moment - later performance tuning will see what the cost of 48 or 63 bit addresses would be.
++ */
++
++ private static Logger log = LoggerFactory.getLogger(BlockAccessMapped.class) ;
++ private enum CopyContents { Overwrite, NoCopy }
++
++ // Segmentation avoids over-mapping; allows file to grow (in chunks)
++ private final int GrowthFactor = 2 ;
++ private final int SegmentSize = SystemIndex.SegmentSize ;
++ private final int blocksPerSegment ;
++
++ private int initialNumSegements = 1 ;
++ private MappedByteBuffer[] segments = new MappedByteBuffer[initialNumSegements] ;
++
++ // Unflushed segments.
++ private int segmentDirtyCount = 0 ;
++ private boolean[] segmentDirty = new boolean[initialNumSegements] ;
++
++ public BlockAccessMapped(String filename, int blockSize) {
++ super(filename, blockSize) ;
++ blocksPerSegment = SegmentSize/blockSize ;
++ if ( SegmentSize%blockSize != 0 )
++ getLog().warn(format("%s: Segment size(%d) not a multiple of blocksize (%d)", filename, SegmentSize, blockSize)) ;
++
++ for ( int i = 0 ; i < initialNumSegements ; i++ )
++ // Not strictly necessary - default value is false.
++ segmentDirty[i] = false ;
++ segmentDirtyCount = 0 ;
++
++ if ( getLog().isDebugEnabled() )
++ getLog().debug(format("Segment:%d BlockSize=%d blocksPerSegment=%d", SegmentSize, blockSize, blocksPerSegment)) ;
++ }
++
++
++ @Override
++ public Block allocate(int blkSize) {
++ if ( blkSize > 0 && blkSize != this.blockSize )
++ throw new FileException("Fixed blocksize only: request= "+blkSize+"fixed size="+this.blockSize) ;
++ int id = allocateId() ;
++ ByteBuffer bb = getByteBuffer(id) ;
++ bb.position(0) ;
++ Block block = new Block(id, bb) ;
++ return block ;
++ }
++
++ @Override
++ public Block read(long id) {
++ check(id) ;
++ checkIfClosed() ;
++ ByteBuffer bb = getByteBuffer(id) ;
++ bb.position(0) ;
++ Block block = new Block(id, bb) ;
++ return block ;
++ }
++
++ @Override
++ public void write(Block block) {
++ write(block, CopyContents.NoCopy) ;
++ }
++
++ @Override
++ public void overwrite(Block block) {
++ overwriteNotification(block) ;
++ write(block, CopyContents.Overwrite) ;
++ }
++
++ private void write(Block block, CopyContents copyContents) {
++ check(block) ;
++ checkIfClosed() ;
++ int id = block.getId().intValue() ;
++
++ if ( copyContents == CopyContents.Overwrite ) {
++ ByteBuffer bbDst = getByteBuffer(id) ;
++ bbDst.position(0) ;
++ ByteBuffer bbSrc = block.getByteBuffer() ;
++ bbSrc.rewind() ;
++ bbDst.put(bbSrc) ;
++ }
++
++ // Assumed MRSW - no need to sync as we are the only Writer
++ segmentDirty[segment(id)] = true ;
++ writeNotification(block) ;
++ }
++
++ @Override
++ public void sync() {
++ checkIfClosed() ;
++ force() ;
++ }
++
++ private ByteBuffer getByteBuffer(long _id) {
++ // Limitation: ids must be integers.
++ // ids are used to index into []-arrays.
++ int id = (int)_id ;
++
++ int seg = segment(id) ; // Segment.
++ int segOff = byteOffset(id) ; // Byte offset in segment
++
++ if ( getLog().isTraceEnabled() )
++ getLog().trace(format("%d => [%d, %d]", id, seg, segOff)) ;
++
++ synchronized (this) {
++ try {
++ // Need to put the alloc AND the slice/reset inside a sync.
++ ByteBuffer segBuffer = allocSegment(seg) ;
++ // Now slice the buffer to get the ByteBuffer to return
++ segBuffer.position(segOff) ;
++ segBuffer.limit(segOff+blockSize) ;
++ ByteBuffer dst = segBuffer.slice() ;
++
++ // And then reset limit to max for segment.
++ segBuffer.limit(segBuffer.capacity()) ;
++ // Extend block count when we allocate above end.
++ numFileBlocks = Math.max(numFileBlocks, id+1) ;
++ return dst ;
++ } catch (IllegalArgumentException ex) {
++ // Shouldn't (ha!) happen because the second "limit" resets
++ log.error("Id: "+id) ;
++ log.error("Seg="+seg) ;
++ log.error("Segoff="+segOff) ;
++ log.error(ex.getMessage(), ex) ;
++ throw ex ;
++ }
++ }
++ }
++
++ private final int segment(int id) { return id/blocksPerSegment ; }
++ private final int byteOffset(int id) { return (id%blocksPerSegment)*blockSize ; }
++ private final long fileLocationForSegment(long segmentNumber) { return segmentNumber*SegmentSize ; }
++
++ // Even for MultipleReader this needs to be sync'ed.??
++ private MappedByteBuffer allocSegment(int seg)
++ {
++ // Auxiliary function for get - which holds the lock needed here.
++ // The MappedByteBuffer must be sliced and reset once found/allocated
++ // so as not to mess up the underlying MappedByteBuffer in segments[].
++
++ // Only allocSegment(seg) and flushDirtySegements() and close()
++ // directly access segments[]
++
++ if ( seg < 0 ) {
++ getLog().error("Segment negative: "+seg) ;
++ throw new FileException("Negative segment: "+seg) ;
++ }
++
++ while ( seg >= segments.length ) {
++ // More space needed.
++ MappedByteBuffer[] segments2 = new MappedByteBuffer[GrowthFactor*segments.length] ;
++ System.arraycopy(segments, 0, segments2, 0, segments.length) ;
++ boolean[] segmentDirty2 = new boolean[GrowthFactor*segmentDirty.length] ;
++ System.arraycopy(segmentDirty, 0, segmentDirty2, 0, segmentDirty.length) ;
++
++ segmentDirty = segmentDirty2 ;
++ segments = segments2 ;
++ }
++
++ long offset = fileLocationForSegment(seg) ;
++
++ if ( offset < 0 ) {
++ getLog().error("Segment offset gone negative: "+seg) ;
++ throw new FileException("Negative segment offset: "+seg) ;
++ }
++
++ MappedByteBuffer segBuffer = segments[seg] ;
++ if ( segBuffer == null ) {
++ try {
++ segBuffer = file.map(MapMode.READ_WRITE, offset, SegmentSize) ;
++ if ( getLog().isDebugEnabled() )
++ getLog().debug(format("Segment: %d", seg)) ;
++ segments[seg] = segBuffer ;
++ }
++ catch (IOException ex) {
++ if ( ex.getCause() instanceof java.lang.OutOfMemoryError )
++ throw new FileException("BlockMgrMapped.segmentAllocate: Segment = " + seg + " : Offset = " + offset) ;
++ throw new FileException("BlockMgrMapped.segmentAllocate: Segment = " + seg, ex) ;
++ }
++ }
++ return segBuffer ;
++ }
++
++ private synchronized void flushDirtySegments() {
++
++ // A linked list (with uniqueness) of dirty segments may be better.
++ for ( int i = 0 ; i < segments.length ; i++ ) {
++ if ( segments[i] != null && segmentDirty[i] ) {
++ // Can we "flush" them all at once?
++ segments[i].force() ;
++ segmentDirty[i] = false ;
++ segmentDirtyCount-- ;
++ }
++ }
++ // This on its own does not force dirty segments to disk.
++ super.force() ;
++ }
++
++ @Override
++ protected void _resetAllocBoundary(long boundary) {
++
++ }
++
++
++ @Override
++ protected void _close() {
++ force() ;
++ // There is no unmap operation for MappedByteBuffers.
++ // Sun Bug id bug_id=4724038
++ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038
++ Arrays.fill(segments, null) ;
++ Arrays.fill(segmentDirty, false) ;
++ segmentDirtyCount = 0 ;
++ }
++
++ @Override
++ protected void force() {
++ flushDirtySegments() ;
++ }
++
++ @Override
++ protected Logger getLog() {
++ return log ;
++ }
++
++ @Override
++ public String toString() {
++ return super.getLabel() ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMem.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMem.java
index 0000000,0000000..c2159f2
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BlockAccessMem.java
@@@ -1,0 -1,0 +1,188 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import static java.lang.String.format ;
++
++import java.nio.ByteBuffer ;
++import java.util.ArrayList ;
++import java.util.List ;
++
++import org.apache.jena.atlas.RuntimeIOException ;
++import org.seaborne.dboe.base.block.Block ;
++import org.seaborne.dboe.sys.Sys;
++
++/**
++ * File access layer that simulates a disk in-memory - for testing, not written for efficiency.
++ * There is a safe mode, whereby blocks are copied in and out to guarantee no writing to an unallocated block.
++ * This is very inefficient but a better simulation of a disk.
++ *
++ * @see BlockAccessByteArray
++ */
++
++public class BlockAccessMem implements BlockAccess
++{
++ // "SafeMode" - duplicate the block data much like a disk.
++ // Mildly expensive for large block sizes.
++
++ public static boolean SafeMode = true ;
++ static final boolean Checking = true ;
++ boolean fileClosed = false ;
++ private List<Block> blocks = new ArrayList<>() ;
++ private final boolean safeModeThisMgr ;
++ protected final int blockSize ;
++ private final String label ;
++
++ public BlockAccessMem(String label, int blockSize) {
++ this(label, blockSize, SafeMode) ;
++ }
++
++ private BlockAccessMem(String label, int blockSize, boolean b) {
++ this.blockSize = blockSize ;
++ this.label = label ;
++ safeModeThisMgr = b ;
++ }
++
++ @Override
++ public Block allocate(int blkSize) {
++ checkNotClosed() ;
++ if ( blkSize > 0 && blkSize != this.blockSize )
++ throw new FileException("Fixed blocksize only: request= " + blkSize + " / fixed size=" + this.blockSize) ;
++
++ int x = blocks.size() ;
++ ByteBuffer bb = ByteBuffer.allocate(blkSize) ;
++ Block block = new Block(x, bb) ;
++ blocks.add(block) ;
++ return block ;
++ }
++
++ @Override
++ public Block read(long id) {
++ checkNotClosed() ;
++ check(id) ;
++ Block blk = blocks.get((int)id) ;
++ blk = replicateBlock(blk) ;
++ blk.setModified(false) ;
++ return blk ;
++ }
++
++ @Override
++ public void write(Block block) {
++ checkNotClosed() ;
++ check(block) ;
++ _write(block) ;
++ }
++
++ @Override
++ public void overwrite(Block block) {
++ checkNotClosed() ;
++ write(block) ;
++ }
++
++ private void _write(Block block) {
++ block = replicateBlock(block) ;
++ block.setModified(false) ;
++ // Memory isn't scaling to multi gigabytes.
++ blocks.set(block.getId().intValue(), block) ;
++ }
++
++ private Block replicateBlock(Block blk) {
++ if ( safeModeThisMgr )
++ // Deep replicate.
++ return blk.replicate() ;
++ // Just the block wrapper.
++ return new Block(blk.getId(), blk.getByteBuffer()) ;
++ }
++
++ @Override
++ public boolean isEmpty() {
++ checkNotClosed() ;
++ return blocks.isEmpty() ;
++ }
++
++ @Override
++ public long allocBoundary() {
++ checkNotClosed() ;
++ return blocks.size() ;
++ }
++
++ @Override
++ public void resetAllocBoundary(long boundary) {
++ checkNotClosed() ;
++ // Clear the list from boundary onwards.
++ blocks.subList((int)boundary, blocks.size()).clear() ;
++ }
++
++ @Override
++ public boolean valid(long id) {
++ checkNotClosed() ;
++ return id >= 0 && id < blocks.size() ;
++ }
++
++ private void checkNotClosed() {
++ if ( fileClosed )
++ throw new RuntimeIOException("Already closed") ;
++ }
++
++ @Override
++ public void close() {
++ if ( fileClosed )
++ return ;
++ fileClosed = true ;
++ blocks = null ;
++ }
++
++ @Override
++ public void sync() {
++ checkNotClosed() ;
++ }
++
++
++ private void check(Block block) {
++ check(block.getId()) ;
++ check(block.getByteBuffer()) ;
++ }
++
++ private void check(long id) {
++ if ( id > Integer.MAX_VALUE )
++ throw new FileException("BlockAccessMem: Bounds exception (id large than an int): " + id) ;
++ if ( !Checking )
++ return ;
++ if ( id < 0 || id >= blocks.size() )
++ throw new FileException("BlockAccessMem: "+label+": Bounds exception: " + id + " in [0, "+blocks.size()+")") ;
++ }
++
++ private void check(ByteBuffer bb) {
++ if ( !Checking )
++ return ;
++ if ( bb.capacity() != blockSize )
++ throw new FileException(format("FileAccessMem: Wrong size block. Expected=%d : actual=%d", blockSize, bb.capacity())) ;
++ if ( bb.order() != Sys.NetworkOrder )
++ throw new FileException("BlockMgrMem: Wrong byte order") ;
++ }
++
++ @Override
++ public String getLabel() {
++ return label ;
++ }
++
++ @Override
++ public String toString() {
++ return "Mem:" + label ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannel.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannel.java
index 0000000,0000000..9858181
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannel.java
@@@ -1,0 -1,0 +1,95 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import java.nio.ByteBuffer ;
++import java.nio.channels.FileChannel ;
++
++import org.apache.jena.atlas.lib.Closeable ;
++import org.apache.jena.atlas.lib.Sync ;
++
++
++/**
++ * Interface to storage : a simplified version of FileChannel. Read and write
++ * bytes, passed via ByteBuffers, addressed by file location. This interface is
++ * not suitable for memory mapped I/O - there is no ability to use slices of a
++ * memort mapped file. This interface does not insert size of ByteBuffer - size
++ * of ByteBuffer passed to read controls the number of bytes read. Having our
++ * own abstraction enables us to implement memory-backed versions.
++ *
++ * @see BlockAccess
++ * @see java.nio.channels.FileChannel
++ */
++public interface BufferChannel extends Sync, Closeable
++{
++ // Like java.nio.channels.FileChannel except
++ // Allows implementation by non-files e.g. a memory backed storage.
++ // Runtime exceptions.
++ // This is a simple, low level "file = array of bytes" interface"
++ // This interface does not support slicing - so it's not suitable for memory mapped I/O
++ //
++ // TODO Consider use of allocateDirect
++
++ /** Return another channel to the same storage but with independent position.
++ * Chaos may result due to concurrent use.
++ */
++ public BufferChannel duplicate() ;
++
++ /** return the position */
++ public long position() ;
++
++ /** set the position */
++ public void position(long pos) ;
++
++ /** Read into a ByteBuffer. Returns the number of bytes read. -1 for end of file.
++ */
++ public int read(ByteBuffer buffer) ;
++
++ /** Read into a ByteBuffer, starting at position loc. Return the number of bytes read.
++ * loc must be within the file.
++ */
++ public int read(ByteBuffer buffer, long loc) ;
++
++ /** Write from ByteBuffer, starting at the current position.
++ * Return the number of bytes written
++ */
++ public int write(ByteBuffer buffer) ;
++
++ /** Write from ByteBuffer, starting at position loc.
++ * Return the number of bytes written.
++ * loc must be within 0 to length - writing at length is append */
++ public int write(ByteBuffer buffer, long loc) ;
++
++ /** Truncate the file.
++ * @see FileChannel#truncate(long)
++ */
++ public void truncate(long size) ;
++
++ /** Length of storage, in bytes.*/
++ public long size() ;
++
++ /** Is it empty? */
++ public boolean isEmpty() ;
++
++ /** useful display string */
++ public String getLabel() ;
++
++ /** Filename for this BufferChannel (maybe null) */
++ public String getFilename() ;
++
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelFile.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelFile.java
index 0000000,0000000..216bff2
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelFile.java
@@@ -1,0 -1,0 +1,171 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++import java.io.IOException ;
++import java.nio.ByteBuffer ;
++import java.nio.channels.FileChannel ;
++
++import org.apache.jena.atlas.io.IO ;
++import org.seaborne.dboe.sys.FileLib ;
++
++public class BufferChannelFile implements BufferChannel {
++ private String filename ;
++ private FileChannel file ;
++
++ /** Create a BufferChannelFile */
++ public static BufferChannelFile create(String filename) {
++ return create(filename, "rw") ;
++ }
++
++ /** Create a BufferChannelFile */
++ public static BufferChannelFile create(String filename, String mode) {
++ FileChannel base = ChannelManager.acquire(filename, mode) ;
++ return new BufferChannelFile(filename, base) ;
++ }
++
++ /** Create a BufferChannelFile with unmangaged file resources - use with care */
++ public static BufferChannelFile createUnmanaged(String filename, String mode) {
++ FileChannel channel = FileLib.openUnmanaged(filename, mode) ;
++ return new BufferChannelFile(filename, channel) ;
++ }
++
++ private BufferChannelFile(String filename, FileChannel channel) {
++ this.filename = filename ;
++ this.file = channel ;
++ }
++
++ @Override
++ public BufferChannel duplicate() {
++ return new BufferChannelFile(filename, file) ;
++ }
++
++ @Override
++ public long position() {
++ try {
++ return file.position() ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ return -1 ;
++ }
++ }
++
++ @Override
++ public void position(long pos) {
++ try {
++ file.position(pos) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ }
++ }
++
++ @Override
++ public void truncate(long length) {
++ try {
++ // http://bugs.sun.com/view_bug.do?bug_id=6191269
++ if ( length < file.position() )
++ file.position(length) ;
++ file.truncate(length) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ }
++ }
++
++ @Override
++ public int read(ByteBuffer buffer) {
++ try {
++ return file.read(buffer) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ return -1 ;
++ }
++ }
++
++ @Override
++ public int read(ByteBuffer buffer, long loc) {
++ try {
++ return file.read(buffer, loc) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ return -1 ;
++ }
++ }
++
++ @Override
++ public int write(ByteBuffer buffer) {
++ try {
++ return file.write(buffer) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ return -1 ;
++ }
++ }
++
++ @Override
++ public int write(ByteBuffer buffer, long loc) {
++ try {
++ return file.write(buffer, loc) ;
++ }
++ catch (IOException e) {
++ IO.exception(e) ;
++ return -1 ;
++ }
++ }
++
++ @Override
++ public long size() {
++ return FileLib.size(file) ;
++ }
++
++ @Override
++ public boolean isEmpty() {
++ return size() == 0 ;
++ }
++
++ @Override
++ public void sync() {
++ FileLib.sync(file) ;
++ }
++
++ @Override
++ public void close() {
++ FileLib.close(file) ;
++ }
++
++ @Override
++ public String getLabel() {
++ return filename ;
++ }
++
++ @Override
++ public String toString() {
++ return filename ;
++ }
++
++ @Override
++ public String getFilename() {
++ return filename ;
++ }
++
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelMem.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelMem.java
index 0000000,0000000..ea3c6ba
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/BufferChannelMem.java
@@@ -1,0 -1,0 +1,240 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++import java.nio.ByteBuffer ;
++
++import org.apache.jena.atlas.lib.ByteBufferLib ;
++import org.apache.jena.atlas.logging.FmtLog ;
++import org.seaborne.dboe.base.StorageException ;
++import org.slf4j.Logger ;
++import org.slf4j.LoggerFactory ;
++
++// XXX Merge/replace with SegmentedByteBuffer which has more predicable performance.
++public class BufferChannelMem implements BufferChannel {
++ private static Logger log = LoggerFactory.getLogger(BufferChannelMem.class) ;
++ // The "file pointer" is the position of this buffer.
++ // The "size" is the limit of this buffer.
++ private ByteBuffer bytes ;
++ private String name ;
++ private static int INC_SIZE = 1024 ;
++
++ private final boolean TRACKING ;
++
++ static public BufferChannel create() {
++ return new BufferChannelMem("unnamed") ;
++ }
++
++ static public BufferChannel create(String name) {
++ return new BufferChannelMem(name) ;
++ }
++
++ private BufferChannelMem() {
++ // Unitialized blank.
++ TRACKING = false ;
++ }
++
++ private BufferChannelMem(String name) {
++ bytes = ByteBuffer.allocate(1024) ;
++ bytes.limit(0) ;
++ this.name = name ;
++ TRACKING = false ;
++ // Debugging : pick a filename.
++ // TRACKING = name.endsWith("prefixes.dat") ;
++ }
++
++ @Override
++ synchronized public BufferChannel duplicate() {
++ BufferChannelMem chan = new BufferChannelMem() ;
++ int x = bytes.position() ;
++ bytes.rewind() ;
++ chan.bytes = bytes.slice() ;
++ chan.bytes.position(0) ;
++ bytes.position(x) ;
++ return chan ;
++ }
++
++ @Override
++ synchronized public long position() {
++ checkIfClosed() ;
++ return bytes.position() ;
++ }
++
++ @Override
++ synchronized public void position(long pos) {
++ checkIfClosed() ;
++ if ( pos < 0 || pos > bytes.capacity() )
++ throw new StorageException("Out of range: " + pos) ;
++ bytes.position((int)pos) ;
++ }
++
++ @Override
++ synchronized public int read(ByteBuffer buffer) {
++ checkIfClosed() ;
++ if ( TRACKING )
++ log("read<<%s", ByteBufferLib.details(buffer));
++
++ int x = bytes.position() ;
++
++ int len = buffer.limit() - buffer.position() ;
++ if ( len > bytes.remaining() )
++ len = bytes.remaining() ;
++ // Copy out, moving the position of the bytes of stroage.
++ for (int i = 0; i < len; i++) {
++ byte b = bytes.get() ;
++ buffer.put(b) ;
++ }
++ if ( TRACKING )
++ log("read>>") ;
++ return len ;
++ }
++
++ @Override
++ synchronized public int read(ByteBuffer buffer, long loc) {
++ checkIfClosed() ;
++ if ( TRACKING )
++ log("read<<@%d", loc) ;
++ if ( loc < 0 || loc > bytes.limit() )
++ throw new StorageException("Out of range(" + name + "[read]): " + loc + " [0," + bytes.limit() + ")") ;
++ if ( loc == bytes.limit() )
++ log.warn("At the limit(" + name + "[read]): " + loc) ;
++ int x = bytes.position() ;
++ bytes.position((int)loc) ;
++ int len = read(buffer) ;
++ bytes.position(x) ;
++ if ( TRACKING )
++ log("read>>@%d",loc) ;
++ return len ;
++ }
++
++ @Override
++ synchronized public int write(ByteBuffer buffer) {
++ checkIfClosed() ;
++ if ( TRACKING )
++ log("write<<%s", ByteBufferLib.details(buffer));
++ int len = buffer.limit() - buffer.position() ;
++ int posn = bytes.position() ;
++
++ int freespace = bytes.capacity() - bytes.position() ;
++
++ if ( len > freespace ) {
++ int inc = len - freespace ;
++ inc += INC_SIZE ;
++ ByteBuffer bb2 = ByteBuffer.allocate(bytes.capacity() + inc) ;
++ bytes.position(0) ;
++ // Copy contents; make written bytes area the same as before.
++ bb2.put(bytes) ;
++ bb2.limit(bytes.limit()) ; // limit is used as the end of active
++ // bytes.
++ bb2.position(posn) ;
++ bytes = bb2 ;
++ }
++
++ if ( bytes.limit() < posn + len )
++ bytes.limit(posn + len) ;
++
++ bytes.put(buffer) ;
++
++ if ( TRACKING )
++ log("write>>") ;
++ return len ;
++ }
++
++ // Invert : write(ByteBuffer) = write(ByteBuffer,posn)
++ @Override
++ synchronized public int write(ByteBuffer buffer, long loc) {
++ checkIfClosed() ;
++ if ( TRACKING )
++ log("write<<@%d", loc) ;
++ if ( loc < 0 || loc > bytes.limit() )
++ // Can write at loc = bytes()
++ throw new StorageException("Out of range(" + name + "[write]): " + loc + " [0," + bytes.limit() + ")") ;
++ int x = bytes.position() ;
++ bytes.position((int)loc) ;
++ int len = write(buffer) ;
++ bytes.position(x) ;
++ if ( TRACKING )
++ log("write>>@%d", loc) ;
++ return len ;
++ }
++
++ @Override
++ synchronized public void truncate(long size) {
++ checkIfClosed() ;
++ if ( TRACKING )
++ log("truncate(%d)", size) ;
++ int x = (int)size ;
++ if ( x < 0 )
++ throw new StorageException("Out of range: " + size) ;
++ if ( x > bytes.limit() )
++ return ;
++
++ if ( bytes.position() > x )
++ bytes.position(x) ;
++ bytes.limit(x) ;
++ }
++
++ @Override
++ synchronized public long size() {
++ checkIfClosed() ;
++ return bytes.limit() ;
++ }
++
++ @Override
++ synchronized public boolean isEmpty() {
++ checkIfClosed() ;
++ return size() == 0 ;
++ }
++
++ @Override
++ synchronized public void sync() {
++ checkIfClosed() ;
++ }
++
++ @Override
++ synchronized public void close() {
++ if ( bytes == null )
++ return ;
++ bytes = null ;
++ }
++
++ private void checkIfClosed() {
++ if ( bytes == null )
++ throw new StorageException("Closed: " + name) ;
++ }
++
++ @Override
++ synchronized public String getLabel() {
++ return name ;
++ }
++
++ @Override
++ synchronized public String toString() {
++ return name ;
++ }
++
++ @Override
++ public String getFilename() {
++ return null ;
++ }
++
++ private void log(String fmt, Object... args) {
++ if ( TRACKING )
++ FmtLog.debug(log, fmt, args);
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/ChannelManager.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/ChannelManager.java
index 0000000,0000000..7b1d041
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/ChannelManager.java
@@@ -1,0 -1,0 +1,114 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import java.io.IOException ;
++import java.io.RandomAccessFile ;
++import java.nio.channels.FileChannel ;
++import java.util.ArrayList ;
++import java.util.HashMap ;
++import java.util.List ;
++import java.util.Map ;
++
++public class ChannelManager
++{
++ // Make per "location"?
++
++ // Because "FileManager" is already in use
++ // ChannelManager
++
++ // FileBase ==> OpenFileRef, ChannelRef
++
++ public static FileChannel acquire(String filename) {
++ return acquire(filename, "rw") ;
++ }
++
++ public static FileChannel acquire(String filename, String mode) {
++ return openref$(filename, mode) ;
++ }
++
++ static private Map<String, FileChannel> name2channel = new HashMap<>() ;
++ static private Map<FileChannel, String> channel2name = new HashMap<>() ;
++
++ private static FileChannel openref$(String filename, String mode) {
++ // Temp - for now, only journal files are tracked.
++ if ( !filename.endsWith(".jrnl") ) {
++ return open$(filename, mode) ;
++ }
++
++ FileChannel chan = name2channel.get(filename) ;
++ if ( chan != null ) {
++ // Scream - it's currently open.
++ throw new FileException("Already open: " + filename) ;
++ }
++ chan = open$(filename, mode) ;
++ name2channel.put(filename, chan) ;
++ channel2name.put(chan, filename) ;
++ return chan ;
++ }
++
++ private static FileChannel open$(String filename, String mode) {
++ try {
++ // "rwd" - Syncs only the file contents
++ // "rws" - Syncs the file contents and metadata
++ // "rw" - OS write behind possible
++ // "r" - read only
++ @SuppressWarnings("resource")
++ RandomAccessFile out = new RandomAccessFile(filename, mode) ;
++ FileChannel channel = out.getChannel() ;
++ return channel ;
++ } catch (IOException ex) { throw new FileException("Failed to open: "+filename+" (mode="+mode+")", ex) ; }
++ }
++
++ public static void release(String filename) {
++ FileChannel channel = name2channel.get(filename) ;
++ if ( channel != null )
++ release(channel) ;
++ }
++
++ public static void release(FileChannel chan) {
++ // Always close even if not managed.
++ try {
++ chan.close() ;
++ }
++ catch (Exception ex) {}
++ String name = channel2name.remove(chan) ;
++ if ( name != null )
++ name2channel.remove(name) ;
++ }
++
++ public static void reset() {
++ releaseAll(null) ;
++ }
++
++ /** Shutdown all the files matching the prefix (typically a directory) */
++ public static void releaseAll(String prefix) {
++ // Use an iterator explicitly so we can remove from the map.
++ List<FileChannel> x = new ArrayList<>() ;
++ for ( String fn : name2channel.keySet() ) {
++ if ( prefix == null || fn.startsWith(prefix) ) {
++ x.add(name2channel.get(fn)) ;
++ // Don't call release here - potential CME problems.
++ // Could use an explicit iterator on teh keySet and .remove from that but
++ // then we nearly duplicate the code in release.
++ }
++ }
++ for ( FileChannel chan : x )
++ release(chan) ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileException.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileException.java
index 0000000,0000000..5174033
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileException.java
@@@ -1,0 -1,0 +1,28 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file;
++
++import org.seaborne.dboe.DBOpEnvException ;
++
++public class FileException extends DBOpEnvException
++{
++ public FileException() { super() ; }
++ public FileException(String msg) { super(msg) ; }
++ public FileException(Throwable th) { super(th) ; }
++ public FileException(String msg, Throwable th) { super(msg, th) ; }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileFactory.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileFactory.java
index 0000000,0000000..ebdc1e4
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileFactory.java
@@@ -1,0 -1,0 +1,48 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++public class FileFactory {
++
++ public static BinaryDataFile createBinaryDataFile(FileSet fileset, String ext) {
++ String x = fileset.filename(ext) ;
++ if ( fileset.isMem() ) {
++ return new BinaryDataFileMem() ;
++ } else {
++ BinaryDataFile bdf = new BinaryDataFileRandomAccess(x) ;
++ bdf = new BinaryDataFileWriteBuffered(bdf) ;
++ return bdf ;
++ }
++ }
++
++ public static BinaryDataFile createBinaryDataFile() {
++ return new BinaryDataFileMem() ;
++ }
++
++ public static BufferChannel createBufferChannel(FileSet fileset, String ext) {
++ String x = fileset.filename(ext) ;
++ if ( fileset.isMem() )
++ return BufferChannelMem.create(x) ;
++ else
++ return BufferChannelFile.create(x) ;
++ }
++
++ public static BufferChannel createBufferChannelMem() {
++ return createBufferChannel(FileSet.mem(), null) ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileSet.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileSet.java
index 0000000,0000000..f04e42d
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/FileSet.java
@@@ -1,0 -1,0 +1,115 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++import java.io.File ;
++
++import org.apache.jena.atlas.lib.FileOps ;
++import org.apache.jena.atlas.lib.tuple.Tuple ;
++import org.slf4j.Logger ;
++import org.slf4j.LoggerFactory ;
++
++/**
++ * Naming metadata management to a collection of related files (same directory,
++ * same basename within directory, various extensions).
++ */
++public class FileSet {
++ // Cope with "in-memory" fileset (location == null)
++
++ private static Logger log = LoggerFactory.getLogger(FileSet.class) ;
++
++ private Location location ;
++ private String basename ;
++
++ /** FileSet for "in-memory" */
++ public static FileSet mem() {
++ FileSet fs = new FileSet(Location.mem(), "mem") ;
++ return fs ;
++ }
++
++ // private FileSet() {} // Uninitialized.
++
++ /**
++ * Create a FileSet given Location (directory) and name within the directory
++ */
++ public FileSet(String directory, String basename) {
++ initFileSet(Location.create(directory), basename) ;
++ }
++
++ /**
++ * Create a FileSet given Location (directory) and name within the directory
++ */
++ public FileSet(String filename) {
++ Tuple<String> t = FileOps.splitDirFile(filename) ;
++ String dir = t.get(0) ;
++ String fn = t.get(1) ;
++ if ( dir == null )
++ dir = "." ;
++ initFileSet(Location.create(dir), fn) ;
++ }
++
++ /**
++ * Create a FileSet given Location (directory) and name within the directory
++ */
++ public FileSet(Location directory, String basename) {
++ initFileSet(directory, basename) ;
++ }
++
++ private void initFileSet(Location directory, String basename) {
++ // Default - don't use the locations metadata
++ initFileSet(directory, basename, false) ;
++ }
++
++ private void initFileSet(Location directory, String basename, boolean useLocationMetadata) {
++ this.location = directory ;
++ this.basename = basename ;
++ }
++
++ public Location getLocation() {
++ return location ;
++ }
++
++ public String getBasename() {
++ return basename ;
++ }
++
++ // public MetaFile getMetaFile() { return metafile ; }
++
++ public boolean isMem() {
++ return location.isMem() ;
++ }
++
++ public boolean exists(String ext) {
++ if ( location.isMem() )
++ return true ;
++ String fn = filename(ext) ;
++ File f = new File(fn) ;
++ if ( f.isDirectory() )
++ log.warn("File clashes with a directory") ;
++ return f.exists() && f.isFile() ;
++ }
++
++ @Override
++ public String toString() {
++ return "FileSet:" + filename(null) ;
++ }
++
++ public String filename(String ext) {
++ return location.getPath(basename, ext) ;
++ }
++}
http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/Location.java
----------------------------------------------------------------------
diff --cc jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/Location.java
index 0000000,0000000..9fbba55
new file mode 100644
--- /dev/null
+++ b/jena-db/dboe-base/src/main/java/org/seaborne/dboe/base/file/Location.java
@@@ -1,0 -1,0 +1,264 @@@
++/*
++ * Licensed 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.
++ *
++ * See the NOTICE file distributed with this work for additional
++ * information regarding copyright ownership.
++ */
++
++package org.seaborne.dboe.base.file ;
++
++import java.io.File ;
++import java.io.IOException ;
++import java.nio.file.Path;
++import java.util.Objects ;
++
++import org.seaborne.dboe.sys.Names ;
++
++/**
++ * Wrapper for a file system directory; can create filenames in that directory.
++ * Enforces some simple consistency policies and provides a "typed string" for a
++ * filename to reduce errors.
++ */
++
++public class Location {
++ static String pathSeparator = File.separator ; // Or just "/"
++
++ private static String memNamePath = Names.memName+pathSeparator ;
++
++ private String pathname ;
++ private MetaFile metafile = null ;
++ private boolean isMem = false ;
++ private boolean isMemUnique = false ;
++
++ static int memoryCount = 0 ;
++
++ /**
++ * Return a fresh memory location : always unique, never .equals to another
++ * location.
++ */
++ static public Location mem() {
++ return mem(null) ;
++ }
++
++ /** Return a memory location with a name */
++ static public Location mem(String name) {
++ Location loc = new Location() ;
++ memInit(loc, name) ;
++ return loc ;
++ }
++
++ /** Return a location for a directory on disk */
++ static public Location create(String directoryName) {
++ if ( directoryName == null )
++ // Fresh, anonymous, memory area
++ return mem() ;
++ Location loc = new Location(directoryName) ;
++ return loc ;
++ }
++
++ /** Return a location for a directory on disk */
++ static public Location create(Path directoryName) {
++ if ( directoryName == null )
++ // Fresh, anonymous, memory area
++ return mem() ;
++ Location loc = new Location(directoryName.toString()) ;
++ return loc ;
++ }
++
++ private Location() {}
++
++ private static void memInit(Location location, String name) {
++ location.pathname = Names.memName ;
++ if ( name != null ) {
++ name = name.replace('\\', '/') ;
++ location.pathname = location.pathname + '/' + name ;
++ } else
++ location.isMemUnique = true ;
++ if ( !location.pathname.endsWith(pathSeparator) )
++ location.pathname = location.pathname + '/' ;
++ location.isMem = true ;
++ location.metafile = new MetaFile(Names.memName, Names.memName) ;
++ }
++
++ private Location(String rootname) {
++ super() ;
++ if ( rootname.equals(Names.memName) ) {
++ memInit(this, null) ;
++ return ;
++ }
++ if ( rootname.startsWith(memNamePath) ) {
++ String name = rootname.substring(memNamePath.length()) ;
++ memInit(this, name) ;
++ return ;
++ }
++
++ ensure(rootname) ;
++ pathname = fixupName(rootname) ;
++ // Metafilename for a directory.
++ String metafileName = getPath(Names.directoryMetafile, Names.extMeta) ;
++
++ metafile = new MetaFile("Location: " + rootname, metafileName) ;
++ }
++
++ // MS Windows:
++ // getCanonicalPath is only good enough for existing files.
++ // It leaves the case as it finds it (upper, lower) and lower cases
++ // not-existing segments. But later creation of a segment with uppercase
++ // changes the exact string returned.
++ private String fixupName(String fsName) {
++ if ( isMem() )
++ return fsName ;
++ File file = new File(fsName) ;
++ try {
++ fsName = file.getCanonicalPath() ;
++ } catch (IOException ex) {
++ throw new FileException("Failed to get canoncial path: " + file.getAbsolutePath(), ex) ;
++ }
++
++ if ( !fsName.endsWith(File.separator) && !fsName.endsWith(pathSeparator) )
++ fsName = fsName + pathSeparator ;
++ return fsName ;
++ }
++
++ public String getDirectoryPath() {
++ return pathname ;
++ }
++
++ public MetaFile getMetaFile() {
++ return metafile ;
++ }
++
++ public boolean isMem() {
++ return isMem ;
++ }
++
++ public boolean isMemUnique() {
++ return isMemUnique ;
++ }
++
++ public Location getSubLocation(String dirname) {
++ String newName = pathname + dirname ;
++ ensure(newName) ;
++ return Location.create(newName) ;
++ }
++
++ private void ensure(String dirname) {
++ if ( isMem() )
++ return ;
++ File file = new File(dirname) ;
++ if ( file.exists() && !file.isDirectory() )
++ throw new FileException("Existing file: " + file.getAbsolutePath()) ;
++ if ( !file.exists() )
++ file.mkdir() ;
++ }
++
++ public String getSubDirectory(String dirname) {
++ return getSubLocation(dirname).getDirectoryPath() ;
++ }
++
++ /**
++ * Return an absolute filename where relative names are resolved from the
++ * location
++ */
++ public String absolute(String filename, String extension) {
++ return (extension == null) ? absolute(filename) : absolute(filename + "." + extension) ;
++ }
++
++ /**
++ * Return an absolute filename where relative names are resolved from the
++ * location
++ */
++ public String absolute(String filename) {
++ File f = new File(filename) ;
++ // Location relative.
++ if ( !f.isAbsolute() )
++ filename = pathname + filename ;
++ return filename ;
++ }
++
++ /** Does the location exist (and it a directory, and is accessible) */
++ public boolean exists() {
++ File f = new File(getDirectoryPath()) ;
++ return f.exists() && f.isDirectory() && f.canRead() ;
++ }
++
++ public boolean exists(String filename) {
++ return exists(filename, null) ;
++ }
++
++ public boolean exists(String filename, String ext) {
++ String fn = getPath(filename, ext) ;
++ File f = new File(fn) ;
++ return f.exists() ;
++ }
++
++ /** Return the name of the file relative to this location */
++ public String getPath(String filename) {
++ return getPath(filename, null) ;
++ }
++
++ /** Return the name of the file, and extension, relative to this location */
++ public String getPath(String filename, String ext) {
++ check(filename, ext) ;
++ if ( ext == null )
++ return pathname + filename ;
++ return pathname + filename + "." + ext ;
++ }
++
++ private void check(String filename, String ext) {
++ if ( filename == null )
++ throw new FileException("Location: null filename") ;
++ if ( filename.contains("/") || filename.contains("\\") )
++ throw new FileException("Illegal file component name: " + filename) ;
++ if ( filename.contains(".") && ext != null )
++ throw new FileException("Filename has an extension: " + filename) ;
++ if ( ext != null ) {
++ if ( ext.contains(".") )
++ throw new FileException("Extension has an extension: " + filename) ;
++ }
++ }
++
++ @Override
++ public int hashCode() {
++ final int prime = 31 ;
++ int result = isMem ? 1 : 2 ;
++ result = prime * result + ((pathname == null) ? 0 : pathname.hashCode()) ;
++ return result ;
++ }
++
++ @Override
++ public boolean equals(Object obj) {
++ if ( this == obj )
++ return true ;
++ if ( obj == null )
++ return false ;
++ if ( getClass() != obj.getClass() )
++ return false ;
++
++ Location other = (Location)obj ;
++ if ( isMem && !other.isMem )
++ return false ;
++ if ( !isMem && other.isMem )
++ return false ;
++ // Not == so ...
++ if ( isMemUnique )
++ return false ;
++
++ return Objects.equals(pathname, other.pathname) ;
++ }
++
++ @Override
++ public String toString() {
++ return "location:" + pathname ;
++ }
++}