All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.util.OutputEncoder Maven / Gradle / Ivy

Go to download

Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data.

There is a newer version: 1.21.0
Show newest version
/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 1996 - 2024 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

package nom.tam.util;

import java.io.EOFException;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import nom.tam.fits.FitsFactory;
import nom.tam.util.type.ElementType;

/**
 * Efficient base class for encoding Java arrays into binary output (primarily for internal use)
 *
 * @author Attila Kovacs
 *
 * @since  1.16
 *
 * @see    InputDecoder
 * @see    ArrayDataFile
 * @see    ArrayInputStream
 * @see    ArrayOutputStream
 */
public abstract class OutputEncoder {

    /**
     * The default local buffer size to use for encoding data into binary format
     */
    private static final int BUFFER_SIZE = FitsFactory.FITS_BLOCK_SIZE;

    /**
     * The output to which to write encoded data (directly or from the conversion buffer)
     */
    protected OutputWriter out;

    /**
     * Cumulative encoded byte count written to the output, including over-writes
     */
    private long count = 0;

    /**
     * A local buffer for efficient conversions before bulk writing to the output
     */
    private OutputBuffer buf;

    /**
     * Instantiates a new Java-to-binary encoder for arrays. To be used by subclass implementations only
     *
     * @see #setOutput(OutputWriter)
     */
    protected OutputEncoder() {
        buf = new OutputBuffer(BUFFER_SIZE);
    }

    /**
     * Instantiates a new Java-to-binary encoder for arrays, writing encoded data to the specified output.
     *
     * @param o the output to which encoded data is to be written.
     */
    public OutputEncoder(OutputWriter o) {
        this();
        setOutput(o);
    }

    /**
     * Sets the output to which encoded data should be written (directly or from the conversion buffer).
     *
     * @param o the new output to which encoded data is to be written.
     */
    protected synchronized void setOutput(OutputWriter o) {
        out = o;
    }

    /**
     * Returns the number of encoded bytes that were written to the output. It does not include bytes written
     * directly (unencoded) to the output, such as via {@link #write(int)} or {@link #write(byte[], int, int)}.
     *
     * @return the number of encoded bytes written to the output.
     *
     * @see    RandomAccess#getFilePointer()
     */
    public synchronized long getCount() {
        return count + buf.buffer.position();
    }

    /**
     * Returns the buffer that is used for conversion, which can be used to collate more elements for writing before
     * bulk flushing data to the output (see {@link OutputBuffer#flush()}).
     *
     * @return the conversion buffer used by this encoder.
     */
    protected OutputBuffer getOutputBuffer() {
        return buf;
    }

    /**
     * Makes sure that there is room in the conversion buffer for an upcoming element conversion, and flushes the buffer
     * as necessary to make room. Subclass implementations should call this method before attempting a conversion
     * operation.
     *
     * @param  bytes       the size of an element we will want to convert. It cannot exceed the size of the conversion
     *                         buffer.
     *
     * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new
     *                         conversion.
     */
    void need(int bytes) throws IOException {
        // TODO Once the deprecated {@link BufferEncoder} is retired, this
        // should become
        // a private method of OutputBuffer, with leading 'buf.' references
        // stripped.
        if (buf.buffer.remaining() < bytes) {
            flush();
        }
    }

    /**
     * Flushes the contents of the conversion buffer to the underlying output.
     *
     * @throws IOException if there was an IO error writing the contents of this buffer to the output.
     */
    protected synchronized void flush() throws IOException {
        int n = buf.buffer.position();
        out.write(buf.data, 0, n);
        count += n;
        buf.rewind();
    }

    /**
     * Writes a byte directly to the output. See the general contract of {@link java.io.DataOutputStream#write(int)}.
     * Unencoded bytes written by this method are not reflected in the value returned by {@link #getCount()}.
     *
     * @param  b           the (unsigned) byte value to write.
     *
     * @throws IOException if there was an underlying IO error
     *
     * @see                java.io.DataOutputStream#write(int)
     */
    protected synchronized void write(int b) throws IOException {
        flush();
        out.write(b);
    }

