1 /* ============================================================
2  * JRobin : Pure java implementation of RRDTool's functionality
3  * ============================================================
4  *
5  * Project Info:  http://www.jrobin.org
6  * Project Lead:  Sasa Markovic (saxon@jrobin.org);
7  *
8  * (C) Copyright 2003-2005, by Sasa Markovic.
9  *
10  * Developers:    Sasa Markovic (saxon@jrobin.org)
11  *
12  *
13  * This library is free software; you can redistribute it and/or modify it under the terms
14  * of the GNU Lesser General Public License as published by the Free Software Foundation;
15  * either version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  * See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along with this
22  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */

25
26 package org.jrobin.core;
27
28 import java.io.IOException;
29
30 /**
31  * Base implementation class for all backend classes. Each Round Robin Database object
32  * ({@link RrdDb} object) is backed with a single RrdBackend object which performs
33  * actual I/O operations on the underlying storage. JRobin supports
34  * three different bakcends out of the box:</p>
35  * <ul>
36  * <li>{@link RrdFileBackend}: objects of this class are created from the
37  * {@link RrdFileBackendFactory} class. This was the default backend used in all
38  * JRobin releases prior to 1.4.0. It uses java.io.* package and
39  * RandomAccessFile class to store RRD data in files on the disk.
40  * <p/>
41  * <li>{@link RrdNioBackend}: objects of this class are created from the
42  * {@link RrdNioBackendFactory} class. The backend uses java.io.* and java.nio.*
43  * classes (mapped ByteBuffer) to store RRD data in files on the disk. This backend is fast, very fast,
44  * but consumes a lot of memory (borrowed not from the JVM but from the underlying operating system
45  * directly). <b>This is the default backend used in JRobin since 1.4.0 release.</b>
46  * <p/>
47  * <li>{@link RrdMemoryBackend}: objects of this class are created from the
48  * {@link RrdMemoryBackendFactory} class. This backend stores all data in memory. Once
49  * JVM exits, all data gets lost. The backend is extremely fast and memory hungry.
50  * </ul>
51  * <p/>
52  * To create your own backend in order to provide some custom type of RRD storage,
53  * you should do the following:</p>
54  * <p/>
55  * <ul>
56  * <li>Create your custom RrdBackend class (RrdCustomBackend, for example)
57  * by extending RrdBackend class. You have to implement all abstract methods defined
58  * in the base class.
59  * <p/>
60  * <li>Create your custom RrdBackendFactory class (RrdCustomBackendFactory,
61  * for example) by extending RrdBackendFactory class. You have to implement all
62  * abstract methods defined in the base class. Your custom factory class will actually
63  * create custom backend objects when necessary.
64  * <p/>
65  * <li>Create instance of your custom RrdBackendFactory and register it as a regular
66  * factory available to JRobin framework. See javadoc for {@link RrdBackendFactory} to
67  * find out how to do this
68  * </ul>
69  */

