001    /* ============================================================
002     * JRobin : Pure java implementation of RRDTool's functionality
003     * ============================================================
004     *
005     * Project Info:  http://www.jrobin.org
006     * Project Lead:  Sasa Markovic (saxon@jrobin.org);
007     *
008     * (C) Copyright 2003-2005, by Sasa Markovic.
009     *
010     * Developers:    Sasa Markovic (saxon@jrobin.org)
011     *
012     *
013     * This library is free software; you can redistribute it and/or modify it under the terms
014     * of the GNU Lesser General Public License as published by the Free Software Foundation;
015     * either version 2.1 of the License, or (at your option) any later version.
016     *
017     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019     * See the GNU Lesser General Public License for more details.
020     *
021     * You should have received a copy of the GNU Lesser General Public License along with this
022     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023     * Boston, MA 02111-1307, USA.
024     */
025    
026    package org.jrobin.core;
027    
028    import java.io.IOException;
029    
030    /**
031     * Base implementation class for all backend classes. Each Round Robin Database object
032     * ({@link RrdDb} object) is backed with a single RrdBackend object which performs
033     * actual I/O operations on the underlying storage. JRobin supports
034     * three different bakcends out of the box:</p>
035     * <ul>
036     * <li>{@link RrdFileBackend}: objects of this class are created from the
037     * {@link RrdFileBackendFactory} class. This was the default backend used in all
038     * JRobin releases prior to 1.4.0. It uses java.io.* package and
039     * RandomAccessFile class to store RRD data in files on the disk.
040     * <p/>
041     * <li>{@link RrdNioBackend}: objects of this class are created from the
042     * {@link RrdNioBackendFactory} class. The backend uses java.io.* and java.nio.*
043     * classes (mapped ByteBuffer) to store RRD data in files on the disk. This backend is fast, very fast,
044     * but consumes a lot of memory (borrowed not from the JVM but from the underlying operating system
045     * directly). <b>This is the default backend used in JRobin since 1.4.0 release.</b>
046     * <p/>
047     * <li>{@link RrdMemoryBackend}: objects of this class are created from the
048     * {@link RrdMemoryBackendFactory} class. This backend stores all data in memory. Once
049     * JVM exits, all data gets lost. The backend is extremely fast and memory hungry.
050     * </ul>
051     * <p/>
052     * To create your own backend in order to provide some custom type of RRD storage,
053     * you should do the following:</p>
054     * <p/>
055     * <ul>
056     * <li>Create your custom RrdBackend class (RrdCustomBackend, for example)
057     * by extending RrdBackend class. You have to implement all abstract methods defined
058     * in the base class.
059     * <p/>
060     * <li>Create your custom RrdBackendFactory class (RrdCustomBackendFactory,
061     * for example) by extending RrdBackendFactory class. You have to implement all
062     * abstract methods defined in the base class. Your custom factory class will actually
063     * create custom backend objects when necessary.
064     * <p/>
065     * <li>Create instance of your custom RrdBackendFactory and register it as a regular
066     * factory available to JRobin framework. See javadoc for {@link RrdBackendFactory} to
067     * find out how to do this
068     * </ul>
069     */
070    public abstract class RrdBackend {
071            private static boolean instanceCreated = false;
072            private String path;
073    
074            /**
075             * Creates backend for a RRD storage with the given path.
076             *
077             * @param path String identifying RRD storage. For files on the disk, this
078             *             argument should represent file path. Other storage types might interpret
079             *             this argument differently.
080             */
081            protected RrdBackend(String path) {
082                    this.path = path;
083                    instanceCreated = true;
084            }
085    
086            /**
087             * Returns path to the storage.
088             *
089             * @return Storage path
090             */
091            public String getPath() {
092                    return path;
093            }
094    
095            /**
096             * Writes an array of bytes to the underlying storage starting from the given
097             * storage offset.
098             *
099             * @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    }