/*******************************************************************************
* Copyright (c) 2001-2005 Sasa Markovic and Ciaran Treanor.
* Copyright (c) 2011 The OpenNMS Group, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************/
package org.jrobin.core.jrrd;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import org.jrobin.core.RrdException;
/**
* This class is a quick hack to read information from an RRD file. Writing
* to RRD files is not currently supported. As I said, this is a quick hack.
* Some thought should be put into the overall design of the file IO.
* <p>
* Currently this can read RRD files that were generated on Solaris (Sparc)
* and Linux (x86).
*
* @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
* @version $Revision$
*/
public class RRDFile implements Constants {
boolean bigEndian;
boolean debug;
int alignment;
RandomAccessFile ras;
byte[] buffer;
RRDFile(String name) throws IOException, RrdException {
this(new File(name));
}
RRDFile(File file) throws IOException, RrdException {
ras = new RandomAccessFile(file, "r");
buffer = new byte[128];
this.debug = false;
initDataLayout(file);
}
private void initDataLayout(File file) throws IOException, RrdException {
if (file.exists()) { // Load the data formats from the file
int bytes = ras.read(buffer, 0, 24);
if (bytes < 24) {
throw new RrdException("Invalid RRD file");
}
int index;
if ((index = indexOf(FLOAT_COOKIE_BIG_ENDIAN, buffer)) != -1) {
bigEndian = true;
}
else if ((index = indexOf(FLOAT_COOKIE_LITTLE_ENDIAN, buffer))
!= -1) {
bigEndian = false;
}
else {
throw new RrdException("Invalid RRD file");
}
switch (index) {
case 12:
alignment = 4;
break;
case 16:
alignment = 8;
break;
default :
throw new RuntimeException("Unsupported architecture - neither 32-bit nor 64-bit, or maybe the file is corrupt");
}
}
else { // Default to data formats for this hardware architecture
}
ras.seek(0); // Reset file pointer to start of file
}
private int indexOf(byte[] pattern, byte[] array) {
return (new String(array)).indexOf(new String(pattern));
}
boolean isBigEndian() {
return bigEndian;
}
int getAlignment() {
return alignment;
}
double readDouble() throws IOException, RrdException {
if(debug) {
System.out.print("Read 8 bytes (Double) from offset "+ras.getFilePointer()+":");
}
//double value;
byte[] tx = new byte[8];
if(ras.read(buffer, 0, 8) != 8) {
throw new RrdException("Invalid RRD file");
}
if (bigEndian) {
tx = buffer;
}
else {
for (int i = 0; i < 8; i++) {
tx[7 - i] = buffer[i];
}
}
DataInputStream reverseDis =
new DataInputStream(new ByteArrayInputStream(tx));
Double result = reverseDis.readDouble();
if(this.debug) {
System.out.println(result);
}
return result;
}
int readInt() throws IOException, RrdException {
return readInt(false);
}
/**
* Reads the next integer (4 or 8 bytes depending on alignment), advancing the file pointer
* and returns it
* If the alignment is 8-bytes (64-bit), then 8 bytes are read, but only the lower 4-bytes (32-bits) are
* returned. The upper 4 bytes are ignored.
*
* @return the 32-bit integer read from the file
* @throws IOException - A file access error
* @throws RrdException - Not enough bytes were left in the file to read the integer.
*/
int readInt(boolean dump) throws IOException, RrdException {
//An integer is "alignment" bytes long - 4 bytes on 32-bit, 8 on 64-bit.
if(this.debug) {
System.out.print("Read "+alignment+" bytes (int) from offset "+ras.getFilePointer()+":");
}
if(ras.read(buffer, 0, alignment) != alignment) {
throw new RrdException("Invalid RRD file");
}
int value;
if (bigEndian) {
if(alignment == 8) {
//For big-endian, the low 4-bytes of the 64-bit integer are the last 4 bytes
value = (0xFF & buffer[7]) | ((0xFF & buffer[6]) << 8)
| ((0xFF & buffer[5]) << 16) | ((0xFF & buffer[4]) << 24);
} else {
value = (0xFF & buffer[3]) | ((0xFF & buffer[2]) << 8)
| ((0xFF & buffer[1]) << 16) | ((0xFF & buffer[0]) << 24);
}
}
else {
//For little-endian, there's no difference between 4 and 8 byte alignment.
// The first 4 bytes are the low end of a 64-bit number
value = (0xFF & buffer[0]) | ((0xFF & buffer[1]) << 8)
| ((0xFF & buffer[2]) << 16) | ((0xFF & buffer[3]) << 24);
}
if(this.debug) {
System.out.println(value);
}
return value;
}
String readString(int maxLength) throws IOException, RrdException {
if(this.debug) {
System.out.print("Read "+maxLength+" bytes (string) from offset "+ras.getFilePointer()+":");
}
maxLength = ras.read(buffer, 0, maxLength);
if(maxLength == -1) {
throw new RrdException("Invalid RRD file");
}
String result = new String(buffer, 0, maxLength).trim();
if(this.debug) {
System.out.println( result +":");
}
return result;
}
void skipBytes(final int n) throws IOException {
int bytesSkipped = ras.skipBytes(n);
if(this.debug) {
System.out.println("Skipping "+bytesSkipped+" bytes");
}
}
int align(int boundary) throws IOException {
int skip = (int) (boundary - (ras.getFilePointer() % boundary)) % boundary;
if (skip != 0) {
skip = ras.skipBytes(skip);
}
if(this.debug) {
System.out.println("Aligning to boundary "+ boundary +". Offset is now "+ras.getFilePointer());
}
return skip;
}
int align() throws IOException {
return align(alignment);
}
long info() throws IOException {
return ras.getFilePointer();
}
long getFilePointer() throws IOException {
return ras.getFilePointer();
}
void close() throws IOException {
ras.close();
}
}