AbstractFixedLengthData.java

// <editor-fold defaultstate="collapsed" desc="license">
/*
 * Copyright (c) 2009, Karl H. Beckers
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 **/
// </editor-fold>
package net.jarre_de_the.griffin.types.data;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import net.jarre_de_the.griffin.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The parent for all data types that have a defined, fixed length in bytes,
 * irrespective of their content.
 *
 * @author charly4711
 * @param <T>
 * @param <P>
 */
public abstract class AbstractFixedLengthData<T extends Number, P extends AbstractData> extends AbstractData<P> {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFixedLengthData.class);

// I want this, but I can't have it in Java:
//    abstract static public P read(RandomAccessFile in)
//            throws IOException;
    /**
     * Returns the value of the instance in a format fit for physical storage in
     * a GFF file.
     *
     * @return
     */
    abstract public byte[] getValueAsByteArray();

    /**
     * Returns the value of the instance as a Number with enough bits to store
     * the value. The numeric representation in Java may be off, though, due to
     * differences regarding signed-ness.
     *
     * @return
     */
    abstract public T getValue();

    /**
     * A way to ask an instance about the number of bytes it needs. This way
     * code that can work with various subclasses of AbstractFixedLengthData
     * doesn't need to use ugly reflection hacks to find that out.
     *
     * @return
     */
    abstract public int length();

    /**
     * Reads a given Number from a file at the current location of the file
     * pointer.
     *
     * @param in
     * @param clazz Long.class, Double.class, Float.class, Integer.class,
     * Short.class, or (the default) Byte.class
     * @return
     * @throws IOException
     */
    protected static Number read(RandomAccessFile in, Class clazz) throws IOException {
        int length = Util.getByteLength(clazz);
        LOGGER.debug("Reading " + length + " bytes from file.");
        byte[] buf = new byte[length];
        in.readFully(buf);
        LOGGER.trace("Read this buffer: " + new String(buf, Util.CHARSET_US_ASCII));

        // GFF data types are always stored as little endian, no matter what
        // the native byte order, so we need to make sure we interpret the
        // data read in that way
        ByteBuffer bb = ByteBuffer.wrap(buf);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        Number value;

        if (Long.class.isAssignableFrom(clazz)) {
            value = bb.getLong(0);
        } else if (Double.class.isAssignableFrom(clazz)) {
            value = bb.getDouble(0);
        } else if (Float.class.isAssignableFrom(clazz)) {
            value = bb.getFloat(0);
        } else if (Integer.class.isAssignableFrom(clazz)) {
            value = bb.getInt(0);
        } else if (Short.class.isAssignableFrom(clazz)) {
            value = bb.getShort(0);
        } else {
            // Byte
            value = bb.get(0);
        }
        LOGGER.trace("Endianness corrected to yield: " + value);
        return value;
    }

    /**
     * @see #getValueAsByteArray() 
     *
     * @param value
     * @return
     */
    protected byte[] getValueAsByteArray(T value) {
        if (null != value) {
            Class<T> clazz = (Class<T>) value.getClass();
            int length = Util.getByteLength(clazz);

            byte[] buf = new byte[length];
            // GFF data types are always little endian, no matter what the
            // native byte order, so we need to make sure we write our data in that
            // way
            ByteBuffer bb = ByteBuffer.wrap(buf);
            bb.order(ByteOrder.LITTLE_ENDIAN);

            if (Long.class.isAssignableFrom(clazz)) {
                bb.putLong(value.longValue());
            } else if (Double.class.isAssignableFrom(clazz)) {
                bb.putDouble(value.doubleValue());
            } else if (Float.class.isAssignableFrom(clazz)) {
                bb.putFloat(value.floatValue());
            } else if (Integer.class.isAssignableFrom(clazz)) {
                bb.putInt(value.intValue());
            } else if (Short.class.isAssignableFrom(clazz)) {
                bb.putShort(value.shortValue());
            } else {
                // Byte
                bb.put(value.byteValue());
            }

            LOGGER.debug("Retrieving byte array to: " + new String(buf, Util.CHARSET_US_ASCII));
            return buf;
        } else {
            return null;
        }
    }
}