--- /dev/null
+<?xml version="1.0"?>
+<project name="fits" default="all" basedir=".">
+ <target name="init" description="Sets build properties">
+ <property name="src" value="${basedir}/src"/>
+ <property name="build" value="${basedir}/build"/>
+ <property name="doc" value="${basedir}/doc"/>
+ <path id="project.class.path">
+ </path>
+ <path id="build.class.path">
+ <!--pathelement location="JUNIT.JAR"/-->
+ <pathelement location="/usr/share/junit-4/lib/junit.jar"/>
+ </path>
+ <path id="test.class.path">
+ <pathelement location="${build}"/>
+ </path>
+ </target>
+ <target name="all" depends="jar,javadoc"
+ description="Pseudo-target that builds JAR and Javadoc">
+ </target>
+ <target name="build" depends="init"
+ description="Compiles the classes">
+ <mkdir dir="${build}"/>
+ <javac destdir="${build}" srcdir="${src}" debug="true"
+ deprecation="true" includeantruntime="false">
+ <classpath refid="project.class.path"/>
+ <classpath refid="build.class.path"/>
+ </javac>
+ </target>
+ <target name="test" depends="build">
+ <junit>
+ <classpath refid="project.class.path" />
+ <classpath refid="test.class.path"/>
+ <formatter type="brief" usefile="false" />
+ <batchtest>
+ <fileset dir="${build}" includes="**/test/*.class" />
+ </batchtest>
+ </junit>
+ </target>
+ <target name="javadoc" depends="init"
+ description="Generates Javadoc API documentation">
+ <mkdir dir="${doc}/api"/>
+ <javadoc packagenames="*"
+ sourcepath="${src}" destdir="${doc}/api"
+ author="true" version="true"
+ use="true" private="true"/>
+ </target>
+ <target name="jar" depends="build"
+ description="Builds a project JAR file">
+ <jar basedir="${build}" jarfile="${basedir}/fits.jar">
+ <manifest>
+ <attribute name="Version" value="1.06.0"/>
+ <attribute name="Main-Class"
+ value="fits"/>
+ </manifest>
+ </jar>
+ </target>
+ <target name="clean" depends="init"
+ description="Erase all generated files and dirs">
+ <delete dir="${build}" verbose="true"/>
+ <delete dir="${doc}/api" verbose="true"/>
+ <delete file="fits.jar" verbose="true"/>
+ </target>
+</project>
--- /dev/null
+package nom.tam.fits;
+
+import nom.tam.util.*;
+import java.lang.reflect.Array;
+import java.io.IOException;
+import java.io.EOFException;
+
+/** An ASCII table. */
+public class AsciiTable extends Data implements TableData {
+
+ /** The number of rows in the table */
+ private int nRows;
+ /** The number of fields in the table */
+ private int nFields;
+ /** The number of bytes in a row */
+ private int rowLen;
+ /** The null string for the field */
+ private String[] nulls;
+ /** The type of data in the field */
+ private Class[] types;
+ /** The offset from the beginning of the row at which the field starts */
+ private int[] offsets;
+ /** The number of bytes in the field */
+ private int[] lengths;
+ /** The byte buffer used to read/write the ASCII table */
+ private byte[] buffer;
+ /** Markers indicating fields that are null */
+ private boolean[] isNull;
+ /** An array of arrays giving the data in the table in
+ * binary numbers
+ */
+ private Object[] data;
+ /** The parser used to convert from buffer to data.
+ */
+ ByteParser bp;
+ /** The actual stream used to input data */
+ ArrayDataInput currInput;
+
+ /** Create an ASCII table given a header */
+ public AsciiTable(Header hdr) throws FitsException {
+
+ nRows = hdr.getIntValue("NAXIS2");
+ nFields = hdr.getIntValue("TFIELDS");
+ rowLen = hdr.getIntValue("NAXIS1");
+
+ types = new Class[nFields];
+ offsets = new int[nFields];
+ lengths = new int[nFields];
+ nulls = new String[nFields];
+
+ for (int i = 0; i < nFields; i += 1) {
+ offsets[i] = hdr.getIntValue("TBCOL" + (i + 1)) - 1;
+ String s = hdr.getStringValue("TFORM" + (i + 1));
+ if (offsets[i] < 0 || s == null) {
+ throw new FitsException("Invalid Specification for column:" + (i + 1));
+ }
+ s = s.trim();
+ char c = s.charAt(0);
+ s = s.substring(1);
+ if (s.indexOf('.') > 0) {
+ s = s.substring(0, s.indexOf('.'));
+ }
+ lengths[i] = Integer.parseInt(s);
+
+ switch (c) {
+ case 'A':
+ types[i] = String.class;
+ break;
+ case 'I':
+ if (lengths[i] > 10) {
+ types[i] = long.class;
+ } else {
+ types[i] = int.class;
+ }
+ break;
+ case 'F':
+ case 'E':
+ types[i] = float.class;
+ break;
+ case 'D':
+ types[i] = double.class;
+ break;
+ }
+
+ nulls[i] = hdr.getStringValue("TNULL" + (i + 1));
+ if (nulls[i] != null) {
+ nulls[i] = nulls[i].trim();
+ }
+ }
+ }
+
+ /** Create an empty ASCII table */
+ public AsciiTable() {
+
+ data = new Object[0];
+ buffer = null;
+ nFields = 0;
+ nRows = 0;
+ rowLen = 0;
+ types = new Class[0];
+ lengths = new int[0];
+ offsets = new int[0];
+ nulls = new String[0];
+ }
+
+ /** Read in an ASCII table. Reading is deferred if
+ * we are reading from a random access device
+ */
+ public void read(ArrayDataInput str) throws FitsException {
+
+ setFileOffset(str);
+ currInput = str;
+
+ if (str instanceof RandomAccess) {
+
+ try {
+ str.skipBytes((long) nRows * rowLen);
+ } catch (IOException e) {
+ throw new FitsException("Error skipping data: " + e);
+ }
+
+ } else {
+ try {
+ if ((long) rowLen * nRows > Integer.MAX_VALUE) {
+ throw new FitsException("Cannot read ASCII table > 2 GB");
+ }
+ getBuffer(rowLen * nRows, 0);
+ } catch (IOException e) {
+ throw new FitsException("Error reading ASCII table:" + e);
+ }
+ }
+
+ try {
+ str.skipBytes(FitsUtil.padding(nRows * rowLen));
+ } catch (EOFException e) {
+ throw new PaddingException("EOF skipping padding after ASCII Table:" + e, this);
+ } catch (IOException e) {
+ throw new FitsException("Error skipping padding after ASCII Table:" + e);
+ }
+ }
+
+ /** Read some data into the buffer.
+ */
+ private void getBuffer(int size, long offset) throws IOException, FitsException {
+
+ if (currInput == null) {
+ throw new IOException("No stream open to read");
+ }
+
+ buffer = new byte[size];
+ if (offset != 0) {
+ FitsUtil.reposition(currInput, offset);
+ }
+ currInput.readFully(buffer);
+ bp = new ByteParser(buffer);
+ }
+
+ /** Get the ASCII table information.
+ * This will actually do the read if it had previously been deferred
+ */
+ public Object getData() throws FitsException {
+
+ if (data == null) {
+
+ data = new Object[nFields];
+
+ for (int i = 0; i < nFields; i += 1) {
+ data[i] = ArrayFuncs.newInstance(types[i], nRows);
+ }
+
+ if (buffer == null) {
+ long newOffset = FitsUtil.findOffset(currInput);
+ try {
+ getBuffer(nRows * rowLen, fileOffset);
+
+ } catch (IOException e) {
+ throw new FitsException("Error in deferred read -- file closed prematurely?:" + e);
+ }
+ FitsUtil.reposition(currInput, newOffset);
+ }
+
+ bp.setOffset(0);
+
+ int rowOffset;
+ for (int i = 0; i < nRows; i += 1) {
+ rowOffset = rowLen * i;
+ for (int j = 0; j < nFields; j += 1) {
+ if (!extractElement(rowOffset + offsets[j], lengths[j], data, j, i, nulls[j])) {
+ if (isNull == null) {
+ isNull = new boolean[nRows * nFields];
+ }
+
+ isNull[j + i * nFields] = true;
+ }
+ }
+ }
+ }
+
+ return data;
+ }
+
+ /** Move an element from the buffer into a data array.
+ * @param offset The offset within buffer at which the element starts.
+ * @param length The number of bytes in the buffer for the element.
+ * @param array An array of objects, each of which is a simple array.
+ * @param col Which element of array is to be modified?
+ * @param row Which index into that element is to be modified?
+ * @param nullFld What string signifies a null element?
+ */
+ private boolean extractElement(int offset, int length, Object[] array,
+ int col, int row, String nullFld)
+ throws FitsException {
+
+ bp.setOffset(offset);
+
+ if (nullFld != null) {
+ String s = bp.getString(length);
+ if (s.trim().equals(nullFld)) {
+ return false;
+ }
+ bp.skip(-length);
+ }
+ try {
+ if (array[col] instanceof String[]) {
+ ((String[]) array[col])[row] = bp.getString(length);
+ } else if (array[col] instanceof int[]) {
+ ((int[]) array[col])[row] = bp.getInt(length);
+ } else if (array[col] instanceof float[]) {
+ ((float[]) array[col])[row] = bp.getFloat(length);
+ } else if (array[col] instanceof double[]) {
+ ((double[]) array[col])[row] = bp.getDouble(length);
+ } else if (array[col] instanceof long[]) {
+ ((long[]) array[col])[row] = bp.getLong(length);
+ } else {
+ throw new FitsException("Invalid type for ASCII table conversion:" + array[col]);
+ }
+ } catch (FormatException e) {
+ throw new FitsException("Error parsing data at row,col:" + row + "," + col
+ + " " + e);
+ }
+ return true;
+ }
+
+ /** Get a column of data */
+ public Object getColumn(int col) throws FitsException {
+ if (data == null) {
+ getData();
+ }
+ return data[col];
+ }
+
+ /** Get a row. If the data has not yet been read just
+ * read this row.
+ */
+ public Object[] getRow(int row) throws FitsException {
+
+ if (data != null) {
+ return singleRow(row);
+ } else {
+ return parseSingleRow(row);
+ }
+ }
+
+ /** Get a single element as a one-d array.
+ * We return String's as arrays for consistency though
+ * they could be returned as a scalar.
+ */
+ public Object getElement(int row, int col) throws FitsException {
+ if (data != null) {
+ return singleElement(row, col);
+ } else {
+ return parseSingleElement(row, col);
+ }
+ }
+
+ /** Extract a single row from a table. This returns
+ * an array of Objects each of which is an array of length 1.
+ */
+ private Object[] singleRow(int row) {
+
+
+ Object[] res = new Object[nFields];
+ for (int i = 0; i < nFields; i += 1) {
+ if (isNull == null || !isNull[row * nFields + i]) {
+ res[i] = ArrayFuncs.newInstance(types[i], 1);
+ System.arraycopy(data[i], row, res[i], 0, 1);
+ }
+ }
+ return res;
+ }
+
+ /** Extract a single element from a table. This returns
+ * an array of length 1.
+ */
+ private Object singleElement(int row, int col) {
+
+ Object res = null;
+ if (isNull == null || !isNull[row * nFields + col]) {
+ res = ArrayFuncs.newInstance(types[col], 1);
+ System.arraycopy(data[col], row, res, 0, 1);
+ }
+ return res;
+ }
+
+ /** Read a single row from the table. This returns
+ * a set of arrays of dimension 1.
+ */
+ private Object[] parseSingleRow(int row) throws FitsException {
+
+ int offset = row * rowLen;
+
+ Object[] res = new Object[nFields];
+
+ try {
+ getBuffer(rowLen, fileOffset + row * rowLen);
+ } catch (IOException e) {
+ throw new FitsException("Unable to read row");
+ }
+
+ for (int i = 0; i < nFields; i += 1) {
+ res[i] = ArrayFuncs.newInstance(types[i], 1);
+ if (!extractElement(offsets[i], lengths[i], res, i, 0, nulls[i])) {
+ res[i] = null;
+ }
+ }
+
+ // Invalidate buffer for future use.
+ buffer = null;
+ return res;
+ }
+
+ /** Read a single element from the table. This returns
+ * an array of dimension 1.
+ */
+ private Object parseSingleElement(int row, int col) throws FitsException {
+
+ Object[] res = new Object[1];
+ try {
+ getBuffer(lengths[col], fileOffset + row * rowLen + offsets[col]);
+ } catch (IOException e) {
+ buffer = null;
+ throw new FitsException("Unable to read element");
+ }
+ res[0] = ArrayFuncs.newInstance(types[col], 1);
+
+ if (extractElement(0, lengths[col], res, 0, 0, nulls[col])) {
+ buffer = null;
+ return res[0];
+
+ } else {
+
+ buffer = null;
+ return null;
+ }
+ }
+
+ /** Write the data to an output stream.
+ */
+ public void write(ArrayDataOutput str) throws FitsException {
+
+ // Make sure we have the data in hand.
+
+ getData();
+ // If buffer is still around we can just reuse it,
+ // since nothing we've done has invalidated it.
+
+ if (buffer == null) {
+
+ if (data == null) {
+ throw new FitsException("Attempt to write undefined ASCII Table");
+ }
+
+ if ((long) nRows * rowLen > Integer.MAX_VALUE) {
+ throw new FitsException("Cannot write ASCII table > 2 GB");
+ }
+
+ buffer = new byte[nRows * rowLen];
+
+ bp = new ByteParser(buffer);
+ for (int i = 0; i < buffer.length; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+
+ ByteFormatter bf = new ByteFormatter();
+ bf.setTruncationThrow(false);
+ bf.setTruncateOnOverflow(true);
+
+ for (int i = 0; i < nRows; i += 1) {
+
+ for (int j = 0; j < nFields; j += 1) {
+ int offset = i * rowLen + offsets[j];
+ int len = lengths[j];
+
+ try {
+ if (isNull != null && isNull[i * nFields + j]) {
+ if (nulls[j] == null) {
+ throw new FitsException("No null value set when needed");
+ }
+ bf.format(nulls[j], buffer, offset, len);
+ } else {
+ if (types[j] == String.class) {
+ String[] s = (String[]) data[j];
+ bf.format(s[i], buffer, offset, len);
+ } else if (types[j] == int.class) {
+ int[] ia = (int[]) data[j];
+ bf.format(ia[i], buffer, offset, len);
+ } else if (types[j] == float.class) {
+ float[] fa = (float[]) data[j];
+ bf.format(fa[i], buffer, offset, len);
+ } else if (types[j] == double.class) {
+ double[] da = (double[]) data[j];
+ bf.format(da[i], buffer, offset, len);
+ } else if (types[j] == long.class) {
+ long[] la = (long[]) data[j];
+ bf.format(la[i], buffer, offset, len);
+ }
+ }
+ } catch (TruncationException e) {
+ System.err.println("Ignoring truncation error:" + i + "," + j);
+ }
+ }
+ }
+ }
+
+ // Now write the buffer.
+ try {
+ str.write(buffer);
+ FitsUtil.pad(str, buffer.length, (byte) ' ');
+ } catch (IOException e) {
+ throw new FitsException("Error writing ASCII Table data");
+ }
+ }
+
+ /** Replace a column with new data.
+ */
+ public void setColumn(int col, Object newData) throws FitsException {
+ if (data == null) {
+ getData();
+ }
+ if (col < 0 || col >= nFields
+ || newData.getClass() != data[col].getClass()
+ || Array.getLength(newData) != Array.getLength(data[col])) {
+ throw new FitsException("Invalid column/column mismatch:" + col);
+ }
+ data[col] = newData;
+
+ // Invalidate the buffer.
+ buffer = null;
+
+ }
+
+ /** Modify a row in the table */
+ public void setRow(int row, Object[] newData) throws FitsException {
+ if (row < 0 || row > nRows) {
+ throw new FitsException("Invalid row in setRow");
+ }
+
+ if (data == null) {
+ getData();
+ }
+ for (int i = 0; i < nFields; i += 1) {
+ try {
+ System.arraycopy(newData[i], 0, data[i], row, 1);
+ } catch (Exception e) {
+ throw new FitsException("Unable to modify row: incompatible data:" + row);
+ }
+ setNull(row, i, false);
+ }
+
+ // Invalidate the buffer
+ buffer = null;
+
+ }
+
+ /** Modify an element in the table */
+ public void setElement(int row, int col, Object newData) throws FitsException {
+
+ if (data == null) {
+ getData();
+ }
+ try {
+ System.arraycopy(newData, 0, data[col], row, 1);
+ } catch (Exception e) {
+ throw new FitsException("Incompatible element:" + row + "," + col);
+ }
+ setNull(row, col, false);
+
+ // Invalidate the buffer
+ buffer = null;
+
+ }
+
+ /** Mark (or unmark) an element as null. Note that if this FITS file is latter
+ * written out, a TNULL keyword needs to be defined in the corresponding
+ * header. This routine does not add an element for String columns.
+ */
+ public void setNull(int row, int col, boolean flag) {
+ if (flag) {
+ if (isNull == null) {
+ isNull = new boolean[nRows * nFields];
+ }
+ isNull[col + row * nFields] = true;
+ } else if (isNull != null) {
+ isNull[col + row * nFields] = false;
+ }
+
+ // Invalidate the buffer
+ buffer = null;
+ }
+
+ /** See if an element is null.
+ */
+ public boolean isNull(int row, int col) {
+ if (isNull != null) {
+ return isNull[row * nFields + col];
+ } else {
+ return false;
+ }
+ }
+
+ /** Add a row to the table. Users should be cautious
+ * of calling this routine directly rather than the corresponding
+ * routine in AsciiTableHDU since this routine knows nothing
+ * of the FITS header modifications required.
+ */
+ public int addColumn(Object newCol) throws FitsException {
+ int maxLen = 0;
+ if (newCol instanceof String[]) {
+
+ String[] sa = (String[]) newCol;
+ for (int i = 0; i < sa.length; i += 1) {
+ if (sa[i] != null && sa[i].length() > maxLen) {
+ maxLen = sa[i].length();
+ }
+ }
+ } else if (newCol instanceof double[]) {
+ maxLen = 24;
+ } else if (newCol instanceof int[]) {
+ maxLen = 10;
+ } else if (newCol instanceof long[]) {
+ maxLen = 20;
+ } else if (newCol instanceof float[]) {
+ maxLen = 16;
+ }
+ addColumn(newCol, maxLen);
+
+ // Invalidate the buffer
+ buffer = null;
+
+ return nFields;
+ }
+
+ /** This version of addColumn allows the user to override
+ * the default length associated with each column type.
+ */
+ public int addColumn(Object newCol, int length) throws FitsException {
+
+ if (nFields > 0 && Array.getLength(newCol) != nRows) {
+ throw new FitsException("New column has different number of rows");
+ }
+
+ if (nFields == 0) {
+ nRows = Array.getLength(newCol);
+ }
+
+ Object[] newData = new Object[nFields + 1];
+ int[] newOffsets = new int[nFields + 1];
+ int[] newLengths = new int[nFields + 1];
+ Class[] newTypes = new Class[nFields + 1];
+ String[] newNulls = new String[nFields + 1];
+
+ System.arraycopy(data, 0, newData, 0, nFields);
+ System.arraycopy(offsets, 0, newOffsets, 0, nFields);
+ System.arraycopy(lengths, 0, newLengths, 0, nFields);
+ System.arraycopy(types, 0, newTypes, 0, nFields);
+ System.arraycopy(nulls, 0, newNulls, 0, nFields);
+
+ data = newData;
+ offsets = newOffsets;
+ lengths = newLengths;
+ types = newTypes;
+ nulls = newNulls;
+
+ newData[nFields] = newCol;
+ offsets[nFields] = rowLen + 1;
+ lengths[nFields] = length;
+ types[nFields] = ArrayFuncs.getBaseClass(newCol);
+
+ rowLen += length + 1;
+ if (isNull != null) {
+ boolean[] newIsNull = new boolean[nRows * (nFields + 1)];
+ // Fix the null pointers.
+ int add = 0;
+ for (int i = 0; i < isNull.length; i += 1) {
+ if (i % nFields == 0) {
+ add += 1;
+ }
+ if (isNull[i]) {
+ newIsNull[i + add] = true;
+ }
+ }
+ isNull = newIsNull;
+ }
+ nFields += 1;
+
+ // Invalidate the buffer
+ buffer = null;
+
+ return nFields;
+ }
+
+ /** Add a row to the FITS table. */
+ public int addRow(Object[] newRow) throws FitsException {
+
+ // If there are no fields, then this is the
+ // first row. We need to add in each of the columns
+ // to get the descriptors set up.
+
+
+ if (nFields == 0) {
+ for (int i = 0; i < newRow.length; i += 1) {
+ addColumn(newRow[i]);
+ }
+ } else {
+ for (int i = 0; i < nFields; i += 1) {
+ try {
+ Object o = ArrayFuncs.newInstance(types[i], nRows + 1);
+ System.arraycopy(data[i], 0, o, 0, nRows);
+ System.arraycopy(newRow[i], 0, o, nRows, 1);
+ data[i] = o;
+ } catch (Exception e) {
+ throw new FitsException("Error adding row:" + e);
+ }
+ }
+ nRows += 1;
+ }
+
+ // Invalidate the buffer
+ buffer = null;
+
+ return nRows;
+ }
+
+ /** Delete rows from a FITS table */
+ public void deleteRows(int start, int len) throws FitsException {
+
+ if (nRows == 0 || start < 0 || start >= nRows || len <= 0) {
+ return;
+ }
+ if (start + len > nRows) {
+ len = nRows - start;
+ }
+ getData();
+ try {
+ for (int i = 0; i < nFields; i += 1) {
+ Object o = ArrayFuncs.newInstance(types[i], nRows - len);
+ System.arraycopy(data[i], 0, o, 0, start);
+ System.arraycopy(data[i], start + len, o, start, nRows - len - start);
+ data[i] = o;
+ }
+ nRows -= len;
+ } catch (Exception e) {
+ throw new FitsException("Error deleting row:" + e);
+ }
+ }
+
+ /** Set the null string for a columns.
+ * This is not a public method since we
+ * want users to call the method in AsciiTableHDU
+ * and update the header also.
+ */
+ void setNullString(int col, String newNull) {
+ if (col >= 0 && col < nulls.length) {
+ nulls[col] = newNull;
+ }
+ }
+
+ /** Return the size of the data section */
+ protected long getTrueSize() {
+ return (long) (nRows) * rowLen;
+ }
+
+ /** Fill in a header with information that points to this
+ * data.
+ */
+ public void fillHeader(Header hdr) {
+
+ try {
+ hdr.setXtension("TABLE");
+ hdr.setBitpix(8);
+ hdr.setNaxes(2);
+ hdr.setNaxis(1, rowLen);
+ hdr.setNaxis(2, nRows);
+ Cursor iter = hdr.iterator();
+ iter.setKey("NAXIS2");
+ iter.next();
+ iter.add("PCOUNT", new HeaderCard("PCOUNT", 0,"ntf::asciitable:pcount:1"));
+ iter.add("GCOUNT", new HeaderCard("GCOUNT", 1, "ntf::asciitable:gcount:1"));
+ iter.add("TFIELDS", new HeaderCard("TFIELDS", nFields, "ntf::asciitable:tfields:1"));
+
+ for (int i = 0; i < nFields; i += 1) {
+ addColInfo(i, iter);
+ }
+
+ } catch (HeaderCardException e) {
+ System.err.println("ImpossibleException in fillHeader:" + e);
+ }
+
+ }
+
+ int addColInfo(int col, Cursor iter) throws HeaderCardException {
+
+ String tform = null;
+ if (types[col] == String.class) {
+ tform = "A" + lengths[col];
+ } else if (types[col] == int.class
+ || types[col] == long.class) {
+ tform = "I" + lengths[col];
+ } else if (types[col] == float.class) {
+ tform = "E" + lengths[col] + ".0";
+ } else if (types[col] == double.class) {
+ tform = "D" + lengths[col] + ".0";
+ }
+ String key;
+ key = "TFORM" + (col + 1);
+ iter.add(key, new HeaderCard(key, tform, "ntf::asciitable:tformN:1"));
+ key = "TBCOL" + (col + 1);
+ iter.add(key, new HeaderCard(key, offsets[col] + 1, "ntf::asciitable:tbcolN:1"));
+ return lengths[col];
+ }
+
+ /** Get the number of rows in the table */
+ public int getNRows() {
+ return nRows;
+ }
+
+ /** Get the number of columns in the table */
+ public int getNCols() {
+ return nFields;
+ }
+
+ /** Get the number of bytes in a row */
+ public int getRowLen() {
+ return rowLen;
+ }
+
+ /** Delete columns from the table.
+ */
+ public void deleteColumns(int start, int len) throws FitsException {
+
+ getData();
+
+ Object[] newData = new Object[nFields - len];
+ int[] newOffsets = new int[nFields - len];
+ int[] newLengths = new int[nFields - len];
+ Class[] newTypes = new Class[nFields - len];
+ String[] newNulls = new String[nFields - len];
+
+ // Copy in the initial stuff...
+ System.arraycopy(data, 0, newData, 0, start);
+ // Don't do the offsets here.
+ System.arraycopy(lengths, 0, newLengths, 0, start);
+ System.arraycopy(types, 0, newTypes, 0, start);
+ System.arraycopy(nulls, 0, newNulls, 0, start);
+
+ // Copy in the final
+ System.arraycopy(data, start + len, newData, start, nFields - start - len);
+ // Don't do the offsets here.
+ System.arraycopy(lengths, start + len, newLengths, start, nFields - start - len);
+ System.arraycopy(types, start + len, newTypes, start, nFields - start - len);
+ System.arraycopy(nulls, start + len, newNulls, start, nFields - start - len);
+
+ for (int i = start; i < start + len; i += 1) {
+ rowLen -= (lengths[i] + 1);
+ }
+
+ data = newData;
+ offsets = newOffsets;
+ lengths = newLengths;
+ types = newTypes;
+ nulls = newNulls;
+
+ if (isNull != null) {
+ boolean found = false;
+
+ boolean[] newIsNull = new boolean[nRows * (nFields - len)];
+ for (int i = 0; i < nRows; i += 1) {
+ int oldOff = nFields * i;
+ int newOff = (nFields - len) * i;
+ for (int col = 0; col < start; col += 1) {
+ newIsNull[newOff + col] = isNull[oldOff + col];
+ found = found || isNull[oldOff + col];
+ }
+ for (int col = start + len; col < nFields; col += 1) {
+ newIsNull[newOff + col - len] = isNull[oldOff + col];
+ found = found || isNull[oldOff + col];
+ }
+ }
+ if (found) {
+ isNull = newIsNull;
+ } else {
+ isNull = null;
+ }
+ }
+
+ // Invalidate the buffer
+ buffer = null;
+
+ nFields -= len;
+ }
+
+ /** This is called after we delete columns. The HDU
+ * doesn't know how to update the TBCOL entries.
+ */
+ public void updateAfterDelete(int oldNCol, Header hdr) throws FitsException {
+
+ int offset = 0;
+ for (int i = 0; i < nFields; i += 1) {
+ offsets[i] = offset;
+ hdr.addValue("TBCOL" + (i + 1), offset + 1, "ntf::asciitable:tbcolN:2");
+ offset += lengths[i] + 1;
+ }
+ for (int i = nFields; i < oldNCol; i += 1) {
+ hdr.deleteKey("TBCOL" + (i + 1));
+ }
+
+ hdr.addValue("NAXIS1", rowLen, "ntf::asciitable:naxis1:1");
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+import java.io.IOException;
+import nom.tam.util.*;
+import java.util.Iterator;
+
+
+/*
+ * Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/**
+ * FITS ASCII table header/data unit
+ */
+public class AsciiTableHDU extends TableHDU {
+
+ /** Just a copy of myData with the correct type */
+ AsciiTable data;
+ /** The standard column stems for an ASCII table.
+ * Note that TBCOL is not included here -- it needs to
+ * be handled specially since it does not simply shift.
+ */
+ private String[] keyStems = {"TFORM", "TZERO", "TNULL", "TTYPE", "TUNIT"};
+
+ /**
+ * Create an ascii table header/data unit.
+ * @param header the template specifying the ascii table.
+ * @param data the FITS data structure containing the table data.
+ * @exception FitsException if there was a problem with the header.
+ */
+ public AsciiTableHDU(Header h, Data d) {
+ super((TableData) d);
+ myHeader = h;
+ data = (AsciiTable) d;
+ myData = d;
+ }
+
+ /**
+ * Check that this is a valid ascii table header.
+ * @param header to validate.
+ * @return <CODE>true</CODE> if this is an ascii table header.
+ */
+ public static boolean isHeader(Header header) {
+ return header.getStringValue("XTENSION").trim().equals("TABLE");
+ }
+
+ /**
+ * Check that this HDU has a valid header.
+ * @return <CODE>true</CODE> if this HDU has a valid header.
+ */
+ public boolean isHeader() {
+ return isHeader(myHeader);
+ }
+
+ /** Check if this data is usable as an ASCII table.
+ */
+ public static boolean isData(Object o) {
+
+ if (o instanceof Object[]) {
+ Object[] oo = (Object[]) o;
+ for (int i = 0; i < oo.length; i += 1) {
+ if (oo[i] instanceof String[]
+ || oo[i] instanceof int[]
+ || oo[i] instanceof long[]
+ || oo[i] instanceof float[]
+ || oo[i] instanceof double[]) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Create a Data object to correspond to the header description.
+ * @return An unfilled Data object which can be used to read
+ * in the data for this HDU.
+ * @exception FitsException if the Data object could not be created
+ * from this HDU's Header
+ */
+ public static Data manufactureData(Header hdr) throws FitsException {
+ return new AsciiTable(hdr);
+ }
+
+ /** Create an empty data structure corresponding to the input header.
+ */
+ public Data manufactureData() throws FitsException {
+ return manufactureData(myHeader);
+ }
+
+ /** Create a header to match the input data. */
+ public static Header manufactureHeader(Data d) throws FitsException {
+ Header hdr = new Header();
+ d.fillHeader(hdr);
+ Iterator iter = hdr.iterator();
+ return hdr;
+ }
+
+ /** Create a ASCII table data structure from an array of objects
+ * representing the columns.
+ */
+ public static Data encapsulate(Object o) throws FitsException {
+
+ Object[] oo = (Object[]) o;
+ AsciiTable d = new AsciiTable();
+ for (int i = 0; i < oo.length; i += 1) {
+ d.addColumn(oo[i]);
+ }
+ return d;
+ }
+
+ /**
+ * Skip the ASCII table and throw an exception.
+ * @param stream the stream from which the data is read.
+ */
+ public void readData(ArrayDataInput stream)
+ throws FitsException {
+ myData.read(stream);
+ }
+
+ /** Mark an entry as null.
+ */
+ public void setNull(int row, int col, boolean flag) {
+
+ if (flag) {
+ String nullStr = myHeader.getStringValue("TNULL" + (col + 1));
+ if (nullStr == null) {
+ setNullString(col, "NULL");
+ }
+ }
+ data.setNull(row, col, flag);
+ }
+
+ /** See if an element is null */
+ public boolean isNull(int row, int col) {
+ return data.isNull(row, col);
+ }
+
+ /** Set the null string for a column */
+ public void setNullString(int col, String newNull) {
+ myHeader.positionAfterIndex("TBCOL", col + 1);
+ try {
+ myHeader.addValue("TNULL" + (col + 1), newNull, "ntf::asciitablehdu:tnullN:1");
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception in setNullString" + e);
+ }
+ data.setNullString(col, newNull);
+ }
+
+ /** Add a column */
+ public int addColumn(Object newCol) throws FitsException {
+
+ data.addColumn(newCol);
+
+ // Move the iterator to point after all the data describing
+ // the previous column.
+
+ Cursor iter =
+ myHeader.positionAfterIndex("TBCOL", data.getNCols());
+
+ int rowlen = data.addColInfo(getNCols(), iter);
+ int oldRowlen = myHeader.getIntValue("NAXIS1");
+ myHeader.setNaxis(1, rowlen + oldRowlen);
+
+ int oldTfields = myHeader.getIntValue("TFIELDS");
+ try {
+ myHeader.addValue("TFIELDS", oldTfields + 1, "ntf::asciitablehdu:tfields:1");
+ } catch (Exception e) {
+ System.err.println("Impossible exception at addColumn:" + e);
+ }
+ return getNCols();
+ }
+
+ /**
+ * Print a little information about the data set.
+ */
+ public void info() {
+ System.out.println("ASCII Table:");
+ System.out.println(" Header:");
+ System.out.println(" Number of fields:" + myHeader.getIntValue("TFIELDS"));
+ System.out.println(" Number of rows: " + myHeader.getIntValue("NAXIS2"));
+ System.out.println(" Length of row: " + myHeader.getIntValue("NAXIS1"));
+ System.out.println(" Data:");
+ Object[] data = (Object[]) getKernel();
+ for (int i = 0; i < getNCols(); i += 1) {
+ System.out.println(" " + i + ":" + ArrayFuncs.arrayDescription(data[i]));
+ }
+ }
+
+ /** Return the FITS data structure associated with this HDU.
+ */
+ public Data getData() {
+ return data;
+ }
+
+ /** Return the keyword column stems for an ASCII table.
+ */
+ public String[] columnKeyStems() {
+ return keyStems;
+ }
+}
+
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/** This exception indicates that an error
+ * was detected while parsing a FITS header record.
+ */
+public class BadHeaderException
+ extends FitsException {
+
+ public BadHeaderException() {
+ super();
+ }
+
+ public BadHeaderException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+import java.io.IOException;
+
+import nom.tam.util.ArrayDataInput;
+import nom.tam.util.ArrayDataOutput;
+import java.util.Iterator;
+
+import java.util.Date;
+
+/** This abstract class is the parent of all HDU types.
+ * It provides basic functionality for an HDU.
+ */
+public abstract class BasicHDU implements FitsElement {
+
+ public static final int BITPIX_BYTE = 8;
+ public static final int BITPIX_SHORT = 16;
+ public static final int BITPIX_INT = 32;
+ public static final int BITPIX_LONG = 64;
+ public static final int BITPIX_FLOAT = -32;
+ public static final int BITPIX_DOUBLE = -64;
+ /** The associated header. */
+ protected Header myHeader = null;
+ /** The associated data unit. */
+ protected Data myData = null;
+ /** Is this the first HDU in a FITS file? */
+ protected boolean isPrimary = false;
+
+ /** Create a Data object to correspond to the header description.
+ * @return An unfilled Data object which can be used to read
+ * in the data for this HDU.
+ * @exception FitsException if the Data object could not be created
+ * from this HDU's Header
+ */
+ abstract Data manufactureData() throws FitsException;
+
+ /** Skip the Data object immediately after the given Header object on
+ * the given stream object.
+ * @param stream the stream which contains the data.
+ * @param Header template indicating length of Data section
+ * @exception IOException if the Data object could not be skipped.
+ */
+ public static void skipData(ArrayDataInput stream, Header hdr)
+ throws IOException {
+ stream.skipBytes(hdr.getDataSize());
+ }
+
+ /** Skip the Data object for this HDU.
+ * @param stream the stream which contains the data.
+ * @exception IOException if the Data object could not be skipped.
+ */
+ public void skipData(ArrayDataInput stream)
+ throws IOException {
+ skipData(stream, myHeader);
+ }
+
+ /** Read in the Data object for this HDU.
+ * @param stream the stream from which the data is read.
+ * @exception FitsException if the Data object could not be created
+ * from this HDU's Header
+ */
+ public void readData(ArrayDataInput stream)
+ throws FitsException {
+ myData = null;
+ try {
+ myData = manufactureData();
+ } finally {
+ // if we cannot build a Data object, skip this section
+ if (myData == null) {
+ try {
+ skipData(stream, myHeader);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ myData.read(stream);
+ }
+
+ /** Get the associated header */
+ public Header getHeader() {
+ return myHeader;
+ }
+
+ /** Get the starting offset of the HDU */
+ public long getFileOffset() {
+ return myHeader.getFileOffset();
+ }
+
+ /** Get the associated Data object*/
+ public Data getData() {
+ return myData;
+ }
+
+ /** Get the non-FITS data object */
+ public Object getKernel() {
+ try {
+ return myData.getKernel();
+ } catch (FitsException e) {
+ return null;
+ }
+ }
+
+ /** Get the total size in bytes of the HDU.
+ * @return The size in bytes.
+ */
+ public long getSize() {
+ int size = 0;
+
+ if (myHeader != null) {
+ size += myHeader.getSize();
+ }
+ if (myData != null) {
+ size += myData.getSize();
+ }
+ return size;
+ }
+
+ /** Check that this is a valid header for the HDU.
+ * @param header to validate.
+ * @return <CODE>true</CODE> if this is a valid header.
+ */
+ public static boolean isHeader(Header header) {
+ return false;
+ }
+
+ /** Print out some information about this HDU.
+ */
+ public abstract void info();
+
+ /** Check if a field is present and if so print it out.
+ * @param The header keyword.
+ * @param Was it found in the header?
+ */
+ boolean checkField(String name) {
+ String value = myHeader.getStringValue(name);
+ if (value == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /* Read out the HDU from the data stream. This
+ * will overwrite any existing header and data components.
+ */
+ public void read(ArrayDataInput stream)
+ throws FitsException, IOException {
+ myHeader = Header.readHeader(stream);
+ myData = myHeader.makeData();
+ myData.read(stream);
+ }
+
+ /* Write out the HDU
+ * @param stream The data stream to be written to.
+ */
+ public void write(ArrayDataOutput stream)
+ throws FitsException {
+ if (myHeader != null) {
+ myHeader.write(stream);
+ }
+ if (myData != null) {
+ myData.write(stream);
+ }
+ try {
+ stream.flush();
+ } catch (java.io.IOException e) {
+ throw new FitsException("Error flushing at end of HDU: "
+ + e.getMessage());
+ }
+ }
+
+ /** Is the HDU rewriteable */
+ public boolean rewriteable() {
+ return myHeader.rewriteable() && myData.rewriteable();
+ }
+
+ /** Rewrite the HDU */
+ public void rewrite()
+ throws FitsException, IOException {
+
+ if (rewriteable()) {
+ myHeader.rewrite();
+ myData.rewrite();
+ } else {
+ throw new FitsException("Invalid attempt to rewrite HDU");
+ }
+ }
+
+ /**
+ * Get the String value associated with <CODE>keyword</CODE>.
+ * @param hdr the header piece of an HDU
+ * @param keyword the FITS keyword
+ * @return either <CODE>null</CODE> or a String with leading/trailing
+ * blanks stripped.
+ */
+ public String getTrimmedString(String keyword) {
+ String s = myHeader.getStringValue(keyword);
+ if (s != null) {
+ s = s.trim();
+ }
+ return s;
+ }
+
+ public int getBitPix()
+ throws FitsException {
+ int bitpix = myHeader.getIntValue("BITPIX", -1);
+ switch (bitpix) {
+ case BITPIX_BYTE:
+ case BITPIX_SHORT:
+ case BITPIX_INT:
+ case BITPIX_FLOAT:
+ case BITPIX_DOUBLE:
+ break;
+ default:
+ throw new FitsException("Unknown BITPIX type " + bitpix);
+ }
+
+ return bitpix;
+ }
+
+ public int[] getAxes()
+ throws FitsException {
+ int nAxis = myHeader.getIntValue("NAXIS", 0);
+ if (nAxis < 0) {
+ throw new FitsException("Negative NAXIS value " + nAxis);
+ }
+ if (nAxis > 999) {
+ throw new FitsException("NAXIS value " + nAxis + " too large");
+ }
+
+ if (nAxis == 0) {
+ return null;
+ }
+
+ int[] axes = new int[nAxis];
+ for (int i = 1; i <= nAxis; i++) {
+ axes[nAxis - i] = myHeader.getIntValue("NAXIS" + i, 0);
+ }
+
+ return axes;
+ }
+
+ public int getParameterCount() {
+ return myHeader.getIntValue("PCOUNT", 0);
+ }
+
+ public int getGroupCount() {
+ return myHeader.getIntValue("GCOUNT", 1);
+ }
+
+ public double getBScale() {
+ return myHeader.getDoubleValue("BSCALE", 1.0);
+ }
+
+ public double getBZero() {
+ return myHeader.getDoubleValue("BZERO", 0.0);
+ }
+
+ public String getBUnit() {
+ return getTrimmedString("BUNIT");
+ }
+
+ public int getBlankValue()
+ throws FitsException {
+ if (!myHeader.containsKey("BLANK")) {
+ throw new FitsException("BLANK undefined");
+ }
+ return myHeader.getIntValue("BLANK");
+ }
+
+ /**
+ * Get the FITS file creation date as a <CODE>Date</CODE> object.
+ * @return either <CODE>null</CODE> or a Date object
+ */
+ public Date getCreationDate() {
+ try {
+ return new FitsDate(myHeader.getStringValue("DATE")).toDate();
+ } catch (FitsException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the FITS file observation date as a <CODE>Date</CODE> object.
+ * @return either <CODE>null</CODE> or a Date object
+ */
+ public Date getObservationDate() {
+ try {
+ return new FitsDate(myHeader.getStringValue("DATE-OBS")).toDate();
+ } catch (FitsException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get the name of the organization which created this FITS file.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getOrigin() {
+ return getTrimmedString("ORIGIN");
+ }
+
+ /**
+ * Get the name of the telescope which was used to acquire the data in
+ * this FITS file.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getTelescope() {
+ return getTrimmedString("TELESCOP");
+ }
+
+ /**
+ * Get the name of the instrument which was used to acquire the data in
+ * this FITS file.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getInstrument() {
+ return getTrimmedString("INSTRUME");
+ }
+
+ /**
+ * Get the name of the person who acquired the data in this FITS file.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getObserver() {
+ return getTrimmedString("OBSERVER");
+ }
+
+ /**
+ * Get the name of the observed object in this FITS file.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getObject() {
+ return getTrimmedString("OBJECT");
+ }
+
+ /**
+ * Get the equinox in years for the celestial coordinate system in which
+ * positions given in either the header or data are expressed.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public double getEquinox() {
+ return myHeader.getDoubleValue("EQUINOX", -1.0);
+ }
+
+ /**
+ * Get the equinox in years for the celestial coordinate system in which
+ * positions given in either the header or data are expressed.
+ * @return either <CODE>null</CODE> or a String object
+ * @deprecated Replaced by getEquinox
+ * @see #getEquinox()
+ */
+ public double getEpoch() {
+ return myHeader.getDoubleValue("EPOCH", -1.0);
+ }
+
+ /**
+ * Return the name of the person who compiled the information in
+ * the data associated with this header.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getAuthor() {
+ return getTrimmedString("AUTHOR");
+ }
+
+ /**
+ * Return the citation of a reference where the data associated with
+ * this header are published.
+ * @return either <CODE>null</CODE> or a String object
+ */
+ public String getReference() {
+ return getTrimmedString("REFERENC");
+ }
+
+ /**
+ * Return the minimum valid value in the array.
+ * @return minimum value.
+ */
+ public double getMaximumValue() {
+ return myHeader.getDoubleValue("DATAMAX");
+ }
+
+ /**
+ * Return the minimum valid value in the array.
+ * @return minimum value.
+ */
+ public double getMinimumValue() {
+ return myHeader.getDoubleValue("DATAMIN");
+ }
+
+ /** Indicate whether HDU can be primary HDU.
+ * This method must be overriden in HDU types which can
+ * appear at the beginning of a FITS file.
+ */
+ boolean canBePrimary() {
+ return false;
+ }
+
+ /** Reset the input stream to the beginning of the HDU, i.e., the beginning of the header */
+ public boolean reset() {
+ return myHeader.reset();
+ }
+
+ /** Indicate that an HDU is the first element of a FITS file. */
+ void setPrimaryHDU(boolean newPrimary) throws FitsException {
+
+ if (newPrimary && !canBePrimary()) {
+ throw new FitsException("Invalid attempt to make HDU of type:"
+ + this.getClass().getName() + " primary.");
+ } else {
+ this.isPrimary = newPrimary;
+ }
+
+ // Some FITS readers don't like the PCOUNT and GCOUNT keywords
+ // in a primary array or they EXTEND keyword in extensions.
+
+ if (isPrimary && !myHeader.getBooleanValue("GROUPS", false)) {
+ myHeader.deleteKey("PCOUNT");
+ myHeader.deleteKey("GCOUNT");
+ }
+
+ if (isPrimary) {
+ HeaderCard card = myHeader.findCard("EXTEND");
+ if (card == null) {
+ getAxes(); // Leaves the iterator pointing to the last NAXISn card.
+ myHeader.nextCard();
+ myHeader.addValue("EXTEND", true, "ntf::basichdu:extend:1");
+ }
+ }
+
+ if (!isPrimary) {
+
+ Iterator iter = myHeader.iterator();
+
+ int pcount = myHeader.getIntValue("PCOUNT", 0);
+ int gcount = myHeader.getIntValue("GCOUNT", 1);
+ int naxis = myHeader.getIntValue("NAXIS", 0);
+ myHeader.deleteKey("EXTEND");
+ HeaderCard card;
+ HeaderCard pcard = myHeader.findCard("PCOUNT");
+ HeaderCard gcard = myHeader.findCard("GCOUNT");
+
+ myHeader.getCard(2 + naxis);
+ if (pcard == null) {
+ myHeader.addValue("PCOUNT", pcount, "ntf::basichdu:pcount:1");
+ }
+ if (gcard == null) {
+ myHeader.addValue("GCOUNT", gcount, "ntf::basichdu:gcount:1");
+ }
+ iter = myHeader.iterator();
+ }
+
+ }
+
+ /** Add information to the header */
+ public void addValue(String key, boolean val, String comment)
+ throws HeaderCardException {
+ myHeader.addValue(key, val, comment);
+ }
+
+ public void addValue(String key, int val, String comment)
+ throws HeaderCardException {
+ myHeader.addValue(key, val, comment);
+ }
+
+ public void addValue(String key, double val, String comment)
+ throws HeaderCardException {
+ myHeader.addValue(key, val, comment);
+ }
+
+ public void addValue(String key, String val, String comment)
+ throws HeaderCardException {
+ myHeader.addValue(key, val, comment);
+ }
+
+ /** Get an HDU without content */
+ public static BasicHDU getDummyHDU() {
+ try {
+ // Update suggested by Laurent Bourges
+ ImageData img = new ImageData((Object) null);
+ return FitsFactory.HDUFactory(ImageHDU.manufactureHeader(img), img);
+ } catch (FitsException e) {
+ System.err.println("Impossible exception in getDummyHDU");
+ return null;
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits;\r
+\r
+/* Copyright: Thomas McGlynn 1997-2000.\r
+ * This code may be used for any purpose, non-commercial\r
+ * or commercial so long as this copyright notice is retained\r
+ * in the source code or included in or referred to in any\r
+ * derived software.\r
+ *\r
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial\r
+ * improvements, enhancements and bug fixes.\r
+ */\r
+import java.io.*;\r
+import nom.tam.util.*;\r
+import java.lang.reflect.Array;\r
+import java.util.Vector;\r
+\r
+/** This class defines the methods for accessing FITS binary table data.\r
+ */\r
+public class BinaryTable extends Data implements TableData {\r
+\r
+ /** This is the area in which variable length column data lives.\r
+ */\r
+ FitsHeap heap;\r
+ /** The number of bytes between the end of the data and the heap */\r
+ int heapOffset;\r
+ // Added by A. Kovacs (4/1/08)\r
+ // as a way for checking whether the heap was initialized from stream...\r
+ /** Has the heap been read */\r
+ boolean heapReadFromStream = false;\r
+ /** The sizes of each column (in number of entries per row)\r
+ */\r
+ int[] sizes;\r
+ /** The dimensions of each column.\r
+ * If a column is a scalar then entry for that\r
+ * index is an array of length 0.\r
+ */\r
+ int[][] dimens;\r
+ /** Info about column */\r
+ int[] flags;\r
+ /** Flag indicating that we've given Variable length conversion warning.\r
+ * We only want to do that once per HDU.\r
+ */\r
+ private boolean warnedOnVariableConversion = false;\r
+ final static int COL_CONSTANT = 0;\r
+ final static int COL_VARYING = 1;\r
+ final static int COL_COMPLEX = 2;\r
+ final static int COL_STRING = 4;\r
+ final static int COL_BOOLEAN = 8;\r
+ final static int COL_BIT = 16;\r
+ final static int COL_LONGVARY = 32;\r
+ /** The number of rows in the table.\r
+ */\r
+ int nRow;\r
+ /** The number of columns in the table.\r
+ */\r
+ int nCol;\r
+ /** The length in bytes of each row.\r
+ */\r
+ int rowLen;\r
+ /** The base classes for the arrays in the table.\r
+ */\r
+ Class[] bases;\r
+ /** An example of the structure of a row\r
+ */\r
+ Object[] modelRow;\r
+ /** A pointer to the data in the columns. This\r
+ * variable is only used to assist in the\r
+ * construction of BinaryTable's that are defined\r
+ * to point to an array of columns. It is\r
+ * not generally filled. The ColumnTable is used\r
+ * to store the actual data of the BinaryTable.\r
+ */\r
+ Object[] columns;\r
+ /** Where the data is actually stored.\r
+ */\r
+ ColumnTable table;\r
+ /** The stream used to input the image\r
+ */\r
+ ArrayDataInput currInput;\r
+\r
+ /** Create a null binary table data segment.\r
+ */\r
+ public BinaryTable() throws FitsException {\r
+\r
+ try {\r
+ table = new ColumnTable(new Object[0], new int[0]);\r
+ } catch (TableException e) {\r
+ System.err.println("Impossible exception in BinaryTable() constructor" + e);\r
+ }\r
+\r
+ heap = new FitsHeap(0);\r
+ extendArrays(0);\r
+ nRow = 0;\r
+ nCol = 0;\r
+ rowLen = 0;\r
+ }\r
+\r
+ /** Create a binary table from given header information.\r
+ *\r
+ * @param header A header describing what the binary\r
+ * table should look like.\r
+ */\r
+ public BinaryTable(Header myHeader) throws FitsException {\r
+\r
+ long heapSizeL = myHeader.getLongValue("PCOUNT");\r
+ long heapOffsetL = myHeader.getLongValue("THEAP");\r
+ if (heapOffsetL > Integer.MAX_VALUE) {\r
+ throw new FitsException("Heap Offset > 2GB");\r
+ }\r
+ heapOffset = (int) heapOffsetL;\r
+ if (heapSizeL > Integer.MAX_VALUE) {\r
+ throw new FitsException("Heap size > 2 GB");\r
+ }\r
+ int heapSize = (int) heapSizeL;\r
+\r
+ int rwsz = myHeader.getIntValue("NAXIS1");\r
+ nRow = myHeader.getIntValue("NAXIS2");\r
+\r
+ // Subtract out the size of the regular table from\r
+ // the heap offset.\r
+\r
+ if (heapOffset > 0) {\r
+ heapOffset -= nRow * rwsz;\r
+ }\r
+\r
+ if (heapOffset < 0 || heapOffset > heapSize) {\r
+ throw new FitsException("Inconsistent THEAP and PCOUNT");\r
+ }\r
+\r
+ if (heapSize - heapOffset > Integer.MAX_VALUE) {\r
+ throw new FitsException("Unable to allocate heap > 2GB");\r
+ }\r
+\r
+ heap = new FitsHeap((heapSize - heapOffset));\r
+ nCol = myHeader.getIntValue("TFIELDS");\r
+ rowLen = 0;\r
+\r
+ extendArrays(nCol);\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ rowLen += processCol(myHeader, col);\r
+ }\r
+\r
+ HeaderCard card = myHeader.findCard("NAXIS1");\r
+ card.setValue(String.valueOf(rowLen));\r
+ myHeader.updateLine("NAXIS1", card);\r
+\r
+ }\r
+\r
+ /** Create a binary table from existing data in row order.\r
+ *\r
+ * @param data The data used to initialize the binary table.\r
+ */\r
+ public BinaryTable(Object[][] data) throws FitsException {\r
+ this(convertToColumns(data));\r
+ }\r
+\r
+ /** Create a binary table from existing data in column order.\r
+ */\r
+ public BinaryTable(Object[] o) throws FitsException {\r
+\r
+ heap = new FitsHeap(0);\r
+ modelRow = new Object[o.length];\r
+ extendArrays(o.length);\r
+\r
+\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ addColumn(o[i]);\r
+ }\r
+ }\r
+\r
+ /** Create a binary table from an existing ColumnTable */\r
+ public BinaryTable(ColumnTable tab) {\r
+\r
+ nCol = tab.getNCols();\r
+\r
+ extendArrays(nCol);\r
+\r
+ bases = tab.getBases();\r
+ sizes = tab.getSizes();\r
+\r
+ modelRow = new Object[nCol];\r
+\r
+ dimens = new int[nCol][];\r
+\r
+ // Set all flags to 0.\r
+ flags = new int[nCol];\r
+\r
+ // Set the column dimension. Note that\r
+ // we cannot distinguish an array of length 1 from a\r
+ // scalar here: we assume a scalar.\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ if (sizes[col] != 1) {\r
+ dimens[col] = new int[]{sizes[col]};\r
+ } else {\r
+ dimens[col] = new int[0];\r
+ }\r
+ }\r
+\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ modelRow[col] = ArrayFuncs.newInstance(bases[col], sizes[col]);\r
+ }\r
+\r
+ columns = null;\r
+ table = tab;\r
+\r
+ heap = new FitsHeap(0);\r
+ rowLen = 0;\r
+ for (int col = 0; col < nCol; col += 1) {\r
+ rowLen += sizes[col] * ArrayFuncs.getBaseLength(tab.getColumn(col));\r
+ }\r
+ heapOffset = 0;\r
+ nRow = tab.getNRows();\r
+ }\r
+\r
+ /** Return a row that may be used for direct i/o to the table.\r
+ */\r
+ public Object[] getModelRow() {\r
+ return modelRow;\r
+ }\r
+\r
+ /** Process one column from a FITS Header */\r
+ private int processCol(Header header, int col) throws FitsException {\r
+\r
+ String tform = header.getStringValue("TFORM" + (col + 1));\r
+ if (tform == null) {\r
+ throw new FitsException("Attempt to process column " + (col + 1) + " but no TFORMn found.");\r
+ }\r
+ tform = tform.trim();\r
+\r
+ String tdims = header.getStringValue("TDIM" + (col + 1));\r
+\r
+ if (tdims != null) {\r
+ tdims = tdims.trim();\r
+ }\r
+\r
+ char type = getTFORMType(tform);\r
+ if (type == 'P' || type == 'Q') {\r
+ flags[col] |= COL_VARYING;\r
+ if (type == 'Q') {\r
+ flags[col] |= COL_LONGVARY;\r
+ }\r
+ type = getTFORMVarType(tform);\r
+ }\r
+\r
+\r
+ int size = getTFORMLength(tform);\r
+\r
+ // Handle the special size cases.\r
+ //\r
+ // Bit arrays (8 bits fit in a byte)\r
+ if (type == 'X') {\r
+ size = (size + 7) / 8;\r
+ flags[col] |= COL_BIT;\r
+\r
+ // Variable length arrays always have a two-element pointer (offset and size)\r
+ } else if (isVarCol(col)) {\r
+ size = 2;\r
+ }\r
+\r
+ // bSize is the number of bytes in the field.\r
+ int bSize = size;\r
+\r
+ int[] dims = null;\r
+\r
+ // Cannot really handle arbitrary arrays of bits.\r
+ if (tdims != null && type != 'X' && !isVarCol(col)) {\r
+ dims = getTDims(tdims);\r
+ }\r
+\r
+ if (dims == null) {\r
+ if (size == 1) {\r
+ dims = new int[0]; // Marks this as a scalar column\r
+ } else {\r
+ dims = new int[]{size};\r
+ }\r
+ }\r
+\r
+ if (type == 'C' || type == 'M') {\r
+ flags[col] |= COL_COMPLEX;\r
+ }\r
+\r
+ Class colBase = null;\r
+\r
+ switch (type) {\r
+ case 'A':\r
+ colBase = byte.class;\r
+ flags[col] |= COL_STRING;\r
+ bases[col] = String.class;\r
+ break;\r
+\r
+ case 'L':\r
+ colBase = byte.class;\r
+ bases[col] = boolean.class;\r
+ flags[col] |= COL_BOOLEAN;\r
+ break;\r
+ case 'X':\r
+ case 'B':\r
+ colBase = byte.class;\r
+ bases[col] = byte.class;\r
+ break;\r
+\r
+ case 'I':\r
+ colBase = short.class;\r
+ bases[col] = short.class;\r
+ bSize *= 2;\r
+ break;\r
+\r
+ case 'J':\r
+ colBase = int.class;\r
+ bases[col] = int.class;\r
+ bSize *= 4;\r
+ break;\r
+\r
+ case 'K':\r
+ colBase = long.class;\r
+ bases[col] = long.class;\r
+ bSize *= 8;\r
+ break;\r
+\r
+ case 'E':\r
+ case 'C':\r
+ colBase = float.class;\r
+ bases[col] = float.class;\r
+ bSize *= 4;\r
+ break;\r
+\r
+ case 'D':\r
+ case 'M':\r
+ colBase = double.class;\r
+ bases[col] = double.class;\r
+ bSize *= 8;\r
+ break;\r
+\r
+ default:\r
+ throw new FitsException("Invalid type in column:" + col);\r
+ }\r
+\r
+ if (isVarCol(col)) {\r
+\r
+ dims = new int[]{nRow, 2};\r
+ colBase = int.class;\r
+ bSize = 8;\r
+\r
+ if (isLongVary(col)) {\r
+ colBase = long.class;\r
+ bSize = 16;\r
+ }\r
+ }\r
+\r
+ if (!isVarCol(col) && isComplex(col)) {\r
+\r
+ int[] xdims = new int[dims.length + 1];\r
+ System.arraycopy(dims, 0, xdims, 0, dims.length);\r
+ xdims[dims.length] = 2;\r
+ dims = xdims;\r
+ bSize *= 2;\r
+ size *= 2;\r
+ }\r
+\r
+ modelRow[col] = ArrayFuncs.newInstance(colBase, dims);\r
+ dimens[col] = dims;\r
+ sizes[col] = size;\r
+\r
+ return bSize;\r
+ }\r
+\r
+ /** Get the type in the TFORM field */\r
+ char getTFORMType(String tform) {\r
+\r
+ for (int i = 0; i < tform.length(); i += 1) {\r
+ if (!Character.isDigit(tform.charAt(i))) {\r
+ return tform.charAt(i);\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /** Get the type in a varying length column TFORM */\r
+ char getTFORMVarType(String tform) {\r
+\r
+ int ind = tform.indexOf("P");\r
+ if (ind < 0) {\r
+ ind = tform.indexOf("Q");\r
+ }\r
+\r
+ if (tform.length() > ind + 1) {\r
+ return tform.charAt(ind + 1);\r
+ } else {\r
+ return 0;\r
+ }\r
+ }\r
+\r
+ /** Get the explicit or implied length of the TFORM field */\r
+ int getTFORMLength(String tform) {\r
+\r
+ tform = tform.trim();\r
+\r
+ if (Character.isDigit(tform.charAt(0))) {\r
+ return initialNumber(tform);\r
+\r
+ } else {\r
+ return 1;\r
+ }\r
+ }\r
+\r
+ /** Get an unsigned number at the beginning of a string */\r
+ private int initialNumber(String tform) {\r
+\r
+ int i;\r
+ for (i = 0; i < tform.length(); i += 1) {\r
+\r
+ if (!Character.isDigit(tform.charAt(i))) {\r
+ break;\r
+ }\r
+\r
+ }\r
+\r
+ return Integer.parseInt(tform.substring(0, i));\r
+ }\r
+\r
+ /** Parse the TDIMS value.\r
+ *\r
+ * If the TDIMS value cannot be deciphered a one-d\r
+ * array with the size given in arrsiz is returned.\r
+ *\r
+ * @param tdims The value of the TDIMSn card.\r
+ * @param arraySize The size field found on the TFORMn card.\r
+ * @return An int array of the desired dimensions.\r
+ * Note that the order of the tdims is the inverse\r
+ * of the order in the TDIMS key.\r
+ */\r
+ public static int[] getTDims(String tdims) {\r
+\r
+ // The TDIMs value should be of the form: "(iiii,jjjj,kkk,...)"\r
+\r
+ int[] dims = null;\r
+\r
+ int first = tdims.indexOf('(');\r
+ int last = tdims.lastIndexOf(')');\r
+ if (first >= 0 && last > first) {\r
+\r
+ tdims = tdims.substring(first + 1, last - first);\r
+\r
+ java.util.StringTokenizer st = new java.util.StringTokenizer(tdims, ",");\r
+ int dim = st.countTokens();\r
+ if (dim > 0) {\r
+\r
+ dims = new int[dim];\r
+\r
+ for (int i = dim - 1; i >= 0; i -= 1) {\r
+ dims[i] = Integer.parseInt(st.nextToken().trim());\r
+ }\r
+ }\r
+ }\r
+ return dims;\r
+ }\r
+\r
+ /** Convert a column from float/double to float complex/double complex.\r
+ * This is only possible for certain columns. The return status\r
+ * indicates if the conversion is possible.\r
+ * @param index The 0-based index of the column to be reset.\r
+ * @return Whether the conversion is possible.\r
+ */\r
+ boolean setComplexColumn(int index) throws FitsException {\r
+\r
+ // Currently there is almost no change required to the BinaryTable\r
+ // object itself when we convert an eligible column to complex, since the internal\r
+ // representation of the data is unchanged. We just need\r
+ // to set the flag that the column is complex.\r
+\r
+ // Check that the index is valid,\r
+ // the data type is float or double\r
+ // the most rapidly changing index in the array has dimension 2.\r
+ if (index >= 0 && index < bases.length\r
+ && (bases[index] == float.class || bases[index] == double.class)\r
+ && dimens[index][dimens[index].length - 1] == 2) {\r
+ // By coincidence a variable length column will also have\r
+ // a last index of 2, so we'll get here. Otherwise\r
+ // we'd need to test that in parallel rather than in series.\r
+\r
+ // If this is a variable length column, then\r
+ // we need to check the length of each row.\r
+ if ((flags[index] & COL_VARYING) != 0) {\r
+\r
+ // We need to make sure that for every row, there are\r
+ // an even number of elements so that we can\r
+ // convert to an integral number of complex numbers.\r
+ Object col = getFlattenedColumn(index);\r
+ if (col instanceof int[]) {\r
+ int[] ptrs = (int[]) col;\r
+ for (int i = 1; i < ptrs.length; i += 2) {\r
+ if (ptrs[i] % 2 != 0) {\r
+ return false;\r
+ }\r
+ }\r
+ } else {\r
+ long[] ptrs = (long[]) col;\r
+ for (int i = 1; i < ptrs.length; i += 1) {\r
+ if (ptrs[i] % 2 != 0) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ // Set the column to complex\r
+ flags[index] |= COL_COMPLEX;\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /** Update a FITS header to reflect the current state of the data.\r
+ */\r
+ public void fillHeader(Header h) throws FitsException {\r
+\r
+ try {\r
+ h.setXtension("BINTABLE");\r
+ h.setBitpix(8);\r
+ h.setNaxes(2);\r
+ h.setNaxis(1, rowLen);\r
+ h.setNaxis(2, nRow);\r
+ h.addValue("PCOUNT", heap.size(), "ntf::binarytable:pcount:1");\r
+ h.addValue("GCOUNT", 1, "ntf::binarytable:gcount:1");\r
+ Cursor iter = h.iterator();\r
+ iter.setKey("GCOUNT");\r
+ iter.next();\r
+ iter.add("TFIELDS", new HeaderCard("TFIELDS", modelRow.length, "ntf::binarytable:tfields:1"));\r
+\r
+ for (int i = 0; i < modelRow.length; i += 1) {\r
+ if (i > 0) {\r
+ h.positionAfterIndex("TFORM", i);\r
+ }\r
+ fillForColumn(h, i, iter);\r
+ }\r
+ } catch (HeaderCardException e) {\r
+ System.err.println("Error updating BinaryTableHeader:" + e);\r
+ }\r
+ }\r
+\r
+ /** Updata the header to reflect information about a given column.\r
+ * This routine tries to ensure that the Header is organized by column.\r
+ */\r
+ void pointToColumn(int col, Header hdr) throws FitsException {\r
+\r
+ Cursor iter = hdr.iterator();\r
+ if (col > 0) {\r
+ hdr.positionAfterIndex("TFORM", col);\r
+ }\r
+ fillForColumn(hdr, col, iter);\r
+ }\r
+\r
+ /** Update the header to reflect the details of a given column */\r
+ void fillForColumn(Header h, int col, Cursor iter) throws FitsException {\r
+\r
+ String tform;\r
+\r
+ if (isVarCol(col)) {\r
+ if (isLongVary(col)) {\r
+ tform = "1Q";\r
+ } else {\r
+ tform = "1P";\r
+ }\r
+\r
+ } else {\r
+ tform = "" + sizes[col];\r
+ }\r
+\r
+ if (bases[col] == int.class) {\r
+ tform += "J";\r
+ } else if (bases[col] == short.class || bases[col] == char.class) {\r
+ tform += "I";\r
+ } else if (bases[col] == byte.class) {\r
+ tform += "B";\r
+ } else if (bases[col] == float.class) {\r
+ if (isComplex(col)) {\r
+ tform += "C";\r
+ } else {\r
+ tform += "E";\r
+ }\r
+ } else if (bases[col] == double.class) {\r
+ if (isComplex(col)) {\r
+ tform += "M";\r
+ } else {\r
+ tform += "D";\r
+ }\r
+ } else if (bases[col] == long.class) {\r
+ tform += "K";\r
+ } else if (bases[col] == boolean.class) {\r
+ tform += "L";\r
+ } else if (bases[col] == String.class) {\r
+ tform += "A";\r
+ } else {\r
+ throw new FitsException("Invalid column data class:" + bases[col]);\r
+ }\r
+\r
+\r
+ String key = "TFORM" + (col + 1);\r
+ iter.add(key, new HeaderCard(key, tform, "ntf::binarytable:tformN:1"));\r
+\r
+ if (dimens[col].length > 0 && !isVarCol(col)) {\r
+\r
+ StringBuffer tdim = new StringBuffer();\r
+ char comma = '(';\r
+ for (int i = dimens[col].length - 1; i >= 0; i -= 1) {\r
+ tdim.append(comma);\r
+ tdim.append(dimens[col][i]);\r
+ comma = ',';\r
+ }\r
+ tdim.append(')');\r
+ key = "TDIM" + (col + 1);\r
+ iter.add(key, new HeaderCard(key, new String(tdim), "ntf::headercard:tdimN:1"));\r
+ }\r
+ }\r
+\r
+ /** Create a column table given the number of\r
+ * rows and a model row. This is used when\r
+ * we defer instantiation of the ColumnTable until\r
+ * the user requests data from the table.\r
+ */\r
+ private ColumnTable createTable() throws FitsException {\r
+\r
+ int nfields = modelRow.length;\r
+\r
+ Object[] arrCol = new Object[nfields];\r
+\r
+ for (int i = 0; i < nfields; i += 1) {\r
+ arrCol[i] = ArrayFuncs.newInstance(\r
+ ArrayFuncs.getBaseClass(modelRow[i]),\r
+ sizes[i] * nRow);\r
+ }\r
+\r
+ ColumnTable table;\r
+\r
+ try {\r
+ table = new ColumnTable(arrCol, sizes);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Unable to create table:" + e);\r
+ }\r
+\r
+ return table;\r
+ }\r
+\r
+ /** Convert a two-d table to a table of columns. Handle\r
+ * String specially. Every other element of data should be\r
+ * a primitive array of some dimensionality.\r
+ */\r
+ private static Object[] convertToColumns(Object[][] data) {\r
+\r
+ Object[] row = data[0];\r
+ int nrow = data.length;\r
+\r
+ Object[] results = new Object[row.length];\r
+\r
+ for (int col = 0; col < row.length; col += 1) {\r
+\r
+ if (row[col] instanceof String) {\r
+\r
+ String[] sa = new String[nrow];\r
+\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ sa[irow] = (String) data[irow][col];\r
+ }\r
+\r
+ results[col] = sa;\r
+\r
+ } else {\r
+\r
+ Class base = ArrayFuncs.getBaseClass(row[col]);\r
+ int[] dims = ArrayFuncs.getDimensions(row[col]);\r
+\r
+ if (dims.length > 1 || dims[0] > 1) {\r
+ int[] xdims = new int[dims.length + 1];\r
+ xdims[0] = nrow;\r
+\r
+ Object[] arr = (Object[]) ArrayFuncs.newInstance(base, xdims);\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ arr[irow] = data[irow][col];\r
+ }\r
+ results[col] = arr;\r
+ } else {\r
+ Object arr = ArrayFuncs.newInstance(base, nrow);\r
+ for (int irow = 0; irow < nrow; irow += 1) {\r
+ System.arraycopy(data[irow][col], 0, arr, irow, 1);\r
+ }\r
+ results[col] = arr;\r
+ }\r
+\r
+ }\r
+ }\r
+ return results;\r
+ }\r
+\r
+ /** Get a given row\r
+ * @param row The index of the row to be returned.\r
+ * @return A row of data.\r
+ */\r
+ public Object[] getRow(int row) throws FitsException {\r
+\r
+ if (!validRow(row)) {\r
+ throw new FitsException("Invalid row");\r
+ }\r
+\r
+ Object[] res;\r
+ if (table != null) {\r
+ res = getMemoryRow(row);\r
+ } else {\r
+ res = getFileRow(row);\r
+ }\r
+ return res;\r
+ }\r
+\r
+ /** Get a row from memory.\r
+ */\r
+ private Object[] getMemoryRow(int row) throws FitsException {\r
+\r
+ Object[] data = new Object[modelRow.length];\r
+ for (int col = 0; col < modelRow.length; col += 1) {\r
+ Object o = table.getElement(row, col);\r
+ o = columnToArray(col, o, 1);\r
+ data[col] = encurl(o, col, 1);\r
+ if (data[col] instanceof Object[]) {\r
+ data[col] = ((Object[]) data[col])[0];\r
+ }\r
+ }\r
+\r
+ return data;\r
+\r
+ }\r
+\r
+ /** Get a row from the file.\r
+ */\r
+ private Object[] getFileRow(int row) throws FitsException {\r
+\r
+ /** Read the row from memory */\r
+ Object[] data = new Object[nCol];\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ data[col] = ArrayFuncs.newInstance(\r
+ ArrayFuncs.getBaseClass(modelRow[col]),\r
+ sizes[col]);\r
+ }\r
+\r
+ try {\r
+ FitsUtil.reposition(currInput, fileOffset + row * rowLen);\r
+ currInput.readLArray(data);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error in deferred row read");\r
+ }\r
+\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ data[col] = columnToArray(col, data[col], 1);\r
+ data[col] = encurl(data[col], col, 1);\r
+ if (data[col] instanceof Object[]) {\r
+ data[col] = ((Object[]) data[col])[0];\r
+ }\r
+ }\r
+ return data;\r
+ }\r
+\r
+ /** Replace a row in the table.\r
+ * @param row The index of the row to be replaced.\r
+ * @param data The new values for the row.\r
+ * @exception FitsException Thrown if the new row cannot\r
+ * match the existing data.\r
+ */\r
+ public void setRow(int row, Object data[]) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (data.length != getNCols()) {\r
+ throw new FitsException("Updated row size does not agree with table");\r
+ }\r
+\r
+ Object[] ydata = new Object[data.length];\r
+\r
+ for (int col = 0; col < data.length; col += 1) {\r
+ Object o = ArrayFuncs.flatten(data[col]);\r
+ ydata[col] = arrayToColumn(col, o);\r
+ }\r
+\r
+ try {\r
+ table.setRow(row, ydata);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error modifying table: " + e);\r
+ }\r
+ }\r
+\r
+ /** Replace a column in the table.\r
+ * @param col The index of the column to be replaced.\r
+ * @param xcol The new data for the column\r
+ * @exception FitsException Thrown if the data does not match\r
+ * the current column description.\r
+ */\r
+ public void setColumn(int col, Object xcol) throws FitsException {\r
+\r
+ xcol = arrayToColumn(col, xcol);\r
+ xcol = ArrayFuncs.flatten(xcol);\r
+ setFlattenedColumn(col, xcol);\r
+ }\r
+\r
+ /** Set a column with the data aleady flattened.\r
+ *\r
+ * @param col The index of the column to be replaced.\r
+ * @param data The new data array. This should be a one-d\r
+ * primitive array.\r
+ * @exception FitsException Thrown if the type of length of\r
+ * the replacement data differs from the\r
+ * original.\r
+ */\r
+ public void setFlattenedColumn(int col, Object data) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ Object oldCol = table.getColumn(col);\r
+ if (data.getClass() != oldCol.getClass()\r
+ || Array.getLength(data) != Array.getLength(oldCol)) {\r
+ throw new FitsException("Replacement column mismatch at column:" + col);\r
+ }\r
+ try {\r
+ table.setColumn(col, data);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Unable to set column:" + col + " error:" + e);\r
+ }\r
+ }\r
+\r
+ /** Get a given column\r
+ * @param col The index of the column.\r
+ */\r
+ public Object getColumn(int col) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ Object res = getFlattenedColumn(col);\r
+ res = encurl(res, col, nRow);\r
+ return res;\r
+ }\r
+\r
+ private Object encurl(Object res, int col, int rows) {\r
+\r
+ if (bases[col] != String.class) {\r
+\r
+ if (!isVarCol(col) && (dimens[col].length > 0)) {\r
+\r
+ int[] dims = new int[dimens[col].length + 1];\r
+ System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length);\r
+ dims[0] = rows;\r
+ res = ArrayFuncs.curl(res, dims);\r
+ }\r
+\r
+ } else {\r
+\r
+ // Handle Strings. Remember the last element\r
+ // in dimens is the length of the Strings and\r
+ // we already used that when we converted from\r
+ // byte arrays to strings. So we need to ignore\r
+ // the last element of dimens, and add the row count\r
+ // at the beginning to curl.\r
+\r
+ if (dimens[col].length > 2) {\r
+ int[] dims = new int[dimens[col].length];\r
+\r
+ System.arraycopy(dimens[col], 0, dims, 1, dimens[col].length - 1);\r
+ dims[0] = rows;\r
+\r
+ res = ArrayFuncs.curl(res, dims);\r
+ }\r
+ }\r
+\r
+ return res;\r
+\r
+ }\r
+\r
+ /** Get a column in flattened format.\r
+ * For large tables getting a column in standard format can be\r
+ * inefficient because a separate object is needed for\r
+ * each row. Leaving the data in flattened format means\r
+ * that only a single object is created.\r
+ * @param col\r
+ */\r
+ public Object getFlattenedColumn(int col) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (!validColumn(col)) {\r
+ throw new FitsException("Invalid column");\r
+ }\r
+\r
+ Object res = table.getColumn(col);\r
+ return columnToArray(col, res, nRow);\r
+ }\r
+\r
+ /** Get a particular element from the table.\r
+ * @param i The row of the element.\r
+ * @param j The column of the element.\r
+ */\r
+ public Object getElement(int i, int j) throws FitsException {\r
+\r
+ if (!validRow(i) || !validColumn(j)) {\r
+ throw new FitsException("No such element");\r
+ }\r
+\r
+ Object ele;\r
+ if (isVarCol(j) && table == null) {\r
+ // Have to read in entire data set.\r
+ getData();\r
+ }\r
+\r
+ if (table == null) {\r
+ // This is really inefficient.\r
+ // Need to either save the row, or just read the one element.\r
+ Object[] row = getRow(i);\r
+ ele = row[j];\r
+\r
+ } else {\r
+\r
+ ele = table.getElement(i, j);\r
+ ele = columnToArray(j, ele, 1);\r
+\r
+ ele = encurl(ele, j, 1);\r
+ if (ele instanceof Object[]) {\r
+ ele = ((Object[]) ele)[0];\r
+ }\r
+ }\r
+\r
+ return ele;\r
+ }\r
+\r
+ /** Get a particular element from the table but\r
+ * do no processing of this element (e.g.,\r
+ * dimension conversion or extraction of\r
+ * variable length array elements/)\r
+ * @param i The row of the element.\r
+ * @param j The column of the element.\r
+ */\r
+ public Object getRawElement(int i, int j) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+ return table.getElement(i, j);\r
+ }\r
+\r
+ /** Add a row at the end of the table. Given the way the\r
+ * table is structured this will normally not be very efficient.\r
+ * @param o An array of elements to be added. Each element of o\r
+ * should be an array of primitives or a String.\r
+ */\r
+ public int addRow(Object[] o) throws FitsException {\r
+\r
+ if (table == null) {\r
+ getData();\r
+ }\r
+\r
+ if (nCol == 0 && nRow == 0) {\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ addColumn(o);\r
+ }\r
+ } else {\r
+\r
+ Object[] flatRow = new Object[getNCols()];\r
+ for (int i = 0; i < getNCols(); i += 1) {\r
+ Object x = ArrayFuncs.flatten(o[i]);\r
+ flatRow[i] = arrayToColumn(i, x);\r
+ }\r
+ try {\r
+ table.addRow(flatRow);\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error add row to table");\r
+ }\r
+\r
+ nRow += 1;\r
+ }\r
+\r
+ return nRow;\r
+ }\r
+\r
+ /** Delete rows from a table.\r
+ * @param row The 0-indexed start of the rows to be deleted.\r
+ * @param len The number of rows to be deleted.\r
+ */\r
+ public void deleteRows(int row, int len) throws FitsException {\r
+ try {\r
+ getData();\r
+ table.deleteRows(row, len);\r
+ nRow -= len;\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error deleting row block " + row + " to " + (row + len - 1) + " from table");\r
+ }\r
+ }\r
+\r
+ /** Add a column to the end of a table.\r
+ * @param o An array of identically structured objects with the\r
+ * same number of elements as other columns in the table.\r
+ */\r
+ public int addColumn(Object o) throws FitsException {\r
+\r
+ int primeDim = Array.getLength(o);\r
+\r
+ extendArrays(nCol + 1);\r
+ Class base = ArrayFuncs.getBaseClass(o);\r
+\r
+ // A varying length column is a two-d primitive\r
+ // array where the second index is not constant.\r
+ // We do not support Q types here, since Java\r
+ // can't handle the long indices anyway...\r
+ // This will probably change in some version of Java.\r
+\r
+ if (isVarying(o)) {\r
+ flags[nCol] |= COL_VARYING;\r
+ dimens[nCol] = new int[]{2};\r
+ }\r
+\r
+ if (isVaryingComp(o)) {\r
+ flags[nCol] |= COL_VARYING | COL_COMPLEX;\r
+ dimens[nCol] = new int[]{2};\r
+ }\r
+\r
+ // Flatten out everything but 1-D arrays and the\r
+ // two-D arrays associated with variable length columns.\r
+\r
+ if (!isVarCol(nCol)) {\r
+\r
+ int[] allDim = ArrayFuncs.getDimensions(o);\r
+\r
+ // Add a dimension for the length of Strings.\r
+ if (base == String.class) {\r
+ int[] xdim = new int[allDim.length + 1];\r
+ System.arraycopy(allDim, 0, xdim, 0, allDim.length);\r
+ xdim[allDim.length] = -1;\r
+ allDim = xdim;\r
+ }\r
+\r
+ if (allDim.length == 1) {\r
+ dimens[nCol] = new int[0];\r
+\r
+ } else {\r
+\r
+ dimens[nCol] = new int[allDim.length - 1];\r
+ System.arraycopy(allDim, 1, dimens[nCol], 0, allDim.length - 1);\r
+ o = ArrayFuncs.flatten(o);\r
+ }\r
+ }\r
+\r
+ addFlattenedColumn(o, dimens[nCol]);\r
+ if (nRow == 0 && nCol == 0) {\r
+ nRow = primeDim;\r
+ }\r
+ nCol += 1;\r
+ return getNCols();\r
+\r
+ }\r
+\r
+ private boolean isVaryingComp(Object o) {\r
+ String classname = o.getClass().getName();\r
+ if (classname.equals("[[[F")) {\r
+ return checkCompVary((float[][][]) o);\r
+ } else if (classname.equals("[[[D")) {\r
+ return checkDCompVary((double[][][]) o);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /** Is this a variable length column?\r
+ * It is if it's a two-d primitive array and\r
+ * the second dimension is not constant.\r
+ * It may also be a 3-d array of type float or double\r
+ * where the last index is always 2 (when the second index\r
+ * is non-zero). In this case it can be\r
+ * a complex varying column.\r
+ */\r
+ private boolean isVarying(Object o) {\r
+\r
+ if (o == null) {\r
+ return false;\r
+ }\r
+ String classname = o.getClass().getName();\r
+\r
+ if (classname.length() != 3\r
+ || classname.charAt(0) != '['\r
+ || classname.charAt(1) != '[') {\r
+ return false;\r
+ }\r
+\r
+ Object[] ox = (Object[]) o;\r
+ if (ox.length < 2) {\r
+ return false;\r
+ }\r
+\r
+ int flen = Array.getLength(ox[0]);\r
+ for (int i = 1; i < ox.length; i += 1) {\r
+ if (Array.getLength(ox[i]) != flen) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ // Check if this is consistent with a varying\r
+ // complex row. That requires\r
+ // The second index varies.\r
+ // The third index is 2 whenever the second\r
+ // index is non-zero.\r
+ // This function will fail if nulls are encountered.\r
+ private boolean checkCompVary(float[][][] o) {\r
+\r
+ boolean varying = false;\r
+ int len0 = o[0].length;\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ if (o[i].length != len0) {\r
+ varying = true;\r
+ }\r
+ if (o[i].length > 0) {\r
+ for (int j = 0; j < o[i].length; j += 1) {\r
+ if (o[i][j].length != 2) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return varying;\r
+ }\r
+\r
+ private boolean checkDCompVary(double[][][] o) {\r
+ boolean varying = false;\r
+ int len0 = o[0].length;\r
+ for (int i = 0; i < o.length; i += 1) {\r
+ if (o[i].length != len0) {\r
+ varying = true;\r
+ }\r
+ if (o[i].length > 0) {\r
+ for (int j = 0; j < o[i].length; j += 1) {\r
+ if (o[i][j].length != 2) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return varying;\r
+ }\r
+\r
+ /** Add a column where the data is already flattened.\r
+ * @param o The new column data. This should be a one-dimensional\r
+ * primitive array.\r
+ * @param dims The dimensions of one row of the column.\r
+ */\r
+ public int addFlattenedColumn(Object o, int[] dims) throws FitsException {\r
+\r
+ extendArrays(nCol + 1);\r
+\r
+ bases[nCol] = ArrayFuncs.getBaseClass(o);\r
+\r
+ if (bases[nCol] == boolean.class) {\r
+ flags[nCol] |= COL_BOOLEAN;\r
+ } else if (bases[nCol] == String.class) {\r
+ flags[nCol] |= COL_STRING;\r
+ }\r
+\r
+ // Convert to column first in case\r
+ // this is a String or variable length array.\r
+\r
+ o = arrayToColumn(nCol, o);\r
+\r
+ int size = 1;\r
+\r
+ for (int dim = 0; dim < dims.length; dim += 1) {\r
+ size *= dims[dim];\r
+ }\r
+ sizes[nCol] = size;\r
+\r
+ if (size != 0) {\r
+ int xRow = Array.getLength(o) / size;\r
+ if (xRow > 0 && nCol != 0 && xRow != nRow) {\r
+ throw new FitsException("Added column does not have correct row count");\r
+ }\r
+ }\r
+\r
+ if (!isVarCol(nCol)) {\r
+ modelRow[nCol] = ArrayFuncs.newInstance(ArrayFuncs.getBaseClass(o), dims);\r
+ rowLen += size * ArrayFuncs.getBaseLength(o);\r
+ } else {\r
+ if (isLongVary(nCol)) {\r
+ modelRow[nCol] = new long[2];\r
+ rowLen += 16;\r
+ } else {\r
+ modelRow[nCol] = new int[2];\r
+ rowLen += 8;\r
+ }\r
+ }\r
+\r
+ // Only add to table if table already exists or if we\r
+ // are filling up the last element in columns.\r
+ // This way if we allocate a bunch of columns at the beginning\r
+ // we only create the column table after we have all the columns\r
+ // ready.\r
+\r
+ columns[nCol] = o;\r
+\r
+ try {\r
+ if (table != null) {\r
+ table.addColumn(o, sizes[nCol]);\r
+ } else if (nCol == columns.length - 1) {\r
+ table = new ColumnTable(columns, sizes);\r
+ }\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error in ColumnTable:" + e);\r
+ }\r
+ return nCol;\r
+ }\r
+\r
+ /** Get the number of rows in the table\r
+ */\r
+ public int getNRows() {\r
+ return nRow;\r
+ }\r
+\r
+ /** Get the number of columns in the table.\r
+ */\r
+ public int getNCols() {\r
+ return nCol;\r
+ }\r
+\r
+ /** Check to see if this is a valid row.\r
+ * @param i The Java index (first=0) of the row to check.\r
+ */\r
+ protected boolean validRow(int i) {\r
+\r
+ if (getNRows() > 0 && i >= 0 && i < getNRows()) {\r
+ return true;\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /** Check if the column number is valid.\r
+ *\r
+ * @param j The Java index (first=0) of the column to check.\r
+ */\r
+ protected boolean validColumn(int j) {\r
+ return (j >= 0 && j < getNCols());\r
+ }\r
+\r
+ /** Replace a single element within the table.\r
+ *\r
+ * @param i The row of the data.\r
+ * @param j The column of the data.\r
+ * @param o The replacement data.\r
+ */\r
+ public void setElement(int i, int j, Object o) throws FitsException {\r
+\r
+ getData();\r
+\r
+ try {\r
+ if (isVarCol(j)) {\r
+\r
+ int size = Array.getLength(o);\r
+ // The offset for the row is the offset to the heap plus the offset within the heap.\r
+ int offset = (int) heap.getSize();\r
+ heap.putData(o);\r
+ if (isLongVary(j)) {\r
+ table.setElement(i, j, new long[]{size, offset});\r
+ } else {\r
+ table.setElement(i, j, new int[]{size, offset});\r
+ }\r
+\r
+ } else {\r
+ table.setElement(i, j, ArrayFuncs.flatten(o));\r
+ }\r
+ } catch (TableException e) {\r
+ throw new FitsException("Error modifying table:" + e);\r
+ }\r
+ }\r
+\r
+ /** Read the data -- or defer reading on random access\r
+ */\r
+ public void read(ArrayDataInput i) throws FitsException {\r
+\r
+ setFileOffset(i);\r
+ currInput = i;\r
+\r
+ if (i instanceof RandomAccess) {\r
+\r
+ try {\r
+ i.skipBytes(getTrueSize());\r
+ } catch (IOException e) {\r
+ throw new FitsException("Unable to skip binary table HDU:" + e);\r
+ }\r
+ try {\r
+ i.skipBytes(FitsUtil.padding(getTrueSize()));\r
+ } catch (EOFException e) {\r
+ throw new PaddingException("Missing padding after binary table:" + e, this);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error skipping padding after binary table:" + e);\r
+ }\r
+\r
+ } else {\r
+\r
+ /** Read the data associated with the HDU including the hash area if present.\r
+ * @param i The input stream\r
+ */\r
+ if (table == null) {\r
+ table = createTable();\r
+ }\r
+\r
+ readTrueData(i);\r
+ }\r
+ }\r
+\r
+ /** Read table, heap and padding */\r
+ protected void readTrueData(ArrayDataInput i) throws FitsException {\r
+ try {\r
+ table.read(i);\r
+ i.skipBytes(heapOffset);\r
+ heap.read(i);\r
+ heapReadFromStream = true;\r
+\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error reading binary table data:" + e);\r
+ }\r
+ try {\r
+ i.skipBytes(FitsUtil.padding(getTrueSize()));\r
+ } catch (EOFException e) {\r
+ throw new PaddingException("Error skipping padding after binary table", this);\r
+ } catch (IOException e) {\r
+ throw new FitsException("Error reading binary table data padding:" + e);\r
+ }\r
+ }\r
+\r
+ /** Read the heap which contains the data for variable length\r
+ * arrays.\r
+ * A. Kovacs (4/1/08) Separated heap reading, s.t. the heap can\r
+ * be properly initialized even if in deferred read mode.\r
+ * columnToArray() checks and initializes the heap as necessary.\r
+ */\r
+ protected void readHeap(ArrayDataInput input) throws FitsException {\r
+ FitsUtil.reposition(input, fileOffset + nRow * rowLen + heapOffset);\r
+ heap.read(input);\r
+ heapReadFromStream = true;\r
+ }\r
+\r
+ /** Get the size of the data in the HDU sans padding.\r
+ */\r
+ public long getTrueSize() {\r
+ long len = ((long) nRow) * rowLen;\r
+ if (heap.size() > 0) {\r
+ len += heap.size() + heapOffset;\r
+ }\r
+ return len;\r
+ }\r
+\r
+ /** Write the table, heap and padding */\r
+ public void write(ArrayDataOutput os) throws FitsException {\r
+\r
+ getData();\r
+ int len;\r
+\r
+ try {\r
+\r
+ // First write the table.\r
+ len = table.write(os);\r
+ if (heapOffset > 0) {\r
+ int off = heapOffset;\r
+ // Minimize memory usage. This also accommodates\r
+ // the possibility that heapOffset > 2GB.\r
+ // Previous code might have allocated up to 2GB\r
+ // array. [In practice this is always going\r
+ // to be really small though...]\r
+ int arrSiz = 4000000;\r
+ while (off > 0) {\r
+ if (arrSiz > off) {\r
+ arrSiz = (int) off;\r
+ }\r
+ os.write(new byte[arrSiz]);\r
+ off -= arrSiz;\r
+ }\r
+ }\r
+\r
+ // Now check if we need to write the heap\r
+ if (heap.size() > 0) {\r
+ heap.write(os);\r
+ }\r
+\r
+ FitsUtil.pad(os, getTrueSize());\r
+\r
+ } catch (IOException e) {\r
+ throw new FitsException("Unable to write table:" + e);\r
+ }\r
+ }\r
+\r
+ public Object getData() throws FitsException {\r
+\r
+\r
+ if (table == null) {\r
+\r
+ if (currInput == null) {\r
+ throw new FitsException("Cannot find input for deferred read");\r
+ }\r
+\r
+ table = createTable();\r
+\r
+ long currentOffset = FitsUtil.findOffset(currInput);\r
+ FitsUtil.reposition(currInput, fileOffset);\r
+ readTrueData(input);\r
+ FitsUtil.reposition(currInput, currentOffset);\r
+ }\r
+\r
+ return table;\r
+ }\r
+\r
+ public int[][] getDimens() {\r
+ return dimens;\r
+ }\r
+\r
+ public Class[] getBases() {\r
+ return table.getBases();\r
+ }\r
+\r
+ public char[] getTypes() {\r
+ if (table == null) {\r
+ try {\r
+ getData();\r
+ } catch (FitsException e) {\r
+ }\r
+ }\r
+ return table.getTypes();\r
+ }\r
+\r
+ public Object[] getFlatColumns() {\r
+ if (table == null) {\r
+ try {\r
+ getData();\r
+ } catch (FitsException e) {\r
+ }\r
+ }\r
+ return table.getColumns();\r
+ }\r
+\r
+ public int[] getSizes() {\r
+ return sizes;\r
+ }\r
+\r
+ /** Convert the external representation to the\r
+ * BinaryTable representation. Transformation include\r
+ * boolean -> T/F, Strings -> byte arrays,\r
+ * variable length arrays -> pointers (after writing data\r
+ * to heap).\r
+ */\r
+ private Object arrayToColumn(int col, Object o) throws FitsException {\r
+\r
+ if (flags[col] == 0) {\r
+ return o;\r
+ }\r
+\r
+ if (!isVarCol(col)) {\r
+\r
+ if (isString(col)) {\r
+\r
+ // Convert strings to array of bytes.\r
+ int[] dims = dimens[col];\r
+\r
+ // Set the length of the string if we are just adding the column.\r
+ if (dims[dims.length - 1] < 0) {\r
+ dims[dims.length - 1] = FitsUtil.maxLength((String[]) o);\r
+ }\r
+ if (o instanceof String) {\r
+ o = new String[]{(String) o};\r
+ }\r
+ o = FitsUtil.stringsToByteArray((String[]) o, dims[dims.length - 1]);\r
+\r
+\r
+ } else if (isBoolean(col)) {\r
+\r
+ // Convert true/false to 'T'/'F'\r
+ o = FitsUtil.booleanToByte((boolean[]) o);\r
+ }\r
+\r
+ } else {\r
+\r
+ if (isBoolean(col)) {\r
+\r
+ // Handle addRow/addElement\r
+ if (o instanceof boolean[]) {\r
+ o = new boolean[][]{(boolean[]) o};\r
+ }\r
+\r
+ // Convert boolean to byte arrays\r
+ boolean[][] to = (boolean[][]) o;\r
+ byte[][] xo = new byte[to.length][];\r
+ for (int i = 0; i < to.length; i += 1) {\r
+ xo[i] = FitsUtil.booleanToByte(to[i]);\r
+ }\r
+ o = xo;\r
+ }\r
+\r
+ // Write all rows of data onto the heap.\r
+ int offset = heap.putData(o);\r
+\r
+ int blen = ArrayFuncs.getBaseLength(o);\r
+\r
+ // Handle an addRow of a variable length element.\r
+ // In this case we only get a one-d array, but we just\r
+ // make is 1 x n to get the second dimension.\r
+ if (!(o instanceof Object[])) {\r
+ o = new Object[]{o};\r
+ }\r
+\r
+ // Create the array descriptors\r
+ int nrow = Array.getLength(o);\r
+ int factor = 1;\r
+ if (isComplex(col)) {\r
+ factor = 2;\r
+ }\r
+ if (isLongVary(col)) {\r
+ long[] descrip = new long[2 * nrow];\r
+\r
+ Object[] x = (Object[]) o;\r
+ // Fill the descriptor for each row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ int len = Array.getLength(x[i]);\r
+ descrip[2 * i] = len;\r
+ descrip[2 * i + 1] = offset;\r
+ offset += len * blen * factor;\r
+ }\r
+ o = descrip;\r
+ } else {\r
+ int[] descrip = new int[2 * nrow];\r
+\r
+ Object[] x = (Object[]) o;\r
+\r
+ // Fill the descriptor for each row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ int len = Array.getLength(x[i]);\r
+ descrip[2 * i] = len;\r
+ descrip[2 * i + 1] = offset;\r
+ offset += len * blen * factor;\r
+ }\r
+ o = descrip;\r
+ }\r
+ }\r
+\r
+ return o;\r
+ }\r
+\r
+ /** Convert data from binary table representation to external\r
+ * Java representation.\r
+ */\r
+ private Object columnToArray(int col, Object o, int rows) throws FitsException {\r
+\r
+ // Most of the time we need do nothing!\r
+ if (flags[col] == 0) {\r
+ return o;\r
+ }\r
+\r
+ // If a varying length column use the descriptors to\r
+ // extract appropriate information from the headers.\r
+ if (isVarCol(col)) {\r
+\r
+ // A. Kovacs (4/1/08)\r
+ // Ensure that the heap has been initialized\r
+ if (!heapReadFromStream) {\r
+ readHeap(currInput);\r
+ }\r
+\r
+ int[] descrip;\r
+ if (isLongVary(col)) {\r
+ // Convert longs to int's. This is dangerous.\r
+ if (!warnedOnVariableConversion) {\r
+ System.err.println("Warning: converting long variable array pointers to int's");\r
+ warnedOnVariableConversion = true;\r
+ }\r
+ descrip = (int[]) ArrayFuncs.convertArray(o, int.class);\r
+ } else {\r
+ descrip = (int[]) o;\r
+ }\r
+\r
+ int nrow = descrip.length / 2;\r
+\r
+ Object[] res; // Res will be the result of extracting from the heap.\r
+ int[] dims; // Used to create result arrays.\r
+\r
+\r
+ if (isComplex(col)) {\r
+ // Complex columns have an extra dimension for each row\r
+ dims = new int[]{nrow, 0, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);\r
+ // Set up dims for individual rows.\r
+ dims = new int[2];\r
+ dims[1] = 2;\r
+\r
+ // ---> Added clause by Attila Kovacs (13 July 2007)\r
+ // String columns have to read data into a byte array at first\r
+ // then do the string conversion later.\r
+\r
+ } else if (isString(col)) {\r
+ dims = new int[]{nrow, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(byte.class, dims);\r
+\r
+ } else {\r
+ // Non-complex data has a simple primitive array for each row\r
+ dims = new int[]{nrow, 0};\r
+ res = (Object[]) ArrayFuncs.newInstance(bases[col], dims);\r
+ }\r
+\r
+ // Now read in each requested row.\r
+ for (int i = 0; i < nrow; i += 1) {\r
+ Object row;\r
+ int offset = descrip[2 * i + 1];\r
+ int dim = descrip[2 * i];\r
+\r
+ if (isComplex(col)) {\r
+ dims[0] = dim;\r
+ row = ArrayFuncs.newInstance(bases[col], dims);\r
+\r
+ // ---> Added clause by Attila Kovacs (13 July 2007)\r
+ // Again, String entries read data into a byte array at first\r
+ // then do the string conversion later.\r
+ } else if (isString(col)) {\r
+ // For string data, we need to read bytes and convert\r
+ // to strings\r
+ row = ArrayFuncs.newInstance(byte.class, dim);\r
+\r
+ } else if (isBoolean(col)) {\r
+ // For boolean data, we need to read bytes and convert\r
+ // to booleans.\r
+ row = ArrayFuncs.newInstance(byte.class, dim);\r
+\r
+ } else {\r
+ row = ArrayFuncs.newInstance(bases[col], dim);\r
+ }\r
+\r
+ heap.getData(offset, row);\r
+\r
+ // Now do the boolean conversion.\r
+ if (isBoolean(col)) {\r
+ row = FitsUtil.byteToBoolean((byte[]) row);\r
+ }\r
+\r
+ res[i] = row;\r
+ }\r
+ o = res;\r
+\r
+ } else { // Fixed length columns\r
+\r
+ // Need to convert String byte arrays to appropriate Strings.\r
+ if (isString(col)) {\r
+ int[] dims = dimens[col];\r
+ byte[] bytes = (byte[]) o;\r
+ if (bytes.length > 0) {\r
+ if (dims.length > 0) {\r
+ o = FitsUtil.byteArrayToStrings(bytes, dims[dims.length - 1]);\r
+ } else {\r
+ o = FitsUtil.byteArrayToStrings(bytes, 1);\r
+ }\r
+ } else {\r
+ // This probably fails for multidimensional arrays of strings where\r
+ // all elements are null.\r
+ String[] str = new String[rows];\r
+ for (int i = 0; i < str.length; i += 1) {\r
+ str[i] = "";\r
+ }\r
+ o = str;\r
+ }\r
+\r
+ } else if (isBoolean(col)) {\r
+ o = FitsUtil.byteToBoolean((byte[]) o);\r
+ }\r
+ }\r
+\r
+ return o;\r
+ }\r
+\r
+ /** Make sure the arrays which describe the columns are\r
+ * long enough, and if not extend them.\r
+ */\r
+ private void extendArrays(int need) {\r
+\r
+ boolean wasNull = false;\r
+ if (sizes == null) {\r
+ wasNull = true;\r
+\r
+ } else if (sizes.length > need) {\r
+ return;\r
+ }\r
+\r
+ // Allocate the arrays.\r
+ int[] newSizes = new int[need];\r
+ int[][] newDimens = new int[need][];\r
+ int[] newFlags = new int[need];\r
+ Object[] newModel = new Object[need];\r
+ Object[] newColumns = new Object[need];\r
+ Class[] newBases = new Class[need];\r
+\r
+ if (!wasNull) {\r
+ int len = sizes.length;\r
+ System.arraycopy(sizes, 0, newSizes, 0, len);\r
+ System.arraycopy(dimens, 0, newDimens, 0, len);\r
+ System.arraycopy(flags, 0, newFlags, 0, len);\r
+ System.arraycopy(modelRow, 0, newModel, 0, len);\r
+ System.arraycopy(columns, 0, newColumns, 0, len);\r
+ System.arraycopy(bases, 0, newBases, 0, len);\r
+ }\r
+\r
+ sizes = newSizes;\r
+ dimens = newDimens;\r
+ flags = newFlags;\r
+ modelRow = newModel;\r
+ columns = newColumns;\r
+ bases = newBases;\r
+ }\r
+\r
+ /** What is the size of the heap -- including the offset from the end of the\r
+ * table data.\r
+ */\r
+ public int getHeapSize() {\r
+ return heapOffset + heap.size();\r
+ }\r
+\r
+ /** What is the offset to the heap */\r
+ public int getHeapOffset() {\r
+ return heapOffset;\r
+ }\r
+\r
+ /** Does this column have variable length arrays? */\r
+ boolean isVarCol(int col) {\r
+ return (flags[col] & COL_VARYING) != 0;\r
+ }\r
+\r
+ /** Does this column have variable length arrays? */\r
+ boolean isLongVary(int col) {\r
+ return (flags[col] & COL_LONGVARY) != 0;\r
+ }\r
+\r
+ /** Is this column a string column */\r
+ private boolean isString(int col) {\r
+ return (flags[col] & COL_STRING) != 0;\r
+ }\r
+\r
+ /** Is this column complex? */\r
+ private boolean isComplex(int col) {\r
+ return (flags[col] & COL_COMPLEX) != 0;\r
+ }\r
+\r
+ /** Is this column a boolean column */\r
+ private boolean isBoolean(int col) {\r
+ return (flags[col] & COL_BOOLEAN) != 0;\r
+ }\r
+\r
+ /** Is this column a bit column */\r
+ private boolean isBit(int col) {\r
+ return (flags[col] & COL_BOOLEAN) != 0;\r
+ }\r
+\r
+ /** Delete a set of columns. Note that this\r
+ * does not fix the header, so users should normally\r
+ * call the routine in TableHDU.\r
+ */\r
+ public void deleteColumns(int start, int len) throws FitsException {\r
+ getData();\r
+ try {\r
+ rowLen = table.deleteColumns(start, len);\r
+ nCol -= len;\r
+ } catch (Exception e) {\r
+ throw new FitsException("Error deleting columns from BinaryTable:" + e);\r
+ }\r
+ }\r
+\r
+ /** Update the header after a deletion. */\r
+ public void updateAfterDelete(int oldNcol, Header hdr) throws FitsException {\r
+ hdr.addValue("NAXIS1", rowLen, "ntf::binarytable:naxis1:1");\r
+ }\r
+}\r
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.*;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+
+/** FITS binary table header/data unit */
+public class BinaryTableHDU
+ extends TableHDU {
+
+ private BinaryTable table;
+ /** The standard column keywords for a binary table. */
+ private String[] keyStems = {"TTYPE", "TFORM", "TUNIT", "TNULL", "TSCAL", "TZERO", "TDISP", "TDIM"};
+
+ public BinaryTableHDU(Header hdr, Data datum) {
+
+ super((TableData) datum);
+ myHeader = hdr;
+ myData = datum;
+ table = (BinaryTable) datum;
+
+ }
+
+ /** Create data from a binary table header.
+ * @param header the template specifying the binary table.
+ * @exception FitsException if there was a problem with the header.
+ */
+ public static Data manufactureData(Header header) throws FitsException {
+ return new BinaryTable(header);
+ }
+
+ public Data manufactureData() throws FitsException {
+ return manufactureData(myHeader);
+ }
+
+ /** Build a binary table HDU from the supplied data.
+ * @param table the array used to build the binary table.
+ * @exception FitsException if there was a problem with the data.
+ */
+ public static Header manufactureHeader(Data data) throws FitsException {
+ Header hdr = new Header();
+ data.fillHeader(hdr);
+ return hdr;
+ }
+
+ /** Encapsulate data in a BinaryTable data type */
+ public static Data encapsulate(Object o) throws FitsException {
+
+ if (o instanceof nom.tam.util.ColumnTable) {
+ return new BinaryTable((nom.tam.util.ColumnTable) o);
+ } else if (o instanceof Object[][]) {
+ return new BinaryTable((Object[][]) o);
+ } else if (o instanceof Object[]) {
+ return new BinaryTable((Object[]) o);
+ } else {
+ throw new FitsException("Unable to encapsulate object of type:"
+ + o.getClass().getName() + " as BinaryTable");
+ }
+ }
+
+ /** Check that this is a valid binary table header.
+ * @param header to validate.
+ * @return <CODE>true</CODE> if this is a binary table header.
+ */
+ public static boolean isHeader(Header header) {
+ String xten = header.getStringValue("XTENSION");
+ if (xten == null) {
+ return false;
+ }
+ xten = xten.trim();
+ if (xten.equals("BINTABLE") || xten.equals("A3DTABLE")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** Check that this HDU has a valid header.
+ * @return <CODE>true</CODE> if this HDU has a valid header.
+ */
+ public boolean isHeader() {
+ return isHeader(myHeader);
+ }
+
+ /* Check if this data object is consistent with a binary table. There
+ * are three options: a column table object, an Object[][], or an Object[].
+ * This routine doesn't check that the dimensions of arrays are properly
+ * consistent.
+ */
+ public static boolean isData(Object o) {
+
+ if (o instanceof nom.tam.util.ColumnTable || o instanceof Object[][]
+ || o instanceof Object[]) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** Add a column without any associated header information.
+ *
+ * @param data The column data to be added. Data should be an Object[] where
+ * type of all of the constituents is identical. The length
+ * of data should match the other columns. <b> Note:</b> It is
+ * valid for data to be a 2 or higher dimensionality primitive
+ * array. In this case the column index is the first (in Java speak)
+ * index of the array. E.g., if called with int[30][20][10], the
+ * number of rows in the table should be 30 and this column
+ * will have elements which are 2-d integer arrays with TDIM = (10,20).
+ * @exception FitsException the column could not be added.
+ */
+ public int addColumn(Object data) throws FitsException {
+
+ int col = table.addColumn(data);
+ table.pointToColumn(getNCols() - 1, myHeader);
+ return col;
+ }
+
+ // Need to tell header about the Heap before writing.
+ public void write(ArrayDataOutput ado) throws FitsException {
+
+ int oldSize = myHeader.getIntValue("PCOUNT");
+ if (oldSize != table.getHeapSize()) {
+ myHeader.addValue("PCOUNT", table.getHeapSize(), "ntf::binarytablehdu:pcount:1");
+ }
+
+ if (myHeader.getIntValue("PCOUNT") == 0) {
+ myHeader.deleteKey("THEAP");
+ } else {
+ myHeader.getIntValue("TFIELDS");
+ int offset = myHeader.getIntValue("NAXIS1")
+ * myHeader.getIntValue("NAXIS2")
+ + table.getHeapOffset();
+ myHeader.addValue("THEAP", offset, "ntf::binarytablehdu:theap:1");
+ }
+
+ super.write(ado);
+ }
+
+ /**
+ * Convert a column in the table to complex. Only tables with appropriate
+ * types and dimensionalities can be converted. It is legal to call this on
+ * a column that is already complex.
+ *
+ * @param index The 0-based index of the column to be converted.
+ * @return Whether the column can be converted
+ * @throws FitsException
+ */
+ public boolean setComplexColumn(int index) throws FitsException {
+ boolean status = false;
+ if (table.setComplexColumn(index)) {
+
+ // No problem with the data. Make sure the header
+ // is right.
+
+ int[] dimens = table.getDimens()[index];
+ Class base = table.getBases()[index];
+
+ int dim = 1;
+ String tdim = "";
+ String sep = "";
+ // Don't loop over all values.
+ // The last is the [2] for the complex data.
+ for (int i = 0; i < dimens.length - 1; i += 1) {
+ dim *= dimens[i];
+ tdim = dimens[i] + sep + tdim;
+ sep = ",";
+ }
+ String suffix = "C"; // For complex
+ // Update the TFORMn keyword.
+ if (base == double.class) {
+ suffix = "M";
+ }
+
+ // Worry about variable length columns.
+ String prefix = "";
+ if (table.isVarCol(index)) {
+ prefix = "P";
+ dim = 1;
+ if (table.isLongVary(index)) {
+ prefix = "Q";
+ }
+ }
+
+ // Now update the header.
+ myHeader.findCard("TFORM" + (index + 1));
+ HeaderCard hc = myHeader.nextCard();
+ String oldComment = hc.getComment();
+ if (oldComment == null) {
+ oldComment = "Column converted to complex";
+ }
+ myHeader.addValue("TFORM" + (index + 1), dim + prefix + suffix, oldComment);
+ if (tdim.length() > 0) {
+ myHeader.addValue("TDIM" + (index + 1), "(" + tdim + ")", "ntf::binarytablehdu:tdimN:1");
+ } else {
+ // Just in case there used to be a TDIM card that's no longer needed.
+ myHeader.removeCard("TDIM" + (index + 1));
+ }
+ status = true;
+ }
+ return status;
+ }
+
+ private void prtField(String type, String field) {
+ String val = myHeader.getStringValue(field);
+ if (val != null) {
+ System.out.print(type + '=' + val + "; ");
+ }
+ }
+
+ /** Print out some information about this HDU.
+ */
+ public void info() {
+
+ BinaryTable myData = (BinaryTable) this.myData;
+
+ System.out.println(" Binary Table");
+ System.out.println(" Header Information:");
+
+ int nhcol = myHeader.getIntValue("TFIELDS", -1);
+ int nrow = myHeader.getIntValue("NAXIS2", -1);
+ int rowsize = myHeader.getIntValue("NAXIS1", -1);
+
+ System.out.print(" " + nhcol + " fields");
+ System.out.println(", " + nrow + " rows of length " + rowsize);
+
+ for (int i = 1; i <= nhcol; i += 1) {
+ System.out.print(" " + i + ":");
+ prtField("Name", "TTYPE" + i);
+ prtField("Format", "TFORM" + i);
+ prtField("Dimens", "TDIM" + i);
+ System.out.println("");
+ }
+
+ System.out.println(" Data Information:");
+ if (myData == null
+ || table.getNRows() == 0 || table.getNCols() == 0) {
+ System.out.println(" No data present");
+ if (table.getHeapSize() > 0) {
+ System.out.println(" Heap size is: " + table.getHeapSize() + " bytes");
+ }
+ } else {
+
+ System.out.println(" Number of rows=" + table.getNRows());
+ System.out.println(" Number of columns=" + table.getNCols());
+ if (table.getHeapSize() > 0) {
+ System.out.println(" Heap size is: " + table.getHeapSize() + " bytes");
+ }
+ Object[] cols = table.getFlatColumns();
+ for (int i = 0; i < cols.length; i += 1) {
+ System.out.println(" " + i + ":" + ArrayFuncs.arrayDescription(cols[i]));
+ }
+ }
+ }
+
+ /** What are the standard column stems for a binary table?
+ */
+ public String[] columnKeyStems() {
+ return keyStems;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+import java.io.*;
+import nom.tam.util.*;
+
+/** This class provides methods to access the data segment of an
+ * HDU.
+ */
+public abstract class Data implements FitsElement {
+
+ /** This is the object which contains the actual data for the HDU.
+ * <ul>
+ * <li> For images and primary data this is a simple (but possibly
+ * multi-dimensional) primitive array. When group data is
+ * supported it will be a possibly multidimensional array
+ * of group objects.
+ * <li> For ASCII data it is a two dimensional Object array where
+ * each of the constituent objects is a primitive array of length 1.
+ * <li> For Binary data it is a two dimensional Object array where
+ * each of the constituent objects is a primitive array of arbitrary
+ * (more or less) dimensionality.
+ * </ul>
+ */
+ /** The starting location of the data when last read */
+ protected long fileOffset = -1;
+ /** The size of the data when last read */
+ protected long dataSize;
+ /** The inputstream used. */
+ protected RandomAccess input;
+
+ /** Get the file offset */
+ public long getFileOffset() {
+ return fileOffset;
+ }
+
+ /** Set the fields needed for a re-read */
+ protected void setFileOffset(Object o) {
+ if (o instanceof RandomAccess) {
+ fileOffset = FitsUtil.findOffset(o);
+ dataSize = getTrueSize();
+ input = (RandomAccess) o;
+ }
+ }
+
+ /** Write the data -- including any buffering needed
+ * @param o The output stream on which to write the data.
+ */
+ public abstract void write(ArrayDataOutput o) throws FitsException;
+
+ /** Read a data array into the current object and if needed position
+ * to the beginning of the next FITS block.
+ * @param i The input data stream
+ */
+ public abstract void read(ArrayDataInput i) throws FitsException;
+
+ public void rewrite() throws FitsException {
+
+ if (!rewriteable()) {
+ throw new FitsException("Illegal attempt to rewrite data");
+ }
+
+ FitsUtil.reposition(input, fileOffset);
+ write((ArrayDataOutput) input);
+ try {
+ ((ArrayDataOutput) input).flush();
+ } catch (IOException e) {
+ throw new FitsException("Error in rewrite flush: " + e);
+ }
+ }
+
+ public boolean reset() {
+ try {
+ FitsUtil.reposition(input, fileOffset);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public boolean rewriteable() {
+ if (input == null
+ || fileOffset < 0
+ || (getTrueSize() + 2879) / 2880 != (dataSize + 2879) / 2880) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ abstract long getTrueSize();
+
+ /** Get the size of the data element in bytes */
+ public long getSize() {
+ return FitsUtil.addPadding(getTrueSize());
+ }
+
+ /** Return the data array object.
+ */
+ public abstract Object getData() throws FitsException;
+
+ /** Return the non-FITS data object */
+ public Object getKernel() throws FitsException {
+ return getData();
+ }
+
+ /** Modify a header to point to this data
+ */
+ abstract void fillHeader(Header head) throws FitsException;
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import nom.tam.util.*;
+
+/** This class provides access to routines to allow users
+ * to read and write FITS files.
+ * <p>
+ *
+ * <p>
+ * <b> Description of the Package </b>
+ * <p>
+ * This FITS package attempts to make using FITS files easy,
+ * but does not do exhaustive error checking. Users should
+ * not assume that just because a FITS file can be read
+ * and written that it is necessarily legal FITS.
+ *
+ *
+ * <ul>
+ * <li> The Fits class provides capabilities to
+ * read and write data at the HDU level, and to
+ * add and delete HDU's from the current Fits object.
+ * A large number of constructors are provided which
+ * allow users to associate the Fits object with
+ * some form of external data. This external
+ * data may be in a compressed format.
+ * <li> The HDU class is a factory class which is used to
+ * create HDUs. HDU's can be of a number of types
+ * derived from the abstract class BasicHDU.
+ * The hierarchy of HDUs is:
+ * <ul>
+ * <li>BasicHDU
+ * <ul>
+ * <li> ImageHDU
+ * <li> RandomGroupsHDU
+ * <li> TableHDU
+ * <ul>
+ * <li> BinaryTableHDU
+ * <li> AsciiTableHDU
+ * </ul>
+ * </ul>
+ * </ul>
+ *
+ * <li> The Header class provides many functions to
+ * add, delete and read header keywords in a variety
+ * of formats.
+ * <li> The HeaderCard class provides access to the structure
+ * of a FITS header card.
+ * <li> The Data class is an abstract class which provides
+ * the basic methods for reading and writing FITS data.
+ * Users will likely only be interested in the getData
+ * method which returns that actual FITS data.
+ * <li> The TableHDU class provides a large number of
+ * methods to access and modify information in
+ * tables.
+ * <li> The Column class
+ * combines the Header information and Data corresponding to
+ * a given column.
+ * </ul>
+ *
+ *
+ * @version 1.06.0 May 21, 2011
+ */
+public class Fits {
+
+ /** The input stream associated with this Fits object.
+ */
+ private ArrayDataInput dataStr;
+
+ /** A vector of HDUs that have been added to this
+ * Fits object.
+ */
+ private Vector hduList = new Vector();
+
+ /** Has the input stream reached the EOF?
+ */
+ private boolean atEOF;
+
+ /** The last offset we reached.
+ * A -1 is used to indicate that we
+ * cannot use the offset.
+ */
+ private long lastFileOffset = -1;
+
+ /** Indicate the version of these classes */
+ public static String version() {
+
+ // Version 0.1: Original test FITS classes -- 9/96
+ // Version 0.2: Pre-alpha release 10/97
+ // Complete rewrite using BufferedData*** and
+ // ArrayFuncs utilities.
+ // Version 0.3: Pre-alpha release 1/98
+ // Incorporation of HDU hierarchy developed
+ // by Dave Glowacki and various bug fixes.
+ // Version 0.4: Alpha-release 2/98
+ // BinaryTable classes revised to use
+ // ColumnTable classes.
+ // Version 0.5: Random Groups Data 3/98
+ // Version 0.6: Handling of bad/skipped FITS, FitsDate (D. Glowacki) 3/98
+ // Version 0.9: ASCII tables, Tiled images, Faux, Bad and SkippedHDU class
+ // deleted. 12/99
+ // Version 0.91: Changed visibility of some methods.
+ // Minor fixes.
+ // Version 0.92: Fix bug in BinaryTable when reading from stream.
+ // Version 0.93: Supports HIERARCH header cards. Added FitsElement interface.
+ // Several bug fixes especially for null HDUs.
+ // Version 0.96: Address issues with mandatory keywords.
+ // Fix problem where some keywords were not properly keyed.
+ // Version 0.96a: Update version in FITS
+ // Version 0.99: Added support for Checksums (thanks to RJ Mathar).
+ // Fixed bug with COMMENT and HISTORY keywords (Rose Early)
+ // Changed checking for compression and fixed bug with TFORM
+ // handling in binary tables (Laurent Michel)
+ // Distinguished arrays of length 1 from scalars in
+ // binary tables (Jorgo Bakker)
+ // Fixed bug in handling of length 0 values in headers (Fred Romerfanger, Jorgo Bakker)
+ // Truncated BufferedFiles when finishing write (but only
+ // for FITS file as a whole.)
+ // Fixed bug writing binary tables with deferred reads.
+ // Made addLine methods in Header public.
+ // Changed ArrayFuncs.newInstance to handle inputs with dimensionality of 0.
+ // Version 0.99.1
+ // Added deleteRows and deleteColumns functionality to all tables.
+ // This includes changes
+ // to TableData, TableHDU, AsciiTable, BinaryTable and util/ColumnTable.
+ // Row deletions were suggested by code of R. Mathar but this works
+ // on all types of tables and implements the deletions at a lower level.
+ // Completely revised util.HashedList to use more standard features from
+ // Collections. The HashedList now melds a HashMap and ArrayList
+ // Added sort to HashedList function to enable sorting of the list.
+ // The logic now uses a simple index for the iterators rather than
+ // traversing a linked list.
+ // Added sort before write in Header to ensure keywords are in correct order.
+ // This uses a new HeaderOrder class which implements java.util.Comparator to
+ // indicate the required order for FITS keywords. Users should now
+ // be able to write required keywords anywhere without getting errors
+ // later when they try to write out the FITS file.
+ // Fixed bug in setColumn in util.Column table where the new column
+ // was not being pointed to. Any new column resets the table.
+ // Several fixes to BinaryTable to address errors in variable length
+ // array handling.
+ // Several fixes to the handling of variable length array in binary tables.
+ // (noted by Guillame Belanger).
+ // Several fixes and changes suggested by Richard Mathar mostly
+ // in BinaryTable.
+ // Version 0.99.2
+ // Revised test routines to use Junit. Note that Junit tests
+ // use annotations and require Java 1.5.
+ // Added ArrayFuncs.arrayEquals() methods to compare
+ // arbitrary arrays.
+ // Fixed bugs in handling of 0 length columns and table update.
+ // Version 0.99.3
+ // Additional fixes for 0 length strings.
+ // Version 0.99.4
+ // Changed handling of constructor for File objects
+ // 0.99.5
+ // Add ability to handle FILE, HTTPS and FTP URLs and to
+ // handle redirects amongst different protocols.
+ // 0.99.5
+ // Fixes to String handling (A. Kovacs)
+ // Truncating long doubles to fit in
+ // standard header.
+ // Made some methods public in FitsFactory
+ // Added Set
+ // 0.99.6
+ // Fix to BinaryTable (L. Michel)
+ // Version 1.00.0
+ // Support for .Z compressed data.
+ // Better detection of compressed data streams
+ // Bug fix for binary tables (A. Kovacs)
+ // Version 1.00.1 (2/09)
+ // Fix for exponential format in header keywords
+ // Version 1.00.2 (3/09)
+ // Fixed offsets when users read rows or elements
+ // within multiHDU files.
+ // Version 1.01.0
+ // Fixes bugs and adds some more graceful
+ // error handling for situations where arrays
+ // could exceed 2G. More work is needed here though.
+ // Data.getTrueSize() now returns a long.
+ //
+ // Fixed bug with initial blanks in HIERARCH
+ // values.
+ // Version 1.02.0 (7/09)
+ // Fixes bugs in ASCII tables where integer and real
+ // fields that are blank should be read as 0 per the FITS
+ // standard. (L. Michel)
+ //
+ // Adds PaddingException to allow users to read
+ // improperly padded last HDU in FITS file. (suggested by L. Michel)
+ // This required changes to the Fits.java and all of the Data subclasses
+ // as well as the new exception classes.
+ // Version 1.03.0 (7/09)
+ // Many changes to support long (>2GB) arrays in
+ // reads and size computations:
+ // ArrayDataInput.readArray deprecated in
+ // favor of ArrayDataInput.readLArray
+ // ArrayUtil.computeSize -> ArrayUtil.computeLSize
+ // ArrayUtil.nElements -> ArrayUtil.nLElements
+ // The skipBytes method in ArrayDataInput is overloaded
+ // to take a long argument and return a long value (in
+ // addition to the method inherited from DataInput
+ // the takes and returns an int).
+ //
+ // Corresponding changes in FITS classes.
+ // [Note that there are still many restrictions
+ // due to the array size limits in Java.]
+ //
+ // A number of obsolete comments regarding BITPIX=64 being non-standard
+ // were removed.
+ // If errors are found in reading the Header of an HDU
+ // an IOException is now returned in some situations
+ // where an Error was being returned.
+ //
+ // A bug in the new PaddingException was fixed that
+ // lets truncated ImageHDUs have Tilers.
+ //
+ // Version 1.03.1 (7/09)
+ // Changed FitsUtil.byteArrayToStrings to make
+ // sure that deleted white space is eligible for
+ // garbage collection. (J.C. Segovia)
+ //
+ // Version 1.04.0 (12/09)
+ //
+ // Added support for the long string convention
+ // (see JavaDocs for Header).
+ // Fixed errors in handling of strings with embedded
+ // quotes.
+ // Other minor bugs.
+
+ // Version 1.05.0 (12/10)
+ // Several fixes suggested by Laurent Bourges
+ // - Better handling of strings in binary tables
+ // including handling of truncated strings (with
+ // embedded nuls) and detection of illegal
+ // non-printing characters.
+ // - New table metadata functions in TableHDU
+ // - Handling of complex data including variable
+ // length arrays.
+ // Added a number of convenience methods
+ // - FitsUtil.pad() is used to write padding
+ // rather than separate code in many classes
+ // - FitsUtil.HDUFactory will now create
+ // an HDU from a Header or input data.
+ // - reset() method added to FitsElement to simplify
+ // reading of Fits data using low level access.
+ // This is implemented in many classes.
+ // - dumpArray method in ArrayFuncs for convenience
+ // in debugging.
+ // Version 1.05.1 (2/11)
+ // Fixed error in Long string support where the
+ // COMMENT keyword was being used instead of the
+ // correct CONTINUE. (V. Forchi)
+ // An error in the positioning of the Header cursor
+ // for primary images was noted by V. Forchi. Updates
+ // to the header could easily result in writing
+ // records before the EXTEND keyword which is a violation
+ // of the FITS standard.
+ // Version 1.06.0 (5/11)
+ // Substantial reworking of compression to accommodate
+ // BZIP2 compression. The Apache Bzip library is used or
+ // since this is very slow, the user can specify a local
+ // command to do the decompression using the BZIP_DECOMPRESSOR
+ // environment variable. This is assumed to require a
+ // '-' argument which is added if not supplied by the user.
+ // The decompressor should act as a filter between standard input
+ // and output.
+ //
+ // User compression flags are now completely
+ // ignored and the compression and the compression
+ // is determined entirely by the content of the stream.
+ // The Apache library will be needed in the
+ // classpath to accommodate BZIP2 inputs if the user
+ // does not supply the BZIP_DECOMPRESSOR.
+ //
+ // Adding additional compression methods should be much easier and
+ // may only involve adding a couple of lines in the
+ // FitsUtil.decompress function if a decompressor class
+ // is available.
+ //
+ // One subtle consequence of how compression is now handled
+ // is that there is no advantage for users to
+ // create their own BufferedDataInputStream's.
+ // Users should just provide a standard input stream
+ // and allow the FITS library to wrap it in a
+ // BufferedDataInputStream.
+ //
+ // A bug in the UndefinedData class was detected
+ // Vincenzo Forzi and has been corrected.
+ //
+ // The nom.tam.util.AsciiFuncs class now handles
+ // ASCII encoding to more cleanly separate this
+ // functionality from the FITS library and to enable
+ // Java 1.5 compatibitity. (Suggested by changes of L.Bourges)
+ // Some other V1.5 incompatiblities removed.
+ //
+ // The HeaderCommentsMap class is now provided to enable
+ // users to control the comments that are generated in system
+ // generated header cards. The map is initialized to values
+ // that should be the same as the current defaults. This
+ // should allow users to emulate the comments of other packages.
+ //
+ // All Java code has been passed through NetBeans formatter
+ // so that it should have a more uniform appearance.
+
+ return "1.060";
+ }
+
+ /** Create an empty Fits object which is not
+ * associated with an input stream.
+ */
+ public Fits() {
+ }
+
+ /** Create a Fits object associated with
+ * the given data stream.
+ * Compression is determined from the first few bytes of the stream.
+ * @param str The data stream.
+ */
+ public Fits(InputStream str) throws FitsException {
+ streamInit(str, false);
+ }
+
+ /** Create a Fits object associated with a data stream.
+ * @param str The data stream.
+ * @param compressed Is the stream compressed? This is currently ignored.
+ * Compression is determined from the first two bytes in the stream.
+ */
+ public Fits(InputStream str, boolean compressed)
+ throws FitsException {
+ streamInit(str);
+ }
+
+ /** Initialize the stream.
+ * @param str The user specified input stream
+ * @param seekable ignored
+ */
+ protected void streamInit(InputStream str, boolean seekable)
+ throws FitsException {
+ streamInit(str);
+ }
+
+ /** Do the stream initialization.
+ *
+ * @param str The input stream.
+ * @param compressed Is this data compressed? This flag
+ * is ignored. The compression is determined from the stream content.
+ * @param seekable Can one seek on the stream. This parameter is ignored.
+ */
+ protected void streamInit(InputStream str, boolean compressed,
+ boolean seekable)
+ throws FitsException {
+ streamInit(str);
+ }
+
+ /** Initialize the input stream. Mostly this
+ * checks to see if the stream is compressed and
+ * wraps the stream if necessary. Even if the
+ * stream is not compressed, it will likely be wrapped
+ * in a PushbackInputStream. So users should probably
+ * not supply a BufferedDataInputStream themselves, but
+ * should allow the Fits class to do the wrapping.
+ * @param str
+ * @throws FitsException
+ */
+ protected void streamInit(InputStream str) throws FitsException {
+ str = FitsUtil.decompress(str);
+ if (str instanceof ArrayDataInput) {
+ dataStr = (ArrayDataInput) str;
+ } else {
+ // Use efficient blocking for input.
+ dataStr = new BufferedDataInputStream(str);
+ }
+ }
+
+ /** Initialize using buffered random access.
+ * This implies that the data is uncompressed.
+ * @param f
+ * @throws FitsException
+ */
+ protected void randomInit(File f) throws FitsException {
+
+ String permissions = "r";
+ if (!f.exists() || !f.canRead()) {
+ throw new FitsException("Non-existent or unreadable file");
+ }
+ if (f.canWrite()) {
+ permissions += "w";
+ }
+ try {
+ dataStr = new BufferedFile(f, permissions);
+
+ ((BufferedFile) dataStr).seek(0);
+ } catch (IOException e) {
+ throw new FitsException("Unable to open file " + f.getPath());
+ }
+ }
+
+ /** Associate FITS object with a File. If the file is
+ * compressed a stream will be used, otherwise random access
+ * will be supported.
+ * @param myFile The File object.
+ */
+ public Fits(File myFile) throws FitsException {
+ this(myFile, FitsUtil.isCompressed(myFile));
+ }
+
+ /** Associate the Fits object with a File
+ * @param myFile The File object.
+ * @param compressed Is the data compressed?
+ */
+ public Fits(File myFile, boolean compressed) throws FitsException {
+ fileInit(myFile, compressed);
+ }
+
+ /** Get a stream from the file and then use the stream initialization.
+ * @param myFile The File to be associated.
+ * @param compressed Is the data compressed?
+ */
+ protected void fileInit(File myFile, boolean compressed) throws FitsException {
+
+ try {
+ if (compressed) {
+ FileInputStream str = new FileInputStream(myFile);
+ streamInit(str);
+ } else {
+ randomInit(myFile);
+ }
+ } catch (IOException e) {
+ throw new FitsException("Unable to create Input Stream from File: " + myFile);
+ }
+ }
+
+ /** Associate the FITS object with a file or URL.
+ *
+ * The string is assumed to be a URL if it begins one of the
+ * protocol strings.
+ * If the string ends in .gz it is assumed that
+ * the data is in a compressed format.
+ * All string comparisons are case insensitive.
+ *
+ * @param filename The name of the file or URL to be processed.
+ * @exception FitsException Thrown if unable to find or open
+ * a file or URL from the string given.
+ **/
+ public Fits(String filename) throws FitsException {
+ this(filename, FitsUtil.isCompressed(filename));
+ }
+
+ /** Associate the FITS object with a file or URL.
+ *
+ * The string is assumed to be a URL if it begins one of the
+ * protocol strings.
+ * If the string ends in .gz it is assumed that
+ * the data is in a compressed format.
+ * All string comparisons are case insensitive.
+ *
+ * @param filename The name of the file or URL to be processed.
+ * @exception FitsException Thrown if unable to find or open
+ * a file or URL from the string given.
+ **/
+ public Fits(String filename, boolean compressed) throws FitsException {
+
+ InputStream inp;
+
+ if (filename == null) {
+ throw new FitsException("Null FITS Identifier String");
+ }
+
+ int len = filename.length();
+ String lc = filename.toLowerCase();
+ try {
+ URL test = new URL(filename);
+ InputStream is = FitsUtil.getURLStream(new URL(filename), 0);
+ streamInit(is);
+ return;
+ } catch (Exception e) {
+ // Just try it as a file
+ }
+
+ File fil = new File(filename);
+ if (fil.exists()) {
+ fileInit(fil, compressed);
+ return;
+ }
+
+
+ try {
+ InputStream str = ClassLoader.getSystemClassLoader().getResourceAsStream(filename);
+ streamInit(str);
+ } catch (Exception e) {
+ //
+ }
+
+ }
+
+ /** Associate the FITS object with a given uncompressed URL
+ * @param myURL The URL to be associated with the FITS file.
+ * @param compressed Compression flag, ignored.
+ * @exception FitsException Thrown if unable to use the specified URL.
+ */
+ public Fits(URL myURL, boolean compressed) throws FitsException {
+ this(myURL);
+ }
+
+ /** Associate the FITS object with a given URL
+ * @param myURL
+ * @exception FitsException Thrown if unable to find or open
+ * a file or URL from the string given.
+ */
+ public Fits(URL myURL) throws FitsException {
+ try {
+ streamInit(FitsUtil.getURLStream(myURL, 0));
+ } catch (IOException e) {
+ throw new FitsException("Unable to open input from URL:" + myURL);
+ }
+ }
+
+ /** Return all HDUs for the Fits object. If the
+ * FITS file is associated with an external stream make
+ * sure that we have exhausted the stream.
+ * @return an array of all HDUs in the Fits object. Returns
+ * null if there are no HDUs associated with this object.
+ */
+ public BasicHDU[] read() throws FitsException {
+
+ readToEnd();
+
+ int size = getNumberOfHDUs();
+
+ if (size == 0) {
+ return null;
+ }
+
+ BasicHDU[] hdus = new BasicHDU[size];
+ hduList.copyInto(hdus);
+ return hdus;
+ }
+
+ /** Read the next HDU on the default input stream.
+ * @return The HDU read, or null if an EOF was detected.
+ * Note that null is only returned when the EOF is detected immediately
+ * at the beginning of reading the HDU.
+ */
+ public BasicHDU readHDU() throws FitsException, IOException {
+
+ if (dataStr == null || atEOF) {
+ return null;
+ }
+
+ if (dataStr instanceof nom.tam.util.RandomAccess && lastFileOffset > 0) {
+ FitsUtil.reposition(dataStr, lastFileOffset);
+ }
+
+ Header hdr = Header.readHeader(dataStr);
+ if (hdr == null) {
+ atEOF = true;
+ return null;
+ }
+
+ Data datum = hdr.makeData();
+ try {
+ datum.read(dataStr);
+ } catch (PaddingException e) {
+ e.updateHeader(hdr);
+ throw e;
+ }
+
+ lastFileOffset = FitsUtil.findOffset(dataStr);
+ BasicHDU nextHDU = FitsFactory.HDUFactory(hdr, datum);
+
+ hduList.addElement(nextHDU);
+ return nextHDU;
+ }
+
+ /** Skip HDUs on the associate input stream.
+ * @param n The number of HDUs to be skipped.
+ */
+ public void skipHDU(int n) throws FitsException, IOException {
+ for (int i = 0; i < n; i += 1) {
+ skipHDU();
+ }
+ }
+
+ /** Skip the next HDU on the default input stream.
+ */
+ public void skipHDU() throws FitsException, IOException {
+
+ if (atEOF) {
+ return;
+ } else {
+ Header hdr = new Header(dataStr);
+ if (hdr == null) {
+ atEOF = true;
+ return;
+ }
+ int dataSize = (int) hdr.getDataSize();
+ dataStr.skip(dataSize);
+ }
+ }
+
+ /** Return the n'th HDU.
+ * If the HDU is already read simply return a pointer to the
+ * cached data. Otherwise read the associated stream
+ * until the n'th HDU is read.
+ * @param n The index of the HDU to be read. The primary HDU is index 0.
+ * @return The n'th HDU or null if it could not be found.
+ */
+ public BasicHDU getHDU(int n) throws FitsException, IOException {
+
+ int size = getNumberOfHDUs();
+
+ for (int i = size; i <= n; i += 1) {
+ BasicHDU hdu;
+ hdu = readHDU();
+ if (hdu == null) {
+ return null;
+ }
+ }
+
+ try {
+ return (BasicHDU) hduList.elementAt(n);
+ } catch (NoSuchElementException e) {
+ throw new FitsException("Internal Error: hduList build failed");
+ }
+ }
+
+ /** Read to the end of the associated input stream */
+ private void readToEnd() throws FitsException {
+
+ while (dataStr != null && !atEOF) {
+ try {
+ if (readHDU() == null) {
+ break;
+ }
+ } catch (IOException e) {
+ throw new FitsException("IO error: " + e);
+ }
+ }
+ }
+
+ /** Return the number of HDUs in the Fits object. If the
+ * FITS file is associated with an external stream make
+ * sure that we have exhausted the stream.
+ * @return number of HDUs.
+ * @deprecated The meaning of size of ambiguous. Use
+ */
+ public int size() throws FitsException {
+ readToEnd();
+ return getNumberOfHDUs();
+ }
+
+ /** Add an HDU to the Fits object. Users may intermix
+ * calls to functions which read HDUs from an associated
+ * input stream with the addHDU and insertHDU calls,
+ * but should be careful to understand the consequences.
+ *
+ * @param myHDU The HDU to be added to the end of the FITS object.
+ */
+ public void addHDU(BasicHDU myHDU)
+ throws FitsException {
+ insertHDU(myHDU, getNumberOfHDUs());
+ }
+
+ /** Insert a FITS object into the list of HDUs.
+ *
+ * @param myHDU The HDU to be inserted into the list of HDUs.
+ * @param n The location at which the HDU is to be inserted.
+ */
+ public void insertHDU(BasicHDU myHDU, int n)
+ throws FitsException {
+
+ if (myHDU == null) {
+ return;
+ }
+
+ if (n < 0 || n > getNumberOfHDUs()) {
+ throw new FitsException("Attempt to insert HDU at invalid location: " + n);
+ }
+
+ try {
+
+ if (n == 0) {
+
+ // Note that the previous initial HDU is no longer the first.
+ // If we were to insert tables backwards from last to first,
+ // we could get a lot of extraneous DummyHDUs but we currently
+ // do not worry about that.
+
+ if (getNumberOfHDUs() > 0) {
+ ((BasicHDU) hduList.elementAt(0)).setPrimaryHDU(false);
+ }
+
+ if (myHDU.canBePrimary()) {
+ myHDU.setPrimaryHDU(true);
+ hduList.insertElementAt(myHDU, 0);
+ } else {
+ insertHDU(BasicHDU.getDummyHDU(), 0);
+ myHDU.setPrimaryHDU(false);
+ hduList.insertElementAt(myHDU, 1);
+ }
+ } else {
+ myHDU.setPrimaryHDU(false);
+ hduList.insertElementAt(myHDU, n);
+ }
+ } catch (NoSuchElementException e) {
+ throw new FitsException("hduList inconsistency in insertHDU");
+ }
+
+ }
+
+ /** Delete an HDU from the HDU list.
+ *
+ * @param n The index of the HDU to be deleted.
+ * If n is 0 and there is more than one HDU present, then
+ * the next HDU will be converted from an image to
+ * primary HDU if possible. If not a dummy header HDU
+ * will then be inserted.
+ */
+ public void deleteHDU(int n) throws FitsException {
+ int size = getNumberOfHDUs();
+ if (n < 0 || n >= size) {
+ throw new FitsException("Attempt to delete non-existent HDU:" + n);
+ }
+ try {
+ hduList.removeElementAt(n);
+ if (n == 0 && size > 1) {
+ BasicHDU newFirst = (BasicHDU) hduList.elementAt(0);
+ if (newFirst.canBePrimary()) {
+ newFirst.setPrimaryHDU(true);
+ } else {
+ insertHDU(BasicHDU.getDummyHDU(), 0);
+ }
+ }
+ } catch (NoSuchElementException e) {
+ throw new FitsException("Internal Error: hduList Vector Inconsitency");
+ }
+ }
+
+ /** Write a Fits Object to an external Stream.
+ *
+ * @param dos A DataOutput stream.
+ */
+ public void write(DataOutput os) throws FitsException {
+
+ ArrayDataOutput obs;
+ boolean newOS = false;
+
+ if (os instanceof ArrayDataOutput) {
+ obs = (ArrayDataOutput) os;
+ } else if (os instanceof DataOutputStream) {
+ newOS = true;
+ obs = new BufferedDataOutputStream((DataOutputStream) os);
+ } else {
+ throw new FitsException("Cannot create ArrayDataOutput from class "
+ + os.getClass().getName());
+ }
+
+ BasicHDU hh;
+ for (int i = 0; i < getNumberOfHDUs(); i += 1) {
+ try {
+ hh = (BasicHDU) hduList.elementAt(i);
+ hh.write(obs);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ e.printStackTrace();
+ throw new FitsException("Internal Error: Vector Inconsistency" + e);
+ }
+ }
+ if (newOS) {
+ try {
+ obs.flush();
+ obs.close();
+ } catch (IOException e) {
+ System.err.println("Warning: error closing FITS output stream");
+ }
+ }
+ try {
+ if (obs instanceof BufferedFile) {
+ ((BufferedFile) obs).setLength(((BufferedFile) obs).getFilePointer());
+ }
+ } catch (IOException e) {
+ // Ignore problems...
+ }
+
+ }
+
+ /** Read a FITS file from an InputStream object.
+ *
+ * @param is The InputStream stream whence the FITS information
+ * is found.
+ */
+ public void read(InputStream is) throws FitsException, IOException {
+
+ boolean newIS = false;
+
+ if (is instanceof ArrayDataInput) {
+ dataStr = (ArrayDataInput) is;
+ } else {
+ dataStr = new BufferedDataInputStream(is);
+ }
+
+ read();
+
+ if (newIS) {
+ dataStr.close();
+ dataStr = null;
+ }
+
+ }
+
+ /** Get the current number of HDUs in the Fits object.
+ * @return The number of HDU's in the object.
+ * @deprecated See getNumberOfHDUs()
+ */
+ public int currentSize() {
+ return hduList.size();
+ }
+
+ /** Get the current number of HDUs in the Fits object.
+ * @return The number of HDU's in the object.
+ */
+ public int getNumberOfHDUs() {
+ return hduList.size();
+ }
+
+ /** Get the data stream used for the Fits Data.
+ * @return The associated data stream. Users may wish to
+ * call this function after opening a Fits object when
+ * they wish detailed control for writing some part of the FITS file.
+ */
+ public ArrayDataInput getStream() {
+ return dataStr;
+ }
+
+ /** Set the data stream to be used for future input.
+ *
+ * @param stream The data stream to be used.
+ */
+ public void setStream(ArrayDataInput stream) {
+ dataStr = stream;
+ atEOF = false;
+ lastFileOffset = -1;
+ }
+
+ /** Create an HDU from the given header.
+ * @param h The header which describes the FITS extension
+ */
+ public static BasicHDU makeHDU(Header h) throws FitsException {
+ Data d = FitsFactory.dataFactory(h);
+ return FitsFactory.HDUFactory(h, d);
+ }
+
+ /** Create an HDU from the given data kernel.
+ * @param o The data to be described in this HDU.
+ */
+ public static BasicHDU makeHDU(Object o) throws FitsException {
+ return FitsFactory.HDUFactory(o);
+ }
+
+ /** Create an HDU from the given Data.
+ * @param datum The data to be described in this HDU.
+ */
+ public static BasicHDU makeHDU(Data datum) throws FitsException {
+ Header hdr = new Header();
+ datum.fillHeader(hdr);
+ return FitsFactory.HDUFactory(hdr, datum);
+ }
+
+ /**
+ * Add or update the CHECKSUM keyword.
+ * @param hdr the primary or other header to get the current DATE
+ * @throws nom.tam.fits.HeaderCardException
+ * @author R J Mathar
+ * @since 2005-10-05
+ */
+ public static void setChecksum(BasicHDU hdu)
+ throws nom.tam.fits.HeaderCardException, nom.tam.fits.FitsException, java.io.IOException {
+ /* the next line with the delete is needed to avoid some unexpected
+ * problems with non.tam.fits.Header.checkCard() which otherwise says
+ * it expected PCOUNT and found DATE.
+ */
+ Header hdr = hdu.getHeader();
+ hdr.deleteKey("CHECKSUM");
+ /* This would need org.nevec.utils.DateUtils compiled before org.nevec.prima.fits ....
+ * final String doneAt = DateUtils.dateToISOstring(0) ;
+ * We need to save the value of the comment string because this is becoming part
+ * of the checksum calculated and needs to be re-inserted again - with the same string -
+ * when the second/final call to addValue() is made below.
+ */
+ final String doneAt = HeaderCommentsMap.getComment("fits:checksum:1");
+ hdr.addValue("CHECKSUM", "0000000000000000", doneAt);
+
+ /* Convert the entire sequence of 2880 byte header cards into a byte array.
+ * The main benefit compared to the C implementations is that we do not need to worry
+ * about the particular byte order on machines (Linux/VAX/MIPS vs Hp-UX, Sparc...) supposed that
+ * the correct implementation is in the write() interface.
+ */
+ ByteArrayOutputStream hduByteImage = new ByteArrayOutputStream();
+ System.err.flush();
+ hdu.write(new BufferedDataOutputStream(hduByteImage));
+ final byte[] data = hduByteImage.toByteArray();
+ final long csu = checksum(data);
+
+ /* This time we do not use a deleteKey() to ensure that the keyword is replaced "in place".
+ * Note that the value of the checksum is actually independent to a permutation of the
+ * 80-byte records within the header.
+ */
+ hdr.addValue("CHECKSUM", checksumEnc(csu, true), doneAt);
+ }
+
+ /**
+ * Add or Modify the CHECKSUM keyword in all headers.
+ * @throws nom.tam.fits.HeaderCardException
+ * @throws nom.tam.fits.FitsException
+ * @author R J Mathar
+ * @since 2005-10-05
+ */
+ public void setChecksum()
+ throws nom.tam.fits.HeaderCardException, nom.tam.fits.FitsException, java.io.IOException {
+ for (int i = 0; i < getNumberOfHDUs(); i += 1) {
+ setChecksum(getHDU(i));
+ }
+ }
+
+ /**
+ * Calculate the Seaman-Pence 32-bit 1's complement checksum over the byte stream. The option
+ * to start from an intermediate checksum accumulated over another previous
+ * byte stream is not implemented.
+ * The implementation accumulates in two 64-bit integer values the two low-order and the two
+ * high-order bytes of adjacent 4-byte groups. A carry-over of bits is never done within the main
+ * loop (only once at the end at reduction to a 32-bit positive integer) since an overflow
+ * of a 64-bit value (signed, with maximum at 2^63-1) by summation of 16-bit values could only
+ * occur after adding approximately 140G short values (=2^47) (280GBytes) or more. We assume
+ * for now that this routine here is never called to swallow FITS files of that size or larger.
+ * @param data the byte sequence
+ * @return the 32bit checksum in the range from 0 to 2^32-1
+ * @see http://heasarc.gsfc.nasa.gov/docs/heasarc/fits/checksum.html
+ * @author R J Mathar
+ * @since 2005-10-05
+ */
+ private static long checksum(final byte[] data) {
+ long hi = 0;
+ long lo = 0;
+ final int len = 2 * (data.length / 4);
+ // System.out.println(data.length + " bytes") ;
+ final int remain = data.length % 4;
+ /* a write(2) on Sparc/PA-RISC would write the MSB first, on Linux the LSB; by some kind
+ * of coincidence, we can stay with the byte order known from the original C version of
+ * the algorithm.
+ */
+ for (int i = 0; i < len; i += 2) {
+ /* The four bytes in this block handled by a single 'i' are each signed (-128 to 127)
+ * in Java and need to be masked indivdually to avoid sign extension /propagation.
+ */
+ hi += (data[2 * i] << 8) & 0xff00L | data[2 * i + 1] & 0xffL;
+ lo += (data[2 * i + 2] << 8) & 0xff00L | data[2 * i + 3] & 0xffL;
+ }
+
+ /* The following three cases actually cannot happen since FITS records are multiples of 2880 bytes.
+ */
+ if (remain >= 1) {
+ hi += (data[2 * len] << 8) & 0xff00L;
+ }
+ if (remain >= 2) {
+ hi += data[2 * len + 1] & 0xffL;
+ }
+ if (remain >= 3) {
+ lo += (data[2 * len + 2] << 8) & 0xff00L;
+ }
+
+ long hicarry = hi >>> 16;
+ long locarry = lo >>> 16;
+ while (hicarry != 0 || locarry != 0) {
+ hi = (hi & 0xffffL) + locarry;
+ lo = (lo & 0xffffL) + hicarry;
+ hicarry = hi >>> 16;
+ locarry = lo >>> 16;
+ }
+ return (hi << 16) + lo;
+ }
+
+ /**
+ * Encode a 32bit integer according to the Seaman-Pence proposal.
+ * @param c the checksum previously calculated
+ * @return the encoded string of 16 bytes.
+ * @see http://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/checksum/node14.html#SECTION00035000000000000000
+ * @author R J Mathar
+ * @since 2005-10-05
+ */
+ private static String checksumEnc(final long c, final boolean compl) {
+ byte[] asc = new byte[16];
+ final int[] exclude = {0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60};
+ final long[] mask = {0xff000000L, 0xff0000L, 0xff00L, 0xffL};
+ final int offset = 0x30; /* ASCII 0 (zero */
+ final long value = compl ? ~c : c;
+ for (int i = 0; i < 4; i++) {
+ final int byt = (int) ((value & mask[i]) >>> (24 - 8 * i)); // each byte becomes four
+ final int quotient = byt / 4 + offset;
+ final int remainder = byt % 4;
+ int[] ch = new int[4];
+ for (int j = 0; j < 4; j++) {
+ ch[j] = quotient;
+ }
+
+ ch[0] += remainder;
+ boolean check = true;
+ for (; check;) // avoid ASCII punctuation
+ {
+ check = false;
+ for (int k = 0; k < exclude.length; k++) {
+ for (int j = 0; j < 4; j += 2) {
+ if (ch[j] == exclude[k] || ch[j + 1] == exclude[k]) {
+ ch[j]++;
+ ch[j + 1]--;
+ check = true;
+ }
+ }
+ }
+ }
+
+ for (int j = 0; j < 4; j++) // assign the bytes
+ {
+ asc[4 * j + i] = (byte) (ch[j]);
+ }
+ }
+
+ // shift the bytes 1 to the right circularly.
+ try {
+ String resul = AsciiFuncs.asciiString(asc, 15, 1);
+ return resul.concat(AsciiFuncs.asciiString(asc, 0, 15));
+ } catch (Exception e) {
+ // Impossible I hope
+ System.err.println("CheckSum Error finding ASCII encoding");
+ return null;
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * This class was contributed by D. Glowacki.
+ */
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.text.DecimalFormat;
+
+public class FitsDate {
+
+ private int year = -1;
+ private int month = -1;
+ private int mday = -1;
+ private int hour = -1;
+ private int minute = -1;
+ private int second = -1;
+ private int millisecond = -1;
+ private Date date = null;
+
+ /**
+ * Convert a FITS date string to a Java <CODE>Date</CODE> object.
+ * @param dStr the FITS date
+ * @return either <CODE>null</CODE> or a Date object
+ * @exception FitsException if <CODE>dStr</CODE> does not
+ * contain a valid FITS date.
+ */
+ public FitsDate(String dStr)
+ throws FitsException {
+ // if the date string is null, we are done
+ if (dStr == null) {
+ return;
+ }
+
+ // if the date string is empty, we are done
+ dStr = dStr.trim();
+ if (dStr.length() == 0) {
+ return;
+ }
+
+ // if string contains at least 8 characters...
+ int len = dStr.length();
+ if (len >= 8) {
+ int first;
+
+ // ... and there is a "/" in the string...
+ first = dStr.indexOf('-');
+ if (first == 4 && first < len) {
+
+ // ... this must be an new-style date
+ buildNewDate(dStr, first, len);
+
+ // no "/" found; maybe it is an old-style date...
+ } else {
+
+ first = dStr.indexOf('/');
+ if (first > 1 && first < len) {
+
+ // ... this must be an old-style date
+ buildOldDate(dStr, first, len);
+ }
+ }
+ }
+
+ if (year == -1) {
+ throw new FitsException("Bad FITS date string \"" + dStr + '"');
+ }
+ }
+
+ private void buildOldDate(String dStr, int first, int len) {
+ int middle = dStr.indexOf('/', first + 1);
+ if (middle > first + 2 && middle < len) {
+
+ try {
+
+ year = Integer.parseInt(dStr.substring(middle + 1)) + 1900;
+ month = Integer.parseInt(dStr.substring(first + 1, middle));
+ mday = Integer.parseInt(dStr.substring(0, first));
+
+ } catch (NumberFormatException e) {
+
+ year = month = mday = -1;
+ }
+ }
+ }
+
+ private void parseTime(String tStr)
+ throws FitsException {
+ int first = tStr.indexOf(':');
+ if (first < 0) {
+ throw new FitsException("Bad time");
+ }
+
+ int len = tStr.length();
+
+ int middle = tStr.indexOf(':', first + 1);
+ if (middle > first + 2 && middle < len) {
+
+ if (middle + 3 < len && tStr.charAt(middle + 3) == '.') {
+ double d = Double.valueOf(tStr.substring(middle + 3)).doubleValue();
+ millisecond = (int) (d * 1000);
+
+ len = middle + 3;
+ }
+
+ try {
+ hour = Integer.parseInt(tStr.substring(0, first));
+ minute = Integer.parseInt(tStr.substring(first + 1, middle));
+ second = Integer.parseInt(tStr.substring(middle + 1, len));
+ } catch (NumberFormatException e) {
+ hour = minute = second = millisecond = -1;
+ }
+ }
+ }
+
+ private void buildNewDate(String dStr, int first, int len)
+ throws FitsException {
+ // find the middle separator
+ int middle = dStr.indexOf('-', first + 1);
+ if (middle > first + 2 && middle < len) {
+
+ try {
+
+ // if this date string includes a time...
+ if (middle + 3 < len && dStr.charAt(middle + 3) == 'T') {
+
+ // ... try to parse the time
+ try {
+ parseTime(dStr.substring(middle + 4));
+ } catch (FitsException e) {
+ throw new FitsException("Bad time in FITS date string \""
+ + dStr + "\"");
+ }
+
+ // we got the time; mark the end of the date string
+ len = middle + 3;
+ }
+
+ // parse date string
+ year = Integer.parseInt(dStr.substring(0, first));
+ month = Integer.parseInt(dStr.substring(first + 1, middle));
+ mday = Integer.parseInt(dStr.substring(middle + 1, len));
+
+ } catch (NumberFormatException e) {
+
+ // yikes, something failed; reset everything
+ year = month = mday = hour = minute = second = millisecond = -1;
+ }
+ }
+ }
+
+ /** Get a Java Date object corresponding to this
+ * FITS date.
+ * @return The Java Date object.
+ */
+ public Date toDate() {
+ if (date == null && year != -1) {
+ TimeZone tz = TimeZone.getTimeZone("GMT");
+ GregorianCalendar cal = new GregorianCalendar(tz);
+
+ cal.set(Calendar.YEAR, year);
+ cal.set(Calendar.MONTH, month - 1);
+ cal.set(Calendar.DAY_OF_MONTH, mday);
+
+ if (hour == -1) {
+
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+
+ } else {
+
+ cal.set(Calendar.HOUR_OF_DAY, hour);
+ cal.set(Calendar.MINUTE, minute);
+ cal.set(Calendar.SECOND, second);
+ if (millisecond == -1) {
+ cal.set(Calendar.MILLISECOND, 0);
+ } else {
+ cal.set(Calendar.MILLISECOND, millisecond);
+ }
+ }
+
+ date = cal.getTime();
+ }
+
+ return date;
+ }
+
+ /** Return the current date in FITS date format */
+ public static String getFitsDateString() {
+ return getFitsDateString(new Date(), true);
+ }
+
+ /** Create FITS format date string Java Date object.
+ * @param epoch The epoch to be converted to FITS format.
+ */
+ public static String getFitsDateString(Date epoch) {
+ return getFitsDateString(epoch, true);
+ }
+
+ /** Create FITS format date string.
+ * Note that the date is not rounded.
+ * @param epoch The epoch to be converted to FITS format.
+ * @param timeOfDay Should time of day information be included?
+ */
+ public static String getFitsDateString(Date epoch, boolean timeOfDay) {
+
+ try {
+ GregorianCalendar cal = new GregorianCalendar(
+ TimeZone.getTimeZone("GMT"));
+
+
+ cal.setTime(epoch);
+
+ StringBuffer fitsDate = new StringBuffer();
+ DecimalFormat df = new DecimalFormat("0000");
+ fitsDate.append(df.format(cal.get(Calendar.YEAR)));
+ fitsDate.append("-");
+ df = new DecimalFormat("00");
+
+ fitsDate.append(df.format(cal.get(Calendar.MONTH) + 1));
+ fitsDate.append("-");
+ fitsDate.append(df.format(cal.get(Calendar.DAY_OF_MONTH)));
+
+ if (timeOfDay) {
+ fitsDate.append("T");
+ fitsDate.append(df.format(cal.get(Calendar.HOUR_OF_DAY)));
+ fitsDate.append(":");
+ fitsDate.append(df.format(cal.get(Calendar.MINUTE)));
+ fitsDate.append(":");
+ fitsDate.append(df.format(cal.get(Calendar.SECOND)));
+ fitsDate.append(".");
+ df = new DecimalFormat("000");
+ fitsDate.append(df.format(cal.get(Calendar.MILLISECOND)));
+ }
+
+ return new String(fitsDate);
+
+ } catch (Exception e) {
+
+ return new String("");
+ }
+ }
+
+ public String toString() {
+ if (year == -1) {
+ return "";
+ }
+
+ StringBuffer buf = new StringBuffer(23);
+ buf.append(year);
+ buf.append('-');
+ if (month < 10) {
+ buf.append('0');
+ }
+ buf.append(month);
+ buf.append('-');
+ if (mday < 10) {
+ buf.append('0');
+ }
+ buf.append(mday);
+
+ if (hour != -1) {
+
+ buf.append('T');
+ if (hour < 10) {
+ buf.append('0');
+ }
+
+ buf.append(hour);
+ buf.append(':');
+
+ if (minute < 10) {
+ buf.append('0');
+ }
+
+ buf.append(minute);
+ buf.append(':');
+
+ if (second < 10) {
+ buf.append('0');
+ }
+ buf.append(second);
+
+ if (millisecond != -1) {
+ buf.append('.');
+
+ if (millisecond < 100) {
+ if (millisecond < 10) {
+ buf.append("00");
+ } else {
+ buf.append('0');
+ }
+ }
+ buf.append(millisecond);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ public static void testArgs(String args[]) {
+ for (int i = 0; i < args.length; i++) {
+
+ try {
+ FitsDate fd = new FitsDate(args[i]);
+ System.out.println("\"" + args[i] + "\" => " + fd + " => "
+ + fd.toDate());
+ } catch (Exception e) {
+ System.err.println("Date \"" + args[i] + "\" threw "
+ + e.getClass().getName() + "(" + e.getMessage()
+ + ")");
+ }
+ }
+ }
+
+ public static void autotest() {
+ String[] good = new String[6];
+ good[0] = "20/09/79";
+ good[1] = "1997-07-25";
+ good[2] = "1987-06-05T04:03:02.01";
+ good[3] = "1998-03-10T16:58:34";
+ good[4] = null;
+ good[5] = " ";
+ testArgs(good);
+
+ String[] badOld = new String[4];
+ badOld[0] = "20/09/";
+ badOld[1] = "/09/79";
+ badOld[2] = "09//79";
+ badOld[3] = "20/09/79/";
+ testArgs(badOld);
+
+ String[] badNew = new String[4];
+ badNew[0] = "1997-07";
+ badNew[1] = "-07-25";
+ badNew[2] = "1997--07-25";
+ badNew[3] = "1997-07-25-";
+ testArgs(badNew);
+
+ String[] badMisc = new String[4];
+ badMisc[0] = "5-Aug-1992";
+ badMisc[1] = "28/02/91 16:32:00";
+ badMisc[2] = "18-Feb-1993";
+ badMisc[3] = "nn/nn/nn";
+ testArgs(badMisc);
+ }
+
+ public static void main(String args[]) {
+ if (args.length == 0) {
+ autotest();
+ } else {
+ testArgs(args);
+ }
+ }
+}
--- /dev/null
+/** This inteface describes allows uses to easily perform
+ * basic I/O operations
+ * on a FITS element.
+ */
+package nom.tam.fits;
+
+import nom.tam.util.*;
+import java.io.IOException;
+
+public interface FitsElement {
+
+ /** Read the contents of the element from an input source.
+ * @param in The input source.
+ */
+ public void read(ArrayDataInput in) throws FitsException, IOException;
+
+ /** Write the contents of the element to a data sink.
+ * @param out The data sink.
+ */
+ public void write(ArrayDataOutput out) throws FitsException, IOException;
+
+ /** Rewrite the contents of the element in place.
+ * The data must have been orignally read from a random
+ * access device, and the size of the element may not have changed.
+ */
+ public void rewrite() throws FitsException, IOException;
+
+ /** Get the byte at which this element begins.
+ * This is only available if the data is originally read from
+ * a random access medium.
+ */
+ public long getFileOffset();
+
+ /** Can this element be rewritten? */
+ public boolean rewriteable();
+
+ /** The size of this element in bytes */
+ public long getSize();
+
+ /** Reset the input stream to point to the beginning of this element
+ * @return True if the reset succeeded.
+ */
+ public boolean reset();
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+public class FitsException extends Exception {
+
+ public FitsException() {
+ super();
+ }
+
+ public FitsException(String msg) {
+ super(msg);
+ }
+
+ public FitsException(String msg, Exception reason) {
+ super(msg, reason);
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/** This class contains the code which
+ * associates particular FITS types with header
+ * and data configurations. It comprises
+ * a set of Factory methods which call
+ * appropriate methods in the HDU classes.
+ * If -- God forbid -- a new FITS HDU type were
+ * created, then the XXHDU, XXData classes would
+ * need to be added and this file modified but
+ * no other changes should be needed in the FITS libraries.
+ *
+ */
+public class FitsFactory {
+
+ private static boolean useAsciiTables = true;
+ private static boolean useHierarch = false;
+ private static boolean checkAsciiStrings = false;
+
+ /** Indicate whether ASCII tables should be used
+ * where feasible.
+ */
+ public static void setUseAsciiTables(boolean flag) {
+ useAsciiTables = flag;
+ }
+
+ /** Get the current status of ASCII table writing */
+ static boolean getUseAsciiTables() {
+ return useAsciiTables;
+ }
+
+ /** Enable/Disable hierarchical keyword processing. */
+ public static void setUseHierarch(boolean flag) {
+ useHierarch = flag;
+ }
+
+ /** Enable/Disable checking of strings values used in tables
+ * to ensure that they are within the range specified by the
+ * FITS standard. The standard only allows the values 0x20 - 0x7E
+ * with null bytes allowed in one limited context.
+ * Disabled by default.
+ */
+ public static void setCheckAsciiStrings(boolean flag) {
+ checkAsciiStrings = flag;
+ }
+
+ /** Get the current status for string checking. */
+ static boolean getCheckAsciiStrings() {
+ return checkAsciiStrings;
+ }
+
+ /** Are we processing HIERARCH style keywords */
+ public static boolean getUseHierarch() {
+ return useHierarch;
+ }
+
+ /** Given a Header return an appropriate datum.
+ */
+ public static Data dataFactory(Header hdr) throws FitsException {
+
+ if (ImageHDU.isHeader(hdr)) {
+ Data d = ImageHDU.manufactureData(hdr);
+ hdr.afterExtend(); // Fix for positioning error noted by V. Forchi
+ return d;
+ } else if (RandomGroupsHDU.isHeader(hdr)) {
+ return RandomGroupsHDU.manufactureData(hdr);
+ } else if (useAsciiTables && AsciiTableHDU.isHeader(hdr)) {
+ return AsciiTableHDU.manufactureData(hdr);
+ } else if (BinaryTableHDU.isHeader(hdr)) {
+ return BinaryTableHDU.manufactureData(hdr);
+ } else if (UndefinedHDU.isHeader(hdr)) {
+ return UndefinedHDU.manufactureData(hdr);
+ } else {
+ throw new FitsException("Unrecognizable header in dataFactory");
+ }
+
+ }
+
+ /** Given an object, create the appropriate
+ * FITS header to describe it.
+ * @param o The object to be described.
+ */
+ public static BasicHDU HDUFactory(Object o) throws FitsException {
+ Data d;
+ Header h;
+
+ if (o instanceof Header) {
+ h = (Header) o;
+ d = dataFactory(h);
+
+ } else if (ImageHDU.isData(o)) {
+ d = ImageHDU.encapsulate(o);
+ h = ImageHDU.manufactureHeader(d);
+ } else if (RandomGroupsHDU.isData(o)) {
+ d = RandomGroupsHDU.encapsulate(o);
+ h = RandomGroupsHDU.manufactureHeader(d);
+ } else if (useAsciiTables && AsciiTableHDU.isData(o)) {
+ d = AsciiTableHDU.encapsulate(o);
+ h = AsciiTableHDU.manufactureHeader(d);
+ } else if (BinaryTableHDU.isData(o)) {
+ d = BinaryTableHDU.encapsulate(o);
+ h = BinaryTableHDU.manufactureHeader(d);
+ } else if (UndefinedHDU.isData(o)) {
+ d = UndefinedHDU.encapsulate(o);
+ h = UndefinedHDU.manufactureHeader(d);
+ } else {
+ throw new FitsException("Invalid data presented to HDUFactory");
+ }
+
+ return HDUFactory(h, d);
+
+ }
+
+ /** Given Header and data objects return
+ * the appropriate type of HDU.
+ */
+ public static BasicHDU HDUFactory(Header hdr, Data d) throws
+ FitsException {
+
+ if (d instanceof ImageData) {
+ return new ImageHDU(hdr, d);
+ } else if (d instanceof RandomGroupsData) {
+ return new RandomGroupsHDU(hdr, d);
+ } else if (d instanceof AsciiTable) {
+ return new AsciiTableHDU(hdr, d);
+ } else if (d instanceof BinaryTable) {
+ return new BinaryTableHDU(hdr, d);
+ } else if (d instanceof UndefinedData) {
+ return new UndefinedHDU(hdr, d);
+ }
+
+ return null;
+ }
+}
+
+
--- /dev/null
+package nom.tam.fits;
+
+import nom.tam.util.*;
+import java.io.*;
+
+/** This class supports the FITS heap. This
+ * is currently used for variable length columns
+ * in binary tables.
+ */
+public class FitsHeap implements FitsElement {
+
+ /** The storage buffer */
+ private byte[] heap;
+ /** The current used size of the buffer <= heap.length */
+ private int heapSize;
+ /** The offset within a file where the heap begins */
+ private long fileOffset = -1;
+ /** Has the heap ever been expanded? */
+ private boolean expanded = false;
+ /** The stream the last read used */
+ private ArrayDataInput input;
+ /** Our current offset into the heap. When we read from
+ * the heap we use a byte array input stream. So long
+ * as we continue to read further into the heap, we can
+ * continue to use the same stream, but we need to
+ * recreate the stream whenever we skip backwards.
+ */
+ private int heapOffset = 0;
+ /** A stream used to read the heap data */
+ private BufferedDataInputStream bstr;
+
+ /** Create a heap of a given size. */
+ FitsHeap(int size) {
+ heap = new byte[size];
+ heapSize = size;
+ }
+
+ /** Read the heap */
+ public void read(ArrayDataInput str) throws FitsException {
+
+ if (str instanceof RandomAccess) {
+ fileOffset = FitsUtil.findOffset(str);
+ input = str;
+ }
+
+ if (heap != null) {
+ try {
+ str.read(heap, 0, heapSize);
+ } catch (IOException e) {
+ throw new FitsException("Error reading heap:" + e);
+ }
+ }
+
+ bstr = null;
+ }
+
+ /** Write the heap */
+ public void write(ArrayDataOutput str) throws FitsException {
+ try {
+ str.write(heap, 0, heapSize);
+ } catch (IOException e) {
+ throw new FitsException("Error writing heap:" + e);
+ }
+ }
+
+ public boolean rewriteable() {
+ return fileOffset >= 0 && input instanceof ArrayDataOutput && !expanded;
+ }
+
+ /** Attempt to rewrite the heap with the current contents.
+ * Note that no checking is done to make sure that the
+ * heap does not extend past its prior boundaries.
+ */
+ public void rewrite() throws IOException, FitsException {
+ if (rewriteable()) {
+ ArrayDataOutput str = (ArrayDataOutput) input;
+ FitsUtil.reposition(str, fileOffset);
+ write(str);
+ } else {
+ throw new FitsException("Invalid attempt to rewrite FitsHeap");
+ }
+
+ }
+
+ public boolean reset() {
+ try {
+ FitsUtil.reposition(input, fileOffset);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /** Get data from the heap.
+ * @param offset The offset at which the data begins.
+ * @param array The array to be extracted.
+ */
+ public void getData(int offset, Object array) throws FitsException {
+
+ try {
+ // Can we reuse the existing byte stream?
+ if (bstr == null || heapOffset > offset) {
+ heapOffset = 0;
+ bstr = new BufferedDataInputStream(
+ new ByteArrayInputStream(heap));
+ }
+
+ bstr.skipBytes(offset - heapOffset);
+ heapOffset = offset;
+ heapOffset += bstr.readLArray(array);
+
+ } catch (IOException e) {
+ throw new FitsException("Error decoding heap area at offset=" + offset
+ + ". Exception: Exception " + e);
+ }
+ }
+
+ /** Check if the Heap can accommodate a given requirement.
+ * If not expand the heap.
+ */
+ void expandHeap(int need) {
+
+ // Invalidate any existing input stream to the heap.
+ bstr = null;
+
+ if (heapSize + need > heap.length) {
+ expanded = true;
+ int newlen = (heapSize + need) * 2;
+ if (newlen < 16384) {
+ newlen = 16384;
+ }
+ byte[] newHeap = new byte[newlen];
+ System.arraycopy(heap, 0, newHeap, 0, heapSize);
+ heap = newHeap;
+ }
+ }
+
+ /** Add some data to the heap. */
+ int putData(Object data) throws FitsException {
+
+ long lsize = ArrayFuncs.computeLSize(data);
+ if (lsize > Integer.MAX_VALUE) {
+ throw new FitsException("FITS Heap > 2 G");
+ }
+ int size = (int) lsize;
+ expandHeap(size);
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(size);
+
+ try {
+ BufferedDataOutputStream o = new BufferedDataOutputStream(bo);
+ o.writeArray(data);
+ o.flush();
+ o.close();
+ } catch (IOException e) {
+ throw new FitsException("Unable to write variable column length data");
+ }
+
+ System.arraycopy(bo.toByteArray(), 0, heap, heapSize, size);
+ int oldOffset = heapSize;
+ heapSize += size;
+
+ return oldOffset;
+ }
+
+ /** Return the size of the Heap */
+ public int size() {
+ return heapSize;
+ }
+
+ /** Return the size of the heap using the more bean compatbile format */
+ public long getSize() {
+ return size();
+ }
+
+ /** Get the file offset of the heap */
+ public long getFileOffset() {
+ return fileOffset;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+import nom.tam.util.RandomAccess;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+
+import java.net.URL;
+import java.net.URLConnection;
+
+import java.util.Map;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import nom.tam.util.ArrayDataOutput;
+import nom.tam.util.AsciiFuncs;
+
+/** This class comprises static
+ * utility functions used throughout
+ * the FITS classes.
+ */
+public class FitsUtil {
+
+ private static boolean wroteCheckingError = false;
+
+ /** Reposition a random access stream to a requested offset */
+ public static void reposition(Object o, long offset)
+ throws FitsException {
+
+ if (o == null) {
+ throw new FitsException("Attempt to reposition null stream");
+ }
+ if (!(o instanceof RandomAccess)
+ || offset < 0) {
+ throw new FitsException("Invalid attempt to reposition stream " + o
+ + " of type " + o.getClass().getName()
+ + " to " + offset);
+ }
+
+ try {
+ ((RandomAccess) o).seek(offset);
+ } catch (IOException e) {
+ throw new FitsException("Unable to repostion stream " + o
+ + " of type " + o.getClass().getName()
+ + " to " + offset + " Exception:" + e);
+ }
+ }
+
+ /** Find out where we are in a random access file */
+ public static long findOffset(Object o) {
+
+ if (o instanceof RandomAccess) {
+ return ((RandomAccess) o).getFilePointer();
+ } else {
+ return -1;
+ }
+ }
+
+ /** How many bytes are needed to fill the last 2880 block? */
+ public static int padding(int size) {
+ return padding((long) size);
+ }
+
+ public static int padding(long size) {
+
+ int mod = (int) (size % 2880);
+ if (mod > 0) {
+ mod = 2880 - mod;
+ }
+ return mod;
+ }
+
+ /** Total size of blocked FITS element */
+ public static int addPadding(int size) {
+ return size + padding(size);
+ }
+
+ public static long addPadding(long size) {
+ return size + padding(size);
+ }
+
+ /** This method decompresses a compressed
+ * input stream. The decompression method is
+ * selected automatically based upon the first two bytes read.
+ * @param compressed The compressed input stram
+ * @return A stream which wraps the input stream and decompresses
+ * it. If the input stream is not compressed, a
+ * pushback input stream wrapping the original stream is returned.
+ */
+ static InputStream decompress(InputStream compressed) throws FitsException {
+
+ PushbackInputStream pb = new PushbackInputStream(compressed, 2);
+
+ int mag1 = -1;
+ int mag2 = -1;
+
+ try {
+ mag1 = pb.read();
+ mag2 = pb.read();
+
+ if (mag1 == 0x1f && mag2 == 0x8b) {
+ // Push the data back into the stream
+ pb.unread(mag2);
+ pb.unread(mag1);
+ return new GZIPInputStream(pb);
+ } else if (mag1 == 0x1f && mag2 == 0x9d) {
+ // Push the data back into the stream
+ pb.unread(mag2);
+ pb.unread(mag1);
+ return compressInputStream(pb);
+ } else if (mag1 == 'B' && mag2 == 'Z') {
+ if (System.getenv("BZIP_DECOMPRESSOR") != null) {
+ pb.unread(mag2);
+ pb.unread(mag1);
+ return bunzipper(pb);
+ }
+ // Don't pushback
+ String cname = "org.apache.tools.bzip2.CBZip2InputStream";
+ // Note that we forego generics here since we don't
+ // want any explicit mention of this class so that users
+ // can compile and run without worrying about having the class in hand.
+ try {
+ Constructor con = Class.forName(cname).getConstructor(InputStream.class);
+ return (InputStream) con.newInstance(pb);
+ } catch (Exception e) {
+ System.err.println("Unable to find constructor for BZIP2 decompression. Is the Apache BZIP jar in the classpath?");
+ throw new FitsException("No CBZip2InputStream class found for bzip2 compressed file");
+ }
+ } else {
+ // Push the data back into the stream
+ pb.unread(mag2);
+ pb.unread(mag1);
+ return pb;
+ }
+
+ } catch (IOException e) {
+ // This is probably a prelude to failure...
+ throw new FitsException("Unable to analyze input stream");
+ }
+ }
+
+ static InputStream compressInputStream(final InputStream compressed) throws FitsException {
+ try {
+ Process proc = new ProcessBuilder("uncompress", "-c").start();
+
+ // This is the input to the process -- but
+ // an output from here.
+ final OutputStream input = proc.getOutputStream();
+
+ // Now copy everything in a separate thread.
+ Thread copier = new Thread(
+ new Runnable() {
+
+ public void run() {
+ try {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = compressed.read(buffer, 0, buffer.length)) > 0) {
+ input.write(buffer, 0, len);
+ }
+ compressed.close();
+ input.close();
+ } catch (IOException e) {
+ return;
+ }
+ }
+ });
+ copier.start();
+ return proc.getInputStream();
+ } catch (Exception e) {
+ throw new FitsException("Unable to read .Z compressed stream.\nIs `uncompress' in the path?\n:" + e);
+ }
+ }
+
+ /** Is a file compressed? */
+ public static boolean isCompressed(File test) {
+ InputStream fis = null;
+ try {
+ if (test.exists()) {
+ fis = new FileInputStream(test);
+ int mag1 = fis.read();
+ int mag2 = fis.read();
+ fis.close();
+ if (mag1 == 0x1f && (mag2 == 0x8b || mag2 == 0x9d)) {
+ return true;
+ } else if (mag1 == 'B' && mag2 == 'Z') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ } catch (IOException e) {
+ // This is probably a prelude to failure...
+ return false;
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Check if a file seems to be compressed.
+ */
+ public static boolean isCompressed(String filename) {
+ if (filename == null) {
+ return false;
+ }
+ FileInputStream fis = null;
+ File test = new File(filename);
+ if (test.exists()) {
+ return isCompressed(test);
+ }
+
+ int len = filename.length();
+ return len > 2 && (filename.substring(len - 3).equalsIgnoreCase(".gz") || filename.substring(len - 2).equals(".Z"));
+ }
+
+ /** Get the maximum length of a String in a String array.
+ */
+ public static int maxLength(String[] o) throws FitsException {
+
+ int max = 0;
+ for (int i = 0; i < o.length; i += 1) {
+ if (o[i] != null && o[i].length() > max) {
+ max = o[i].length();
+ }
+ }
+ return max;
+ }
+
+ /** Copy an array of Strings to bytes.*/
+ public static byte[] stringsToByteArray(String[] o, int maxLen) {
+ byte[] res = new byte[o.length * maxLen];
+ for (int i = 0; i < o.length; i += 1) {
+ byte[] bstr = null;
+ if (o[i] == null) {
+ bstr = new byte[0];
+ } else {
+ bstr = AsciiFuncs.getBytes(o[i]);
+ }
+ int cnt = bstr.length;
+ if (cnt > maxLen) {
+ cnt = maxLen;
+ }
+ System.arraycopy(bstr, 0, res, i * maxLen, cnt);
+ for (int j = cnt; j < maxLen; j += 1) {
+ res[i * maxLen + j] = (byte) ' ';
+ }
+ }
+ return res;
+ }
+
+ /** Convert bytes to Strings */
+ public static String[] byteArrayToStrings(byte[] o, int maxLen) {
+ boolean checking = FitsFactory.getCheckAsciiStrings();
+
+ // Note that if a String in a binary table contains an internal 0,
+ // the FITS standard says that it is to be considered as terminating
+ // the string at that point, so that software reading the
+ // data back may not include subsequent characters.
+ // No warning of this truncation is given.
+
+ String[] res = new String[o.length / maxLen];
+ for (int i = 0; i < res.length; i += 1) {
+
+ int start = i * maxLen;
+ int end = start + maxLen;
+ // Pre-trim the string to avoid keeping memory
+ // hanging around. (Suggested by J.C. Segovia, ESA).
+
+ // Note that the FITS standard does not mandate
+ // that we should be trimming the string at all, but
+ // this seems to best meet the desires of the community.
+ for (; start < end; start += 1) {
+ if (o[start] != 32) {
+ break; // Skip only spaces.
+ }
+ }
+
+ for (; end > start; end -= 1) {
+ if (o[end - 1] != 32) {
+ break;
+ }
+ }
+
+ // For FITS binary tables, 0 values are supposed
+ // to terminate strings, a la C. [They shouldn't appear in
+ // any other context.]
+ // Other non-printing ASCII characters
+ // should always be an error which we can check for
+ // if the user requests.
+
+ // The lack of handling of null bytes was noted by Laurent Bourges.
+ boolean errFound = false;
+ for (int j = start; j < end; j += 1) {
+
+ if (o[j] == 0) {
+ end = j;
+ break;
+ }
+ if (checking) {
+ if (o[j] < 32 || o[j] > 126) {
+ errFound = true;
+ o[j] = 32;
+ }
+ }
+ }
+ res[i] = AsciiFuncs.asciiString(o, start, end - start);
+ if (errFound && !wroteCheckingError) {
+ System.err.println("Warning: Invalid ASCII character[s] detected in string:" + res[i]);
+ System.err.println(" Converted to space[s]. Any subsequent invalid characters will be converted silently");
+ wroteCheckingError = true;
+ }
+ }
+ return res;
+
+ }
+
+ /** Convert an array of booleans to bytes */
+ static byte[] booleanToByte(boolean[] bool) {
+
+ byte[] byt = new byte[bool.length];
+ for (int i = 0; i < bool.length; i += 1) {
+ byt[i] = bool[i] ? (byte) 'T' : (byte) 'F';
+ }
+ return byt;
+ }
+
+ /** Convert an array of bytes to booleans */
+ static boolean[] byteToBoolean(byte[] byt) {
+ boolean[] bool = new boolean[byt.length];
+
+
+
+
+ for (int i = 0; i < byt.length; i += 1) {
+ bool[i] = (byt[i] == 'T');
+ }
+ return bool;
+ }
+
+ /** Get a stream to a URL accommodating possible redirections.
+ * Note that if a redirection request points to a different
+ * protocol than the original request, then the redirection
+ * is not handled automatically.
+ */
+ public static InputStream getURLStream(URL url, int level) throws IOException {
+
+ // Hard coded....sigh
+ if (level > 5) {
+ throw new IOException("Two many levels of redirection in URL");
+ }
+ URLConnection conn = url.openConnection();
+// Map<String,List<String>> hdrs = conn.getHeaderFields();
+ Map hdrs = conn.getHeaderFields();
+
+ // Read through the headers and see if there is a redirection header.
+ // We loop (rather than just do a get on hdrs)
+ // since we want to match without regard to case.
+ String[] keys = (String[]) hdrs.keySet().toArray(new String[0]);
+// for (String key: hdrs.keySet()) {
+ for (int i = 0; i < keys.length; i += 1) {
+ String key = keys[i];
+
+ if (key != null && key.toLowerCase().equals("location")) {
+// String val = hdrs.get(key).get(0);
+ String val = (String) ((List) hdrs.get(key)).get(0);
+ if (val != null) {
+ val = val.trim();
+ if (val.length() > 0) {
+ // Redirect
+ return getURLStream(new URL(val), level + 1);
+ }
+ }
+ }
+ }
+ // No redirection
+ return conn.getInputStream();
+ }
+
+ /** Add padding to an output stream. */
+ public static void pad(ArrayDataOutput stream, long size) throws FitsException {
+ pad(stream, size, (byte) 0);
+ }
+
+ /** Add padding to an output stream. */
+ public static void pad(ArrayDataOutput stream, long size, byte fill)
+ throws FitsException {
+ int len = padding(size);
+ if (len > 0) {
+ byte[] buf = new byte[len];
+ for (int i = 0; i < len; i += 1) {
+ buf[i] = fill;
+ }
+ try {
+ stream.write(buf);
+ stream.flush();
+ } catch (Exception e) {
+ throw new FitsException("Unable to write padding", e);
+ }
+ }
+ }
+
+ static InputStream bunzipper(final InputStream pb) throws FitsException {
+ String cmd = System.getenv("BZIP_DECOMPRESSOR");
+ // Allow the user to have already specified the - option.
+ if (cmd.indexOf(" -") < 0) {
+ cmd += " -";
+ }
+ final OutputStream out;
+ String[] flds = cmd.split(" +");
+ Thread t;
+ Process p;
+ try {
+ p = new ProcessBuilder(flds).start();
+ out = p.getOutputStream();
+
+ t = new Thread(new Runnable() {
+
+ public void run() {
+ try {
+ byte[] buf = new byte[16384];
+ int len;
+ long total = 0;
+ while ((len = pb.read(buf)) > 0) {
+ try {
+ out.write(buf, 0, len);
+ } catch (Exception e) {
+ // Skip this. It can happen when we
+ // stop reading the compressed file in mid stream.
+ break;
+ }
+ total += len;
+ }
+ pb.close();
+ out.close();
+
+ } catch (IOException e) {
+ throw new Error("Error reading BZIP compression using: " + System.getenv("BZIP_DECOMPRESSOR"), e);
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ throw new FitsException("Error initiating BZIP decompression: " + e);
+ }
+ t.start();
+ return new CloseIS(p.getInputStream(), pb, out);
+
+ }
+}
+
+class CloseIS extends FilterInputStream {
+
+ InputStream i;
+ OutputStream o;
+
+ CloseIS(InputStream inp, InputStream i, OutputStream o) {
+ super(inp);
+ this.i = i;
+ this.o = o;
+ }
+
+ public void close() throws IOException {
+ super.close();
+ o.close();
+ i.close();
+ }
+}
+
--- /dev/null
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+import java.io.*;
+import java.util.*;
+import nom.tam.util.RandomAccess;
+import nom.tam.util.*;
+
+/** This class describes methods to access and manipulate the header
+ * for a FITS HDU. This class does not include code specific
+ * to particular types of HDU.
+ *
+ * As of version 1.1 this class supports the long keyword convention
+ * which allows long string keyword values to be split among multiple
+ * keywords
+ * <pre>
+ * KEY = 'ABC&' /A comment
+ * CONTINUE 'DEF&' / Another comment
+ * CONTINUE 'GHIJKL '
+ * </pre>
+ * The methods getStringValue(key), addValue(key,value,comment)
+ * and deleteCard(key) will get, create/update and delete long string
+ * values if the longStringsEnabled flag is set. This flag is set
+ * automatically when a FITS header with a LONGSTRN card is found.
+ * The value is not checked. It may also be set/unset using the
+ * static method setLongStringsEnabled(boolean). [So if a user wishes to ensure
+ * that it is not set, it should be unset after any header is read]
+ * When long strings are found in the FITS header users should be careful not to interpose
+ * new header cards within a long value sequence.
+ *
+ * When writing long strings, the comment is included in the last
+ * card. If a user is writing long strings, a the keyword
+ * LONGSTRN = 'OGIP 1.0'
+ * should be added to the FITS header, but this is not done automatically
+ * for the user.
+ */
+public class Header implements FitsElement {
+
+ /** The actual header data stored as a HashedList of
+ * HeaderCard's.
+ */
+ private HashedList cards = new HashedList();
+ /** This iterator allows one to run through the list.
+ */
+ private Cursor iter = cards.iterator(0);
+ /** Offset of this Header in the FITS file */
+ private long fileOffset = -1;
+ /** Number of cards in header last time it was read */
+ private int oldSize;
+ /** Input descriptor last time header was read */
+ private ArrayDataInput input;
+
+ /** Create an empty header */
+ public Header() {
+ }
+ /** Do we support long strings when reading/writing keywords */
+ private static boolean longStringsEnabled = false;
+
+ public static void setLongStringsEnabled(boolean flag) {
+ longStringsEnabled = flag;
+ }
+
+ public static boolean getLongStringsEnabled() {
+ return longStringsEnabled;
+ }
+
+ /** Create a header and populate it from the input stream
+ * @param is The input stream where header information is expected.
+ */
+ public Header(ArrayDataInput is)
+ throws TruncatedFileException, IOException {
+ read(is);
+ }
+
+ /** Create a header and initialize it with a vector of strings.
+ * @param newCards Card images to be placed in the header.
+ */
+ public Header(String[] newCards) {
+
+ for (int i = 0; i < newCards.length; i += 1) {
+ HeaderCard card = new HeaderCard(newCards[i]);
+ if (card.getValue() == null) {
+ cards.add(card);
+ } else {
+ cards.add(card.getKey(), card);
+ }
+
+ }
+ }
+
+ /** Create a header which points to the
+ * given data object.
+ * @param o The data object to be described.
+ * @exception FitsException if the data was not valid for this header.
+ */
+ public Header(Data o) throws FitsException {
+ o.fillHeader(this);
+ }
+
+ /** Create the data element corresponding to the current header */
+ public Data makeData() throws FitsException {
+ return FitsFactory.dataFactory(this);
+ }
+
+ /**
+ * Update a line in the header
+ * @param key The key of the card to be replaced.
+ * @param card A new card
+ */
+ public void updateLine(String key, HeaderCard card) throws HeaderCardException {
+ removeCard(key);
+ iter.add(key, card);
+ }
+
+ /**
+ * Overwrite the lines in the header.
+ * Add the new PHDU header to the current one. If keywords appear
+ * twice, the new value and comment overwrite the current contents.
+ *
+ * @param newHdr the list of new header data lines to replace the current
+ * ones.
+ * @throws nom.tam.fits.HeaderCardException
+ * @author Richard J Mathar
+ * @since 2005-10-24
+ */
+ public void updateLines(final Header newHdr) throws nom.tam.fits.HeaderCardException {
+ Cursor j = newHdr.iterator();
+
+ while (j.hasNext()) {
+ HeaderCard nextHCard = (HeaderCard) j.next();
+ // updateLine() doesn't work with COMMENTs because
+ // this would allow only one COMMENT in total in each header
+ if (nextHCard.getKey().startsWith("COMMENT")) {
+ insertComment(nextHCard.getComment());
+ } else {
+ updateLine(nextHCard.getKey(), nextHCard);
+ }
+ }
+ }
+
+ /** Find the number of cards in the header */
+ public int getNumberOfCards() {
+ return cards.size();
+ }
+
+ /** Get an iterator over the header cards */
+ public Cursor iterator() {
+ return cards.iterator(0);
+ }
+
+ /** Get the offset of this header */
+ public long getFileOffset() {
+ return fileOffset;
+ }
+
+ /** Calculate the unpadded size of the data segment from
+ * the header information.
+ *
+ * @return the unpadded data segment size.
+ */
+ int trueDataSize() {
+
+ if (!isValidHeader()) {
+ return 0;
+ }
+
+
+ int naxis = getIntValue("NAXIS", 0);
+ int bitpix = getIntValue("BITPIX");
+
+ int[] axes = new int[naxis];
+
+ for (int axis = 1; axis <= naxis; axis += 1) {
+ axes[axis - 1] = getIntValue("NAXIS" + axis, 0);
+ }
+
+ boolean isGroup = getBooleanValue("GROUPS", false);
+
+ int pcount = getIntValue("PCOUNT", 0);
+ int gcount = getIntValue("GCOUNT", 1);
+
+ int startAxis = 0;
+
+ if (isGroup && naxis > 1 && axes[0] == 0) {
+ startAxis = 1;
+ }
+
+ int size = 1;
+ for (int i = startAxis; i < naxis; i += 1) {
+ size *= axes[i];
+ }
+
+ size += pcount;
+ size *= gcount;
+
+ // Now multiply by the number of bits per pixel and
+ // convert to bytes.
+ size *= Math.abs(getIntValue("BITPIX", 0)) / 8;
+
+ return size;
+ }
+
+ /** Return the size of the data including any needed padding.
+ * @return the data segment size including any needed padding.
+ */
+ public long getDataSize() {
+ return FitsUtil.addPadding(trueDataSize());
+ }
+
+ /** Get the size of the header in bytes */
+ public long getSize() {
+ return headerSize();
+ }
+
+ /** Return the size of the header data including padding.
+ * @return the header size including any needed padding.
+ */
+ int headerSize() {
+
+ if (!isValidHeader()) {
+ return 0;
+ }
+
+ return FitsUtil.addPadding(cards.size() * 80);
+ }
+
+ /** Is this a valid header.
+ * @return <CODE>true</CODE> for a valid header,
+ * <CODE>false</CODE> otherwise.
+ */
+ boolean isValidHeader() {
+
+ if (getNumberOfCards() < 4) {
+ return false;
+ }
+ iter = iterator();
+
+ String key = ((HeaderCard) iter.next()).getKey();
+ if (!key.equals("SIMPLE") && !key.equals("XTENSION")) {
+ return false;
+ }
+ key = ((HeaderCard) iter.next()).getKey();
+ if (!key.equals("BITPIX")) {
+ return false;
+ }
+ key = ((HeaderCard) iter.next()).getKey();
+ if (!key.equals("NAXIS")) {
+ return false;
+ }
+ while (iter.hasNext()) {
+ key = ((HeaderCard) iter.next()).getKey();
+ }
+ if (!key.equals("END")) {
+ return false;
+ }
+ return true;
+
+ }
+
+ /** Find the card associated with a given key.
+ * If found this sets the mark to the card, otherwise it
+ * unsets the mark.
+ * @param key The header key.
+ * @return <CODE>null</CODE> if the keyword could not be found;
+ * return the HeaderCard object otherwise.
+ */
+ public HeaderCard findCard(String key) {
+
+ HeaderCard card = (HeaderCard) cards.get(key);
+ if (card != null) {
+ iter.setKey(key);
+ }
+ return card;
+ }
+
+ /** Get the value associated with the key as an int.
+ * @param key The header key.
+ * @param dft The value to be returned if the key is not found.
+ */
+ public int getIntValue(String key, int dft) {
+ return (int) getLongValue(key, (long) dft);
+ }
+
+ /** Get the <CODE>int</CODE> value associated with the given key.
+ * @param key The header key.
+ * @return The associated value or 0 if not found.
+ */
+ public int getIntValue(String key) {
+ return (int) getLongValue(key);
+ }
+
+ /** Get the <CODE>long</CODE> value associated with the given key.
+ * @param key The header key.
+ * @return The associated value or 0 if not found.
+ */
+ public long getLongValue(String key) {
+ return getLongValue(key, 0L);
+ }
+
+ /** Get the <CODE>long</CODE> value associated with the given key.
+ * @param key The header key.
+ * @param dft The default value to be returned if the key cannot be found.
+ * @return the associated value.
+ */
+ public long getLongValue(String key, long dft) {
+
+ HeaderCard fcard = findCard(key);
+ if (fcard == null) {
+ return dft;
+ }
+
+ try {
+ String v = fcard.getValue();
+ if (v != null) {
+ return Long.parseLong(v);
+ }
+ } catch (NumberFormatException e) {
+ }
+
+ return dft;
+ }
+
+ /** Get the <CODE>float</CODE> value associated with the given key.
+ * @param key The header key.
+ * @param dft The value to be returned if the key is not found.
+ */
+ public float getFloatValue(String key, float dft) {
+ return (float) getDoubleValue(key, dft);
+ }
+
+ /** Get the <CODE>float</CODE> value associated with the given key.
+ * @param key The header key.
+ * @return The associated value or 0.0 if not found.
+ */
+ public float getFloatValue(String key) {
+ return (float) getDoubleValue(key);
+ }
+
+ /** Get the <CODE>double</CODE> value associated with the given key.
+ * @param key The header key.
+ * @return The associated value or 0.0 if not found.
+ */
+ public double getDoubleValue(String key) {
+ return getDoubleValue(key, 0.);
+ }
+
+ /** Get the <CODE>double</CODE> value associated with the given key.
+ * @param key The header key.
+ * @param dft The default value to return if the key cannot be found.
+ * @return the associated value.
+ */
+ public double getDoubleValue(String key, double dft) {
+
+ HeaderCard fcard = findCard(key);
+ if (fcard == null) {
+ return dft;
+ }
+
+ try {
+ String v = fcard.getValue();
+ if (v != null) {
+ return new Double(v).doubleValue();
+ }
+ } catch (NumberFormatException e) {
+ }
+
+ return dft;
+ }
+
+ /** Get the <CODE>boolean</CODE> value associated with the given key.
+ * @param The header key.
+ * @return The value found, or false if not found or if the
+ * keyword is not a logical keyword.
+ */
+ public boolean getBooleanValue(String key) {
+ return getBooleanValue(key, false);
+ }
+
+ /** Get the <CODE>boolean</CODE> value associated with the given key.
+ * @param key The header key.
+ * @param dft The value to be returned if the key cannot be found
+ * or if the parameter does not seem to be a boolean.
+ * @return the associated value.
+ */
+ public boolean getBooleanValue(String key, boolean dft) {
+
+ HeaderCard fcard = findCard(key);
+ if (fcard == null) {
+ return dft;
+ }
+
+ String val = fcard.getValue();
+ if (val == null) {
+ return dft;
+ }
+
+ if (val.equals("T")) {
+ return true;
+ } else if (val.equals("F")) {
+ return false;
+ } else {
+ return dft;
+ }
+ }
+
+ /** Get the <CODE>String</CODE> value associated with the given key.
+ *
+ * @param key The header key.
+ * @return The associated value or null if not found or if the value is not a string.
+ */
+ public String getStringValue(String key) {
+
+ HeaderCard fcard = findCard(key);
+ if (fcard == null || !fcard.isStringValue()) {
+ return null;
+ }
+
+ String val = fcard.getValue();
+ boolean append = longStringsEnabled
+ && val != null && val.endsWith("&");
+ iter.next(); // skip the primary card.
+ while (append) {
+ HeaderCard nxt = (HeaderCard) iter.next();
+ if (nxt == null) {
+ append = false;
+ } else {
+ key = nxt.getKey();
+ String comm = nxt.getComment();
+ if (key == null || comm == null || !key.equals("CONTINUE")) {
+ append = false;
+ } else {
+ comm = continueString(comm);
+ if (comm != null) {
+ comm = comm.substring(1, comm.length() - 1);
+ val = val.substring(0, val.length() - 1) + comm;
+ append = comm.endsWith("&");
+ }
+ }
+ }
+ }
+
+ return val;
+ }
+
+ /** Add a card image to the header.
+ * @param fcard The card to be added.
+ */
+ public void addLine(HeaderCard fcard) {
+
+ if (fcard != null) {
+ if (fcard.isKeyValuePair()) {
+ iter.add(fcard.getKey(), fcard);
+ } else {
+ iter.add(fcard);
+ }
+ }
+ }
+
+ /** Add a card image to the header.
+ * @param card The card to be added.
+ * @exception HeaderCardException If the card is not valid.
+ */
+ public void addLine(String card)
+ throws HeaderCardException {
+ addLine(new HeaderCard(card));
+ }
+
+ /** Create a header by reading the information from the input stream.
+ * @param dis The input stream to read the data from.
+ * @return <CODE>null</CODE> if there was a problem with the header;
+ * otherwise return the header read from the input stream.
+ */
+ public static Header readHeader(ArrayDataInput dis)
+ throws TruncatedFileException, IOException {
+ Header myHeader = new Header();
+ try {
+ myHeader.read(dis);
+ } catch (EOFException e) {
+ // An EOF exception is thrown only if the EOF was detected
+ // when reading the first card. In this case we want
+ // to return a null.
+ return null;
+ }
+ return myHeader;
+ }
+
+ /** Read a stream for header data.
+ * @param dis The input stream to read the data from.
+ * @return <CODE>null</CODE> if there was a problem with the header;
+ * otherwise return the header read from the input stream.
+ */
+ public void read(ArrayDataInput dis)
+ throws TruncatedFileException, IOException {
+ if (dis instanceof RandomAccess) {
+ fileOffset = FitsUtil.findOffset(dis);
+ } else {
+ fileOffset = -1;
+ }
+
+ byte[] buffer = new byte[80];
+
+ boolean firstCard = true;
+ int count = 0;
+
+ try {
+ while (true) {
+
+ int len;
+
+ int need = 80;
+
+ try {
+
+ while (need > 0) {
+ len = dis.read(buffer, 80 - need, need);
+ count += 1;
+ if (len == 0) {
+ throw new TruncatedFileException();
+ }
+ need -= len;
+ }
+ } catch (EOFException e) {
+
+ // Rethrow the EOF if we are at the beginning of the header,
+ // otherwise we have a FITS error.
+ //
+ if (firstCard && need == 80) {
+ throw e;
+ }
+ throw new TruncatedFileException(e.getMessage());
+ }
+
+ String cbuf = AsciiFuncs.asciiString(buffer);
+ HeaderCard fcard = new HeaderCard(cbuf);
+
+ if (firstCard) {
+
+ String key = fcard.getKey();
+
+ if (key == null || (!key.equals("SIMPLE") && !key.equals("XTENSION"))) {
+ throw new IOException("Not FITS format at " + fileOffset + ":" + cbuf);
+ }
+ firstCard = false;
+ }
+
+ String key = fcard.getKey();
+ if (key != null && cards.containsKey(key)) {
+ System.err.println("Warning: multiple occurrences of key:" + key);
+ }
+
+ // We don't check the value here. If the user
+ // wants to be sure that long strings are disabled,
+ // they can call setLongStringsEnabled(false) after
+ // reading the header.
+ if (key.equals("LONGSTRN")) {
+ longStringsEnabled = true;
+ }
+ // save card
+ addLine(fcard);
+ if (cbuf.substring(0, 8).equals("END ")) {
+ break; // Out of reading the header.
+ }
+ }
+
+ } catch (EOFException e) {
+ throw e;
+
+ } catch (Exception e) {
+ if (!(e instanceof EOFException)) {
+ // For compatibility with Java V5 we just add in the error message
+ // rather than using using the cause mechanism.
+ // Probably should update this when we can ignore Java 5.
+ throw new IOException("Invalid FITS Header:"+ e);
+ }
+ }
+ if (fileOffset >= 0) {
+ oldSize = cards.size();
+ input = dis;
+ }
+
+ // Read to the end of the current FITS block.
+ //
+ try {
+ dis.skipBytes(FitsUtil.padding(count * 80));
+ } catch (IOException e) {
+ throw new TruncatedFileException(e.getMessage());
+ }
+ }
+
+ /** Find the card associated with a given key.
+ * @param key The header key.
+ * @return <CODE>null</CODE> if the keyword could not be found;
+ * return the card image otherwise.
+ */
+ public String findKey(String key) {
+ HeaderCard card = findCard(key);
+ if (card == null) {
+ return null;
+ } else {
+ return card.toString();
+ }
+ }
+
+ /** Replace the key with a new key. Typically this is used
+ * when deleting or inserting columns so that TFORMx -> TFORMx-1
+ * @param oldKey The old header keyword.
+ * @param newKey the new header keyword.
+ * @return <CODE>true</CODE> if the card was replaced.
+ * @exception HeaderCardException If <CODE>newKey</CODE> is not a
+ * valid FITS keyword.
+ */
+ boolean replaceKey(String oldKey, String newKey)
+ throws HeaderCardException {
+
+ HeaderCard oldCard = findCard(oldKey);
+ if (oldCard == null) {
+ return false;
+ }
+ if (!cards.replaceKey(oldKey, newKey)) {
+ throw new HeaderCardException("Duplicate key in replace");
+ }
+
+ oldCard.setKey(newKey);
+
+ return true;
+ }
+
+ /** Write the current header (including any needed padding) to the
+ * output stream.
+ * @param dos The output stream to which the data is to be written.
+ * @exception FitsException if the header could not be written.
+ */
+ public void write(ArrayDataOutput dos) throws FitsException {
+
+ fileOffset = FitsUtil.findOffset(dos);
+
+ // Ensure that all cards are in the proper order.
+ cards.sort(new HeaderOrder());
+ checkBeginning();
+ checkEnd();
+ if (cards.size() <= 0) {
+ return;
+ }
+
+
+ Cursor iter = cards.iterator(0);
+
+ try {
+ while (iter.hasNext()) {
+ HeaderCard card = (HeaderCard) iter.next();
+
+ byte[] b = AsciiFuncs.getBytes(card.toString());
+ dos.write(b);
+ }
+
+ FitsUtil.pad(dos, getNumberOfCards() * 80, (byte) ' ');
+ } catch (IOException e) {
+ throw new FitsException("IO Error writing header: " + e);
+ }
+ try {
+ dos.flush();
+ } catch (IOException e) {
+ }
+
+ }
+
+ /** Rewrite the header. */
+ public void rewrite() throws FitsException, IOException {
+
+ ArrayDataOutput dos = (ArrayDataOutput) input;
+
+ if (rewriteable()) {
+ FitsUtil.reposition(dos, fileOffset);
+ write(dos);
+ dos.flush();
+ } else {
+ throw new FitsException("Invalid attempt to rewrite Header.");
+ }
+ }
+
+ /** Reset the file pointer to the beginning of the header */
+ public boolean reset() {
+ try {
+ FitsUtil.reposition(input, fileOffset);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /** Can the header be rewritten without rewriting the entire file? */
+ public boolean rewriteable() {
+
+ if (fileOffset >= 0
+ && input instanceof ArrayDataOutput
+ && (cards.size() + 35) / 36 == (oldSize + 35) / 36) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** Add or replace a key with the given boolean value and comment.
+ * @param key The header key.
+ * @param val The boolean value.
+ * @param comment A comment to append to the card.
+ * @exception HeaderCardException If the parameters cannot build a
+ * valid FITS card.
+ */
+ public void addValue(String key, boolean val, String comment)
+ throws HeaderCardException {
+ removeCard(key);
+ iter.add(key, new HeaderCard(key, val, comment));
+ }
+
+ /** Add or replace a key with the given double value and comment.
+ * Note that float values will be promoted to doubles.
+ * @param key The header key.
+ * @param val The double value.
+ * @param comment A comment to append to the card.
+ * @exception HeaderCardException If the parameters cannot build a
+ * valid FITS card.
+ */
+ public void addValue(String key, double val, String comment)
+ throws HeaderCardException {
+ removeCard(key);
+ iter.add(key, new HeaderCard(key, val, comment));
+ }
+
+ ;
+
+ /** Add or replace a key with the given string value and comment.
+ * @param key The header key.
+ * @param val The string value.
+ * @param comment A comment to append to the card.
+ * @exception HeaderCardException If the parameters cannot build a
+ * valid FITS card.
+ */
+ public void addValue(String key, String val, String comment)
+ throws HeaderCardException {
+ removeCard(key);
+ // Remember that quotes get doubled in the value...
+ if (longStringsEnabled && val.replace("'", "''").length() > 68) {
+ addLongString(key, val, comment);
+ } else {
+ iter.add(key, new HeaderCard(key, val, comment));
+ }
+ }
+
+ /** Add or replace a key with the given long value and comment.
+ * Note that int's will be promoted to long's.
+ * @param key The header key.
+ * @param val The long value.
+ * @param comment A comment to append to the card.
+ * @exception HeaderCardException If the parameters cannot build a
+ * valid FITS card.
+ */
+ public void addValue(String key, long val, String comment)
+ throws HeaderCardException {
+ removeCard(key);
+ iter.add(key, new HeaderCard(key, val, comment));
+ }
+
+ private int getAdjustedLength(String in, int max) {
+ // Find the longest string that we can use when
+ // we accommodate needing to double quotes.
+ int size = 0;
+ int i;
+ for (i = 0; i < in.length() && size < max; i += 1) {
+ if (in.charAt(i) == '\'') {
+ size += 2;
+ if (size > max) {
+ break; // Jumped over the edge
+ }
+ } else {
+ size += 1;
+ }
+ }
+ return i;
+ }
+
+ protected void addLongString(String key, String val, String comment)
+ throws HeaderCardException {
+ // We assume that we've made the test so that
+ // we need to write a long string. We need to
+ // double the quotes in the string value. addValue
+ // takes care of that for us, but we need to do it
+ // ourselves when we are extending into the comments.
+ // We also need to be careful that single quotes don't
+ // make the string too long and that we don't split
+ // in the middle of a quote.
+ int off = getAdjustedLength(val, 67);
+ String curr = val.substring(0, off) + '&';
+ // No comment here since we're using as much of the card as we can
+ addValue(key, curr, null);
+ val = val.substring(off);
+
+ while (val != null && val.length() > 0) {
+ off = getAdjustedLength(val, 67);
+ if (off < val.length()) {
+ curr = "'" + val.substring(0, off).replace("'", "''") + "&'";
+ val = val.substring(off);
+ } else {
+ curr = "'" + val.replace("'", "''") + "' / " + comment;
+ val = null;
+ }
+
+ iter.add(new HeaderCard("CONTINUE", null, curr));
+ }
+ }
+
+ /** Delete a key.
+ * @param key The header key.
+ */
+ public void removeCard(String key)
+ throws HeaderCardException {
+
+ if (cards.containsKey(key)) {
+ iter.setKey(key);
+ if (iter.hasNext()) {
+ HeaderCard hc = (HeaderCard) iter.next();
+ String val = hc.getValue();
+ boolean delExtensions =
+ longStringsEnabled && val != null && val.endsWith("&");
+ iter.remove();
+ while (delExtensions) {
+ hc = (HeaderCard) iter.next();
+ if (hc == null) {
+ delExtensions = false;
+ } else {
+ if (hc.getKey().equals("CONTINUE")) {
+ String more = hc.getComment();
+ more = continueString(more);
+ if (more != null) {
+ iter.remove();
+ delExtensions = more.endsWith("&'");
+ } else {
+ delExtensions = false;
+ }
+ } else {
+ delExtensions = false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Look for the continuation part of a COMMENT.
+ * The comment may also include a 'real' comment, e.g.,
+ * <pre>
+ * X = 'AB&'
+ * CONTINUE 'CDEF' / ABC
+ * </pre>
+ * Here we are looking for just the 'CDEF' part of the CONTINUE card.
+ */
+ private String continueString(String input) {
+ if (input == null) {
+ return null;
+ }
+
+ input = input.trim();
+ if (input.length() < 2 || input.charAt(0) != '\'') {
+ return null;
+ }
+
+ for (int i = 1; i < input.length(); i += 1) {
+ char c = input.charAt(i);
+ if (c == '\'') {
+ if (i < input.length() - 1 && input.charAt(i + 1) == c) {
+ // consecutive quotes -> escaped single quote
+ // Get rid of the extra quote.
+ input = input.substring(0, i) + input.substring(i + 1);
+ continue; // Check the next character.
+ } else {
+ // Found closing apostrophe
+ return input.substring(0, i + 1);
+ }
+ }
+ }
+ // Never found a closing apostrophe.
+ return null;
+ }
+
+ /** Add a line to the header using the COMMENT style, i.e., no '='
+ * in column 9.
+ * @param header The comment style header.
+ * @param value A string to follow the header.
+ * @exception HeaderCardException If the parameters cannot build a
+ * valid FITS card.
+ */
+ public void insertCommentStyle(String header, String value) {
+ // Should just truncate strings, so we should never get
+ // an exception...
+
+ try {
+ iter.add(new HeaderCard(header, null, value));
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible Exception for comment style:" + header + ":" + value);
+ }
+ }
+
+ /** Add a COMMENT line.
+ * @param value The comment.
+ * @exception HeaderCardException If the parameter is not a
+ * valid FITS comment.
+ */
+ public void insertComment(String value)
+ throws HeaderCardException {
+ insertCommentStyle("COMMENT", value);
+ }
+
+ /** Add a HISTORY line.
+ * @param value The history record.
+ * @exception HeaderCardException If the parameter is not a
+ * valid FITS comment.
+ */
+ public void insertHistory(String value)
+ throws HeaderCardException {
+ insertCommentStyle("HISTORY", value);
+ }
+
+ /** Delete the card associated with the given key.
+ * Nothing occurs if the key is not found.
+ *
+ * @param key The header key.
+ */
+ public void deleteKey(String key) {
+
+ iter.setKey(key);
+ if (iter.hasNext()) {
+ iter.next();
+ iter.remove();
+ }
+ }
+
+ /** Tests if the specified keyword is present in this table.
+ * @param key the keyword to be found.
+ * @return <CODE>true<CODE> if the specified keyword is present in this
+ * table; <CODE>false<CODE> otherwise.
+ */
+ public final boolean containsKey(String key) {
+ return cards.containsKey(key);
+ }
+
+ /** Create a header for a null image.
+ */
+ void nullImage() {
+
+ iter = iterator();
+ try {
+ addValue("SIMPLE", true, "ntf::header:simple:2");
+ addValue("BITPIX", 8, "ntf::header:bitpix:2");
+ addValue("NAXIS", 0, "ntf::header:naxis:2");
+ addValue("EXTEND", true, "ntf::header:extend:2");
+ } catch (HeaderCardException e) {
+ }
+ }
+
+ /** Set the SIMPLE keyword to the given value.
+ * @param val The boolean value -- Should be true for FITS data.
+ */
+ public void setSimple(boolean val) {
+ deleteKey("SIMPLE");
+ deleteKey("XTENSION");
+
+ // If we're flipping back to and from the primary header
+ // we need to add in the EXTEND keyword whenever we become
+ // a primary, because it's not permitted in the extensions
+ // (at least not where it needs to be in the primary array).
+ if (findCard("NAXIS") != null) {
+ int nax = getIntValue("NAXIS");
+
+ iter = iterator();
+
+
+ if (findCard("NAXIS" + nax) != null) {
+ HeaderCard hc = (HeaderCard) iter.next();
+ try {
+ removeCard("EXTEND");
+ iter.add("EXTEND", new HeaderCard("EXTEND", true, "ntf::header:extend:1"));
+ } catch (Exception e) { // Ignore the exception
+ }
+ ;
+ }
+ }
+
+ iter = iterator();
+ try {
+ iter.add("SIMPLE",
+ new HeaderCard("SIMPLE", val, "ntf::header:simple:1"));
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception at setSimple " + e);
+ }
+ }
+
+ /** Set the XTENSION keyword to the given value.
+ * @param val The name of the extension. "IMAGE" and "BINTABLE" are supported.
+ */
+ public void setXtension(String val) {
+ deleteKey("SIMPLE");
+ deleteKey("XTENSION");
+ deleteKey("EXTEND");
+ iter = iterator();
+ try {
+ iter.add("XTENSION",
+ new HeaderCard("XTENSION", val, "ntf::header:xtension:1"));
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception at setXtension " + e);
+ }
+ }
+
+ /** Set the BITPIX value for the header.
+ * @param val. The following values are permitted by FITS conventions:
+ * <ul>
+ * <li> 8 -- signed bytes data. Also used for tables.
+ * <li> 16 -- signed short data.
+ * <li> 32 -- signed int data.
+ * <li> 64 -- signed long data.
+ * <li> -32 -- IEEE 32 bit floating point numbers.
+ * <li> -64 -- IEEE 64 bit floating point numbers.
+ * </ul>
+ */
+ public void setBitpix(int val) {
+ iter = iterator();
+ iter.next();
+ try {
+ iter.add("BITPIX", new HeaderCard("BITPIX", val, "ntf::header:bitpix:1"));
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception at setBitpix " + e);
+ }
+ }
+
+ /** Set the value of the NAXIS keyword
+ * @param val The dimensionality of the data.
+ */
+ public void setNaxes(int val) {
+ iter.setKey("BITPIX");
+ if (iter.hasNext()) {
+ iter.next();
+ }
+
+ try {
+ iter.add("NAXIS", new HeaderCard("NAXIS", val, "ntf::header:naxis:1"));
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception at setNaxes " + e);
+ }
+ }
+
+ /** Set the dimension for a given axis.
+ * @param axis The axis being set.
+ * @param dim The dimension
+ */
+ public void setNaxis(int axis, int dim) {
+
+ if (axis <= 0) {
+ return;
+ }
+ if (axis == 1) {
+ iter.setKey("NAXIS");
+ } else if (axis > 1) {
+ iter.setKey("NAXIS" + (axis - 1));
+ }
+ if (iter.hasNext()) {
+ iter.next();
+ }
+ try {
+ iter.add("NAXIS" + axis,
+ new HeaderCard("NAXIS" + axis, dim, "ntf::header:naxisN:1"));
+
+ } catch (HeaderCardException e) {
+ System.err.println("Impossible exception at setNaxis " + e);
+ }
+ }
+
+ /** Ensure that the header begins with
+ * a valid set of keywords. Note that we
+ * do not check the values of these keywords.
+ */
+ void checkBeginning() throws FitsException {
+
+ iter = iterator();
+
+ if (!iter.hasNext()) {
+ throw new FitsException("Empty Header");
+ }
+ HeaderCard card = (HeaderCard) iter.next();
+ String key = card.getKey();
+ if (!key.equals("SIMPLE") && !key.equals("XTENSION")) {
+ throw new FitsException("No SIMPLE or XTENSION at beginning of Header");
+ }
+ boolean isTable = false;
+ boolean isExtension = false;
+ if (key.equals("XTENSION")) {
+ String value = card.getValue();
+ if (value == null) {
+ throw new FitsException("Empty XTENSION keyword");
+ }
+
+ isExtension = true;
+
+ if (value.equals("BINTABLE") || value.equals("A3DTABLE")
+ || value.equals("TABLE")) {
+ isTable = true;
+ }
+ }
+
+ cardCheck("BITPIX");
+ cardCheck("NAXIS");
+
+ int nax = getIntValue("NAXIS");
+ iter.next();
+
+ for (int i = 1; i <= nax; i += 1) {
+ cardCheck("NAXIS" + i);
+ }
+
+ if (isExtension) {
+ cardCheck("PCOUNT");
+ cardCheck("GCOUNT");
+ if (isTable) {
+ cardCheck("TFIELDS");
+ }
+ }
+ // This does not check for the EXTEND keyword which
+ // if present in the primary array must immediately follow
+ // the NAXISn.
+ }
+
+ /** Check if the given key is the next one available in
+ * the header.
+ */
+ private void cardCheck(String key) throws FitsException {
+
+ if (!iter.hasNext()) {
+ throw new FitsException("Header terminates before " + key);
+ }
+ HeaderCard card = (HeaderCard) iter.next();
+ if (!card.getKey().equals(key)) {
+ throw new FitsException("Key " + key + " not found where expected."
+ + "Found " + card.getKey());
+ }
+ }
+
+ /** Ensure that the header has exactly one END keyword in
+ * the appropriate location.
+ */
+ void checkEnd() {
+
+ // Ensure we have an END card only at the end of the
+ // header.
+ //
+ iter = iterator();
+ HeaderCard card;
+
+ while (iter.hasNext()) {
+ card = (HeaderCard) iter.next();
+ if (!card.isKeyValuePair() && card.getKey().equals("END")) {
+ iter.remove();
+ }
+ }
+ try {
+ // End cannot have a comment
+ iter.add(new HeaderCard("END", null, null));
+ } catch (HeaderCardException e) {
+ }
+ }
+
+ /** Print the header to a given stream.
+ * @param ps the stream to which the card images are dumped.
+ */
+ public void dumpHeader(PrintStream ps) {
+ iter = iterator();
+ while (iter.hasNext()) {
+ ps.println(iter.next());
+ }
+ }
+
+ /***** Deprecated methods *******/
+ /** Find the number of cards in the header
+ * @deprecated see numberOfCards(). The units
+ * of the size of the header may be unclear.
+ */
+ public int size() {
+ return cards.size();
+ }
+
+ /** Get the n'th card image in the header
+ * @return the card image; return <CODE>null</CODE> if the n'th card
+ * does not exist.
+ * @deprecated An iterator should be used for sequential
+ * access to the header.
+ */
+ public String getCard(int n) {
+ if (n >= 0 && n < cards.size()) {
+ iter = cards.iterator(n);
+ HeaderCard c = (HeaderCard) iter.next();
+ return c.toString();
+ }
+ return null;
+ }
+
+ /** Get the n'th key in the header.
+ * @return the card image; return <CODE>null</CODE> if the n'th key
+ * does not exist.
+ * @deprecated An iterator should be used for sequential
+ * access to the header.
+ */
+ public String getKey(int n) {
+
+ String card = getCard(n);
+ if (card == null) {
+ return null;
+ }
+
+ String key = card.substring(0, 8);
+ if (key.charAt(0) == ' ') {
+ return "";
+ }
+
+
+ if (key.indexOf(' ') >= 1) {
+ key = key.substring(0, key.indexOf(' '));
+ }
+ return key;
+ }
+
+ /** Create a header which points to the
+ * given data object.
+ * @param o The data object to be described.
+ * @exception FitsException if the data was not valid for this header.
+ * @deprecated Use the appropriate Header constructor.
+ */
+ public void pointToData(Data o) throws FitsException {
+ o.fillHeader(this);
+ }
+
+ /** Find the end of a set of keywords describing a column or axis
+ * (or anything else terminated by an index. This routine leaves
+ * the header ready to add keywords after any existing keywords
+ * with the index specified. The user should specify a
+ * prefix to a keyword that is guaranteed to be present.
+ */
+ Cursor positionAfterIndex(String prefix, int col) {
+ String colnum = "" + col;
+
+ iter.setKey(prefix + colnum);
+
+ if (iter.hasNext()) {
+
+ // Bug fix (references to forward) here by Laurent Borges
+ boolean forward = false;
+
+ String key;
+ while (iter.hasNext()) {
+
+ key = ((HeaderCard) iter.next()).getKey().trim();
+ if (key == null
+ || key.length() <= colnum.length()
+ || !key.substring(key.length() - colnum.length()).equals(colnum)) {
+ forward = true;
+ break;
+ }
+ }
+ if (forward) {
+ iter.prev(); // Gone one too far, so skip back an element.
+ }
+ }
+ return iter;
+ }
+
+ /** Get the next card in the Header using the current iterator */
+ public HeaderCard nextCard() {
+ if (iter == null) {
+ return null;
+ }
+ if (iter.hasNext()) {
+ return (HeaderCard) iter.next();
+ } else {
+ return null;
+ }
+ }
+
+ /** Move after the EXTEND keyword in images.
+ * Used in bug fix noted by V. Forchi
+ */
+ void afterExtend() {
+ if (findCard("EXTEND") != null) {
+ nextCard();
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes -- including
+ * this class.
+ */
+/** This class describes methods to access and manipulate the individual
+ * cards for a FITS Header.
+ */
+public class HeaderCard {
+
+ /** The keyword part of the card (set to null if there's no keyword) */
+ private String key;
+ /** The value part of the card (set to null if there's no value) */
+ private String value;
+ /** The comment part of the card (set to null if there's no comment) */
+ private String comment;
+ /** Does this card represent a nullable field. ? */
+ private boolean nullable;
+ /** A flag indicating whether or not this is a string value */
+ private boolean isString;
+ /** Maximum length of a FITS keyword field */
+ public static final int MAX_KEYWORD_LENGTH = 8;
+ /** Maximum length of a FITS value field */
+ public static final int MAX_VALUE_LENGTH = 70;
+ /** padding for building card images */
+ private static String space80 = " ";
+
+ /** Create a HeaderCard from its component parts
+ * @param key keyword (null for a comment)
+ * @param value value (null for a comment or keyword without an '=')
+ * @param comment comment
+ * @exception HeaderCardException for any invalid keyword
+ */
+ public HeaderCard(String key, double value, String comment)
+ throws HeaderCardException {
+ this(key, dblString(value), comment);
+ isString = false;
+ }
+
+ /** Create a HeaderCard from its component parts
+ * @param key keyword (null for a comment)
+ * @param value value (null for a comment or keyword without an '=')
+ * @param comment comment
+ * @exception HeaderCardException for any invalid keyword
+ */
+ public HeaderCard(String key, boolean value, String comment)
+ throws HeaderCardException {
+ this(key, value ? "T" : "F", comment);
+ isString = false;
+ }
+
+ /** Create a HeaderCard from its component parts
+ * @param key keyword (null for a comment)
+ * @param value value (null for a comment or keyword without an '=')
+ * @param comment comment
+ * @exception HeaderCardException for any invalid keyword
+ */
+ public HeaderCard(String key, int value, String comment)
+ throws HeaderCardException {
+ this(key, String.valueOf(value), comment);
+ isString = false;
+ }
+
+ /** Create a HeaderCard from its component parts
+ * @param key keyword (null for a comment)
+ * @param value value (null for a comment or keyword without an '=')
+ * @param comment comment
+ * @exception HeaderCardException for any invalid keyword
+ */
+ public HeaderCard(String key, long value, String comment)
+ throws HeaderCardException {
+ this(key, String.valueOf(value), comment);
+ isString = false;
+ }
+
+ /** Create a HeaderCard from its component parts
+ * @param key keyword (null for a comment)
+ * @param value value (null for a comment or keyword without an '=')
+ * @param comment comment
+ * @exception HeaderCardException for any invalid keyword or value
+ */
+ public HeaderCard(String key, String value, String comment)
+ throws HeaderCardException {
+ this(key, value, comment, false);
+ }
+
+ /** Create a comment style card.
+ * This constructor builds a card which has no value.
+ * This may be either a comment style card in which case the
+ * nullable field should be false, or a value field which
+ * has a null value, in which case the nullable field should be
+ * true.
+ * @param key The key for the comment or nullable field.
+ * @param comment The comment
+ * @param nullable Is this a nullable field or a comment-style card?
+ */
+ public HeaderCard(String key, String comment, boolean nullable)
+ throws HeaderCardException {
+ this(key, null, comment, nullable);
+ }
+
+ /** Create a string from a double making sure that it's
+ * not more than 20 characters long.
+ * Probably would be better if we had a way to override this
+ * since we can loose precision for some doubles.
+ */
+ private static String dblString(double input) {
+ String value = String.valueOf(input);
+ if (value.length() > 20) {
+ value = new java.util.Formatter().format("%20.13G", input).out().toString();
+ }
+ return value;
+ }
+
+ /** Create a HeaderCard from its component parts
+ * @param key Keyword (null for a COMMENT)
+ * @param value Value
+ * @param comment Comment
+ * @param nullable Is this a nullable value card?
+ * @exception HeaderCardException for any invalid keyword or value
+ */
+ public HeaderCard(String key, String value, String comment, boolean nullable)
+ throws HeaderCardException {
+ if (comment != null && comment.startsWith("ntf::")) {
+ String ckey = comment.substring(5); // Get rid of ntf:: prefix
+ comment = HeaderCommentsMap.getComment(ckey);
+ }
+ if (key == null && value != null) {
+ throw new HeaderCardException("Null keyword with non-null value");
+ }
+
+ if (key != null && key.length() > MAX_KEYWORD_LENGTH) {
+ if (!FitsFactory.getUseHierarch()
+ || !key.substring(0, 9).equals("HIERARCH.")) {
+ throw new HeaderCardException("Keyword too long");
+ }
+ }
+
+ if (value != null) {
+ value = value.replaceAll(" *$", "");
+
+ if (value.length() > MAX_VALUE_LENGTH) {
+ throw new HeaderCardException("Value too long");
+ }
+
+ if (value.startsWith("'")) {
+ if (value.charAt(value.length() - 1) != '\'') {
+ throw new HeaderCardException("Missing end quote in string value");
+ }
+
+ value = value.substring(1, value.length() - 1).trim();
+
+ }
+ }
+
+ this.key = key;
+ this.value = value;
+ this.comment = comment;
+ this.nullable = nullable;
+ isString = true;
+ }
+
+ /** Create a HeaderCard from a FITS card image
+ * @param card the 80 character card image
+ */
+ public HeaderCard(String card) {
+ key = null;
+ value = null;
+ comment = null;
+ isString = false;
+
+ if (card.length() > 80) {
+ card = card.substring(0, 80);
+ }
+
+ if (FitsFactory.getUseHierarch()
+ && card.length() > 9
+ && card.substring(0, 9).equals("HIERARCH ")) {
+ hierarchCard(card);
+ return;
+ }
+
+ // We are going to assume that the value has no blanks in
+ // it unless it is enclosed in quotes. Also, we assume that
+ // a / terminates the string (except inside quotes)
+
+ // treat short lines as special keywords
+ if (card.length() < 9) {
+ key = card;
+ return;
+ }
+
+ // extract the key
+ key = card.substring(0, 8).trim();
+
+ // if it is an empty key, assume the remainder of the card is a comment
+ if (key.length() == 0) {
+ key = "";
+ comment = card.substring(8);
+ return;
+ }
+
+ // Non-key/value pair lines are treated as keyed comments
+ if (key.equals("COMMENT") || key.equals("HISTORY")
+ || !card.substring(8, 10).equals("= ")) {
+ comment = card.substring(8).trim();
+ return;
+ }
+
+ // extract the value/comment part of the string
+ String valueAndComment = card.substring(10).trim();
+
+ // If there is no value/comment part, we are done.
+ if (valueAndComment.length() == 0) {
+ value = "";
+ return;
+ }
+
+ int vend = -1;
+ boolean quote = false;
+
+ // If we have a ' then find the matching '.
+ if (valueAndComment.charAt(0) == '\'') {
+
+ int offset = 1;
+ while (offset < valueAndComment.length()) {
+
+ // look for next single-quote character
+ vend = valueAndComment.indexOf("'", offset);
+
+ // if the quote character is the last character on the line...
+ if (vend == valueAndComment.length() - 1) {
+ break;
+ }
+
+ // if we did not find a matching single-quote...
+ if (vend == -1) {
+ // pretend this is a comment card
+ key = null;
+ comment = card;
+ return;
+ }
+
+ // if this is not an escaped single-quote, we are done
+ if (valueAndComment.charAt(vend + 1) != '\'') {
+ break;
+ }
+
+ // skip past escaped single-quote
+ offset = vend + 2;
+ }
+
+ // break apart character string
+ value = valueAndComment.substring(1, vend).trim();
+ value = value.replace("''", "'");
+
+
+ if (vend + 1 >= valueAndComment.length()) {
+ comment = null;
+ } else {
+
+ comment = valueAndComment.substring(vend + 1).trim();
+ if (comment.charAt(0) == '/') {
+ if (comment.length() > 1) {
+ comment = comment.substring(1);
+ } else {
+ comment = "";
+ }
+ }
+
+ if (comment.length() == 0) {
+ comment = null;
+ }
+
+ }
+ isString = true;
+
+
+ } else {
+
+ // look for a / to terminate the field.
+ int slashLoc = valueAndComment.indexOf('/');
+ if (slashLoc != -1) {
+ comment = valueAndComment.substring(slashLoc + 1).trim();
+ value = valueAndComment.substring(0, slashLoc).trim();
+ } else {
+ value = valueAndComment;
+ }
+ }
+ }
+
+ /** Process HIERARCH style cards...
+ * HIERARCH LEV1 LEV2 ... = value / comment
+ * The keyword for the card will be "HIERARCH.LEV1.LEV2..."
+ * A '/' is assumed to start a comment.
+ */
+ private void hierarchCard(String card) {
+
+ String name = "";
+ String token = null;
+ String separator = "";
+ int[] tokLimits;
+ int posit = 0;
+ int commStart = -1;
+
+ // First get the hierarchy levels
+ while ((tokLimits = getToken(card, posit)) != null) {
+ token = card.substring(tokLimits[0], tokLimits[1]);
+ if (!token.equals("=")) {
+ name += separator + token;
+ separator = ".";
+ } else {
+ tokLimits = getToken(card, tokLimits[1]);
+ if (tokLimits != null) {
+ token = card.substring(tokLimits[0], tokLimits[1]);
+ } else {
+ key = name;
+ value = null;
+ comment = null;
+ return;
+ }
+ break;
+ }
+ posit = tokLimits[1];
+ }
+ key = name;
+
+
+ // At the end?
+ if (tokLimits == null) {
+ value = null;
+ comment = null;
+ isString = false;
+ return;
+ }
+
+ // Really should consolidate the two instances
+ // of this test in this class!
+ if (token.charAt(0) == '\'') {
+ // Find the next undoubled quote...
+ isString = true;
+ if (token.length() > 1 && token.charAt(1) == '\''
+ && (token.length() == 2 || token.charAt(2) != '\'')) {
+ value = "";
+ commStart = tokLimits[0] + 2;
+ } else if (card.length() < tokLimits[0] + 2) {
+ value = null;
+ comment = null;
+ isString = false;
+ return;
+ } else {
+ int i;
+ for (i = tokLimits[0] + 1; i < card.length(); i += 1) {
+ if (card.charAt(i) == '\'') {
+ if (i == card.length() - 1) {
+ value = card.substring(tokLimits[0] + 1, i);
+ commStart = i + 1;
+ break;
+ } else if (card.charAt(i + 1) == '\'') {
+ // Doubled quotes.
+ i += 1;
+ continue;
+ } else {
+ value = card.substring(tokLimits[0] + 1, i);
+ commStart = i + 1;
+ break;
+ }
+ }
+ }
+ }
+ if (commStart < 0) {
+ value = null;
+ comment = null;
+ isString = false;
+ return;
+ }
+ for (int i = commStart; i < card.length(); i += 1) {
+ if (card.charAt(i) == '/') {
+ comment = card.substring(i + 1).trim();
+ break;
+ } else if (card.charAt(i) != ' ') {
+ comment = null;
+ break;
+ }
+ }
+ } else {
+ isString = false;
+ int sl = token.indexOf('/');
+ if (sl == 0) {
+ value = null;
+ comment = card.substring(tokLimits[0] + 1);
+ } else if (sl > 0) {
+ value = token.substring(0, sl);
+ comment = card.substring(tokLimits[0] + sl + 1);
+ } else {
+ value = token;
+
+ for (int i = tokLimits[1]; i < card.length(); i += 1) {
+ if (card.charAt(i) == '/') {
+ comment = card.substring(i + 1).trim();
+ break;
+ } else if (card.charAt(i) != ' ') {
+ comment = null;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /** Get the next token. Can't use StringTokenizer
+ * since we sometimes need to know the position within
+ * the string.
+ */
+ private int[] getToken(String card, int posit) {
+
+ int i;
+ for (i = posit; i < card.length(); i += 1) {
+ if (card.charAt(i) != ' ') {
+ break;
+ }
+ }
+
+ if (i >= card.length()) {
+ return null;
+ }
+
+ if (card.charAt(i) == '=') {
+ return new int[]{i, i + 1};
+ }
+
+ int j;
+ for (j = i + 1; j < card.length(); j += 1) {
+ if (card.charAt(j) == ' ' || card.charAt(j) == '=') {
+ break;
+ }
+ }
+ return new int[]{i, j};
+ }
+
+ /** Does this card contain a string value?
+ */
+ public boolean isStringValue() {
+ return isString;
+ }
+
+ /** Is this a key/value card?
+ */
+ public boolean isKeyValuePair() {
+ return (key != null && value != null);
+ }
+
+ /** Set the key.
+ */
+ void setKey(String newKey) {
+ key = newKey;
+ }
+
+ /** Return the keyword from this card
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /** Return the value from this card
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /** Set the value for this card.
+ */
+ public void setValue(String update) {
+ value = update;
+ }
+
+ /** Return the comment from this card
+ */
+ public String getComment() {
+ return comment;
+ }
+
+ /** Return the 80 character card image
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer(80);
+
+ // start with the keyword, if there is one
+ if (key != null) {
+ if (key.length() > 9 && key.substring(0, 9).equals("HIERARCH.")) {
+ return hierarchToString();
+ }
+ buf.append(key);
+ if (key.length() < 8) {
+ buf.append(space80.substring(0, 8 - buf.length()));
+ }
+ }
+
+ if (value != null || nullable) {
+ buf.append("= ");
+
+ if (value != null) {
+
+ if (isString) {
+ // left justify the string inside the quotes
+ buf.append('\'');
+ buf.append(value.replace("'", "''"));
+ if (buf.length() < 19) {
+
+ buf.append(space80.substring(0, 19 - buf.length()));
+ }
+ buf.append('\'');
+ // Now add space to the comment area starting at column 40
+ if (buf.length() < 30) {
+ buf.append(space80.substring(0, 30 - buf.length()));
+ }
+
+ } else {
+
+ int offset = buf.length();
+ if (value.length() < 20) {
+ buf.append(space80.substring(0, 20 - value.length()));
+ }
+
+ buf.append(value);
+
+ }
+ } else {
+ // Pad out a null value.
+ buf.append(space80.substring(0, 20));
+ }
+
+ // if there is a comment, add a comment delimiter
+ if (comment != null) {
+ buf.append(" / ");
+ }
+
+ } else if (comment != null && comment.startsWith("= ")) {
+ buf.append(" ");
+ }
+
+ // finally, add any comment
+ if (comment != null) {
+ buf.append(comment);
+ }
+
+ // make sure the final string is exactly 80 characters long
+ if (buf.length() > 80) {
+ buf.setLength(80);
+
+ } else {
+
+ if (buf.length() < 80) {
+ buf.append(space80.substring(0, 80 - buf.length()));
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private String hierarchToString() {
+
+
+ StringBuffer b = new StringBuffer(80);
+ int p = 0;
+ String space = "";
+ while (p < key.length()) {
+ int q = key.indexOf('.', p);
+ if (q < 0) {
+ b.append(space + key.substring(p));
+ break;
+ } else {
+ b.append(space + key.substring(p, q));
+ }
+ space = " ";
+ p = q + 1;
+ }
+
+ if (value != null || nullable) {
+ b.append("= ");
+
+ if (value != null) {
+ // Try to align values
+ int avail = 80 - (b.length() + value.length());
+
+ if (isString) {
+ avail -= 2;
+ }
+ if (comment != null) {
+ avail -= 3 + comment.length();
+ }
+
+ if (avail > 0 && b.length() < 29) {
+ b.append(space80.substring(0, Math.min(avail, 29 - b.length())));
+ }
+
+ if (isString) {
+ b.append('\'');
+ } else if (avail > 0 && value.length() < 10) {
+ b.append(space80.substring(0, Math.min(avail, 10 - value.length())));
+ }
+ b.append(value);
+ if (isString) {
+ b.append('\'');
+ }
+ } else if (b.length() < 30) {
+
+ // Pad out a null value
+ b.append(space80.substring(0, 30 - b.length()));
+ }
+ }
+
+
+ if (comment != null) {
+ b.append(" / " + comment);
+ }
+ if (b.length() < 80) {
+ b.append(space80.substring(0, 80 - b.length()));
+ }
+ String card = new String(b);
+ if (card.length() > 80) {
+ card = card.substring(0, 80);
+ }
+ return card;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+/* This class was contributed by David Glowacki */
+public class HeaderCardException
+ extends FitsException {
+
+ public HeaderCardException() {
+ super();
+ }
+
+ public HeaderCardException(String s) {
+ super(s);
+ }
+}
+
--- /dev/null
+/*
+ * This class provides a modifiable map in which the comment fields for FITS
+ * header keywords
+ * produced by this library are set. The map is a simple String -> String
+ * map where the key Strings are normally class:keyword:id where class is
+ * the class name where the keyword is set, keyword is the keyword set and id
+ * is an integer used to distinguish multiple instances.
+ *
+ * Most users need not worry about this class, but users who wish to customize
+ * the appearance of FITS files may update the map. The code itself is likely
+ * to be needed to understand which values in the map must be modified.
+ *
+ * Note that the Header writing utilities look for the prefix ntf:: in comments
+ * and if this is found, the comment is replaced by looking in this map for
+ * a key given by the remainder of the original comment.
+ */
+
+package nom.tam.fits;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HeaderCommentsMap {
+
+ private static Map<String,String> commentMap = new HashMap<String,String>();
+ static {
+ commentMap.put("header:extend:1", "Extensions are permitted");
+ commentMap.put("header:simple:1", "Java FITS: "+ new java.util.Date());
+ commentMap.put("header:xtension:1", "Java FITS: "+ new java.util.Date());
+ commentMap.put("header:naxis:1", "Dimensionality");
+ commentMap.put("header:extend:2", "Extensions are permitted");
+ commentMap.put("asciitable:pcount:1", "No group data");
+ commentMap.put("asciitable:gcount:1", "One group");
+ commentMap.put("asciitable:tfields:1", "Number of fields in table");
+ commentMap.put("asciitable:tbcolN:1", "Column offset");
+ commentMap.put("asciitable:naxis1:1", "Size of row in bytes");
+ commentMap.put("undefineddata:naxis1:1","Number of Bytes");
+ commentMap.put("undefineddata:extend:1","Extensions are permitted");
+ commentMap.put("binarytablehdu:pcount:1", "Includes heap");
+ commentMap.put("binarytable:naxis1:1", "Bytes per row");
+ commentMap.put("fits:checksum:1", "as of " + FitsDate.getFitsDateString());
+ commentMap.put("basichdu:extend:1", "Allow extensions");
+ commentMap.put("basichdu:gcount:1", "Required value");
+ commentMap.put("basichdu:pcount:1", "Required value");
+ commentMap.put("imagedata:extend:1", "Extension permitted");
+ commentMap.put("imagedata:pcount:1", "No extra parameters");
+ commentMap.put("imagedata:gcount:1", "One group");
+ commentMap.put("tablehdu:tfields:1", "Number of table fields");
+ /* Null entries:
+ * header:bitpix:1
+ * header:simple:2
+ * header:bitpix:2
+ * header:naxisN:1
+ * header:naxis:2
+ * undefineddata:pcount:1
+ * undefineddata:gcount:1
+ * randomgroupsdata:naxis1:1
+ * randomgroupsdata:naxisN:1
+ * randomgroupsdata:groups:1
+ * randomgroupsdata:gcount:1
+ * randomgroupsdata:pcount:1
+ * binarytablehdu:theap:1
+ * binarytablehdu:tdimN:1
+ * asciitable:tformN:1
+ * asciitablehdu:tnullN:1
+ * asciitablehdu:tfields:1
+ * binarytable:pcount:1
+ * binarytable:gcount:1
+ * binarytable:tfields:1
+ * binarytable:tformN:1
+ * binarytable:tdimN:1
+ * tablehdu:naxis2:1
+ */
+ }
+
+ public static String getComment(String key) {
+ return commentMap.get(key);
+ }
+
+ public static void updateComment(String key, String comment) {
+ commentMap.put(key, comment);
+ }
+
+ public static void deleteComment(String key) {
+ commentMap.remove(key);
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+/** This class implements a comparator which ensures
+ * that FITS keywords are written out in a proper order.
+ */
+public class HeaderOrder implements java.util.Comparator {
+
+ /** Can two cards be exchanged when being written out? */
+ public boolean equals(Object a, Object b) {
+ return compare(a, b) == 0;
+ }
+
+ /** Which order should the cards indexed by these keys
+ * be written out? This method assumes that the
+ * arguments are either the FITS Header keywords as
+ * strings, and some other type (or null) for comment
+ * style keywords.
+ *
+ * @return -1 if the first argument should be written first <br>
+ * 1 if the second argument should be written first <br>
+ * 0 if either is legal.
+ */
+ public int compare(Object a, Object b) {
+
+ String c1, c2;
+
+ if (a != null && a instanceof String) {
+ c1 = (String) a;
+ } else {
+ c1 = " ";
+ }
+
+ if (b != null && b instanceof String) {
+ c2 = (String) b;
+ } else {
+ c2 = " ";
+ }
+
+
+ // Equals are equal
+ if (c1.equals(c2)) {
+ return 0;
+ }
+
+ // Now search in the order in which cards must appear
+ // in the header.
+
+ if (c1.equals("SIMPLE") || c1.equals("XTENSION")) {
+ return -1;
+ }
+ if (c2.equals("SIMPLE") || c2.equals("XTENSION")) {
+ return 1;
+ }
+
+ if (c1.equals("BITPIX")) {
+ return -1;
+ }
+ if (c2.equals("BITPIX")) {
+ return 1;
+ }
+
+ if (c1.equals("NAXIS")) {
+ return -1;
+ }
+ if (c2.equals("NAXIS")) {
+ return 1;
+ }
+
+ // Check the NAXISn cards. These must
+ // be in axis order.
+
+ if (naxisN(c1) > 0) {
+ if (naxisN(c2) > 0) {
+ if (naxisN(c1) < naxisN(c2)) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ return -1;
+ }
+
+ if (naxisN(c2) > 0) {
+ return 1;
+ }
+
+ if (c1.equals("PCOUNT")) {
+ return -1;
+ }
+ if (c2.equals("PCOUNT")) {
+ return 1;
+ }
+
+ if (c1.equals("GCOUNT")) {
+ return -1;
+ }
+ if (c2.equals("GCOUNT")) {
+ return 1;
+ }
+
+ if (c1.equals("TFIELDS")) {
+ return -1;
+ }
+ if (c2.equals("TFIELDS")) {
+ return 1;
+ }
+
+ // In principal this only needs to be in the first 36 cards,
+ // but we put it here since it's convenient. BLOCKED is
+ // deprecated currently.
+ if (c1.equals("BLOCKED")) {
+ return -1;
+ }
+ if (c2.equals("BLOCKED")) {
+ return 1;
+ }
+
+ // Note that this must be at the end, so the
+ // values returned are inverted.
+ if (c1.equals("END")) {
+ return 1;
+ }
+ if (c2.equals("END")) {
+ return -1;
+ }
+
+ // All other cards can be in any order.
+ return 0;
+ }
+
+ /** Find the index for NAXISn keywords */
+ private int naxisN(String key) {
+
+ if (key.length() > 5 && key.substring(0, 5).equals("NAXIS")) {
+ for (int i = 5; i < key.length(); i += 1) {
+
+ boolean number = true;
+ char c = key.charAt(i);
+ if ('0' > c || c > '9') {
+ number = false;
+ break;
+ }
+ if (number) {
+ return Integer.parseInt(key.substring(5));
+ }
+ }
+ }
+ return -1;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+import java.lang.reflect.Array;
+import nom.tam.image.ImageTiler;
+import nom.tam.util.*;
+import java.io.*;
+
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/** This class instantiates FITS primary HDU and IMAGE extension data.
+ * Essentially these data are a primitive multi-dimensional array.
+ * <p>
+ * Starting in version 0.9 of the FITS library, this routine
+ * allows users to defer the reading of images if the FITS
+ * data is being read from a file. An ImageTiler object is
+ * supplied which can return an arbitrary subset of the image
+ * as a one dimensional array -- suitable for manipulation by
+ * standard Java libraries. A call to the getData() method
+ * will still return a multi-dimensional array, but the
+ * image data will not be read until the user explicitly requests.
+ * it.
+ */
+public class ImageData extends Data {
+
+ /** The size of the data */
+ long byteSize;
+ /** The actual array of data. This
+ * is normally a multi-dimensional primitive array.
+ * It may be null until the getData() routine is
+ * invoked, or it may be filled by during the read
+ * call when a non-random access device is used.
+ */
+ Object dataArray;
+
+ /** This class describes an array */
+ protected class ArrayDesc {
+
+ int[] dims;
+ Class type;
+
+ ArrayDesc(int[] dims, Class type) {
+ this.dims = dims;
+ this.type = type;
+ }
+ }
+ /** A description of what the data should look like */
+ ArrayDesc dataDescription;
+
+ /** This inner class allows the ImageTiler
+ * to see if the user has read in the data.
+ */
+ protected class ImageDataTiler extends nom.tam.image.ImageTiler {
+
+ ImageDataTiler(RandomAccess o, long offset, ArrayDesc d) {
+ super(o, offset, d.dims, d.type);
+ }
+
+ public Object getMemoryImage() {
+ return dataArray;
+ }
+ }
+ /** The image tiler associated with this image. */
+ private ImageTiler tiler;
+
+ /** Create an array from a header description.
+ * This is typically how data will be created when reading
+ * FITS data from a file where the header is read first.
+ * This creates an empty array.
+ * @param h header to be used as a template.
+ * @exception FitsException if there was a problem with the header description.
+ */
+ public ImageData(Header h) throws FitsException {
+
+ dataDescription = parseHeader(h);
+ }
+
+ protected ArrayDesc parseHeader(Header h) throws FitsException {
+
+ int bitpix;
+ int type;
+ int ndim;
+ int[] dims;
+
+ int i;
+
+ Object dataArray;
+
+ Class baseClass;
+
+
+ int gCount = h.getIntValue("GCOUNT", 1);
+ int pCount = h.getIntValue("PCOUNT", 0);
+ if (gCount > 1 || pCount != 0) {
+ throw new FitsException("Group data treated as images");
+ }
+
+ bitpix = h.getIntValue("BITPIX", 0);
+
+ if (bitpix == 8) {
+ baseClass = Byte.TYPE;
+ } else if (bitpix == 16) {
+ baseClass = Short.TYPE;
+ } else if (bitpix == 32) {
+ baseClass = Integer.TYPE;
+ } else if (bitpix == 64) {
+ baseClass = Long.TYPE;
+ } else if (bitpix == -32) {
+ baseClass = Float.TYPE;
+ } else if (bitpix == -64) {
+ baseClass = Double.TYPE;
+ } else {
+ throw new FitsException("Invalid BITPIX:" + bitpix);
+ }
+
+ ndim = h.getIntValue("NAXIS", 0);
+ dims = new int[ndim];
+
+
+ // Note that we have to invert the order of the axes
+ // for the FITS file to get the order in the array we
+ // are generating.
+
+ byteSize = 1;
+ for (i = 0; i < ndim; i += 1) {
+ int cdim = h.getIntValue("NAXIS" + (i + 1), 0);
+ if (cdim < 0) {
+ throw new FitsException("Invalid array dimension:" + cdim);
+ }
+ byteSize *= cdim;
+ dims[ndim - i - 1] = cdim;
+ }
+ byteSize *= Math.abs(bitpix) / 8;
+ if (ndim == 0) {
+ byteSize = 0;
+ }
+ return new ArrayDesc(dims, baseClass);
+ }
+
+ /** Create the equivalent of a null data element.
+ */
+ public ImageData() {
+ dataArray = new byte[0];
+ byteSize = 0;
+ }
+
+ /** Create an ImageData object using the specified object to
+ * initialize the data array.
+ * @param x The initial data array. This should be a primitive
+ * array but this is not checked currently.
+ */
+ public ImageData(Object x) {
+ dataArray = x;
+ byteSize = ArrayFuncs.computeLSize(x);
+ }
+
+ /** Fill header with keywords that describe
+ * image data.
+ * @param head The FITS header
+ * @exception FitsException if the object does not contain
+ * valid image data.
+ */
+ protected void fillHeader(Header head) throws FitsException {
+
+
+ if (dataArray == null) {
+ head.nullImage();
+ return;
+ }
+
+ String classname = dataArray.getClass().getName();
+
+ int[] dimens = ArrayFuncs.getDimensions(dataArray);
+
+ if (dimens == null || dimens.length == 0) {
+ throw new FitsException("Image data object not array");
+ }
+
+
+ int bitpix;
+ switch (classname.charAt(dimens.length)) {
+ case 'B':
+ bitpix = 8;
+ break;
+ case 'S':
+ bitpix = 16;
+ break;
+ case 'I':
+ bitpix = 32;
+ break;
+ case 'J':
+ bitpix = 64;
+ break;
+ case 'F':
+ bitpix = -32;
+ break;
+ case 'D':
+ bitpix = -64;
+ break;
+ default:
+ throw new FitsException("Invalid Object Type for FITS data:"
+ + classname.charAt(dimens.length));
+ }
+
+ // if this is neither a primary header nor an image extension,
+ // make it a primary header
+ head.setSimple(true);
+ head.setBitpix(bitpix);
+ head.setNaxes(dimens.length);
+
+ for (int i = 1; i <= dimens.length; i += 1) {
+ if (dimens[i - 1] == -1) {
+ throw new FitsException("Unfilled array for dimension: " + i);
+ }
+ head.setNaxis(i, dimens[dimens.length - i]);
+ }
+ head.addValue("EXTEND", true,"ntf::imagedata:extend:1"); // Just in case!
+ head.addValue("PCOUNT", 0, "ntf::imagedata:pcount:1");
+ head.addValue("GCOUNT", 1, "ntf::imagedata:gcount:1");
+
+ }
+
+ public void read(ArrayDataInput i) throws FitsException {
+
+ // Don't need to read null data (noted by Jens Knudstrup)
+ if (byteSize == 0) {
+ return;
+ }
+ setFileOffset(i);
+
+
+ if (i instanceof RandomAccess) {
+ tiler = new ImageDataTiler((RandomAccess) i,
+ ((RandomAccess) i).getFilePointer(),
+ dataDescription);
+ try {
+ // Handle long skips.
+ i.skipBytes(byteSize);
+ } catch (IOException e) {
+ throw new FitsException("Unable to skip over image:" + e);
+ }
+
+ } else {
+ dataArray = ArrayFuncs.newInstance(dataDescription.type,
+ dataDescription.dims);
+ try {
+ i.readLArray(dataArray);
+ } catch (IOException e) {
+ throw new FitsException("Unable to read image data:" + e);
+ }
+
+ tiler = new ImageDataTiler(null, 0, dataDescription);
+ }
+
+ int pad = FitsUtil.padding(getTrueSize());
+ try {
+ i.skipBytes(pad);
+ } catch (EOFException e) {
+ throw new PaddingException("Error skipping padding after image", this);
+ } catch (IOException e) {
+ throw new FitsException("Error skipping padding after image");
+ }
+ }
+
+ public void write(ArrayDataOutput o) throws FitsException {
+
+ // Don't need to write null data (noted by Jens Knudstrup)
+ if (byteSize == 0) {
+ return;
+ }
+
+ if (dataArray == null) {
+ if (tiler != null) {
+
+ // Need to read in the whole image first.
+ try {
+ dataArray = tiler.getCompleteImage();
+ } catch (IOException e) {
+ throw new FitsException("Error attempting to fill image");
+ }
+
+ } else if (dataArray == null && dataDescription != null) {
+ // Need to create an array to match a specified header.
+ dataArray = ArrayFuncs.newInstance(dataDescription.type,
+ dataDescription.dims);
+
+ } else {
+ // This image isn't ready to be written!
+ throw new FitsException("Null image data");
+ }
+ }
+
+ try {
+ o.writeArray(dataArray);
+ } catch (IOException e) {
+ throw new FitsException("IO Error on image write" + e);
+ }
+
+ FitsUtil.pad(o, getTrueSize());
+ }
+
+ /** Get the size in bytes of the data */
+ protected long getTrueSize() {
+ return byteSize;
+ }
+
+ /** Return the actual data.
+ * Note that this may return a null when
+ * the data is not readable. It might be better
+ * to throw a FitsException, but this is
+ * a very commonly called method and we prefered
+ * not to change how users must invoke it.
+ */
+ public Object getData() {
+
+ if (dataArray == null && tiler != null) {
+ try {
+ dataArray = tiler.getCompleteImage();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ return dataArray;
+ }
+
+ void setTiler(ImageTiler tiler) {
+ this.tiler = tiler;
+ }
+
+ public ImageTiler getTiler() {
+ return tiler;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.BufferedDataInputStream;
+import nom.tam.image.ImageTiler;
+
+/** FITS image header/data unit */
+public class ImageHDU
+ extends BasicHDU {
+
+ /** Build an image HDU using the supplied data.
+ * @param obj the data used to build the image.
+ * @exception FitsException if there was a problem with the data.
+ */
+ public ImageHDU(Header h, Data d)
+ throws FitsException {
+ myData = d;
+ myHeader = h;
+
+ }
+
+ /** Indicate that Images can appear at the beginning of a FITS dataset */
+ protected boolean canBePrimary() {
+ return true;
+ }
+
+ /** Change the Image from/to primary */
+ protected void setPrimaryHDU(boolean status) {
+
+ try {
+ super.setPrimaryHDU(status);
+ } catch (FitsException e) {
+ System.err.println("Impossible exception in ImageData");
+ }
+
+ if (status) {
+ myHeader.setSimple(true);
+ } else {
+ myHeader.setXtension("IMAGE");
+ }
+ }
+
+ /** Check that this HDU has a valid header for this type.
+ * @return <CODE>true</CODE> if this HDU has a valid header.
+ */
+ public static boolean isHeader(Header hdr) {
+ boolean found = false;
+ found = hdr.getBooleanValue("SIMPLE");
+ if (!found) {
+ String s = hdr.getStringValue("XTENSION");
+ if (s != null) {
+ if (s.trim().equals("IMAGE") || s.trim().equals("IUEIMAGE")) {
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ return !hdr.getBooleanValue("GROUPS");
+ }
+
+ /** Check if this object can be described as a FITS image.
+ * @param o The Object being tested.
+ */
+ public static boolean isData(Object o) {
+ String s = o.getClass().getName();
+
+ int i;
+ for (i = 0; i < s.length(); i += 1) {
+ if (s.charAt(i) != '[') {
+ break;
+ }
+ }
+
+ // Allow all non-boolean/Object arrays.
+ // This does not check the rectangularity of the array though.
+ if (i <= 0 || s.charAt(i) == 'L' || s.charAt(i) == 'Z') {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /** Create a Data object to correspond to the header description.
+ * @return An unfilled Data object which can be used to read
+ * in the data for this HDU.
+ * @exception FitsException if the image extension could not be created.
+ */
+ public Data manufactureData()
+ throws FitsException {
+ return manufactureData(myHeader);
+ }
+
+ public static Data manufactureData(Header hdr)
+ throws FitsException {
+ return new ImageData(hdr);
+ }
+
+ /** Create a header that describes the given
+ * image data.
+ * @param o The image to be described.
+ * @exception FitsException if the object does not contain
+ * valid image data.
+ */
+ public static Header manufactureHeader(Data d)
+ throws FitsException {
+
+ if (d == null) {
+ return null;
+ }
+
+ Header h = new Header();
+ d.fillHeader(h);
+
+ return h;
+ }
+
+ /** Encapsulate an object as an ImageHDU. */
+ public static Data encapsulate(Object o) throws FitsException {
+ return new ImageData(o);
+ }
+
+ public ImageTiler getTiler() {
+ return ((ImageData) myData).getTiler();
+ }
+
+ /** Print out some information about this HDU.
+ */
+ public void info() {
+ if (isHeader(myHeader)) {
+ System.out.println(" Image");
+ } else {
+ System.out.println(" Image (bad header)");
+ }
+
+ System.out.println(" Header Information:");
+ System.out.println(" BITPIX=" + myHeader.getIntValue("BITPIX", -1));
+ int naxis = myHeader.getIntValue("NAXIS", -1);
+ System.out.println(" NAXIS=" + naxis);
+ for (int i = 1; i <= naxis; i += 1) {
+ System.out.println(" NAXIS" + i + "="
+ + myHeader.getIntValue("NAXIS" + i, -1));
+ }
+
+ System.out.println(" Data information:");
+ try {
+ if (myData.getData() == null) {
+ System.out.println(" No Data");
+ } else {
+ System.out.println(" "
+ + ArrayFuncs.arrayDescription(myData.getData()));
+ }
+ } catch (Exception e) {
+ System.out.println(" Unable to get data");
+ }
+ }
+}
--- /dev/null
+In BinaryTable:
+
+ -- For ComplexData has bSize *= 2, size*=2.
+ Not sure this is right. Seems to be handled by adding dimension...
+
+ -- getTformType (and getTformLength) methods
+ Changed name to ...TFORM... and made accessible to
+ other classes by giving it package level access.
+
+
+In FitsFactory
+ -- Added setCheckAsciiStrings flag (which defaults to false).
+ If set then when a warning will be noted if invalid ASCII characters
+ are found, but the program will not fail.
+
+ -- Added ASCII charset variable.
+
+In HeaderCard, FitsUtil, nom.tam.util.ByteParser
+ -- Conversions of bytes to strings now use the FitsFactory.ASCII charset.
+
+
+In Header
+ -- Fixed bug with positioning of header card in positionAfterIndex
+
+Changes to changes
+
+In BinaryTable: didn't change getTFORMType to protected but to package
+level access, since that is actual what we are using.
+
+Did not make classes final. I can easily conceive of users wishing to extend
+classes like BinaryTable to customize to their own needs.
+
+Did not incorporate the small formatting changes. Many of them (including
+the spacing in expressions would probably be a good idea), but I've become
+very wary of using the ++ and -- operators (see my contribution in the thread at
+http://groups.google.com/group/comp.lang.java.programmer/browse_frm/thread/357b4587c4b36352/dcb152343ace64f5?lnk=gst&q=mcglynn#dcb152343ace64f5
+for some of the motivation).
+
+Did not incorporate changes to packages (not that I expected to).
--- /dev/null
+package nom.tam.fits;
+
+/**
+ * This exception is thrown if an error is found
+ * reading the padding following a valid FITS HDU.
+ * This padding is required by the FITS standard, but
+ * some FITS writes forego writing it. To access such data
+ * users can use something like:
+ *
+ * <code>
+ * Fits f = new Fits("somefile");
+ * try {
+ * f.read();
+ * } catch (PaddingException e) {
+ * f.addHDU(e.getHDU());
+ * }
+ * </code>
+ * to ensure that a truncated HDU is included in the FITS object.
+ * Generally the FITS file have already added any HDUs prior
+ * to the truncatd one.
+ */
+public class PaddingException extends FitsException {
+
+ /** The HDU where the error happened.
+ */
+ private BasicHDU truncatedHDU;
+
+ /**
+ * When the error is thrown, the data object being
+ * read must be supplied. We initially create a dummy
+ * header for this. If someone is reading the entire
+ * HDU, then they can trap the exception and set the header
+ * to the appropriate value.
+ */
+ public PaddingException(Data datum) throws FitsException {
+ truncatedHDU = FitsFactory.HDUFactory(datum.getKernel());
+ // We want to use the original Data object... so
+ truncatedHDU = FitsFactory.HDUFactory(truncatedHDU.getHeader(), datum);
+ }
+
+ public PaddingException(String msg, Data datum) throws FitsException {
+ super(msg);
+ truncatedHDU = FitsFactory.HDUFactory(datum.getKernel());
+ truncatedHDU = FitsFactory.HDUFactory(truncatedHDU.getHeader(), datum);
+ }
+
+ void updateHeader(Header hdr) throws FitsException {
+ truncatedHDU = FitsFactory.HDUFactory(hdr, truncatedHDU.getData());
+ }
+
+ public BasicHDU getTruncatedHDU() {
+ return truncatedHDU;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+/* Copyright: Thomas McGlynn 1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import nom.tam.util.*;
+import java.io.IOException;
+import java.io.EOFException;
+
+/** This class instantiates FITS Random Groups data.
+ * Random groups are instantiated as a two-dimensional
+ * array of objects. The first dimension of the array
+ * is the number of groups. The second dimension is 2.
+ * The first object in every row is a one dimensional
+ * parameter array. The second element is the n-dimensional
+ * data array.
+ */
+public class RandomGroupsData extends Data {
+
+ private Object[][] dataArray;
+
+ /** Create the equivalent of a null data element.
+ */
+ public RandomGroupsData() {
+ dataArray = new Object[0][];
+ }
+
+ /** Create a RandomGroupsData object using the specified object to
+ * initialize the data array.
+ * @param x The initial data array. This should a two-d
+ * array of objects as described above.
+ */
+ public RandomGroupsData(Object[][] x) {
+ dataArray = x;
+ }
+
+ /** Get the size of the actual data element. */
+ protected long getTrueSize() {
+
+ if (dataArray != null && dataArray.length > 0) {
+ return (ArrayFuncs.computeLSize(dataArray[0][0]) +
+ ArrayFuncs.computeLSize(dataArray[0][1])) * dataArray.length;
+ } else {
+ return 0;
+ }
+ }
+
+ /** Read the RandomGroupsData */
+ public void read(ArrayDataInput str) throws FitsException {
+
+ setFileOffset(str);
+
+ try {
+ str.readLArray(dataArray);
+ } catch (IOException e) {
+ throw new FitsException("IO error reading Random Groups data "+e);
+ }
+ int pad = FitsUtil.padding(getTrueSize());
+ try {
+ str.skipBytes(pad);
+ } catch (EOFException e) {
+ throw new PaddingException("EOF reading padding after random groups", this);
+ } catch (IOException e) {
+ throw new FitsException("IO error reading padding after random groups");
+ }
+ }
+
+ /** Write the RandomGroupsData */
+ public void write(ArrayDataOutput str) throws FitsException {
+ try {
+ str.writeArray(dataArray);
+ FitsUtil.pad(str, getTrueSize());
+ } catch (IOException e) {
+ throw new FitsException("IO error writing random groups data "+e);
+ }
+ }
+
+ protected void fillHeader(Header h) throws FitsException {
+
+ if (dataArray.length <= 0 || dataArray[0].length != 2) {
+ throw new FitsException("Data not conformable to Random Groups");
+ }
+
+ int gcount = dataArray.length;
+ Object paraSamp = dataArray[0][0];
+ Object dataSamp = dataArray[0][1];
+
+ Class pbase = nom.tam.util.ArrayFuncs.getBaseClass(paraSamp);
+ Class dbase = nom.tam.util.ArrayFuncs.getBaseClass(dataSamp);
+
+ if (pbase != dbase) {
+ throw new FitsException("Data and parameters do not agree in type for random group");
+ }
+
+ int[] pdims = nom.tam.util.ArrayFuncs.getDimensions(paraSamp);
+ int[] ddims = nom.tam.util.ArrayFuncs.getDimensions(dataSamp);
+
+ if (pdims.length != 1) {
+ throw new FitsException("Parameters are not 1 d array for random groups");
+ }
+
+ // Got the information we need to build the header.
+
+ h.setSimple(true);
+ if (dbase == byte.class) {
+ h.setBitpix(8);
+ } else if (dbase == short.class) {
+ h.setBitpix(16);
+ } else if (dbase == int.class) {
+ h.setBitpix(32);
+ } else if (dbase == long.class) { // Non-standard
+ h.setBitpix(64);
+ } else if (dbase == float.class) {
+ h.setBitpix(-32);
+ } else if (dbase == double.class) {
+ h.setBitpix(-64);
+ } else {
+ throw new FitsException("Data type:"+dbase+" not supported for random groups");
+ }
+
+
+ h.setNaxes(ddims.length+1);
+ h.addValue("NAXIS1", 0, "ntf::randomgroupsdata:naxis1:1");
+ for (int i=2; i<=ddims.length+1; i += 1) {
+ h.addValue("NAXIS"+i, ddims[i-2], "ntf::randomgroupsdata:naxisN:1");
+ }
+
+ h.addValue("GROUPS", true, "ntf::randomgroupsdata:groups:1");
+ h.addValue("GCOUNT", dataArray.length, "ntf::randomgroupsdata:gcount:1");
+ h.addValue("PCOUNT", pdims[0], "ntf::randomgroupsdata:pcount:1");
+ }
+
+ public Object getData() {
+ return dataArray;
+ }
+
+
+}
--- /dev/null
+package nom.tam.fits;
+
+import nom.tam.util.ArrayFuncs;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/** Random groups HDUs. Note that the internal storage of random
+ * groups is a Object[ngroup][2] array. The first element of
+ * each group is the parameter data from that group. The second element
+ * is the data. The parameters should be a one dimensional array
+ * of the primitive types byte, short, int, long, float or double.
+ * The second element is a n-dimensional array of the same type.
+ * When analyzing group data structure only the first group is examined,
+ * but for a valid FITS file all groups must have the same structure.
+ */
+public class RandomGroupsHDU extends BasicHDU {
+
+ Object dataArray;
+
+ /** Create an HDU from the given header and data */
+ public RandomGroupsHDU(Header h, Data d) {
+ myHeader = h;
+ myData = d;
+ }
+
+ /** Indicate that a RandomGroupsHDU can come at
+ * the beginning of a FITS file.
+ */
+ protected boolean canBePrimary() {
+ return true;
+ }
+
+ /** Move a RandomGroupsHDU to or from the beginning
+ * of a FITS file. Note that the FITS standard only
+ * supports Random Groups data at the beginning
+ * of the file, but we allow it within Image extensions.
+ */
+ protected void setPrimaryHDU(boolean status) {
+ try {
+ super.setPrimaryHDU(status);
+ } catch (FitsException e) {
+ System.err.println("Unreachable catch in RandomGroupsHDU");
+ }
+ if (status) {
+ myHeader.setSimple(true);
+ } else {
+ myHeader.setXtension("IMAGE");
+ }
+ }
+
+ /** Make a header point to the given object.
+ * @param odata The random groups data the header should describe.
+ */
+ static Header manufactureHeader(Data d) throws FitsException {
+
+ if (d == null) {
+ throw new FitsException("Attempt to create null Random Groups data");
+ }
+ Header h = new Header();
+ d.fillHeader(h);
+ return h;
+
+ }
+
+ /** Is this a random groups header?
+ * @param myHeader The header to be tested.
+ */
+ public static boolean isHeader(Header hdr) {
+
+ if (hdr.getBooleanValue("SIMPLE")) {
+ return hdr.getBooleanValue("GROUPS");
+ }
+
+ String s = hdr.getStringValue("XTENSION");
+ if (s.trim().equals("IMAGE")) {
+ return hdr.getBooleanValue("GROUPS");
+ }
+
+ return false;
+ }
+
+ /** Check that this HDU has a valid header.
+ * @return <CODE>true</CODE> if this HDU has a valid header.
+ */
+ public boolean isHeader() {
+ return isHeader(myHeader);
+ }
+
+ /** Check if this data is compatible with Random Groups structure.
+ * Must be an Object[ngr][2] structure with both elements of each
+ * group having the same base type and the first element being
+ * a simple primitive array. We do not check anything but
+ * the first row.
+ */
+ public static boolean isData(Object oo) {
+ if (oo instanceof Object[][]) {
+
+ Object[][] o = (Object[][]) oo;
+
+ if (o.length > 0) {
+ if (o[0].length == 2) {
+ if (ArrayFuncs.getBaseClass(o[0][0])
+ == ArrayFuncs.getBaseClass(o[0][1])) {
+ String cn = o[0][0].getClass().getName();
+ if (cn.length() == 2 && cn.charAt(1) != 'Z'
+ || cn.charAt(1) != 'C') {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Create a FITS Data object corresponding to
+ * this HDU header.
+ */
+ public Data manufactureData() throws FitsException {
+ return manufactureData(myHeader);
+ }
+
+ /** Create FITS data object corresponding to a given header.
+ */
+ public static Data manufactureData(Header hdr) throws FitsException {
+
+ int gcount = hdr.getIntValue("GCOUNT", -1);
+ int pcount = hdr.getIntValue("PCOUNT", -1);
+
+ if (!hdr.getBooleanValue("GROUPS")
+ || hdr.getIntValue("NAXIS1", -1) != 0
+ || gcount < 0 || pcount < 0
+ || hdr.getIntValue("NAXIS") < 2) {
+ throw new FitsException("Invalid Random Groups Parameters");
+ }
+
+ // Allocate the object.
+ Object[][] dataArray;
+
+ if (gcount > 0) {
+ dataArray = new Object[gcount][2];
+ } else {
+ dataArray = new Object[0][];
+ }
+
+ Object[] sampleRow = generateSampleRow(hdr);
+ for (int i = 0; i < gcount; i += 1) {
+ ((Object[][]) dataArray)[i][0] =
+ ((Object[]) nom.tam.util.ArrayFuncs.deepClone(sampleRow))[0];
+ ((Object[][]) dataArray)[i][1] =
+ ((Object[]) nom.tam.util.ArrayFuncs.deepClone(sampleRow))[1];
+ }
+ return new RandomGroupsData(dataArray);
+
+ }
+
+ static Object[] generateSampleRow(Header h)
+ throws FitsException {
+
+ int ndim = h.getIntValue("NAXIS", 0) - 1;
+ int[] dims = new int[ndim];
+
+ int bitpix = h.getIntValue("BITPIX", 0);
+
+
+ Class baseClass;
+
+ switch (bitpix) {
+ case 8:
+ baseClass = Byte.TYPE;
+ break;
+ case 16:
+ baseClass = Short.TYPE;
+ break;
+ case 32:
+ baseClass = Integer.TYPE;
+ break;
+ case 64:
+ baseClass = Long.TYPE;
+ break;
+ case -32:
+ baseClass = Float.TYPE;
+ break;
+ case -64:
+ baseClass = Double.TYPE;
+ break;
+ default:
+ throw new FitsException("Invalid BITPIX:" + bitpix);
+ }
+
+ // Note that we have to invert the order of the axes
+ // for the FITS file to get the order in the array we
+ // are generating. Also recall that NAXIS1=0, so that
+ // we have an 'extra' dimension.
+
+ for (int i = 0; i < ndim; i += 1) {
+ long cdim = h.getIntValue("NAXIS" + (i + 2), 0);
+ if (cdim < 0) {
+ throw new FitsException("Invalid array dimension:" + cdim);
+ }
+ dims[ndim - i - 1] = (int) cdim;
+ }
+
+ Object[] sample = new Object[2];
+ sample[0] = ArrayFuncs.newInstance(baseClass, h.getIntValue("PCOUNT"));
+ sample[1] = ArrayFuncs.newInstance(baseClass, dims);
+
+ return sample;
+ }
+
+ public static Data encapsulate(Object o) throws FitsException {
+ if (o instanceof Object[][]) {
+ return new RandomGroupsData((Object[][]) o);
+ } else {
+ throw new FitsException("Attempt to encapsulate invalid data in Random Group");
+ }
+ }
+
+ /** Display structural information about the current HDU.
+ */
+ public void info() {
+
+ System.out.println("Random Groups HDU");
+ if (myHeader != null) {
+ System.out.println(" HeaderInformation:");
+ System.out.println(" Ngroups:" + myHeader.getIntValue("GCOUNT"));
+ System.out.println(" Npar: " + myHeader.getIntValue("PCOUNT"));
+ System.out.println(" BITPIX: " + myHeader.getIntValue("BITPIX"));
+ System.out.println(" NAXIS: " + myHeader.getIntValue("NAXIS"));
+ for (int i = 0; i < myHeader.getIntValue("NAXIS"); i += 1) {
+ System.out.println(" NAXIS" + (i + 1) + "= "
+ + myHeader.getIntValue("NAXIS" + (i + 1)));
+ }
+ } else {
+ System.out.println(" No Header Information");
+ }
+
+
+ Object[][] data = null;
+ if (myData != null) {
+ try {
+ data = (Object[][]) myData.getData();
+ } catch (FitsException e) {
+ data = null;
+ }
+ }
+
+ if (data == null || data.length < 1 || data[0].length != 2) {
+ System.out.println(" Invalid/unreadable data");
+ } else {
+ System.out.println(" Number of groups:" + data.length);
+ System.out.println(" Parameters: " + nom.tam.util.ArrayFuncs.arrayDescription(data[0][0]));
+ System.out.println(" Data:" + nom.tam.util.ArrayFuncs.arrayDescription(data[0][1]));
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+
+/** This class allows FITS binary and ASCII tables to
+ * be accessed via a common interface.
+ */
+
+public interface TableData {
+
+ public abstract Object[] getRow (int row) throws FitsException;
+ public abstract Object getColumn (int col) throws FitsException;
+ public abstract Object getElement(int row, int col) throws FitsException;
+
+ public abstract void setRow (int row, Object[] newRow) throws FitsException;
+ public abstract void setColumn (int col, Object newCol) throws FitsException;
+ public abstract void setElement (int row, int col, Object element) throws FitsException;
+
+ public abstract int addRow (Object[] newRow) throws FitsException;
+ public abstract int addColumn(Object newCol) throws FitsException;
+
+ public abstract void deleteRows(int row, int len) throws FitsException;
+ public abstract void deleteColumns(int row, int len) throws FitsException;
+
+ public abstract void updateAfterDelete(int oldNcol, Header hdr) throws FitsException;
+
+ public abstract int getNCols();
+ public abstract int getNRows();
+
+}
--- /dev/null
+package nom.tam.fits;
+
+import java.util.Iterator;
+import nom.tam.util.Cursor;
+
+/** This class allows FITS binary and ASCII tables to
+ * be accessed via a common interface.
+ *
+ * Bug Fix: 3/28/01 to findColumn.
+ */
+public abstract class TableHDU extends BasicHDU {
+
+ private TableData table;
+ private int currentColumn;
+
+ /** Create the TableHDU. Note that this
+ * will normally only be invoked by subclasses
+ * in the FITS package.
+ * @param td The data for the table.
+ */
+ TableHDU(TableData td) {
+ table = td;
+ }
+
+ /** Get a specific row of the table */
+ public Object[] getRow(int row) throws FitsException {
+ return table.getRow(row);
+ }
+
+ /** Get a specific column of the table where
+ * the column name is specified using the TTYPEn keywords
+ * in the header.
+ * @param colName The name of the column to be extracted.
+ * @throws FitsException
+ */
+ public Object getColumn(String colName) throws FitsException {
+ return getColumn(findColumn(colName));
+ }
+
+ /** Get a specific column from the table using 0-based column
+ * indexing.
+ */
+ public Object getColumn(int col) throws FitsException {
+ return table.getColumn(col);
+ }
+
+ /** Get all of the columns of the table.
+ */
+ public Object[] getColumns() throws FitsException {
+ Object[] result = new Object[getNCols()];
+ for (int i = 0; i < result.length; i += 1) {
+ result[i] = getColumn(i);
+ }
+ return result;
+ }
+
+ /** Get a specific element of the table using 0-based indices.
+ *
+ */
+ public Object getElement(int row, int col) throws FitsException {
+ return table.getElement(row, col);
+ }
+
+ /** Update a row within a table.
+ *
+ */
+ public void setRow(int row, Object[] newRow) throws FitsException {
+ table.setRow(row, newRow);
+ }
+
+ /** Update a column within a table. The new column should have the
+ * same format as the column being replaced.
+ */
+ public void setColumn(String colName, Object newCol) throws FitsException {
+ setColumn(findColumn(colName), newCol);
+ }
+
+ /** Update a column within a table. The new column should have the same
+ * format ast the column being replaced.
+ */
+ public void setColumn(int col, Object newCol) throws FitsException {
+ table.setColumn(col, newCol);
+ }
+
+ /** Update a single element within the table.
+ */
+ public void setElement(int row, int col, Object element) throws FitsException {
+ table.setElement(row, col, element);
+ }
+
+ /** Add a row to the end of the table. If this is the first row,
+ * then this will add appropriate columns for each of the entries.
+ */
+ public int addRow(Object[] newRow) throws FitsException {
+
+ int row = table.addRow(newRow);
+ myHeader.addValue("NAXIS2", row, "ntf::tablehdu:naxis2:1");
+ return row;
+ }
+
+ /** Find the 0-based column index corresponding to a particular
+ * column name.
+ */
+ public int findColumn(String colName) {
+
+ for (int i = 0; i < getNCols(); i += 1) {
+
+ String val = myHeader.getStringValue("TTYPE" + (i + 1));
+ if (val != null && val.trim().equals(colName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /** Add a column to the table. */
+ public abstract int addColumn(Object data) throws FitsException;
+
+ /** Get the number of columns for this table
+ * @return The number of columns in the table.
+ */
+ public int getNCols() {
+ return table.getNCols();
+ }
+
+ /** Get the number of rows for this table
+ * @return The number of rows in the table.
+ */
+ public int getNRows() {
+ return table.getNRows();
+ }
+
+ /** Get the name of a column in the table.
+ * @param index The 0-based column index.
+ * @return The column name.
+ * @exception FitsException if an invalid index was requested.
+ */
+ public String getColumnName(int index) {
+
+ String ttype = myHeader.getStringValue("TTYPE" + (index + 1));
+ if (ttype != null) {
+ ttype = ttype.trim();
+ }
+ return ttype;
+ }
+
+ public void setColumnName(int index, String name, String comment)
+ throws FitsException {
+ setColumnMeta(index, "TTYPE", name, comment, true);
+ }
+
+ /** Specify column metadata for a given column in a way that
+ * allows all of the column metadata for a given column
+ * to be organized together.
+ *
+ * @param index The 0-based index of the column
+ * @param key The column key. I.e., the keyword will be key+(index+1)
+ * @param value The value to be placed in the header.
+ * @param comment The comment for the header
+ * @param after Should the header card be after the current column metadata block
+ * (true), or immediately before the TFORM card (false).
+ * @throws FitsException
+ */
+ public void setColumnMeta(int index, String key, String value, String comment, boolean after)
+ throws FitsException {
+ setCurrentColumn(index, after);
+ myHeader.addValue(key + (index + 1), value, comment);
+ }
+
+ /** Convenience method for getting column data. Note that this works
+ * only for metadata that returns a string value. This is equivalent
+ * to getStringValue(type+index);
+ */
+ public String getColumnMeta(int index, String type) {
+ return myHeader.getStringValue(type+(index+1));
+ }
+
+ public void setColumnMeta(int index, String key, String value, String comment)
+ throws FitsException {
+ setColumnMeta(index, key, value, comment, true);
+ }
+
+ public void setColumnMeta(int index, String key, long value, String comment, boolean after)
+ throws FitsException {
+ setCurrentColumn(index, after);
+ myHeader.addValue(key + (index + 1), value, comment);
+ }
+
+ public void setColumnMeta(int index, String key, double value, String comment, boolean after)
+ throws FitsException {
+ setCurrentColumn(index, after);
+ myHeader.addValue(key + (index + 1), value, comment);
+ }
+
+ public void setColumnMeta(int index, String key, boolean value, String comment, boolean after)
+ throws FitsException {
+ setCurrentColumn(index, after);
+ myHeader.addValue(key + (index + 1), value, comment);
+ }
+
+ /** Get the FITS type of a column in the table.
+ * @param index The 0-based index of the column.
+ * @return The FITS type.
+ * @exception FitsException if an invalid index was requested.
+ */
+ public String getColumnFormat(int index)
+ throws FitsException {
+ int flds = myHeader.getIntValue("TFIELDS", 0);
+ if (index < 0 || index >= flds) {
+ throw new FitsException("Bad column index " + index + " (only " + flds
+ + " columns)");
+ }
+
+ return myHeader.getStringValue("TFORM" + (index + 1)).trim();
+ }
+
+ /** Set the cursor in the header to point after the
+ * metadata for the specified column
+ * @param col The 0-based index of the column
+ */
+ public void setCurrentColumn(int col) {
+ setCurrentColumn(col, true);
+ }
+
+ /** Set the cursor in the header to point either before the
+ * TFORM value or after the column metadat
+ * @param col The 0-based index of the column
+ * @param after True if the cursor should be placed after the existing column
+ * metadata or false if the cursor is to be placed before the TFORM value.
+ * If no corresponding TFORM is found, the cursoe will be placed at the end of
+ * current header.
+ */
+ public void setCurrentColumn(int col, boolean after) {
+ if (after) {
+ myHeader.positionAfterIndex("TFORM", col + 1);
+ } else {
+ String tform = "TFORM" + (col + 1);
+ myHeader.findCard(tform);
+ }
+ }
+
+ /**
+ * Remove all rows from the table starting at some specific index from the table.
+ * Inspired by a routine by R. Mathar but re-implemented using the DataTable and
+ * changes to AsciiTable so that it can be done easily for both Binary and ASCII tables.
+ * @param row the (0-based) index of the first row to be deleted.
+ * @throws FitsExcpetion if an error occurs.
+ */
+ public void deleteRows(final int row) throws FitsException {
+ deleteRows(row, getNRows() - row);
+ }
+
+ /**
+ * Remove a number of adjacent rows from the table. This routine
+ * was inspired by code by R.Mathar but re-implemented using changes
+ * in the ColumnTable class abd AsciiTable so that we can do
+ * it for all FITS tables.
+ * @param firstRow the (0-based) index of the first row to be deleted.
+ * This is zero-based indexing: 0<=firstrow< number of rows.
+ * @param nRow the total number of rows to be deleted.
+ * @throws FitsException If an error occurs in the deletion.
+ */
+ public void deleteRows(final int firstRow, int nRow) throws FitsException {
+
+ // Just ignore invalid requests.
+ if (nRow <= 0 || firstRow >= getNRows() || nRow <= 0) {
+ return;
+ }
+
+ /* correct if more rows are requested than available */
+ if (nRow > getNRows() - firstRow) {
+ nRow = getNRows() - firstRow;
+ }
+
+ table.deleteRows(firstRow, nRow);
+ myHeader.setNaxis(2, getNRows());
+ }
+
+ /** Delete a set of columns from a table.
+ */
+ public void deleteColumnsIndexOne(int column, int len) throws FitsException {
+ deleteColumnsIndexZero(column - 1, len);
+ }
+
+ /** Delete a set of columns from a table.
+ */
+ public void deleteColumnsIndexZero(int column, int len) throws FitsException {
+ deleteColumnsIndexZero(column, len, columnKeyStems());
+ }
+
+ /** Delete a set of columns from a table.
+ * @param column The one-indexed start column.
+ * @param len The number of columns to delete.
+ * @param fields Stems for the header fields to be removed
+ * for the table.
+ */
+ public void deleteColumnsIndexOne(int column, int len, String[] fields) throws FitsException {
+ deleteColumnsIndexZero(column - 1, len, fields);
+ }
+
+ /** Delete a set of columns from a table.
+ * @param column The zero-indexed start column.
+ * @param len The number of columns to delete.
+ * @param fields Stems for the header fields to be removed
+ * for the table.
+ */
+ public void deleteColumnsIndexZero(int column, int len, String[] fields) throws FitsException {
+
+ if (column < 0 || len < 0 || column + len > getNCols()) {
+ throw new FitsException("Illegal columns deletion request- Start:" + column + " Len:" + len + " from table with " + getNCols() + " columns");
+ }
+
+ if (len == 0) {
+ return;
+ }
+
+ int ncol = getNCols();
+ table.deleteColumns(column, len);
+
+
+ // Get rid of the keywords for the deleted columns
+ for (int col = column; col < column + len; col += 1) {
+ for (int fld = 0; fld < fields.length; fld += 1) {
+ String key = fields[fld] + (col + 1);
+ myHeader.deleteKey(key);
+ }
+ }
+
+ // Shift the keywords for the columns after the deleted columns
+ for (int col = column + len; col < ncol; col += 1) {
+ for (int fld = 0; fld < fields.length; fld += 1) {
+ String oldKey = fields[fld] + (col + 1);
+ String newKey = fields[fld] + (col + 1 - len);
+ if (myHeader.containsKey(oldKey)) {
+ myHeader.replaceKey(oldKey, newKey);
+ }
+ }
+ }
+ // Update the number of fields.
+ myHeader.addValue("TFIELDS", getNCols(), "ntf::tablehdu:tfields:1");
+
+ // Give the data sections a chance to update the header too.
+ table.updateAfterDelete(ncol, myHeader);
+ }
+
+ /** Get the stems of the keywords that are associated
+ * with table columns. Users can supplement this
+ * with their own and call the appropriate deleteColumns fields.
+ */
+ public abstract String[] columnKeyStems();
+}
--- /dev/null
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/** This exception is thrown when an EOF is detected in the middle
+ * of an HDU.
+ */
+public class TruncatedFileException
+ extends FitsException {
+
+ public TruncatedFileException() {
+ super();
+ }
+
+ public TruncatedFileException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package nom.tam.fits;
+
+import nom.tam.util.*;
+import java.io.*;
+
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+/** This class provides a simple holder for data which is
+ * not handled by other classes.
+ */
+public class UndefinedData extends Data {
+
+ /** The size of the data */
+ long byteSize;
+ byte[] data;
+
+ public UndefinedData(Header h) throws FitsException {
+
+ /** Just get a byte buffer to hold the data.
+ */
+ // Bug fix by Vincenzo Forzi.
+ int naxis = h.getIntValue("NAXIS");
+
+ int size = naxis > 0 ? 1 : 0;
+ for (int i = 0; i < naxis; i += 1) {
+ size *= h.getIntValue("NAXIS" + (i + 1));
+ }
+ size += h.getIntValue("PCOUNT");
+ if (h.getIntValue("GCOUNT") > 1) {
+ size *= h.getIntValue("GCOUNT");
+ }
+ size *= Math.abs(h.getIntValue("BITPIX") / 8);
+
+ data = new byte[size];
+ byteSize = size;
+ }
+
+ /** Create an UndefinedData object using the specified object.
+ */
+ public UndefinedData(Object x) {
+
+ byteSize = ArrayFuncs.computeLSize(x);
+ data = new byte[(int) byteSize];
+ }
+
+ /** Fill header with keywords that describe data.
+ * @param head The FITS header
+ */
+ protected void fillHeader(Header head) {
+
+ try {
+ head.setXtension("UNKNOWN");
+ head.setBitpix(8);
+ head.setNaxes(1);
+ head.addValue("NAXIS1", byteSize,"ntf::undefineddata:naxis1:1");
+ head.addValue("PCOUNT", 0, "ntf::undefineddata:pcount:1");
+ head.addValue("GCOUNT", 1, "ntf::undefineddata:gcount:1");
+ head.addValue("EXTEND", true, "ntf::undefineddata:extend:1"); // Just in case!
+ } catch (HeaderCardException e) {
+ System.err.println("Unable to create unknown header:" + e);
+ }
+
+ }
+
+ public void read(ArrayDataInput i) throws FitsException {
+ setFileOffset(i);
+
+ if (i instanceof RandomAccess) {
+ try {
+ i.skipBytes(byteSize);
+ } catch (IOException e) {
+ throw new FitsException("Unable to skip over data:" + e);
+ }
+
+ } else {
+ try {
+ i.readFully(data);
+ } catch (IOException e) {
+ throw new FitsException("Unable to read unknown data:" + e);
+ }
+
+ }
+
+ int pad = FitsUtil.padding(getTrueSize());
+ try {
+ i.skipBytes(pad);
+ } catch (EOFException e) {
+ throw new PaddingException("EOF skipping padding in undefined data", this);
+ } catch (IOException e) {
+ throw new FitsException("Error skipping padding in undefined data");
+ }
+ }
+
+ public void write(ArrayDataOutput o) throws FitsException {
+
+ if (data == null) {
+ getData();
+ }
+
+ if (data == null) {
+ throw new FitsException("Null unknown data");
+ }
+
+ try {
+ o.write(data);
+ } catch (IOException e) {
+ throw new FitsException("IO Error on unknown data write" + e);
+ }
+
+ FitsUtil.pad(o, getTrueSize());
+
+ }
+
+ /** Get the size in bytes of the data */
+ protected long getTrueSize() {
+ return byteSize;
+ }
+
+ /** Return the actual data.
+ * Note that this may return a null when
+ * the data is not readable. It might be better
+ * to throw a FitsException, but this is
+ * a very commonly called method and we prefered
+ * not to change how users must invoke it.
+ */
+ public Object getData() {
+
+ if (data == null) {
+
+ try {
+ FitsUtil.reposition(input, fileOffset);
+ input.read(data);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ return data;
+ }
+}
--- /dev/null
+package nom.tam.fits;
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import nom.tam.util.ArrayFuncs;
+
+/** Holder for unknown data types. */
+public class UndefinedHDU
+ extends BasicHDU {
+
+ /** Build an image HDU using the supplied data.
+ * @param obj the data used to build the image.
+ * @exception FitsException if there was a problem with the data.
+ */
+ public UndefinedHDU(Header h, Data d)
+ throws FitsException {
+ myData = d;
+ myHeader = h;
+
+ }
+
+ /* Check if we can find the length of the data for this
+ * header.
+ * @return <CODE>true</CODE> if this HDU has a valid header.
+ */
+ public static boolean isHeader(Header hdr) {
+ if (hdr.getStringValue("XTENSION") != null
+ && hdr.getIntValue("NAXIS", -1) >= 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /** Check if we can use the following object as
+ * in an Undefined FITS block. We allow this
+ * so long as computeLSize can get a size. Note
+ * that computeLSize may be wrong!
+ * @param o The Object being tested.
+ */
+ public static boolean isData(Object o) {
+ return ArrayFuncs.computeLSize(o) > 0;
+ }
+
+ /** Create a Data object to correspond to the header description.
+ * @return An unfilled Data object which can be used to read
+ * in the data for this HDU.
+ * @exception FitsException if the image extension could not be created.
+ */
+ public Data manufactureData()
+ throws FitsException {
+ return manufactureData(myHeader);
+ }
+
+ public static Data manufactureData(Header hdr)
+ throws FitsException {
+ return new UndefinedData(hdr);
+ }
+
+ /** Create a header that describes the given
+ * image data.
+ * @param o The image to be described.
+ * @exception FitsException if the object does not contain
+ * valid image data.
+ */
+ public static Header manufactureHeader(Data d)
+ throws FitsException {
+
+ Header h = new Header();
+ d.fillHeader(h);
+
+ return h;
+ }
+
+ /** Encapsulate an object as an ImageHDU. */
+ public static Data encapsulate(Object o) throws FitsException {
+ return new UndefinedData(o);
+ }
+
+ /** Print out some information about this HDU.
+ */
+ public void info() {
+
+ System.out.println(" Unhandled/Undefined/Unknown Type");
+ System.out.println(" XTENSION=" + myHeader.getStringValue("XTENSION").trim());
+ System.out.println(" Apparent size:" + myData.getTrueSize());
+ }
+}
--- /dev/null
+# This file contains comment values for standard keywords.
+# The first token on each non-comment line is a keyword that
+# identifies the comment of the form class_keyword_n
+# where class is the class in which the data is written
+# keyword is the key value for the comment being written
+# and n is an integer (usually 1) used to distinguish multiple
+# entries.
+
+header_simple_1 Null Image Header
+header_bitpix_1 BITPIX for null image
+header_naxis_1 NAXIS for null image
+
+header_extend_1 Extensions are permitted
+header_simple_2 Java FITS: $DATE
+header_xtension_1 Java FITS: $DATE
+header_bitpix_2
+header_naxis_2 Dimensionality
+header_naxisN_1
+header_end_1
+
+randomgroupsdata_naxis1_1
+randomgroupsdata_naxisN_1
+randomgroupsdata_groups_1
+randomgroupsdata_gcount_1
+randomgroupsdata_pcount_1
+
+binarytablehdu_pcount_1
+binarytablehdu_theap_1
+binarytablehdu_tformN_1
+binarytablehdu_tdimN_1
+
+asciitable_pcount_1 No group data
+asciitable_gcount_1 One group
+asciitable_tfields_1 Number of fields in table
+
+asciitable_tbcolN_1 Column Offset
+asciitable_naxis1_1 Size of row in bytes
+asciitablehdu_tnullN_1
+asciitablehdu_tfields_1
+
+binarytable_pcount_1
+binarytable_gcount_1
+binarytable_naxis1_1 Bytes per row
+binarytable_tfields
+
+basichdu_extend_1 Allow extensions
+basichdu_pcount_1 Required value
+basichdu_gcount_1 Required value
+
+imagedata_extend_1 Extension permitted
+imagedata_pcount_1 No extra parameters
+imagedata_gcount_1 One group
+
+tablehdu_naxis2_1
+tablehdu_tfields_1 Number of table fields
+
+
+
+
+
+
+
+
+undefineddata_naxis1_1 Number of bytes in unknown structure
+undefineddata_pcount_1
+undefineddata_gcount_1
+undefineddata_extend_1 Extensions are permitted
\ No newline at end of file
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.util.*;
+import nom.tam.fits.*;
+
+/** This class tests the AsciiTableHDU and AsciiTable FITS
+ * classes and implicitly the ByteFormatter and ByteParser
+ * classes in the nam.tam.util library.
+ * Tests include:
+ * Create columns of every type
+ * Read columns of every type
+ * Create a table column by column
+ * Create a table row by row
+ * Use deferred input on rows
+ * Use deferred input on elements
+ * Read rows, columns and elements from in-memory kernel.
+ * Specify width of columns.
+ * Rewrite data/header in place.
+ * Set and read null elements.
+ */
+public class AsciiTableTest {
+
+ Object[] getSampleCols() {
+
+ float[] realCol = new float[50];
+
+ for (int i = 0; i < realCol.length; i += 1) {
+ realCol[i] = 10000.F * (i) * (i) * (i) + 1;
+ }
+
+ int[] intCol = (int[]) ArrayFuncs.convertArray(realCol, int.class);
+ long[] longCol = (long[]) ArrayFuncs.convertArray(realCol, long.class);
+ double[] doubleCol = (double[]) ArrayFuncs.convertArray(realCol, double.class);
+
+ String[] strCol = new String[realCol.length];
+
+ for (int i = 0; i < realCol.length; i += 1) {
+ strCol[i] = "ABC" + String.valueOf(realCol[i]) + "CDE";
+ }
+ return new Object[]{realCol, intCol, longCol, doubleCol, strCol};
+ }
+
+ Fits makeAsciiTable() throws Exception {
+ Object[] cols = getSampleCols();
+ // Create the new ASCII table.
+ Fits f = new Fits();
+ f.addHDU(Fits.makeHDU(cols));
+ return f;
+ }
+
+ public void writeFile(Fits f, String name) throws Exception {
+ BufferedFile bf = new BufferedFile(name, "rw");
+ f.write(bf);
+ bf.flush();
+ bf.close();
+ }
+
+ @Test
+ public void test() throws Exception {
+ createByColumn();
+ createByRow();
+ readByRow();
+ readByColumn();
+ readByElement();
+ modifyTable();
+ delete();
+ }
+
+ public void createByColumn() throws Exception {
+ Fits f = makeAsciiTable();
+ writeFile(f, "at1.fits");
+
+ // Read back the data from the file.
+ f = new Fits("at1.fits");
+ AsciiTableHDU hdu = (AsciiTableHDU) f.getHDU(1);
+
+ Object[] inputs = getSampleCols();
+ Object[] outputs = (Object[]) hdu.getKernel();
+
+ for (int i = 0; i < 50; i += 1) {
+ ((String[]) outputs[4])[i] = ((String[]) outputs[4])[i].trim();
+ }
+
+ for (int j = 0; j < 5; j += 1) {
+ assertEquals("ByCol:" + j, true, ArrayFuncs.arrayEquals(inputs[j], outputs[j], 1.e-6, 1.e-14));
+ }
+
+ }
+
+ Object[] getRow(int i) {
+ return new Object[]{
+ new int[]{i},
+ new float[]{i},
+ new String[]{"Str" + i}
+ };
+ }
+
+ Object[] getRowBlock(int max) {
+ Object[] o = new Object[]{new int[max], new float[max], new String[max]};
+ for (int i = 0; i < max; i += 1) {
+ ((int[]) o[0])[i] = i;
+ ((float[]) o[1])[i] = i;
+ ((String[]) o[2])[i] = "Str" + i;
+ }
+ return o;
+ }
+
+ public void createByRow() throws Exception {
+
+ // Create a table row by row .
+ Fits f = new Fits();
+ AsciiTable data = new AsciiTable();
+ Object[] row = new Object[4];
+
+ for (int i = 0; i < 50; i += 1) {
+ data.addRow(getRow(i));
+ }
+
+ f.addHDU(Fits.makeHDU(data));
+
+ writeFile(f, "at2.fits");
+
+ // Read it back.
+ f = new Fits("at2.fits");
+
+ Object[] output = (Object[]) f.getHDU(1).getKernel();
+ Object[] input = getRowBlock(50);
+
+ for (int i = 0; i < 50; i += 1) {
+ String[] str = (String[]) output[2];
+ String[] istr = (String[]) input[2];
+ int len1 = str[1].length();
+ str[i] = str[i].trim();
+ // The first row would have set the length for all the
+ // remaining rows...
+ if (istr[i].length() > len1) {
+ istr[i] = istr[i].substring(0, len1);
+ }
+ }
+
+ for (int j = 0; j < 3; j += 1) {
+ assertEquals("ByRow:" + j, true, ArrayFuncs.arrayEquals(input[j], output[j], 1.e-6, 1.e-14));
+ }
+ }
+
+ public void readByRow() throws Exception {
+
+ Fits f = new Fits("at1.fits");
+ Object[] cols = getSampleCols();
+
+ AsciiTableHDU hdu = (AsciiTableHDU) f.getHDU(1);
+ AsciiTable data = (AsciiTable) hdu.getData();
+
+ for (int i = 0; i < data.getNRows(); i += 1) {
+ assertEquals("Rows:" + i, 50, data.getNRows());
+ Object[] row = data.getRow(i);
+ assertEquals("Ascii Rows: float" + i, 1.F, ((float[]) cols[0])[i] / ((float[]) row[0])[0], 1.e-6);
+ assertEquals("Ascii Rows: int" + i, ((int[]) cols[1])[i], ((int[]) row[1])[0]);
+ assertEquals("Ascii Rows: long" + i, ((long[]) cols[2])[i], ((long[]) row[2])[0]);
+ assertEquals("Ascii Rows: double" + i, 1., ((double[]) cols[3])[i] / ((double[]) row[3])[0], 1.e-14);
+ String[] st = (String[]) row[4];
+ st[0] = st[0].trim();
+ assertEquals("Ascii Rows: Str" + i, ((String[]) cols[4])[i], ((String[]) row[4])[0]);
+ }
+ }
+
+ public void readByColumn() throws Exception {
+ Fits f = new Fits("at1.fits");
+ AsciiTableHDU hdu = (AsciiTableHDU) f.getHDU(1);
+ AsciiTable data = (AsciiTable) hdu.getData();
+ Object[] cols = getSampleCols();
+
+ assertEquals("Number of rows", data.getNRows(), 50);
+ assertEquals("Number of columns", data.getNCols(), 5);
+
+ for (int j = 0; j < data.getNCols(); j += 1) {
+ Object col = data.getColumn(j);
+ if (j == 4) {
+ String[] st = (String[]) col;
+ for (int i = 0; i < st.length; i += 1) {
+ st[i] = st[i].trim();
+ }
+ }
+ assertEquals("Ascii Columns:" + j, true, ArrayFuncs.arrayEquals(cols[j], col, 1.e-6, 1.e-14));
+ }
+ }
+
+ public void readByElement() throws Exception {
+
+ Fits f = new Fits("at2.fits");
+ AsciiTableHDU hdu = (AsciiTableHDU) f.getHDU(1);
+ AsciiTable data = (AsciiTable) hdu.getData();
+
+
+ for (int i = 0; i < data.getNRows(); i += 1) {
+ Object[] row = (Object[]) data.getRow(i);
+ for (int j = 0; j < data.getNCols(); j += 1) {
+ Object val = data.getElement(i, j);
+ assertEquals("Ascii readElement", true, ArrayFuncs.arrayEquals(val, row[j]));
+ }
+ }
+ }
+
+ public void modifyTable() throws Exception {
+
+ Fits f = new Fits("at1.fits");
+ Object[] samp = getSampleCols();
+
+ AsciiTableHDU hdu = (AsciiTableHDU) f.getHDU(1);
+ AsciiTable data = (AsciiTable) hdu.getData();
+ float[] f1 = (float[]) data.getColumn(0);
+ float[] f2 = (float[]) f1.clone();
+ for (int i = 0; i < f2.length; i += 1) {
+ f2[i] = 2 * f2[i];
+ }
+
+ data.setColumn(0, f2);
+ f1 = new float[]{3.14159f};
+ data.setElement(3, 0, f1);
+
+ hdu.setNullString(0, "**INVALID**");
+ data.setNull(5, 0, true);
+ data.setNull(6, 0, true);
+
+ Object[] row = new Object[5];
+ row[0] = new float[]{6.28f};
+ row[1] = new int[]{22};
+ row[2] = new long[]{0};
+ row[3] = new double[]{-3};
+ row[4] = new String[]{"A string"};
+
+ data.setRow(5, row);
+
+ data.setElement(4, 2, new long[]{54321});
+
+ BufferedFile bf = new BufferedFile("at1x.fits", "rw");
+ f.write(bf);
+
+
+ f = new Fits("at1x.fits");
+ AsciiTable tab = (AsciiTable) f.getHDU(1).getData();
+ Object[] kern = (Object[]) tab.getKernel();
+
+ float[] fx = (float[]) kern[0];
+ int[] ix = (int[]) kern[1];
+ long[] lx = (long[]) kern[2];
+ double[] dx = (double[]) kern[3];
+ String[] sx = (String[]) kern[4];
+
+ float[] fy = (float[]) samp[0];
+ int[] iy = (int[]) samp[1];
+ long[] ly = (long[]) samp[2];
+ double[] dy = (double[]) samp[3];
+ String[] sy = (String[]) samp[4];
+
+ assertEquals("Null", true, tab.isNull(6, 0));
+ assertEquals("Null2", false, tab.isNull(5, 0));
+
+ for (int i = 0; i < data.getNRows(); i += 1) {
+ if (i != 5) {
+ if (i != 6) { // Null
+ assertEquals("f" + i, 1., f2[i] / fx[i], 1.e-6);
+ }
+ assertEquals("i" + i, iy[i], ix[i]);
+ if (i == 4) {
+ assertEquals("l4", 54321L, lx[i]);
+ } else {
+ assertEquals("l" + i, ly[i], lx[i]);
+ }
+ assertEquals("d" + i, 1., dy[i] / dx[i], 1.e-14);
+ assertEquals("s" + i, sy[i], sx[i].trim());
+ }
+ }
+ Object[] r5 = (Object[]) data.getRow(5);
+ String[] st = (String[]) r5[4];
+ st[0] = st[0].trim();
+ assertEquals("row5", true, ArrayFuncs.arrayEquals(row, r5, 1.e-6, 1.e-14));
+ }
+
+ public void delete() throws Exception {
+
+ Fits f = new Fits("at1.fits");
+
+ TableHDU th = (TableHDU) f.getHDU(1);
+ assertEquals("delrBef", 50, th.getNRows());
+ th.deleteRows(2, 2);
+ assertEquals("delrAft", 48, th.getNRows());
+ BufferedFile bf = new BufferedFile("at1y.fits", "rw");
+ f.write(bf);
+ bf.close();
+
+ f = new Fits("at1y.fits");
+ th = (TableHDU) f.getHDU(1);
+ assertEquals("delrAft2", 48, th.getNRows());
+
+ assertEquals("delcBef", 5, th.getNCols());
+ th.deleteColumnsIndexZero(3, 2);
+ assertEquals("delcAft1", 3, th.getNCols());
+ th.deleteColumnsIndexZero(0, 2);
+ assertEquals("delcAft2", 1, th.getNCols());
+ bf = new BufferedFile("at1z.fits", "rw");
+ f.write(bf);
+ bf.close();
+
+ f = new Fits("at1z.fits");
+ th = (TableHDU) f.getHDU(1);
+ assertEquals("delcAft3", 1, th.getNCols());
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import nom.tam.util.*;
+import nom.tam.fits.*;
+import java.io.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+import java.lang.reflect.*;
+
+/** This class tests the binary table classes for
+ * the Java FITS library, notably BinaryTableHDU,
+ * BinaryTable, FitsHeap and the utility class ColumnTable.
+ * Tests include:
+ * <pre>
+ * Reading and writing data of all valid types.
+ * Reading and writing variable length da
+ * Creating binary tables from:
+ * Object[][] array
+ * Object[] array
+ * ColumnTable
+ * Column x Column
+ * Row x Row
+ * Read binary table
+ * Row x row
+ * Element x element
+ * Modify
+ * Row, column, element
+ * Rewrite binary table in place
+ * </pre>
+ */
+public class BinaryTableTest {
+
+ byte[] bytes = new byte[50];
+ byte[][] bits = new byte[50][2];
+ boolean[] bools = new boolean[50];
+ short[][] shorts = new short[50][3];
+ int[] ints = new int[50];
+ float[][][] floats = new float[50][4][4];
+ double[] doubles = new double[50];
+ long[] longs = new long[50];
+ String[] strings = new String[50];
+ float[][] vf = new float[50][];
+ short[][] vs = new short[50][];
+ double[][] vd = new double[50][];
+ boolean[][] vbool = new boolean[50][];
+ float[][][] vc = new float[50][][];
+ double[][][] vdc = new double[50][][];
+ float[][] complex = new float[50][2];
+ float[][][] complex_arr = new float[50][4][2];
+ double[][] dcomplex = new double[50][2];
+ double[][][] dcomplex_arr = new double[50][4][2];
+
+ @Before
+ public void initialize() {
+
+ for (int i = 0; i < bytes.length; i += 1) {
+ bytes[i] = (byte) (2 * i);
+ bits[i][0] = bytes[i];
+ bits[i][1] = (byte) (~bytes[i]);
+ bools[i] = (bytes[i] % 8) == 0 ? true : false;
+
+ shorts[i][0] = (short) (2 * i);
+ shorts[i][1] = (short) (3 * i);
+ shorts[i][2] = (short) (4 * i);
+
+ ints[i] = i * i;
+ for (int j = 0; j < 4; j += 1) {
+ for (int k = 0; k < 4; k += 1) {
+ floats[i][j][k] = (float) (i + j * Math.exp(k));
+ }
+ }
+ doubles[i] = 3 * Math.sin(i);
+ longs[i] = i * i * i * i;
+ strings[i] = "abcdefghijklmnopqrstuvwxzy".substring(0, i % 20);
+
+ vf[i] = new float[i + 1];
+ vf[i][i / 2] = i * 3;
+ vs[i] = new short[i / 10 + 1];
+ vs[i][i / 10] = (short) -i;
+ vd[i] = new double[i % 2 == 0 ? 1 : 2];
+ vd[i][0] = 99.99;
+ vbool[i] = new boolean[i / 10];
+ if (i >= 10) {
+ vbool[i][0] = i % 2 == 1;
+ }
+
+ int m5 = i % 5;
+ vc[i] = new float[m5][];
+ for (int j = 0; j < m5; j += 1) {
+ vc[i][j] = new float[2];
+ vc[i][j][0] = i;
+ vc[i][j][1] = -j;
+ }
+ vdc[i] = new double[m5][];
+ for (int j = 0; j < m5; j += 1) {
+ vdc[i][j] = new double[2];
+ vdc[i][j][0] = -j;
+ vdc[i][j][1] = i;
+ }
+ double rad = 2 * i * Math.PI / bytes.length;
+ complex[i][0] = (float) (Math.cos(rad));
+ complex[i][1] = (float) (Math.sin(rad));
+ dcomplex[i][0] = complex[i][0];
+ dcomplex[i][1] = complex[i][1];
+ for (int j = 0; j < 4; j += 1) {
+ complex_arr[i][j][0] = (j + 1) * complex[i][0];
+ complex_arr[i][j][1] = (j + 1) * complex[i][1];
+ dcomplex_arr[i][j][0] = (j + 1) * complex[i][0];
+ dcomplex_arr[i][j][1] = (j + 1) * complex[i][1];
+ }
+ }
+ }
+
+ @Test
+ public void testSimpleIO() throws Exception {
+
+ FitsFactory.setUseAsciiTables(false);
+
+ Fits f = new Fits();
+ Object[] data = new Object[]{bytes, bits, bools, shorts, ints,
+ floats, doubles, longs, strings, complex, dcomplex, complex_arr, dcomplex_arr};
+ f.addHDU(Fits.makeHDU(data));
+
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+ bhdu.setColumnName(0, "bytes", null);
+ bhdu.setColumnName(1, "bits", "bits later on");
+ bhdu.setColumnName(6, "doubles", null);
+ bhdu.setColumnName(5, "floats", "4 x 4 array");
+
+ BufferedFile bf = new BufferedFile("bt1.fits", "rw");
+ f.write(bf);
+ bf.flush();
+ bf.close();
+
+
+ f = new Fits("bt1.fits");
+ f.read();
+
+ assertEquals("NHDU", 2, f.getNumberOfHDUs());
+
+
+ BinaryTableHDU thdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = thdu.getHeader();
+
+ assertEquals("HDR1", data.length, hdr.getIntValue("TFIELDS"));
+ assertEquals("HDR2", 2, hdr.getIntValue("NAXIS"));
+ assertEquals("HDR3", 8, hdr.getIntValue("BITPIX"));
+ assertEquals("HDR4", "BINTABLE", hdr.getStringValue("XTENSION"));
+ assertEquals("HDR5", "bytes", hdr.getStringValue("TTYPE1"));
+ assertEquals("HDR6", "doubles", hdr.getStringValue("TTYPE7"));
+
+ for (int i = 0; i < data.length; i += 1) {
+ Object col = thdu.getColumn(i);
+ if (i == 8) {
+ String[] st = (String[]) col;
+
+ for (int j = 0; j < st.length; j += 1) {
+ st[j] = st[j].trim();
+ }
+ }
+ assertEquals("Data" + i, true, ArrayFuncs.arrayEquals(data[i], col));
+ }
+ }
+
+ @Test
+ public void testSimpleComplex() throws Exception {
+ try {
+ FitsFactory.setUseAsciiTables(false);
+
+ Fits f = new Fits();
+ Object[] data = new Object[]{bytes, bits, bools, shorts, ints,
+ floats, doubles, longs, strings, complex, dcomplex, complex_arr, dcomplex_arr};
+ BinaryTableHDU bhdu = (BinaryTableHDU) Fits.makeHDU(data);
+
+ bhdu.setComplexColumn(9);
+ bhdu.setComplexColumn(10);
+ bhdu.setComplexColumn(11);
+ bhdu.setComplexColumn(12);
+
+ f.addHDU(bhdu);
+ bhdu.setColumnName(9, "Complex1", null);
+
+ BufferedFile bf = new BufferedFile("bt1c.fits", "rw");
+ f.write(bf);
+ bf.flush();
+ bf.close();
+
+ f = new Fits("bt1c.fits");
+ f.read();
+
+ assertEquals("NHDUc", 2, f.getNumberOfHDUs());
+
+
+ BinaryTableHDU thdu = null;
+ thdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = thdu.getHeader();
+
+ for (int i = 0; i < data.length; i += 1) {
+
+ Object col = thdu.getColumn(i);
+ if (i == 8) {
+ String[] st = (String[]) col;
+
+ for (int j = 0; j < st.length; j += 1) {
+ st[j] = st[j].trim();
+ }
+ }
+ int n = Array.getLength(data[i]);
+
+ assertEquals("DataC" + i, true, ArrayFuncs.arrayEquals(data[i], col));
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ throw e;
+ }
+
+ }
+
+ @Test
+ public void testRowDelete() throws Exception {
+ Fits f = new Fits("bt1.fits");
+ f.read();
+
+ BinaryTableHDU thdu = (BinaryTableHDU) f.getHDU(1);
+
+ assertEquals("Del1", 50, thdu.getNRows());
+ thdu.deleteRows(10, 20);
+ assertEquals("Del2", 30, thdu.getNRows());
+
+ double[] dbl = (double[]) thdu.getColumn(6);
+ assertEquals("del3", dbl[9], doubles[9], 0);
+ assertEquals("del4", dbl[10], doubles[30], 0);
+
+ BufferedFile bf = new BufferedFile("bt1x.fits", "rw");
+ f.write(bf);
+ bf.close();
+
+ f = new Fits("bt1x.fits");
+ f.read();
+ thdu = (BinaryTableHDU) f.getHDU(1);
+ dbl = (double[]) thdu.getColumn(6);
+ assertEquals("del5", 30, thdu.getNRows());
+ assertEquals("del6", 13, thdu.getNCols());
+ assertEquals("del7", dbl[9], doubles[9], 0);
+ assertEquals("del8", dbl[10], doubles[30], 0);
+
+ thdu.deleteRows(20);
+ assertEquals("del9", 20, thdu.getNRows());
+ dbl = (double[]) thdu.getColumn(6);
+ assertEquals("del10", 20, dbl.length);
+ assertEquals("del11", dbl[0], doubles[0], 0);
+ assertEquals("del12", dbl[19], doubles[39], 0);
+ }
+
+ @Test
+ public void testVar() throws Exception {
+ try {
+ Object[] data = new Object[]{floats, vf, vs, vd, shorts, vbool, vc, vdc};
+ BasicHDU hdu = Fits.makeHDU(data);
+ Fits f = new Fits();
+ f.addHDU(hdu);
+ BufferedDataOutputStream bdos = new BufferedDataOutputStream(new FileOutputStream("bt2.fits"));
+ f.write(bdos);
+ bdos.close();
+
+ f = new Fits("bt2.fits");
+ f.read();
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = bhdu.getHeader();
+
+ assertEquals("var1", true, hdr.getIntValue("PCOUNT") > 0);
+ assertEquals("var2", data.length, hdr.getIntValue("TFIELDS"));
+
+ for (int i = 0; i < data.length; i += 1) {
+ assertEquals("vardata" + i, true, ArrayFuncs.arrayEquals(data[i], bhdu.getColumn(i)));
+ }
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ throw e;
+ }
+ }
+
+ @Test
+ public void testSet() throws Exception {
+
+ Fits f = new Fits("bt2.fits");
+ f.read();
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = bhdu.getHeader();
+
+ // Check the various set methods on variable length data.
+ float[] dta = (float[]) bhdu.getElement(4, 1);
+ dta = new float[]{22, 21, 20};
+ bhdu.setElement(4, 1, dta);
+
+ BufferedDataOutputStream bdos = new BufferedDataOutputStream(new FileOutputStream("bt2a.fits"));
+ f.write(bdos);
+ bdos.close();
+
+ f = new Fits("bt2a.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ float[] xdta = (float[]) bhdu.getElement(4, 1);
+
+ assertEquals("ts1", true, ArrayFuncs.arrayEquals(dta, xdta));
+ assertEquals("ts2", true, ArrayFuncs.arrayEquals(bhdu.getElement(3, 1), vf[3]));
+ assertEquals("ts4", true, ArrayFuncs.arrayEquals(bhdu.getElement(5, 1), vf[5]));
+
+ assertEquals("ts5", true, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), dta));
+
+ float tvf[] = new float[]{101, 102, 103, 104};
+ vf[4] = tvf;
+
+ bhdu.setColumn(1, vf);
+ assertEquals("ts6", true, ArrayFuncs.arrayEquals(bhdu.getElement(3, 1), vf[3]));
+ assertEquals("ts7", true, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), vf[4]));
+ assertEquals("ts8", true, ArrayFuncs.arrayEquals(bhdu.getElement(5, 1), vf[5]));
+
+ bdos = new BufferedDataOutputStream(new FileOutputStream("bt2b.fits"));
+ f.write(bdos);
+ bdos.close();
+
+ f = new Fits("bt2b.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ assertEquals("ts9", true, ArrayFuncs.arrayEquals(bhdu.getElement(3, 1), vf[3]));
+ assertEquals("ts10", true, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), vf[4]));
+ assertEquals("ts11", true, ArrayFuncs.arrayEquals(bhdu.getElement(5, 1), vf[5]));
+
+ Object[] rw = bhdu.getRow(4);
+
+ float[] trw = new float[]{-1, -2, -3, -4, -5, -6};
+ rw[1] = trw;
+
+ bhdu.setRow(4, rw);
+ assertEquals("ts12", true, ArrayFuncs.arrayEquals(bhdu.getElement(3, 1), vf[3]));
+ assertEquals("ts13", false, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), vf[4]));
+ assertEquals("ts14", true, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), trw));
+ assertEquals("ts15", true, ArrayFuncs.arrayEquals(bhdu.getElement(5, 1), vf[5]));
+
+ bdos = new BufferedDataOutputStream(new FileOutputStream("bt2c.fits"));
+ f.write(bdos);
+ bdos.close();
+
+ f = new Fits("bt2c.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ assertEquals("ts16", true, ArrayFuncs.arrayEquals(bhdu.getElement(3, 1), vf[3]));
+ assertEquals("ts17", false, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), vf[4]));
+ assertEquals("ts18", true, ArrayFuncs.arrayEquals(bhdu.getElement(4, 1), trw));
+ assertEquals("ts19", true, ArrayFuncs.arrayEquals(bhdu.getElement(5, 1), vf[5]));
+ }
+
+ @Test
+ public void buildByColumn() throws Exception {
+
+ BinaryTable btab = new BinaryTable();
+
+ btab.addColumn(floats);
+ btab.addColumn(vf);
+ btab.addColumn(strings);
+ btab.addColumn(vbool);
+ btab.addColumn(ints);
+ btab.addColumn(vc);
+ btab.addColumn(complex);
+
+ Fits f = new Fits();
+ f.addHDU(Fits.makeHDU(btab));
+
+ BufferedDataOutputStream bdos = new BufferedDataOutputStream(new FileOutputStream("bt3.fits"));
+ f.write(bdos);
+
+ f = new Fits("bt3.fits");
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+ btab = (BinaryTable) bhdu.getData();
+
+ assertEquals("col1", true, ArrayFuncs.arrayEquals(floats, bhdu.getColumn(0)));
+ assertEquals("col2", true, ArrayFuncs.arrayEquals(vf, bhdu.getColumn(1)));
+ assertEquals("col6", true, ArrayFuncs.arrayEquals(vc, bhdu.getColumn(5)));
+ assertEquals("col7", true, ArrayFuncs.arrayEquals(complex, bhdu.getColumn(6)));
+
+ String[] col = (String[]) bhdu.getColumn(2);
+ for (int i = 0; i < col.length; i += 1) {
+ col[i] = col[i].trim();
+ }
+ assertEquals("coi3", true, ArrayFuncs.arrayEquals(strings, col));
+
+ assertEquals("col4", true, ArrayFuncs.arrayEquals(vbool, bhdu.getColumn(3)));
+ assertEquals("col5", true, ArrayFuncs.arrayEquals(ints, bhdu.getColumn(4)));
+ }
+
+ @Test
+ public void buildByRow() throws Exception {
+
+ Fits f = new Fits("bt2.fits");
+ f.read();
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = bhdu.getHeader();
+ BinaryTable btab = (BinaryTable) bhdu.getData();
+ for (int i = 0; i < 50; i += 1) {
+
+ Object[] row = btab.getRow(i);
+ float[] qx = (float[]) row[1];
+ float[][] p = (float[][]) row[0];
+ p[0][0] = (float) (i * Math.sin(i));
+ btab.addRow(row);
+ }
+
+ f = new Fits();
+ f.addHDU(Fits.makeHDU(btab));
+ BufferedFile bf = new BufferedFile("bt4.fits", "rw");
+ f.write(bf);
+ bf.flush();
+ bf.close();
+
+ f = new Fits("bt4.fits");
+
+ btab = (BinaryTable) f.getHDU(1).getData();
+ assertEquals("row1", 100, btab.getNRows());
+
+
+ // Try getting data before we read in the table.
+
+ float[][][] xf = (float[][][]) btab.getColumn(0);
+ assertEquals("row2", (float) 0., xf[50][0][0], 0);
+ assertEquals("row3", (float) (49 * Math.sin(49)), xf[99][0][0], 0);
+
+ for (int i = 0; i < xf.length; i += 3) {
+
+ boolean[] ba = (boolean[]) btab.getElement(i, 5);
+ float[] fx = (float[]) btab.getElement(i, 1);
+
+ int trow = i % 50;
+
+ assertEquals("row4", true, ArrayFuncs.arrayEquals(ba, vbool[trow]));
+ assertEquals("row6", true, ArrayFuncs.arrayEquals(fx, vf[trow]));
+
+ }
+ float[][][] cmplx = (float[][][]) btab.getColumn(6);
+ for (int i = 0; i < vc.length; i += 1) {
+ for (int j = 0; j < vc[i].length; j += 1) {
+ assertEquals("rowvc" + i + "_" + j, true, ArrayFuncs.arrayEquals(vc[i][j], cmplx[i + vc.length][j]));
+ }
+ }
+ // Fill the table.
+ f.getHDU(1).getData();
+
+ xf = (float[][][]) btab.getColumn(0);
+ assertEquals("row7", 0.F, xf[50][0][0], 0);
+ assertEquals("row8", (float) (49 * Math.sin(49)), xf[99][0][0], 0);
+
+ for (int i = 0; i < xf.length; i += 3) {
+
+ boolean[] ba = (boolean[]) btab.getElement(i, 5);
+ float[] fx = (float[]) btab.getElement(i, 1);
+
+ int trow = i % 50;
+
+ assertEquals("row9", true, ArrayFuncs.arrayEquals(ba, vbool[trow]));
+ assertEquals("row11", true, ArrayFuncs.arrayEquals(fx, vf[trow]));
+
+ }
+ }
+
+ @Test
+ public void testObj() throws Exception {
+
+ /*** Create a binary table from an Object[][] array */
+ Object[][] x = new Object[5][3];
+ for (int i = 0; i < 5; i += 1) {
+ x[i][0] = new float[]{i};
+ x[i][1] = new String("AString" + i);
+ x[i][2] = new int[][]{{i, 2 * i}, {3 * i, 4 * i}};
+ }
+
+ Fits f = new Fits();
+ BasicHDU hdu = Fits.makeHDU(x);
+ f.addHDU(hdu);
+ BufferedFile bf = new BufferedFile("bt5.fits", "rw");
+ f.write(bf);
+ bf.close();
+
+ /** Now get rid of some columns */
+ BinaryTableHDU xhdu = (BinaryTableHDU) hdu;
+
+
+ // First column
+ assertEquals("delcol1", 3, xhdu.getNCols());
+ xhdu.deleteColumnsIndexOne(1, 1);
+ assertEquals("delcol2", 2, xhdu.getNCols());
+
+ xhdu.deleteColumnsIndexZero(1, 1);
+ assertEquals("delcol3", 1, xhdu.getNCols());
+
+ bf = new BufferedFile("bt6.fits", "rw");
+ f.write(bf);
+
+ f = new Fits("bt6.fits");
+
+ xhdu = (BinaryTableHDU) f.getHDU(1);
+ assertEquals("delcol4", 1, xhdu.getNCols());
+ }
+
+ @Test
+ public void testDegenerate() throws Exception {
+
+ String[] sa = new String[10];
+ int[][] ia = new int[10][0];
+ Fits f = new Fits();
+
+ for (int i = 0; i < sa.length; i += 1) {
+ sa[i] = "";
+ }
+
+ Object[] data = new Object[]{sa, ia};
+ BinaryTableHDU bhdu = (BinaryTableHDU) Fits.makeHDU(data);
+ Header hdr = bhdu.getHeader();
+ f.addHDU(bhdu);
+ BufferedFile bf = new BufferedFile("bt7.fits", "rw");
+ f.write(bf);
+ bf.close();
+
+ assertEquals("degen1", 2, hdr.getIntValue("TFIELDS"));
+ assertEquals("degen2", 10, hdr.getIntValue("NAXIS2"));
+ assertEquals("degen3", 0, hdr.getIntValue("NAXIS1"));
+
+ f = new Fits("bt7.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+
+ hdr = bhdu.getHeader();
+ assertEquals("degen4", 2, hdr.getIntValue("TFIELDS"));
+ assertEquals("degen5", 10, hdr.getIntValue("NAXIS2"));
+ assertEquals("degen6", 0, hdr.getIntValue("NAXIS1"));
+ }
+
+ @Test
+ public void testDegen2() throws Exception {
+ FitsFactory.setUseAsciiTables(false);
+
+ Object[] data = new Object[]{
+ new String[]{"a", "b", "c", "d", "e", "f"},
+ new int[]{1, 2, 3, 4, 5, 6},
+ new float[]{1.f, 2.f, 3.f, 4.f, 5.f, 6.f},
+ new String[]{"", "", "", "", "", ""},
+ new String[]{"a", "", "c", "", "e", "f"},
+ new String[]{"", "b", "c", "d", "e", "f"},
+ new String[]{"a", "b", "c", "d", "e", ""},
+ new String[]{null, null, null, null, null, null},
+ new String[]{"a", null, "c", null, "e", "f"},
+ new String[]{null, "b", "c", "d", "e", "f"},
+ new String[]{"a", "b", "c", "d", "e", null}
+ };
+
+ Fits f = new Fits();
+ f.addHDU(Fits.makeHDU(data));
+ BufferedFile ff = new BufferedFile("bt8.fits", "rw");
+ f.write(ff);
+
+ f = new Fits("bt8.fits");
+ BinaryTableHDU bhdu = (BinaryTableHDU) f.getHDU(1);
+
+ assertEquals("deg21", "e", bhdu.getElement(4, data.length - 1));
+ assertEquals("deg22", "", bhdu.getElement(5, data.length - 1));
+
+ String[] col = (String[]) bhdu.getColumn(0);
+ assertEquals("deg23", "a", col[0]);
+ assertEquals("deg24", "f", col[5]);
+
+ col = (String[]) bhdu.getColumn(3);
+ assertEquals("deg25", "", col[0]);
+ assertEquals("deg26", "", col[5]);
+
+ col = (String[]) bhdu.getColumn(7); // All nulls
+ assertEquals("deg27", "", col[0]);
+ assertEquals("deg28", "", col[5]);
+
+ col = (String[]) bhdu.getColumn(8);
+
+ assertEquals("deg29", "a", col[0]);
+ assertEquals("deg210", "", col[1]);
+ }
+
+ @Test
+ public void testMultHDU() throws Exception {
+ BufferedFile ff = new BufferedFile("bt9.fits", "rw");
+ Object[] data = new Object[]{bytes, bits, bools, shorts, ints,
+ floats, doubles, longs, strings};
+
+ Fits f = new Fits();
+
+ // Add two identical HDUs
+ f.addHDU(Fits.makeHDU(data));
+ f.addHDU(Fits.makeHDU(data));
+ f.write(ff);
+ ff.close();
+
+ f = new Fits("bt9.fits");
+
+ f.readHDU();
+ BinaryTableHDU hdu;
+ // This would fail before...
+ int count = 0;
+ while ((hdu = (BinaryTableHDU) f.readHDU()) != null) {
+ int nrow = hdu.getHeader().getIntValue("NAXIS2");
+ count += 1;
+ assertEquals(nrow, 50);
+ for (int i = 0; i < nrow; i += 1) {
+ Object o = hdu.getRow(i);
+ }
+ }
+ assertEquals(count, 2);
+ }
+
+ @Test
+ public void testByteArray() {
+ String[] sarr = {"abc", " de", "f"};
+ byte[] barr = {'a', 'b', 'c', ' ', 'b', 'c', 'a', 'b', ' '};
+
+ byte[] obytes = nom.tam.fits.FitsUtil.stringsToByteArray(sarr, 3);
+ assertEquals("blen", obytes.length, 9);
+ assertEquals("b1", obytes[0], (byte) 'a');
+ assertEquals("b1", obytes[1], (byte) 'b');
+ assertEquals("b1", obytes[2], (byte) 'c');
+ assertEquals("b1", obytes[3], (byte) ' ');
+ assertEquals("b1", obytes[4], (byte) 'd');
+ assertEquals("b1", obytes[5], (byte) 'e');
+ assertEquals("b1", obytes[6], (byte) 'f');
+ assertEquals("b1", obytes[7], (byte) ' ');
+ assertEquals("b1", obytes[8], (byte) ' ');
+
+ String[] ostrings = nom.tam.fits.FitsUtil.byteArrayToStrings(barr, 3);
+ assertEquals("slen", ostrings.length, 3);
+ assertEquals("s1", ostrings[0], "abc");
+ assertEquals("s2", ostrings[1], "bc");
+ assertEquals("s3", ostrings[2], "ab");
+ }
+
+ @Test
+ public void columnMetaTest() throws Exception {
+ Object[] data = new Object[]{shorts, ints,
+ floats, doubles};
+
+ Fits f = new Fits();
+
+ // Add two identical HDUs
+ BinaryTableHDU bhdu = (BinaryTableHDU) Fits.makeHDU(data);
+ f.addHDU(bhdu);
+
+ // makeHDU creates the TFORM keywords and sometimes
+ // the TDIM keywords. Let's add some additional
+ // column metadata. For each column we'll want a TTYPE, TCOMM,
+ // TUNIT and TX and TY
+ // value and we want the final header to be in this order
+ // TTYPE, TCOMM, TFORM, [TDIM,] TUNIT, TX, TY
+ int oldNCols = bhdu.getNCols();
+
+ for (int i = 0; i < bhdu.getNCols(); i += 1) {
+ bhdu.setColumnMeta(i, "TTYPE", "NAM" + (i + 1), null, false);
+ bhdu.setColumnMeta(i, "TCOMM", true, "Comment in comment", false);
+ bhdu.setColumnMeta(i, "TUNIT", "UNIT" + (i + 1), null, true);
+ bhdu.setColumnMeta(i, "TX", (i + 1), null, true);
+ bhdu.setColumnMeta(i, "TY", 2. * (i + 1), null, true);
+ }
+
+ BufferedFile ff = new BufferedFile("bt10.fits", "rw");
+ f.write(ff);
+ ff.close();
+ f = new Fits("bt10.fits");
+
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ Header hdr = bhdu.getHeader();
+ assertEquals("metaCount", oldNCols, bhdu.getNCols());
+ for (int i = 0; i < bhdu.getNCols(); i += 1) {
+ // If this worked, the first header should be the TTYPE
+ hdr.findCard("TTYPE" + (i + 1));
+ HeaderCard hc = hdr.nextCard();
+ assertEquals("M" + i + "0", "TTYPE" + (i + 1), hc.getKey());
+ hc = hdr.nextCard();
+ assertEquals("M" + i + "A", "TCOMM" + (i + 1), hc.getKey());
+ hc = hdr.nextCard();
+ assertEquals("M" + i + "B", "TFORM" + (i + 1), hc.getKey());
+ hc = hdr.nextCard();
+ // There may have been a TDIM keyword inserted automatically. Let's
+ // skip it if it was. It should only appear immediately after the
+ // TFORM keyword.
+ if (hc.getKey().startsWith("TDIM")) {
+ hc = hdr.nextCard();
+ }
+ assertEquals("M" + i + "C", "TUNIT" + (i + 1), hc.getKey());
+ hc = hdr.nextCard();
+ assertEquals("M" + i + "D", "TX" + (i + 1), hc.getKey());
+ hc = hdr.nextCard();
+ assertEquals("M" + i + "E", "TY" + (i + 1), hc.getKey());
+ }
+ }
+
+ @Test
+ public void specialStringsTest() throws Exception {
+ String[] strings = new String[]{
+ "abc",
+ "abc\000",
+ "abc\012abc",
+ "abc\000abc",
+ "abc\177",
+ "abc\001def\002ghi\003"
+ };
+
+ String[] results1 = new String[]{
+ strings[0], strings[0], strings[2], strings[0], strings[4], strings[5]
+ };
+ String[] results2 = new String[]{
+ strings[0], strings[0], "abc abc", strings[0], "abc ", "abc def ghi "
+ };
+
+ FitsFactory.setUseAsciiTables(false);
+ FitsFactory.setCheckAsciiStrings(false);
+
+ Fits f = new Fits();
+
+ Object[] objs = new Object[]{strings};
+ BinaryTableHDU bhdu = (BinaryTableHDU) Fits.makeHDU(objs);
+ f.addHDU(bhdu);
+
+ BufferedFile bf = new BufferedFile("bt11a.fits", "rw");
+ f.write(bf);
+
+ bf.close();
+
+ f = new Fits("bt11a.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ String[] vals = (String[]) bhdu.getColumn(0);
+ for (int i = 0; i < strings.length; i += 1) {
+ assertEquals("ssa" + i, results1[i], vals[i]);
+ }
+
+ FitsFactory.setCheckAsciiStrings(true);
+ System.err.println(" A warning about invalid ASCII strings should follow.");
+ f = new Fits();
+
+ bhdu = (BinaryTableHDU) Fits.makeHDU(objs);
+ f.addHDU(bhdu);
+ bf = new BufferedFile("bt11b.fits", "rw");
+ f.write(bf);
+
+ bf.close();
+
+ f = new Fits("bt11b.fits");
+ bhdu = (BinaryTableHDU) f.getHDU(1);
+ vals = (String[]) bhdu.getColumn(0);
+ for (int i = 0; i < strings.length; i += 1) {
+ assertEquals("ssb" + i, results2[i], vals[i]);
+ }
+
+ FitsFactory.setCheckAsciiStrings(false);
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.image.*;
+import nom.tam.util.*;
+import nom.tam.fits.*;
+
+import java.net.URL;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+/** Test reading .Z and .gz compressed files.
+ */
+public class CompressTest {
+
+ @Test
+ public void testgz() throws Exception {
+
+ File fil = new File(".");
+ System.out.println("File is:" + fil.getCanonicalPath());
+ Fits f = new Fits("http://heasarc.gsfc.nasa.gov/FTP/asca/data/rev2/43021000/images/ad43021000gis25670_lo.totsky.gz");
+
+ BasicHDU h = f.readHDU();
+ int[][] data = (int[][]) h.getKernel();
+ double sum = 0;
+ for (int i = 0; i < data.length; i += 1) {
+ for (int j = 0; j < data[i].length; j += 1) {
+ sum += data[i][j];
+ }
+ }
+ assertEquals("ZCompress", sum, 296915., 0);
+ }
+
+ @Test
+ public void testZ() throws Exception {
+
+ Fits f = new Fits("http://heasarc.gsfc.nasa.gov/FTP/rosat/data/pspc/processed_data/600000/rp600245n00/rp600245n00_im1.fits.Z");
+
+ BasicHDU h = f.readHDU();
+ short[][] data = (short[][]) h.getKernel();
+ double sum = 0;
+ for (int i = 0; i < data.length; i += 1) {
+ for (int j = 0; j < data[i].length; j += 1) {
+ sum += data[i][j];
+ }
+ }
+ assertEquals("ZCompress", sum, 91806., 0);
+ }
+
+ @Test
+ public void testStream() throws Exception {
+ InputStream is;
+
+ is = new FileInputStream("test.fits");
+ assertEquals("Stream1", 300, streamRead(is, false, false));
+
+ is = new FileInputStream("test.fits.Z");
+ assertEquals("Stream2", 300, streamRead(is, false, false));
+
+ is = new FileInputStream("test.fits.gz");
+ assertEquals("Stream3", 300, streamRead(is, false, false));
+
+ is = new FileInputStream("test.fits");
+ assertEquals("Stream4", 300, streamRead(is, false, true));
+
+ is = new FileInputStream("test.fits.Z");
+ assertEquals("Stream5", 300, streamRead(is, false, true));
+
+ is = new FileInputStream("test.fits.gz");
+ assertEquals("Stream6", 300, streamRead(is, false, true));
+
+
+ is = new FileInputStream("test.fits.Z");
+ assertEquals("Stream7", 300, streamRead(is, true, true));
+
+ is = new FileInputStream("test.fits.gz");
+ assertEquals("Stream8", 300, streamRead(is, true, true));
+
+ is = new FileInputStream("test.fits.bz2");
+ assertEquals("Stream9", 300, streamRead(is, true, true));
+ }
+
+ @Test
+ public void testFile() throws Exception {
+ File is = new File("test.fits");
+ assertEquals("File1", 300, fileRead(is, false, false));
+
+ is = new File("test.fits.Z");
+ assertEquals("File2", 300, fileRead(is, false, false));
+
+ is = new File("test.fits.gz");
+ assertEquals("File3", 300, fileRead(is, false, false));
+
+ is = new File("test.fits");
+ assertEquals("File4", 300, fileRead(is, false, true));
+
+ is = new File("test.fits.Z");
+ assertEquals("File7", 300, fileRead(is, true, true));
+
+ is = new File("test.fits.gz");
+ assertEquals("File8", 300, fileRead(is, true, true));
+
+ is = new File("test.fits.bz2");
+ assertEquals("File9", 300, fileRead(is, true, true));
+ }
+
+ @Test
+ public void testString() throws Exception {
+ String is = "test.fits";
+ assertEquals("String1", 300, stringRead(is, false, false));
+
+ is = "test.fits.Z";
+ assertEquals("String2", 300, stringRead(is, false, false));
+
+ is = "test.fits.gz";
+ assertEquals("String3", 300, stringRead(is, false, false));
+
+ is = "test.fits";
+ assertEquals("String4", 300, stringRead(is, false, true));
+
+ is = "test.fits.Z";
+ assertEquals("String7", 300, stringRead(is, true, true));
+
+ is = "test.fits.gz";
+ assertEquals("String8", 300, stringRead(is, true, true));
+
+ is = "test.fits.bz2";
+ assertEquals("String8", 300, stringRead(is, true, true));
+
+ }
+
+ @Test
+ public void testURL() throws Exception {
+ String is = "test.fits";
+ assertEquals("String1", 300, urlRead(is, false, false));
+
+ is = "test.fits.Z";
+ assertEquals("String2", 300, urlRead(is, false, false));
+
+ is = "test.fits.gz";
+ assertEquals("String3", 300, urlRead(is, false, false));
+
+ is = "test.fits";
+ assertEquals("String4", 300, urlRead(is, false, true));
+
+ is = "test.fits.Z";
+ assertEquals("String7", 300, urlRead(is, true, true));
+
+ is = "test.fits.gz";
+ assertEquals("String8", 300, urlRead(is, true, true));
+
+ is = "test.fits.bz2";
+ assertEquals("String8", 300, urlRead(is, true, true));
+ }
+
+ int urlRead(String is, boolean comp, boolean useComp)
+ throws Exception {
+ File fil = new File(is);
+
+ String path = fil.getCanonicalPath();
+ URL u = new URL("file://" + path);
+
+ Fits f;
+ if (useComp) {
+ f = new Fits(u, comp);
+ } else {
+ f = new Fits(u);
+ }
+ short[][] data = (short[][]) f.readHDU().getKernel();
+
+ return total(data);
+ }
+
+ int streamRead(InputStream is, boolean comp, boolean useComp)
+ throws Exception {
+ Fits f;
+ if (useComp) {
+ f = new Fits(is, comp);
+ } else {
+ f = new Fits(is);
+ }
+ short[][] data = (short[][]) f.readHDU().getKernel();
+ is.close();
+
+ return total(data);
+ }
+
+ int fileRead(File is, boolean comp, boolean useComp)
+ throws Exception {
+ Fits f;
+ if (useComp) {
+ f = new Fits(is, comp);
+ } else {
+ f = new Fits(is);
+ }
+ short[][] data = (short[][]) f.readHDU().getKernel();
+
+ return total(data);
+ }
+
+ int stringRead(String is, boolean comp, boolean useComp)
+ throws Exception {
+ Fits f;
+ if (useComp) {
+ f = new Fits(is, comp);
+ } else {
+ f = new Fits(is);
+ }
+ short[][] data = (short[][]) f.readHDU().getKernel();
+
+ return total(data);
+ }
+
+ int total(short[][] data) {
+ int total = 0;
+ for (int i = 0; i < data.length; i += 1) {
+ for (int j = 0; j < data[i].length; j += 1) {
+ total += data[i][j];
+ }
+ }
+ return total;
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.fits.FitsDate;
+
+/** Test the FITS date class.
+ * This class is derived from the internal testing utilities
+ * in FitsDate written by David Glowacki.
+ */
+public class DateTester {
+
+ @Test
+ public void test() {
+
+ assertEquals("t1", true, testArg("20/09/79"));
+ assertEquals("t1", true, testArg("1997-07-25"));
+ assertEquals("t1", true, testArg("1987-06-05T04:03:02.01"));
+ assertEquals("t1", true, testArg("1998-03-10T16:58:34"));
+ assertEquals("t1", true, testArg(null));
+ assertEquals("t1", true, testArg(" "));
+
+ assertEquals("t1", false, testArg("20/09/"));
+ assertEquals("t1", false, testArg("/09/79"));
+ assertEquals("t1", false, testArg("09//79"));
+ assertEquals("t1", false, testArg("20/09/79/"));
+
+ assertEquals("t1", false, testArg("1997-07"));
+ assertEquals("t1", false, testArg("-07-25"));
+ assertEquals("t1", false, testArg("1997--07-25"));
+ assertEquals("t1", false, testArg("1997-07-25-"));
+
+ assertEquals("t1", false, testArg("5-Aug-1992"));
+ assertEquals("t1", false, testArg("28/02/91 16:32:00"));
+ assertEquals("t1", false, testArg("18-Feb-1993"));
+ assertEquals("t1", false, testArg("nn/nn/nn"));
+ }
+
+ boolean testArg(String arg) {
+ try {
+ FitsDate fd = new FitsDate(arg);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import junit.framework.JUnit4TestAdapter;
+import nom.tam.fits.HeaderCard;
+import nom.tam.fits.FitsFactory;
+
+public class HeaderCardTest {
+
+ @Test
+ public void test1() throws Exception {
+
+ HeaderCard p;
+ p = new HeaderCard("SIMPLE = T");
+
+ assertEquals("t1", "SIMPLE", p.getKey());
+ assertEquals("t2", "T", p.getValue());
+ assertNull("t3", p.getComment());
+
+ p = new HeaderCard("VALUE = 123");
+ assertEquals("t4", "VALUE", p.getKey());
+ assertEquals("t5", "123", p.getValue());
+ assertNull("t3", p.getComment());
+
+ p = new HeaderCard("VALUE = 1.23698789798798E23 / Comment ");
+ assertEquals("t6", "VALUE", p.getKey());
+ assertEquals("t7", "1.23698789798798E23", p.getValue());
+ assertEquals("t8", "Comment", p.getComment());
+
+ String lng = "111111111111111111111111111111111111111111111111111111111111111111111111";
+ p = new HeaderCard("COMMENT " + lng);
+ assertEquals("t9", "COMMENT", p.getKey());
+ assertNull("t10", p.getValue());
+ assertEquals("t11", lng, p.getComment());
+
+ boolean thrown = false;
+ try {
+ //
+ p = new HeaderCard("VALUE = ' ");
+ } catch (Exception e) {
+ thrown = true;
+ }
+ assertEquals("t12", true, thrown);
+
+
+ p = new HeaderCard("COMMENT " + lng + lng);
+ assertEquals("t13", lng, p.getComment());
+
+ HeaderCard z = new HeaderCard("TTTT", 1.234567891234567891234567e101, "a comment");
+ assertTrue("t14", z.toString().indexOf("E") > 0);
+ }
+
+ @Test
+ public void test3() throws Exception {
+
+ HeaderCard p = new HeaderCard("KEY", "VALUE", "COMMENT");
+ assertEquals("x1",
+ "KEY = 'VALUE ' / COMMENT ",
+ p.toString());
+
+ p = new HeaderCard("KEY", 123, "COMMENT");
+ assertEquals("x2",
+ "KEY = 123 / COMMENT ",
+ p.toString());
+ p = new HeaderCard("KEY", 1.23, "COMMENT");
+ assertEquals("x3",
+ "KEY = 1.23 / COMMENT ",
+ p.toString());
+ p = new HeaderCard("KEY", true, "COMMENT");
+ assertEquals("x4",
+ "KEY = T / COMMENT ",
+ p.toString());
+
+
+ boolean thrown = false;
+ try {
+ p = new HeaderCard("LONGKEYWORD", 123, "COMMENT");
+ } catch (Exception e) {
+ thrown = true;
+ }
+ assertEquals("x5", true, thrown);
+
+ thrown = false;
+ String lng = "00000000001111111111222222222233333333334444444444555555555566666666667777777777";
+ try {
+ p = new HeaderCard("KEY", lng, "COMMENT");
+ } catch (Exception e) {
+ thrown = true;
+ }
+ assertEquals("x6", true, thrown);
+
+
+ // Only trailing spaces are stripped.
+ p = new HeaderCard("STRING", "VALUE", null);
+ assertEquals("x6", "VALUE", p.getValue());
+
+ p = new HeaderCard("STRING", "VALUE ", null);
+ assertEquals("x7", "VALUE", p.getValue());
+
+ p = new HeaderCard("STRING", " VALUE", null);
+ assertEquals("x8", " VALUE", p.getValue());
+
+ p = new HeaderCard("STRING", " VALUE ", null);
+ assertEquals("x9", " VALUE", p.getValue());
+
+ p = new HeaderCard("QUOTES", "ABC'DEF", null);
+ assertEquals("x10", "ABC'DEF", p.getValue());
+ assertEquals("x10b", p.toString().indexOf("''") > 0, true);
+
+ p = new HeaderCard("QUOTES", "ABC''DEF", null);
+ assertEquals("x11", "ABC''DEF", p.getValue());
+ assertEquals("x10b", p.toString().indexOf("''''") > 0, true);
+ }
+
+ @Test
+ public void testHierarch() throws Exception {
+
+ HeaderCard hc;
+ String key = "HIERARCH.TEST1.TEST2.INT";
+ boolean thrown = false;
+ try {
+ hc = new HeaderCard(key, 123, "Comment");
+ } catch (Exception e) {
+ thrown = true;
+ }
+ assertEquals("h1", true, thrown);
+
+ String card = "HIERARCH TEST1 TEST2 INT= 123 / Comment ";
+ hc = new HeaderCard(card);
+ assertEquals("h2", "HIERARCH", hc.getKey());
+ assertNull("h3", hc.getValue());
+ assertEquals("h4", "TEST1 TEST2 INT= 123 / Comment", hc.getComment());
+
+ FitsFactory.setUseHierarch(true);
+
+ hc = new HeaderCard(key, 123, "Comment");
+
+ assertEquals("h5", key, hc.getKey());
+ assertEquals("h6", "123", hc.getValue());
+ assertEquals("h7", "Comment", hc.getComment());
+
+ hc = new HeaderCard(card);
+ assertEquals("h8", key, hc.getKey());
+ assertEquals("h9", "123", hc.getValue());
+ assertEquals("h10", "Comment", hc.getComment());
+ }
+
+ @Test
+ public void testLongDoubles() throws Exception {
+ // Check to see if we make long double values
+ // fit in the recommended space.
+ HeaderCard hc = new HeaderCard("TEST", -1.234567890123456789e-123, "dummy");
+ String val = hc.getValue();
+ assertEquals("tld1", val.length(), 20);
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import nom.tam.fits.*;
+import nom.tam.util.*;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+public class HeaderTest {
+
+ /** Check out header manipulation.
+ */
+ @Test
+ public void simpleImages() throws Exception {
+ float[][] img = new float[300][300];
+
+ Fits f = new Fits();
+
+ ImageHDU hdu = (ImageHDU) Fits.makeHDU(img);
+ BufferedFile bf = new BufferedFile("ht1.fits", "rw");
+ f.addHDU(hdu);
+ f.write(bf);
+ bf.close();
+
+ f = new Fits("ht1.fits");
+ hdu = (ImageHDU) f.getHDU(0);
+ Header hdr = hdu.getHeader();
+
+ assertEquals("NAXIS", 2, hdr.getIntValue("NAXIS"));
+ assertEquals("NAXIS1", 300, hdr.getIntValue("NAXIS1"));
+ assertEquals("NAXIS2", 300, hdr.getIntValue("NAXIS2"));
+ assertEquals("NAXIS2a", 300, hdr.getIntValue("NAXIS2", -1));
+ assertEquals("NAXIS3", -1, hdr.getIntValue("NAXIS3", -1));
+
+ assertEquals("BITPIX", -32, hdr.getIntValue("BITPIX"));
+
+
+ Cursor c = hdr.iterator();
+ HeaderCard hc = (HeaderCard) c.next();
+ assertEquals("SIMPLE_1", "SIMPLE", hc.getKey());
+
+ hc = (HeaderCard) c.next();
+ assertEquals("BITPIX_2", "BITPIX", hc.getKey());
+
+ hc = (HeaderCard) c.next();
+ assertEquals("NAXIS_3", "NAXIS", hc.getKey());
+
+ hc = (HeaderCard) c.next();
+ assertEquals("NAXIS1_4", "NAXIS1", hc.getKey());
+
+ hc = (HeaderCard) c.next();
+ assertEquals("NAXIS2_5", "NAXIS2", hc.getKey());
+ }
+
+ /** Confirm initial location versus EXTEND keyword (V. Forchi). */
+ @Test
+ public void extendTest() throws Exception {
+ Fits f = new Fits("ht1.fits");
+ Header h = f.getHDU(0).getHeader();
+ h.addValue("TESTKEY", "TESTVAL", "TESTCOMM");
+ h.rewrite();
+ f.getStream().close();
+ f = new Fits("ht1.fits");
+ h = f.getHDU(0).getHeader();
+
+ // We should be pointed after the EXTEND and before TESTKEY
+ h.addValue("TESTKEY2", "TESTVAL2", null); // Should precede TESTKEY
+
+ Cursor c = h.iterator();
+ assertEquals("E1", ((HeaderCard) c.next()).getKey(), "SIMPLE");
+ assertEquals("E2", ((HeaderCard) c.next()).getKey(), "BITPIX");
+ assertEquals("E3", ((HeaderCard) c.next()).getKey(), "NAXIS");
+ assertEquals("E4", ((HeaderCard) c.next()).getKey(), "NAXIS1");
+ assertEquals("E5", ((HeaderCard) c.next()).getKey(), "NAXIS2");
+ assertEquals("E6", ((HeaderCard) c.next()).getKey(), "EXTEND");
+ assertEquals("E7", ((HeaderCard) c.next()).getKey(), "TESTKEY2");
+ assertEquals("E8", ((HeaderCard) c.next()).getKey(), "TESTKEY");
+
+ }
+
+ @Test
+ public void cursorTest() throws Exception {
+
+ Fits f = new Fits("ht1.fits");
+ ImageHDU hdu = (ImageHDU) f.getHDU(0);
+ Header hdr = hdu.getHeader();
+ Cursor c = hdr.iterator();
+
+
+ c.setKey("XXX");
+ c.add("CTYPE1", new HeaderCard("CTYPE1", "GLON-CAR", "Galactic Longitude"));
+ c.add("CTYPE2", new HeaderCard("CTYPE2", "GLAT-CAR", "Galactic Latitude"));
+ c.setKey("CTYPE1"); // Move before CTYPE1
+ c.add("CRVAL1", new HeaderCard("CRVAL1", 0., "Longitude at reference"));
+ c.setKey("CTYPE2"); // Move before CTYPE2
+ c.add("CRVAL2", new HeaderCard("CRVAL2", -90., "Latitude at reference"));
+ c.setKey("CTYPE1"); // Just practicing moving around!!
+ c.add("CRPIX1", new HeaderCard("CRPIX1", 150.0, "Reference Pixel X"));
+ c.setKey("CTYPE2");
+ c.add("CRPIX2", new HeaderCard("CRPIX2", 0., "Reference pixel Y"));
+ c.add("INV2", new HeaderCard("INV2", true, "Invertible axis"));
+ c.add("SYM2", new HeaderCard("SYM2", "YZ SYMMETRIC", "Symmetries..."));
+
+ assertEquals("CTYPE1", "GLON-CAR", hdr.getStringValue("CTYPE1"));
+ assertEquals("CRPIX2", 0., hdr.getDoubleValue("CRPIX2", -2.), 0);
+
+ c.setKey("CRVAL1");
+ HeaderCard hc = (HeaderCard) c.next();
+ assertEquals("CRVAL1_c", "CRVAL1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("CRPIX1_c", "CRPIX1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("CTYPE1_c", "CTYPE1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("CRVAL2_c", "CRVAL2", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("CRPIX2_c", "CRPIX2", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("INV2_c", "INV2", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("SYM2_c", "SYM2", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("CTYPE2_c", "CTYPE2", hc.getKey());
+
+ hdr.findCard("CRPIX1");
+ hdr.addValue("INTVAL1", 1, "An integer value");
+ hdr.addValue("LOG1", true, "A true value");
+ hdr.addValue("LOGB1", false, "A false value");
+ hdr.addValue("FLT1", 1.34, "A float value");
+ hdr.addValue("FLT2", -1.234567890e-134, "A very long float");
+ hdr.insertComment("Comment after flt2");
+
+ c.setKey("INTVAL1");
+ hc = (HeaderCard) c.next();
+ assertEquals("INTVAL1", "INTVAL1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("LOG1", "LOG1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("LOGB1", "LOGB1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("FLT1", "FLT1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("FLT2", "FLT2", hc.getKey());
+ c.next(); // Skip comment
+ hc = (HeaderCard) c.next();
+ assertEquals("CRPIX1x", "CRPIX1", hc.getKey());
+
+ assertEquals("FLT1", 1.34, hdr.getDoubleValue("FLT1", 0), 0);
+ c.setKey("FLT1");
+ c.next();
+ c.remove();
+ assertEquals("FLT1", 0., hdr.getDoubleValue("FLT1", 0), 0);
+ c.setKey("LOGB1");
+ hc = (HeaderCard) c.next();
+ assertEquals("AftDel1", "LOGB1", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("AftDel2", "FLT2", hc.getKey());
+ hc = (HeaderCard) c.next();
+ assertEquals("AftDel3", "Comment after flt2", hc.getComment());
+ }
+
+ @Test
+ public void testBadHeader() throws Exception {
+
+ Fits f = new Fits("ht1.fits");
+ ImageHDU hdu = (ImageHDU) f.getHDU(0);
+ Header hdr = hdu.getHeader();
+ Cursor c = hdr.iterator();
+
+ c = hdr.iterator();
+ c.next();
+ c.next();
+ c.remove();
+ boolean thrown = false;
+ try {
+ hdr.rewrite();
+ } catch (Exception e) {
+ thrown = true;
+ }
+ assertEquals("BITPIX delete", true, thrown);
+ }
+
+ @Test
+ public void testUpdateHeaderComments() throws Exception {
+ byte[][] z = new byte[4][4];
+ Fits f = new Fits();
+ f.addHDU(FitsFactory.HDUFactory(z));
+ BufferedFile bf = new BufferedFile("hx1.fits", "rw");
+ f.write(bf);
+ bf.close();
+ f = new Fits("hx1.fits");
+ HeaderCard c1 = f.getHDU(0).getHeader().findCard("SIMPLE");
+ assertEquals("tuhc1", c1.getComment(), HeaderCommentsMap.getComment("header:simple:1"));
+ c1 = f.getHDU(0).getHeader().findCard("BITPIX");
+ assertEquals("tuhc2", c1.getComment(), HeaderCommentsMap.getComment("header:bitpix:1"));
+ HeaderCommentsMap.updateComment("header:bitpix:1", "A byte array");
+ HeaderCommentsMap.deleteComment("header:simple:1");
+ f = new Fits();
+ f.addHDU(FitsFactory.HDUFactory(z));
+ bf = new BufferedFile("hx2.fits", "rw");
+ f.write(bf);
+ bf.close();
+ f = new Fits("hx2.fits");
+ c1 = f.getHDU(0).getHeader().findCard("SIMPLE");
+ assertEquals("tuhc1", c1.getComment(), null);
+ c1 = f.getHDU(0).getHeader().findCard("BITPIX");
+ assertEquals("tuhc2", c1.getComment(), "A byte array");
+ }
+
+ @Test
+ public void testRewrite() throws Exception {
+
+ // Should be rewriteable until we add enough cards to
+ // start a new block.
+
+ Fits f = new Fits("ht1.fits");
+ ImageHDU hdu = (ImageHDU) f.getHDU(0);
+ Header hdr = hdu.getHeader();
+ Cursor c = hdr.iterator();
+
+ int nc = hdr.getNumberOfCards();
+ int nb = (nc - 1) / 36;
+
+ while (hdr.rewriteable()) {
+ int nbx = (hdr.getNumberOfCards() - 1) / 36;
+ assertEquals("Rewrite:" + nbx, nb == nbx, hdr.rewriteable());
+ c.add(new HeaderCard("DUMMY" + nbx, null, null));
+ }
+ }
+
+ @Test
+ public void longStringTest() throws Exception {
+
+ Header hdr = new Fits("ht1.fits").getHDU(0).getHeader();
+
+ String seq = "0123456789";
+ String lng = "";
+ for (int i = 0; i < 20; i += 1) {
+ lng += seq;
+ }
+ assertEquals("Initial state:", false, Header.getLongStringsEnabled());
+ Header.setLongStringsEnabled(true);
+ assertEquals("Set state:", true, Header.getLongStringsEnabled());
+ hdr.addValue("LONG1", lng, "Here is a comment");
+ hdr.addValue("LONG2", "xx'yy'zz" + lng, "Another comment");
+ hdr.addValue("SHORT", "A STRING ENDING IN A &", null);
+ hdr.addValue("LONGISH", lng + "&", null);
+ hdr.addValue("LONGSTRN", "OGIP 1.0", "Uses long strings");
+
+ String sixty = seq + seq + seq + seq + seq + seq;
+ hdr.addValue("APOS1", sixty + "''''''''''", "Should be 70 chars long");
+ hdr.addValue("APOS2", sixty + " ''''''''''", "Should be 71 chars long");
+
+ // Now try to read the values back.
+ BufferedFile bf = new BufferedFile("ht4.hdr", "rw");
+ hdr.write(bf);
+ bf.close();
+ String val = hdr.getStringValue("LONG1");
+ assertEquals("LongT1", val, lng);
+ val = hdr.getStringValue("LONG2");
+ assertEquals("LongT2", val, "xx'yy'zz" + lng);
+ assertEquals("APOS1", hdr.getStringValue("APOS1").length(), 70);
+ assertEquals("APOS2", hdr.getStringValue("APOS2").length(), 71);
+ Header.setLongStringsEnabled(false);
+ val = hdr.getStringValue("LONG1");
+ assertEquals("LongT3", true, !val.equals(lng));
+ assertEquals("Longt4", true, val.length() <= 70);
+ assertEquals("longamp1", hdr.getStringValue("SHORT"), "A STRING ENDING IN A &");
+ bf = new BufferedFile("ht4.hdr", "r");
+ hdr = new Header(bf);
+ assertEquals("Set state2:", true, Header.getLongStringsEnabled());
+ val = hdr.getStringValue("LONG1");
+ assertEquals("LongT5", val, lng);
+ val = hdr.getStringValue("LONG2");
+ assertEquals("LongT6", val, "xx'yy'zz" + lng);
+ assertEquals("longamp2", hdr.getStringValue("LONGISH"), lng + "&");
+ assertEquals("APOS1b", hdr.getStringValue("APOS1").length(), 70);
+ assertEquals("APOS2b", hdr.getStringValue("APOS2").length(), 71);
+ assertEquals("APOS2c", hdr.getStringValue("APOS2"), sixty + " ''''''''''");
+ assertEquals("longamp1b", hdr.getStringValue("SHORT"), "A STRING ENDING IN A &");
+ assertEquals("longamp2b", hdr.getStringValue("LONGISH"), lng + "&");
+
+ int cnt = hdr.getNumberOfCards();
+ // This should remove all three cards associated with
+ // LONG1
+ hdr.removeCard("LONG1");
+ assertEquals("deltest", cnt - 3, hdr.getNumberOfCards());
+ Header.setLongStringsEnabled(false);
+ // With long strings disabled this should only remove one more card.
+ hdr.removeCard("LONG2");
+ assertEquals("deltest2", cnt - 4, hdr.getNumberOfCards());
+
+ }
+}
+
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.image.*;
+import nom.tam.util.*;
+import nom.tam.fits.*;
+
+import java.io.File;
+
+/** Test the ImageHDU, ImageData and ImageTiler classes.
+ * - multiple HDU's in a single file
+ * - deferred input of HDUs
+ * - creating and reading arrays of all permitted types.
+ * - Tiles of 1, 2 and 3 dimensions
+ * - from a file
+ * - from internal data
+ * - Multiple tiles extracted from an image.
+ */
+public class ImageTest {
+
+ @Test
+ public void test() throws Exception {
+
+ Fits f = new Fits();
+
+ byte[][] bimg = new byte[40][40];
+ for (int i = 10; i < 30; i += 1) {
+ for (int j = 10; j < 30; j += 1) {
+ bimg[i][j] = (byte) (i + j);
+ }
+ }
+
+ short[][] simg = (short[][]) ArrayFuncs.convertArray(bimg, short.class);
+ int[][] iimg = (int[][]) ArrayFuncs.convertArray(bimg, int.class);
+ long[][] limg = (long[][]) ArrayFuncs.convertArray(bimg, long.class);
+ float[][] fimg = (float[][]) ArrayFuncs.convertArray(bimg, float.class);
+ double[][] dimg = (double[][]) ArrayFuncs.convertArray(bimg, double.class);
+ int[][][] img3 = new int[10][20][30];
+ for (int i = 0; i < 10; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ for (int k = 0; k < 30; k += 1) {
+ img3[i][j][k] = i + j + k;
+ }
+ }
+ }
+
+ double[] img1 = (double[]) ArrayFuncs.flatten(dimg);
+
+ // Make HDUs of various types.
+ f.addHDU(Fits.makeHDU(bimg));
+ f.addHDU(Fits.makeHDU(simg));
+ f.addHDU(Fits.makeHDU(iimg));
+ f.addHDU(Fits.makeHDU(limg));
+ f.addHDU(Fits.makeHDU(fimg));
+ f.addHDU(Fits.makeHDU(dimg));
+ f.addHDU(Fits.makeHDU(img3));
+ f.addHDU(Fits.makeHDU(img1));
+
+ assertEquals("HDU count before", f.getNumberOfHDUs(), 8);
+
+
+ // Write a FITS file.
+
+ BufferedFile bf = new BufferedFile("image1.fits", "rw");
+ f.write(bf);
+ bf.flush();
+ bf.close();
+ bf = null;
+
+
+ f = null;
+
+ bf = new BufferedFile("image1.fits");
+
+ // Read a FITS file
+ f = new Fits("image1.fits");
+ BasicHDU[] hdus = f.read();
+
+ assertEquals("HDU count after", f.getNumberOfHDUs(), 8);
+ assertEquals("byte image", true, ArrayFuncs.arrayEquals(bimg, hdus[0].getData().getKernel()));
+ assertEquals("short image", true, ArrayFuncs.arrayEquals(simg, hdus[1].getData().getKernel()));
+ assertEquals("int image", true, ArrayFuncs.arrayEquals(iimg, hdus[2].getData().getKernel()));
+ assertEquals("long image", true, ArrayFuncs.arrayEquals(limg, hdus[3].getData().getKernel()));
+ assertEquals("float image", true, ArrayFuncs.arrayEquals(fimg, hdus[4].getData().getKernel()));
+ assertEquals("double image", true, ArrayFuncs.arrayEquals(dimg, hdus[5].getData().getKernel()));
+ assertEquals("int3 image", true, ArrayFuncs.arrayEquals(img3, hdus[6].getData().getKernel()));
+ assertEquals("double1 image", true, ArrayFuncs.arrayEquals(img1, hdus[7].getData().getKernel()));
+ }
+
+ @Test
+ public void fileTest() throws Exception {
+
+ byte[][] bimg = new byte[40][40];
+ for (int i = 10; i < 30; i += 1) {
+ for (int j = 10; j < 30; j += 1) {
+ bimg[i][j] = (byte) (i + j);
+ }
+ }
+
+ short[][] simg = (short[][]) ArrayFuncs.convertArray(bimg, short.class);
+ int[][] iimg = (int[][]) ArrayFuncs.convertArray(bimg, int.class);
+ long[][] limg = (long[][]) ArrayFuncs.convertArray(bimg, long.class);
+ float[][] fimg = (float[][]) ArrayFuncs.convertArray(bimg, float.class);
+ double[][] dimg = (double[][]) ArrayFuncs.convertArray(bimg, double.class);
+ int[][][] img3 = new int[10][20][30];
+ for (int i = 0; i < 10; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ for (int k = 0; k < 30; k += 1) {
+ img3[i][j][k] = i + j + k;
+ }
+ }
+ }
+ double[] img1 = (double[]) ArrayFuncs.flatten(dimg);
+
+ Fits f = new Fits(new File("image1.fits"));
+ BasicHDU[] hdus = f.read();
+
+ assertEquals("fbyte image", true, ArrayFuncs.arrayEquals(bimg, hdus[0].getData().getKernel()));
+ assertEquals("fshort image", true, ArrayFuncs.arrayEquals(simg, hdus[1].getData().getKernel()));
+ assertEquals("fint image", true, ArrayFuncs.arrayEquals(iimg, hdus[2].getData().getKernel()));
+ assertEquals("flong image", true, ArrayFuncs.arrayEquals(limg, hdus[3].getData().getKernel()));
+ assertEquals("ffloat image", true, ArrayFuncs.arrayEquals(fimg, hdus[4].getData().getKernel()));
+ assertEquals("fdouble image", true, ArrayFuncs.arrayEquals(dimg, hdus[5].getData().getKernel()));
+ assertEquals("fint3 image", true, ArrayFuncs.arrayEquals(img3, hdus[6].getData().getKernel()));
+ assertEquals("fdouble1 image", true, ArrayFuncs.arrayEquals(img1, hdus[7].getData().getKernel()));
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.image.*;
+import nom.tam.util.*;
+import nom.tam.fits.*;
+
+import java.io.File;
+
+/** Test that we can read files that fail due to lack of padding in the final HDU.
+ */
+public class PaddingTest {
+
+ @Test
+ public void test1() throws Exception {
+
+ Fits f = new Fits();
+
+ byte[][] bimg = new byte[20][20];
+ for (int i = 0; i < 20; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ bimg[i][j] = (byte) (i + j);
+ }
+ }
+
+ BasicHDU hdu = Fits.makeHDU(bimg);
+ Header hdr = hdu.getHeader();
+ hdr.addValue("NEWKEY", "TESTVALUE", "Test keyword");
+ BufferedFile bf = new BufferedFile("padding1.fits", "rw");
+ hdr.write(bf);
+ bf.writeArray(bimg); // The data but no following padding.
+ bf.flush();
+ bf.close();
+
+ // Now try reading this back.
+ f = new Fits("padding1.fits");
+
+ try {
+ f.read();
+ } catch (PaddingException e) {
+ assertEquals("HDUCount", 0, f.getNumberOfHDUs());
+ f.addHDU(e.getTruncatedHDU());
+ assertEquals("HDUCount2", 1, f.getNumberOfHDUs());
+ }
+
+
+ ImageHDU hdu0 = (ImageHDU) (f.getHDU(0));
+ byte[][] aa = (byte[][]) hdu0.getKernel();
+ int miss = 0;
+ int match = 0;
+ for (int i = 0; i < 20; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ if (aa[i][j] != (byte) (i + j)) {
+ miss += 1;
+ } else {
+ match += 1;
+ }
+ }
+ }
+ assertEquals("PadMiss1:", miss, 0);
+ assertEquals("PadMatch1:", match, 400);
+ // Make sure we got the real header and not the one generated strictly from the data.
+ assertEquals("Update header:", hdu0.getHeader().getStringValue("NEWKEY"), "TESTVALUE");
+
+
+ nom.tam.image.ImageTiler it = hdu0.getTiler();
+
+ // Remember that the tile is always a flattened
+ // 1-D representation of the data.
+ byte[] data = (byte[]) it.getTile(new int[]{2, 2}, new int[]{2, 2});
+
+ assertEquals("tilet1:", data.length, 4);
+ assertEquals("tilet2:", data[0] + 0, 4);
+ assertEquals("tilet3:", data[1] + 0, 5);
+ assertEquals("tilet4:", data[2] + 0, 5);
+ assertEquals("tilet5:", data[3] + 0, 6);
+
+ }
+
+ @Test
+ public void test2() throws Exception {
+
+ Fits f = new Fits();
+
+ byte[][] bimg = new byte[20][20];
+ for (int i = 0; i < 20; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ bimg[i][j] = (byte) (i + j);
+ }
+ }
+
+ BasicHDU hdu = Fits.makeHDU(bimg);
+ f.addHDU(hdu);
+
+ // First create a FITS file with a truncated second HDU.
+ BufferedFile bf = new BufferedFile("padding2.fits", "rw");
+ f.write(bf);
+
+ hdu.getHeader().setXtension("IMAGE");
+ Cursor curs = hdu.getHeader().iterator();
+ int cnt = 0;
+ // Write the header
+ while (curs.hasNext()) {
+ bf.write(((HeaderCard) curs.next()).toString().getBytes());
+ cnt += 1;
+ }
+
+ // The padding between header and data
+ byte[] b = new byte[(36 - cnt) * 80]; // Assuming fewer than 36 cards.
+ for (int i = 0; i < b.length; i += 1) {
+ b[i] = 32; // i.e., a blank
+ }
+ bf.write(b);
+ for (int i = 0; i < 20; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ bimg[i][j] = (byte) (2 * (i + j));
+ }
+ }
+ bf.writeArray(bimg); // The data but no following padding.
+ bf.flush();
+ bf.close();
+
+ // Now try reading this back.
+ f = new Fits("padding2.fits");
+
+ try {
+ f.read();
+ } catch (PaddingException e) {
+ assertEquals("HDUCount", 1, f.getNumberOfHDUs());
+ f.addHDU(e.getTruncatedHDU());
+ assertEquals("HDUCount2", 2, f.getNumberOfHDUs());
+ }
+
+ ImageHDU hdu0 = (ImageHDU) (f.getHDU(0));
+ ImageHDU hdu1 = (ImageHDU) (f.getHDU(1));
+ byte[][] aa = (byte[][]) hdu0.getKernel();
+ byte[][] bb = (byte[][]) hdu1.getKernel();
+ int miss = 0;
+ int match = 0;
+ for (int i = 0; i < 20; i += 1) {
+ for (int j = 0; j < 20; j += 1) {
+ if (bb[i][j] != (byte) (2 * aa[i][j])) {
+ miss += 1;
+ } else {
+ match += 1;
+ }
+ }
+ }
+ assertEquals("PadMiss2:", miss, 0);
+ assertEquals("PadMatch2:", match, 400);
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import nom.tam.util.*;
+import nom.tam.fits.*;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+/** Test random groups formats in FITS data.
+ * Write and read random groups data
+ */
+public class RandomGroupsTest {
+
+ @Test
+ public void test() throws Exception {
+
+ float[][] fa = new float[20][20];
+ float[] pa = new float[3];
+
+ BufferedFile bf = new BufferedFile("rg1.fits", "rw");
+
+ Object[][] data = new Object[1][2];
+ data[0][0] = pa;
+ data[0][1] = fa;
+
+ // First lets write out the file painfully group by group.
+ BasicHDU hdu = Fits.makeHDU(data);
+ Header hdr = hdu.getHeader();
+ // Change the number of groups
+ hdr.addValue("GCOUNT", 20, "Number of groups");
+ hdr.write(bf);
+
+ for (int i = 0; i < 20; i += 1) {
+
+ for (int j = 0; j < pa.length; j += 1) {
+ pa[j] = i + j;
+ }
+ for (int j = 0; j < fa.length; j += 1) {
+ fa[j][j] = i * j;
+ }
+ // Write a group
+ bf.writeArray(data);
+ }
+
+ byte[] padding = new byte[FitsUtil.padding(20 * ArrayFuncs.computeLSize(data))];
+ bf.write(padding);
+
+ bf.flush();
+ bf.close();
+
+ // Read back the data.
+ Fits f = new Fits("rg1.fits");
+ BasicHDU[] hdus = f.read();
+
+ data = (Object[][]) hdus[0].getKernel();
+
+ for (int i = 0; i < data.length; i += 1) {
+
+ pa = (float[]) data[i][0];
+ fa = (float[][]) data[i][1];
+ for (int j = 0; j < pa.length; j += 1) {
+ assertEquals("paramTest:" + i + " " + j, (float) (i + j), pa[j], 0);
+ }
+ for (int j = 0; j < fa.length; j += 1) {
+ assertEquals("dataTest:" + i + " " + j, (float) (i * j), fa[j][j], 0);
+ }
+ }
+
+ // Now do it in one fell swoop -- but we have to have
+ // all the data in place first.
+ f = new Fits();
+
+ // Generate a FITS HDU from the kernel.
+ f.addHDU(Fits.makeHDU(data));
+ bf = new BufferedFile("rg2.fits", "rw");
+ f.write(bf);
+
+ bf.flush();
+ bf.close();
+
+ f = new Fits("rg2.fits");
+ data = (Object[][]) f.read()[0].getKernel();
+ for (int i = 0; i < data.length; i += 1) {
+
+ pa = (float[]) data[i][0];
+ fa = (float[][]) data[i][1];
+ for (int j = 0; j < pa.length; j += 1) {
+ assertEquals("paramTest:" + i + " " + j, (float) (i + j), pa[j], 0);
+ }
+ for (int j = 0; j < fa.length; j += 1) {
+ assertEquals("dataTest:" + i + " " + j, (float) (i * j), fa[j][j], 0);
+ }
+ }
+ }
+}
--- /dev/null
+package nom.tam.fits.test;
+
+import nom.tam.fits.*;
+import nom.tam.util.*;
+import nom.tam.image.ImageTiler;
+import java.io.*;
+
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+/** This class tests the ImageTiler. It
+ * first creates a FITS file and then reads
+ * it back and allows the user to select
+ * tiles. The values of the corner and center
+ * pixels for the selected tile are displayed.
+ * Both file and memory tiles are checked.
+ */
+public class TilerTest {
+
+ void doTile(String test,
+ float[][] data,
+ ImageTiler t,
+ int x, int y, int nx, int ny)
+ throws Exception {
+
+ float[] tile = new float[nx * ny];
+ t.getTile(tile, new int[]{y, x}, new int[]{ny, nx});
+
+
+ float sum0 = 0;
+ float sum1 = 0;
+
+ for (int i = 0; i < nx; i += 1) {
+ for (int j = 0; j < ny; j += 1) {
+ sum0 += tile[i + j * nx];
+ sum1 += data[j + y][i + x];
+ }
+ }
+
+ assertEquals("Tiler" + test, sum0, sum1, 0);
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ float[][] data = new float[300][300];
+
+ for (int i = 0; i < 300; i += 1) {
+ for (int j = 0; j < 300; j += 1) {
+ data[i][j] = 1000 * i + j;
+ }
+ }
+
+ Fits f = new Fits();
+
+ BufferedFile bf = new BufferedFile("tiler1.fits", "rw");
+ f.addHDU(Fits.makeHDU(data));
+
+ f.write(bf);
+ bf.close();
+
+ f = new Fits("tiler1.fits");
+
+ ImageHDU h = (ImageHDU) f.readHDU();
+
+ ImageTiler t = h.getTiler();
+ doTile("t1", data, t, 200, 200, 50, 50);
+ doTile("t2", data, t, 133, 133, 72, 26);
+
+ Object o = h.getData().getKernel();
+ doTile("t3", data, t, 200, 200, 50, 50);
+ doTile("t4", data, t, 133, 133, 72, 26);
+ }
+}
--- /dev/null
+package nom.tam.fits.utilities;
+
+import nom.tam.fits.*;
+import nom.tam.util.*;
+
+public class FitsCopy {
+
+ public static void main(String[] args) throws Exception {
+
+ String file = args[0];
+
+ Fits f = new Fits(file);
+ int i = 0;
+ BasicHDU h;
+
+ do {
+ h = f.readHDU();
+ if (h != null) {
+ if (i == 0) {
+ System.out.println("\n\nPrimary header:\n");
+ } else {
+ System.out.println("\n\nExtension " + i + ":\n");
+ }
+ i += 1;
+ h.info();
+ }
+ } while (h != null);
+ BufferedFile bf = new BufferedFile(args[1], "rw");
+ f.write(bf);
+ bf.close();
+
+ }
+}
--- /dev/null
+package nom.tam.fits.utilities;
+
+import nom.tam.fits.*;
+
+public class FitsReader {
+
+ public static void main(String[] args) throws Exception {
+
+ String file = args[0];
+
+ Fits f = new Fits(file);
+ int i = 0;
+ BasicHDU h;
+
+ do {
+ h = f.readHDU();
+ if (h != null) {
+ if (i == 0) {
+ System.out.println("\n\nPrimary header:\n");
+ } else {
+ System.out.println("\n\nExtension " + i + ":\n");
+ }
+ i += 1;
+ h.info();
+ }
+ } while (h != null);
+
+ }
+}
--- /dev/null
+package nom.tam.image;
+
+import nom.tam.util.*;
+import java.lang.reflect.Array;
+import java.io.IOException;
+
+/** This class provides a subset of an N-dimensional image.
+ * Modified May 2, 2000 by T. McGlynn to permit
+ * tiles that go off the edge of the image.
+ */
+public class ImageTiler {
+
+ RandomAccess f;
+ long fileOffset;
+ int[] dims;
+ Class base;
+
+ /** Create a tiler.
+ * @param f The random access device from which image data may be read.
+ * This may be null if the tile information is available from
+ * memory.
+ * @param fileOffset The file offset within the RandomAccess device at which
+ * the data begins.
+ * @param dims The actual dimensions of the image.
+ * @param base The base class (should be a primitive type) of the image.
+ */
+ public ImageTiler(RandomAccess f, long fileOffset, int[] dims,
+ Class base) {
+ this.f = f;
+ this.fileOffset = fileOffset;
+ this.dims = dims;
+ this.base = base;
+ }
+
+ /** See if we can get the image data from memory.
+ * This may be overriden by other classes, notably
+ * in nom.tam.fits.ImageData.
+ */
+ public Object getMemoryImage() {
+ return null;
+ }
+
+ /** Get a subset of the image. An image tile is returned
+ * as a one-dimensional array although the image will
+ * normally be multi-dimensional.
+ * @param The starting corner (using 0 as the start) for the image.
+ * @param The length requested in each dimension.
+ */
+ public Object getTile(int[] corners, int[] lengths) throws IOException {
+
+ if (corners.length != dims.length || lengths.length != dims.length) {
+ throw new IOException("Inconsistent sub-image request");
+ }
+
+ int arraySize = 1;
+ for (int i = 0; i < dims.length; i += 1) {
+
+ if (corners[i] < 0 || lengths[i] < 0 || corners[i] + lengths[i] > dims[i]) {
+ throw new IOException("Sub-image not within image");
+ }
+
+ arraySize *= lengths[i];
+ }
+
+ Object outArray = ArrayFuncs.newInstance(base, arraySize);
+
+ getTile(outArray, corners, lengths);
+ return outArray;
+ }
+
+ /** Get a tile, filling in a prespecified array.
+ * This version does not check that the user hase
+ * entered a valid set of corner and length arrays.
+ * ensure that out matches the
+ * length implied by the lengths array.
+ *
+ * @param outArray The output tile array. A one-dimensional
+ * array.
+ * Data not within the valid limits of the image will
+ * be left unchanged. The length of this
+ * array should be the product of lengths.
+ * @param corners The corners of the tile.
+ * @param lengths The dimensions of the tile.
+ *
+ */
+ public void getTile(Object outArray, int[] corners, int[] lengths)
+ throws IOException {
+
+ Object data = getMemoryImage();
+
+ if (data == null && f == null) {
+ throw new IOException("No data source for tile subset");
+ }
+ fillTile(data, outArray, dims, corners, lengths);
+ }
+
+ /** Fill the subset.
+ * @param data The memory-resident data image.
+ * This may be null if the image is to
+ * be read from a file. This should
+ * be a multi-dimensional primitive array.
+ * @param o The tile to be filled. This is a
+ * simple primitive array.
+ * @param dims The dimensions of the full image.
+ * @param corners The indices of the corner of the image.
+ * @param lengths The dimensions of the subset.
+ */
+ protected void fillTile(Object data, Object o, int[] dims, int[] corners, int[] lengths)
+ throws IOException {
+
+
+ int n = dims.length;
+ int[] posits = new int[n];
+ int baseLength = ArrayFuncs.getBaseLength(o);
+ int segment = lengths[n - 1];
+
+ System.arraycopy(corners, 0, posits, 0, n);
+ long currentOffset = 0;
+ if (data == null) {
+ currentOffset = f.getFilePointer();
+ }
+
+ int outputOffset = 0;
+
+
+ do {
+
+ // This implies there is some overlap
+ // in the last index (in conjunction
+ // with other tests)
+
+ int mx = dims.length - 1;
+ boolean validSegment =
+ posits[mx] + lengths[mx] >= 0
+ && posits[mx] < dims[mx];
+
+
+ // Don't do anything for the current
+ // segment if anything but the
+ // last index is out of range.
+
+ if (validSegment) {
+ for (int i = 0; i < mx; i += 1) {
+ if (posits[i] < 0 || posits[i] >= dims[i]) {
+ validSegment = false;
+ break;
+ }
+ }
+ }
+
+ if (validSegment) {
+ if (data != null) {
+ fillMemData(data, posits, segment, o, outputOffset, 0);
+ } else {
+ int offset = getOffset(dims, posits) * baseLength;
+
+ // Point to offset at real beginning
+ // of segment
+ int actualLen = segment;
+ int actualOffset = offset;
+ int actualOutput = outputOffset;
+ if (posits[mx] < 0) {
+ actualOffset -= posits[mx] * baseLength;
+ actualOutput -= posits[mx];
+ actualLen += posits[mx];
+ }
+ if (posits[mx] + segment > dims[mx]) {
+ actualLen -= posits[mx] + segment - dims[mx];
+ }
+ fillFileData(o, actualOffset, actualOutput, actualLen);
+ }
+ }
+ outputOffset += segment;
+
+ } while (incrementPosition(corners, posits, lengths));
+ if (data == null) {
+ f.seek(currentOffset);
+ }
+ }
+
+ /** Fill a single segment from memory.
+ * This routine is called recursively to handle multi-dimensional
+ * arrays. E.g., if data is three-dimensional, this will
+ * recurse two levels until we get a call with a single dimensional
+ * datum. At that point the appropriate data will be copied
+ * into the output.
+ *
+ * @param data The in-memory image data.
+ * @param posits The current position for which data is requested.
+ * @param length The size of the segments.
+ * @param output The output tile.
+ * @param outputOffset The current offset into the output tile.
+ * @param dim The current dimension being
+ */
+ protected void fillMemData(Object data, int[] posits, int length,
+ Object output, int outputOffset, int dim) {
+
+
+ if (data instanceof Object[]) {
+
+ Object[] xo = (Object[]) data;
+ fillMemData(xo[posits[dim]], posits, length, output, outputOffset, dim + 1);
+
+ } else {
+
+ // Adjust the spacing for the actual copy.
+ int startFrom = posits[dim];
+ int startTo = outputOffset;
+ int copyLength = length;
+
+ if (posits[dim] < 0) {
+ startFrom -= posits[dim];
+ startTo -= posits[dim];
+ copyLength += posits[dim];
+ }
+ if (posits[dim] + length > dims[dim]) {
+ copyLength -= (posits[dim] + length - dims[dim]);
+ }
+
+ System.arraycopy(data, startFrom, output, startTo, copyLength);
+ }
+ }
+
+ /** File a tile segment from a file.
+ * @param output The output tile.
+ * @param delta The offset from the beginning of the image in bytes.
+ * @param outputOffset The index into the output array.
+ * @param segment The number of elements to be read for this segment.
+ */
+ protected void fillFileData(Object output, int delta, int outputOffset,
+ int segment) throws IOException {
+
+
+ f.seek(fileOffset + delta);
+
+ if (base == float.class) {
+ f.read((float[]) output, outputOffset, segment);
+ } else if (base == int.class) {
+ f.read((int[]) output, outputOffset, segment);
+ } else if (base == short.class) {
+ f.read((short[]) output, outputOffset, segment);
+ } else if (base == double.class) {
+ f.read((double[]) output, outputOffset, segment);
+ } else if (base == byte.class) {
+ f.read((byte[]) output, outputOffset, segment);
+ } else if (base == char.class) {
+ f.read((char[]) output, outputOffset, segment);
+ } else if (base == long.class) {
+ f.read((long[]) output, outputOffset, segment);
+ } else {
+ throw new IOException("Invalid type for tile array");
+ }
+ }
+
+ /** Increment the offset within the position array.
+ * Note that we never look at the last index since
+ * we copy data a block at a time and not byte by byte.
+ * @param start The starting corner values.
+ * @param current The current offsets.
+ * @param lengths The desired dimensions of the subset.
+ */
+ protected static boolean incrementPosition(int[] start,
+ int[] current,
+ int[] lengths) {
+
+ for (int i = start.length - 2; i >= 0; i -= 1) {
+ if (current[i] - start[i] < lengths[i] - 1) {
+ current[i] += 1;
+ for (int j = i + 1; j < start.length - 1; j += 1) {
+ current[j] = start[j];
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Get the offset of a given position.
+ * @param dims The dimensions of the array.
+ * @param pos The index requested.
+ */
+ public static final int getOffset(int[] dims, int[] pos) {
+
+ int offset = 0;
+ for (int i = 0; i < dims.length; i += 1) {
+ if (i > 0) {
+ offset *= dims[i];
+ }
+ offset += pos[i];
+ }
+ return offset;
+ }
+
+ /** Read the entire image into a multidimensional
+ * array.
+ */
+ public Object getCompleteImage() throws IOException {
+
+ if (f == null) {
+ throw new IOException("Attempt to read from null file");
+ }
+ long currentOffset = f.getFilePointer();
+ Object o = ArrayFuncs.newInstance(base, dims);
+ f.seek(fileOffset);
+ f.readLArray(o);
+ f.seek(currentOffset);
+ return o;
+ }
+}
+
--- /dev/null
+package nom.tam.util;\r
+\r
+import java.io.IOException;\r
+\r
+public interface ArrayDataInput extends java.io.DataInput {\r
+\r
+ /** Read a generic (possibly multidimenionsional) primitive array.\r
+ * An Object[] array is also a legal argument if each element\r
+ * of the array is a legal.\r
+ * <p>\r
+ * The ArrayDataInput classes do not support String input since\r
+ * it is unclear how one would read in an Array of strings.\r
+ * @param o A [multidimensional] primitive (or Object) array.\r
+ * @deprecated See readLArray(Object o).\r
+ */\r
+ public int readArray(Object o) throws IOException;\r
+\r
+ /** Read an array. This version works even if the\r
+ * underlying data is more than 2 Gigabytes.\r
+ */\r
+ public long readLArray(Object o) throws IOException;\r
+\r
+ /* Read a complete primitive array */\r
+ public int read(byte[] buf) throws IOException;\r
+\r
+ public int read(boolean[] buf) throws IOException;\r
+\r
+ public int read(short[] buf) throws IOException;\r
+\r
+ public int read(char[] buf) throws IOException;\r
+\r
+ public int read(int[] buf) throws IOException;\r
+\r
+ public int read(long[] buf) throws IOException;\r
+\r
+ public int read(float[] buf) throws IOException;\r
+\r
+ public int read(double[] buf) throws IOException;\r
+\r
+ /* Read a segment of a primitive array. */\r
+ public int read(byte[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(boolean[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(char[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(short[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(int[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(long[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(float[] buf, int offset, int size) throws IOException;\r
+\r
+ public int read(double[] buf, int offset, int size) throws IOException;\r
+\r
+ /* Skip (forward) in a file */\r
+ public long skip(long distance) throws IOException;\r
+\r
+ /* Skip and require that the data be there. */\r
+ public long skipBytes(long toSkip) throws IOException;\r
+\r
+ /* Close the file. */\r
+ public void close() throws IOException;\r
+}\r
--- /dev/null
+package nom.tam.util;\r
+\r
+import java.io.IOException;\r
+\r
+public interface ArrayDataOutput extends java.io.DataOutput {\r
+\r
+ /** Write a generic (possibly multi-dimenionsional) primitive or String\r
+ * array. An array of Objects is also allowed if all\r
+ * of the elements are valid arrays.\r
+ * <p>\r
+ * This routine is not called 'write' to avoid possible compilation\r
+ * errors in routines which define only some of the other methods\r
+ * of the interface (and defer to the superclass on others).\r
+ * In that case there is an ambiguity as to whether to\r
+ * call the routine in the current class but convert to\r
+ * Object, or call the method from the super class with\r
+ * the same type argument.\r
+ * @param o The primitive or String array to be written.\r
+ * @throws IOException if the argument is not of the proper type\r
+ */\r
+ public void writeArray(Object o) throws IOException;\r
+\r
+ /* Write a complete array */\r
+ public void write(byte[] buf) throws IOException;\r
+\r
+ public void write(boolean[] buf) throws IOException;\r
+\r
+ public void write(short[] buf) throws IOException;\r
+\r
+ public void write(char[] buf) throws IOException;\r
+\r
+ public void write(int[] buf) throws IOException;\r
+\r
+ public void write(long[] buf) throws IOException;\r
+\r
+ public void write(float[] buf) throws IOException;\r
+\r
+ public void write(double[] buf) throws IOException;\r
+\r
+ /* Write an array of Strings */\r
+ public void write(String[] buf) throws IOException;\r
+\r
+ /* Write a segment of a primitive array. */\r
+ public void write(byte[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(boolean[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(char[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(short[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(int[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(long[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(float[] buf, int offset, int size) throws IOException;\r
+\r
+ public void write(double[] buf, int offset, int size) throws IOException;\r
+\r
+ /* Write some of an array of Strings */\r
+ public void write(String[] buf, int offset, int size) throws IOException;\r
+\r
+ /* Flush the output buffer */\r
+ public void flush() throws IOException;\r
+\r
+ public void close() throws IOException;\r
+}\r
--- /dev/null
+// Member of the utility package.\r
+// Modified July 20, 2009 to handle very large arrays\r
+// in some contexts.\r
+package nom.tam.util;\r
+\r
+/* Copyright: Thomas McGlynn 1997-1998.\r
+ * This code may be used for any purpose, non-commercial\r
+ * or commercial so long as this copyright notice is retained\r
+ * in the source code or included in or referred to in any\r
+ * derived software.\r
+ */\r
+import java.lang.reflect.*;\r
+import java.util.Arrays;\r
+\r
+/** This is a package of static functions which perform\r
+ * computations on arrays. Generally these routines attempt\r
+ * to complete without throwing errors by ignoring data\r
+ * they cannot understand.\r
+ */\r
+public class ArrayFuncs implements PrimitiveInfo {\r
+\r
+ /** Compute the size of an object. Note that this only handles\r
+ * arrays or scalars of the primitive objects and Strings. It\r
+ * returns 0 for any object array element it does not understand.\r
+ *\r
+ * @param o The object whose size is desired.\r
+ * @deprecated May silently underestimate the size\r
+ * if the size > 2 GB.\r
+ */\r
+ public static int computeSize(Object o) {\r
+ return (int) computeLSize(o);\r
+ }\r
+\r
+ public static long computeLSize(Object o) {\r
+\r
+ if (o == null) {\r
+ return 0;\r
+ }\r
+\r
+ long size = 0;\r
+ String classname = o.getClass().getName();\r
+ if (classname.substring(0, 2).equals("[[")) {\r
+\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ size += computeLSize(((Object[]) o)[i]);\r
+ }\r
+ return size;\r
+ }\r
+\r
+ if (classname.charAt(0) == '[' && classname.charAt(1) != 'L') {\r
+ char c = classname.charAt(1);\r
+\r
+ for (int i = 0; i < PrimitiveInfo.suffixes.length; i += 1) {\r
+ if (c == PrimitiveInfo.suffixes[i]) {\r
+ return (long) (Array.getLength(o)) * PrimitiveInfo.sizes[i];\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ // Do we have a non-primitive array?\r
+ if (classname.charAt(0) == '[') {\r
+ int len = 0;\r
+ for (int i = 0; i < Array.getLength(o); i += 1) {\r
+ len += computeLSize(Array.get(o, i));\r
+ }\r
+ return len;\r
+ }\r
+\r
+ // Now a few special scalar objects.\r
+ if (classname.substring(0, 10).equals("java.lang.")) {\r
+ classname = classname.substring(10, classname.length());\r
+ if (classname.equals("Integer") || classname.equals("Float")) {\r
+ return 4;\r
+ } else if (classname.equals("Double") || classname.equals("Long")) {\r
+ return 8;\r
+ } else if (classname.equals("Short") || classname.equals("Char")) {\r
+ return 2;\r
+ } else if (classname.equals("Byte") || classname.equals("Boolean")) {\r
+ return 1;\r
+ } else if (classname.equals("String")) {\r
+ return ((String) o).length();\r
+ } else {\r
+ return 0;\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+\r
+ /** Count the number of elements in an array.\r
+ * @deprecated May silently underestimate\r
+ * size if number is > 2 G.\r
+ */\r
+ public static int nElements(Object o) {\r
+ return (int) nLElements(o);\r
+ }\r
+\r
+ /** Count the number of elements in an array.\r
+ * @deprecated May silently underestimate\r
+ * size if number is > 2 G.\r
+ */\r
+ public static long nLElements(Object o) {\r
+\r
+ if (o == null) {\r
+ return 0;\r
+ }\r
+\r
+ String classname = o.getClass().getName();\r
+ if (classname.charAt(1) == '[') {\r
+ int count = 0;\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ count += nLElements(((Object[]) o)[i]);\r
+ }\r
+ return count;\r
+\r
+ } else if (classname.charAt(0) == '[') {\r
+ return Array.getLength(o);\r
+\r
+ } else {\r
+ return 1;\r
+ }\r
+ }\r
+\r
+ /** Try to create a deep clone of an Array or a standard clone of a scalar.\r
+ * The object may comprise arrays of\r
+ * any primitive type or any Object type which implements Cloneable.\r
+ * However, if the Object is some kind of collection, e.g., a Vector\r
+ * then only a shallow copy of that object is made. I.e., deep refers\r
+ * only to arrays.\r
+ *\r
+ * @param o The object to be copied.\r
+ */\r
+ public static Object deepClone(Object o) {\r
+\r
+ if (o == null) {\r
+ return null;\r
+ }\r
+\r
+ String classname = o.getClass().getName();\r
+\r
+ // Is this an array?\r
+ if (classname.charAt(0) != '[') {\r
+ return genericClone(o);\r
+ }\r
+\r
+ // Check if this is a 1D primitive array.\r
+ if (classname.charAt(1) != '[' && classname.charAt(1) != 'L') {\r
+ try {\r
+ // Some compilers (SuperCede, e.g.) still\r
+ // think you have to catch this...\r
+ if (false) {\r
+ throw new CloneNotSupportedException();\r
+ }\r
+ switch (classname.charAt(1)) {\r
+ case 'B':\r
+ return ((byte[]) o).clone();\r
+ case 'Z':\r
+ return ((boolean[]) o).clone();\r
+ case 'C':\r
+ return ((char[]) o).clone();\r
+ case 'S':\r
+ return ((short[]) o).clone();\r
+ case 'I':\r
+ return ((int[]) o).clone();\r
+ case 'J':\r
+ return ((long[]) o).clone();\r
+ case 'F':\r
+ return ((float[]) o).clone();\r
+ case 'D':\r
+ return ((double[]) o).clone();\r
+ default:\r
+ System.err.println("Unknown primtive array class:" + classname);\r
+ return null;\r
+\r
+ }\r
+ } catch (CloneNotSupportedException e) {\r
+ }\r
+ }\r
+\r
+ // Get the base type.\r
+ int ndim = 1;\r
+ while (classname.charAt(ndim) == '[') {\r
+ ndim += 1;\r
+ }\r
+ Class baseClass;\r
+ if (classname.charAt(ndim) != 'L') {\r
+ baseClass = getBaseClass(o);\r
+ } else {\r
+ try {\r
+ baseClass = Class.forName(classname.substring(ndim + 1, classname.length() - 1));\r
+ } catch (ClassNotFoundException e) {\r
+ System.err.println("Internal error: class definition inconsistency: " + classname);\r
+ return null;\r
+ }\r
+ }\r
+\r
+ // Allocate the array but make all but the first dimension 0.\r
+ int[] dims = new int[ndim];\r
+ dims[0] = Array.getLength(o);\r
+ for (int i = 1; i < ndim; i += 1) {\r
+ dims[i] = 0;\r
+ }\r
+\r
+ Object copy = ArrayFuncs.newInstance(baseClass, dims);\r
+\r
+ // Now fill in the next level down by recursion.\r
+ for (int i = 0; i < dims[0]; i += 1) {\r
+ Array.set(copy, i, deepClone(Array.get(o, i)));\r
+ }\r
+\r
+ return copy;\r
+ }\r
+\r
+ /** Clone an Object if possible.\r
+ *\r
+ * This method returns an Object which is a clone of the\r
+ * input object. It checks if the method implements the\r
+ * Cloneable interface and then uses reflection to invoke\r
+ * the clone method. This can't be done directly since\r
+ * as far as the compiler is concerned the clone method for\r
+ * Object is protected and someone could implement Cloneable but\r
+ * leave the clone method protected. The cloning can fail in a\r
+ * variety of ways which are trapped so that it returns null instead.\r
+ * This method will generally create a shallow clone. If you\r
+ * wish a deep copy of an array the method deepClone should be used.\r
+ *\r
+ * @param o The object to be cloned.\r
+ */\r
+ public static Object genericClone(Object o) {\r
+\r
+ if (!(o instanceof Cloneable)) {\r
+ return null;\r
+ }\r
+\r
+ Class[] argTypes = new Class[0];\r
+ Object[] args = new Object[0];\r
+ Class type = o.getClass();\r
+\r
+ try {\r
+ return type.getMethod("clone", argTypes).invoke(o, args);\r
+ } catch (Exception e) {\r
+ if (type.isArray()) {\r
+ return deepClone(o);\r
+ }\r
+ // Implements cloneable, but does not\r
+ // apparently make clone public.\r
+ return null;\r
+ }\r
+ }\r
+\r
+ /** Copy one array into another.\r
+ * This function copies the contents of one array\r
+ * into a previously allocated array.\r
+ * The arrays must agree in type and size.\r
+ * @param original The array to be copied.\r
+ * @param copy The array to be copied into. This\r
+ * array must already be fully allocated.\r
+ */\r
+ public static void copyArray(Object original, Object copy) {\r
+ String oname = original.getClass().getName();\r
+ String cname = copy.getClass().getName();\r
+\r
+ if (!oname.equals(cname)) {\r
+ return;\r
+ }\r
+\r
+ if (oname.charAt(0) != '[') {\r
+ return;\r
+ }\r
+\r
+ if (oname.charAt(1) == '[') {\r
+ Object[] x = (Object[]) original;\r
+ Object[] y = (Object[]) copy;\r
+ if (x.length != y.length) {\r
+ return;\r
+ }\r
+ for (int i = 0; i < x.length; i += 1) {\r
+ copyArray(x, y);\r
+ }\r
+ }\r
+ int len = Array.getLength(original);\r
+\r
+ System.arraycopy(original, 0, copy, 0, len);\r
+ }\r
+\r
+ /** Find the dimensions of an object.\r
+ *\r
+ * This method returns an integer array with the dimensions\r
+ * of the object o which should usually be an array.\r
+ *\r
+ * It returns an array of dimension 0 for scalar objects\r
+ * and it returns -1 for dimension which have not been allocated,\r
+ * e.g., int[][][] x = new int[100][][]; should return [100,-1,-1].\r
+ *\r
+ * @param o The object to get the dimensions of.\r
+ */\r
+ public static int[] getDimensions(Object o) {\r
+\r
+ if (o == null) {\r
+ return null;\r
+ }\r
+\r
+ String classname = o.getClass().getName();\r
+\r
+ int ndim = 0;\r
+\r
+ while (classname.charAt(ndim) == '[') {\r
+ ndim += 1;\r
+ }\r
+\r
+ int[] dimens = new int[ndim];\r
+\r
+ for (int i = 0; i < ndim; i += 1) {\r
+ dimens[i] = -1; // So that we can distinguish a null from a 0 length.\r
+ }\r
+\r
+ for (int i = 0; i < ndim; i += 1) {\r
+ dimens[i] = java.lang.reflect.Array.getLength(o);\r
+ if (dimens[i] == 0) {\r
+ return dimens;\r
+ }\r
+ if (i != ndim - 1) {\r
+ o = ((Object[]) o)[0];\r
+ if (o == null) {\r
+ return dimens;\r
+ }\r
+ }\r
+ }\r
+ return dimens;\r
+ }\r
+\r
+ /** This routine returns the base array of a multi-dimensional\r
+ * array. I.e., a one-d array of whatever the array is composed\r
+ * of. Note that arrays are not guaranteed to be rectangular,\r
+ * so this returns o[0][0]....\r
+ */\r
+ public static Object getBaseArray(Object o) {\r
+ String cname = o.getClass().getName();\r
+ if (cname.charAt(1) == '[') {\r
+ return getBaseArray(((Object[]) o)[0]);\r
+ } else {\r
+ return o;\r
+ }\r
+ }\r
+\r
+ /** This routine returns the base class of an object. This is just\r
+ * the class of the object for non-arrays.\r
+ */\r
+ public static Class getBaseClass(Object o) {\r
+\r
+ if (o == null) {\r
+ return Void.TYPE;\r
+ }\r
+\r
+ String className = o.getClass().getName();\r
+\r
+ int dims = 0;\r
+ while (className.charAt(dims) == '[') {\r
+ dims += 1;\r
+ }\r
+\r
+ if (dims == 0) {\r
+ return o.getClass();\r
+ }\r
+\r
+ char c = className.charAt(dims);\r
+ for (int i = 0; i < PrimitiveInfo.suffixes.length; i += 1) {\r
+ if (c == PrimitiveInfo.suffixes[i]) {\r
+ return PrimitiveInfo.classes[i];\r
+ }\r
+ }\r
+\r
+ if (c == 'L') {\r
+ try {\r
+ return Class.forName(className.substring(dims + 1, className.length() - 1));\r
+ } catch (ClassNotFoundException e) {\r
+ return null;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /** This routine returns the size of the base element of an array.\r
+ * @param o The array object whose base length is desired.\r
+ * @return the size of the object in bytes, 0 if null, or\r
+ * -1 if not a primitive array.\r
+ */\r
+ public static int getBaseLength(Object o) {\r
+\r
+ if (o == null) {\r
+ return 0;\r
+ }\r
+\r
+ String className = o.getClass().getName();\r
+\r
+ int dims = 0;\r
+\r
+ while (className.charAt(dims) == '[') {\r
+ dims += 1;\r
+ }\r
+\r
+ if (dims == 0) {\r
+ return -1;\r
+ }\r
+\r
+ char c = className.charAt(dims);\r
+ for (int i = 0; i < PrimitiveInfo.suffixes.length; i += 1) {\r
+ if (c == PrimitiveInfo.suffixes[i]) {\r
+ return PrimitiveInfo.sizes[i];\r
+ }\r
+ }\r
+ return -1;\r
+ }\r
+\r
+ /** Create an array and populate it with a test pattern.\r
+ *\r
+ * @param baseType The base type of the array. This is expected to\r
+ * be a numeric type, but this is not checked.\r
+ * @param dims The desired dimensions.\r
+ * @return An array object populated with a simple test pattern.\r
+ */\r
+ public static Object generateArray(Class baseType, int[] dims) {\r
+\r
+ // Generate an array and populate it with a test pattern of\r
+ // data.\r
+\r
+ Object x = ArrayFuncs.newInstance(baseType, dims);\r
+ testPattern(x, (byte) 0);\r
+ return x;\r
+ }\r
+\r
+ /** Just create a simple pattern cycling through valid byte values.\r
+ * We use bytes because they can be cast to any other numeric type.\r
+ * @param o The array in which the test pattern is to be set.\r
+ * @param start The value for the first element.\r
+ */\r
+ public static byte testPattern(Object o, byte start) {\r
+\r
+ int[] dims = getDimensions(o);\r
+ if (dims.length > 1) {\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ start = testPattern(((Object[]) o)[i], start);\r
+ }\r
+\r
+ } else if (dims.length == 1) {\r
+ for (int i = 0; i < dims[0]; i += 1) {\r
+ java.lang.reflect.Array.setByte(o, i, start);\r
+ start += 1;\r
+ }\r
+ }\r
+ return start;\r
+ }\r
+\r
+ /** Generate a description of an array (presumed rectangular).\r
+ * @param o The array to be described.\r
+ */\r
+ public static String arrayDescription(Object o) {\r
+\r
+ Class base = getBaseClass(o);\r
+ if (base == Void.TYPE) {\r
+ return "NULL";\r
+ }\r
+\r
+ int[] dims = getDimensions(o);\r
+\r
+ StringBuffer desc = new StringBuffer();\r
+\r
+ // Note that all instances Class describing a given class are\r
+ // the same so we can use == here.\r
+ boolean found = false;\r
+\r
+ for (int i = 0; i < PrimitiveInfo.classes.length; i += 1) {\r
+ if (base == PrimitiveInfo.classes[i]) {\r
+ found = true;\r
+ desc.append(PrimitiveInfo.types[i]);\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (!found) {\r
+ desc.append(base.getName());\r
+ }\r
+\r
+ if (dims != null) {\r
+ desc.append("[");\r
+ for (int i = 0; i < dims.length; i += 1) {\r
+ desc.append("" + dims[i]);\r
+ if (i < dims.length - 1) {\r
+ desc.append("][");\r
+ }\r
+ }\r
+ desc.append("]");\r
+ }\r
+ return new String(desc);\r
+ }\r
+\r
+ /** Examine the structure of an array in detail.\r
+ * @param o The array to be examined.\r
+ */\r
+ public static void examinePrimitiveArray(Object o) {\r
+ String className = o.getClass().getName();\r
+\r
+ // If we have a two-d array, or if the array is a one-d array\r
+ // of Objects, then recurse over the next dimension. We handle\r
+ // Object specially because each element could itself be an array.\r
+ if (className.substring(0, 2).equals("[[")\r
+ || className.equals("[Ljava.lang.Object;")) {\r
+ System.out.println("[");\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ examinePrimitiveArray(((Object[]) o)[i]);\r
+ }\r
+ System.out.print("]");\r
+ } else if (className.charAt(0) != '[') {\r
+ System.out.println(className);\r
+ } else {\r
+ System.out.println("[" + java.lang.reflect.Array.getLength(o) + "]"\r
+ + className.substring(1));\r
+ }\r
+ }\r
+\r
+ /** Given an array of arbitrary dimensionality return\r
+ * the array flattened into a single dimension.\r
+ * @param input The input array.\r
+ */\r
+ public static Object flatten(Object input) {\r
+\r
+ int[] dimens = getDimensions(input);\r
+ if (dimens.length <= 1) {\r
+ return input;\r
+ }\r
+ int size = 1;\r
+ for (int i = 0; i < dimens.length; i += 1) {\r
+ size *= dimens[i];\r
+ }\r
+\r
+ Object flat = ArrayFuncs.newInstance(getBaseClass(input), size);\r
+\r
+ if (size == 0) {\r
+ return flat;\r
+ }\r
+\r
+ int offset = 0;\r
+ doFlatten(input, flat, offset);\r
+ return flat;\r
+ }\r
+\r
+ /** This routine does the actually flattening of multi-dimensional\r
+ * arrays.\r
+ * @param input The input array to be flattened.\r
+ * @param output The flattened array.\r
+ * @param offset The current offset within the output array.\r
+ * @return The number of elements within the array.\r
+ */\r
+ protected static int doFlatten(Object input, Object output, int offset) {\r
+\r
+ String classname = input.getClass().getName();\r
+ if (classname.charAt(0) != '[') {\r
+ throw new RuntimeException("Attempt to flatten non-array");\r
+ }\r
+ int size = Array.getLength(input);\r
+\r
+ if (classname.charAt(1) != '[') {\r
+ System.arraycopy(input, 0, output, offset, size);\r
+ return size;\r
+ }\r
+ int total = 0;\r
+ Object[] xx = (Object[]) input;\r
+ for (int i = 0; i < size; i += 1) {\r
+ int len = doFlatten(xx[i], output, offset + total);\r
+ total += len;\r
+ }\r
+ return total;\r
+ }\r
+\r
+ /** Curl an input array up into a multi-dimensional array.\r
+ *\r
+ * @param input The one dimensional array to be curled.\r
+ * @param dimens The desired dimensions\r
+ * @return The curled array.\r
+ */\r
+ public static Object curl(Object input, int[] dimens) {\r
+\r
+ if (input == null) {\r
+ return null;\r
+ }\r
+ String classname = input.getClass().getName();\r
+ if (classname.charAt(0) != '[' || classname.charAt(1) == '[') {\r
+ throw new RuntimeException("Attempt to curl non-1D array");\r
+ }\r
+\r
+ int size = Array.getLength(input);\r
+\r
+ int test = 1;\r
+ for (int i = 0; i < dimens.length; i += 1) {\r
+ test *= dimens[i];\r
+ }\r
+\r
+ if (test != size) {\r
+ throw new RuntimeException("Curled array does not fit desired dimensions");\r
+ }\r
+\r
+ Class base = getBaseClass(input);\r
+\r
+ Object newArray = ArrayFuncs.newInstance(base, dimens);\r
+\r
+ int offset = 0;\r
+\r
+ doCurl(input, newArray, dimens, offset);\r
+ return newArray;\r
+\r
+ }\r
+\r
+ /** Do the curling of the 1-d to multi-d array.\r
+ * @param input The 1-d array to be curled.\r
+ * @param output The multi-dimensional array to be filled.\r
+ * @param dimens The desired output dimensions.\r
+ * @param offset The current offset in the input array.\r
+ * @return The number of elements curled.\r
+ */\r
+ protected static int doCurl(Object input, Object output,\r
+ int[] dimens, int offset) {\r
+\r
+ if (dimens.length == 1) {\r
+ System.arraycopy(input, offset, output, 0, dimens[0]);\r
+ return dimens[0];\r
+ }\r
+\r
+ int total = 0;\r
+ int[] xdimens = new int[dimens.length - 1];\r
+ for (int i = 1; i < dimens.length; i += 1) {\r
+ xdimens[i - 1] = dimens[i];\r
+ }\r
+\r
+ for (int i = 0; i < dimens[0]; i += 1) {\r
+ total += doCurl(input, ((Object[]) output)[i], xdimens, offset + total);\r
+ }\r
+ return total;\r
+ }\r
+\r
+ /** Create an array of a type given by new type with\r
+ * the dimensionality given in array.\r
+ * @param array A possibly multidimensional array to be converted.\r
+ * @param newType The desired output type. This should be one of the\r
+ * class descriptors for primitive numeric data, e.g., double.type.\r
+ */\r
+ public static Object mimicArray(Object array, Class newType) {\r
+\r
+ String classname = array.getClass().getName();\r
+ if (classname.charAt(0) != '[') {\r
+ return null;\r
+ }\r
+\r
+ int dims = 1;\r
+\r
+ while (classname.charAt(dims) == '[') {\r
+ dims += 1;\r
+ }\r
+\r
+ Object mimic;\r
+\r
+ if (dims > 1) {\r
+\r
+ Object[] xarray = (Object[]) array;\r
+ int[] dimens = new int[dims];\r
+ dimens[0] = xarray.length; // Leave other dimensions at 0.\r
+\r
+\r
+ mimic = ArrayFuncs.newInstance(newType, dimens);\r
+\r
+ for (int i = 0; i < xarray.length; i += 1) {\r
+ Object temp = mimicArray(xarray[i], newType);\r
+ ((Object[]) mimic)[i] = temp;\r
+ }\r
+\r
+ } else {\r
+ mimic = ArrayFuncs.newInstance(newType, Array.getLength(array));\r
+ }\r
+\r
+ return mimic;\r
+ }\r
+\r
+ /** Convert an array to a specified type. This method supports conversions\r
+ * only among the primitive numeric types.\r
+ * @param array A possibly multidimensional array to be converted.\r
+ * @param newType The desired output type. This should be one of the\r
+ * class descriptors for primitive numeric data, e.g., double.type.\r
+ * @param preserve If set, and the requested type is the same as the\r
+ * original, then the original is returned.\r
+ */\r
+ public static Object convertArray(Object array, Class newType, boolean reuse) {\r
+\r
+ if (getBaseClass(array) == newType && reuse) {\r
+ return array;\r
+ } else {\r
+ return convertArray(array, newType);\r
+ }\r
+ }\r
+\r
+ /** Convert an array to a specified type. This method supports conversions\r
+ * only among the primitive numeric types.\r
+ * @param array A possibly multidimensional array to be converted.\r
+ * @param newType The desired output type. This should be one of the\r
+ * class descriptors for primitive numeric data, e.g., double.type.\r
+ */\r
+ public static Object convertArray(Object array, Class newType) {\r
+\r
+ /* We break this up into two steps so that users\r
+ * can reuse an array many times and only allocate a\r
+ * new array when needed.\r
+ */\r
+\r
+ /* First create the full new array. */\r
+ Object mimic = mimicArray(array, newType);\r
+ if (mimic == null) {\r
+ return mimic;\r
+ }\r
+\r
+ /* Now copy the info into the new array */\r
+ copyInto(array, mimic);\r
+\r
+ return mimic;\r
+ }\r
+\r
+ /** Copy an array into an array of a different type.\r
+ * The dimensions and dimensionalities of the two\r
+ * arrays should be the same.\r
+ * @param array The original array.\r
+ * @param mimic The array mimicking the original.\r
+ */\r
+ public static void copyInto(Object array, Object mimic) {\r
+\r
+ String classname = array.getClass().getName();\r
+ if (classname.charAt(0) != '[') {\r
+ return;\r
+ }\r
+\r
+ /* Do multidimensional arrays recursively */\r
+ if (classname.charAt(1) == '[') {\r
+\r
+ for (int i = 0; i < ((Object[]) array).length; i += 1) {\r
+ copyInto(((Object[]) array)[i], ((Object[]) mimic)[i]);\r
+ }\r
+\r
+ } else {\r
+\r
+ byte[] xbarr;\r
+ short[] xsarr;\r
+ char[] xcarr;\r
+ int[] xiarr;\r
+ long[] xlarr;\r
+ float[] xfarr;\r
+ double[] xdarr;\r
+\r
+ Class base = getBaseClass(array);\r
+ Class newType = getBaseClass(mimic);\r
+\r
+ if (base == byte.class) {\r
+ byte[] barr = (byte[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ System.arraycopy(array, 0, mimic, 0, barr.length);\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xsarr[i] = barr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xcarr[i] = (char) barr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xiarr[i] = barr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xlarr[i] = barr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xfarr[i] = barr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < barr.length; i += 1) {\r
+ xdarr[i] = barr[i];\r
+ }\r
+ }\r
+\r
+ } else if (base == short.class) {\r
+ short[] sarr = (short[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xbarr[i] = (byte) sarr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ System.arraycopy(array, 0, mimic, 0, sarr.length);\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xcarr[i] = (char) sarr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xiarr[i] = sarr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xlarr[i] = sarr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xfarr[i] = sarr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < sarr.length; i += 1) {\r
+ xdarr[i] = sarr[i];\r
+ }\r
+ }\r
+\r
+ } else if (base == char.class) {\r
+ char[] carr = (char[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xbarr[i] = (byte) carr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xsarr[i] = (short) carr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ System.arraycopy(array, 0, mimic, 0, carr.length);\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xiarr[i] = carr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xlarr[i] = carr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xfarr[i] = carr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < carr.length; i += 1) {\r
+ xdarr[i] = carr[i];\r
+ }\r
+ }\r
+\r
+ } else if (base == int.class) {\r
+ int[] iarr = (int[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xbarr[i] = (byte) iarr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xsarr[i] = (short) iarr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xcarr[i] = (char) iarr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ System.arraycopy(array, 0, mimic, 0, iarr.length);\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xlarr[i] = iarr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xfarr[i] = iarr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < iarr.length; i += 1) {\r
+ xdarr[i] = iarr[i];\r
+ }\r
+ }\r
+\r
+\r
+ } else if (base == long.class) {\r
+ long[] larr = (long[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xbarr[i] = (byte) larr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xsarr[i] = (short) larr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xcarr[i] = (char) larr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xiarr[i] = (int) larr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ System.arraycopy(array, 0, mimic, 0, larr.length);\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xfarr[i] = (float) larr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < larr.length; i += 1) {\r
+ xdarr[i] = (double) larr[i];\r
+ }\r
+ }\r
+\r
+ } else if (base == float.class) {\r
+ float[] farr = (float[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xbarr[i] = (byte) farr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xsarr[i] = (short) farr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xcarr[i] = (char) farr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xiarr[i] = (int) farr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xlarr[i] = (long) farr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ System.arraycopy(array, 0, mimic, 0, farr.length);\r
+\r
+ } else if (newType == double.class) {\r
+ xdarr = (double[]) mimic;\r
+ for (int i = 0; i < farr.length; i += 1) {\r
+ xdarr[i] = farr[i];\r
+ }\r
+ }\r
+\r
+\r
+ } else if (base == double.class) {\r
+ double[] darr = (double[]) array;\r
+\r
+ if (newType == byte.class) {\r
+ xbarr = (byte[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xbarr[i] = (byte) darr[i];\r
+ }\r
+\r
+ } else if (newType == short.class) {\r
+ xsarr = (short[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xsarr[i] = (short) darr[i];\r
+ }\r
+\r
+ } else if (newType == char.class) {\r
+ xcarr = (char[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xcarr[i] = (char) darr[i];\r
+ }\r
+\r
+ } else if (newType == int.class) {\r
+ xiarr = (int[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xiarr[i] = (int) darr[i];\r
+ }\r
+\r
+ } else if (newType == long.class) {\r
+ xlarr = (long[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xlarr[i] = (long) darr[i];\r
+ }\r
+\r
+ } else if (newType == float.class) {\r
+ xfarr = (float[]) mimic;\r
+ for (int i = 0; i < darr.length; i += 1) {\r
+ xfarr[i] = (float) darr[i];\r
+ }\r
+\r
+ } else if (newType == double.class) {\r
+ System.arraycopy(array, 0, mimic, 0, darr.length);\r
+ }\r
+ }\r
+ }\r
+\r
+ return;\r
+\r
+ }\r
+\r
+ /** Allocate an array dynamically. The Array.newInstance method\r
+ * does not throw an error when there is insufficient memory\r
+ * and silently returns a null.\r
+ * @param cl The class of the array.\r
+ * @param dim The dimension of the array.\r
+ * @return The allocated array.\r
+ * @throws An OutOfMemoryError if insufficient space is available.\r
+ */\r
+ public static Object newInstance(Class cl, int dim) {\r
+\r
+ Object o = Array.newInstance(cl, dim);\r
+ if (o == null) {\r
+ String desc = cl + "[" + dim + "]";\r
+ throw new OutOfMemoryError("Unable to allocate array: " + desc);\r
+ }\r
+ return o;\r
+ }\r
+\r
+ /** Allocate an array dynamically. The Array.newInstance method\r
+ * does not throw an error and silently returns a null.\r
+ *\r
+ * @param cl The class of the array.\r
+ * @param dims The dimensions of the array.\r
+ * @return The allocated array.\r
+ * @throws An OutOfMemoryError if insufficient space is available.\r
+ */\r
+ public static Object newInstance(Class cl, int[] dims) {\r
+\r
+ if (dims.length == 0) {\r
+ // Treat a scalar as a 1-d array of length 1\r
+ dims = new int[]{1};\r
+ }\r
+\r
+ Object o = Array.newInstance(cl, dims);\r
+ if (o == null) {\r
+ String desc = cl + "[";\r
+ String comma = "";\r
+ for (int i = 0; i < dims.length; i += 1) {\r
+ desc += comma + dims[i];\r
+ comma = ",";\r
+ }\r
+ desc += "]";\r
+ throw new OutOfMemoryError("Unable to allocate array: " + desc);\r
+ }\r
+ return o;\r
+ }\r
+\r
+ /** Are two objects equal? Arrays have the standard object equals\r
+ * method which only returns true if the two object are the same.\r
+ * This method returns true if every element of the arrays match.\r
+ * The inputs may be of any dimensionality. The dimensionality\r
+ * and dimensions of the arrays must match as well as any elements.\r
+ * If the elements are non-primitive. non-array objects, then the\r
+ * equals method is called for each element.\r
+ * If both elements are multi-dimensional arrays, then\r
+ * the method recurses.\r
+ */\r
+ public static boolean arrayEquals(Object x, Object y) {\r
+ return arrayEquals(x, y, 0, 0);\r
+ }\r
+\r
+ /** Are two objects equal? Arrays have the standard object equals\r
+ * method which only returns true if the two object are the same.\r
+ * This method returns true if every element of the arrays match.\r
+ * The inputs may be of any dimensionality. The dimensionality\r
+ * and dimensions of the arrays must match as well as any elements.\r
+ * If the elements are non-primitive. non-array objects, then the\r
+ * equals method is called for each element.\r
+ * If both elements are multi-dimensional arrays, then\r
+ * the method recurses.\r
+ */\r
+ public static boolean arrayEquals(Object x, Object y, double tolf, double told) {\r
+\r
+ // Handle the special cases first.\r
+ // We treat null == null so that two object arrays\r
+ // can match if they have matching null elements.\r
+ if (x == null && y == null) {\r
+ return true;\r
+ }\r
+\r
+ if (x == null || y == null) {\r
+ return false;\r
+ }\r
+\r
+ Class xClass = x.getClass();\r
+ Class yClass = y.getClass();\r
+\r
+ if (xClass != yClass) {\r
+ return false;\r
+ }\r
+\r
+ if (!xClass.isArray()) {\r
+ return x.equals(y);\r
+\r
+ } else {\r
+ if (xClass.equals(int[].class)) {\r
+ return Arrays.equals((int[]) x, (int[]) y);\r
+\r
+ } else if (xClass.equals(double[].class)) {\r
+ if (told == 0) {\r
+ return Arrays.equals((double[]) x, (double[]) y);\r
+ } else {\r
+ return doubleArrayEquals((double[]) x, (double[]) y, told);\r
+ }\r
+\r
+ } else if (xClass.equals(long[].class)) {\r
+ return Arrays.equals((long[]) x, (long[]) y);\r
+\r
+ } else if (xClass.equals(float[].class)) {\r
+ if (tolf == 0) {\r
+ return Arrays.equals((float[]) x, (float[]) y);\r
+ } else {\r
+ return floatArrayEquals((float[]) x, (float[]) y, (float) tolf);\r
+ }\r
+\r
+ } else if (xClass.equals(byte[].class)) {\r
+ return Arrays.equals((byte[]) x, (byte[]) y);\r
+\r
+ } else if (xClass.equals(short[].class)) {\r
+ return Arrays.equals((short[]) x, (short[]) y);\r
+\r
+ } else if (xClass.equals(char[].class)) {\r
+ return Arrays.equals((char[]) x, (char[]) y);\r
+\r
+ } else if (xClass.equals(boolean[].class)) {\r
+ return Arrays.equals((boolean[]) x, (boolean[]) y);\r
+\r
+ } else {\r
+ // Non-primitive and multidimensional arrays can be\r
+ // cast to Object[]\r
+ Object[] xo = (Object[]) x;\r
+ Object[] yo = (Object[]) y;\r
+ if (xo.length != yo.length) {\r
+ return false;\r
+ }\r
+ for (int i = 0; i < xo.length; i += 1) {\r
+ if (!arrayEquals(xo[i], yo[i], tolf, told)) {\r
+ return false;\r
+ }\r
+ }\r
+\r
+ return true;\r
+\r
+ }\r
+ }\r
+ }\r
+\r
+ /** Compare two double arrays using a given tolerance */\r
+ public static boolean doubleArrayEquals(double[] x, double[] y, double tol) {\r
+\r
+ for (int i = 0; i < x.length; i += 1) {\r
+ if (x[i] == 0) {\r
+ return y[i] == 0;\r
+ }\r
+ if (Math.abs((y[i] - x[i]) / x[i]) > tol) {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /** Compare two float arrays using a given tolerance */\r
+ public static boolean floatArrayEquals(float[] x, float[] y, float tol) {\r
+\r
+ for (int i = 0; i < x.length; i += 1) {\r
+ if (x[i] == 0) {\r
+ return y[i] == 0;\r
+ }\r
+ if (Math.abs((y[i] - x[i]) / x[i]) > tol) {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /** Dump an array on the given print steam */\r
+ public static void dumpArray(java.io.PrintStream p, Object arr) {\r
+ // Get the dimensionality and then dump.\r
+ if (arr == null) {\r
+ p.print("null ");\r
+ } else {\r
+ Class nm = arr.getClass();\r
+ if (nm.isArray()) {\r
+ p.print("[");\r
+ for (int i = 0; i < java.lang.reflect.Array.getLength(arr); i += 1) {\r
+ dumpArray(p, java.lang.reflect.Array.get(arr, i));\r
+ }\r
+ p.print("]\n");\r
+ } else {\r
+ p.print(" " + arr.toString() + " ");\r
+ }\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/*
+ * This class provides conversions to ASCII strings without breaking
+ * compatibility with Java 1.5.
+ */
+package nom.tam.util;
+
+import java.io.UnsupportedEncodingException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author tmcglynn
+ */
+public class AsciiFuncs {
+
+ public final static String ASCII = "US-ASCII";
+
+ /** Convert to ASCII or return null if not compatible */
+ public static String asciiString(byte[] buf) {
+ return asciiString(buf, 0, buf.length);
+ }
+
+ /** Convert to ASCII or return null if not compatible */
+ public static String asciiString(byte[] buf, int start, int len) {
+ try {
+ return new String(buf, start, len, ASCII);
+ } catch (java.io.UnsupportedEncodingException e) {
+ // Shouldn't happen
+ System.err.println("AsciiFuncs.asciiString error finding ASCII encoding");
+ return null;
+ }
+ }
+
+ /** Convert an ASCII string to bytes */
+ public static byte[] getBytes(String in) {
+ try {
+ return in.getBytes(ASCII);
+ } catch (UnsupportedEncodingException ex) {
+ System.err.println("Unable to find ASCII encoding");
+ return null;
+ }
+ }
+}
--- /dev/null
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+// What do we use in here?
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+/** This class is intended for high performance I/O in scientific applications.
+ * It combines the functionality of the BufferedInputStream and the
+ * DataInputStream as well as more efficient handling of arrays.
+ * This minimizes the number of method calls that are required to
+ * read data. Informal tests of this method show that it can
+ * be as much as 10 times faster than using a DataInputStream layered
+ * on a BufferedInputStream for writing large arrays. The performance
+ * gain on scalars or small arrays will be less but there should probably
+ * never be substantial degradation of performance.
+ * <p>
+ * Many new read calls are added to allow efficient reading
+ * off array data. The read(Object o) call provides
+ * for reading a primitive array of arbitrary type or
+ * dimensionality. There are also reads for each type
+ * of one dimensional array.
+ * <p>
+ * Note that there is substantial duplication of code to minimize method
+ * invocations. E.g., the floating point read routines read the data
+ * as integer values and then convert to float. However the integer
+ * code is duplicated rather than invoked. There has been
+ * considerable effort expended to ensure that these routines are
+ * efficient, but they could easily be superceded if
+ * an efficient underlying I/O package were ever delivered
+ * as part of the basic Java libraries.
+ * [This has subsequently happened with the NIO package
+ * and in an ideal universe these classes would be
+ * rewritten to take advantage of NIO.]
+ * <p>
+ * Testing and timing routines are provided in the
+ * nom.tam.util.test.BufferedFileTester class.
+ *
+ * Version 1.1: October 12, 2000: Fixed handling of EOF to return
+ * partially read arrays when EOF is detected.
+ * Version 1.2: July 20, 2009: Added handling of very large Object
+ * arrays. Additional work is required to handle very large arrays
+ * generally.
+ */
+public class BufferedDataInputStream
+ extends BufferedInputStream
+ implements ArrayDataInput {
+
+ private long primitiveArrayCount;
+ private byte[] bb = new byte[8];
+
+ /** Use the BufferedInputStream constructor
+ */
+ public BufferedDataInputStream(InputStream o) {
+ super(o, 32768);
+ }
+
+ /** Use the BufferedInputStream constructor
+ */
+ public BufferedDataInputStream(InputStream o, int bufLength) {
+ super(o, bufLength);
+ }
+
+ /** Read a byte array. This is the only method
+ * for reading arrays in the fundamental I/O classes.
+ * @param obuf The byte array.
+ * @param offset The starting offset into the array.
+ * @param len The number of bytes to read.
+ * @return The actual number of bytes read.
+ */
+ public int read(byte[] obuf, int offset, int len) throws IOException {
+
+ int total = 0;
+
+ while (len > 0) {
+
+ // Use just the buffered I/O to get needed info.
+
+ int xlen = super.read(obuf, offset, len);
+ if (xlen <= 0) {
+ if (total == 0) {
+ throw new EOFException();
+ } else {
+ return total;
+ }
+ } else {
+ len -= xlen;
+ total += xlen;
+ offset += xlen;
+ }
+ }
+ return total;
+
+ }
+
+ /** Read a boolean value.
+ * @return b The value read.
+ */
+ public boolean readBoolean() throws IOException {
+
+ int b = read();
+ if (b == 1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /** Read a byte value in the range -128 to 127.
+ * @return The byte value as a byte (see read() to return the value
+ * as an integer.
+ */
+ public byte readByte() throws IOException {
+ return (byte) read();
+ }
+
+ /** Read a byte value in the range 0-255.
+ * @return The byte value as an integer.
+ */
+ public int readUnsignedByte() throws IOException {
+ return read() | 0x00ff;
+ }
+
+ /** Read an integer.
+ * @return The integer value.
+ */
+ public int readInt() throws IOException {
+
+ if (read(bb, 0, 4) < 4) {
+ throw new EOFException();
+ }
+ int i = bb[0] << 24 | (bb[1] & 0xFF) << 16 | (bb[2] & 0xFF) << 8 | (bb[3] & 0xFF);
+ return i;
+ }
+
+ /** Read a 2-byte value as a short (-32788 to 32767)
+ * @return The short value.
+ */
+ public short readShort() throws IOException {
+
+ if (read(bb, 0, 2) < 2) {
+ throw new EOFException();
+ }
+
+ short s = (short) (bb[0] << 8 | (bb[1] & 0xFF));
+ return s;
+ }
+
+ /** Read a 2-byte value in the range 0-65536.
+ * @return the value as an integer.
+ */
+ public int readUnsignedShort() throws IOException {
+
+ if (read(bb, 0, 2) < 2) {
+ throw new EOFException();
+ }
+
+ return (bb[0] & 0xFF) << 8 | (bb[1] & 0xFF);
+ }
+
+ /** Read a 2-byte value as a character.
+ * @return The character read.
+ */
+ public char readChar() throws IOException {
+ byte[] b = new byte[2];
+
+ if (read(b, 0, 2) < 2) {
+ throw new EOFException();
+ }
+
+ char c = (char) (b[0] << 8 | (b[1] & 0xFF));
+ return c;
+ }
+
+ /** Read a long.
+ * @return The value read.
+ */
+ public long readLong() throws IOException {
+
+ // use two ints as intermediarys to
+ // avoid casts of bytes to longs...
+ if (read(bb, 0, 8) < 8) {
+ throw new EOFException();
+ }
+ int i1 = bb[0] << 24 | (bb[1] & 0xFF) << 16 | (bb[2] & 0xFF) << 8 | (bb[3] & 0xFF);
+ int i2 = bb[4] << 24 | (bb[5] & 0xFF) << 16 | (bb[6] & 0xFF) << 8 | (bb[7] & 0xFF);
+ return (((long) i1) << 32) | (((long) i2) & 0x00000000ffffffffL);
+ }
+
+ /** Read a 4 byte real number.
+ * @return The value as a float.
+ */
+ public float readFloat() throws IOException {
+
+ if (read(bb, 0, 4) < 4) {
+ throw new EOFException();
+ }
+
+ int i = bb[0] << 24 | (bb[1] & 0xFF) << 16 | (bb[2] & 0xFF) << 8 | (bb[3] & 0xFF);
+ return Float.intBitsToFloat(i);
+
+ }
+
+ /** Read an 8 byte real number.
+ * @return The value as a double.
+ */
+ public double readDouble() throws IOException {
+
+ if (read(bb, 0, 8) < 8) {
+ throw new EOFException();
+ }
+
+ int i1 = bb[0] << 24 | (bb[1] & 0xFF) << 16 | (bb[2] & 0xFF) << 8 | (bb[3] & 0xFF);
+ int i2 = bb[4] << 24 | (bb[5] & 0xFF) << 16 | (bb[6] & 0xFF) << 8 | (bb[7] & 0xFF);
+
+ return Double.longBitsToDouble(((long) i1) << 32 | ((long) i2 & 0x00000000ffffffffL));
+ }
+
+ /** Read a buffer and signal an EOF if the buffer
+ * cannot be fully read.
+ * @param b The buffer to be read.
+ */
+ public void readFully(byte[] b) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ /** Read a buffer and signal an EOF if the requested elements
+ * cannot be read.
+ *
+ * This differs from read(b,off,len) since that call
+ * will not signal and end of file unless no bytes can
+ * be read. However both of these routines will attempt
+ * to fill their buffers completely.
+ * @param b The input buffer.
+ * @param off The requested offset into the buffer.
+ * @param len The number of bytes requested.
+ */
+ public void readFully(byte[] b, int off, int len) throws IOException {
+
+ if (off < 0 || len < 0 || off + len > b.length) {
+ throw new IOException("Attempt to read outside byte array");
+ }
+
+ if (read(b, off, len) < len) {
+ throw new EOFException();
+ }
+ }
+ /** Skip the requested number of bytes.
+ * This differs from the skip call in that
+ * it takes an long argument and will throw
+ * an end of file if the full number of bytes cannot be skipped.
+ * @param toSkip The number of bytes to skip.
+ */
+ private byte[] skipBuf = null;
+
+ public int skipBytes(int toSkip) throws IOException {
+ return (int) skipBytes((long) toSkip);
+ }
+
+ public long skipBytes(long toSkip) throws IOException {
+
+ long need = toSkip;
+
+ while (need > 0) {
+
+ try {
+ long got = skip(need);
+ if (got > 0) {
+ need -= got;
+ } else {
+ break;
+ }
+ } catch (IOException e) {
+ // Some input streams (process outputs) don't allow
+ // skipping. The kludgy solution here is to
+ // try to do a read when we get an error in the skip....
+ // Real IO errors will presumably casue an error
+ // in these reads too.
+ if (skipBuf == null) {
+ skipBuf = new byte[8192];
+ }
+ while (need > 8192) {
+ int got = read(skipBuf, 0, 8192);
+ if (got <= 0) {
+ break;
+ }
+ need -= got;
+ }
+ while (need > 0) {
+ int got = read(skipBuf, 0, (int) need);
+ if (got <= 0) {
+ break;
+ }
+ need -= got;
+ }
+ }
+
+ }
+
+ if (need > 0) {
+ throw new EOFException();
+ } else {
+ return toSkip;
+ }
+ }
+
+ /** Read a String in the UTF format.
+ * The implementation of this is very inefficient and
+ * use of this class is not recommended for applications
+ * which will use this routine heavily.
+ * @return The String that was read.
+ */
+ public String readUTF() throws IOException {
+
+ // Punt on this one and use DataInputStream routines.
+ DataInputStream d = new DataInputStream(this);
+ return d.readUTF();
+
+ }
+
+ /**
+ * Emulate the deprecated DataInputStream.readLine() method.
+ * Originally we used the method itself, but Alan Brighton
+ * suggested using a BufferedReader to eliminate the deprecation warning.
+ * This method is slow regardless.
+ *
+ * @return The String read.
+ * @deprecated Use BufferedReader methods.
+ */
+ public String readLine() throws IOException {
+ // Punt on this and use BufferedReader routines.
+ BufferedReader d = new BufferedReader(new InputStreamReader(this));
+ return d.readLine();
+ }
+
+ /** This routine provides efficient reading of arrays of any primitive type.
+ * It is an error to invoke this method with an object that is not an array
+ * of some primitive type. Note that there is no corresponding capability
+ * to writePrimitiveArray in BufferedDataOutputStream to read in an
+ * array of Strings.
+ *
+ * @param o The object to be read. It must be an array of a primitive type,
+ * or an array of Object's.
+ * @deprecated See readLArray(Object o).
+ */
+ public int readPrimitiveArray(Object o) throws IOException {
+
+ // Note that we assume that only a single thread is
+ // doing a primitive Array read at any given time. Otherwise
+ // primitiveArrayCount can be wrong and also the
+ // input data can be mixed up.
+
+ primitiveArrayCount = 0;
+ return (int) readLArray(o);
+ }
+
+ /** Read an object. An EOF will be signaled if the
+ * object cannot be fully read. The getPrimitiveArrayCount()
+ * method may then be used to get a minimum number of bytes read.
+ * @param o The object to be read. This object should
+ * be a primitive (possibly multi-dimensional) array.
+ *
+ * @returns The number of bytes read.
+ * @deprecated See readLArray(Object) which handles large arrays properly.
+ */
+ public int readArray(Object o) throws IOException {
+ return (int) readLArray(o);
+ }
+
+ /** Read an object. An EOF will be signaled if the
+ * object cannot be fully read. The getPrimitiveArrayCount()
+ * method may then be used to get a minimum number of bytes read.
+ * @param o The object to be read. This object should
+ * be a primitive (possibly multi-dimensional) array.
+ *
+ * @returns The number of bytes read.
+ */
+ public long readLArray(Object o) throws IOException {
+ primitiveArrayCount = 0;
+ return primitiveArrayRecurse(o);
+ }
+
+ /** Read recursively over a multi-dimensional array.
+ * @return The number of bytes read.
+ */
+ protected long primitiveArrayRecurse(Object o) throws IOException {
+
+ if (o == null) {
+ return primitiveArrayCount;
+ }
+
+ String className = o.getClass().getName();
+
+ if (className.charAt(0) != '[') {
+ throw new IOException("Invalid object passed to BufferedDataInputStream.readArray:" + className);
+ }
+
+ // Is this a multidimensional array? If so process recursively.
+ if (className.charAt(1) == '[') {
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {
+ primitiveArrayRecurse(((Object[]) o)[i]);
+ }
+ } else {
+
+ // This is a one-d array. Process it using our special functions.
+ switch (className.charAt(1)) {
+ case 'Z':
+ primitiveArrayCount += read((boolean[]) o, 0, ((boolean[]) o).length);
+ break;
+ case 'B':
+ int len = read((byte[]) o, 0, ((byte[]) o).length);
+ primitiveArrayCount += len;
+
+ if (len < ((byte[]) o).length) {
+ throw new EOFException();
+ }
+ break;
+ case 'C':
+ primitiveArrayCount += read((char[]) o, 0, ((char[]) o).length);
+ break;
+ case 'S':
+ primitiveArrayCount += read((short[]) o, 0, ((short[]) o).length);
+ break;
+ case 'I':
+ primitiveArrayCount += read((int[]) o, 0, ((int[]) o).length);
+ break;
+ case 'J':
+ primitiveArrayCount += read((long[]) o, 0, ((long[]) o).length);
+ break;
+ case 'F':
+ primitiveArrayCount += read((float[]) o, 0, ((float[]) o).length);
+ break;
+ case 'D':
+ primitiveArrayCount += read((double[]) o, 0, ((double[]) o).length);
+ break;
+ case 'L':
+
+ // Handle an array of Objects by recursion. Anything
+ // else is an error.
+ if (className.equals("[Ljava.lang.Object;")) {
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {
+ primitiveArrayRecurse(((Object[]) o)[i]);
+ }
+ } else {
+ throw new IOException("Invalid object passed to BufferedDataInputStream.readArray: " + className);
+ }
+ break;
+ default:
+ throw new IOException("Invalid object passed to BufferedDataInputStream.readArray: " + className);
+ }
+ }
+ return primitiveArrayCount;
+ }
+
+ /** Ensure that the requested number of bytes
+ * are available in the buffer or throw an EOF
+ * if they cannot be obtained. Note that this
+ * routine will try to fill the buffer completely.
+ *
+ * @param The required number of bytes.
+ */
+ private void fillBuf(int need) throws IOException {
+
+ if (count > pos) {
+ System.arraycopy(buf, pos, buf, 0, count - pos);
+ count -= pos;
+ need -= count;
+ pos = 0;
+ } else {
+ count = 0;
+ pos = 0;
+ }
+
+ while (need > 0) {
+
+
+ int len = in.read(buf, count, buf.length - count);
+ if (len <= 0) {
+ throw new EOFException();
+ }
+ count += len;
+ need -= len;
+ }
+ }
+
+ /** Read a boolean array */
+ public int read(boolean[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /** Read a boolean array.
+ */
+ public int read(boolean[] b, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+
+ if (pos >= count) {
+ fillBuf(1);
+ }
+
+ if (buf[pos] == 1) {
+ b[i] = true;
+ } else {
+ b[i] = false;
+ }
+ pos += 1;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 1);
+ }
+ return len;
+ }
+
+ /** Read a short array */
+ public int read(short[] s) throws IOException {
+ return read(s, 0, s.length);
+ }
+
+ /** Read a short array */
+ public int read(short[] s, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+ if (count - pos < 2) {
+ fillBuf(2);
+ }
+ s[i] = (short) (buf[pos] << 8 | (buf[pos + 1] & 0xFF));
+ pos += 2;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 2);
+ }
+ return 2 * len;
+ }
+
+ /** Read a character array */
+ public int read(char[] c) throws IOException {
+ return read(c, 0, c.length);
+ }
+
+ /** Read a character array */
+ public int read(char[] c, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+ if (count - pos < 2) {
+ fillBuf(2);
+ }
+ c[i] = (char) (buf[pos] << 8 | (buf[pos + 1] & 0xFF));
+ pos += 2;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 2);
+ }
+ return 2 * len;
+ }
+
+ /** Read an integer array */
+ public int read(int[] i) throws IOException {
+ return read(i, 0, i.length);
+ }
+
+ /** Read an integer array */
+ public int read(int[] i, int start, int len) throws IOException {
+
+ int ii = start;
+ try {
+ for (; ii < start + len; ii += 1) {
+
+ if (count - pos < 4) {
+ fillBuf(4);
+ }
+
+ i[ii] = buf[pos] << 24
+ | (buf[pos + 1] & 0xFF) << 16
+ | (buf[pos + 2] & 0xFF) << 8
+ | (buf[pos + 3] & 0xFF);
+ pos += 4;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, ii, start, 4);
+ }
+ return i.length * 4;
+ }
+
+ /** Read a long array */
+ public int read(long[] l) throws IOException {
+ return read(l, 0, l.length);
+ }
+
+ /** Read a long array */
+ public int read(long[] l, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+ if (count - pos < 8) {
+ fillBuf(8);
+ }
+ int i1 = buf[pos] << 24 | (buf[pos + 1] & 0xFF) << 16 | (buf[pos + 2] & 0xFF) << 8 | (buf[pos + 3] & 0xFF);
+ int i2 = buf[pos + 4] << 24 | (buf[pos + 5] & 0xFF) << 16 | (buf[pos + 6] & 0xFF) << 8 | (buf[pos + 7] & 0xFF);
+ l[i] = ((long) i1) << 32 | ((long) i2 & 0x00000000FFFFFFFFL);
+ pos += 8;
+ }
+
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 8);
+ }
+ return 8 * len;
+ }
+
+ /** Read a float array */
+ public int read(float[] f) throws IOException {
+ return read(f, 0, f.length);
+ }
+
+ /** Read a float array */
+ public int read(float[] f, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+ if (count - pos < 4) {
+ fillBuf(4);
+ }
+ int t = buf[pos] << 24
+ | (buf[pos + 1] & 0xFF) << 16
+ | (buf[pos + 2] & 0xFF) << 8
+ | (buf[pos + 3] & 0xFF);
+ f[i] = Float.intBitsToFloat(t);
+ pos += 4;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 4);
+ }
+ return 4 * len;
+ }
+
+ /** Read a double array */
+ public int read(double[] d) throws IOException {
+ return read(d, 0, d.length);
+ }
+
+ /** Read a double array */
+ public int read(double[] d, int start, int len) throws IOException {
+
+ int i = start;
+ try {
+ for (; i < start + len; i += 1) {
+
+ if (count - pos < 8) {
+ fillBuf(8);
+ }
+ int i1 = buf[pos] << 24 | (buf[pos + 1] & 0xFF) << 16 | (buf[pos + 2] & 0xFF) << 8 | (buf[pos + 3] & 0xFF);
+ int i2 = buf[pos + 4] << 24 | (buf[pos + 5] & 0xFF) << 16 | (buf[pos + 6] & 0xFF) << 8 | (buf[pos + 7] & 0xFF);
+ d[i] = Double.longBitsToDouble(
+ ((long) i1) << 32 | ((long) i2 & 0x00000000FFFFFFFFL));
+ pos += 8;
+ }
+ } catch (EOFException e) {
+ return eofCheck(e, i, start, 8);
+ }
+ return 8 * len;
+ }
+
+ /** For array reads return an EOF if unable to
+ * read any data.
+ */
+ private int eofCheck(EOFException e, int i, int start, int length)
+ throws EOFException {
+
+ if (i == start) {
+ throw e;
+ } else {
+ return (i - start) * length;
+ }
+ }
+
+ /** Represent the stream as a string */
+ public String toString() {
+ return super.toString() + "[count=" + count + ",pos=" + pos + "]";
+ }
+}
--- /dev/null
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+// What do we use in here?
+import java.io.OutputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/** This class is intended for high performance I/O in scientific applications.
+ * It combines the functionality of the BufferedOutputStream and the
+ * DataOutputStream as well as more efficient handling of arrays.
+ * This minimizes the number of method calls that are required to
+ * write data. Informal tests of this method show that it can
+ * be as much as 10 times faster than using a DataOutputStream layered
+ * on a BufferedOutputStream for writing large arrays. The performance
+ * gain on scalars or small arrays will be less but there should probably
+ * never be substantial degradation of performance.
+ * <p>
+ * Note that there is substantial duplication of code to minimize method
+ * invocations. However simple output methods were used where empirical
+ * tests seemed to indicate that the simpler method did not cost any time.
+ * It seems likely that most of these variations will be
+ * washed out across different compilers and users who wish to tune
+ * the method for their particular system may wish to compare the
+ * the implementation of write(int[], int, int) with write(float[], int, int).
+ * <p>
+ * Testing and timing for this class is
+ * peformed in the nom.tam.util.test.BufferedFileTester class.
+ */
+public class BufferedDataOutputStream
+ extends BufferedOutputStream
+ implements ArrayDataOutput {
+
+ /** Use the BufferedOutputStream constructor
+ * @param o An open output stream.
+ */
+ public BufferedDataOutputStream(OutputStream o) {
+ super(o, 32768);
+ }
+
+ /** Use the BufferedOutputStream constructor
+ * @param o An open output stream.
+ * @param bufLength The buffer size.
+ */
+ public BufferedDataOutputStream(OutputStream o, int bufLength) {
+ super(o, bufLength);
+ }
+
+ /** Write a boolean value
+ * @param b The value to be written. Externally true is represented as
+ * a byte of 1 and false as a byte value of 0.
+ */
+ public void writeBoolean(boolean b) throws IOException {
+
+ checkBuf(1);
+ if (b) {
+ buf[count++] = 1;
+ } else {
+ buf[count++] = 0;
+ }
+ }
+
+ /** Write a byte value.
+ */
+ public void writeByte(int b) throws IOException {
+ checkBuf(1);
+ buf[count++] = (byte) b;
+ }
+
+ /** Write an integer value.
+ */
+ public void writeInt(int i) throws IOException {
+
+ checkBuf(4);
+ buf[count++] = (byte) (i >>> 24);
+ buf[count++] = (byte) (i >>> 16);
+ buf[count++] = (byte) (i >>> 8);
+ buf[count++] = (byte) i;
+ }
+
+ /** Write a short value.
+ */
+ public void writeShort(int s) throws IOException {
+
+ checkBuf(2);
+ buf[count++] = (byte) (s >>> 8);
+ buf[count++] = (byte) s;
+
+ }
+
+ /** Write a char value.
+ */
+ public void writeChar(int c) throws IOException {
+
+ checkBuf(2);
+ buf[count++] = (byte) (c >>> 8);
+ buf[count++] = (byte) c;
+ }
+
+ /** Write a long value.
+ */
+ public void writeLong(long l) throws IOException {
+
+ checkBuf(8);
+
+ buf[count++] = (byte) (l >>> 56);
+ buf[count++] = (byte) (l >>> 48);
+ buf[count++] = (byte) (l >>> 40);
+ buf[count++] = (byte) (l >>> 32);
+ buf[count++] = (byte) (l >>> 24);
+ buf[count++] = (byte) (l >>> 16);
+ buf[count++] = (byte) (l >>> 8);
+ buf[count++] = (byte) l;
+ }
+
+ /** Write a float value.
+ */
+ public void writeFloat(float f) throws IOException {
+
+ checkBuf(4);
+
+ int i = Float.floatToIntBits(f);
+
+ buf[count++] = (byte) (i >>> 24);
+ buf[count++] = (byte) (i >>> 16);
+ buf[count++] = (byte) (i >>> 8);
+ buf[count++] = (byte) i;
+
+
+ }
+
+ /** Write a double value.
+ */
+ public void writeDouble(double d) throws IOException {
+
+ checkBuf(8);
+ long l = Double.doubleToLongBits(d);
+
+ buf[count++] = (byte) (l >>> 56);
+ buf[count++] = (byte) (l >>> 48);
+ buf[count++] = (byte) (l >>> 40);
+ buf[count++] = (byte) (l >>> 32);
+ buf[count++] = (byte) (l >>> 24);
+ buf[count++] = (byte) (l >>> 16);
+ buf[count++] = (byte) (l >>> 8);
+ buf[count++] = (byte) l;
+
+ }
+
+ /** Write a string using the local protocol to convert char's to bytes.
+ *
+ * @param s The string to be written.
+ */
+ public void writeBytes(String s) throws IOException {
+
+ write(s.getBytes(), 0, s.length());
+ }
+
+ /** Write a string as an array of chars.
+ */
+ public void writeChars(String s) throws IOException {
+
+ for (int i = 0; i < s.length(); i += 1) {
+ writeChar(s.charAt(i));
+ }
+ }
+
+ /** Write a string as a UTF. Note that this class does not
+ * handle this situation efficiently since it creates
+ * new DataOutputStream to handle each call.
+ */
+ public void writeUTF(String s) throws IOException {
+
+ // Punt on this one and use standard routines.
+ DataOutputStream d = new DataOutputStream(this);
+ d.writeUTF(s);
+ d.flush();
+ d.close();
+ }
+
+ /** This routine provides efficient writing of arrays of any primitive type.
+ * The String class is also handled but it is an error to invoke this
+ * method with an object that is not an array of these types. If the
+ * array is multidimensional, then it calls itself recursively to write
+ * the entire array. Strings are written using the standard
+ * 1 byte format (i.e., as in writeBytes).
+ *
+ * If the array is an array of objects, then writePrimitiveArray will
+ * be called for each element of the array.
+ *
+ * @param o The object to be written. It must be an array of a primitive
+ * type, Object, or String.
+ */
+ public void writePrimitiveArray(Object o) throws IOException {
+ writeArray(o);
+ }
+
+ /** This routine provides efficient writing of arrays of any primitive type.
+ * The String class is also handled but it is an error to invoke this
+ * method with an object that is not an array of these types. If the
+ * array is multidimensional, then it calls itself recursively to write
+ * the entire array. Strings are written using the standard
+ * 1 byte format (i.e., as in writeBytes).
+ *
+ * If the array is an array of objects, then writePrimitiveArray will
+ * be called for each element of the array.
+ *
+ * @param o The object to be written. It must be an array of a primitive
+ * type, Object, or String.
+ */
+ public void writeArray(Object o) throws IOException {
+ String className = o.getClass().getName();
+
+ if (className.charAt(0) != '[') {
+ throw new IOException("Invalid object passed to BufferedDataOutputStream.write" + className);
+ }
+
+ // Is this a multidimensional array? If so process recursively.
+ if (className.charAt(1) == '[') {
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {
+ writeArray(((Object[]) o)[i]);
+ }
+ } else {
+
+ // This is a one-d array. Process it using our special functions.
+ switch (className.charAt(1)) {
+ case 'Z':
+ write((boolean[]) o, 0, ((boolean[]) o).length);
+ break;
+ case 'B':
+ write((byte[]) o, 0, ((byte[]) o).length);
+ break;
+ case 'C':
+ write((char[]) o, 0, ((char[]) o).length);
+ break;
+ case 'S':
+ write((short[]) o, 0, ((short[]) o).length);
+ break;
+ case 'I':
+ write((int[]) o, 0, ((int[]) o).length);
+ break;
+ case 'J':
+ write((long[]) o, 0, ((long[]) o).length);
+ break;
+ case 'F':
+ write((float[]) o, 0, ((float[]) o).length);
+ break;
+ case 'D':
+ write((double[]) o, 0, ((double[]) o).length);
+ break;
+ case 'L':
+
+ // Handle two exceptions: an array of strings, or an
+ // array of objects. .
+ if (className.equals("[Ljava.lang.String;")) {
+ write((String[]) o, 0, ((String[]) o).length);
+ } else if (className.equals("[Ljava.lang.Object;")) {
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {
+ writeArray(((Object[]) o)[i]);
+ }
+ } else {
+ throw new IOException("Invalid object passed to BufferedDataOutputStream.writeArray: " + className);
+ }
+ break;
+ default:
+ throw new IOException("Invalid object passed to BufferedDataOutputStream.writeArray: " + className);
+ }
+ }
+
+ }
+
+ /** Write an array of booleans.
+ */
+ public void write(boolean[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ /** Write a segment of an array of booleans.
+ */
+ public void write(boolean[] b, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+
+ if (count + 1 > buf.length) {
+ checkBuf(1);
+ }
+ if (b[i]) {
+ buf[count++] = 1;
+ } else {
+ buf[count++] = 0;
+ }
+ }
+ }
+
+ /** Write an array of shorts.
+ */
+ public void write(short[] s) throws IOException {
+ write(s, 0, s.length);
+ }
+
+ /** Write a segment of an array of shorts.
+ */
+ public void write(short[] s, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+ if (count + 2 > buf.length) {
+ checkBuf(2);
+ }
+ buf[count++] = (byte) (s[i] >> 8);
+ buf[count++] = (byte) (s[i]);
+ }
+ }
+
+ /** Write an array of char's.
+ */
+ public void write(char[] c) throws IOException {
+ write(c, 0, c.length);
+ }
+
+ /** Write a segment of an array of char's.
+ */
+ public void write(char[] c, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+ if (count + 2 > buf.length) {
+ checkBuf(2);
+ }
+ buf[count++] = (byte) (c[i] >> 8);
+ buf[count++] = (byte) (c[i]);
+ }
+ }
+
+ /** Write an array of int's.
+ */
+ public void write(int[] i) throws IOException {
+ write(i, 0, i.length);
+ }
+
+ /** Write a segment of an array of int's.
+ */
+ public void write(int[] i, int start, int len) throws IOException {
+
+ for (int ii = start; ii < start + len; ii += 1) {
+ if (count + 4 > buf.length) {
+ checkBuf(4);
+ }
+
+ buf[count++] = (byte) (i[ii] >>> 24);
+ buf[count++] = (byte) (i[ii] >>> 16);
+ buf[count++] = (byte) (i[ii] >>> 8);
+ buf[count++] = (byte) (i[ii]);
+
+ }
+
+ }
+
+ /** Write an array of longs.
+ */
+ public void write(long[] l) throws IOException {
+ write(l, 0, l.length);
+ }
+
+ /** Write a segement of an array of longs.
+ */
+ public void write(long[] l, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+ if (count + 8 > buf.length) {
+ checkBuf(8);
+ }
+ int t = (int) (l[i] >>> 32);
+
+ buf[count++] = (byte) (t >>> 24);
+ buf[count++] = (byte) (t >>> 16);
+ buf[count++] = (byte) (t >>> 8);
+ buf[count++] = (byte) (t);
+
+ t = (int) (l[i]);
+
+ buf[count++] = (byte) (t >>> 24);
+ buf[count++] = (byte) (t >>> 16);
+ buf[count++] = (byte) (t >>> 8);
+ buf[count++] = (byte) (t);
+ }
+ }
+
+ /** Write an array of floats.
+ */
+ public void write(float[] f) throws IOException {
+ write(f, 0, f.length);
+ }
+
+ public void write(float[] f, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+
+ if (count + 4 > buf.length) {
+ checkBuf(4);
+ }
+ int t = Float.floatToIntBits(f[i]);
+ buf[count++] = (byte) (t >>> 24);
+ buf[count++] = (byte) (t >>> 16);
+ buf[count++] = (byte) (t >>> 8);
+ buf[count++] = (byte) t;
+ }
+ }
+
+ /** Write an array of doubles.
+ */
+ public void write(double[] d) throws IOException {
+ write(d, 0, d.length);
+ }
+
+ public void write(double[] d, int start, int len) throws IOException {
+
+ for (int i = start; i < start + len; i += 1) {
+ if (count + 8 > buf.length) {
+ checkBuf(8);
+ }
+ long t = Double.doubleToLongBits(d[i]);
+
+ int ix = (int) (t >>> 32);
+
+ buf[count++] = (byte) (ix >>> 24);
+ buf[count++] = (byte) (ix >>> 16);
+ buf[count++] = (byte) (ix >>> 8);
+ buf[count++] = (byte) (ix);
+
+ ix = (int) t;
+
+ buf[count++] = (byte) (ix >>> 24);
+ buf[count++] = (byte) (ix >>> 16);
+ buf[count++] = (byte) (ix >>> 8);
+ buf[count++] = (byte) ix;
+ }
+
+ }
+
+ /** Write an array of Strings -- equivalent to calling writeBytes for each string.
+ */
+ public void write(String[] s) throws IOException {
+ write(s, 0, s.length);
+ }
+
+ /** Write a segment of an array of Strings.
+ * Equivalent to calling writeBytes for the selected elements.
+ */
+ public void write(String[] s, int start, int len) throws IOException {
+
+ // Do not worry about buffering this specially since the
+ // strings may be of differing lengths.
+
+ for (int i = 0; i < s.length; i += 1) {
+ writeBytes(s[i]);
+ }
+ }
+
+ /* See if there is enough space to add
+ * something to the buffer.
+ */
+ protected void checkBuf(int need) throws IOException {
+
+ if (count + need > buf.length) {
+ out.write(buf, 0, count);
+ count = 0;
+ }
+ }
+}
--- /dev/null
+package nom.tam.util;\r
+\r
+/* Copyright: Thomas McGlynn 1997-1999.\r
+ * This code may be used for any purpose, non-commercial\r
+ * or commercial so long as this copyright notice is retained\r
+ * in the source code or included in or referred to in any\r
+ * derived software.\r
+ */\r
+/** This class is intended for high performance I/O in scientific applications.\r
+ * It adds buffering to the RandomAccessFile and also\r
+ * provides efficient handling of arrays. Primitive arrays\r
+ * may be written using a single method call. Large buffers\r
+ * are used to minimize synchronization overheads since methods\r
+ * of this class are not synchronized.\r
+ * <p>\r
+ * Note that although this class supports most of the\r
+ * contract of RandomAccessFile it does not (and can not)\r
+ * extend that class since many of the methods of\r
+ * RandomAccessFile are final. In practice this\r
+ * method works much like the StreamFilter classes.\r
+ * All methods are implemented in this class but\r
+ * some are simply delegated to an underlying RandomAccessFile member.\r
+ * <p>\r
+ * Testing and timing routines are available in\r
+ * the nom.tam.util.test.BufferedFileTester class.\r
+ *\r
+ * Version 1.1 October 12, 2000: Fixed handling of EOF in array reads\r
+ * so that a partial array will be returned when an EOF is detected.\r
+ * Excess bytes that cannot be used to construct array elements will\r
+ * be discarded (e.g., if there are 2 bytes left and the user is\r
+ * reading an int array).\r
+ * Version 1.2 December 8, 2002: Added getChannel method.\r
+ * Version 1.3 March 2, 2007: Added File based constructors.\r
+ * Version 1.4 July 20, 2009: Added support for >2G Object reads.\r
+ * This is still a bit problematic in that we do not support\r
+ * primitive arrays larger than 2 GB/atomsize. However except\r
+ * in the case of bytes this is not currently a major issue.\r
+ *\r
+ */\r
+import java.io.RandomAccessFile;\r
+import java.io.File;\r
+import java.io.FileDescriptor;\r
+import java.io.IOException;\r
+import java.io.EOFException;\r
+\r
+public class BufferedFile\r
+ implements ArrayDataInput, ArrayDataOutput, RandomAccess {\r
+\r
+ /** The current offset into the buffer */\r
+ private int bufferOffset;\r
+ /** The number of valid characters in the buffer */\r
+ private int bufferLength;\r
+ /** The declared length of the buffer array */\r
+ private int bufferSize;\r
+ /** Counter used in reading arrays */\r
+ private long primitiveArrayCount;\r
+ /** The data buffer. */\r
+ private byte[] buffer;\r
+ /** The underlying access to the file system */\r
+ private RandomAccessFile raf;\r
+ /** The offset of the beginning of the current buffer */\r
+ private long fileOffset;\r
+ /** Is the buffer being used for input or output */\r
+ private boolean doingInput;\r
+\r
+ /** Create a read-only buffered file */\r
+ public BufferedFile(String filename) throws IOException {\r
+ this(filename, "r", 32768);\r
+ }\r
+\r
+ /** Create a buffered file with the given mode.\r
+ * @param filename The file to be accessed.\r
+ * @param mode A string composed of "r" and "w" for\r
+ * read and write access.\r
+ */\r
+ public BufferedFile(String filename, String mode) throws IOException {\r
+ this(filename, mode, 32768);\r
+ }\r
+\r
+ /** Create a buffered file from a File descriptor */\r
+ public BufferedFile(File file) throws IOException {\r
+ this(file, "r", 32768);\r
+ }\r
+\r
+ /** Create a buffered file from a File descriptor */\r
+ public BufferedFile(File file, String mode) throws IOException {\r
+ this(file, mode, 32768);\r
+ }\r
+\r
+ /** Create a buffered file with the given mode and a specified\r
+ * buffer size.\r
+ * @param filename The file to be accessed.\r
+ * @param mode A string composed of "r" and "w" indicating\r
+ * read or write access.\r
+ * @param buffer The buffer size to be used. This should be\r
+ * substantially larger than 100 bytes and\r
+ * defaults to 32768 bytes in the other\r
+ * constructors.\r
+ */\r
+ public BufferedFile(String filename, String mode, int bufferSize) throws IOException {\r
+\r
+ File file = new File(filename);\r
+ initialize(file, mode, bufferSize);\r
+ }\r
+\r
+ /** Create a buffered file from a file descriptor */\r
+ public BufferedFile(File file, String mode, int bufferSize) throws IOException {\r
+ initialize(file, mode, bufferSize);\r
+ }\r
+\r
+ protected void initialize(File file, String mode, int bufferSize) throws IOException {\r
+\r
+ raf = new RandomAccessFile(file, mode);\r
+ buffer = new byte[bufferSize];\r
+ bufferOffset = 0;\r
+ bufferLength = 0;\r
+ fileOffset = 0;\r
+ this.bufferSize = bufferSize;\r
+\r
+ }\r
+\r
+ /** Create a buffered file using a mapped\r
+\r
+\r
+ /** Read an entire byte array.\r
+ * Note BufferedFile will return a partially filled array\r
+ * only at an end-of-file.\r
+ * @param buf The array to be filled.\r
+ */\r
+ public int read(byte[] buf) throws IOException {\r
+ return read(buf, 0, buf.length);\r
+ }\r
+\r
+ /** Read into a segment of a byte array.\r
+ * @param buf The array to be filled.\r
+ * @param offset The starting location for input.\r
+ * @param length The number of bytes to be read. Fewer bytes\r
+ * will be read if an EOF is reached.\r
+ */\r
+ public int read(byte[] buf, int offset, int len) throws IOException {\r
+\r
+ checkBuffer(-1);\r
+ int total = 0;\r
+\r
+ // Ensure that the entire buffer is read.\r
+ while (len > 0) {\r
+\r
+ if (bufferOffset < bufferLength) {\r
+\r
+ int get = len;\r
+ if (bufferOffset + get > bufferLength) {\r
+ get = bufferLength - bufferOffset;\r
+ }\r
+ System.arraycopy(buffer, bufferOffset, buf, offset, get);\r
+ len -= get;\r
+ bufferOffset += get;\r
+ offset += get;\r
+ total += get;\r
+ continue;\r
+\r
+ } else {\r
+\r
+ // This might be pretty long, but we know that the\r
+ // old buffer is exhausted.\r
+ try {\r
+ if (len > bufferSize) {\r
+ checkBuffer(bufferSize);\r
+ } else {\r
+ checkBuffer(len);\r
+ }\r
+ } catch (EOFException e) {\r
+ if (bufferLength > 0) {\r
+ System.arraycopy(buffer, 0, buf, offset, bufferLength);\r
+ total += bufferLength;\r
+ bufferLength = 0;\r
+ }\r
+ if (total == 0) {\r
+ throw e;\r
+ } else {\r
+ return total;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ return total;\r
+ }\r
+\r
+ /** This should only be used when a small number of\r
+ * bytes is required (substantially smaller than\r
+ * bufferSize.\r
+ */\r
+ private void checkBuffer(int needBytes) throws IOException {\r
+\r
+ // Check if the buffer has some pending output.\r
+ if (!doingInput && bufferOffset > 0) {\r
+ flush();\r
+ }\r
+ doingInput = true;\r
+\r
+ if (bufferOffset + needBytes < bufferLength) {\r
+ return;\r
+ }\r
+ /* Move the last few bytes to the beginning of the buffer\r
+ * and read in enough data to fill the current demand.\r
+ */\r
+ int len = bufferLength - bufferOffset;\r
+\r
+ /* Note that new location that the beginning of the buffer\r
+ * corresponds to.\r
+ */\r
+ fileOffset += bufferOffset;\r
+ if (len > 0) {\r
+ System.arraycopy(buffer, bufferOffset, buffer, 0, len);\r
+ }\r
+ needBytes -= len;\r
+ bufferLength = len;\r
+ bufferOffset = 0;\r
+\r
+ while (needBytes > 0) {\r
+ len = raf.read(buffer, bufferLength, bufferSize - bufferLength);\r
+ if (len < 0) {\r
+ throw new EOFException();\r
+ }\r
+ needBytes -= len;\r
+ bufferLength += len;\r
+ }\r
+ }\r
+\r
+ /** Read a byte */\r
+ public int read() throws IOException {\r
+ checkBuffer(1);\r
+ bufferOffset += 1;\r
+ return buffer[bufferOffset - 1];\r
+ }\r
+\r
+ /** Skip from the current position.\r
+ * @param offset The number of bytes from the\r
+ * current position. This may\r
+ * be negative.\r
+ */\r
+ public long skip(long offset) throws IOException {\r
+\r
+ if (offset > 0 && fileOffset + bufferOffset + offset > raf.length()) {\r
+ offset = raf.length() - fileOffset - bufferOffset;\r
+ seek(raf.length());\r
+ } else if (fileOffset + bufferOffset + offset < 0) {\r
+ offset = -(fileOffset + bufferOffset);\r
+ seek(0);\r
+ } else {\r
+ seek(fileOffset + bufferOffset + offset);\r
+ }\r
+ return offset;\r
+ }\r
+\r
+ /** Move to the current offset from the beginning of the file.\r
+ * A user may move past the end of file but this\r
+ * does not extend the file unless data is written there.\r
+ */\r
+ public void seek(long offsetFromStart) throws IOException {\r
+\r
+ if (!doingInput) {\r
+ // Have to flush before a seek...\r
+ flush();\r
+ }\r
+\r
+ // Are we within the current buffer?\r
+ if (fileOffset <= offsetFromStart && offsetFromStart < fileOffset + bufferLength) {\r
+ bufferOffset = (int) (offsetFromStart - fileOffset);\r
+ } else {\r
+\r
+ // Seek to the desired location.\r
+ if (offsetFromStart < 0) {\r
+ offsetFromStart = 0;\r
+ }\r
+\r
+ fileOffset = offsetFromStart;\r
+ raf.seek(fileOffset);\r
+\r
+ // Invalidate the current buffer.\r
+ bufferLength = 0;\r
+ bufferOffset = 0;\r
+ }\r
+ }\r
+\r
+ /** Read a boolean\r
+ * @return a boolean generated from the next\r
+ * byte in the input.\r
+ */\r
+ public boolean readBoolean() throws IOException {\r
+ return convertToBoolean();\r
+ }\r
+\r
+ /** Get a boolean from the buffer */\r
+ private boolean convertToBoolean() throws IOException {\r
+ checkBuffer(1);\r
+ bufferOffset += 1;\r
+ return buffer[bufferOffset - 1] == 1;\r
+ }\r
+\r
+ /** Read a byte\r
+ * @return the next byte in the input.\r
+ */\r
+ public byte readByte() throws IOException {\r
+ checkBuffer(1);\r
+ bufferOffset += 1;\r
+ return buffer[bufferOffset - 1];\r
+ }\r
+\r
+ /** Read an unsigned byte.\r
+ * @return the unsigned value of the next byte as\r
+ * an integer.\r
+ */\r
+ public int readUnsignedByte() throws IOException {\r
+ checkBuffer(1);\r
+ bufferOffset += 1;\r
+ return buffer[bufferOffset - 1] | 0x00ff;\r
+ }\r
+\r
+ /** Read an int\r
+ * @return an integer read from the input.\r
+ */\r
+ public int readInt() throws IOException {\r
+ return convertToInt();\r
+ }\r
+\r
+ /** Get an integer value from the buffer */\r
+ private int convertToInt() throws IOException {\r
+ checkBuffer(4);\r
+ int x = bufferOffset;\r
+ int i = buffer[x] << 24 | (buffer[x + 1] & 0xFF) << 16 | (buffer[x + 2] & 0xFF) << 8 | (buffer[x + 3] & 0xFF);\r
+ bufferOffset += 4;\r
+ return i;\r
+ }\r
+\r
+ /** Read a short\r
+ * @return a short read from the input.\r
+ */\r
+ public short readShort() throws IOException {\r
+ return convertToShort();\r
+ }\r
+\r
+ /** Get a short from the buffer */\r
+ private short convertToShort() throws IOException {\r
+ checkBuffer(2);\r
+ short s = (short) (buffer[bufferOffset] << 8 | (buffer[bufferOffset + 1] & 0xFF));\r
+ bufferOffset += 2;\r
+ return s;\r
+ }\r
+\r
+ /** Read an unsigned short.\r
+ * @return an unsigned short value as an integer.\r
+ */\r
+ public int readUnsignedShort() throws IOException {\r
+ return readShort() & 0xFFFF;\r
+ }\r
+\r
+ /** Read a char\r
+ * @return a char read from the input.\r
+ */\r
+ public char readChar() throws IOException {\r
+ return convertToChar();\r
+ }\r
+\r
+ /** Get a char from the buffer */\r
+ private char convertToChar() throws IOException {\r
+ checkBuffer(2);\r
+ char c = (char) (buffer[bufferOffset] << 8 | (buffer[bufferOffset + 1] & 0xFF));\r
+ bufferOffset += 2;\r
+ return c;\r
+ }\r
+\r
+ /** Read a long.\r
+ * @return a long value read from the input.\r
+ */\r
+ public long readLong() throws IOException {\r
+ return convertToLong();\r
+ }\r
+\r
+ /** Get a long value from the buffer */\r
+ private long convertToLong() throws IOException {\r
+ checkBuffer(8);\r
+ int x = bufferOffset;\r
+\r
+ int i1 = buffer[x] << 24 | (buffer[x + 1] & 0xFF) << 16 | (buffer[x + 2] & 0xFF) << 8 | (buffer[x + 3] & 0xFF);\r
+ int i2 = buffer[x + 4] << 24 | (buffer[x + 5] & 0xFF) << 16 | (buffer[x + 6] & 0xFF) << 8 | (buffer[x + 7] & 0xFF);\r
+ bufferOffset += 8;\r
+\r
+ return (((long) i1) << 32) | (((long) i2) & 0x00000000ffffffffL);\r
+ }\r
+\r
+ /** Read a float.\r
+ * @return a float value read from the input.\r
+ */\r
+ public float readFloat() throws IOException {\r
+ return Float.intBitsToFloat(convertToInt());\r
+ }\r
+\r
+ /** Read a double.\r
+ * @return a double value read from the input.\r
+ */\r
+ public double readDouble() throws IOException {\r
+ return Double.longBitsToDouble(convertToLong());\r
+ }\r
+\r
+ /** Read a byte array fully.\r
+ * Since the read method of this class reads an entire\r
+ * buffer, the only difference with readFully is that\r
+ * readFully will signal an EOF if the buffer cannot be filled.\r
+ */\r
+ public void readFully(byte[] b) throws IOException {\r
+ readFully(b, 0, b.length);\r
+ }\r
+\r
+ /** Read a byte array fully.\r
+ * Since the read method of this class reads an entire\r
+ * buffer, the only difference with readFully is that\r
+ * readFully will signal an EOF if the buffer cannot be filled.\r
+ */\r
+ public void readFully(byte[] b, int off, int len) throws IOException {\r
+\r
+ if (off < 0 || len < 0 || off + len > b.length) {\r
+ throw new IOException("Attempt to read outside byte array");\r
+ }\r
+\r
+ if (read(b, off, len) < len) {\r
+ throw new EOFException();\r
+ }\r
+ }\r
+\r
+ /** Skip the number of bytes.\r
+ * This differs from the skip method in that\r
+ * it will throw an EOF if a forward skip cannot\r
+ * be fully accomplished... (However that isn't\r
+ * supposed to happen with a random access file,\r
+ * so there is probably no operational difference).\r
+ */\r
+ public int skipBytes(int toSkip) throws IOException {\r
+ return (int) skipBytes((long) toSkip);\r
+ }\r
+\r
+ public long skipBytes(long toSkip) throws IOException {\r
+\r
+ // Note that we allow negative skips...\r
+ if (skip(toSkip) < toSkip) {\r
+ throw new EOFException();\r
+ } else {\r
+ return toSkip;\r
+ }\r
+ }\r
+\r
+ /** Read a string encoded as a UTF.\r
+ * @return the string.\r
+ */\r
+ public String readUTF() throws IOException {\r
+ checkBuffer(-1);\r
+ raf.seek(fileOffset + bufferOffset);\r
+ String utf = raf.readUTF();\r
+ fileOffset = raf.getFilePointer();\r
+\r
+ // Invalidate the buffer.\r
+ bufferLength = 0;\r
+ bufferOffset = 0;\r
+\r
+ return utf;\r
+ }\r
+\r
+ /** Read a line of input.\r
+ * @return the next line.\r
+ */\r
+ public String readLine() throws IOException {\r
+\r
+ checkBuffer(-1);\r
+ raf.seek(fileOffset + bufferOffset);\r
+ String line = raf.readLine();\r
+ fileOffset = raf.getFilePointer();\r
+\r
+ // Invalidate the buffer.\r
+ bufferLength = 0;\r
+ bufferOffset = 0;\r
+\r
+ return line;\r
+ }\r
+\r
+ /** This routine provides efficient reading of arrays of any primitive type.\r
+ * @deprecated The readLArray(Object) routine should be used to\r
+ * ensure that large arrays which read more than\r
+ * two-gigabytes return the proper value.\r
+ *\r
+ *\r
+ * @param o The object to be read. It must be an array of a primitive type,\r
+ * or an array of Object's.\r
+ */\r
+ public int readArray(Object o) throws IOException {\r
+ return (int) readLArray(o);\r
+ }\r
+\r
+ /** This routine provides efficient reading of arrays of any primitive\r
+ * type.\r
+ * @param o The object to be read. It must be an arraof of a primtive type\r
+ * (or any dimension), or an array of Objects which contains\r
+ * pointers to primitive arrays or other object arrays.\r
+ */\r
+ public long readLArray(Object o) throws IOException {\r
+\r
+\r
+ // Note that we assume that only a single thread is\r
+ // doing a primitive Array read at any given time. Otherwise\r
+ // primitiveArrayCount can be wrong and also the\r
+ // input data can be mixed up. If this assumption is not\r
+ // true we need to synchronize this call.\r
+\r
+ primitiveArrayCount = 0;\r
+ return primitiveArrayRecurse(o);\r
+ }\r
+\r
+ protected long primitiveArrayRecurse(Object o) throws IOException {\r
+\r
+ if (o == null) {\r
+ return primitiveArrayCount;\r
+ }\r
+\r
+ String className = o.getClass().getName();\r
+\r
+ if (className.charAt(0) != '[') {\r
+ throw new IOException("Invalid object passed to BufferedDataInputStream.readArray:" + className);\r
+ }\r
+\r
+ // Is this a multidimensional array? If so process recursively.\r
+ if (className.charAt(1) == '[') {\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ primitiveArrayRecurse(((Object[]) o)[i]);\r
+ }\r
+ } else {\r
+\r
+ // This is a one-d array. Process it using our special functions.\r
+ switch (className.charAt(1)) {\r
+ case 'Z':\r
+ primitiveArrayCount += read((boolean[]) o, 0, ((boolean[]) o).length);\r
+ break;\r
+ case 'B':\r
+ int len = read((byte[]) o, 0, ((byte[]) o).length);\r
+ break;\r
+ case 'C':\r
+ primitiveArrayCount += read((char[]) o, 0, ((char[]) o).length);\r
+ break;\r
+ case 'S':\r
+ primitiveArrayCount += read((short[]) o, 0, ((short[]) o).length);\r
+ break;\r
+ case 'I':\r
+ primitiveArrayCount += read((int[]) o, 0, ((int[]) o).length);\r
+ break;\r
+ case 'J':\r
+ primitiveArrayCount += read((long[]) o, 0, ((long[]) o).length);\r
+ break;\r
+ case 'F':\r
+ primitiveArrayCount += read((float[]) o, 0, ((float[]) o).length);\r
+ break;\r
+ case 'D':\r
+ primitiveArrayCount += read((double[]) o, 0, ((double[]) o).length);\r
+ break;\r
+ case 'L':\r
+\r
+ // Handle an array of Objects by recursion. Anything\r
+ // else is an error.\r
+ if (className.equals("[Ljava.lang.Object;")) {\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ primitiveArrayRecurse(((Object[]) o)[i]);\r
+ }\r
+ } else {\r
+ throw new IOException("Invalid object passed to BufferedFile.readPrimitiveArray: " + className);\r
+ }\r
+ break;\r
+ default:\r
+ throw new IOException("Invalid object passed to BufferedDataInputStream.readArray: " + className);\r
+ }\r
+ }\r
+ return primitiveArrayCount;\r
+ }\r
+\r
+ public int read(boolean[] b) throws IOException {\r
+ return read(b, 0, b.length);\r
+ }\r
+\r
+ public int read(boolean[] b, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ b[i] = convertToBoolean();\r
+ }\r
+ return length;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 1);\r
+ }\r
+ }\r
+\r
+ public int read(short[] s) throws IOException {\r
+ return read(s, 0, s.length);\r
+ }\r
+\r
+ public int read(short[] s, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ s[i] = convertToShort();\r
+ }\r
+ return length * 2;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 2);\r
+ }\r
+ }\r
+\r
+ public int read(char[] c) throws IOException {\r
+ return read(c, 0, c.length);\r
+ }\r
+\r
+ public int read(char[] c, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ c[i] = convertToChar();\r
+ }\r
+ return length * 2;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 2);\r
+ }\r
+ }\r
+\r
+ public int read(int[] i) throws IOException {\r
+ return read(i, 0, i.length);\r
+ }\r
+\r
+ public int read(int[] i, int start, int length) throws IOException {\r
+\r
+ int ii = start;\r
+ try {\r
+ for (; ii < start + length; ii += 1) {\r
+ i[ii] = convertToInt();\r
+ }\r
+ return length * 4;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, ii, 4);\r
+ }\r
+ }\r
+\r
+ public int read(long[] l) throws IOException {\r
+ return read(l, 0, l.length);\r
+ }\r
+\r
+ public int read(long[] l, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ l[i] = convertToLong();\r
+ }\r
+ return length * 8;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 8);\r
+ }\r
+\r
+ }\r
+\r
+ public int read(float[] f) throws IOException {\r
+ return read(f, 0, f.length);\r
+ }\r
+\r
+ public int read(float[] f, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ f[i] = Float.intBitsToFloat(convertToInt());\r
+ }\r
+ return length * 4;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 4);\r
+ }\r
+ }\r
+\r
+ public int read(double[] d) throws IOException {\r
+ return read(d, 0, d.length);\r
+ }\r
+\r
+ public int read(double[] d, int start, int length) throws IOException {\r
+\r
+ int i = start;\r
+ try {\r
+ for (; i < start + length; i += 1) {\r
+ d[i] = Double.longBitsToDouble(convertToLong());\r
+ }\r
+ return length * 8;\r
+ } catch (EOFException e) {\r
+ return eofCheck(e, start, i, 8);\r
+ }\r
+ }\r
+\r
+ /** See if an exception should be thrown during an array read. */\r
+ private int eofCheck(EOFException e, int start, int index, int length)\r
+ throws EOFException {\r
+ if (start == index) {\r
+ throw e;\r
+ } else {\r
+ return (index - start) * length;\r
+ }\r
+ }\r
+\r
+ /**** Output Routines ****/\r
+ private void needBuffer(int need) throws IOException {\r
+\r
+ if (doingInput) {\r
+\r
+ fileOffset += bufferOffset;\r
+ raf.seek(fileOffset);\r
+\r
+ doingInput = false;\r
+\r
+ bufferOffset = 0;\r
+ bufferLength = 0;\r
+ }\r
+\r
+ if (bufferOffset + need >= bufferSize) {\r
+ raf.write(buffer, 0, bufferOffset);\r
+ fileOffset += bufferOffset;\r
+ bufferOffset = 0;\r
+ }\r
+ }\r
+\r
+ public void write(int buf) throws IOException {\r
+ convertFromByte(buf);\r
+ }\r
+\r
+ public void write(byte[] buf) throws IOException {\r
+ write(buf, 0, buf.length);\r
+ }\r
+\r
+ public void write(byte[] buf, int offset, int length) throws IOException {\r
+\r
+ if (length < bufferSize) {\r
+ /* If we can use the buffer do so... */\r
+ needBuffer(length);\r
+ System.arraycopy(buf, offset, buffer, bufferOffset, length);\r
+ bufferOffset += length;\r
+ } else {\r
+ /* Otherwise flush the buffer and write the data directly.\r
+ * Make sure that we indicate that the buffer is clean when\r
+ * we're done.\r
+ */\r
+ flush();\r
+\r
+ raf.write(buf, offset, length);\r
+\r
+ fileOffset += length;\r
+\r
+ doingInput = false;\r
+ bufferOffset = 0;\r
+ bufferLength = 0;\r
+ }\r
+ }\r
+\r
+ /** Flush output buffer if necessary.\r
+ * This method is not present in RandomAccessFile\r
+ * but users may need to call flush to ensure\r
+ * that data has been written.\r
+ */\r
+ public void flush() throws IOException {\r
+\r
+ if (!doingInput && bufferOffset > 0) {\r
+ raf.write(buffer, 0, bufferOffset);\r
+ fileOffset += bufferOffset;\r
+ bufferOffset = 0;\r
+ bufferLength = 0;\r
+ }\r
+ }\r
+\r
+ /** Clear up any pending output at cleanup.\r
+ */\r
+ protected void finalize() {\r
+ try {\r
+ if (getFD().valid()) {\r
+ flush();\r
+ close();\r
+ }\r
+ } catch (Exception e) {\r
+ }\r
+ }\r
+\r
+ /** Write a boolean value\r
+ * @param b The value to be written. Externally true is represented as\r
+ * a byte of 1 and false as a byte value of 0.\r
+ */\r
+ public void writeBoolean(boolean b) throws IOException {\r
+ convertFromBoolean(b);\r
+ }\r
+\r
+ private void convertFromBoolean(boolean b) throws IOException {\r
+ needBuffer(1);\r
+ if (b) {\r
+ buffer[bufferOffset] = (byte) 1;\r
+ } else {\r
+ buffer[bufferOffset] = (byte) 0;\r
+ }\r
+ bufferOffset += 1;\r
+ }\r
+\r
+ /** Write a byte value.\r
+ */\r
+ public void writeByte(int b) throws IOException {\r
+ convertFromByte(b);\r
+ }\r
+\r
+ private void convertFromByte(int b) throws IOException {\r
+ needBuffer(1);\r
+ buffer[bufferOffset++] = (byte) b;\r
+ }\r
+\r
+ /** Write an integer value.\r
+ */\r
+ public void writeInt(int i) throws IOException {\r
+ convertFromInt(i);\r
+ }\r
+\r
+ private void convertFromInt(int i) throws IOException {\r
+\r
+ needBuffer(4);\r
+ buffer[bufferOffset++] = (byte) (i >>> 24);\r
+ buffer[bufferOffset++] = (byte) (i >>> 16);\r
+ buffer[bufferOffset++] = (byte) (i >>> 8);\r
+ buffer[bufferOffset++] = (byte) i;\r
+ }\r
+\r
+ /** Write a short value.\r
+ */\r
+ public void writeShort(int s) throws IOException {\r
+\r
+ convertFromShort(s);\r
+ }\r
+\r
+ private void convertFromShort(int s) throws IOException {\r
+ needBuffer(2);\r
+\r
+ buffer[bufferOffset++] = (byte) (s >>> 8);\r
+ buffer[bufferOffset++] = (byte) s;\r
+ }\r
+\r
+ /** Write a char value.\r
+ */\r
+ public void writeChar(int c) throws IOException {\r
+ convertFromChar(c);\r
+ }\r
+\r
+ private void convertFromChar(int c) throws IOException {\r
+ needBuffer(2);\r
+ buffer[bufferOffset++] = (byte) (c >>> 8);\r
+ buffer[bufferOffset++] = (byte) c;\r
+ }\r
+\r
+ /** Write a long value.\r
+ */\r
+ public void writeLong(long l) throws IOException {\r
+ convertFromLong(l);\r
+ }\r
+\r
+ private void convertFromLong(long l) throws IOException {\r
+ needBuffer(8);\r
+\r
+ buffer[bufferOffset++] = (byte) (l >>> 56);\r
+ buffer[bufferOffset++] = (byte) (l >>> 48);\r
+ buffer[bufferOffset++] = (byte) (l >>> 40);\r
+ buffer[bufferOffset++] = (byte) (l >>> 32);\r
+ buffer[bufferOffset++] = (byte) (l >>> 24);\r
+ buffer[bufferOffset++] = (byte) (l >>> 16);\r
+ buffer[bufferOffset++] = (byte) (l >>> 8);\r
+ buffer[bufferOffset++] = (byte) l;\r
+ }\r
+\r
+ /** Write a float value.\r
+ */\r
+ public void writeFloat(float f) throws IOException {\r
+ convertFromInt(Float.floatToIntBits(f));\r
+ }\r
+\r
+ /** Write a double value.\r
+ */\r
+ public void writeDouble(double d) throws IOException {\r
+ convertFromLong(Double.doubleToLongBits(d));\r
+ }\r
+\r
+ /** Write a string using the local protocol to convert char's to bytes.\r
+ *\r
+ * @param s The string to be written.\r
+ */\r
+ public void writeBytes(String s) throws IOException {\r
+ write(s.getBytes(), 0, s.length());\r
+ }\r
+\r
+ /** Write a string as an array of chars.\r
+ */\r
+ public void writeChars(String s) throws IOException {\r
+\r
+ int len = s.length();\r
+ for (int i = 0; i < len; i += 1) {\r
+ convertFromChar(s.charAt(i));\r
+ }\r
+ }\r
+\r
+ /** Write a string as a UTF.\r
+ */\r
+ public void writeUTF(String s) throws IOException {\r
+ flush();\r
+ raf.writeUTF(s);\r
+ fileOffset = raf.getFilePointer();\r
+ }\r
+\r
+ /** This routine provides efficient writing of arrays of any primitive type.\r
+ * The String class is also handled but it is an error to invoke this\r
+ * method with an object that is not an array of these types. If the\r
+ * array is multidimensional, then it calls itself recursively to write\r
+ * the entire array. Strings are written using the standard\r
+ * 1 byte format (i.e., as in writeBytes).\r
+ *\r
+ * If the array is an array of objects, then write will\r
+ * be called for each element of the array.\r
+ *\r
+ * @param o The object to be written. It must be an array of a primitive\r
+ * type, Object, or String.\r
+ */\r
+ public void writeArray(Object o) throws IOException {\r
+ String className = o.getClass().getName();\r
+\r
+ if (className.charAt(0) != '[') {\r
+ throw new IOException("Invalid object passed to BufferedFile.writeArray:" + className);\r
+ }\r
+\r
+ // Is this a multidimensional array? If so process recursively.\r
+ if (className.charAt(1) == '[') {\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ writeArray(((Object[]) o)[i]);\r
+ }\r
+ } else {\r
+\r
+ // This is a one-d array. Process it using our special functions.\r
+ switch (className.charAt(1)) {\r
+ case 'Z':\r
+ write((boolean[]) o, 0, ((boolean[]) o).length);\r
+ break;\r
+ case 'B':\r
+ write((byte[]) o, 0, ((byte[]) o).length);\r
+ break;\r
+ case 'C':\r
+ write((char[]) o, 0, ((char[]) o).length);\r
+ break;\r
+ case 'S':\r
+ write((short[]) o, 0, ((short[]) o).length);\r
+ break;\r
+ case 'I':\r
+ write((int[]) o, 0, ((int[]) o).length);\r
+ break;\r
+ case 'J':\r
+ write((long[]) o, 0, ((long[]) o).length);\r
+ break;\r
+ case 'F':\r
+ write((float[]) o, 0, ((float[]) o).length);\r
+ break;\r
+ case 'D':\r
+ write((double[]) o, 0, ((double[]) o).length);\r
+ break;\r
+ case 'L':\r
+\r
+ // Handle two exceptions: an array of strings, or an\r
+ // array of objects. .\r
+ if (className.equals("[Ljava.lang.String;")) {\r
+ write((String[]) o, 0, ((String[]) o).length);\r
+ } else if (className.equals("[Ljava.lang.Object;")) {\r
+ for (int i = 0; i < ((Object[]) o).length; i += 1) {\r
+ writeArray(((Object[]) o)[i]);\r
+ }\r
+ } else {\r
+ throw new IOException("Invalid object passed to BufferedFile.write: " + className);\r
+ }\r
+ break;\r
+ default:\r
+ throw new IOException("Invalid object passed to BufferedFile.write: " + className);\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ /** Write an array of booleans.\r
+ */\r
+ public void write(boolean[] b) throws IOException {\r
+ write(b, 0, b.length);\r
+ }\r
+\r
+ public void write(boolean[] b, int start, int length) throws IOException {\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromBoolean(b[i]);\r
+ }\r
+ }\r
+\r
+ /** Write an array of shorts.\r
+ */\r
+ public void write(short[] s) throws IOException {\r
+ write(s, 0, s.length);\r
+ }\r
+\r
+ public void write(short[] s, int start, int length) throws IOException {\r
+\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromShort(s[i]);\r
+ }\r
+ }\r
+\r
+ /** Write an array of char's.\r
+ */\r
+ public void write(char[] c) throws IOException {\r
+ write(c, 0, c.length);\r
+ }\r
+\r
+ public void write(char[] c, int start, int length) throws IOException {\r
+\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromChar(c[i]);\r
+ }\r
+ }\r
+\r
+ /** Write an array of int's.\r
+ */\r
+ public void write(int[] i) throws IOException {\r
+ write(i, 0, i.length);\r
+ }\r
+\r
+ public void write(int[] i, int start, int length) throws IOException {\r
+ for (int ii = start; ii < start + length; ii += 1) {\r
+ convertFromInt(i[ii]);\r
+ }\r
+ }\r
+\r
+ /** Write an array of longs.\r
+ */\r
+ public void write(long[] l) throws IOException {\r
+ write(l, 0, l.length);\r
+ }\r
+\r
+ public void write(long[] l, int start, int length) throws IOException {\r
+\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromLong(l[i]);\r
+ }\r
+ }\r
+\r
+ /** Write an array of floats.\r
+ */\r
+ public void write(float[] f) throws IOException {\r
+ write(f, 0, f.length);\r
+ }\r
+\r
+ public void write(float[] f, int start, int length) throws IOException {\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromInt(Float.floatToIntBits(f[i]));\r
+ }\r
+ }\r
+\r
+ /** Write an array of doubles.\r
+ */\r
+ public void write(double[] d) throws IOException {\r
+ write(d, 0, d.length);\r
+ }\r
+\r
+ public void write(double[] d, int start, int length) throws IOException {\r
+\r
+ for (int i = start; i < start + length; i += 1) {\r
+ convertFromLong(Double.doubleToLongBits(d[i]));\r
+ }\r
+ }\r
+\r
+ /** Write an array of Strings -- equivalent to calling writeBytes for each string.\r
+ */\r
+ public void write(String[] s) throws IOException {\r
+ write(s, 0, s.length);\r
+ }\r
+\r
+ public void write(String[] s, int start, int length) throws IOException {\r
+ for (int i = start; i < start + length; i += 1) {\r
+ writeBytes(s[i]);\r
+ }\r
+ }\r
+\r
+ /** Close the file */\r
+ public void close() throws IOException {\r
+ flush();\r
+ raf.close();\r
+ }\r
+\r
+ /** Get the file descriptor associated with\r
+ * this stream. Note that this returns the file\r
+ * descriptor of the associated RandomAccessFile.\r
+ */\r
+ public FileDescriptor getFD() throws IOException {\r
+ return raf.getFD();\r
+ }\r
+\r
+ /** Get the channel associated with\r
+ * this file. Note that this returns the channel\r
+ * of the associated RandomAccessFile.\r
+ * Note that since the BufferedFile buffers the I/O's to the\r
+ * underlying file, the offset of the channel may be\r
+ * different than the offset of the BufferedFile. This\r
+ * is different than for a RandomAccessFile where the\r
+ * offsets are guaranteed to be the same.\r
+ */\r
+ public java.nio.channels.FileChannel getChannel() {\r
+ return raf.getChannel();\r
+ }\r
+\r
+ /** Get the current length of the file.\r
+ */\r
+ public long length() throws IOException {\r
+ flush();\r
+ return raf.length();\r
+ }\r
+\r
+ /** Get the current offset into the file.\r
+ */\r
+ public long getFilePointer() {\r
+ return fileOffset + bufferOffset;\r
+ }\r
+\r
+ /** Set the length of the file. This method calls\r
+ * the method of the same name in RandomAccessFile which\r
+ * is only available in JDK1.2 and greater. This method\r
+ * may be deleted for compilation with earlier versions.\r
+ *\r
+ * @param newLength The number of bytes at which the file\r
+ * is set.\r
+ *\r
+ */\r
+ public void setLength(long newLength) throws IOException {\r
+\r
+ flush();\r
+ raf.setLength(newLength);\r
+ if (newLength < fileOffset) {\r
+ fileOffset = newLength;\r
+ }\r
+\r
+ }\r
+}\r
--- /dev/null
+package nom.tam.util;
+
+/** This class provides mechanisms for
+ * efficiently formatting numbers and Strings.
+ * Data is appended to existing byte arrays. Note
+ * that the formatting of real or double values
+ * may differ slightly (in the last bit) from
+ * the standard Java packages since this routines
+ * are optimized for speed rather than accuracy.
+ * <p>
+ * The methods in this class create no objects.
+ * <p>
+ * If a number cannot fit into the requested space
+ * the truncateOnOverlow flag controls whether the
+ * formatter will attempt to append it using the
+ * available length in the output (a la C or Perl style
+ * formats). If this flag is set, or if the number
+ * cannot fit into space left in the buffer it is 'truncated'
+ * and the requested space is filled with a truncation fill
+ * character. A TruncationException may be thrown if the truncationThrow
+ * flag is set.
+ * <p>
+ * This class does not explicitly support separate methods
+ * for formatting reals in exponential notation. Real numbers
+ * near one are by default formatted in decimal notation while
+ * numbers with large (or very negative) exponents are formatted
+ * in exponential notation. By setting the limits at which these
+ * transitions take place the user can force either exponential or
+ * decimal notation.
+ *
+ */
+public final class ByteFormatter {
+
+ /** Internal buffers used in formatting fields */
+ private byte[] tbuf1 = new byte[32];
+ private byte[] tbuf2 = new byte[32];
+ private static final double ilog10 = 1. / Math.log(10);
+ /** Should we truncate overflows or just run over limit */
+ private boolean truncateOnOverflow = true;
+ /** What do we use to fill when we cannot print the number? */
+ private byte truncationFill = (byte) '*'; // Default is often used in Fortran
+ /** Throw exception on truncations */
+ private boolean truncationThrow = true;
+ /** Should we right align? */
+ private boolean align = false;
+ /** Minimum magnitude to print in non-scientific notation. */
+ double simpleMin = 1.e-3;
+ /** Maximum magnitude to print in non-scientific notation. */
+ double simpleMax = 1.e6;
+ /** Powers of 10. We overextend on both sides.
+ * These should perhaps be tabulated rather than
+ * computed though it may be faster to calculate
+ * them than to read in the extra bytes in the class file.
+ */
+ private static final double tenpow[];
+ /** What index of tenpow is 10^0 */
+ private static final int zeropow;
+
+ static { // Static initializer
+
+ int min = (int) Math.floor((int) (Math.log(Double.MIN_VALUE) * ilog10));
+ int max = (int) Math.floor((int) (Math.log(Double.MAX_VALUE) * ilog10));
+ max += 1;
+
+ tenpow = new double[(max - min) + 1];
+
+
+ for (int i = 0; i < tenpow.length; i += 1) {
+ tenpow[i] = Math.pow(10, i + min);
+ }
+ zeropow = -min;
+ }
+ /** Digits. We could handle other bases
+ * by extending or truncating this list and changing
+ * the division by 10 (and it's factors) at various
+ * locations.
+ */
+ private static final byte[] digits = {
+ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4',
+ (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9'};
+
+ /** Set the truncation behavior.
+ * @param val If set to true (the default) then do not
+ * exceed the requested length. If a number cannot
+ * be sensibly formatted, the truncation fill character
+ * may be inserted.
+ */
+ public void setTruncateOnOverflow(boolean val) {
+ truncateOnOverflow = val;
+ }
+
+ /** Should truncations cause a truncation overflow? */
+ public void setTruncationThrow(boolean throwException) {
+ truncationThrow = throwException;
+ }
+
+ /** Set the truncation fill character.
+ * @param val The character to be used in subsequent truncations.
+ */
+ public void setTruncationFill(char val) {
+ truncationFill = (byte) val;
+ }
+
+ /** Set the alignment flag.
+ * @param val Should numbers be right aligned?
+ */
+ public void setAlign(boolean val) {
+ align = val;
+ }
+
+ /** Set the range of real numbers that will be formatted in
+ * non-scientific notation, i.e., .00001 rather than 1.0e-5.
+ * The sign of the number is ignored.
+ * @param min The minimum value for non-scientific notation.
+ * @param max The maximum value for non-scientific notation.
+ */
+ public void setSimpleRange(double min, double max) {
+ simpleMin = min;
+ simpleMax = max;
+ }
+
+ /** Format an int into an array.
+ * @param val The int to be formatted.
+ * @param array The array in which to place the result.
+ * @return The number of characters used.
+ */
+ public int format(int val, byte[] array) throws TruncationException {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Format an int into an existing array.
+ * @param val Integer to be formatted
+ * @param buf Buffer in which result is to be stored
+ * @param off Offset within buffer
+ * @param len Maximum length of integer
+ * @return offset of next unused character in input buffer.
+ */
+ public int format(int val, byte[] buf,
+ int off, int len) throws TruncationException {
+
+ // Special case
+ if (val == Integer.MIN_VALUE) {
+ if (len > 10 || (!truncateOnOverflow && buf.length - off > 10)) {
+ return format("-2147483648", buf, off, len);
+ } else {
+ truncationFiller(buf, off, len);
+ return off + len;
+ }
+ }
+
+ int pos = Math.abs(val);
+
+ // First count the number of characters in the result.
+ // Otherwise we need to use an intermediary buffer.
+
+ int ndig = 1;
+ int dmax = 10;
+
+ while (ndig < 10 && pos >= dmax) {
+ ndig += 1;
+ dmax *= 10;
+ }
+
+ if (val < 0) {
+ ndig += 1;
+ }
+
+ // Truncate if necessary.
+ if ((truncateOnOverflow && ndig > len) || ndig > buf.length - off) {
+ truncationFiller(buf, off, len);
+ return off + len;
+ }
+
+ // Right justify if requested.
+ if (align) {
+ off = alignFill(buf, off, len - ndig);
+ }
+
+ // Now insert the actual characters we want -- backwards
+ // We use a do{} while() to handle the case of 0.
+
+ off += ndig;
+
+ int xoff = off - 1;
+ do {
+ buf[xoff] = digits[pos % 10];
+ xoff -= 1;
+ pos /= 10;
+ } while (pos > 0);
+
+ if (val < 0) {
+ buf[xoff] = (byte) '-';
+ }
+
+ return off;
+ }
+
+ /** Format a long into an array.
+ * @param val The long to be formatted.
+ * @param array The array in which to place the result.
+ * @return The number of characters used.
+ */
+ public int format(long val, byte[] array) throws TruncationException {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Format a long into an existing array.
+ * @param val Long to be formatted
+ * @param buf Buffer in which result is to be stored
+ * @param off Offset within buffer
+ * @param len Maximum length of integer
+ * @return offset of next unused character in input buffer.
+ */
+ public int format(long val, byte[] buf,
+ int off, int len) throws TruncationException {
+
+ // Special case
+ if (val == Long.MIN_VALUE) {
+ if (len > 19 || (!truncateOnOverflow && buf.length - off > 19)) {
+ return format("-9223372036854775808", buf, off, len);
+ } else {
+ truncationFiller(buf, off, len);
+ return off + len;
+ }
+ }
+
+ long pos = Math.abs(val);
+
+ // First count the number of characters in the result.
+ // Otherwise we need to use an intermediary buffer.
+
+ int ndig = 1;
+ long dmax = 10;
+
+ // Might be faster to try to do this partially in ints
+ while (ndig < 19 && pos >= dmax) {
+
+ ndig += 1;
+ dmax *= 10;
+ }
+
+ if (val < 0) {
+ ndig += 1;
+ }
+
+ // Truncate if necessary.
+
+ if ((truncateOnOverflow && ndig > len) || ndig > buf.length - off) {
+ truncationFiller(buf, off, len);
+ return off + len;
+ }
+
+ // Right justify if requested.
+ if (align) {
+ off = alignFill(buf, off, len - ndig);
+ }
+
+ // Now insert the actual characters we want -- backwards.
+
+
+ off += ndig;
+ int xoff = off - 1;
+
+ buf[xoff] = (byte) '0';
+ boolean last = (pos == 0);
+
+ while (!last) {
+
+ // Work on ints rather than longs.
+
+ int giga = (int) (pos % 1000000000L);
+ pos /= 1000000000L;
+
+ last = (pos == 0);
+
+ for (int i = 0; i < 9; i += 1) {
+
+ buf[xoff] = digits[giga % 10];
+ xoff -= 1;
+ giga /= 10;
+ if (last && giga == 0) {
+ break;
+ }
+ }
+ }
+
+
+ if (val < 0) {
+ buf[xoff] = (byte) '-';
+ }
+
+ return off;
+ }
+
+ /** Format a boolean into an existing array.
+ */
+ public int format(boolean val, byte[] array) {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Format a boolean into an existing array
+ * @param val The boolean to be formatted
+ * @param array The buffer in which to format the data.
+ * @param off The starting offset within the buffer.
+ * @param len The maximum number of characters to use
+ * use in formatting the number.
+ * @return Offset of next available character in buffer.
+ */
+ public int format(boolean val, byte[] array, int off,
+ int len) {
+ if (align && len > 1) {
+ off = alignFill(array, off, len - 1);
+ }
+
+ if (len > 0) {
+ if (val) {
+ array[off] = (byte) 'T';
+ } else {
+ array[off] = (byte) 'F';
+ }
+ off += 1;
+ }
+ return off;
+ }
+
+ /** Insert a string at the beginning of an array */
+ public int format(String val, byte[] array) {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Insert a String into an existing character array.
+ * If the String is longer than len, then only the
+ * the initial len characters will be inserted.
+ * @param val The string to be inserted. A null string
+ * will insert len spaces.
+ * @param array The buffer in which to insert the string.
+ * @param off The starting offset to insert the string.
+ * @param len The maximum number of characters to insert.
+ * @return Offset of next available character in buffer.
+ */
+ public int format(String val, byte[] array, int off, int len) {
+
+ if (val == null) {
+ for (int i = 0; i < len; i += 1) {
+ array[off + i] = (byte) ' ';
+ }
+ return off + len;
+ }
+
+ int slen = val.length();
+
+ if ((truncateOnOverflow && slen > len) || (slen > array.length - off)) {
+ val = val.substring(0, len);
+ slen = len;
+ }
+
+ if (align && (len > slen)) {
+ off = alignFill(array, off, len - slen);
+ }
+
+ /** We should probably require ASCII here, but for the nonce we do not [TAM 5/11] */
+ System.arraycopy(val.getBytes(), 0, array, off, slen);
+ return off + slen;
+ }
+
+ /** Format a float into an array.
+ * @param val The float to be formatted.
+ * @param array The array in which to place the result.
+ * @return The number of characters used.
+ */
+ public int format(float val, byte[] array) throws TruncationException {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Format a float into an existing byteacter array.
+ * <p>
+ * This is hard to do exactly right... The JDK code does
+ * stuff with rational arithmetic and so forth.
+ * We use a much simpler algorithm which may give
+ * an answer off in the lowest order bit.
+ * Since this is pure Java, it should still be consistent
+ * from machine to machine.
+ * <p>
+ * Recall that the binary representation of
+ * the float is of the form <tt>d = 0.bbbbbbbb x 2<sup>n</sup></tt>
+ * where there are up to 24 binary digits in the binary
+ * fraction (including the assumed leading 1 bit
+ * for normalized numbers).
+ * We find a value m such that <tt>10<sup>m</su> d</tt> is between
+ * <tt>2<sup>24</sup></tt> and <tt>>2<sup>32</sup></tt>.
+ * This product will be exactly convertible to an int
+ * with no loss of precision. Getting the
+ * decimal representation for that is trivial (see formatInteger).
+ * This is a decimal mantissa and we have an exponent (<tt>-m</tt>).
+ * All we have to do is manipulate the decimal point
+ * to where we want to see it. Errors can
+ * arise due to roundoff in the scaling multiplication, but
+ * should be very small.
+ *
+ * @param val Float to be formatted
+ * @param buf Buffer in which result is to be stored
+ * @param off Offset within buffer
+ * @param len Maximum length of field
+ * @return Offset of next character in buffer.
+ */
+ public int format(float val, byte[] buf,
+ int off, int len) throws TruncationException {
+
+ float pos = (float) Math.abs(val);
+
+ int minlen, actlen;
+
+ // Special cases
+ if (pos == 0.) {
+ return format("0.0", buf, off, len);
+ } else if (Float.isNaN(val)) {
+ return format("NaN", buf, off, len);
+ } else if (Float.isInfinite(val)) {
+ if (val > 0) {
+ return format("Infinity", buf, off, len);
+ } else {
+ return format("-Infinity", buf, off, len);
+ }
+ }
+
+ int power = (int) Math.floor((Math.log(pos) * ilog10));
+ int shift = 8 - power;
+ float scale;
+ float scale2 = 1;
+
+ // Scale the number so that we get a number ~ n x 10^8.
+ if (shift < 30) {
+ scale = (float) tenpow[shift + zeropow];
+ } else {
+ // Can get overflow if the original number is
+ // very small, so we break out the shift
+ // into two multipliers.
+ scale2 = (float) tenpow[30 + zeropow];
+ scale = (float) tenpow[shift - 30 + zeropow];
+ }
+
+
+ pos = (pos * scale) * scale2;
+
+ // Parse the float bits.
+
+ int bits = Float.floatToIntBits(pos);
+
+ // The exponent should be a little more than 23
+ int exp = ((bits & 0x7F800000) >> 23) - 127;
+
+ int numb = (bits & 0x007FFFFF);
+
+ if (exp > -127) {
+ // Normalized....
+ numb |= (0x00800000);
+ } else {
+ // Denormalized
+ exp += 1;
+ }
+
+
+ // Multiple this number by the excess of the exponent
+ // over 24. This completes the conversion of float to int
+ // (<<= did not work on Alpha TruUnix)
+
+ numb = numb << (exp - 23L);
+
+ // Get a decimal mantissa.
+ boolean oldAlign = align;
+ align = false;
+ int ndig = format(numb, tbuf1, 0, 32);
+ align = oldAlign;
+
+
+ // Now format the float.
+
+ return combineReal(val, buf, off, len, tbuf1, ndig, shift);
+ }
+
+ /** Format a double into an array.
+ * @param val The double to be formatted.
+ * @param array The array in which to place the result.
+ * @return The number of characters used.
+ */
+ public int format(double val, byte[] array) throws TruncationException {
+ return format(val, array, 0, array.length);
+ }
+
+ /** Format a double into an existing character array.
+ * <p>
+ * This is hard to do exactly right... The JDK code does
+ * stuff with rational arithmetic and so forth.
+ * We use a much simpler algorithm which may give
+ * an answer off in the lowest order bit.
+ * Since this is pure Java, it should still be consistent
+ * from machine to machine.
+ * <p>
+ * Recall that the binary representation of
+ * the double is of the form <tt>d = 0.bbbbbbbb x 2<sup>n</sup></tt>
+ * where there are up to 53 binary digits in the binary
+ * fraction (including the assumed leading 1 bit
+ * for normalized numbers).
+ * We find a value m such that <tt>10<sup>m</su> d</tt> is between
+ * <tt>2<sup>53</sup></tt> and <tt>>2<sup>63</sup></tt>.
+ * This product will be exactly convertible to a long
+ * with no loss of precision. Getting the
+ * decimal representation for that is trivial (see formatLong).
+ * This is a decimal mantissa and we have an exponent (<tt>-m</tt>).
+ * All we have to do is manipulate the decimal point
+ * to where we want to see it. Errors can
+ * arise due to roundoff in the scaling multiplication, but
+ * should be no more than a single bit.
+ *
+ * @param val Double to be formatted
+ * @param buf Buffer in which result is to be stored
+ * @param off Offset within buffer
+ * @param len Maximum length of integer
+ * @return offset of next unused character in input buffer.
+ */
+ public int format(double val, byte[] buf,
+ int off, int len) throws TruncationException {
+
+ double pos = Math.abs(val);
+
+ int minlen, actlen;
+
+ // Special cases -- It is OK if these get truncated.
+ if (pos == 0.) {
+ return format("0.0", buf, off, len);
+ } else if (Double.isNaN(val)) {
+ return format("NaN", buf, off, len);
+ } else if (Double.isInfinite(val)) {
+ if (val > 0) {
+ return format("Infinity", buf, off, len);
+ } else {
+ return format("-Infinity", buf, off, len);
+ }
+ }
+
+ int power = (int) (Math.log(pos) * ilog10);
+ int shift = 17 - power;
+ double scale;
+ double scale2 = 1;
+
+ // Scale the number so that we get a number ~ n x 10^17.
+ if (shift < 200) {
+ scale = tenpow[shift + zeropow];
+ } else {
+ // Can get overflow if the original number is
+ // very small, so we break out the shift
+ // into two multipliers.
+ scale2 = tenpow[200 + zeropow];
+ scale = tenpow[shift - 200 + zeropow];
+ }
+
+
+ pos = (pos * scale) * scale2;
+
+ // Parse the double bits.
+
+ long bits = Double.doubleToLongBits(pos);
+
+ // The exponent should be a little more than 52.
+ int exp = (int) (((bits & 0x7FF0000000000000L) >> 52) - 1023);
+
+ long numb = (bits & 0x000FFFFFFFFFFFFFL);
+
+ if (exp > -1023) {
+ // Normalized....
+ numb |= (0x0010000000000000L);
+ } else {
+ // Denormalized
+ exp += 1;
+ }
+
+
+ // Multiple this number by the excess of the exponent
+ // over 52. This completes the conversion of double to long.
+ numb = numb << (exp - 52);
+
+ // Get a decimal mantissa.
+ boolean oldAlign = align;
+ align = false;
+ int ndig = format(numb, tbuf1, 0, 32);
+ align = oldAlign;
+
+ // Now format the double.
+
+ return combineReal(val, buf, off, len, tbuf1, ndig, shift);
+ }
+
+ /** This method formats a double given
+ * a decimal mantissa and exponent information.
+ * @param val The original number
+ * @param buf Output buffer
+ * @param off Offset into buffer
+ * @param len Maximum number of characters to use in buffer.
+ * @param mant A decimal mantissa for the number.
+ * @param lmant The number of characters in the mantissa
+ * @param shift The exponent of the power of 10 that
+ * we shifted val to get the given mantissa.
+ * @return Offset of next available character in buffer.
+ */
+ int combineReal(double val, byte[] buf, int off, int len,
+ byte[] mant, int lmant, int shift) throws TruncationException {
+
+ // First get the minimum size for the number
+
+ double pos = Math.abs(val);
+ boolean simple = false;
+ int minSize;
+ int maxSize;
+
+ if (pos >= simpleMin && pos <= simpleMax) {
+ simple = true;
+ }
+
+ int exp = lmant - shift - 1;
+ int lexp = 0;
+
+ if (!simple) {
+
+ boolean oldAlign = align;
+ align = false;
+ lexp = format(exp, tbuf2, 0, 32);
+ align = oldAlign;
+
+ minSize = lexp + 2; // e.g., 2e-12
+ maxSize = lexp + lmant + 2; // add in "." and e
+ } else {
+ if (exp >= 0) {
+ minSize = exp + 1; // e.g. 32
+
+ // Special case. E.g., 99.9 has
+ // minumum size of 3.
+ int i;
+ for (i = 0; i < lmant && i <= exp; i += 1) {
+ if (mant[i] != (byte) '9') {
+ break;
+ }
+ }
+ if (i > exp && i < lmant && mant[i] >= (byte) '5') {
+ minSize += 1;
+ }
+
+ maxSize = lmant + 1; // Add in "."
+ if (maxSize <= minSize) { // Very large numbers.
+ maxSize = minSize + 1;
+ }
+ } else {
+ minSize = 2;
+ maxSize = 1 + Math.abs(exp) + lmant;
+ }
+ }
+ if (val < 0) {
+ minSize += 1;
+ maxSize += 1;
+ }
+
+ // Can the number fit?
+ if ((truncateOnOverflow && minSize > len)
+ || (minSize > buf.length - off)) {
+ truncationFiller(buf, off, len);
+ return off + len;
+ }
+
+ // Do we need to align it?
+ if (maxSize < len && align) {
+ int nal = len - maxSize;
+ off = alignFill(buf, off, nal);
+ len -= nal;
+ }
+
+
+ int off0 = off;
+
+ // Now begin filling in the buffer.
+ if (val < 0) {
+ buf[off] = (byte) '-';
+ off += 1;
+ len -= 1;
+ }
+
+
+ if (simple) {
+ return Math.abs(mantissa(mant, lmant, exp, simple, buf, off, len));
+ } else {
+ off = mantissa(mant, lmant, 0, simple, buf, off, len - lexp - 1);
+ if (off < 0) {
+ off = -off;
+ len -= off;
+ // Handle the expanded exponent by filling
+ if (exp == 9 || exp == 99) {
+ // Cannot fit...
+ if (off + len == minSize) {
+ truncationFiller(buf, off, len);
+ return off + len;
+ } else {
+ // Steal a character from the mantissa.
+ off -= 1;
+ }
+ }
+ exp += 1;
+ lexp = format(exp, tbuf2, 0, 32);
+ }
+ buf[off] = (byte) 'E';
+ off += 1;
+ System.arraycopy(tbuf2, 0, buf, off, lexp);
+ return off + lexp;
+ }
+ }
+
+ /** Write the mantissa of the number. This method addresses
+ * the subtleties involved in rounding numbers.
+ */
+ int mantissa(byte[] mant, int lmant, int exp, boolean simple,
+ byte[] buf, int off, int len) {
+
+ // Save in case we need to extend the number.
+ int off0 = off;
+ int pos = 0;
+
+ if (exp < 0) {
+ buf[off] = (byte) '0';
+ len -= 1;
+ off += 1;
+ if (len > 0) {
+ buf[off] = (byte) '.';
+ off += 1;
+ len -= 1;
+ }
+ // Leading 0s in small numbers.
+ int cexp = exp;
+ while (cexp < -1 && len > 0) {
+ buf[off] = (byte) '0';
+ cexp += 1;
+ off += 1;
+ len -= 1;
+ }
+
+ } else {
+
+ // Print out all digits to the left of the decimal.
+ while (exp >= 0 && pos < lmant) {
+ buf[off] = mant[pos];
+ off += 1;
+ pos += 1;
+ len -= 1;
+ exp -= 1;
+ }
+ // Trust we have enough space for this.
+ for (int i = 0; i <= exp; i += 1) {
+ buf[off] = (byte) '0';
+ off += 1;
+ len -= 1;
+ }
+
+ // Add in a decimal if we have space.
+ if (len > 0) {
+ buf[off] = (byte) '.';
+ len -= 1;
+ off += 1;
+ }
+ }
+
+ // Now handle the digits to the right of the decimal.
+ while (len > 0 && pos < lmant) {
+ buf[off] = mant[pos];
+ off += 1;
+ exp -= 1;
+ len -= 1;
+ pos += 1;
+ }
+
+ // Now handle rounding.
+
+ if (pos < lmant && mant[pos] >= (byte) '5') {
+ int i;
+
+ // Increment to the left until we find a non-9
+ for (i = off - 1; i >= off0; i -= 1) {
+
+
+ if (buf[i] == (byte) '.' || buf[i] == (byte) '-') {
+ continue;
+ }
+ if (buf[i] == (byte) '9') {
+ buf[i] = (byte) '0';
+ } else {
+ buf[i] += 1;
+ break;
+ }
+ }
+
+ // Now we handle 99.99 case. This can cause problems
+ // in two cases. If we are not using scientific notation
+ // then we may want to convert 99.9 to 100., i.e.,
+ // we need to move the decimal point. If there is no
+ // decimal point, then we must not be truncating on overflow
+ // but we should be allowed to write it to the
+ // next character (i.e., we are not at the end of buf).
+ //
+ // If we are printing in scientific notation, then we want
+ // to convert 9.99 to 1.00, i.e. we do not move the decimal.
+ // However we need to signal that the exponent should be
+ // incremented by one.
+ //
+ // We cannot have aligned the number, since that requires
+ // the full precision number to fit within the requested
+ // length, and we would have printed out the entire
+ // mantissa (i.e., pos >= lmant)
+
+ if (i < off0) {
+
+ buf[off0] = (byte) '1';
+ boolean foundDecimal = false;
+ for (i = off0 + 1; i < off; i += 1) {
+ if (buf[i] == (byte) '.') {
+ foundDecimal = true;
+ if (simple) {
+ buf[i] = (byte) '0';
+ i += 1;
+ if (i < off) {
+ buf[i] = (byte) '.';
+ }
+ }
+ break;
+ }
+ }
+ if (simple && !foundDecimal) {
+ buf[off + 1] = (byte) '0'; // 99 went to 100
+ off += 1;
+ }
+
+ off = -off; // Signal to change exponent if necessary.
+ }
+
+ }
+
+ return off;
+ }
+
+ /** Fill the buffer with truncation characters. After filling
+ * the buffer, a TruncationException will be thrown if the
+ * appropriate flag is set.
+ */
+ void truncationFiller(byte[] buffer, int offset, int length)
+ throws TruncationException {
+
+ for (int i = offset; i < offset + length; i += 1) {
+ buffer[i] = truncationFill;
+ }
+ if (truncationThrow) {
+ throw new TruncationException();
+ }
+ return;
+ }
+
+ /** Fill the buffer with blanks to align
+ * a field.
+ */
+ public int alignFill(byte[] buffer, int offset, int len) {
+ for (int i = offset; i < offset + len; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+ return offset + len;
+ }
+}
--- /dev/null
+package nom.tam.util;\r
+\r
+import java.io.UnsupportedEncodingException;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+/** This class provides routines\r
+ * for efficient parsing of data stored in a byte array.\r
+ * This routine is optimized (in theory at least!) for efficiency\r
+ * rather than accuracy. The values read in for doubles or floats\r
+ * may differ in the last bit or so from the standard input\r
+ * utilities, especially in the case where a float is specified\r
+ * as a very long string of digits (substantially longer than\r
+ * the precision of the type).\r
+ * <p>\r
+ * The get methods generally are available with or without a length\r
+ * parameter specified. When a length parameter is specified only\r
+ * the bytes with the specified range from the current offset will\r
+ * be search for the number. If no length is specified, the entire\r
+ * buffer from the current offset will be searched.\r
+ * <p>\r
+ * The getString method returns a string with leading and trailing\r
+ * white space left intact. For all other get calls, leading\r
+ * white space is ignored. If fillFields is set, then the get\r
+ * methods check that only white space follows valid data and a\r
+ * FormatException is thrown if that is not the case. If\r
+ * fillFields is not set and valid data is found, then the\r
+ * methods return having read as much as possible. E.g., for\r
+ * the sequence "T123.258E13", a getBoolean, getInteger and\r
+ * getFloat call would return true, 123, and 2.58e12 when\r
+ * called in succession.\r
+ *\r
+ */\r
+public class ByteParser {\r
+\r
+ /** Array being parsed */\r
+ private byte[] input;\r
+ /** Current offset into input. */\r
+ private int offset;\r
+ /** Length of last parsed value */\r
+ private int numberLength;\r
+ /** Did we find a sign last time we checked? */\r
+ private boolean foundSign;\r
+ /** Do we fill up fields? */\r
+ private boolean fillFields = false;\r
+\r
+ /** Construct a parser.\r
+ * @param input The byte array to be parsed.\r
+ * Note that the array can be re-used by\r
+ * refilling its contents and resetting the offset.\r
+ */\r
+ public ByteParser(byte[] input) {\r
+ this.input = input;\r
+ this.offset = 0;\r
+ }\r
+\r
+ /** Set the buffer for the parser */\r
+ public void setBuffer(byte[] buf) {\r
+ this.input = buf;\r
+ this.offset = 0;\r
+ }\r
+\r
+ /** Get the buffer being used by the parser */\r
+ public byte[] getBuffer() {\r
+ return input;\r
+ }\r
+\r
+ /** Set the offset into the array.\r
+ * @param offset The desired offset from the beginning\r
+ * of the array.\r
+ */\r
+ public void setOffset(int offset) {\r
+ this.offset = offset;\r
+ }\r
+\r
+ /** Do we require a field to completely fill up the specified\r
+ * length (with optional leading and trailing white space.\r
+ @param flag Is filling required?\r
+ */\r
+ public void setFillFields(boolean flag) {\r
+ fillFields = flag;\r
+ }\r
+\r
+ /** Get the current offset\r
+ @return The current offset within the buffer.\r
+ */\r
+ public int getOffset() {\r
+ return offset;\r
+ }\r
+\r
+ /** Get the number of characters used to parse the previous\r
+ * number (or the length of the previous String returned).\r
+ */\r
+ public int getNumberLength() {\r
+ return numberLength;\r
+ }\r
+\r
+ /** Read in the buffer until a double is read. This will read\r
+ * the entire buffer if fillFields is set.\r
+ * @return The value found.\r
+ */\r
+ public double getDouble() throws FormatException {\r
+ return getDouble(input.length - offset);\r
+ }\r
+\r
+ /** Look for a double in the buffer.\r
+ * Leading spaces are ignored.\r
+ * @param length The maximum number of characters\r
+ * used to parse this number. If fillFields\r
+ * is specified then exactly only whitespace may follow\r
+ * a valid double value.\r
+ */\r
+ public double getDouble(int length) throws FormatException {\r
+\r
+ int startOffset = offset;\r
+\r
+ boolean error = true;\r
+\r
+ double number = 0;\r
+ int i = 0;\r
+\r
+ // Skip initial blanks.\r
+ length -= skipWhite(length);\r
+\r
+ if (length == 0) {\r
+ numberLength = offset - startOffset;\r
+ return 0;\r
+ }\r
+\r
+ double mantissaSign = checkSign();\r
+ if (foundSign) {\r
+ length -= 1;\r
+ }\r
+\r
+ // Look for the special strings NaN, Inf,\r
+ if (length >= 3\r
+ && (input[offset] == 'n' || input[offset] == 'N')\r
+ && (input[offset + 1] == 'a' || input[offset + 1] == 'A')\r
+ && (input[offset + 2] == 'n' || input[offset + 2] == 'N')) {\r
+\r
+ number = Double.NaN;\r
+ length -= 3;\r
+ offset += 3;\r
+\r
+ // Look for the longer string first then try the shorter.\r
+ } else if (length >= 8\r
+ && (input[offset] == 'i' || input[offset] == 'I')\r
+ && (input[offset + 1] == 'n' || input[offset + 1] == 'N')\r
+ && (input[offset + 2] == 'f' || input[offset + 2] == 'F')\r
+ && (input[offset + 3] == 'i' || input[offset + 3] == 'I')\r
+ && (input[offset + 4] == 'n' || input[offset + 4] == 'N')\r
+ && (input[offset + 5] == 'i' || input[offset + 5] == 'I')\r
+ && (input[offset + 6] == 't' || input[offset + 6] == 'T')\r
+ && (input[offset + 7] == 'y' || input[offset + 7] == 'Y')) {\r
+ number = Double.POSITIVE_INFINITY;\r
+ length -= 8;\r
+ offset += 8;\r
+\r
+ } else if (length >= 3\r
+ && (input[offset] == 'i' || input[offset] == 'I')\r
+ && (input[offset + 1] == 'n' || input[offset + 1] == 'N')\r
+ && (input[offset + 2] == 'f' || input[offset + 2] == 'F')) {\r
+ number = Double.POSITIVE_INFINITY;\r
+ length -= 3;\r
+ offset += 3;\r
+\r
+ } else {\r
+\r
+ number = getBareInteger(length); // This will update offset\r
+ length -= numberLength; // Set by getBareInteger\r
+\r
+ if (numberLength > 0) {\r
+ error = false;\r
+ }\r
+\r
+ // Check for fractional values after decimal\r
+ if (length > 0 && input[offset] == '.') {\r
+\r
+ offset += 1;\r
+ length -= 1;\r
+\r
+ double numerator = getBareInteger(length);\r
+ if (numerator > 0) {\r
+ number += numerator / Math.pow(10., numberLength);\r
+ }\r
+ length -= numberLength;\r
+ if (numberLength > 0) {\r
+ error = false;\r
+ }\r
+ }\r
+\r
+ if (error) {\r
+ offset = startOffset;\r
+ numberLength = 0;\r
+ throw new FormatException("Invalid real field");\r
+ }\r
+\r
+ // Look for an exponent\r
+ if (length > 0) {\r
+\r
+ // Our Fortran heritage means that we allow 'D' for the exponent indicator.\r
+ if (input[offset] == 'e' || input[offset] == 'E'\r
+ || input[offset] == 'd' || input[offset] == 'D') {\r
+\r
+ offset += 1;\r
+ length -= 1;\r
+ if (length > 0) {\r
+ int sign = checkSign();\r
+ if (foundSign) {\r
+ length -= 1;\r
+ }\r
+\r
+ int exponent = (int) getBareInteger(length);\r
+\r
+ // For very small numbers we try to miminize\r
+ // effects of denormalization.\r
+ if (exponent * sign > -300) {\r
+ number *= Math.pow(10., exponent * sign);\r
+ } else {\r
+ number = 1.e-300 * (number * Math.pow(10., exponent * sign + 300));\r
+ }\r
+ length -= numberLength;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (fillFields && length > 0) {\r
+\r
+ if (isWhite(length)) {\r
+ offset += length;\r
+ } else {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Non-blanks following real.");\r
+ }\r
+ }\r
+\r
+ numberLength = offset - startOffset;\r
+ return mantissaSign * number;\r
+ }\r
+\r
+ /** Get a floating point value from the buffer. (see getDouble(int())\r
+ */\r
+ public float getFloat() throws FormatException {\r
+ return (float) getDouble(input.length - offset);\r
+ }\r
+\r
+ /** Get a floating point value in a region of the buffer */\r
+ public float getFloat(int length) throws FormatException {\r
+ return (float) getDouble(length);\r
+ }\r
+\r
+ /** Convert a region of the buffer to an integer */\r
+ public int getInt(int length) throws FormatException {\r
+ int startOffset = offset;\r
+\r
+ length -= skipWhite(length);\r
+ if (length == 0) {\r
+ numberLength = offset - startOffset;\r
+ return 0;\r
+ }\r
+\r
+ int number = 0;\r
+ boolean error = true;\r
+\r
+ int sign = checkSign();\r
+ if (foundSign) {\r
+ length -= 1;\r
+ }\r
+\r
+ while (length > 0 && input[offset] >= '0' && input[offset] <= '9') {\r
+ number = number * 10 + input[offset] - '0';\r
+ offset += 1;\r
+ length -= 1;\r
+ error = false;\r
+ }\r
+\r
+ if (error) {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Invalid Integer");\r
+ }\r
+\r
+ if (length > 0 && fillFields) {\r
+ if (isWhite(length)) {\r
+ offset += length;\r
+ } else {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Non-white following integer");\r
+ }\r
+ }\r
+\r
+ numberLength = offset - startOffset;\r
+ return sign * number;\r
+ }\r
+\r
+ /** Look for an integer at the beginning of the buffer */\r
+ public int getInt() throws FormatException {\r
+ return getInt(input.length - offset);\r
+ }\r
+\r
+ /** Look for a long in a specified region of the buffer */\r
+ public long getLong(int length) throws FormatException {\r
+\r
+ int startOffset = offset;\r
+\r
+ // Skip white space.\r
+ length -= skipWhite(length);\r
+ if (length == 0) {\r
+ numberLength = offset - startOffset;\r
+ return 0;\r
+ }\r
+\r
+ long number = 0;\r
+ boolean error = true;\r
+\r
+ long sign = checkSign();\r
+ if (foundSign) {\r
+ length -= 1;\r
+ }\r
+\r
+ while (length > 0 && input[offset] >= '0' && input[offset] <= '9') {\r
+ number = number * 10 + input[offset] - '0';\r
+ error = false;\r
+ offset += 1;\r
+ length -= 1;\r
+ }\r
+\r
+ if (error) {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Invalid long number");\r
+ }\r
+\r
+ if (length > 0 && fillFields) {\r
+ if (isWhite(length)) {\r
+ offset += length;\r
+ } else {\r
+ offset = startOffset;\r
+ numberLength = 0;\r
+ throw new FormatException("Non-white following long");\r
+ }\r
+ }\r
+ numberLength = offset - startOffset;\r
+ return sign * number;\r
+ }\r
+\r
+ /** Get a string\r
+ * @param length The length of the string.\r
+ */\r
+ public String getString(int length) {\r
+\r
+ String s = AsciiFuncs.asciiString(input, offset, length);\r
+ offset += length;\r
+ numberLength = length;\r
+ return s;\r
+ }\r
+\r
+ /** Get a boolean value from the beginning of the buffer */\r
+ public boolean getBoolean() throws FormatException {\r
+ return getBoolean(input.length - offset);\r
+ }\r
+\r
+ /** Get a boolean value from a specified region of the buffer */\r
+ public boolean getBoolean(int length) throws FormatException {\r
+\r
+ int startOffset = offset;\r
+ length -= skipWhite(length);\r
+ if (length == 0) {\r
+ throw new FormatException("Blank boolean field");\r
+ }\r
+\r
+ boolean value = false;\r
+ if (input[offset] == 'T' || input[offset] == 't') {\r
+ value = true;\r
+ } else if (input[offset] != 'F' && input[offset] != 'f') {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Invalid boolean value");\r
+ }\r
+ offset += 1;\r
+ length -= 1;\r
+\r
+ if (fillFields && length > 0) {\r
+ if (isWhite(length)) {\r
+ offset += length;\r
+ } else {\r
+ numberLength = 0;\r
+ offset = startOffset;\r
+ throw new FormatException("Non-white following boolean");\r
+ }\r
+ }\r
+ numberLength = offset - startOffset;\r
+ return value;\r
+ }\r
+\r
+ /** Skip bytes in the buffer */\r
+ public void skip(int nBytes) {\r
+ offset += nBytes;\r
+ }\r
+\r
+ /** Get the integer value starting at the current position.\r
+ * This routine returns a double rather than an int/long\r
+ * to enable it to read very long integers (with reduced\r
+ * precision) such as 111111111111111111111111111111111111111111.\r
+ * Note that this routine does set numberLength.\r
+ *\r
+ * @param length The maximum number of characters to use.\r
+ */\r
+ private double getBareInteger(int length) {\r
+\r
+ int startOffset = offset;\r
+ double number = 0;\r
+\r
+ while (length > 0 && input[offset] >= '0' && input[offset] <= '9') {\r
+\r
+ number *= 10;\r
+ number += input[offset] - '0';\r
+ offset += 1;\r
+ length -= 1;\r
+ }\r
+ numberLength = offset - startOffset;\r
+ return number;\r
+ }\r
+\r
+ /** Skip white space. This routine skips with space in\r
+ * the input and returns the number of character skipped.\r
+ * White space is defined as ' ', '\t', '\n' or '\r'\r
+ *\r
+ * @param length The maximum number of characters to skip.\r
+ */\r
+ public int skipWhite(int length) {\r
+\r
+ int i;\r
+ for (i = 0; i < length; i += 1) {\r
+ if (input[offset + i] != ' ' && input[offset + i] != '\t'\r
+ && input[offset + i] != '\n' && input[offset + i] != '\r') {\r
+ break;\r
+ }\r
+ }\r
+\r
+ offset += i;\r
+ return i;\r
+\r
+ }\r
+\r
+ /** Find the sign for a number .\r
+ * This routine looks for a sign (+/-) at the current location\r
+ * and return +1/-1 if one is found, or +1 if not.\r
+ * The foundSign boolean is set if a sign is found and offset is\r
+ * incremented.\r
+ */\r
+ private int checkSign() {\r
+\r
+ foundSign = false;\r
+\r
+ if (input[offset] == '+') {\r
+ foundSign = true;\r
+ offset += 1;\r
+ return 1;\r
+ } else if (input[offset] == '-') {\r
+ foundSign = true;\r
+ offset += 1;\r
+ return -1;\r
+ }\r
+\r
+ return 1;\r
+ }\r
+\r
+ /** Is a region blank?\r
+ * @param length The length of the region to be tested\r
+ */\r
+ private boolean isWhite(int length) {\r
+ int oldOffset = offset;\r
+ boolean value = skipWhite(length) == length;\r
+ offset = oldOffset;\r
+ return value;\r
+ }\r
+}\r
--- /dev/null
+package nom.tam.util;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+import java.io.*;
+import java.lang.reflect.Array;
+
+/** A data table is conventionally considered to consist of rows and
+ * columns, where the structure within each column is constant, but
+ * different columns may have different structures. I.e., structurally
+ * columns may differ but rows are identical.
+ * Typically tabular data is usually stored in row order which can
+ * make it extremely difficult to access efficiently using Java.
+ * This class provides efficient
+ * access to data which is stored in row order and allows users to
+ * get and set the elements of the table.
+ * The table can consist only of arrays of primitive types.
+ * Data stored in column order can
+ * be efficiently read and written using the
+ * BufferedDataXputStream classes.
+ *
+ * The table is represented entirely as a set of one-dimensional primitive
+ * arrays. For a given column, a row consists of some number of
+ * contiguous elements of the array. Each column is required to have
+ * the same number of rows.
+ */
+public class ColumnTable implements DataTable {
+
+ /** The columns to be read/written */
+ private Object[] arrays;
+ /** The number of elements in a row for each column */
+ private int[] sizes;
+ /** The number of rows */
+ private int nrow;
+ /** The number or rows to read/write in one I/O. */
+ private int chunk;
+ /** The size of a row in bytes */
+ private int rowSize;
+ /** The base type of each row (using the second character
+ * of the [x class names of the arrays.
+ */
+ private char[] types;
+ private Class[] bases;
+ // The following arrays are used to avoid having to check
+ // casts during the I/O loops.
+ // They point to elements of arrays.
+ private byte[][] bytePointers;
+ private short[][] shortPointers;
+ private int[][] intPointers;
+ private long[][] longPointers;
+ private float[][] floatPointers;
+ private double[][] doublePointers;
+ private char[][] charPointers;
+ private boolean[][] booleanPointers;
+
+ /** Create the object after checking consistency.
+ * @param arrays An array of one-d primitive arrays.
+ * @param sizes The number of elements in each row
+ * for the corresponding column
+ */
+ public ColumnTable(Object[] arrays, int[] sizes) throws TableException {
+ setup(arrays, sizes);
+ }
+
+ /** Actually perform the initialization.
+ */
+ protected void setup(Object[] arrays, int[] sizes) throws TableException {
+
+ checkArrayConsistency(arrays, sizes);
+ getNumberOfRows();
+ initializePointers();
+
+ }
+
+ /** Get the number of rows in the table.
+ */
+ public int getNRows() {
+ return nrow;
+ }
+
+ /** Get the number of columns in the table.
+ */
+ public int getNCols() {
+ return arrays.length;
+ }
+
+ /** Get a particular column.
+ * @param col The column desired.
+ * @return an object containing the column data desired.
+ * This will be an instance of a 1-d primitive array.
+ */
+ public Object getColumn(int col) {
+ return arrays[col];
+ }
+
+ /**
+ * Set the values in a particular column. The new values must match the old in
+ * length but not necessarily in type.
+ *
+ * @param col
+ * The column to modify.
+ * @param newColumn
+ * The new column data. This should be a primitive array.
+ * @exception TableException
+ * Thrown when the new data is not commenserable with information
+ * in the table.
+ */
+ public void setColumn(int col, Object newColumn) throws TableException {
+
+ boolean reset = newColumn.getClass() != arrays[col].getClass()
+ || Array.getLength(newColumn) != Array.getLength(arrays[col]);
+ arrays[col] = newColumn;
+ if (reset) {
+ setup(arrays, sizes);
+ } else {
+ // This is required, because otherwise the typed pointer may point to the old
+ // array, which has been replaced by newColumn. Added by Jeroen de Jong, 1 Aug 2006
+ initializePointers();
+ }
+ }
+
+ /** Add a column */
+ public void addColumn(Object newColumn, int size) throws TableException {
+
+ String classname = newColumn.getClass().getName();
+ nrow = checkColumnConsistency(newColumn, classname, nrow, size);
+
+ rowSize += nrow * ArrayFuncs.getBaseLength(newColumn);
+
+ getNumberOfRows();
+
+ int ncol = arrays.length;
+
+ Object[] newArrays = new Object[ncol + 1];
+ int[] newSizes = new int[ncol + 1];
+ Class[] newBases = new Class[ncol + 1];
+ char[] newTypes = new char[ncol + 1];
+
+ System.arraycopy(arrays, 0, newArrays, 0, ncol);
+ System.arraycopy(sizes, 0, newSizes, 0, ncol);
+ System.arraycopy(bases, 0, newBases, 0, ncol);
+ System.arraycopy(types, 0, newTypes, 0, ncol);
+
+ arrays = newArrays;
+ sizes = newSizes;
+ bases = newBases;
+ types = newTypes;
+
+ arrays[ncol] = newColumn;
+ sizes[ncol] = size;
+ bases[ncol] = ArrayFuncs.getBaseClass(newColumn);
+ types[ncol] = classname.charAt(1);
+ addPointer(newColumn);
+ }
+
+ /** Add a row to the table. This method is very inefficient
+ * for adding multiple rows and should be avoided if possible.
+ */
+ public void addRow(Object[] row) throws TableException {
+
+ if (arrays.length == 0) {
+
+ for (int i = 0; i < row.length; i += 1) {
+ addColumn(row[i], Array.getLength(row[i]));
+ }
+
+ } else {
+
+ if (row.length != arrays.length) {
+ throw new TableException("Row length mismatch");
+ }
+
+ for (int i = 0; i < row.length; i += 1) {
+ if (row[i].getClass() != arrays[i].getClass()
+ || Array.getLength(row[i]) != sizes[i]) {
+ throw new TableException("Row column mismatch at column:" + i);
+ }
+ Object xarray = ArrayFuncs.newInstance(bases[i], (nrow + 1) * sizes[i]);
+ System.arraycopy(arrays[i], 0, xarray, 0, nrow * sizes[i]);
+ System.arraycopy(row[i], 0, xarray, nrow * sizes[i], sizes[i]);
+ arrays[i] = xarray;
+ }
+ initializePointers();
+ nrow += 1;
+ }
+ }
+
+ /** Get a element of the table.
+ * @param row The row desired.
+ * @param col The column desired.
+ * @return A primitive array containing the information. Note
+ * that an array will be returned even if the element
+ * is a scalar.
+ */
+ public Object getElement(int row, int col) {
+
+ Object x = ArrayFuncs.newInstance(bases[col], sizes[col]);
+ System.arraycopy(arrays[col], sizes[col] * row, x, 0, sizes[col]);
+ return x;
+ }
+
+ /** Modify an element of the table.
+ * @param row The row containing the element.
+ * @param col The column containing the element.
+ * @param x The new datum. This should be 1-d primitive
+ * array.
+ * @exception TableException Thrown when the new data
+ * is not of the same type as
+ * the data it replaces.
+ */
+ public void setElement(int row, int col, Object x)
+ throws TableException {
+
+ String classname = x.getClass().getName();
+
+ if (!classname.equals("[" + types[col])) {
+ throw new TableException("setElement: Incompatible element type");
+ }
+
+ if (Array.getLength(x) != sizes[col]) {
+ throw new TableException("setElement: Incompatible element size");
+ }
+
+ System.arraycopy(x, 0, arrays[col], sizes[col] * row, sizes[col]);
+ }
+
+ /** Get a row of data.
+ * @param The row desired.
+ * @return An array of objects each containing a primitive array.
+ */
+ public Object getRow(int row) {
+
+ Object[] x = new Object[arrays.length];
+ for (int col = 0; col < arrays.length; col += 1) {
+ x[col] = getElement(row, col);
+ }
+ return x;
+ }
+
+ /** Modify a row of data.
+ * @param row The row to be modified.
+ * @param x The data to be modified. This should be an
+ * array of objects. It is described as an Object
+ * here since other table implementations may
+ * use other methods to store the data (e.g.,
+ * @see ColumnTable.getColumn.
+ */
+ public void setRow(int row, Object x) throws TableException {
+
+ if (!(x instanceof Object[])) {
+ throw new TableException("setRow: Incompatible row");
+ }
+
+ for (int col = 0; col < arrays.length; col += 1) {
+ setElement(row, col, ((Object[]) x)[col]);
+ }
+ }
+
+ /** Check that the columns and sizes are consistent.
+ * Inconsistencies include:
+ * <ul>
+ * <li> arrays and sizes have different lengths.
+ * <li> an element of arrays is not a primitive array.
+ * <li> the size of an array is not divisible by the sizes entry.
+ * <li> the number of rows differs for the columns.
+ * </ul>
+ * @param arrays The arrays defining the columns.
+ * @param sizes The number of elements in each row for the column.
+ */
+ protected void checkArrayConsistency(Object[] arrays, int[] sizes)
+ throws TableException {
+
+ // This routine throws an error if it detects an inconsistency
+ // between the arrays being read in.
+
+ // First check that the lengths of the two arrays are the same.
+ if (arrays.length != sizes.length) {
+ throw new TableException("readArraysAsColumns: Incompatible arrays and sizes.");
+ }
+
+ // Now check that that we fill up all of the arrays exactly.
+ int ratio = 0;
+ int rowSize = 0;
+
+ this.types = new char[arrays.length];
+ this.bases = new Class[arrays.length];
+
+ // Check for a null table.
+ boolean nullTable = true;
+
+ for (int i = 0; i < arrays.length; i += 1) {
+
+ String classname = arrays[i].getClass().getName();
+
+ ratio = checkColumnConsistency(arrays[i], classname, ratio, sizes[i]);
+
+ rowSize += sizes[i] * ArrayFuncs.getBaseLength(arrays[i]);
+ types[i] = classname.charAt(1);
+ bases[i] = ArrayFuncs.getBaseClass(arrays[i]);
+ }
+
+ this.nrow = ratio;
+ this.rowSize = rowSize;
+ this.arrays = arrays;
+ this.sizes = sizes;
+ }
+
+ private int checkColumnConsistency(Object data, String classname, int ratio, int size)
+ throws TableException {
+
+
+ if (classname.charAt(0) != '[' || classname.length() != 2) {
+ throw new TableException("Non-primitive array for column");
+ }
+
+ int thisSize = Array.getLength(data);
+ if ((thisSize == 0 && size != 0 && ratio != 0)
+ || (thisSize != 0 && size == 0)) {
+ throw new TableException("Size mismatch in column: " + thisSize + " != " + size);
+ }
+
+ // The row size must evenly divide the size of the array.
+ if (size != 0 && thisSize % size != 0) {
+ throw new TableException("Row size does not divide array for column");
+ }
+
+ // Finally the ratio of sizes must be the same for all columns -- this
+ // is the number of rows in the table.
+ int thisRatio = 0;
+ if (size > 0) {
+ thisRatio = thisSize / size;
+
+ if (ratio != 0 && (thisRatio != ratio)) {
+ throw new TableException("Different number of rows in different columns");
+ }
+ }
+ if (thisRatio > 0) {
+ return thisRatio;
+ } else {
+ return ratio;
+ }
+
+ }
+
+ /** Calculate the number of rows to read/write at a time.
+ * @param rowSize The size of a row in bytes.
+ * @param nrows The number of rows in the table.
+ */
+ protected void getNumberOfRows() {
+
+ int bufSize = 65536;
+
+ // If a row is larger than bufSize, then read one row at a time.
+ if (rowSize == 0) {
+ this.chunk = 0;
+
+ } else if (rowSize > bufSize) {
+ this.chunk = 1;
+
+ // If the entire set is not too big, just read it all.
+ } else if (bufSize / rowSize >= nrow) {
+ this.chunk = nrow;
+ } else {
+ this.chunk = bufSize / rowSize + 1;
+ }
+
+ }
+
+ /** Set the pointer arrays for the eight primitive types
+ * to point to the appropriate elements of arrays.
+ */
+ protected void initializePointers() {
+
+ int nbyte, nshort, nint, nlong, nfloat, ndouble, nchar, nboolean;
+
+ // Count how many of each type we have.
+ nbyte = 0;
+ nshort = 0;
+ nint = 0;
+ nlong = 0;
+ nfloat = 0;
+ ndouble = 0;
+ nchar = 0;
+ nboolean = 0;
+
+ for (int col = 0; col < arrays.length; col += 1) {
+ switch (types[col]) {
+
+ case 'B':
+ nbyte += 1;
+ break;
+ case 'S':
+ nshort += 1;
+ break;
+ case 'I':
+ nint += 1;
+ break;
+ case 'J':
+ nlong += 1;
+ break;
+ case 'F':
+ nfloat += 1;
+ break;
+ case 'D':
+ ndouble += 1;
+ break;
+ case 'C':
+ nchar += 1;
+ break;
+ case 'Z':
+ nboolean += 1;
+ break;
+ }
+ }
+
+ // Allocate the pointer arrays. Note that many will be
+ // zero-length.
+
+ bytePointers = new byte[nbyte][];
+ shortPointers = new short[nshort][];
+ intPointers = new int[nint][];
+ longPointers = new long[nlong][];
+ floatPointers = new float[nfloat][];
+ doublePointers = new double[ndouble][];
+ charPointers = new char[nchar][];
+ booleanPointers = new boolean[nboolean][];
+
+ // Now set the pointers.
+ nbyte = 0;
+ nshort = 0;
+ nint = 0;
+ nlong = 0;
+ nfloat = 0;
+ ndouble = 0;
+ nchar = 0;
+ nboolean = 0;
+
+ for (int col = 0; col < arrays.length; col += 1) {
+ switch (types[col]) {
+
+ case 'B':
+ bytePointers[nbyte] = (byte[]) arrays[col];
+ nbyte += 1;
+ break;
+ case 'S':
+ shortPointers[nshort] = (short[]) arrays[col];
+ nshort += 1;
+ break;
+ case 'I':
+ intPointers[nint] = (int[]) arrays[col];
+ nint += 1;
+ break;
+ case 'J':
+ longPointers[nlong] = (long[]) arrays[col];
+ nlong += 1;
+ break;
+ case 'F':
+ floatPointers[nfloat] = (float[]) arrays[col];
+ nfloat += 1;
+ break;
+ case 'D':
+ doublePointers[ndouble] = (double[]) arrays[col];
+ ndouble += 1;
+ break;
+ case 'C':
+ charPointers[nchar] = (char[]) arrays[col];
+ nchar += 1;
+ break;
+ case 'Z':
+ booleanPointers[nboolean] = (boolean[]) arrays[col];
+ nboolean += 1;
+ break;
+ }
+ }
+ }
+
+ // Add a pointer in the pointer lists.
+ protected void addPointer(Object data) throws TableException {
+ String classname = data.getClass().getName();
+ char type = classname.charAt(1);
+
+ switch (type) {
+ case 'B': {
+ byte[][] xb = new byte[bytePointers.length + 1][];
+ System.arraycopy(bytePointers, 0, xb, 0, bytePointers.length);
+ xb[bytePointers.length] = (byte[]) data;
+ bytePointers = xb;
+ break;
+ }
+ case 'Z': {
+ boolean[][] xb = new boolean[booleanPointers.length + 1][];
+ System.arraycopy(booleanPointers, 0, xb, 0, booleanPointers.length);
+ xb[booleanPointers.length] = (boolean[]) data;
+ booleanPointers = xb;
+ break;
+ }
+ case 'S': {
+ short[][] xb = new short[shortPointers.length + 1][];
+ System.arraycopy(shortPointers, 0, xb, 0, shortPointers.length);
+ xb[shortPointers.length] = (short[]) data;
+ shortPointers = xb;
+ break;
+ }
+ case 'C': {
+ char[][] xb = new char[charPointers.length + 1][];
+ System.arraycopy(charPointers, 0, xb, 0, charPointers.length);
+ xb[charPointers.length] = (char[]) data;
+ charPointers = xb;
+ break;
+ }
+ case 'I': {
+ int[][] xb = new int[intPointers.length + 1][];
+ System.arraycopy(intPointers, 0, xb, 0, intPointers.length);
+ xb[intPointers.length] = (int[]) data;
+ intPointers = xb;
+ break;
+ }
+ case 'J': {
+ long[][] xb = new long[longPointers.length + 1][];
+ System.arraycopy(longPointers, 0, xb, 0, longPointers.length);
+ xb[longPointers.length] = (long[]) data;
+ longPointers = xb;
+ break;
+ }
+ case 'F': {
+ float[][] xb = new float[floatPointers.length + 1][];
+ System.arraycopy(floatPointers, 0, xb, 0, floatPointers.length);
+ xb[floatPointers.length] = (float[]) data;
+ floatPointers = xb;
+ break;
+ }
+ case 'D': {
+ double[][] xb = new double[doublePointers.length + 1][];
+ System.arraycopy(doublePointers, 0, xb, 0, doublePointers.length);
+ xb[doublePointers.length] = (double[]) data;
+ doublePointers = xb;
+ break;
+ }
+ default:
+ throw new TableException("Invalid type for added column:" + classname);
+ }
+ }
+
+ /** Read a table.
+ * @param is The input stream to read from.
+ */
+ public int read(ArrayDataInput is) throws IOException {
+
+ int currRow = 0;
+
+ // While we have not finished reading the table..
+ for (int row = 0; row < nrow; row += 1) {
+
+ int ibyte = 0;
+ int ishort = 0;
+ int iint = 0;
+ int ilong = 0;
+ int ichar = 0;
+ int ifloat = 0;
+ int idouble = 0;
+ int iboolean = 0;
+
+ // Loop over the columns within the row.
+ for (int col = 0; col < arrays.length; col += 1) {
+
+ int arrOffset = sizes[col] * row;
+ int size = sizes[col];
+
+ switch (types[col]) {
+ // In anticpated order of use.
+ case 'I':
+ int[] ia = intPointers[iint];
+ iint += 1;
+ is.read(ia, arrOffset, size);
+ break;
+
+ case 'S':
+ short[] s = shortPointers[ishort];
+ ishort += 1;
+ is.read(s, arrOffset, size);
+ break;
+
+ case 'B':
+ byte[] b = bytePointers[ibyte];
+ ibyte += 1;
+ is.read(b, arrOffset, size);
+ break;
+
+ case 'F':
+ float[] f = floatPointers[ifloat];
+ ifloat += 1;
+ is.read(f, arrOffset, size);
+ break;
+
+ case 'D':
+ double[] d = doublePointers[idouble];
+ idouble += 1;
+ is.read(d, arrOffset, size);
+ break;
+
+ case 'C':
+ char[] c = charPointers[ichar];
+ ichar += 1;
+ is.read(c, arrOffset, size);
+ break;
+
+ case 'J':
+ long[] l = longPointers[ilong];
+ ilong += 1;
+ is.read(l, arrOffset, size);
+ break;
+
+ case 'Z':
+
+ boolean[] bool = booleanPointers[iboolean];
+ iboolean += 1;
+ is.read(bool, arrOffset, size);
+ break;
+ }
+ }
+ }
+
+ // All done if we get here...
+ return rowSize * nrow;
+ }
+
+ /** Write a table.
+ * @param os the output stream to write to.
+ */
+ public int write(ArrayDataOutput os) throws IOException {
+
+ if (rowSize == 0) {
+ return 0;
+ }
+
+ for (int row = 0; row < nrow; row += 1) {
+
+ int ibyte = 0;
+ int ishort = 0;
+ int iint = 0;
+ int ilong = 0;
+ int ichar = 0;
+ int ifloat = 0;
+ int idouble = 0;
+ int iboolean = 0;
+
+ // Loop over the columns within the row.
+ for (int col = 0; col < arrays.length; col += 1) {
+
+ int arrOffset = sizes[col] * row;
+ int size = sizes[col];
+
+ switch (types[col]) {
+ // In anticpated order of use.
+ case 'I':
+ int[] ia = intPointers[iint];
+ iint += 1;
+ os.write(ia, arrOffset, size);
+ break;
+
+ case 'S':
+ short[] s = shortPointers[ishort];
+ ishort += 1;
+ os.write(s, arrOffset, size);
+ break;
+
+ case 'B':
+ byte[] b = bytePointers[ibyte];
+ ibyte += 1;
+ os.write(b, arrOffset, size);
+ break;
+
+ case 'F':
+ float[] f = floatPointers[ifloat];
+ ifloat += 1;
+ os.write(f, arrOffset, size);
+ break;
+
+ case 'D':
+ double[] d = doublePointers[idouble];
+ idouble += 1;
+ os.write(d, arrOffset, size);
+ break;
+
+ case 'C':
+ char[] c = charPointers[ichar];
+ ichar += 1;
+ os.write(c, arrOffset, size);
+ break;
+
+ case 'J':
+ long[] l = longPointers[ilong];
+ ilong += 1;
+ os.write(l, arrOffset, size);
+ break;
+
+ case 'Z':
+ boolean[] bool = booleanPointers[iboolean];
+ iboolean += 1;
+ os.write(bool, arrOffset, size);
+ break;
+ }
+
+ }
+
+ }
+
+ // All done if we get here...
+ return rowSize * nrow;
+ }
+
+ /** Get the base classes of the columns.
+ * @return An array of Class objects, one for each column.
+ */
+ public Class[] getBases() {
+ return bases;
+ }
+
+ /** Get the characters describing the base classes of the columns.
+ * @return An array of char's, one for each column.
+ */
+ public char[] getTypes() {
+ return types;
+ }
+
+ /** Get the actual data arrays */
+ public Object[] getColumns() {
+ return arrays;
+ }
+
+ public int[] getSizes() {
+ return sizes;
+ }
+
+ /** Delete a row from the table.
+ * @param row The row (0-indexed) to be deleted.
+ */
+ public void deleteRow(int row) throws TableException {
+ deleteRows(row, 1);
+ }
+
+ /** Delete a contiguous set of rows from the table.
+ * @param row The row (0-indexed) to be deleted.
+ * @param length The number of rows to be deleted.
+ * @throws TableException if the request goes outside
+ * the boundaries of the table or if the length is negative.
+ */
+ public void deleteRows(int row, int length) throws TableException {
+
+ if (row < 0 || length < 0 || row + length > nrow) {
+ throw new TableException("Invalid request to delete rows start: " + row + " length:" + length
+ + " for table with " + nrow + " rows.");
+ }
+
+ if (length == 0) {
+ return;
+ }
+
+ for (int col = 0; col < arrays.length; col += 1) {
+
+ int sz = sizes[col];
+ int newSize = sz * (nrow - length);
+ Object newArr = ArrayFuncs.newInstance(bases[col], newSize);
+
+ // Copy whatever comes before the deletion
+ System.arraycopy(arrays[col], 0, newArr, 0, row * sz);
+
+ // Copy whatever comes after the deletion
+ System.arraycopy(arrays[col], (row + length) * sz, newArr, row * sz, (nrow - row - length) * sz);
+ arrays[col] = newArr;
+ }
+ nrow -= length;
+ initializePointers();
+ }
+
+ /** Delete a contiguous set of columns from the table.
+ * @param col The column (0-indexed) to be deleted.
+ * @param length The number of rows to be deleted.
+ * @throws TableException if the request goes outside
+ * the boundaries of the table or if the length is negative.
+ */
+ public int deleteColumns(int start, int len) throws TableException {
+
+ int ncol = arrays.length;
+
+ if (start < 0 || len < 0 || start + len > ncol) {
+ throw new TableException("Invalid request to delete columns start: " + start + " length:" + len
+ + " for table with " + ncol + " columns.");
+ }
+
+ if (len == 0) {
+ return rowSize;
+ }
+
+ for (int i = start; i < start + len; i += 1) {
+ rowSize -= sizes[i] * ArrayFuncs.getBaseLength(arrays[i]);
+ }
+
+ int ocol = ncol;
+ ncol -= len;
+
+ Object[] newArrays = new Object[ncol];
+ int[] newSizes = new int[ncol];
+ Class[] newBases = new Class[ncol];
+ char[] newTypes = new char[ncol];
+
+ System.arraycopy(arrays, 0, newArrays, 0, start);
+ System.arraycopy(sizes, 0, newSizes, 0, start);
+ System.arraycopy(bases, 0, newBases, 0, start);
+ System.arraycopy(types, 0, newTypes, 0, start);
+
+ int rem = ocol - (start + len);
+
+ System.arraycopy(arrays, start + len, newArrays, start, rem);
+ System.arraycopy(sizes, start + len, newSizes, start, rem);
+ System.arraycopy(bases, start + len, newBases, start, rem);
+ System.arraycopy(types, start + len, newTypes, start, rem);
+
+
+ arrays = newArrays;
+ sizes = newSizes;
+ bases = newBases;
+ types = newTypes;
+
+ initializePointers();
+ return rowSize;
+ }
+}
--- /dev/null
+package nom.tam.util;
+
+/** This interface extends the Iterator interface
+ * to allow insertion of data and move to previous entries
+ * in a collection.
+ */
+public interface Cursor extends java.util.Iterator {
+
+ /** Is there a previous element in the collection? */
+ public abstract boolean hasPrev();
+
+ /** Get the previous element */
+ public abstract Object prev() throws java.util.NoSuchElementException;
+
+ /** Point the list at a particular element.
+ * Point to the end of the list if the key is not found.
+ */
+ public abstract void setKey(Object key);
+
+ /** Add an unkeyed element to the collection.
+ * The new element is placed such that it will be called
+ * by a prev() call, but not a next() call.
+ */
+ public abstract void add(Object reference);
+
+ /** Add a keyed element to the collection.
+ * The new element is placed such that it will be called
+ * by a prev() call, but not a next() call.
+ */
+ public abstract void add(Object key, Object reference);
+}
--- /dev/null
+package nom.tam.util;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+
+/** This interface combines the DataInput, DataOutput and
+ * RandomAccess interfaces to provide a reference type
+ * which can be used to build BufferedFile in a fashion
+ * that accommodates both the RandomAccessFile and ByteBuffers
+ */
+public interface DataIO extends DataInput, DataOutput, RandomAccess {
+}
--- /dev/null
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+/** This interface defines the properties that
+ * a generic table should have.
+ */
+public interface DataTable {
+
+ public abstract void setRow(int row, Object newRow)
+ throws TableException;
+
+ public abstract Object getRow(int row);
+
+ public abstract void setColumn(int column, Object newColumn)
+ throws TableException;
+
+ public abstract Object getColumn(int column);
+
+ public abstract void setElement(int row, int col, Object newElement)
+ throws TableException;
+
+ public abstract Object getElement(int row, int col);
+
+ public abstract int getNRows();
+
+ public abstract int getNCols();
+}
--- /dev/null
+package nom.tam.util;
+
+public class FormatException extends java.lang.Exception {
+
+ FormatException() {
+ super();
+ }
+
+ FormatException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+/** This class implements a structure which can
+ * be accessed either through a hash or
+ * as linear list. Only some elements may have
+ * a hash key.
+ *
+ * This class is motivated by the FITS header
+ * structure where a user may wish to go through
+ * the header element by element, or jump directly
+ * to a given keyword. It assumes that all
+ * keys are unique. However, all elements in the
+ * structure need not have a key.
+ *
+ * This class does only the search structure
+ * and knows nothing of the semantics of the
+ * referenced objects.
+ *
+ */
+import java.util.*;
+import java.lang.reflect.Array;
+
+import nom.tam.util.ArrayFuncs;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+
+public class HashedList implements Collection {
+
+ /** An ordered list of the keys */
+ private ArrayList ordered = new ArrayList();
+ /** The key value pairs */
+ private HashMap keyed = new HashMap();
+ /** This is used to generate unique keys for
+ * elements entered without an key.
+ */
+ private int unkeyedIndex = 0;
+
+ private class HashedListIterator implements Cursor {
+
+ /** This index points to the value that would be returned in
+ * the next 'next' call.
+ */
+ private int current;
+
+ HashedListIterator(int start) {
+ current = start;
+ }
+
+ /** Is there another element? */
+ public boolean hasNext() {
+ return current >= 0 && current < ordered.size();
+ }
+
+ /** Is there a previous element? */
+ public boolean hasPrev() {
+ return current > 0;
+ }
+
+ /** Get the next entry. */
+ public Object next() throws NoSuchElementException {
+
+ if (current < 0 || current >= ordered.size()) {
+ throw new NoSuchElementException("Outside list");
+
+ } else {
+ Object key = ordered.get(current);
+ current += 1;
+ return keyed.get(key);
+ }
+ }
+
+ /** Get the previous entry. */
+ public Object prev() throws NoSuchElementException {
+ if (current <= 0) {
+ throw new NoSuchElementException("Before beginning of list");
+ }
+ current -= 1;
+ Object key = ordered.get(current);
+ return keyed.get(key);
+ }
+
+ /** Remove an entry from the tree. Note that this can
+ * now be called anytime after the iterator is created.
+ */
+ public void remove() {
+ if (current > 0 && current <= ordered.size()) {
+
+ HashedList.this.remove(current - 1);
+
+ // If we just removed the last entry, then we need
+ // to go back one.
+ if (current > 0) {
+ current -= 1;
+ }
+ }
+ }
+
+ /** Add an entry at the current location. The new entry goes before
+ * the entry that would be returned in the next 'next' call, and
+ * that call will not be affected by the insertion.
+ * Note: this method is not in the Iterator interface.
+ */
+ public void add(Object ref) {
+ Integer nKey = new Integer(unkeyedIndex);
+ unkeyedIndex += 1;
+ HashedList.this.add(current, nKey, ref);
+ current += 1;
+ }
+
+ /** Add a keyed entry at the current location. The new entry is inserted
+ * before the entry that would be returned in the next invocation of
+ * 'next'. The return value for that call is unaffected.
+ * Note: this method is not in the Iterator interface.
+ */
+ public void add(Object key, Object ref) {
+ HashedList.this.add(current, key, ref);
+ current += 1;
+ }
+
+ /** Point the iterator to a particular keyed entry. This
+ * method is not in the Iterator interface.
+ * @param key
+ */
+ public void setKey(Object key) {
+ if (keyed.containsKey(key)) {
+ current = ordered.indexOf(key);
+ } else {
+ current = ordered.size();
+ }
+
+ }
+ }
+
+ /** Add an element to the end of the list. */
+ public boolean add(Object reference) {
+ Integer nKey = new Integer(unkeyedIndex);
+ unkeyedIndex += 1;
+ HashedList.this.add(ordered.size(), nKey, reference);
+ return true;
+
+ }
+
+ /** Add a keyed element to the end of the list. */
+ public boolean add(Object key, Object reference) {
+ add(ordered.size(), key, reference);
+ return true;
+ }
+
+ /** Add an element to the list.
+ * @param pos The element before which the current element
+ * be placed. If pos is null put the element at
+ * the end of the list.
+ * @param key The hash key for the new object. This may be null
+ * for an unkeyed entry.
+ * @param reference The actual object being stored.
+ */
+ public boolean add(int pos, Object key, Object reference) {
+
+ if (keyed.containsKey(key)) {
+ int oldPos = ordered.indexOf(key);
+ removeKey(key);
+ if (oldPos < pos) {
+ pos -= 1;
+ }
+ }
+
+ keyed.put(key, reference);
+ if (pos >= ordered.size()) {
+ ordered.add(key);
+
+ } else {
+ ordered.add(pos, key);
+ }
+
+ return true;
+ }
+
+ /** Remove a keyed object from the list. Unkeyed
+ * objects can be removed from the list using a
+ * HashedListIterator or using the remove(Object)
+ * method.
+ */
+ public boolean removeKey(Object key) {
+ if (keyed.containsKey(key)) {
+ int index = ordered.indexOf(key);
+ keyed.remove(key);
+ ordered.remove(index);
+ return true;
+ }
+ return false;
+ }
+
+ /** Remove an object from the list giving just
+ * the object value.
+ */
+ public boolean remove(Object o) {
+
+ if (keyed.containsValue(o)) {
+ for (int i = 0; i < ordered.size(); i += 1) {
+ if (keyed.get(ordered.get(i)).equals(o)) {
+ return removeKey(ordered.get(i));
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Remove an object from the list giving the object index..*/
+ public boolean remove(int index) {
+ if (index >= 0 && index < ordered.size()) {
+ Object key = ordered.get(index);
+ return removeKey(key);
+ }
+ return false;
+ }
+
+ /** Return an iterator over the entire list.
+ * The iterator may be used to delete
+ * entries as well as to retrieve existing
+ * entries. A knowledgeable user can
+ * cast this to a HashedListIterator and
+ * use it to add as well as delete entries.
+ */
+ public Iterator iterator() {
+ return new HashedListIterator(0);
+ }
+
+ /** Return an iterator over the list starting
+ * with the entry with a given key.
+ */
+ public HashedListIterator iterator(Object key) throws NoSuchElementException {
+ if (keyed.containsKey(key)) {
+ return new HashedListIterator(ordered.indexOf(key));
+ } else {
+ throw new NoSuchElementException("Unknown key for iterator:" + key);
+ }
+ }
+
+ /** Return an iterator starting with the n'th
+ * entry.
+ */
+ public HashedListIterator iterator(int n) throws NoSuchElementException {
+ if (n >= 0 && n <= ordered.size()) {
+ return new HashedListIterator(n);
+ } else {
+ throw new NoSuchElementException("Invalid index for iterator:" + n);
+ }
+ }
+
+ /** Return the value of a keyed entry. Non-keyed
+ * entries may be returned by requesting an iterator.
+ */
+ public Object get(Object key) {
+ return keyed.get(key);
+ }
+
+ /** Return the n'th entry from the beginning. */
+ public Object get(int n) throws NoSuchElementException {
+ return keyed.get(ordered.get(n));
+ }
+
+ /** Replace the key of a given element.
+ * @param oldKey The previous key. This key must
+ * be present in the hash.
+ * @param newKey The new key. This key
+ * must not be present in the hash.
+ * @return if the replacement was successful.
+ */
+ public boolean replaceKey(Object oldKey, Object newKey) {
+
+ if (!keyed.containsKey(oldKey) || keyed.containsKey(newKey)) {
+ return false;
+ }
+
+ Object oldVal = keyed.get(oldKey);
+ int index = ordered.indexOf(oldKey);
+ remove(index);
+ return add(index, newKey, oldVal);
+
+ }
+
+ /** Check if the key is included in the list */
+ public boolean containsKey(Object key) {
+ return keyed.containsKey(key);
+ }
+
+ /** Return the number of elements in the list. */
+ public int size() {
+ return ordered.size();
+ }
+
+ /** Add another collection to this one list.
+ * All entries are added as unkeyed entries to the end of the list.
+ */
+ public boolean addAll(Collection c) {
+ Object[] array = c.toArray();
+ for (int i = 0; i < array.length; i += 1) {
+ add(array[i]);
+ }
+ return true;
+ }
+
+ /** Clear the collection */
+ public void clear() {
+ keyed.clear();
+ ordered.clear();
+ }
+
+ /** Does the HashedList contain this element? */
+ public boolean contains(Object o) {
+ return keyed.containsValue(o);
+ }
+
+ /** Does the HashedList contain all the elements
+ * of this other collection.
+ */
+ public boolean containsAll(Collection c) {
+ return keyed.values().containsAll(c);
+ }
+
+ /** Is the HashedList empty? */
+ public boolean isEmpty() {
+ return keyed.isEmpty();
+ }
+
+ /** Remove all the elements that are found in another collection. */
+ public boolean removeAll(Collection c) {
+ Object[] o = c.toArray();
+ boolean result = false;
+ for (int i = 0; i < o.length; i += 1) {
+ result = result | remove(o[i]);
+ }
+ return result;
+ }
+
+ /** Retain only elements contained in another collection */
+ public boolean retainAll(Collection c) {
+
+ Iterator iter = iterator();
+ boolean result = false;
+ while (iter.hasNext()) {
+ Object o = iter.next();
+ if (!c.contains(o)) {
+ iter.remove();
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ /** Convert to an array of objects */
+ public Object[] toArray() {
+ Object[] o = new Object[ordered.size()];
+ return toArray(o);
+ }
+
+ /** Convert to an array of objects of
+ * a specified type.
+ */
+ public Object[] toArray(Object[] o) {
+ return keyed.values().toArray(o);
+ }
+
+ /** Sort the keys into some desired order.
+ */
+ public void sort(Comparator comp) {
+ java.util.Collections.sort(ordered, comp);
+ }
+}
--- /dev/null
+package nom.tam.util;\r
+\r
+/** This interface collects some information about Java primitives.\r
+ */\r
+public interface PrimitiveInfo {\r
+\r
+ /** Suffixes used for the classnames for primitive arrays. */\r
+ char[] suffixes = new char[]{'B', 'S', 'C', 'I', 'J', 'F', 'D', 'Z'};\r
+ /** Classes of the primitives. These should be in windening order\r
+ * (char is as always a problem).\r
+ */\r
+ Class[] classes = new Class[]{\r
+ byte.class, short.class, char.class, int.class,\r
+ long.class, float.class, double.class, boolean.class};\r
+ /** Is this a numeric class */\r
+ boolean[] isNumeric = new boolean[]{true, true, true, true, true, true, true, false};\r
+ /** Full names */\r
+ String[] types = new String[]{\r
+ "byte", "short", "char", "int",\r
+ "long", "float", "double", "boolean"\r
+ };\r
+ /** Sizes */\r
+ int[] sizes = new int[]{1, 2, 2, 4, 8, 4, 8, 1};\r
+ /** Index of first element of above arrays referring to a numeric type */\r
+ int FIRST_NUMERIC = 0;\r
+ /** Index of last element of above arrays referring to a numeric type */\r
+ int LAST_NUMERIC = 6;\r
+ int BYTE_INDEX = 0;\r
+ int SHORT_INDEX = 1;\r
+ int CHAR_INDEX = 2;\r
+ int INT_INDEX = 3;\r
+ int LONG_INDEX = 4;\r
+ int FLOAT_INDEX = 5;\r
+ int DOUBLE_INDEX = 6;\r
+ int BOOLEAN_INDEX = 7;\r
+}\r
+\r
+\r
--- /dev/null
+package nom.tam.util;
+
+/** These packages define the methods which indicate that
+ * an i/o stream may be accessed in arbitrary order.
+ * The method signatures are taken from RandomAccessFile
+ * though that class does not implement this interface.
+ */
+public interface RandomAccess extends ArrayDataInput {
+
+ /** Move to a specified location in the stream. */
+ public void seek(long offsetFromStart) throws java.io.IOException;
+
+ /** Get the current position in the stream */
+ public long getFilePointer();
+}
--- /dev/null
+package nom.tam.util;
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+public class TableException extends Exception {
+
+ public TableException() {
+ super();
+ }
+
+ public TableException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package nom.tam.util;
+
+public class TruncationException extends Exception {
+
+ public TruncationException() {
+ super();
+ }
+
+ public TruncationException(String msg) {
+ super(msg);
+ }
+}
--- /dev/null
+package nom.tam.util.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+import nom.tam.util.ArrayFuncs;
+
+public class ArrayFuncs2Test {
+
+ /** Test and demonstrate the ArrayFuncs methods.
+ */
+ @Test
+ public void test() {
+
+ int[][][] test1 = new int[10][9][8];
+ boolean[][] test2 = new boolean[4][];
+ test2[0] = new boolean[5];
+ test2[1] = new boolean[4];
+ test2[2] = new boolean[3];
+ test2[3] = new boolean[2];
+
+ double[][] test3 = new double[10][20];
+ StringBuffer[][] test4 = new StringBuffer[3][2];
+
+ assertEquals("getBaseClass()", int.class, ArrayFuncs.getBaseClass(test1));
+ assertEquals("getBaseLength()", 4, ArrayFuncs.getBaseLength(test1));
+ assertEquals("computeSize()", 4 * 8 * 9 * 10, ArrayFuncs.computeSize(test1));
+ assertEquals("computeLSize()", 4 * 8 * 9 * 10L, ArrayFuncs.computeLSize(test1));
+
+ assertEquals("getBaseClass(boolean)", boolean.class, ArrayFuncs.getBaseClass(test2));
+ assertEquals("getBaseLength(boolean)", 1, ArrayFuncs.getBaseLength(test2));
+ assertEquals("computeSize() not rect", 1 * (2 + 3 + 4 + 5), ArrayFuncs.computeSize(test2));
+ assertEquals("computeLSize() not rect", 1L * (2 + 3 + 4 + 5), ArrayFuncs.computeLSize(test2));
+
+ assertEquals("getBaseClass(double)", double.class, ArrayFuncs.getBaseClass(test3));
+ assertEquals("getBaseLength(double)", 8, ArrayFuncs.getBaseLength(test3));
+ assertEquals("computeSize(double)", 8 * 10 * 20, ArrayFuncs.computeSize(test3));
+ assertEquals("computeLSize(double)", 8 * 10 * 20L, ArrayFuncs.computeLSize(test3));
+
+ assertEquals("getBaseClass(StrBuf)", StringBuffer.class, ArrayFuncs.getBaseClass(test4));
+ assertEquals("getBaseLength(StrBuf)", -1, ArrayFuncs.getBaseLength(test4));
+ assertEquals("computeSize(StrBuf)", 0, ArrayFuncs.computeSize(test4));
+ assertEquals("computeLSize(StrBuf)", 0L, ArrayFuncs.computeLSize(test4));
+
+ Object[] agg = new Object[4];
+ agg[0] = test1;
+ agg[1] = test2;
+ agg[2] = test3;
+ agg[3] = test4;
+
+ assertEquals("getBaseClass(Object[])", Object.class, ArrayFuncs.getBaseClass(agg));
+ assertEquals("getBaseLength(Object[])", -1, ArrayFuncs.getBaseLength(agg));
+
+ // Add up all the primitive arrays and ignore the objects.
+ assertEquals("computeSize(Object[])", 2880 + 14 + 1600 + 0, ArrayFuncs.computeSize(agg));
+ assertEquals("computeLSize(Object[])", 2880L + 14 + 1600 + 0, ArrayFuncs.computeLSize(agg));
+
+ // Try allocating a very large array. This is likely to fail
+ // in the allocation step, so don't consider that to be a failure.
+ try {
+ float[][] data = new float[10000][30000];
+ long expect = 10000L * 30000 * 4;
+ assertEquals("computLSize(big)", ArrayFuncs.computeLSize(data), expect);
+
+ } catch (Error e) {
+ System.out.println("Unable to allocate large array. Test skipped");
+ }
+
+
+ for (int i = 0; i < test1.length; i += 1) {
+ for (int j = 0; j < test1[i].length; j += 1) {
+ for (int k = 0; k < test1[i][j].length; k += 1) {
+ test1[i][j][k] = i + j + k;
+ }
+ }
+ }
+ int[][][] test5 = (int[][][]) ArrayFuncs.deepClone(test1);
+
+ assertEquals("deepClone()", true, ArrayFuncs.arrayEquals(test1, test5));
+ test5[1][1][1] = -3;
+ assertEquals("arrayEquals()", false, ArrayFuncs.arrayEquals(test1, test5));
+
+ int[] dimsOrig = ArrayFuncs.getDimensions(test1);
+ int[] test6 = (int[]) ArrayFuncs.flatten(test1);
+
+ int[] dims = ArrayFuncs.getDimensions(test6);
+
+ assertEquals("getDimensions()", 3, dimsOrig.length);
+ assertEquals("getDimensions()", 10, dimsOrig[0]);
+ assertEquals("getDimensions()", 9, dimsOrig[1]);
+ assertEquals("getDimensions()", 8, dimsOrig[2]);
+ assertEquals("flatten()", 1, dims.length);
+
+ int[] newdims = {8, 9, 10};
+
+ int[][][] test7 = (int[][][]) ArrayFuncs.curl(test6, newdims);
+
+ int[] dimsAfter = ArrayFuncs.getDimensions(test7);
+
+ assertEquals("curl()", 3, dimsAfter.length);
+ assertEquals("getDimensions()", 8, dimsAfter[0]);
+ assertEquals("getDimensions()", 9, dimsAfter[1]);
+ assertEquals("getDimensions()", 10, dimsAfter[2]);
+
+ byte[][][] xtest1 = (byte[][][]) ArrayFuncs.convertArray(test1, byte.class);
+
+ assertEquals("convertArray(toByte)", byte.class, ArrayFuncs.getBaseClass(xtest1));
+ assertEquals("convertArray(tobyte)", test1[3][3][3], (int) xtest1[3][3][3]);
+
+ double[][][] xtest2 = (double[][][]) ArrayFuncs.convertArray(test1, double.class);
+ assertEquals("convertArray(toByte)", double.class, ArrayFuncs.getBaseClass(xtest2));
+ assertEquals("convertArray(tobyte)", test1[3][3][3], (int) xtest2[3][3][3]);
+
+ int[] xtest3 = (int[]) ArrayFuncs.newInstance(int.class, 20);
+ int[] xtd = ArrayFuncs.getDimensions(xtest3);
+ assertEquals("newInstance(vector)", 1, xtd.length);
+ assertEquals("newInstance(vector)", 20, xtd[0]);
+ double[][][][] xtest4 = (double[][][][]) ArrayFuncs.newInstance(double.class, new int[]{5, 4, 3, 2});
+ xtd = ArrayFuncs.getDimensions(xtest4);
+ assertEquals("newInstance(tensor)", 4, xtd.length);
+ assertEquals("newInstance(tensor)", 5, xtd[0]);
+ assertEquals("newInstance(tensor)", 4, xtd[1]);
+ assertEquals("newInstance(tensor)", 3, xtd[2]);
+ assertEquals("newInstance(tensor)", 2, xtd[3]);
+ assertEquals("nElements()", 120, ArrayFuncs.nElements(xtest4));
+ assertEquals("nLElements()", 120L, ArrayFuncs.nLElements(xtest4));
+
+ ArrayFuncs.testPattern(xtest4, (byte) -1);
+
+ assertEquals("testPattern()", (double) -1, xtest4[0][0][0][0], 0);
+ assertEquals("testPattern()", (double) 118, xtest4[4][3][2][1], 0);
+ double[] xtest4x = (double[]) ArrayFuncs.getBaseArray(xtest4);
+
+ assertEquals("getBaseArray()", 2, xtest4x.length);
+
+ double[] x = {1, 2, 3, 4, 5};
+ double[] y = new double[x.length];
+ for (int i = 0; i < y.length; i += 1) {
+ y[i] = x[i] + 1.E-10;
+ }
+
+ assertEquals("eqTest", false, ArrayFuncs.arrayEquals(x, y));
+ assertEquals("eqTest2", true, ArrayFuncs.arrayEquals(x, y, 0., 1.e-9));
+ assertEquals("eqTest3", true, ArrayFuncs.arrayEquals(x, y, 1.E-5, 1.e-9));
+ assertEquals("eqTest4", false, ArrayFuncs.arrayEquals(x, y, 0., 1.e-11));
+ assertEquals("eqTest5", false, ArrayFuncs.arrayEquals(x, y, 1.E-5, 0.));
+
+ float[] fx = {1, 2, 3, 4, 5};
+ float[] fy = new float[fx.length];
+ for (int i = 0; i < fy.length; i += 1) {
+ fy[i] = fx[i] + 1.E-5F;
+ }
+
+ assertEquals("eqTest6", false, ArrayFuncs.arrayEquals(fx, fy));
+ assertEquals("eqTest7", true, ArrayFuncs.arrayEquals(fx, fy, 1.E-4, 0.));
+ assertEquals("eqTest8", false, ArrayFuncs.arrayEquals(fx, fy, 1.E-6, 0.));
+ assertEquals("eqTest9", false, ArrayFuncs.arrayEquals(fx, fy, 0., 0.));
+ assertEquals("eqTest10", false, ArrayFuncs.arrayEquals(fx, fy, 0., 1.E-4));
+
+ }
+}
--- /dev/null
+/*\r
+ * ArrayFuncsTest.java\r
+ * JUnit based test\r
+ *\r
+ * Created on December 2, 2007, 7:19 PM\r
+ */\r
+package nom.tam.util.test;\r
+\r
+import junit.framework.*;\r
+import java.lang.reflect.*;\r
+import java.util.Arrays;\r
+import nom.tam.util.ArrayFuncs;\r
+\r
+/**\r
+ *\r
+ * @author Thomas McGlynn\r
+ */\r
+public class ArrayFuncsTest extends TestCase {\r
+\r
+ public ArrayFuncsTest(String testName) {\r
+ super(testName);\r
+ }\r
+\r
+ protected void setUp() throws Exception {\r
+ }\r
+\r
+ protected void tearDown() throws Exception {\r
+ }\r
+\r
+ /**\r
+ * Test of computeSize method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testComputeSize() {\r
+ System.out.println("computeSize");\r
+\r
+ Object o = null;\r
+\r
+ int expResult = 0;\r
+ int result = ArrayFuncs.computeSize(o);\r
+ assertEquals(expResult, result);\r
+ int[][] x = new int[2][3];\r
+ assertEquals(ArrayFuncs.computeSize(x), 24);\r
+ assertEquals(ArrayFuncs.computeSize(new double[3]), 24);\r
+ assertEquals(ArrayFuncs.computeSize("1234"), 4);\r
+ assertEquals(ArrayFuncs.computeSize(new Object()), 0);\r
+ assertEquals(ArrayFuncs.computeSize(new Double[5]), 0);\r
+ assertEquals(ArrayFuncs.computeSize(new Double[]{\r
+ new Double(0), new Double(1), new Double(2)}), 24);\r
+ assertEquals(ArrayFuncs.computeLSize(x), 24);\r
+ assertEquals(ArrayFuncs.computeLSize(new double[3]), 24);\r
+ assertEquals(ArrayFuncs.computeLSize("1234"), 4);\r
+ assertEquals(ArrayFuncs.computeLSize(new Object()), 0);\r
+ assertEquals(ArrayFuncs.computeLSize(new Double[5]), 0);\r
+ assertEquals(ArrayFuncs.computeLSize(new Double[]{\r
+ new Double(0), new Double(1), new Double(2)}), 24);\r
+ }\r
+\r
+ /**\r
+ * Test of nElements method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testNElements() {\r
+ System.out.println("nElements");\r
+\r
+ Object o = null;\r
+\r
+ assertEquals(ArrayFuncs.nElements(null), 0);\r
+ assertEquals(ArrayFuncs.nElements(new int[2][2][3]), 12);\r
+ assertEquals(ArrayFuncs.nLElements(null), 0);\r
+ assertEquals(ArrayFuncs.nLElements(new int[2][2][3]), 12);\r
+ }\r
+\r
+ /**\r
+ * Test of deepClone method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testDeepClone() {\r
+ int[][] test = {{0, 1}, {2, 3}, {4, 5}};\r
+ int[][] result = (int[][]) nom.tam.util.ArrayFuncs.deepClone(test);\r
+\r
+ for (int i = 0; i < test.length; i += 1) {\r
+ for (int j = 0; j < test[i].length; j += 1) {\r
+ assertEquals(test[i][j], result[i][j]);\r
+ }\r
+ }\r
+ }\r
+\r
+ public class CloneTest implements Cloneable {\r
+\r
+ public int value = 2;\r
+\r
+ public Object clone() {\r
+ try {\r
+ return super.clone();\r
+ } catch (Exception e) {\r
+ }\r
+ return null;\r
+ }\r
+\r
+ public boolean equals(Object x) {\r
+ return (x instanceof CloneTest)\r
+ && (((CloneTest) x).value == this.value);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Test of genericClone method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGenericClone() {\r
+ System.out.println("genericClone");\r
+\r
+ Object o = new int[]{1, 2, 3};\r
+\r
+ Object result = nom.tam.util.ArrayFuncs.genericClone(o);\r
+\r
+ int[] x = (int[]) o;\r
+ int[] y = (int[]) result;\r
+ for (int i = 0; i < x.length; i += 1) {\r
+ assertEquals(x[i], y[i]);\r
+ }\r
+ CloneTest xa = new CloneTest();\r
+ xa.value = 4;\r
+ Object ya = ArrayFuncs.genericClone(xa);\r
+ assertTrue(xa != ya);\r
+ assertTrue(xa.equals(ya));\r
+ }\r
+\r
+ /**\r
+ * Test of copyArray method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testCopyArray() {\r
+ System.out.println("copyArray");\r
+\r
+ double[] start = new double[]{1, 2, 3, 4, 5, 6};\r
+ double[] finish = new double[6];\r
+ ArrayFuncs.copyArray(start, finish);\r
+ assertTrue(ArrayFuncs.arrayEquals(start, finish));\r
+ }\r
+\r
+ /**\r
+ * Test of getDimensions method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGetDimensions() {\r
+ System.out.println("getDimensions");\r
+\r
+ Object o = null;\r
+ int[] expResult = null;\r
+ int[] result = nom.tam.util.ArrayFuncs.getDimensions(o);\r
+ assertEquals(expResult, result);\r
+\r
+ assertEquals(ArrayFuncs.getDimensions(new Integer(0)).length, 0);\r
+ int[][] test = new int[2][3];\r
+ int[] dims = ArrayFuncs.getDimensions(test);\r
+ assertEquals(dims.length, 2);\r
+ assertEquals(dims[0], 2);\r
+ assertEquals(dims[1], 3);\r
+ }\r
+\r
+ /**\r
+ * Test of getBaseArray method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGetBaseArray() {\r
+\r
+ int[][][] test = new int[2][3][4];\r
+ byte b = 0;\r
+ ArrayFuncs.testPattern(test, b);\r
+\r
+ assertEquals(ArrayFuncs.getBaseArray(test), test[0][0]);\r
+ }\r
+\r
+ /**\r
+ * Test of getBaseClass method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGetBaseClass() {\r
+ System.out.println("getBaseClass");\r
+\r
+ assertEquals(ArrayFuncs.getBaseClass(new int[2][3]), int.class);\r
+ assertEquals(ArrayFuncs.getBaseClass(new String[3]), String.class);\r
+ }\r
+\r
+ /**\r
+ * Test of getBaseLength method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGetBaseLength() {\r
+\r
+ assertEquals(ArrayFuncs.getBaseLength(new int[2][3]), 4);\r
+ assertEquals(ArrayFuncs.getBaseLength(new double[2][3]), 8);\r
+ assertEquals(ArrayFuncs.getBaseLength(new byte[2][3]), 1);\r
+ assertEquals(ArrayFuncs.getBaseLength(new short[2][3]), 2);\r
+ assertEquals(ArrayFuncs.getBaseLength(new int[2][3]), 4);\r
+ assertEquals(ArrayFuncs.getBaseLength(new char[2][3]), 2);\r
+ assertEquals(ArrayFuncs.getBaseLength(new float[2][3]), 4);\r
+ assertEquals(ArrayFuncs.getBaseLength(new boolean[2][3]), 1);\r
+ assertEquals(ArrayFuncs.getBaseLength(new Object[2][3]), -1);\r
+ }\r
+\r
+ /**\r
+ * Test of generateArray method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testGenerateArray() {\r
+ System.out.println("generateArray");\r
+\r
+ Class baseType = int.class;\r
+ int[] dims = {2, 3, 4};\r
+\r
+ Object result = nom.tam.util.ArrayFuncs.generateArray(baseType, dims);\r
+ assertEquals(result.getClass(), int[][][].class);\r
+ int[][][] x = (int[][][]) result;\r
+ assertEquals(x.length, 2);\r
+ assertEquals(x[0].length, 3);\r
+ assertEquals(x[0][0].length, 4);\r
+\r
+ }\r
+\r
+ /**\r
+ * Test of testPattern method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testTestPattern() {\r
+ System.out.println("testPattern");\r
+\r
+ byte start = 2;\r
+ int[] arr = new int[8];\r
+\r
+ byte expResult = 0;\r
+ byte result = nom.tam.util.ArrayFuncs.testPattern(arr, start);\r
+ assertEquals(result, (byte) (start + arr.length));\r
+ assertEquals(start, arr[0]);\r
+ assertEquals(start + arr.length - 1, arr[arr.length - 1]);\r
+ }\r
+\r
+ /**\r
+ * Test of flatten method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testFlatten() {\r
+ System.out.println("flatten");\r
+\r
+ int[][][] test = new int[2][3][4];\r
+\r
+ int[] result = (int[]) ArrayFuncs.flatten(test);\r
+ assertEquals(result.length, 24);\r
+ }\r
+\r
+ /**\r
+ * Test of curl method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testCurl() {\r
+ System.out.println("curl");\r
+\r
+ int[] dimens = new int[]{2, 3, 4};\r
+ int[] test = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};\r
+\r
+ int[][][] res = (int[][][]) nom.tam.util.ArrayFuncs.curl(test, dimens);\r
+ assertEquals(res.length, 2);\r
+ assertEquals(res[0].length, 3);\r
+ assertEquals(res[0][0].length, 4);\r
+ assertEquals(res[0][0][0], 0);\r
+ assertEquals(res[0][0][3], 3);\r
+ assertEquals(res[1][2][3], 23);\r
+ }\r
+\r
+ /**\r
+ * Test of mimicArray method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testMimicArray() {\r
+ System.out.println("mimicArray");\r
+\r
+ int[][] array = new int[2][3];\r
+ Class newType = double.class;\r
+\r
+ double[][] result = (double[][]) nom.tam.util.ArrayFuncs.mimicArray(array, newType);\r
+ assertEquals(result.length, array.length);\r
+ assertEquals(result[0].length, array[0].length);\r
+ }\r
+\r
+ /**\r
+ * Test of convertArray method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testConvertArray() {\r
+ System.out.println("convertArray");\r
+\r
+ int[][] array = {{1, 2, 3}, {4, 5, 6}};\r
+ Class newType = double.class;\r
+\r
+ boolean reuse = true;\r
+ double[][] dres = (double[][]) ArrayFuncs.convertArray(array, newType, reuse);\r
+ assertEquals(dres.length, array.length);\r
+ assertEquals(dres[0].length, array[0].length);\r
+\r
+ newType = int.class;\r
+ int[][] ires = (int[][]) ArrayFuncs.convertArray(array, newType, true);\r
+ assertEquals(array, ires);\r
+\r
+ ires = (int[][]) ArrayFuncs.convertArray(array, newType, false);\r
+ assertNotSame(array, ires);\r
+ assertTrue(ArrayFuncs.arrayEquals(array, ires));\r
+ }\r
+\r
+ /**\r
+ * Test of copyInto method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testCopyInto() {\r
+ System.out.println("copyInto");\r
+\r
+ int[][] x = {{2, 3, 4}, {5, 6, 7}};\r
+ double[][] y = new double[2][3];\r
+\r
+ ArrayFuncs.copyInto(x, y);\r
+\r
+ assertEquals((double) x[0][0], y[0][0]);\r
+ assertEquals((double) x[1][2], y[1][2]);\r
+ }\r
+\r
+ /**\r
+ * Test of arrayEquals method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testArrayEquals() {\r
+ System.out.println("arrayEquals");\r
+\r
+ int[][] x = {{1, 2, 3}, {4, 5, 6}};\r
+ int[][] y = {{1, 2, 3}, {4, 5, 6}};\r
+ int[][] z = {{1, 2, 3}, {4, 5, 7}};\r
+ int[][] t = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};\r
+\r
+\r
+ assertTrue(ArrayFuncs.arrayEquals(null, null));\r
+ assertFalse(ArrayFuncs.arrayEquals(null, new int[2]));\r
+ assertTrue(ArrayFuncs.arrayEquals(x, y));\r
+ assertFalse(ArrayFuncs.arrayEquals(x, z));\r
+ assertFalse(ArrayFuncs.arrayEquals(x, t));\r
+ assertTrue(ArrayFuncs.arrayEquals(x[0], z[0]));\r
+ }\r
+\r
+ /**\r
+ * Test of doubleArrayEquals method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testDoubleArrayEquals() {\r
+\r
+ double x[] = {1, 2, 3};\r
+ double y[] = {1, 2, 3};\r
+ System.out.println("doubleArrayEquals");\r
+\r
+ double tol = 0.0;\r
+\r
+ assertTrue(ArrayFuncs.doubleArrayEquals(x, y, tol));\r
+ x[0] += 1.e-14;\r
+ assertFalse(ArrayFuncs.doubleArrayEquals(x, y, tol));\r
+ tol = 1.e-13;\r
+ assertTrue(ArrayFuncs.doubleArrayEquals(x, y, tol));\r
+ }\r
+\r
+ /**\r
+ * Test of floatArrayEquals method, of class nom.tam.util.ArrayFuncs.\r
+ */\r
+ public void testFloatArrayEquals() {\r
+ float x[] = {1f, 2f, 3f};\r
+ float y[] = {1f, 2f, 3f};\r
+ System.out.println("floatArrayEquals");\r
+\r
+ float tol = 0.0F;\r
+ assertTrue(ArrayFuncs.floatArrayEquals(x, y, tol));\r
+ x[0] += 1.e-6f;\r
+ assertFalse(ArrayFuncs.floatArrayEquals(x, y, tol));\r
+ tol = 1.e-5f;\r
+ assertTrue(ArrayFuncs.floatArrayEquals(x, y, tol));\r
+ }\r
+}\r
--- /dev/null
+package nom.tam.util.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+import nom.tam.util.BufferedFile;
+import nom.tam.util.BufferedDataInputStream;
+import java.io.FileInputStream;
+
+public class BigFileTest {
+
+ @Test
+ public void test() throws Exception {
+ try {
+ // First create a 3 GB file.
+ String fname = System.getenv("BIGFILETEST");
+ if (fname == null) {
+ System.out.println("BIGFILETEST environment not set. Returning without test");
+ return;
+ }
+ System.out.println("Big file test. Takes quite a while.");
+ byte[] buf = new byte[100000000]; // 100 MB
+ BufferedFile bf = new BufferedFile(fname, "rw");
+ byte sample = 13;
+
+ for (int i = 0; i < 30; i += 1) {
+ bf.write(buf); // 30 x 100 MB = 3 GB.
+ if (i == 24) {
+ bf.write(new byte[]{sample});
+ } // Add a marker.
+ }
+ bf.close();
+
+ // Now try to skip within the file.
+ bf = new BufferedFile(fname, "r");
+ long skip = 2500000000L; // 2.5 G
+
+ long val1 = bf.skipBytes(skip);
+ long val2 = bf.getFilePointer();
+ int val = bf.read();
+ bf.close();
+
+ assertEquals("SkipResult", skip, val1);
+ assertEquals("SkipPos", skip, val2);
+ assertEquals("SkipVal", (int) sample, val);
+
+ BufferedDataInputStream bdis = new BufferedDataInputStream(
+ new FileInputStream(fname));
+ val1 = bdis.skipBytes(skip);
+ val = bdis.read();
+ bdis.close();
+ assertEquals("SSkipResult", skip, val1);
+ assertEquals("SSkipVal", (int) sample, val);
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ throw e;
+ }
+ }
+}
--- /dev/null
+/* Copyright: Thomas McGlynn 1999.\r
+ * This code may be used for any purpose, non-commercial\r
+ * or commercial so long as this copyright notice is retained\r
+ * in the source code or included in or referred to in any\r
+ * derived software.\r
+ */\r
+package nom.tam.util.test;\r
+\r
+import org.junit.Test;\r
+import static org.junit.Assert.assertEquals;\r
+import junit.framework.JUnit4TestAdapter;\r
+\r
+import nom.tam.util.*;\r
+import nom.tam.util.ArrayFuncs;\r
+import java.io.*;\r
+\r
+/** This class provides runs tests of the\r
+ * BufferedI/O classes: BufferedFile, BufferedDataInputStream\r
+ * and BufferedDataOutputStream. A limited comparison\r
+ * to the standard I/O classes can also be made.\r
+ * <p>\r
+ * Input and output of all primitive scalar and array types is\r
+ * tested, however input and output of String data is not.\r
+ * Users may choose to test the BufferedFile class, the\r
+ * BufferedDataXPUT classes array methods, the BufferedDataXPUT\r
+ * classes using the methods of DataXput, the traditional\r
+ * I/O classes, or any combination thereof.\r
+ */\r
+public class BufferedFileTester {\r
+\r
+ /** Usage: java nom.tam.util.test.BufferedFileTester file [dim [iter [flags]]]\r
+ * where\r
+ * file is the file to be read and written.\r
+ * dim is the dimension of the arrays to be written.\r
+ * iter is the number of times each array is written.\r
+ * flags a string indicating what I/O to test\r
+ * O -- test old I/O (RandomAccessFile and standard streams)\r
+ * R -- BufferedFile (i.e., random access)\r
+ * S -- BufferedDataXPutStream\r
+ * X -- BufferedDataXPutStream using standard methods\r
+ */\r
+ public static void main(String[] args) throws Exception {\r
+\r
+ String filename = args[0];\r
+ int dim = 1000;\r
+ if (args.length > 1) {\r
+ dim = Integer.parseInt(args[1]);\r
+ }\r
+ int iter = 1;\r
+ if (args.length > 2) {\r
+ iter = Integer.parseInt(args[2]);\r
+ }\r
+\r
+ System.out.println("Allocating arrays.");\r
+ double[] db = new double[dim];\r
+ float[] fl = new float[dim];\r
+ int[] in = new int[dim];\r
+ long[] ln = new long[dim];\r
+ short[] sh = new short[dim];\r
+ byte[] by = new byte[dim];\r
+ char[] ch = new char[dim];\r
+ boolean[] bl = new boolean[dim];\r
+\r
+ System.out.println("Initializing arrays -- may take a while");\r
+ int sign = 1;\r
+ for (int i = 0; i < dim; i += 1) {\r
+\r
+\r
+ double x = sign * Math.pow(10., 20 * Math.random() - 10);\r
+ db[i] = x;\r
+ fl[i] = (float) x;\r
+\r
+ if (Math.abs(x) < 1) {\r
+ x = 1 / x;\r
+ }\r
+\r
+ in[i] = (int) x;\r
+ ln[i] = (long) x;\r
+ sh[i] = (short) x;\r
+ by[i] = (byte) x;\r
+ ch[i] = (char) x;\r
+ bl[i] = x > 0;\r
+\r
+ sign = -sign;\r
+ }\r
+\r
+ // Ensure special values are tested.\r
+\r
+ by[0] = Byte.MIN_VALUE;\r
+ by[1] = Byte.MAX_VALUE;\r
+ by[2] = 0;\r
+ ch[0] = Character.MIN_VALUE;\r
+ ch[1] = Character.MAX_VALUE;\r
+ ch[2] = 0;\r
+ sh[0] = Short.MAX_VALUE;\r
+ sh[1] = Short.MIN_VALUE;\r
+ sh[0] = 0;\r
+ in[0] = Integer.MAX_VALUE;\r
+ in[1] = Integer.MIN_VALUE;\r
+ in[2] = 0;\r
+ ln[0] = Long.MIN_VALUE;\r
+ ln[1] = Long.MAX_VALUE;\r
+ ln[2] = 0;\r
+ fl[0] = Float.MIN_VALUE;\r
+ fl[1] = Float.MAX_VALUE;\r
+ fl[2] = Float.POSITIVE_INFINITY;\r
+ fl[3] = Float.NEGATIVE_INFINITY;\r
+ fl[4] = Float.NaN;\r
+ fl[5] = 0;\r
+ db[0] = Double.MIN_VALUE;\r
+ db[1] = Double.MAX_VALUE;\r
+ db[2] = Double.POSITIVE_INFINITY;\r
+ db[3] = Double.NEGATIVE_INFINITY;\r
+ db[4] = Double.NaN;\r
+ db[5] = 0;\r
+\r
+ double[] db2 = new double[dim];\r
+ float[] fl2 = new float[dim];\r
+ int[] in2 = new int[dim];\r
+ long[] ln2 = new long[dim];\r
+ short[] sh2 = new short[dim];\r
+ byte[] by2 = new byte[dim];\r
+ char[] ch2 = new char[dim];\r
+ boolean[] bl2 = new boolean[dim];\r
+\r
+ int[][][][] multi = new int[10][10][10][10];\r
+ int[][][][] multi2 = new int[10][10][10][10];\r
+ for (int i = 0; i < 10; i += 1) {\r
+ multi[i][i][i][i] = i;\r
+ }\r
+\r
+ if (args.length < 4 || args[3].indexOf('O') >= 0) {\r
+ standardFileTest(filename, iter, in, in2);\r
+ standardStreamTest(filename, iter, in, in2);\r
+ }\r
+\r
+ if (args.length < 4 || args[3].indexOf('X') >= 0) {\r
+ buffStreamSimpleTest(filename, iter, in, in2);\r
+ }\r
+\r
+ if (args.length < 4 || args[3].indexOf('R') >= 0) {\r
+ bufferedFileTest(filename, iter, db, db2, fl, fl2, ln, ln2, in, in2, sh, sh2,\r
+ ch, ch2, by, by2, bl, bl2, multi, multi2);\r
+ }\r
+\r
+\r
+ if (args.length < 4 || args[3].indexOf('S') >= 0) {\r
+ bufferedStreamTest(filename, iter, db, db2, fl, fl2, ln, ln2, in, in2, sh, sh2,\r
+ ch, ch2, by, by2, bl, bl2, multi, multi2);\r
+ }\r
+ }\r
+\r
+ public static void standardFileTest(String filename, int iter, int[] in, int[] in2)\r
+ throws Exception {\r
+ System.out.println("Standard I/O library: java.io.RandomAccessFile");\r
+\r
+ RandomAccessFile f = new RandomAccessFile(filename, "rw");\r
+ int dim = in.length;\r
+ resetTime();\r
+ f.seek(0);\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ f.writeInt(in[i]);\r
+ }\r
+ }\r
+ System.out.println(" RAF Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ f.seek(0);\r
+ resetTime();\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ in2[i] = f.readInt();\r
+ }\r
+ }\r
+ System.out.println(" RAF Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+\r
+\r
+ synchronized (f) {\r
+ f.seek(0);\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ f.writeInt(in[i]);\r
+ }\r
+ }\r
+ System.out.println(" SyncRAF Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ f.seek(0);\r
+ resetTime();\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ in2[i] = f.readInt();\r
+ }\r
+ }\r
+ }\r
+ System.out.println(" SyncRAF Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ }\r
+\r
+ public static void standardStreamTest(String filename, int iter, int[] in, int[] in2)\r
+ throws Exception {\r
+ System.out.println("Standard I/O library: java.io.DataXXputStream");\r
+ System.out.println(" layered atop a BufferedXXputStream");\r
+\r
+ DataOutputStream f = new DataOutputStream(new BufferedOutputStream(\r
+ new FileOutputStream(filename), 32768));\r
+ resetTime();\r
+ int dim = in.length;\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ f.writeInt(in[i]);\r
+ }\r
+ }\r
+ f.flush();\r
+ f.close();\r
+ System.out.println(" DIS Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ DataInputStream is = new DataInputStream(new BufferedInputStream(\r
+ new FileInputStream(filename), 32768));\r
+ resetTime();\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ in2[i] = is.readInt();\r
+ }\r
+ }\r
+ System.out.println(" DIS Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+\r
+\r
+ f = new DataOutputStream(new BufferedOutputStream(\r
+ new FileOutputStream(filename), 32768));\r
+ resetTime();\r
+ dim = in.length;\r
+ synchronized (f) {\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ f.writeInt(in[i]);\r
+ }\r
+ }\r
+ f.flush();\r
+ f.close();\r
+ System.out.println(" DIS Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ is = new DataInputStream(new BufferedInputStream(\r
+ new FileInputStream(filename), 32768));\r
+ resetTime();\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ in2[i] = is.readInt();\r
+ }\r
+ }\r
+ }\r
+ System.out.println(" DIS Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ }\r
+\r
+ public static void buffStreamSimpleTest(String filename, int iter, int[] in, int[] in2)\r
+ throws Exception {\r
+\r
+ System.out.println("New libraries: nom.tam.BufferedDataXXputStream");\r
+ System.out.println(" Using non-array I/O");\r
+ BufferedDataOutputStream f = new BufferedDataOutputStream(\r
+ new FileOutputStream(filename), 32768);\r
+ resetTime();\r
+ int dim = in.length;\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ f.writeInt(in[i]);\r
+ }\r
+ }\r
+ f.flush();\r
+ f.close();\r
+ System.out.println(" BDS Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ BufferedDataInputStream is = new BufferedDataInputStream(new BufferedInputStream(\r
+ new FileInputStream(filename), 32768));\r
+ resetTime();\r
+ for (int j = 0; j < iter; j += 1) {\r
+ for (int i = 0; i < dim; i += 1) {\r
+ in2[i] = is.readInt();\r
+ }\r
+ }\r
+ System.out.println(" BDS Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ }\r
+\r
+ public static void bufferedStreamTest(String filename, int iter, double[] db, double[] db2,\r
+ float[] fl, float[] fl2, long[] ln, long[] ln2,\r
+ int[] in, int[] in2, short[] sh, short[] sh2,\r
+ char[] ch, char[] ch2, byte[] by, byte[] by2,\r
+ boolean[] bl, boolean[] bl2,\r
+ int[][][][] multi, int[][][][] multi2) throws Exception {\r
+\r
+ int dim = db.length;\r
+\r
+ double ds = Math.random() - 0.5;\r
+ double ds2;\r
+ float fs = (float) (Math.random() - 0.5);\r
+ float fs2;\r
+ int is = (int) (1000000 * (Math.random() - 500000));\r
+ int is2;\r
+ long ls = (long) (100000000000L * (Math.random() - 50000000000L));\r
+ long ls2;\r
+ short ss = (short) (60000 * (Math.random() - 30000));\r
+ short ss2;\r
+ char cs = (char) (60000 * Math.random());\r
+ char cs2;\r
+ byte bs = (byte) (256 * Math.random() - 128);\r
+ byte bs2;\r
+ boolean bls = (Math.random() > 0.5);\r
+ boolean bls2;\r
+ System.out.println("New libraries: nom.tam.util.BufferedDataXXputStream");\r
+ System.out.println(" Using array I/O methods");\r
+\r
+ {\r
+ BufferedDataOutputStream f = new BufferedDataOutputStream(new FileOutputStream(filename));\r
+\r
+ resetTime();\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(db);\r
+ }\r
+ System.out.println(" BDS Dbl write: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(fl);\r
+ }\r
+ System.out.println(" BDS Flt write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(in);\r
+ }\r
+ System.out.println(" BDS Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(ln);\r
+ }\r
+ System.out.println(" BDS Lng write: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(sh);\r
+ }\r
+ System.out.println(" BDS Sht write: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(ch);\r
+ }\r
+ System.out.println(" BDS Chr write: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray((byte[]) by);\r
+ }\r
+ System.out.println(" BDS Byt write: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(bl);\r
+ }\r
+ System.out.println(" BDS Boo write: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ f.writeByte(bs);\r
+ f.writeChar(cs);\r
+ f.writeShort(ss);\r
+ f.writeInt(is);\r
+ f.writeLong(ls);\r
+ f.writeFloat(fs);\r
+ f.writeDouble(ds);\r
+ f.writeBoolean(bls);\r
+\r
+ f.writeArray(multi);\r
+ f.flush();\r
+ f.close();\r
+ }\r
+\r
+ {\r
+ BufferedDataInputStream f = new BufferedDataInputStream(new FileInputStream(filename));\r
+\r
+\r
+ resetTime();\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(db2);\r
+ }\r
+ System.out.println(" BDS Dbl read: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(fl2);\r
+ }\r
+ System.out.println(" BDS Flt read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(in2);\r
+ }\r
+ System.out.println(" BDS Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(ln2);\r
+ }\r
+ System.out.println(" BDS Lng read: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(sh2);\r
+ }\r
+ System.out.println(" BDS Sht read: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(ch2);\r
+ }\r
+ System.out.println(" BDS Chr read: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray((byte[]) by2);\r
+ }\r
+ System.out.println(" BDS Byt read: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(bl2);\r
+ }\r
+ System.out.println(" BDS Boo read: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ bs2 = f.readByte();\r
+ cs2 = f.readChar();\r
+ ss2 = f.readShort();\r
+ is2 = f.readInt();\r
+ ls2 = f.readLong();\r
+ fs2 = f.readFloat();\r
+ ds2 = f.readDouble();\r
+ bls2 = f.readBoolean();\r
+\r
+ for (int i = 0; i < 10; i += 1) {\r
+ multi2[i][i][i][i] = 0;\r
+ }\r
+\r
+ // Now read only pieces of the multidimensional array.\r
+ for (int i = 0; i < 5; i += 1) {\r
+ System.out.println("Multiread:" + i);\r
+ // Skip the odd initial indices and\r
+ // read the evens.\r
+ f.skipBytes(4000);\r
+ f.readLArray(multi2[2 * i + 1]);\r
+ }\r
+ f.close();\r
+ }\r
+\r
+ System.out.println("Stream Verification:");\r
+ System.out.println(" An error should be reported for double and float NaN's");\r
+ System.out.println(" Arrays:");\r
+\r
+ for (int i = 0; i < dim; i += 1) {\r
+\r
+ if (db[i] != db2[i]) {\r
+ System.out.println(" Double error at " + i + " " + db[i] + " " + db2[i]);\r
+ }\r
+ if (fl[i] != fl2[i]) {\r
+ System.out.println(" Float error at " + i + " " + fl[i] + " " + fl2[i]);\r
+ }\r
+ if (in[i] != in2[i]) {\r
+ System.out.println(" Int error at " + i + " " + in[i] + " " + in2[i]);\r
+ }\r
+ if (ln[i] != ln2[i]) {\r
+ System.out.println(" Long error at " + i + " " + ln[i] + " " + ln2[i]);\r
+ }\r
+ if (sh[i] != sh2[i]) {\r
+ System.out.println(" Short error at " + i + " " + sh[i] + " " + sh2[i]);\r
+ }\r
+ if (ch[i] != ch2[i]) {\r
+ System.out.println(" Char error at " + i + " " + (int) ch[i] + " " + (int) ch2[i]);\r
+ }\r
+ if (by[i] != by2[i]) {\r
+ System.out.println(" Byte error at " + i + " " + by[i] + " " + by2[i]);\r
+ }\r
+ if (bl[i] != bl2[i]) {\r
+ System.out.println(" Bool error at " + i + " " + bl[i] + " " + bl2[i]);\r
+ }\r
+ }\r
+\r
+ System.out.println(" Scalars:");\r
+ // Check the scalars.\r
+ if (bls != bls2) {\r
+ System.out.println(" Bool Scalar mismatch:" + bls + " " + bls2);\r
+ }\r
+ if (bs != bs2) {\r
+ System.out.println(" Byte Scalar mismatch:" + bs + " " + bs2);\r
+ }\r
+ if (cs != cs2) {\r
+ System.out.println(" Char Scalar mismatch:" + (int) cs + " " + (int) cs2);\r
+ }\r
+ if (ss != ss2) {\r
+ System.out.println(" Short Scalar mismatch:" + ss + " " + ss2);\r
+ }\r
+ if (is != is2) {\r
+ System.out.println(" Int Scalar mismatch:" + is + " " + is2);\r
+ }\r
+ if (ls != ls2) {\r
+ System.out.println(" Long Scalar mismatch:" + ls + " " + ls2);\r
+ }\r
+ if (fs != fs2) {\r
+ System.out.println(" Float Scalar mismatch:" + fs + " " + fs2);\r
+ }\r
+ if (ds != ds2) {\r
+ System.out.println(" Double Scalar mismatch:" + ds + " " + ds2);\r
+ }\r
+\r
+ System.out.println(" Multi: odd rows should match");\r
+ for (int i = 0; i < 10; i += 1) {\r
+ System.out.println(" " + i + " " + multi[i][i][i][i] + " " + multi2[i][i][i][i]);\r
+ }\r
+ System.out.println("Done BufferedStream Tests");\r
+ }\r
+\r
+ public static void bufferedFileTest(String filename, int iter, double[] db, double[] db2,\r
+ float[] fl, float[] fl2, long[] ln, long[] ln2,\r
+ int[] in, int[] in2, short[] sh, short[] sh2,\r
+ char[] ch, char[] ch2, byte[] by, byte[] by2,\r
+ boolean[] bl, boolean[] bl2,\r
+ int[][][][] multi, int[][][][] multi2) throws Exception {\r
+\r
+\r
+ int dim = db.length;\r
+\r
+ double ds = Math.random() - 0.5;\r
+ double ds2;\r
+ float fs = (float) (Math.random() - 0.5);\r
+ float fs2;\r
+ int is = (int) (1000000 * (Math.random() - 500000));\r
+ int is2;\r
+ long ls = (long) (100000000000L * (Math.random() - 50000000000L));\r
+ long ls2;\r
+ short ss = (short) (60000 * (Math.random() - 30000));\r
+ short ss2;\r
+ char cs = (char) (60000 * Math.random());\r
+ char cs2;\r
+ byte bs = (byte) (256 * Math.random() - 128);\r
+ byte bs2;\r
+ boolean bls = (Math.random() > 0.5);\r
+ boolean bls2;\r
+\r
+ System.out.println("New libraries: nom.tam.util.BufferedFile");\r
+ System.out.println(" Using array I/O methods.");\r
+\r
+ BufferedFile f = new BufferedFile(filename, "rw");\r
+\r
+ resetTime();\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(db);\r
+ }\r
+ System.out.println(" BF Dbl write: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(fl);\r
+ }\r
+ System.out.println(" BF Flt write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(in);\r
+ }\r
+ System.out.println(" BF Int write: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(ln);\r
+ }\r
+ System.out.println(" BF Lng write: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(sh);\r
+ }\r
+ System.out.println(" BF Sht write: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(ch);\r
+ }\r
+ System.out.println(" BF Chr write: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(by);\r
+ }\r
+ System.out.println(" BF Byt write: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.writeArray(bl);\r
+ }\r
+ System.out.println(" BF Boo write: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ f.writeByte(bs);\r
+ f.writeChar(cs);\r
+ f.writeShort(ss);\r
+ f.writeInt(is);\r
+ f.writeLong(ls);\r
+ f.writeFloat(fs);\r
+ f.writeDouble(ds);\r
+ f.writeBoolean(bls);\r
+\r
+ f.writeArray(multi);\r
+ f.seek(0);\r
+\r
+\r
+ resetTime();\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(db2);\r
+ }\r
+ System.out.println(" BF Dbl read: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(fl2);\r
+ }\r
+ System.out.println(" BF Flt read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(in2);\r
+ }\r
+ System.out.println(" BF Int read: " + (4 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(ln2);\r
+ }\r
+ System.out.println(" BF Lng read: " + (8 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(sh2);\r
+ }\r
+ System.out.println(" BF Sht read: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(ch2);\r
+ }\r
+ System.out.println(" BF Chr read: " + (2 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(by2);\r
+ }\r
+ System.out.println(" BF Byt read: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+ for (int i = 0; i < iter; i += 1) {\r
+ f.readLArray(bl2);\r
+ }\r
+ System.out.println(" BF Boo read: " + (1 * dim * iter) / (1000 * deltaTime()));\r
+\r
+ bs2 = f.readByte();\r
+ cs2 = f.readChar();\r
+ ss2 = f.readShort();\r
+ is2 = f.readInt();\r
+ ls2 = f.readLong();\r
+ fs2 = f.readFloat();\r
+ ds2 = f.readDouble();\r
+ bls2 = f.readBoolean();\r
+\r
+ // Now read only pieces of the multidimensional array.\r
+ for (int i = 0; i < 5; i += 1) {\r
+ // Skip the odd initial indices and\r
+ // read the evens.\r
+ f.skipBytes(4000);\r
+ f.readLArray(multi2[2 * i + 1]);\r
+ }\r
+\r
+ System.out.println("BufferedFile Verification:");\r
+ System.out.println(" An error should be reported for double and float NaN's");\r
+ System.out.println(" Arrays:");\r
+\r
+ for (int i = 0; i < dim; i += 1) {\r
+\r
+ if (db[i] != db2[i]) {\r
+ System.out.println(" Double error at " + i + " " + db[i] + " " + db2[i]);\r
+ }\r
+ if (fl[i] != fl2[i]) {\r
+ System.out.println(" Float error at " + i + " " + fl[i] + " " + fl2[i]);\r
+ }\r
+ if (in[i] != in2[i]) {\r
+ System.out.println(" Int error at " + i + " " + in[i] + " " + in2[i]);\r
+ }\r
+ if (ln[i] != ln2[i]) {\r
+ System.out.println(" Long error at " + i + " " + ln[i] + " " + ln2[i]);\r
+ }\r
+ if (sh[i] != sh2[i]) {\r
+ System.out.println(" Short error at " + i + " " + sh[i] + " " + sh2[i]);\r
+ }\r
+ if (ch[i] != ch2[i]) {\r
+ System.out.println(" Char error at " + i + " " + (int) ch[i] + " " + (int) ch2[i]);\r
+ }\r
+ if (by[i] != by2[i]) {\r
+ System.out.println(" Byte error at " + i + " " + by[i] + " " + by2[i]);\r
+ }\r
+ if (bl[i] != bl2[i]) {\r
+ System.out.println(" Bool error at " + i + " " + bl[i] + " " + bl2[i]);\r
+ }\r
+ }\r
+\r
+ System.out.println(" Scalars:");\r
+ // Check the scalars.\r
+ if (bls != bls2) {\r
+ System.out.println(" Bool Scalar mismatch:" + bls + " " + bls2);\r
+ }\r
+ if (bs != bs2) {\r
+ System.out.println(" Byte Scalar mismatch:" + bs + " " + bs2);\r
+ }\r
+ if (cs != cs2) {\r
+ System.out.println(" Char Scalar mismatch:" + (int) cs + " " + (int) cs2);\r
+ }\r
+ if (ss != ss2) {\r
+ System.out.println(" Short Scalar mismatch:" + ss + " " + ss2);\r
+ }\r
+ if (is != is2) {\r
+ System.out.println(" Int Scalar mismatch:" + is + " " + is2);\r
+ }\r
+ if (ls != ls2) {\r
+ System.out.println(" Long Scalar mismatch:" + ls + " " + ls2);\r
+ }\r
+ if (fs != fs2) {\r
+ System.out.println(" Float Scalar mismatch:" + fs + " " + fs2);\r
+ }\r
+ if (ds != ds2) {\r
+ System.out.println(" Double Scalar mismatch:" + ds + " " + ds2);\r
+ }\r
+\r
+ System.out.println(" Multi: odd rows should match");\r
+ for (int i = 0; i < 10; i += 1) {\r
+ System.out.println(" " + i + " " + multi[i][i][i][i] + " " + multi2[i][i][i][i]);\r
+ }\r
+ System.out.println("Done BufferedFile Tests");\r
+ }\r
+ static long lastTime;\r
+\r
+ static void resetTime() {\r
+ lastTime = new java.util.Date().getTime();\r
+ }\r
+\r
+ static double deltaTime() {\r
+ long time = lastTime;\r
+ lastTime = new java.util.Date().getTime();\r
+ return (lastTime - time) / 1000.;\r
+ }\r
+\r
+ @Test\r
+ public void testBufferedFile() throws Exception {\r
+\r
+ double[][] td = new double[100][600];\r
+ for (int i = 0; i < 100; i += 1) {\r
+ for (int j = 0; j < 600; j += 1) {\r
+ td[i][j] = i + 2 * j;\r
+ }\r
+ }\r
+ int[][][] ti = new int[5][4][3];\r
+ for (int i = 0; i < 5; i += 1) {\r
+ for (int j = 0; j < 4; j += 1) {\r
+ for (int k = 0; k < 3; k += 1) {\r
+ ti[i][j][k] = i * j * k;\r
+ }\r
+ }\r
+ }\r
+\r
+ float[][] tf = new float[10][];\r
+ for (int i = 0; i < 10; i += 1) {\r
+ tf[i] = new float[i];\r
+ for (int j = 0; j < i; j += 1) {\r
+ tf[i][j] = (float) Math.sin(i * j);\r
+ }\r
+ }\r
+\r
+ boolean[] tb = new boolean[100];\r
+ for (int i = 2; i < 100; i += 1) {\r
+ tb[i] = !tb[i - 1];\r
+ }\r
+\r
+ short[][] ts = new short[5][5];\r
+ ts[2][2] = 222;\r
+\r
+ byte[] tbyte = new byte[1024];\r
+ for (int i = 0; i < tbyte.length; i += 1) {\r
+ tbyte[i] = (byte) i;\r
+ }\r
+\r
+ char[] tc = new char[10];\r
+ tc[3] = 'c';\r
+\r
+ long[][][] tl0 = new long[1][1][1];\r
+ long[][][] tl1 = new long[1][1][0];\r
+\r
+ BufferedFile bf = new BufferedFile("jtest.fil", "rw");\r
+\r
+ bf.writeArray(td);\r
+ bf.writeArray(tf);\r
+ bf.writeArray(ti);\r
+ bf.writeArray(ts);\r
+ bf.writeArray(tb);\r
+ bf.writeArray(tbyte);\r
+ bf.writeArray(tc);\r
+ bf.writeArray(tl0);\r
+ bf.writeArray(tl1);\r
+ bf.writeArray(ts);\r
+\r
+ bf.close();\r
+\r
+ bf = new BufferedFile("jtest.fil", "r");\r
+\r
+ boolean thrown = false;\r
+\r
+ try {\r
+ bf.writeArray(td);\r
+ } catch (Exception e) {\r
+ thrown = true;\r
+ }\r
+ assertEquals("BufferedFile protections", true, thrown);\r
+ try {\r
+ bf.close();\r
+ } catch (Exception e) {\r
+ }\r
+\r
+ bf = new BufferedFile("jtest.fil", "r");\r
+\r
+ testArray(bf, "double", td);\r
+ testArray(bf, "float", tf);\r
+ testArray(bf, "int", ti);\r
+ testArray(bf, "short", ts);\r
+ testArray(bf, "bool", tb);\r
+ testArray(bf, "byte", tbyte);\r
+ testArray(bf, "char", tc);\r
+ testArray(bf, "long1", tl0);\r
+ testArray(bf, "longnull", tl1);\r
+ testArray(bf, "short2", ts);\r
+ }\r
+\r
+ @Test\r
+ public void testBufferedStreams() throws Exception {\r
+\r
+ double[][] td = new double[100][600];\r
+ for (int i = 0; i < 100; i += 1) {\r
+ for (int j = 0; j < 600; j += 1) {\r
+ td[i][j] = i + 2 * j;\r
+ }\r
+ }\r
+ int[][][] ti = new int[5][4][3];\r
+ for (int i = 0; i < 5; i += 1) {\r
+ for (int j = 0; j < 4; j += 1) {\r
+ for (int k = 0; k < 3; k += 1) {\r
+ ti[i][j][k] = i * j * k;\r
+ }\r
+ }\r
+ }\r
+\r
+ float[][] tf = new float[10][];\r
+ for (int i = 0; i < 10; i += 1) {\r
+ tf[i] = new float[i];\r
+ for (int j = 0; j < i; j += 1) {\r
+ tf[i][j] = (float) Math.sin(i * j);\r
+ }\r
+ }\r
+\r
+ boolean[] tb = new boolean[100];\r
+ for (int i = 2; i < 100; i += 1) {\r
+ tb[i] = !tb[i - 1];\r
+ }\r
+\r
+ short[][] ts = new short[5][5];\r
+ ts[2][2] = 222;\r
+\r
+ byte[] tbyte = new byte[1024];\r
+ for (int i = 0; i < tbyte.length; i += 1) {\r
+ tbyte[i] = (byte) i;\r
+ }\r
+\r
+ char[] tc = new char[10];\r
+ tc[3] = 'c';\r
+\r
+ long[][][] tl0 = new long[1][1][1];\r
+ long[][][] tl1 = new long[1][1][0];\r
+\r
+ BufferedDataOutputStream bf = new BufferedDataOutputStream(\r
+ new FileOutputStream("jtest.fil"));\r
+\r
+ bf.writeArray(td);\r
+ bf.writeArray(tf);\r
+ bf.writeArray(ti);\r
+ bf.writeArray(ts);\r
+ bf.writeArray(tb);\r
+ bf.writeArray(tbyte);\r
+ bf.writeArray(tc);\r
+ bf.writeArray(tl0);\r
+ bf.writeArray(tl1);\r
+ bf.writeArray(ts);\r
+\r
+ bf.close();\r
+\r
+ BufferedDataInputStream bi = new BufferedDataInputStream(\r
+ new FileInputStream("jtest.fil"));\r
+\r
+ testArray(bi, "sdouble", td);\r
+ testArray(bi, "sfloat", tf);\r
+ testArray(bi, "sint", ti);\r
+ testArray(bi, "sshort", ts);\r
+ testArray(bi, "sbool", tb);\r
+ testArray(bi, "sbyte", tbyte);\r
+ testArray(bi, "schar", tc);\r
+ testArray(bi, "slong1", tl0);\r
+ testArray(bi, "slongnull", tl1);\r
+ testArray(bi, "sshort2", ts);\r
+ }\r
+\r
+ void testArray(ArrayDataInput bf, String label, Object array) throws Exception {\r
+ Object newArray = ArrayFuncs.mimicArray(array, ArrayFuncs.getBaseClass(array));\r
+ bf.readLArray(newArray);\r
+ boolean state = ArrayFuncs.arrayEquals(array, newArray);\r
+ assertEquals(label, true, state);\r
+ }\r
+}\r
+\r
+\r
--- /dev/null
+package nom.tam.util.test;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+/** This class tests the ByteFormatter and ByteParser classes.
+ */
+import nom.tam.util.*;
+import java.util.Arrays;
+
+public class ByteFormatParseTest {
+
+ byte[] buffer = new byte[100000];
+ ByteFormatter bf = new ByteFormatter();
+ ByteParser bp = new ByteParser(buffer);
+ int offset = 0;
+ int cnt = 0;
+
+ @Test
+ public void testInt() throws Exception {
+
+ for (int i = 0; i < 10; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+ bp.setOffset(0);
+ assertEquals("IntBlank", 0, bp.getInt(10));
+
+ bf.setAlign(true);
+ bf.setTruncationThrow(false);
+
+ int[] tint = new int[100];
+
+ tint[0] = Integer.MIN_VALUE;
+ tint[1] = Integer.MAX_VALUE;
+ tint[2] = 0;
+
+ for (int i = 0; i < tint.length; i += 1) {
+ tint[i] = (int) (Integer.MAX_VALUE * (2 * (Math.random() - .5)));
+ }
+
+
+ // Write 100 numbers
+ int colSize = 12;
+ while (cnt < tint.length) {
+ offset = bf.format(tint[cnt], buffer, offset, colSize);
+ cnt += 1;
+ if (cnt % 8 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+ // Now see if we can get them back
+ bp.setOffset(0);
+ for (int i = 0; i < tint.length; i += 1) {
+
+ int chk = bp.getInt(colSize);
+
+ assertEquals("IntegersRA", chk, tint[i]);
+ if ((i + 1) % 8 == 0) {
+ bp.skip(1);
+ }
+ }
+
+ // Now do it with left-aligned numbers.
+ bf.setAlign(false);
+ bp.setFillFields(true);
+ offset = 0;
+ colSize = 12;
+ cnt = 0;
+ offset = 0;
+ while (cnt < tint.length) {
+ int oldOffset = offset;
+ offset = bf.format(tint[cnt], buffer, offset, colSize);
+ int nb = colSize - (offset - oldOffset);
+ if (nb > 0) {
+ offset = bf.alignFill(buffer, offset, nb);
+ }
+ cnt += 1;
+ if (cnt % 8 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+ // Now see if we can get them back
+ bp.setOffset(0);
+ for (int i = 0; i < tint.length; i += 1) {
+
+ int chk = bp.getInt(colSize);
+
+ assertEquals("IntegersLA", chk, tint[i]);
+ if ((i + 1) % 8 == 0) {
+ bp.skip(1);
+ }
+ }
+
+ offset = 0;
+ colSize = 12;
+ cnt = 0;
+ offset = 0;
+ while (cnt < tint.length) {
+ offset = bf.format(tint[cnt], buffer, offset, colSize);
+ cnt += 1;
+ if (cnt % 8 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+ String myStr = new String(buffer, 0, offset);
+ assertEquals("No spaces", -1, myStr.indexOf(" "));
+
+ bf.setAlign(false);
+
+ offset = 0;
+ colSize = 12;
+ cnt = 0;
+ offset = 0;
+ while (cnt < tint.length) {
+ offset = bf.format(tint[cnt], buffer, offset, colSize);
+ offset = bf.format(" ", buffer, offset, 1);
+ cnt += 1;
+ }
+ myStr = new String(buffer, 0, offset);
+ String[] array = myStr.split(" ");
+
+ assertEquals("Split size", 100, array.length);
+
+ for (int i = 0; i < array.length; i += 1) {
+ assertEquals("Parse token", tint[i], Integer.parseInt(array[i]));
+ }
+
+
+ bf.setTruncationThrow(false);
+
+ int val = 1;
+ Arrays.fill(buffer, (byte) ' ');
+
+ for (int i = 0; i < 10; i += 1) {
+ offset = bf.format(val, buffer, 0, 6);
+ String test = (val + " ").substring(0, 6);
+ if (i < 6) {
+ assertEquals("TestTrunc" + i, test, new String(buffer, 0, 6));
+ } else {
+ assertEquals("TestTrunc" + i, "******", new String(buffer, 0, 6));
+ }
+ val *= 10;
+ }
+
+ bf.setTruncationThrow(true);
+ val = 1;
+ for (int i = 0; i < 10; i += 1) {
+ boolean thrown = false;
+ try {
+ offset = bf.format(val, buffer, 0, 6);
+ } catch (TruncationException e) {
+ thrown = true;
+ }
+ if (i < 6) {
+ assertEquals("TestTruncThrow" + i, false, thrown);
+ } else {
+ assertEquals("TestTruncThrow" + i, true, thrown);
+ }
+ val *= 10;
+ }
+ }
+
+ @Test
+ public void testLong() throws Exception {
+
+ for (int i = 0; i < 10; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+ bp.setOffset(0);
+ assertEquals("LongBlank", 0L, bp.getLong(10));
+
+ long[] lng = new long[100];
+ for (int i = 0; i < lng.length; i += 1) {
+ lng[i] = (long) (Long.MAX_VALUE * (2 * (Math.random() - 0.5)));
+ }
+
+ lng[0] = Long.MAX_VALUE;
+ lng[1] = Long.MIN_VALUE;
+ lng[2] = 0;
+
+ bf.setTruncationThrow(false);
+ bp.setFillFields(true);
+ bf.setAlign(true);
+ offset = 0;
+ for (int i = 0; i < lng.length; i += 1) {
+ offset = bf.format(lng[i], buffer, offset, 20);
+ if ((i + 1) % 4 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+ bp.setOffset(0);
+
+ for (int i = 0; i < lng.length; i += 1) {
+ assertEquals("Long check", lng[i], bp.getLong(20));
+ if ((i + 1) % 4 == 0) {
+ bp.skip(1);
+ }
+ }
+ }
+
+ @Test
+ public void testFloat() throws Exception {
+
+ for (int i = 0; i < 10; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+ bp.setOffset(0);
+ assertEquals("FloatBlank", 0.f, bp.getFloat(10), 0.);
+
+ float[] flt = new float[100];
+ for (int i = 6; i < flt.length; i += 1) {
+ flt[i] = (float) (2 * (Math.random() - 0.5) * Math.pow(10, 60 * (Math.random() - 0.5)));
+ }
+
+ flt[0] = Float.MAX_VALUE;
+ flt[1] = Float.MIN_VALUE;
+ flt[2] = 0;
+ flt[3] = Float.NaN;
+ flt[4] = Float.POSITIVE_INFINITY;
+ flt[5] = Float.NEGATIVE_INFINITY;
+
+
+ bf.setTruncationThrow(false);
+ bf.setAlign(true);
+
+ offset = 0;
+ cnt = 0;
+
+ while (cnt < flt.length) {
+ offset = bf.format(flt[cnt], buffer, offset, 24);
+ cnt += 1;
+ if (cnt % 4 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+
+ bp.setOffset(0);
+
+ for (int i = 0; i < flt.length; i += 1) {
+
+ float chk = bp.getFloat(24);
+
+ float dx = Math.abs(chk - flt[i]);
+ if (flt[i] != 0) {
+ dx = dx / Math.abs(flt[i]);
+ }
+ if (Float.isNaN(flt[i])) {
+ assertEquals("Float check:" + i, true, Float.isNaN(chk));
+ } else if (Float.isInfinite(flt[i])) {
+ assertEquals("Float check:" + i, flt[i], chk, 0);
+ } else {
+ assertEquals("Float check:" + i, 0., dx, 1.e-6);
+ }
+ if ((i + 1) % 4 == 0) {
+ bp.skip(1);
+ }
+ }
+ }
+
+ @Test
+ public void testDouble() throws Exception {
+
+ for (int i = 0; i < 10; i += 1) {
+ buffer[i] = (byte) ' ';
+ }
+ bp.setOffset(0);
+ assertEquals("DoubBlank", 0., bp.getDouble(10), 0.);
+
+ double[] dbl = new double[100];
+ for (int i = 6; i < dbl.length; i += 1) {
+ dbl[i] = 2 * (Math.random() - 0.5) * Math.pow(10, 60 * (Math.random() - 0.5));
+ }
+
+ dbl[0] = Double.MAX_VALUE;
+ dbl[1] = Double.MIN_VALUE;
+ dbl[2] = 0;
+ dbl[3] = Double.NaN;
+ dbl[4] = Double.POSITIVE_INFINITY;
+ dbl[5] = Double.NEGATIVE_INFINITY;
+
+
+ bf.setTruncationThrow(false);
+ bf.setAlign(true);
+ offset = 0;
+ cnt = 0;
+ while (cnt < dbl.length) {
+ offset = bf.format(dbl[cnt], buffer, offset, 25);
+ cnt += 1;
+ if (cnt % 4 == 0) {
+ offset = bf.format("\n", buffer, offset, 1);
+ }
+ }
+
+
+ bp.setOffset(0);
+ for (int i = 0; i < dbl.length; i += 1) {
+
+ double chk = bp.getDouble(25);
+
+ double dx = Math.abs(chk - dbl[i]);
+ if (dbl[i] != 0) {
+ dx = dx / Math.abs(dbl[i]);
+ }
+ if (Double.isNaN(dbl[i])) {
+ assertEquals("Double check:" + i, true, Double.isNaN(chk));
+ } else if (Double.isInfinite(dbl[i])) {
+ assertEquals("Double check:" + i, dbl[i], chk, 0);
+ } else {
+ assertEquals("Double check:" + i, 0., dx, 1.e-14);
+ }
+
+ if ((i + 1) % 4 == 0) {
+ bp.skip(1);
+ }
+ }
+ }
+
+ @Test
+ public void testBoolean() throws Exception {
+
+ boolean[] btst = new boolean[100];
+ for (int i = 0; i < btst.length; i += 1) {
+ btst[i] = Math.random() > 0.5;
+ }
+ offset = 0;
+ for (int i = 0; i < btst.length; i += 1) {
+ offset = bf.format(btst[i], buffer, offset, 1);
+ offset = bf.format(" ", buffer, offset, 1);
+ }
+
+ bp.setOffset(0);
+ for (int i = 0; i < btst.length; i += 1) {
+ assertEquals("Boolean:" + i, btst[i], bp.getBoolean());
+ }
+ }
+
+ @Test
+ public void testString() throws Exception {
+
+ offset = 0;
+ String bigStr = "abcdefghijklmnopqrstuvwxyz";
+
+ for (int i = 0; i < 100; i += 1) {
+ offset = bf.format(bigStr.substring(i % 27), buffer, offset, 13);
+ offset = bf.format(" ", buffer, offset, 1);
+ }
+
+ bp.setOffset(0);
+ for (int i = 0; i < 100; i += 1) {
+ int ind = i % 27;
+ if (ind > 13) {
+ ind = 13;
+ }
+ String want = bigStr.substring(i % 27);
+ if (want.length() > 13) {
+ want = want.substring(0, 13);
+ }
+ String s = bp.getString(want.length());
+ assertEquals("String:" + i, want, s);
+ bp.skip(1);
+ }
+ }
+}
--- /dev/null
+package nom.tam.util.test;
+
+/* Copyright: Thomas McGlynn 1999.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+import nom.tam.util.HashedList;
+import nom.tam.util.Cursor;
+import java.util.*;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import junit.framework.JUnit4TestAdapter;
+
+/** This class tests and illustrates the use
+ * of the HashedList class. Tests are in three
+ * parts.
+ * <p>
+ * The first section (in testCollection) tests the methods
+ * that are present in the Collection interface.
+ * All of the optional methods of that interface
+ * are supported. This involves tests of the
+ * HashedClass interface directly.
+ * <p>
+ * The second set of tests uses the Iterator (in testIterator)
+ * returned by the iterator() method and tests
+ * the standard Iterator methods to display
+ * and remove rows from the HashedList.
+ * <p>
+ * The third set of tests (in testCursor) tests the extended
+ * capabilities of the HashedListIterator
+ * to add rows to the table, and to work
+ * as a cursor to move in a non-linear fashion
+ * through the list.
+ *
+ */
+public class HashedListTest {
+
+ @Test
+ public void testCollection() {
+
+ HashedList h1 = new HashedList();
+ HashedList h2 = new HashedList();
+
+ Cursor i = h1.iterator(0);
+ Iterator j;
+
+ // Add a few unkeyed rows.
+
+
+ h1.add("Row 1");
+ h1.add("Row 2");
+ h1.add("Row 3");
+
+ assertEquals("Adding unkeyed rows", 3, h1.size());
+
+ assertEquals("Has row 1", true, h1.contains("Row 1"));
+ assertEquals("Has row 2", true, h1.contains("Row 2"));
+ h1.remove("Row 2");
+ assertEquals("Has row 1", true, h1.contains("Row 1"));
+ assertEquals("Has row 2", false, h1.contains("Row 2"));
+
+ assertEquals("Delete unkeyed rows", 2, h1.size());
+
+ h1.clear();
+ assertEquals("Cleared unkeyed rows", 0, h1.size());
+
+ h1.add("key 1", "Row 1");
+ h1.add("key 2", "Row 2");
+ h1.add("key 3", "Row 3");
+
+ assertEquals("Adding keyed rows", 3, h1.size());
+
+ assertEquals("Has Row 1", true, h1.contains("Row 1"));
+ assertEquals("Has key 1", true, h1.containsKey("key 1"));
+ assertEquals("Has Row 2", true, h1.contains("Row 2"));
+ assertEquals("Has key 2", true, h1.containsKey("key 2"));
+ assertEquals("Has Row 3", true, h1.contains("Row 3"));
+ assertEquals("Has key 3", true, h1.containsKey("key 3"));
+
+ h1.removeKey("key 2");
+ assertEquals("Delete keyed row", 2, h1.size());
+ assertEquals("Has Row 1", true, h1.contains("Row 1"));
+ assertEquals("Has key 1", true, h1.containsKey("key 1"));
+ assertEquals("Has Row 2", false, h1.contains("Row 2"));
+ assertEquals("Has key 2", false, h1.containsKey("key 2"));
+ assertEquals("Has Row 3", true, h1.contains("Row 3"));
+ assertEquals("Has key 3", true, h1.containsKey("key 3"));
+
+ h1.clear();
+ assertEquals("Clear keyed rows", 0, h1.size());
+
+ h1.add("key 1", "Row 1");
+ h1.add("key 2", "Row 2");
+ h1.add("key 3", "Row 3");
+ assertEquals("Re-Adding keyed rows", 3, h1.size());
+ assertEquals("Has Row 2", true, h1.contains("Row 2"));
+ assertEquals("Has key 2", true, h1.containsKey("key 2"));
+
+ h2.add("key 4", "Row 4");
+ h2.add("key 5", "Row 5");
+
+ assertEquals("containsAll(beforeAdd)", false, h1.containsAll(h2));
+
+ h1.addAll(h2);
+
+ assertEquals("addAll()", 5, h1.size());
+ assertEquals("containsAll(afterAdd)", true, h1.containsAll(h2));
+ assertEquals("has row 4", true, h1.contains("Row 4"));
+ h1.remove("Row 4");
+ assertEquals("dropped row 4", false, h1.contains("Row 4"));
+ assertEquals("containsAll(afterDrop)", false, h1.containsAll(h2));
+
+ assertEquals("isEmpty(false)", false, h1.isEmpty());
+ h1.remove("Row 1");
+ h1.remove("Row 2");
+ h1.remove("Row 3");
+ h1.remove("Row 5");
+ assertEquals("isEmpty(true)", true, h1.isEmpty());
+ h1.add("Row 1");
+ h1.add("Row 2");
+ h1.add("Row 3");
+ h1.addAll(h2);
+ assertEquals("Adding back", 5, h1.size());
+ h1.removeAll(h2);
+
+ assertEquals("removeAll()", 3, h1.size());
+ h1.addAll(h2);
+
+ assertEquals("Adding back again", 5, h1.size());
+ h1.retainAll(h2);
+ assertEquals("retainAll()", 2, h1.size());
+
+ }
+
+ @Test
+ public void testIterator() {
+
+ HashedList h1 = new HashedList();
+
+ h1.add("key 4", "Row 4");
+ h1.add("key 5", "Row 5");
+
+
+ Iterator j = h1.iterator();
+ assertEquals("next1", true, j.hasNext());
+ assertEquals("TestIter1", "Row 4", (String) j.next());
+ assertEquals("next2", true, j.hasNext());
+ assertEquals("TestIter2", "Row 5", (String) j.next());
+ assertEquals("next3", false, j.hasNext());
+
+ h1.clear();
+
+ h1.add("key 1", "Row 1");
+ h1.add("key 2", "Row 2");
+ h1.add("Row 3");
+ h1.add("key 4", "Row 4");
+ h1.add("Row 5");
+
+ assertEquals("Before remove", true, h1.contains("Row 2"));
+ j = h1.iterator();
+ j.next();
+ j.next();
+ j.remove(); // Should get rid of second row
+ assertEquals("After remove", false, h1.contains("Row 2"));
+ assertEquals("n3", true, j.hasNext());
+ assertEquals("n3v", "Row 3", (String) j.next());
+ assertEquals("n4", true, j.hasNext());
+ assertEquals("n4v", "Row 4", (String) j.next());
+ assertEquals("n5", true, j.hasNext());
+ assertEquals("n5v", "Row 5", (String) j.next());
+ assertEquals("n6", false, j.hasNext());
+ }
+
+ @Test
+ public void TestCursor() {
+
+ HashedList h1 = new HashedList();
+
+ h1.add("key 1", "Row 1");
+ h1.add("Row 3");
+ h1.add("key 4", "Row 4");
+ h1.add("Row 5");
+
+ Cursor j = (Cursor) h1.iterator(0);
+ assertEquals("n1x", true, j.hasNext());
+ assertEquals("n1xv", "Row 1", (String) j.next());
+ assertEquals("n1xv", "Row 3", (String) j.next());
+
+ assertEquals("No Row 2", false, h1.containsKey("key 2"));
+ assertEquals("No Row 2", false, h1.contains("Row 2"));
+ j.setKey("key 1");
+ assertEquals("setKey()", "Row 1", (String) j.next());
+ j.add("key 2", "Row 2");
+ assertEquals("has Row 2", true, h1.contains("Row 2"));
+ assertEquals("after add", "Row 3", (String) j.next());
+
+ j.setKey("key 4");
+ assertEquals("setKey(1)", "Row 4", (String) j.next());
+ assertEquals("setKey(2)", "Row 5", (String) j.next());
+ assertEquals("setKey(3)", false, j.hasNext());
+
+
+ j.setKey("key 2");
+ assertEquals("setKey(4)", "Row 2", (String) j.next());
+ assertEquals("setKey(5)", "Row 3", (String) j.next());
+ j.add("Row 3.5");
+ j.add("Row 3.6");
+ assertEquals("After add", 7, h1.size());
+
+ j = h1.iterator("key 2");
+ j.add("Row 1.5");
+ j.add("key 1.7", "Row 1.7");
+ j.add("Row 1.9");
+ assertEquals("next() after adds", "Row 2", (String) j.next());
+ j.setKey("key 1.7");
+ assertEquals("next() after adds", "Row 1.7", (String) j.next());
+ assertEquals("prev(1)", "Row 1.7", (String) j.prev());
+ assertEquals("prev(2)", "Row 1.5", (String) j.prev());
+ assertEquals("prev(3)", true, j.hasPrev());
+ assertEquals("prev(4)", "Row 1", (String) j.prev());
+ assertEquals("prev(5)", false, j.hasPrev());
+ }
+
+ void show(HashedList h, String msg) {
+ Iterator t = h.iterator();
+ System.out.println("\n Looking at list:" + msg);
+ while (t.hasNext()) {
+ System.out.println("Has element:" + t.next());
+ }
+ }
+}