70 public abstract class RrdBackend {
71     private static boolean instanceCreated = false;
72     private String path;
73
74     /**
75      * Creates backend for a RRD storage with the given path.
76      *
77      * @param path String identifying RRD storage. For files on the disk, this
78      *             argument should represent file path. Other storage types might interpret
79      *             this argument differently.
80      */

81     protected RrdBackend(String path) {
82         this.path = path;
83         instanceCreated = true;
84     }
85
86     /**
87      * Returns path to the storage.
88      *
89      * @return Storage path
90      */

91     public String getPath() {
92         return path;
93     }
94
95     /**
96      * Writes an array of bytes to the underlying storage starting from the given
97      * storage offset.
98      *
99      * @param offset Storage offset.
100      * @param b      Array of bytes that should be copied to the underlying storage
101      * @throws IOException Thrown in case of I/O error
102      */

103     protected abstract void write(long offset, byte[] b) throws IOException;
104
105     /**
106      * Reads an array of bytes from the underlying storage starting from the given
107      * storage offset.
108      *
109      * @param offset Storage offset.
110      * @param b      Array which receives bytes from the underlying storage
111      * @throws IOException Thrown in case of I/O error
112      */

113     protected abstract void read(long offset, byte[] b) throws IOException;
114
115     /**
116      * Returns the number of RRD bytes in the underlying storage.
117      *
118      * @return Number of RRD bytes in the storage.
119      * @throws IOException Thrown in case of I/O error.
120      */

121     public abstract long getLength() throws IOException;
122
123     /**
124      * Sets the number of bytes in the underlying RRD storage.
125      * This method is called only once, immediately after a new RRD storage gets created.
126      *
127      * @param length Length of the underlying RRD storage in bytes.
128      * @throws IOException Thrown in case of I/O error.
129      */

130     protected abstract void setLength(long length) throws IOException;
131
132     /**
133      * Closes the underlying backend.
134      *
135      * @throws IOException Thrown in case of I/O error
136      */

137     public void close() throws IOException {
138     }
139
140     /**
141      * This method suggests the caching policy to the JRobin frontend (high-level) classes. If <code>true</code>
142      * is returned, frontent classes will cache frequently used parts of a RRD file in memory to improve
143      * performance. If </code>false</code> is returned, high level classes will never cache RRD file sections
144      * in memory.
145      *
146      * @return <code>true</code> if file caching is enabled, <code>false</code> otherwise. By default, the
147      *         method returns <code>true</code> but it can be overriden in subclasses.
148      */

149     protected boolean isCachingAllowed() {
150         return true;
151     }
152
153     /**
154      * Reads all RRD bytes from the underlying storage
155      *
156      * @return RRD bytes
157      * @throws IOException Thrown in case of I/O error
158      */

159     public final byte[] readAll() throws IOException {
160         byte[] b = new byte[(int) getLength()];
161         read(0, b);
162         return b;
163     }
164
165     final void writeInt(long offset, int value) throws IOException {
166         write(offset, getIntBytes(value));
167     }
168
169     final void writeLong(long offset, long value) throws IOException {
170         write(offset, getLongBytes(value));
171     }
172
173     final void writeDouble(long offset, double value) throws IOException {
174         write(offset, getDoubleBytes(value));
175     }
176
177     final void writeDouble(long offset, double value, int count) throws IOException {
178         byte[] b = getDoubleBytes(value);
179         byte[] image = new byte[8 * count];
180         for (int i = 0, k = 0; i < count; i++) {
181             image[k++] = b[0];
182             image[k++] = b[1];
183             image[k++] = b[2];
184             image[k++] = b[3];
185             image[k++] = b[4];
186             image[k++] = b[5];
187             image[k++] = b[6];
188             image[k++] = b[7];
189         }
190         write(offset, image);
191     }
192
193     final void writeDouble(long offset, double[] values) throws IOException {
194         int count = values.length;
195         byte[] image = new byte[8 * count];
196         for (int i = 0, k = 0; i < count; i++) {
197             byte[] b = getDoubleBytes(values[i]);
198             image[k++] = b[0];
199             image[k++] = b[1];
200             image[k++] = b[2];
201             image[k++] = b[3];
202             image[k++] = b[4];
203             image[k++] = b[5];
204             image[k++] = b[6];
205             image[k++] = b[7];
206         }
207         write(offset, image);
208     }
209
210     final void writeString(long offset, String value) throws IOException {
211         value = value.trim();
212         byte[] b = new byte[RrdPrimitive.STRING_LENGTH * 2];
213         for (int i = 0, k = 0; i < RrdPrimitive.STRING_LENGTH; i++) {
214             char c = (i < value.length()) ? value.charAt(i) : ' ';
215             byte[] cb = getCharBytes(c);
216             b[k++] = cb[0];
217             b[k++] = cb[1];
218         }
219         write(offset, b);
220     }
221
222     final int readInt(long offset) throws IOException {
223         byte[] b = new byte[4];
224         read(offset, b);
225         return getInt(b);
226     }
227
228     final long readLong(long offset) throws IOException {
229         byte[] b = new byte[8];
230         read(offset, b);
231         return getLong(b);
232     }
233
234     final double readDouble(long offset) throws IOException {
235         byte[] b = new byte[8];
236         read(offset, b);
237         return getDouble(b);
238     }
239
240     final double[] readDouble(long offset, int count) throws IOException {
241         int byteCount = 8 * count;
242         byte[] image = new byte[byteCount];
243         read(offset, image);
244         double[] values = new double[count];
245         for (int i = 0, k = -1; i < count; i++) {
246             byte[] b = new byte[] {
247                     image[++k], image[++k], image[++k], image[++k],
248                     image[++k], image[++k], image[++k], image[++k]
249             };
250             values[i] = getDouble(b);
251         }
252         return values;
253     }
254
255     final String readString(long offset) throws IOException {
256         byte[] b = new byte[RrdPrimitive.STRING_LENGTH * 2];
257         char[] c = new char[RrdPrimitive.STRING_LENGTH];
258         read(offset, b);
259         for (int i = 0, k = -1; i < RrdPrimitive.STRING_LENGTH; i++) {
260             byte[] cb = new byte[] {b[++k], b[++k]};
261             c[i] = getChar(cb);
262         }
263         return new String(c).trim();
264     }
265
266     // static helper methods
267
268     private static byte[] getIntBytes(int value) {
269         byte[] b = new byte[4];
270         b[0] = (byte) ((value >>> 24) & 0xFF);
271         b[1] = (byte) ((value >>> 16) & 0xFF);
272         b[2] = (byte) ((value >>> 8) & 0xFF);
273         b[3] = (byte) ((value) & 0xFF);
274         return b;
275     }
276
277     private static byte[] getLongBytes(long value) {
278         byte[] b = new byte[8];
279         b[0] = (byte) ((int) (value >>> 56) & 0xFF);
280         b[1] = (byte) ((int) (value >>> 48) & 0xFF);
281         b[2] = (byte) ((int) (value >>> 40) & 0xFF);
282         b[3] = (byte) ((int) (value >>> 32) & 0xFF);
283         b[4] = (byte) ((int) (value >>> 24) & 0xFF);
284         b[5] = (byte) ((int) (value >>> 16) & 0xFF);
285         b[6] = (byte) ((int) (value >>> 8) & 0xFF);
286         b[7] = (byte) ((int) (value) & 0xFF);
287         return b;
288     }
289
290     private static byte[] getCharBytes(char value) {
291         byte[] b = new byte[2];
292         b[0] = (byte) ((value >>> 8) & 0xFF);
293         b[1] = (byte) ((value) & 0xFF);
294         return b;
295     }
296
297     private static byte[] getDoubleBytes(double value) {
298         return getLongBytes(Double.doubleToLongBits(value));
299     }
300
301     private static int getInt(byte[] b) {
302         assert b.length == 4: "Invalid number of bytes for integer conversion";
303         return ((b[0] << 24) & 0xFF000000) + ((b[1] << 16) & 0x00FF0000) +
304                 ((b[2] << 8) & 0x0000FF00) + (b[3] & 0x000000FF);
305     }
306
307     private static long getLong(byte[] b) {
308         assert b.length == 8: "Invalid number of bytes for long conversion";
309         int high = getInt(new byte[] {b[0], b[1], b[2], b[3]});
310         int low = getInt(new byte[] {b[4], b[5], b[6], b[7]});
311         return ((long) (high) << 32) + (low & 0xFFFFFFFFL);
312     }
313
314     private static char getChar(byte[] b) {
315         assert b.length == 2: "Invalid number of bytes for char conversion";
316         return (char) (((b[0] << 8) & 0x0000FF00)
317                 + (b[1] & 0x000000FF));
318     }
319
320     private static double getDouble(byte[] b) {
321         assert b.length == 8: "Invalid number of bytes for double conversion";
322         return Double.longBitsToDouble(getLong(b));
323     }
324
325     static boolean isInstanceCreated() {
326         return instanceCreated;
327     }
328 }
329