    /**
     * Writes up to the specified number of bytes from a buffer directly to the output. See the general contract of
     * {@link java.io.DataOutputStream#write(byte[], int, int)}. The number of unencoded bytes written by this method
     * are not reflected in the value returned by {@link #getCount()}.
     *
     * @param  b           the buffer
     * @param  start       the starting buffer index
     * @param  length      the number of bytes to write.
     *
     * @throws IOException if there was an underlying IO error
     *
     * @see                java.io.DataOutputStream#write(byte[], int, int)
     */
    protected synchronized void write(byte[] b, int start, int length) throws IOException {
        flush();
        out.write(b, start, length);
    }

    /**
     * Writes the contents of a Java array to the output translating the data to the required binary representation. The
     * argument may be any generic Java array, including heterogeneous arrays of arrays.
     *
     * @param  o                        the Java array, including heterogeneous arrays of arrays. If null
     *                                      nothing will be written to the output.
     *
     * @throws IOException              if there was an IO error writing to the output
     * @throws IllegalArgumentException if the supplied object is not a Java array or if it contains Java types that are
     *                                      not supported by the decoder.
     *
     * @see                             ArrayDataOutput#writeArray(Object)
     */
    public abstract void writeArray(Object o) throws IOException, IllegalArgumentException;

    /**
     * 

* The conversion buffer for encoding Java arrays (objects) into a binary data representation. *

*

* The buffering is most efficient if multiple conversions (put methods) are collated before a forced * {@link #flush()} call to the output. The caller need not worry about space remaining in the buffer. As new data * is placed (put) into the buffer, the buffer will automatically flush the contents to the output to make space for * new elements as it goes. The caller only needs to call the final {@link #flush()}, to ensure that all elements * bufferes so far are written to the output. *

* *
     * short[] shortArray = new short[100];
     * float[] floaTarray = new float[48];
     *
     * // populate the arrays with data...
     *
     * // Convert to binary representation using the local
     * // conversion buffer.
     * ConversionBuffer buf = getBuffer();
     *
     * // Convert as much data as we want to the output format...
     * buf.putDouble(1.0);
     * buf.putInt(-1);
     * buf.put(shortArray, 0, shortArray.length);
     * buf.put(floatArray, 0, floatArray.length);
     *
     * // Once we are done with a chunk of data, we need to
     * // make sure all it written to the output
     * buf.flush();
     * 
* * @author Attila Kovacs */ protected final class OutputBuffer { /** * the byte array that stores pending data to be written to the output */ private final byte[] data; /** the buffer wrapped for NIO access */ private final ByteBuffer buffer; /** The current type-specific view of the buffer or null */ private Buffer view; private OutputBuffer(int size) { data = new byte[size]; buffer = ByteBuffer.wrap(data); } /** * Sets the byte order of the binary representation to which data is encoded. * * @param order the new byte order * * @see #byteOrder() * @see ByteBuffer#order(ByteOrder) */ protected void setByteOrder(ByteOrder order) { buffer.order(order); } /** * Returns the current byte order of the binary representation to which data is encoded. * * @return the byte order * * @see #setByteOrder(ByteOrder) * @see ByteBuffer#order() */ protected ByteOrder byteOrder() { return buffer.order(); } private boolean isViewingAs(Class type) { if (view == null) { return false; } return type.isAssignableFrom(view.getClass()); } private void assertView(ElementType type) { if (!isViewingAs(type.bufferClass())) { view = type.asTypedBuffer(buffer); } } private void rewind() { buffer.rewind(); view = null; } /** * Puts a single byte into the conversion buffer, making space for it as needed by flushing the current buffer * contents to the output as necessary. * * @param b the byte value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putByte(byte b) throws IOException { need(1); view = null; buffer.put(b); } /** * Puts a 2-byte integer into the conversion buffer, making space for it as needed by flushing the current * buffer contents to the output as necessary. * * @param s the 16-bit integer value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putShort(short s) throws IOException { need(Short.BYTES); view = null; buffer.putShort(s); } /** * Puts a 4-byte integer into the conversion buffer, making space for it as needed by flushing the current * buffer contents to the output as necessary. * * @param i the 32-bit integer value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putInt(int i) throws IOException { need(Integer.BYTES); view = null; buffer.putInt(i); } /** * Puts an 8-byte integer into the conversion buffer, making space for it as needed by flushing the current * buffer contents to the output as necessary. * * @param l the 64-bit integer value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putLong(long l) throws IOException { need(Long.BYTES); view = null; buffer.putLong(l); } /** * Puts an 4-byte single-precision floating point value into the conversion buffer, making space for it as * needed by flushing the current buffer contents to the output as necessary. * * @param f the 32-bit single-precision floating point value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putFloat(float f) throws IOException { need(Float.BYTES); view = null; buffer.putFloat(f); } /** * Puts an 8-byte double-precision floating point value into the conversion buffer, making space for it as * needed by flushing the current buffer contents to the output as necessary. * * @param d the 64-bit double-precision floating point value * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. * * @see #flush() */ protected void putDouble(double d) throws IOException { need(Double.BYTES); view = null; buffer.putDouble(d); } /** * Puts an array of bytes into the conversion buffer, flushing the buffer intermittently as necessary to make * room as it goes. * * @param src an array of byte values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(byte[] src, int start, int length) throws IOException { if (length == 1) { need(1); buffer.put(src[start]); return; } view = null; int got = 0; while (got < length) { need(1); int m = Math.min(length - got, buffer.remaining()); buffer.put(src, start + got, m); got += m; } } /** * Puts an array of 16-bit integers into the conversion buffer, flushing the buffer intermittently as necessary * to make room as it goes. * * @param src an array of 16-bit integer values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(short[] src, int start, int length) throws IOException { if (length == 1 && !isViewingAs(ElementType.SHORT.bufferClass())) { putShort(src[start]); } else { put(ElementType.SHORT, src, start, length); } } /** * Puts an array of 32-bit integers into the conversion buffer, flushing the buffer intermittently as necessary * to make room as it goes. * * @param src an array of 32-bit integer values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(int[] src, int start, int length) throws IOException { if (length == 1 && !isViewingAs(ElementType.INT.bufferClass())) { putInt(src[start]); } else { put(ElementType.INT, src, start, length); } } /** * Puts an array of 64-bit integers into the conversion buffer, flushing the buffer intermittently as necessary * to make room as it goes. * * @param src an array of 64-bit integer values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(long[] src, int start, int length) throws IOException { if (length == 1 && !isViewingAs(ElementType.LONG.bufferClass())) { putLong(src[start]); } else { put(ElementType.LONG, src, start, length); } } /** * Puts an array of 32-bit single-precision floating point values into the conversion buffer, flushing the * buffer intermittently as necessary to make room as it goes. * * @param src an array of 32-bit single-precision floating point values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(float[] src, int start, int length) throws IOException { if (length == 1 && !isViewingAs(ElementType.FLOAT.bufferClass())) { putFloat(src[start]); } else { put(ElementType.FLOAT, src, start, length); } } /** * Puts an array of 64-bit double-precision floating point values into the conversion buffer, flushing the * buffer intermittently as necessary to make room as it goes. * * @param src an array of 64-bit double-precision floating point values * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ protected void put(double[] src, int start, int length) throws IOException { if (length == 1 && !isViewingAs(ElementType.DOUBLE.bufferClass())) { putDouble(src[start]); } else { put(ElementType.DOUBLE, src, start, length); } } /** * Puts an array of 64-bit values into the conversion buffer, flushing the buffer intermittently as necessary to * make room as it goes. * * @param the FITS element type of the the 1D array * @param src a 1D array of values of the specified element type * @param start the index of the first element to convert * @param length the number of elements to convert * * @throws IOException if the conversion buffer could not be flushed to the output to make room for the new * conversion. */ @SuppressWarnings("unchecked") private void put(ElementType e, Object dst, int from, int n) throws EOFException, IOException { int got = 0; while (got < n) { need(e.size()); assertView(e); int m = Math.min(n - got, view.remaining()); e.putArray((B) view, dst, from + got, m); buffer.position(buffer.position() + m * e.size()); got += m; } } } }