// $Id: NetcdfFile.java,v 1.9 2003-03-14 16:29:05 donm Exp $
/*
* Copyright 1997-2000 Unidata Program Center/University Corporation for
* Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
* support@unidata.ucar.edu.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package ucar.netcdf;
import ucar.multiarray.Accessor;
import ucar.multiarray.AbstractAccessor;
import ucar.multiarray.MultiArray;
import ucar.multiarray.MultiArrayImpl;
import ucar.multiarray.IndexIterator;
import ucar.multiarray.OffsetIndexIterator;
import java.lang.reflect.Array;
import java.lang.Math;
import java.io.File;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
// use buffered ucar.netcdf.RandomAccessFile instead
// import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
/**
* A concrete implementation of the Netcdf interface,
* this class provides connection to NetCDF version 1 files.
* <p>
* Constructors for creating new files and opening existing
* ones.
*
* @see Netcdf
* @author $Author: donm $
* @version $Revision: 1.9 $ $Date: 2003-03-14 16:29:05 $
*/
public class NetcdfFile extends AbstractNetcdf {
/* Begin constructors */
/**
* Create a new netcdf version 1 file from a Schema template.
*
* @param file the file name as File object
* @param clobber if <code>true</code>, overwrite existing
* @param fill if <code>false</code>, suppress variable pre fill
* @param template the Schema used as construction template. May be empty,
* shouldn't be null.
*
* @see #setFill
* @see Netcdf
*/
public
NetcdfFile(File file, boolean clobber, boolean fill,
Schema template)
throws IOException
{
super(new Schema(template), true);
if(!clobber && file.exists())
{
// TODO: netcdf exception?
throw new SecurityException(file.getName() + " exists");
}
// else
this.file = file;
this.raf = new RandomAccessFile(file, "rw");
this.doFill = fill;
compileBegins();
initRecSize();
writeV1();
fillerup();
url = null;
}
/**
* Create a new netcdf version 1 file from a Schema template.
*
* @param path the file name as a String
* @param clobber if <code>true</code>, overwrite existing
* @param fill if <code>false</code>, suppress variable pre fill
* @param template the Schema used as construction template. May be empty,
* shouldn't be null.
*
* @see #setFill
* @see Netcdf
*/
public
NetcdfFile(String path, boolean clobber, boolean fill,
Schema template)
throws IOException
{
this(new File(path), clobber, fill, template);
}
/**
* Open existing netcdf version 1 file.
*
* @param file the file name as File object
* @param readonly if <code>true</code>, open read only,
* else open for read and write.
*/
public
NetcdfFile(File file, boolean readonly) throws IOException {
super();
this.file = file;
raf = new RandomAccessFile(file, readonly ? "r" : "rw");
readV1(raf);
initRecSize();
this.doFill = true;
url = null;
}
/**
* Open existing netcdf version 1 file.
*
* @param path the file name as a String
* @param readonly if <code>true</code>, open read only,
* else open for read and write.
*/
public
NetcdfFile(String path, boolean ro)
throws IOException
{
this(new File(path), ro);
}
/**
* Open existing, read-only netcdf file through a URL. This may use either the
* file: or http: protocol. If it uses the file protocol, it will be opened as a
* read-only file using url.getFile(). If it uses the http protocol, it will be
* read over http using HTTPRandomAccessFile. The query
* component of the URL is ignored
*
* <p>Modified from ncBrowse (Donald Denbo).</p>
*
* @param url the URL of the netCDF dataset.
* @throws FileNotFoundException if the URL specifies a file that doesn't
* exist.
* @throws IOException if an I/O failure occurs.
*/
public NetcdfFile(URL url) throws FileNotFoundException, IOException {
super();
String path = url.getFile();
int i = path.indexOf('?');
if (i != -1)
path = path.substring(0, i);
if (url.getProtocol().equalsIgnoreCase("file"))
{
/*
* URL.getPath() isn't used in order to accomodate JDK 1.2.
*/
this.url = null;
file = new File(path);
raf = new RandomAccessFile (path, "r", 204800);
} else {
/* Defensive copy */
this.url =
new URL(url.getProtocol(), url.getHost(), url.getPort(), path);
file = null;
//Use a pretty big buffer to reduce the number of seeks
raf = new HTTPRandomAccessFile(this.url, 204800);
//raf = new HTTPRandomAccessFile(this.url);
}
readV1(raf);
initRecSize();
this.doFill = true;
}
/* End constructors */
/* Begin Public */
/**
* Close this netcdf file.
* The inquiry interface calls will continue to be available,
* but I/O accesses will fail after this call.
* @see RandomAccessFile#close
*/
public void
close() throws IOException
{
raf.close();
}
/**
* Flush anything written to disk.
* @see RandomAccessFile#flush
*/
public void
flush() throws IOException
{
raf.flush();
}
/**
* Useful for identifying this instance among others.
* @return File object this was opened or created as.
*/
public final File getFile() {
return file;
}
/**
* Useful for identifying this instance among others.
* @return File object this was opened or created as.
*/
public final String getName() {
return (file != null) ? file.getPath() : url.toString();
}
/**
* Sets the "fill mode" to the argument.
* If true (the default), new storage is prefilled with the
* appropriate fill value. Otherwise, this activity is suppressed
* and the programmer should initialize all values.
* @see #getFill
* @param pleaseFill true to fill.
*/
public synchronized void
setFill(boolean pleaseFill)
{
doFill = pleaseFill;
}
/**
* Get the current "fill mode".
* @see #setFill
* @return true iff we are prefilling new storage
* with the appropriate fill value.
*/
public final boolean
getFill()
{
return doFill;
}
/**
* If this has an unlimited Dimension,
* return it, otherwise null.
* Note that this specific to NetcdfFile, not
* part of the Netcdf interface. Other implementations
* may support multiple unlimited dimensions.
* @deprecated
* @return UnlimitedDimension the unlimited dimension
*/
public final UnlimitedDimension
unlimitedDimension()
{
return recDim;
}
/**
* Format as CDL.
* @param buf StringBuffer into which to write
*/
public void
toCdl(StringBuffer buf)
{
buf.append("netcdf ");
if (file != null)
buf.append(file.getName());
else
buf.append(url.toString());
buf.append(" ");
super.toCdl(buf);
}
/* End Public */
/* Begin Constants */
/*
* netcdf file format version 1 "magic number"
*/
final static int v1magic = 0x43444601;
/*
* tag for signed 1 byte integer
*/
final static int NC_BYTE = 1;
/*
* tag for ISO/ASCII characters
*/
final static int NC_CHAR = 2;
/*
* tag for signed 2 byte integer
*/
final static int NC_SHORT = 3;
/*
* tag for signed 4 byte integer
*/
final static int NC_INT = 4;
/*
* tag for single precision floating point number
*/
final static int NC_FLOAT = 5;
/*
* tag for double precision floating point number
*/
final static int NC_DOUBLE = 6;
/*
* tag for array of netcdf Dimensions
*/
final static int NC_DIMENSION = 10;
/*
* tag for array of netcdf Variables
*/
final static int NC_VARIABLE = 11;
/*
* tag for array of netcdf attributes
*/
final static int NC_ATTRIBUTE = 12;
/*
* External representation aligns to 4 byte boundaries.
* a.k.a. BYTES_PER_XDR_UNIT
*/
final static int X_ALIGN = 4;
/*
* External size of char.
* Netcdf v1 file format uses 8 bit ASCII
*/
final static int X_SIZEOF_CHAR = 1;
/*
* External size of byte
*/
final static int X_SIZEOF_BYTE = 1;
/*
* External size of short
*/
final static int X_SIZEOF_SHORT = 2;
/*
* External size of int
*/
final static int X_SIZEOF_INT = 4;
/*
* External size of float
*/
final static int X_SIZEOF_FLOAT = 4;
/*
* External size of DOUBLE
*/
final static int X_SIZEOF_DOUBLE = 8;
/*
* Reserved name of Fill attribute
*/
static final String _FillValue = "_FillValue";
static final byte NC_FILL_BYTE = -127;
static final byte NC_FILL_CHAR = 0;
static final short NC_FILL_SHORT = -32767;
static final int NC_FILL_INT = -2147483647;
static final float NC_FILL_FLOAT = 9.9692099683868690e+36F;
static final double NC_FILL_DOUBLE = 9.9692099683868690e+36;
/* End Constants */
private int
padsz(int xsz) {
int rem = xsz % X_ALIGN;
if(rem == 0)
return 0;
return (X_ALIGN - rem);
}
private int
rndup(int xsz) {
return (xsz + padsz(xsz));
}
private int
xszofV1String(String str) {
int xsz = X_SIZEOF_INT;
xsz += rndup(str.length());
return xsz;
}
private void
writeV1String(String str)
throws IOException {
int rndup = str.length() % X_ALIGN;
if(rndup != 0)
rndup = X_ALIGN - rndup;
raf.writeInt(str.length());
raf.writeBytes(str);
while(rndup != 0) {
raf.writeByte(0);
rndup--;
}
}
private String
readV1String(DataInput hgs, int size)
throws IOException {
int rndup = size % X_ALIGN;
if(rndup != 0)
rndup = X_ALIGN - rndup;
byte [] bytes = new byte[size];
hgs.readFully(bytes); // TODO: premature EOF detection provided?
hgs.skipBytes(rndup);
return new String(bytes).intern();
}
private String
readV1String(DataInput hgs)
throws IOException {
return readV1String(hgs, hgs.readInt());
}
private void
writeV1Bytes(byte [] bytes)
throws IOException {
int rndup = bytes.length % X_ALIGN;
if(rndup != 0)
rndup = X_ALIGN - rndup;
raf.writeInt(bytes.length);
raf.write(bytes);
while(rndup != 0) {
raf.writeByte(0);
rndup--;
}
}
private int
v1TypeEncode(Class componentType) {
if(componentType.isPrimitive()) {
if(componentType.equals(Character.TYPE))
return NC_CHAR;
if(componentType.equals(Byte.TYPE))
return NC_BYTE;
if(componentType.equals(Short.TYPE))
return NC_SHORT;
if(componentType.equals(Integer.TYPE))
return NC_INT;
if(componentType.equals(Float.TYPE))
return NC_FLOAT;
if(componentType.equals(Double.TYPE))
return NC_DOUBLE;
}
throw new IllegalArgumentException("Not a V1 type: " + componentType);
}
private Class
v1TypeDecode(int v1type) {
switch (v1type) {
case NC_CHAR:
return Character.TYPE;
case NC_BYTE:
return Byte.TYPE;
case NC_SHORT:
return Short.TYPE;
case NC_INT:
return Integer.TYPE;
case NC_FLOAT:
return Float.TYPE;
case NC_DOUBLE:
return Double.TYPE;
}
return null;
}
private int
xszofElement(Class componentType) {
if(componentType.equals(Short.TYPE))
return 2;
if(componentType.equals(Integer.TYPE))
return 4;
if(componentType.equals(Float.TYPE))
return 4;
if(componentType.equals(Double.TYPE))
return 8;
return 1;
}
/*
* Used to calculate V1Io.vsize and this.recsize
*/
private int
initVsize(DimensionIterator ee, int xsz) {
int size = 1;
while ( ee.hasNext() ) {
final Dimension dim = ee.next();
if(dim instanceof UnlimitedDimension) {
continue;
}
// else
size *= dim.getLength();
}
size *= xsz;
return size; // N.B. not rounded up
}
private void
writeV1(Dimension dim)
throws IOException {
writeV1String(dim.getName());
if(dim instanceof UnlimitedDimension)
raf.writeInt(0);
else
raf.writeInt(dim.getLength());
}
private int
xszof(Dimension dim) {
int xsz = xszofV1String(dim.getName());
xsz += X_SIZEOF_INT;
return xsz;
}
private void
writeV1(Attribute attr) throws IOException {
writeV1String(attr.getName());
if(attr.isString())
{
raf.writeInt(NC_CHAR); // type
writeV1String((String)attr.getValue());
return;
}
// else
final int v1type = v1TypeEncode(attr.getComponentType());
raf.writeInt(v1type); // type
if(v1type == NC_CHAR)
{
writeV1String((String)attr.getValue());
return;
}
if(v1type == NC_BYTE)
{
writeV1Bytes((byte [])attr.getValue());
return;
}
// else
final int length = Array.getLength(attr.getValue());
raf.writeInt(length); // nelems
// TODO: invert so loop is inside switch?
for(int ii = 0; ii < length; ii++) {
switch (v1type) {
case NC_SHORT:
raf.writeShort(((short[])attr.getValue())[ii]);
if(length % 2 != 0)
raf.writeShort(0); // pad to X_ALIGN
break;
case NC_INT:
raf.writeInt(((int[])attr.getValue())[ii]);
break;
case NC_FLOAT:
raf.writeFloat(((float[])attr.getValue())[ii]);
break;
case NC_DOUBLE:
raf.writeDouble(((double[])attr.getValue())[ii]);
break;
}
}
}
private int
xszof(Attribute attr) {
int xsz = xszofV1String(attr.getName());
xsz += X_SIZEOF_INT; // tag
if(attr.isString()) {
return xsz + xszofV1String((String)attr.getValue());
}
// else
xsz += X_SIZEOF_INT; // nelems
final int v1type = v1TypeEncode(attr.getComponentType());
final int length = Array.getLength(attr.getValue());
switch (v1type) {
case NC_BYTE:
case NC_CHAR:
xsz += rndup(length);
break;
case NC_SHORT:
xsz += rndup(length * X_SIZEOF_SHORT);
break;
case NC_INT:
xsz += length * X_SIZEOF_INT;
break;
case NC_FLOAT:
xsz += length * X_SIZEOF_FLOAT;
break;
case NC_DOUBLE:
xsz += length * X_SIZEOF_DOUBLE;
break;
}
return xsz;
}
private Dimension []
readV1DimensionArray(DataInput hgs)
throws IOException {
final int numrecs = hgs.readInt();
final int tag = hgs.readInt();
if(tag != NC_DIMENSION && tag != 0)
throw new IllegalArgumentException(
"Not a netcdf file (dimensions)");
final int ndims = hgs.readInt();
Dimension [] dimArray = new Dimension[ndims];
for(int ii = 0; ii < ndims; ii++) {
final String name = readV1String(hgs);
final int length = hgs.readInt();
if(length == 0) {
if(this.recDim != null)
throw new IllegalArgumentException(
"Multiple UnlimitedDimensions");
this.recDim =
new UnlimitedDimension(name, numrecs);
dimArray[ii] = this.recDim;
}
else
dimArray[ii] = new Dimension(name, length);
}
return dimArray;
}
private void
writeV1(DimensionSet ds)
throws IOException
{
writeV1numrecs();
final int size = ds.size();
if(size != 0)
raf.writeInt(NC_DIMENSION);
else
raf.writeInt(0); // bit for bit backward compat.
raf.writeInt(size);
final DimensionIterator ee = ds.iterator();
while(ee.hasNext()) {
writeV1(ee.next());
}
}
private int
xszof(DimensionSet ds) {
int xsz = X_SIZEOF_INT; // numrecs
xsz += X_SIZEOF_INT; // tag
xsz += X_SIZEOF_INT; // nelems
final DimensionIterator ee = ds.iterator();
while(ee.hasNext()) {
xsz += xszof(ee.next());
}
return xsz;
}
private void
writeV1numrecs()
throws IOException
{
if(recDim == null)
raf.writeInt(0);
else
raf.writeInt(recDim.getLength());
}
private void
writeV1(AttributeSet as)
throws IOException
{
final int size = as.size();
if(size != 0)
raf.writeInt(NC_ATTRIBUTE);
else
raf.writeInt(0); // bit for bit backward compat.
raf.writeInt(size);
final AttributeIterator ee = as.iterator();
while (ee.hasNext()) {
writeV1(ee.next());
}
}
private Object
readV1AttrVal(DataInput hgs) throws IOException {
final int v1type = hgs.readInt();
final int nelems = hgs.readInt();
switch (v1type) {
case NC_CHAR:
{
int rndup = nelems % X_ALIGN;
if(rndup != 0)
rndup = X_ALIGN - rndup;
char [] values = new char[nelems];
for(int ii = 0; ii < nelems; ii++)
values[ii] = (char) hgs.readUnsignedByte();
hgs.skipBytes(rndup);
return values;
}
case NC_BYTE:
{
int rndup = nelems % X_ALIGN;
if(rndup != 0)
rndup = X_ALIGN - rndup;
byte [] values = new byte[nelems];
hgs.readFully(values); // TODO: premature EOF detection?
hgs.skipBytes(rndup);
return values;
}
case NC_SHORT:
{
short [] values = new short[nelems];
for(int ii = 0; ii < nelems; ii++)
values[ii] = hgs.readShort();
if(nelems % 2 != 0)
hgs.skipBytes(2); // pad to X_ALIGN
return values;
}
case NC_INT:
{
int [] values = new int[nelems];
for(int ii = 0; ii < nelems; ii++)
values[ii] = hgs.readInt();
return values;
}
case NC_FLOAT:
{
float [] values = new float[nelems];
for(int ii = 0; ii < nelems; ii++)
values[ii] = hgs.readFloat();
return values;
}
case NC_DOUBLE:
{
double [] values = new double[nelems];
for(int ii = 0; ii < nelems; ii++)
values[ii] = hgs.readDouble();
return values;
}
} // end switch
/*NOTREACHED*/
return null;
}
private Attribute []
readV1AttributeArray(DataInput hgs)
throws IOException {
final int tag = hgs.readInt();
if(tag != NC_ATTRIBUTE && tag != 0)
throw new IllegalArgumentException(
"Not a netcdf file (attributes)");
final int nelems = hgs.readInt();
Attribute [] attrArray = new Attribute[nelems];
for(int ii = 0; ii < nelems; ii++) {
final String name = readV1String(hgs);
final Object value = readV1AttrVal(hgs);
attrArray[ii] = new Attribute(name, value);
}
return attrArray;
}
private int
xszof(AttributeSet as) {
int xsz = X_SIZEOF_INT; // tag
xsz += X_SIZEOF_INT; // nelems
final AttributeIterator ee = as.iterator();
while (ee.hasNext()) {
xsz += xszof(ee.next());
}
return xsz;
}
abstract class
V1Io extends AbstractAccessor {
/*
* This form of constructor used when creating.
*/
protected
V1Io(ProtoVariable proto) {
meta = proto;
lengths = proto.getLengths();
initFillValue(proto);
// TODO
this.vsize = rndup(initVsize(proto.getDimensionIterator(),
xszofElement(proto.getComponentType())));
this.begin = 0;
isUnlimited = proto.isUnlimited();
this.dsizes = compileDsizes(proto.getLengths());
this.xsz = xszofElement(proto.getComponentType());
}
abstract /* protected */ void
readArray(long offset, Object dst, int dst_position, int nelems)
throws IOException;
private final int
iocount(int [] origin, int [] shape)
{
int product = 1;
int minIndex = 0;
if(isUnlimited)
minIndex = 1;
for(int ii = shape.length -1; ii >= minIndex; ii--)
{
final int si = shape[ii];
product *= si;
if(origin[ii] != 0 || si < lengths[ii] )
break;
}
return product;
}
public MultiArray
copyout(int [] origin, int [] shape)
throws IOException {
final int [] dimensions = (int []) shape.clone();
final int [] products = new int[dimensions.length];
final int product = MultiArrayImpl.numberOfElements(dimensions, products);
final Object storage = Array.newInstance (
meta.getComponentType(),
product);
final int contig = iocount(origin, shape);
// convert dimensions to limits
final int [] limits = (int []) dimensions.clone();
for(int ii = 0; ii < limits.length; ii++)
limits[ii] += origin[ii];
final OffsetIndexIterator odo = new OffsetIndexIterator(origin, limits);
int cnt = 0;
for(int begin = 0;
odo.notDone ();
odo.advance (contig), begin += contig)
{
final long offset = computeOffset (odo.value());
readArray(offset, storage, begin, contig);
cnt++;
}
MultiArray result = new MultiArrayImpl(dimensions, products, storage);
return result;
}
/*
* @param data Array of byte which can be
* * * * * * * * contiguously written.
*/
abstract void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException;
public void
copyin(int [] origin, MultiArray data)
throws IOException
{
/*
* The switch on subclass here is justified
* to make the specialized optimization available here
* without adding specializations to Accessor,
* AbstractAccessor, and Variable.
*/
if(data instanceof MultiArrayImpl)
{
this.copyin(origin, (MultiArrayImpl) data);
}
else
{
// TODO checkfill
if(isUnlimited)
checkfill(origin[0] + (data.getLengths())[0]);
super.copyin(origin, data); // AbstractAccessor.
}
}
public void
copyin(int [] origin, MultiArrayImpl data)
throws IOException
{
final int [] dimensions = data.getLengths();
final int contig = iocount(origin, dimensions);
// convert dimensions to limits
for(int ii = 0; ii < dimensions.length; ii++)
dimensions[ii] += origin[ii];
if(isUnlimited)
checkfill(dimensions[0]);
final Object storage = data.storage;
final OffsetIndexIterator odo = new OffsetIndexIterator(origin,
dimensions);
for(int begin = 0; odo.notDone(); odo.advance(contig),
begin += contig)
{
final long offset = computeOffset(odo.value());
writeArray(offset, storage, begin, contig);
}
}
public Object
toArray()
throws IOException
{
return this.toArray(null, null, null);
}
public Object
toArray(Object dst, int [] origin, int [] shape)
throws IOException
{
final int rank = getRank();
if(origin == null)
origin = new int[rank];
else if(origin.length != rank)
throw new IllegalArgumentException("Rank Mismatch");
int [] shp = null;
if(shape == null)
shp = (int []) lengths.clone();
else if(shape.length == rank)
shp = (int []) shape.clone();
else
throw new IllegalArgumentException("Rank Mismatch");
final int product = MultiArrayImpl.numberOfElements(shp);
dst = MultiArrayImpl.fixDest(dst, product,
meta.getComponentType());
final int contig = iocount(origin, shp);
// convert dimensions to limits
final int [] limits = (int []) shp.clone();
for(int ii = 0; ii < limits.length; ii++)
limits[ii] += origin[ii];
final OffsetIndexIterator odo = new OffsetIndexIterator(origin,
limits);
for(int begin = 0; odo.notDone(); odo.advance(contig),
begin += contig)
{
final long offset = computeOffset(odo.value());
readArray(offset, dst, begin, contig);
}
return dst;
}
/**/
private final int []
compileDsizes(int [] shape)
{
final int [] ds = new int [shape.length];
int product = 1;
for(int ii = shape.length - 1; ii >= 0; ii--)
{
if(!(ii == 0 && isUnlimited))
product *= shape[ii];
ds[ii] = product;
}
return ds;
}
public final void
checkfill(int newLength)
throws IOException {
synchronized(recDim) {
int length = recDim.getLength();
if(newLength > length)
{
if(doFill)
{
for(; length < newLength; length++)
{
fillRec(length);
}
}
recDim.setLength(newLength);
// TODO? allow caching? (NC_NSYNC)
raf.seek(((long)4)); // NC_NUMRECS_OFFSET
// writeV1numrecs();
raf.writeInt(recDim.getLength());
}
}
}
/* TODO */
final int getRank() { return dsizes.length; }
final boolean isScalar() { return 0 == getRank(); }
final long
computeOffset(int [] origin) {
if(isScalar())
return begin;
// else
if(getRank() == 1) {
if(isUnlimited) {
return (begin + origin[0] * recsize);
}
// else
return (begin + origin[0] * this.xsz);
}
// else
final int end = dsizes.length -1;
int lcoord = origin[end];
int index = 0;
if(isUnlimited) {
index++;
}
for(; index < end ; index++) {
lcoord += dsizes[index +1] * origin[index];
}
lcoord *= this.xsz;
if(isUnlimited)
lcoord += origin[0] * recsize;
lcoord += begin;
return lcoord;
}
abstract void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException;
private final void
initFillValue(Attribute fAttr) {
final int nbytes = 32; // tune here
ByteArrayOutputStream bos = new ByteArrayOutputStream(nbytes);
DataOutputStream dos = new DataOutputStream(bos);
try {
this.fill(dos, nbytes, fAttr);
dos.flush();
} catch (IOException ioe) {
// assert(cant happen);
}
fillbytes = bos.toByteArray();
}
private final void
initFillValue(ProtoVariable proto)
{
initFillValue(proto.getAttribute(_FillValue));
}
private final void
initFillValue(Attribute [] attrArray)
{
Attribute fAttr = null;
for(int ii = 0; ii < attrArray.length; ii++)
{
if(attrArray[ii].getName() == _FillValue)
fAttr = attrArray[ii];
}
initFillValue(fAttr);
}
void
fillO(long offset)
throws IOException {
raf.seek(offset);
int remainder = vsize;
for(; remainder >= fillbytes.length;
remainder -= fillbytes.length)
raf.write(fillbytes);
// handle any remainder;
if(remainder > 0)
for(int ii = 0; ii < remainder; ii++)
raf.write(fillbytes[ii]);
}
void
fill(int recno)
throws IOException {
long offset = begin;
if(isUnlimited)
offset += (long)recno * recsize;
this.fillO(offset);
}
private final ProtoVariable meta; // sibling within the Variable.
private final int [] lengths; // cache of meta.getLengths()
byte [] fillbytes;
int vsize;
int begin;
final boolean isUnlimited; // TODO factor this!!
final int [] dsizes;
int xsz; // TODO: Is this member needed?
}
/* Begin IWISHWEHADTEMPLATES or a macro preprocessor */
private final class
V1ByteIo extends V1Io {
V1ByteIo(ProtoVariable var) {
super(var);
}
void
readArray(long offset, Object into, int begin, int nelems)
throws IOException {
final byte [] values = (byte []) into;
raf.seek(offset);
raf.read(values, begin, nelems);
}
public byte
getByte(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return raf.readByte();
}
public Object
get(int [] index)
throws IOException
{
return new Byte(this.getByte(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
byte [] values = (byte []) from;
raf.seek(offset);
raf.write(values, begin, nelems);
}
public void
setByte(int [] index, byte value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeByte(value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setByte(index, ((Number)value).byteValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
byte fv = NC_FILL_BYTE;
if(fAttr != null)
fv = fAttr.getNumericValue().byteValue();
for(int ii = 0; ii < nbytes; ii++)
dos.write(fv);
}
}
private final class
V1CharacterIo extends V1Io {
V1CharacterIo(ProtoVariable var) {
super(var);
}
void
readArray(long offset, Object into, int begin, int nelems)
throws IOException {
final char [] values = (char []) into;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
values[ii] = (char) raf.readUnsignedByte();
}
}
public char
getChar(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return (char) raf.readUnsignedByte();
}
public Object
get(int [] index)
throws IOException
{
return new Character(this.getChar(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
final char [] values = (char []) from;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
raf.writeByte((byte) values[ii]);
}
}
public void
setChar(int [] index, char value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeByte((byte) value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setChar(index, ((Character)value).charValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
byte fv = NC_FILL_CHAR;
if(fAttr != null)
fv = fAttr.getLength() == 0
? 0 // because Atribute strips trailing NUL
: fAttr.getNumericValue().byteValue();
for(int ii = 0; ii < nbytes; ii++)
dos.write(fv);
}
}
private final class
V1ShortIo extends V1Io {
V1ShortIo(ProtoVariable var) {
super(var);
}
void
readArray(long offset, Object into, int begin, int nelems)
throws IOException {
final short [] values = (short []) into;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
values[ii] = raf.readShort();
}
}
public short
getShort(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return raf.readShort();
}
public Object
get(int [] index)
throws IOException
{
return new Short(this.getShort(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
final short [] values = (short []) from;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
raf.writeShort(values[ii]);
}
}
public void
setShort(int [] index, short value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeShort(value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setShort(index, ((Number)value).shortValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
short fv = NC_FILL_SHORT;
if(fAttr != null)
fv = fAttr.getNumericValue().shortValue();
for(int ii = 0; ii < nbytes; ii++)
dos.writeShort(fv);
}
}
private final class
V1IntegerIo extends V1Io {
V1IntegerIo(ProtoVariable var) {
super(var);
}
void
readArray(long offset, Object into, int begin, int nelems)
throws IOException {
final int [] values = (int []) into;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
values[ii] = raf.readInt();
}
}
public int
getInt(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return raf.readInt();
}
public Object
get(int [] index)
throws IOException
{
return new Integer(this.getInt(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
final int [] values = (int []) from;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
raf.writeInt(values[ii]);
}
}
public void
setInt(int [] index, int value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeInt(value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setInt(index, ((Number)value).intValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
int fv = NC_FILL_INT;
if(fAttr != null)
fv = fAttr.getNumericValue().intValue();
for(int ii = 0; ii < nbytes; ii++)
dos.writeInt(fv);
}
}
private final class
V1FloatIo extends V1Io {
V1FloatIo(ProtoVariable var) {
super(var);
}
void readArray(long offset, Object into, int begin, int nelems)
throws IOException {
// if (begin+nelems>100)
float [] values = (float []) into;
raf.seek (offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
values[ii] = raf.readFloat();
}
}
public float
getFloat(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return raf.readFloat();
}
public Object
get(int [] index)
throws IOException
{
return new Float(this.getFloat(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
final float [] values = (float []) from;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
raf.writeFloat(values[ii]);
}
}
public void
setFloat(int [] index, float value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeFloat(value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setFloat(index, ((Number)value).floatValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
float fv = NC_FILL_FLOAT;
if(fAttr != null)
fv = fAttr.getNumericValue().floatValue();
for(int ii = 0; ii < nbytes; ii++)
dos.writeFloat(fv);
}
}
private final class
V1DoubleIo extends V1Io {
V1DoubleIo(ProtoVariable var) {
super(var);
}
void
readArray(long offset, Object into, int begin, int nelems)
throws IOException {
final double [] values = (double []) into;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
values[ii] = raf.readDouble();
}
}
public double
getDouble(int [] index)
throws IOException
{
raf.seek(computeOffset(index));
return raf.readDouble();
}
public Object
get(int [] index)
throws IOException
{
return new Double(this.getDouble(index));
}
void
writeArray(long offset, Object from, int begin, int nelems)
throws IOException {
final double [] values = (double []) from;
raf.seek(offset);
final int end = begin + nelems;
for(int ii = begin; ii < end; ii++) {
raf.writeDouble(values[ii]);
}
}
public void
setDouble(int [] index, double value)
throws IOException
{
if(isUnlimited)
checkfill(index[0] +1);
raf.seek(computeOffset(index));
raf.writeDouble(value);
}
public void
set(int [] index, Object value)
throws IOException
{
this.setDouble(index, ((Number)value).doubleValue());
}
final void
fill(DataOutput dos, int nbytes, Attribute fAttr)
throws IOException {
double fv = NC_FILL_DOUBLE;
if(fAttr != null)
fv = fAttr.getNumericValue().doubleValue();
for(int ii = 0; ii < nbytes; ii++)
dos.writeDouble(fv);
}
}
/* End IWISHWEHADTEMPLATES or a macro preprocessor */
private V1Io
V1IoFactory(ProtoVariable proto)
{
final Class componentType = proto.getComponentType();
V1Io io = null;
if(componentType.equals(Character.TYPE)) {
io = new V1CharacterIo(proto);
}
else if (componentType.equals(Byte.TYPE)) {
io = new V1ByteIo(proto);
}
else if (componentType.equals(Short.TYPE)) {
io = new V1ShortIo(proto);
}
else if (componentType.equals(Integer.TYPE)) {
io = new V1IntegerIo(proto);
}
else if (componentType.equals(Float.TYPE)) {
io = new V1FloatIo(proto);
}
else if (componentType.equals(Double.TYPE)) {
io = new V1DoubleIo(proto);
}
return io;
}
protected Accessor
ioFactory(ProtoVariable proto)
{ return V1IoFactory(proto); }
private void
writeV1(Variable var)
throws IOException
{
writeV1String(var.getName());
raf.writeInt(var.getRank());
DimensionIterator ee = var.getDimensionIterator();
while(ee.hasNext())
{
raf.writeInt(indexOf(ee.next()));
}
writeV1(var.getAttributes());
raf.writeInt(v1TypeEncode(var.getComponentType()));
final V1Io io = (V1Io) var.io;
raf.writeInt(io.vsize);
raf.writeInt(io.begin);
}
private int
xszof(Variable var) {
int xsz = xszofV1String(var.getName());
xsz += X_SIZEOF_INT; // dimArray.length
xsz += var.getRank() * X_SIZEOF_INT; // dim indexes
xsz += xszof(var.getAttributes());
xsz += X_SIZEOF_INT; // tag
xsz += X_SIZEOF_INT; // vsize
xsz += X_SIZEOF_INT; // begin
return xsz;
}
private void
readV1VarArray(DataInput hgs, Dimension [] allDims)
throws IOException {
int tag = hgs.readInt();
if(tag != NC_VARIABLE && tag != 0)
throw new IllegalArgumentException(
"Not a netcdf file (variables)");
int nelems = hgs.readInt();
for(int ii = 0; ii < nelems; ii++) {
final String name = readV1String(hgs);
final int ndims = hgs.readInt();
final Dimension [] dimArray = new Dimension[ndims];
for(int jj = 0; jj < ndims; jj++)
dimArray[jj] = allDims[hgs.readInt()];
final Attribute [] attrArray =
readV1AttributeArray(hgs);
final Class type = v1TypeDecode(hgs.readInt());
final ProtoVariable proto = new ProtoVariable(
name, type, dimArray, attrArray
);
final V1Io io = V1IoFactory(proto);
io.vsize = hgs.readInt();
io.begin = hgs.readInt();
try {
add(proto, io);
}
catch (InstantiationException ie)
{
// Can't happen: Variable is concrete
throw new Error();
}
catch (IllegalAccessException iae)
{
// Can't happen: Variable is accessable
throw new Error();
}
catch (InvocationTargetException ite)
{
// all the possible target exceptions are
// RuntimeException
throw (RuntimeException)
ite.getTargetException();
}
}
}
private void
writeV1(int size, VariableIterator ee)
throws IOException
{
if(size != 0)
raf.writeInt(NC_VARIABLE);
else
raf.writeInt(0); // bit for bit backward compat.
raf.writeInt(size);
while( ee.hasNext()) {
writeV1(ee.next());
}
}
private int
xszof(VariableIterator ee) {
int xsz = X_SIZEOF_INT; // tag
xsz += X_SIZEOF_INT; // nelems
while(ee.hasNext()) {
xsz += xszof(ee.next());
}
return xsz;
}
private void
writeV1()
throws IOException
{
raf.writeInt(v1magic);
writeV1(getDimensions());
writeV1(getAttributes());
writeV1(size(), iterator());
}
private int
xszof() {
int xsz = X_SIZEOF_INT; // magic number
xsz += xszof(getDimensions());
xsz += xszof(getAttributes());
xsz += xszof(iterator());
return xsz;
}
private void
readV1(DataInput hgs)
throws IOException {
final int magic = hgs.readInt();
if(magic != v1magic)
throw new IllegalArgumentException("Not a netcdf file");
final Dimension [] dimArray = readV1DimensionArray(hgs);
for(int ii = 0; ii < dimArray.length; ii++)
putDimension(dimArray[ii]);
{
final Attribute [] gAttrArray =
readV1AttributeArray(hgs);
for(int ii = 0; ii < gAttrArray.length; ii++)
putAttribute(gAttrArray[ii]);
}
readV1VarArray(hgs, dimArray);
}
/*
* In the C interface this is called NC_begins();
*/
private void
compileBegins() {
int index = xszof();
/* loop thru vars, first pass is for the 'non-record' vars */
{
final VariableIterator ee = iterator();
while(ee.hasNext())
{
final Variable var = ee.next();
if(var.isUnlimited())
continue;
// else
final V1Io io = (V1Io) var.io;
io.begin = index;
index += io.vsize;
}
}
{
/* loop thru vars, 2nd pass is for the 'record' vars */
final VariableIterator ee = iterator();
while(ee.hasNext())
{
final Variable var = ee.next();
if(!var.isUnlimited())
continue;
if(recDim == null)
{
final Dimension dim0 =
var.getDimensionIterator().next();
if(!(dim0 instanceof UnlimitedDimension))
throw new IllegalArgumentException(
"Unlimited Dim not leftmost"
);
recDim = (UnlimitedDimension)dim0;
}
final V1Io io = (V1Io) var.io;
io.begin = index;
index += io.vsize;
}
}
}
private void
initRecSize() {
recsize = 0;
/* loop thru vars, 2nd pass is for the 'record' vars */
final VariableIterator ee = iterator();
while(ee.hasNext())
{
final Variable var = ee.next();
if(!var.isUnlimited())
continue;
final V1Io io = (V1Io) var.io;
if(recsize == 0 && !ee.hasNext())
{
// special case exactly one record variable
// pack value
recsize = initVsize(var.getDimensionIterator(),
io.xsz);
break;
}
// else
recsize += io.vsize;
}
}
// can't be private and still visible in inner class V1Var?
void
fillRec(int recno) throws IOException {
// synchronized in caller
// "only call when doFill set" checked in caller
final VariableIterator ee = iterator();
while(ee.hasNext())
{
final Variable var = ee.next();
if(!var.isUnlimited())
continue;
// else
// var.fill(recno);
final V1Io io = (V1Io) var.io;
final long offset = (long)io.begin + (long)recno * recsize;
io.fillO(offset);
}
}
private void
fillerup()
throws IOException {
if(!this.doFill)
return;
final VariableIterator ee = iterator();
while(ee.hasNext())
{
final Variable var = ee.next();
if(var.isUnlimited())
continue;
// else
final V1Io io = (V1Io) var.io;
io.fillO((long)io.begin);
}
if(this.recDim != null) {
final int nrecs = recDim.getLength();
for(int recno = 0; recno < nrecs; recno++)
fillRec(recno);
}
}
/**
* Ensures that the close method of this file is called when
* there are no more
* references to it.
* @exception Throwable The lack of covariance for exception specifications
* dictates the specificed type;
* it can actually only be <code>IOException</code> thrown
* by <code>RandomAccessFile.close</code>.
* @see NetcdfFile#close
*/
protected void
finalize() throws Throwable
{
super.finalize();
close();
}
private URL url; // not "final" to accomodate JDK 1.2
private File file; // not "final" to accomodate JDK 1.2
private RandomAccessFile raf; // unidata.netcdf version, not java.io
private UnlimitedDimension recDim;
private int recsize;
private boolean doFill; // set to false to suppress data prefill.
}
/* Change History:
$Log: not supported by cvs2svn $
Revision 1.15 2003/03/04 22:26:32 jeffmc
Add a new ctor that takes buffer size and cleanup some old debug in netcdffile
Revision 1.14 2003/03/04 22:23:22 jeffmc
Create the httprandomaccess file with a largish buffer size
Revision 1.13 2003/01/21 21:24:05 jeffmc
Add a getStorage method that returns the raw multiarray object
Revision 1.12 2002/07/15 21:39:17 steve
Changed use of _FillValue attribute. If zero-length, then use byte-value 0.
Revision 1.11 2002/05/24 00:06:06 caron
add flush()
Revision 1.10 2001/09/14 21:29:28 caron
minor doc improvements
Revision 1.9 2001/09/10 20:37:12 steve
Improved constructor NetcdfFile(URL):
Replaced URL.getPath() with URL.getFile() to accomodate JDK 1.2.
Made protocol comparison case-insensitive.
Added defensive copying of modifiable URL argument.
Added FileNotFoundException.
Added ignoring of query component of URL.
Enhanced JavaDoc.
Revision 1.8 2001/08/28 16:59:59 steve
Added support for "file" protocol to constructor NetcdfFile(URL).
Revision 1.7 2001/05/17 15:15:09 steve
Modified to accomodate JDK 1.2.
Revision 1.6 2001/05/01 15:06:02 caron
add netcdf HTTP access
*/