/*
* This file is part of ELKI:
* Environment for Developing KDD-Applications Supported by Index-Structures
*
* Copyright (C) 2017
* ELKI Development Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.lmu.ifi.dbs.elki.persistent;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Class representing an upper triangle matrix backed by an on-disk array of
* O((n+1)*n/2) size
*
* @apiviz.composedOf OnDiskArray
*
* @author Erich Schubert
* @since 0.2
*/
public class OnDiskUpperTriangleMatrix implements AutoCloseable {
/**
* Serial number, also used for generating a magic
*/
private static final long serialVersionUID = -4489942156357634702L;
/**
* Size of this class' header
*/
private static final int TRIANGLE_HEADER_SIZE = 4;
/**
* Size of the matrix
*/
private int matrixsize;
/**
* Data storage
*/
private OnDiskArray array;
/**
* Constructor to access an existing array.
*
* @param filename File name
* @param magicseed Magic number
* @param extraheadersize Size of extra header data
* @param recordsize Record size
* @param writable flag to open writable
* @throws IOException on IO errors
*/
public OnDiskUpperTriangleMatrix(File filename, int magicseed, int extraheadersize, int recordsize, boolean writable) throws IOException {
array = new OnDiskArray(filename, OnDiskArray.mixMagic((int) serialVersionUID, magicseed), extraheadersize + TRIANGLE_HEADER_SIZE, recordsize, writable);
ByteBuffer header = array.getExtraHeader();
this.matrixsize = header.getInt();
if(arraysize(matrixsize) != array.getNumRecords()) {
throw new IOException("Matrix file size doesn't match specified dimensions: " + matrixsize + "->" + arraysize(matrixsize) + " vs. " + array.getNumRecords());
}
}
/**
* Constructor to access a new array.
*
* @param filename File name
* @param magicseed Magic number
* @param extraheadersize Size of extra header data
* @param recordsize Record size
* @param matrixsize Size of matrix to store
* @throws IOException on IO errors
*/
public OnDiskUpperTriangleMatrix(File filename, int magicseed, int extraheadersize, int recordsize, int matrixsize) throws IOException {
if(matrixsize >= 0xFFFF) {
throw new RuntimeException("Matrix size is too big and will overflow the integer datatype.");
}
this.matrixsize = matrixsize;
array = new OnDiskArray(filename, OnDiskArray.mixMagic((int) serialVersionUID, magicseed), extraheadersize + TRIANGLE_HEADER_SIZE, recordsize, arraysize(matrixsize));
ByteBuffer header = array.getExtraHeader();
header.putInt(this.matrixsize);
}
/**
* Resize the matrix to cover newsize x newsize.
*
* @param newsize New matrix size.
* @throws IOException on IO errors
*/
public synchronized void resizeMatrix(int newsize) throws IOException {
if(newsize >= 0xFFFF) {
throw new RuntimeException("Matrix size is too big and will overflow the integer datatype.");
}
if(!array.isWritable()) {
throw new IOException("Can't resize a read-only array.");
}
array.resizeFile(arraysize(newsize));
this.matrixsize = newsize;
ByteBuffer header = array.getExtraHeader();
header.putInt(this.matrixsize);
}
/**
* Compute the size of the needed backing array from the matrix dimensions.
*
* @param matrixsize size of the matrix
* @return size of the array
*/
private static int arraysize(int matrixsize) {
return (matrixsize * (matrixsize + 1)) >> 1;
}
/**
* Compute the offset within the file.
*
* @param x First coordinate
* @param y Second coordinate
* @return Linear offset
*/
private int computeOffset(int x, int y) {
if(y > x) {
return computeOffset(y, x);
}
return ((x * (x + 1)) >> 1) + y;
}
/**
* Get a record buffer
*
* @param x First coordinate
* @param y Second coordinate
* @return Byte buffer for the record
* @throws IOException on IO errors
*/
public synchronized ByteBuffer getRecordBuffer(int x, int y) throws IOException {
if(x >= matrixsize || y >= matrixsize) {
throw new ArrayIndexOutOfBoundsException();
}
return array.getRecordBuffer(computeOffset(x, y));
}
/**
* Close the matrix file.
*
* @throws IOException on IO errors
*/
public synchronized void close() throws IOException {
array.close();
}
/**
* Query the size of the matrix.
*
* @return size of the matrix
*/
public int getMatrixSize() {
return matrixsize;
}
}