// $Id: ProtoVariable.java,v 1.4 2002-05-29 18:31:35 steve 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.MultiArrayInfo; import java.io.Serializable; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.io.ObjectInputStream; import java.io.InvalidClassException; /** * Prototype for Netcdf Variable. * Instances of ProtoVariable provide the description of a Netcdf Variable * without data i/o functionality. Instances of this class are used in a Schema, * which is used when we create a new Netcdf. An instance has a name and a shape * specified by an array of Dimensions. * It may also have descriptive attributes. The attribute set is * modifiable. * <p> * The data logically contained in a Netcdf Variable is not accessed * through this object. * <p> * Although there is no explicit relationship between this class and * and Variable, they share common method signatures and semantics where * appropriate. * * @see Variable * @author $Author: steve $ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $ */ public class ProtoVariable implements Named, MultiArrayInfo, Serializable, Cloneable { /** * Check if the given Class corresponds to a netcdf type. * This method is used to check Attributes as well... * * @param componentType Class to check * @return <code>true</code> if Okay, <code>false</code> otherwise. */ static final boolean checkComponentType(Class componentType) { if(!componentType.isPrimitive() || componentType.equals(Long.TYPE) || componentType.equals(Boolean.TYPE)) return false; return true; } /* Begin Constructors */ /** * The usual constructor, used when you are going to * add the attributes after construction. * * @param name String which is to be the name of this Variable * @param componentType Class (primitive type) contained herein. * One of * <code>Character.Type</code>, * <code>Byte.TYPE</code>, * <code>Short.TYPE</code>, * <code>Integer.TYPE</code>, * <code>Float.TYPE</code>, * or * <code>Double.Type</code>. * @param dimArray The dimensions which define the * shape of this Variable. If null or zero length array, * this is a scalar variable. */ public ProtoVariable(String name, Class componentType, Dimension [] dimArray) { this.name = name; if(!checkComponentType(componentType)) throw new IllegalArgumentException("Invalid Type"); this.componentType = componentType; if(dimArray == null) { this.dimArray = new Dimension[0]; } else synchronized (dimArray) { this.dimArray = new Dimension[dimArray.length]; for(int ii = 0; ii < dimArray.length; ii++) { final Dimension dim = dimArray[ii]; if(dim instanceof UnlimitedDimension) { if(ii > 0) throw new IllegalArgumentException( "UnlimitedDimension not is leftmost position"); } else if (dim.getLength() == 0) { throw new IllegalArgumentException( "Zero length dimension"); } this.dimArray[ii] = dim; } } this.attributes = new AttributeDictionary(); } /** * Convenience constructor for 1-dimensional Variables, often * used for coordinate variables. Typically attributes would * be added after construction when this constructor is used. * * @param name String which is to be the name of this Variable * @param componentType Class (primitive type) contained herein. * One of * <code>Character.Type</code>, * <code>Byte.TYPE</code>, * <code>Short.TYPE</code>, * <code>Integer.TYPE</code>, * <code>Float.TYPE</code>, * or * <code>Double.Type</code>. * @param dimension A single dimension to define the array. */ public ProtoVariable(String name, Class componentType, Dimension dimension) { this.name = name; if(!checkComponentType(componentType)) throw new IllegalArgumentException("Invalid Type"); this.componentType = componentType; this.dimArray = new Dimension[1]; this.dimArray[0] = dimension; this.attributes = new AttributeDictionary(); } /** * More general constructor. Initializes attribute set * during construction. * * @param name String which is to be the name of this Variable * @param componentType Class (primitive type) contained herein. * One of * <code>Character.Type</code>, * <code>Byte.TYPE</code>, * <code>Short.TYPE</code>, * <code>Integer.TYPE</code>, * <code>Float.TYPE</code>, * or * <code>Double.Type</code>. * @param dimArray The dimensions which define the shape * of this Variable. If null or zero length array, * this is a scalar variable. * @param attrArray Attributes associated with this Variable. * May be null or a zero length array. */ public ProtoVariable(String name, Class componentType, Dimension [] dimArray, Attribute [] attrArray) { this.name = name; if(!checkComponentType(componentType)) throw new IllegalArgumentException("Invalid Type"); this.componentType = componentType; if(dimArray == null) { this.dimArray = new Dimension[0]; } else synchronized (dimArray) { this.dimArray = new Dimension[dimArray.length]; for(int ii = 0; ii < dimArray.length; ii++) { final Dimension dim = dimArray[ii]; if(dim instanceof UnlimitedDimension) { if(ii > 0) throw new IllegalArgumentException( "UnlimitedDimension not is leftmost position"); } else if (dim.getLength() == 0){ throw new IllegalArgumentException( "Zero length dimension"); } this.dimArray[ii] = dim; } } this.attributes = new AttributeDictionary(attrArray); } /** * copy constructor. */ ProtoVariable(ProtoVariable pv) { name = pv.getName(); componentType = pv.getComponentType(); pv.copyVolatile(this); } /** * Conversion constructor. */ public ProtoVariable(Variable var) { /* * Why ask why? Would prefer to say: * this(var.meta); * ==> Blank final xxx may not have been initialized. */ name = var.meta.getName(); componentType = var.meta.getComponentType(); var.meta.copyVolatile(this); } /* End Constructors */ /** * Factor common code used * to implement copy constructor and clone() method. * Copy modifiable portions of this to dest. */ private synchronized void copyVolatile(ProtoVariable dest) { dest.dimArray = new Dimension[dimArray.length]; for(int ii = 0; ii < dimArray.length; ii++) { dest.dimArray[ii] = (Dimension) dimArray[ii].clone(); } dest.attributes = new AttributeDictionary(attributes); } /** * Returns a clone of this */ public Object clone() { try { final ProtoVariable pv = (ProtoVariable) super.clone(); copyVolatile(pv); return pv; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new Error(); } } /** * Returns the name of this Variable. * @return String which identifies this Variable. */ public final String getName() { return name; } /** * Returns the Class object representing the component * type of the Variable. * @return Class The componentType * @see java.lang.Class#getComponentType */ public final Class getComponentType() { return componentType; } /** * Returns the number of dimensions of the variable. * @return int number of dimensions of the variable */ public final int getRank() { return dimArray.length; } /** * Return an array whose length is the rank of this * and whose elements represent the * length of each of its dimensions. * * @return int array whose length is the rank of this * and whose elements represent the * length of each of its dimensions */ public final int [] getLengths() { int [] lengths = new int[dimArray.length]; for(int ii = 0; ii < dimArray.length; ii++) lengths[ii] = dimArray[ii].getLength(); return lengths; } /** * Returns <code>true</code> if and only if the this variable can grow. * This is equivalent to saying * at least one of its dimensions is unlimited. * In the current implementation, exactly one dimension, the most * slowly varying (leftmost), can be unlimited. * @return boolean <code>true</code> iff this can grow */ public final boolean isUnlimited() { return ((dimArray.length > 0) && dimArray[0] instanceof UnlimitedDimension); } /** * Convenience interface; return <code>true</code> * if and only if the rank is zero. * @return boolean <code>true</code> iff rank == 0 */ public final boolean isScalar() { return (dimArray.length == 0); } /** * Returns a DimensionIterator of the dimensions * used by this variable. The most slowly varying (leftmost * for java and C programmers) dimension is first. * For scalar variables, the set has no elements and the iteration * is empty. * @return DimensionIterator of the elements. * @see DimensionIterator */ public DimensionIterator getDimensionIterator() { return new DimensionIterator() { int position = 0; public boolean hasNext() { return position < dimArray.length; } public Dimension next() { return dimArray[position++]; } }; } /** * Convenience function; look up Attribute by name. * * @param name the name of the attribute * @return the attribute, or null if not found */ public Attribute getAttribute(String name) { return attributes.get(name); } /** * Returns the (modifiable) set of attributes * associated with this. * * @return AttributeSet. May be empty. Won't be null. */ public AttributeSet getAttributes() { return (AttributeSet) attributes; } /** * Convenience function; add attribute. * @see AttributeSet#put * @param attr the Attribute to be added to this set. * @return Attribute replaced or null if not a replacement */ public Attribute putAttribute(Attribute attr) { return attributes.put(attr); } /** * Format as CDL. * @param buf StringBuffer into which to write */ public void toCdl(StringBuffer buf) { buf.append(this.getComponentType()); buf.append(" "); buf.append(this.getName()); buf.append("("); for(DimensionIterator iter = this.getDimensionIterator(); iter.hasNext() ;) { buf.append( iter.next().getName() ); if(!iter.hasNext()) break; buf.append(", "); } buf.append(") ;\n"); /* * We need to tag each attribute with this variable's name. */ for (AttributeIterator iter = this.getAttributes().iterator(); iter.hasNext() ;) { buf.append("\t\t"); buf.append(this.getName()); iter.next().toCdl(buf); buf.append("\n"); } } /** * @return a string representation of this */ public String toString() { StringBuffer buf = new StringBuffer(); toCdl(buf); return buf.toString(); } /** * Ensure that the dimensions referenced by this * are members of the specified Dictionary. * This may modify dimArray elements to reference * different (equivalent) dimension instances. * * package private * */ synchronized void connectDims(DimensionDictionary dimensions) { for(int ii = 0; ii < dimArray.length; ii++) { dimArray[ii] = dimensions.put(dimArray[ii]); } } /** * Because members of Class Class can't be serialized, we have * to come up with our own encoding of the Class field 'componentType'. * <p> * The intent of this function is to use the same encoding as * 'prim_typecode' from the object serialization stream spec. * */ private int encodeComponentType() { if(componentType.isPrimitive()) { if(componentType.equals(Byte.TYPE)) return (byte) 'B'; if(componentType.equals(Character.TYPE)) return (byte) 'C'; if(componentType.equals(Double.TYPE)) return (byte) 'D'; if(componentType.equals(Float.TYPE)) return (byte) 'F'; if(componentType.equals(Integer.TYPE)) return (byte) 'I'; if(componentType.equals(Long.TYPE)) return (byte) 'J'; if(componentType.equals(Short.TYPE)) return (byte) 'S'; if(componentType.equals(Boolean.TYPE)) return (byte) 'Z'; } throw new IllegalArgumentException(componentType.toString()); } static private Class decodeComponentType(int typecode) throws InvalidClassException { switch (typecode) { case (byte) 'B': return Byte.TYPE; case (byte) 'C': return Character.TYPE; case (byte) 'D': return Double.TYPE; case (byte) 'F': return Float.TYPE; case (byte) 'I': return Integer.TYPE; case (byte) 'J': return Long.TYPE; case (byte) 'S': return Short.TYPE; case (byte) 'Z': return Boolean.TYPE; } throw new InvalidClassException(Integer.toHexString(typecode)); } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(name); out.write(encodeComponentType()); out.writeObject(dimArray); out.writeObject(attributes); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); final int typecode = in.read(); componentType = decodeComponentType(typecode); dimArray = (Dimension []) in.readObject(); attributes = (AttributeDictionary) in.readObject(); } /** * @serial */ private /* final */ String name; /** * @serial */ private /* final */ Class componentType; /** * @serial */ private /* final */ Dimension [] dimArray; /** * @serial */ private /* final */ AttributeDictionary attributes; }