summaryrefslogtreecommitdiff
path: root/src/de/lmu/ifi/dbs/elki/persistent/OnDiskUpperTriangleMatrix.java
blob: cc1880c7975ac61cf7c7a134cbff04041ce7be94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package de.lmu.ifi.dbs.elki.persistent;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures

Copyright (C) 2011
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
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/>.
*/

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
 */
public class OnDiskUpperTriangleMatrix {
  /**
   * 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) / 2;
  }

  /**
   * 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)) / 2 + 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;
  }
}