/////////////////////////////////////////////////////////////////////////////
// Copyright (c) 1998, California Institute of Technology.
// ALL RIGHTS RESERVED. U.S. Government Sponsorship acknowledged.
//
// Please read the full copyright notice in the file COPYRIGHT
// in this directory.
//
// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
// Jake.Hamby@jpl.nasa.gov
/////////////////////////////////////////////////////////////////////////////
package dods.dap;
import java.io.*;
import java.util.Vector;
import java.util.Enumeration;
/**
* This class holds a <code>DArray</code> and a set of "Map"
* vectors. The Map vectors are one-dimensional arrays corresponding
* to each dimension of the central <code>Array</code>. Using this scheme, a
* <code>Grid</code> can represent, in a rectilinear array, data which is not
* in reality rectilinear. An example will help make this clear.
* <p>
* Assume that the following array contains measurements of some real
* quantity, conducted at nine different points in space:
*
* <code><pre>
* A = [ 1 2 3 4 ]
* [ 2 4 6 8 ]
* [ 3 6 9 12]
* </pre></code>
*
* To locate this <code>Array</code> in the real world, we could note the
* location of one corner of the grid, and the grid spacing. This would allow
* us to calculate the location of any of the other points of the
* <code>Array</code>.
* <p>
* This approach will not work, however, unless the grid spacing is
* precisely regular. If the distance between Row 1 and Row 2 is not
* the same as the distance between Row 2 and Row 3, the scheme will
* break down. The solution is to equip the <code>Array</code> with two
* <code>Map</code> vectors that define the location of each row or column of
* the array:
*
* <code><pre>
* A = [ 1 2 3 4 ] Row = [ 0 ]
* [ 2 4 6 8 ] [ 3 ]
* [ 3 6 9 12] [ 8 ]
*
* Column = [ 0 2 8 27]
* </pre></code>
*
* The real location of the point in the first row and column of the
* array is now exactly fixed at (0,0), and the point in the last row
* and last column is at (8,27).
*
* @version $Revision: 1.3 $
* @author jehamby
* @see BaseType
* @see DArray
*/
public class DGrid extends DConstructor implements ClientIO {
/** The <code>Array</code> part of the <code>DGrid</code> */
public static final int ARRAY = 1;
/** The Map part of the <code>DGrid</code> */
public static final int MAPS = 2;
/** The Array component of this <code>DGrid</code>. */
protected DArray arrayVar;
/** The Map component of this <code>DGrid</code>. */
protected Vector mapVars;
/** Constructs a new <code>DGrid</code>. */
public DGrid() { this(null); }
/**
* Constructs a new <code>DGrid</code> with name <code>n</code>.
* @param n the name of the variable.
*/
public DGrid(String n) {
super(n);
mapVars = new Vector();
}
/**
* Returns a clone of this <code>DGrid</code>. A deep copy is performed
* on all data inside the variable.
*
* @return a clone of this <code>DGrid</code>.
*/
public Object clone() {
DGrid g = (DGrid)super.clone();
g.arrayVar = (DArray)arrayVar.clone();
g.mapVars = new Vector();
for(int i=0; i<mapVars.size(); i++) {
BaseType bt = (BaseType)mapVars.elementAt(i);
g.mapVars.addElement(bt.clone());
}
return g;
}
/**
* Returns the DODS type name of the class instance as a <code>String</code>.
* @return the DODS type name of the class instance as a <code>String</code>.
*/
public String getTypeName() {
return "Grid";
}
/**
* Returns the number of variables contained in this object. For simple and
* vector type variables, it always returns 1. To count the number
* of simple-type variable in the variable tree rooted at this variable, set
* <code>leaves</code> to <code>true</code>.
*
* @param leaves If true, count all the simple types in the `tree' of
* variables rooted at this variable.
* @return the number of contained variables.
*/
public int elementCount(boolean leaves) {
if (!leaves)
return mapVars.size() + 1; // Number of Maps plus 1 Array component
else {
int count = 0;
for (Enumeration e = mapVars.elements() ; e.hasMoreElements() ;) {
BaseType bt = (BaseType)e.nextElement();
count += bt.elementCount(leaves);
}
count += arrayVar.elementCount(leaves);
return count;
}
}
/**
* Adds a variable to the container.
* @param v the variable to add.
* @param part the part of the <code>DGrid</code> to be modified. Allowed
* values are <code>ARRAY</code> or <code>MAPS</code>.
* @exception IllegalArgumentException if an invalid part was given.
*/
public void addVariable(BaseType v, int part) {
if (!(v instanceof DArray)) throw new IllegalArgumentException(
"Grid `" + getName() +
"'s' member `" + arrayVar.getName() +
"' must be an array");
v.setParent(this);
switch (part) {
case ARRAY:
arrayVar = (DArray)v;
return;
case MAPS:
mapVars.addElement(v);
return;
default:
throw new IllegalArgumentException("addVariable(): Unknown Grid part");
}
}
/**
* Returns the named variable.
* @param name the name of the variable.
* @return the named variable.
* @exception NoSuchVariableException if the named variable does not
* exist in this container.
*/
public BaseType getVariable(String name) throws NoSuchVariableException {
int dotIndex = name.indexOf('.');
if (dotIndex != -1) { // name contains "."
String aggregate = name.substring(0, dotIndex);
String field = name.substring(dotIndex+1);
BaseType aggRef = getVariable(aggregate);
if (aggRef instanceof DConstructor)
return ((DConstructor)aggRef).getVariable(field); // recurse
else
; // fall through to throw statement
}
else {
if (arrayVar.getName().equals(name))
return arrayVar;
for (Enumeration e = mapVars.elements() ; e.hasMoreElements() ;) {
BaseType v = (BaseType)e.nextElement();
if (v.getName().equals(name))
return v;
}
}
throw new NoSuchVariableException("DGrid.getVariable() No such variable: '" + name + "'");
}
/**
* Gets the indexed variable. For a DGrid the index 0 returns the <code>DArray</code> and
* indexes 1 and higher return the associated map <code>Vector</code>s.
* @param index the index of the variable in the <code>Vector</code> Vars.
* @return the indexed variable.
* @exception NoSuchVariableException if the named variable does not
* exist in this container.
*/
public BaseType getVar(int index)
throws NoSuchVariableException {
if(index == 0){
return(arrayVar);
}
else {
int i = index - 1;
if(i < mapVars.size())
return((BaseType)mapVars.elementAt(i));
else
throw new NoSuchVariableException("DGrid.getVariable() No Such variable: " + index + " - 1)");
}
}
/** Private class for implemantation of the Enumeration. Because
* DStructure and DSequence are simpler classes and use a single Vector,
* their implementations of getVariables aren't as fancy. */
class EnumerateDGrid implements Enumeration {
boolean array;
Enumeration e;
EnumerateDGrid() {
array = false; // true when the array is/has being/been
// visited
e = mapVars.elements();
}
public boolean hasMoreElements() {
return (array == false) || e.hasMoreElements();
}
public Object nextElement() {
if (!array) {
array = true;
return arrayVar;
}
else {
return e.nextElement();
}
}
}
/** Return an Enumeration that can be used to iterate over the members of
* a Structure. This implementation provides access to the elements of
* the Structure. Each Object returned by the Enumeration can be cast to
* a BaseType.
@return An Enumeration */
public Enumeration getVariables() {
return new EnumerateDGrid();
}
/**
* Checks for internal consistency. For <code>DGrid</code>, verify that
* the map variables have unique names and match the number of dimensions
* of the array variable.
*
* @param all for complex constructor types, this flag indicates whether to
* check the semantics of the member variables, too.
* @exception BadSemanticsException if semantics are bad, explains why.
* @see BaseType#checkSemantics(boolean)
*/
public void checkSemantics(boolean all) throws BadSemanticsException {
super.checkSemantics(all);
Util.uniqueNames(mapVars, getName(), getTypeName());
if (arrayVar == null)
throw new BadSemanticsException("DGrid.checkSemantics(): Null grid base array in `"
+ getName() + "'");
// check semantics of array variable
arrayVar.checkSemantics(all);
// enough maps?
if (mapVars.size() != arrayVar.numDimensions())
throw new BadSemanticsException("DGrid.checkSemantics(): The number of map variables for grid `"
+ getName() + "' does not match the number of dimensions of `"
+ arrayVar.getName() + "'");
//----- I added this next test 12/3/99. As soon as I did I questioned whether or not
//----- it adds any value. ie: Can it ever happen that this test fails? I don't think
//----- so now that I have written it... ndp 12/3/99
// Is the size of the maps equal to the size of the cooresponding dimensions?
Enumeration emap = mapVars.elements();
Enumeration edims = arrayVar.getDimensions();
int dim = 0;
while(emap.hasMoreElements() && edims.hasMoreElements()){
DArray thisMapArray = (DArray) emap.nextElement();
Enumeration ema = thisMapArray.getDimensions();
DArrayDimension thisMapDim = (DArrayDimension) ema.nextElement();
DArrayDimension thisArrayDim = (DArrayDimension) edims.nextElement();
if(thisMapDim.getSize() != thisArrayDim.getSize() ){
throw new BadSemanticsException("In grid '" +
getName() +
" The size of dimension " +
dim + " in the array component '" +
arrayVar.getName() +
"is not equal to the size of the coresponding map vector '" +
thisMapArray.getName() + ".");
}
dim++;
}
//----- end ndp 12/3/99
}
/**
* Write the variable's declaration in a C-style syntax. This
* function is used to create textual representation of the Data
* Descriptor Structure (DDS). See <em>The DODS User Manual</em> for
* information about this structure.
*
* @param os The <code>PrintWriter</code> on which to print the
* declaration.
* @param space Each line of the declaration will begin with the
* characters in this string. Usually used for leading spaces.
* @param print_semi a boolean value indicating whether to print a
* semicolon at the end of the declaration.
*
* @see BaseType#printDecl(PrintWriter, String, boolean)
*/
public void printDecl(PrintWriter os, String space,boolean print_semi, boolean constrained) {
os.println(space + getTypeName() + " {");
os.println(space + " ARRAY:");
arrayVar.printDecl(os, space + " ", true);
os.println(space + " MAPS:");
for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
BaseType bt = (BaseType)e.nextElement();
bt.printDecl(os, space + " ", true);
}
os.print(space + "} " + getName());
if (print_semi)
os.println(";");
}
/**
* Prints the value of the variable, with its declaration. This
* function is primarily intended for debugging DODS applications and
* text-based clients such as geturl.
*
* @param os the <code>PrintWriter</code> on which to print the value.
* @param space this value is passed to the <code>printDecl</code> method,
* and controls the leading spaces of the output.
* @param print_decl_p a boolean value controlling whether the
* variable declaration is printed as well as the value.
* @see BaseType#printVal(PrintWriter, String, boolean)
*/
public void printVal(PrintWriter os, String space, boolean print_decl_p) {
if (print_decl_p) {
printDecl(os, space, false);
os.print(" = ");
}
os.print("{ ARRAY: ");
arrayVar.printVal(os, "", false);
os.print(" MAPS: ");
for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
BaseType bt = (BaseType)e.nextElement();
bt.printVal(os, "", false);
if(e.hasMoreElements())
os.print(", ");
}
os.print(" }");
if (print_decl_p)
os.println(";");
}
/**
* Reads data from a <code>DataInputStream</code>. This method is only used
* on the client side of the DODS client/server connection.
*
* @param source a <code>DataInputStream</code> to read from.
* @param sv the <code>ServerVersion</code> returned by the server.
* @param statusUI the <code>StatusUI</code> object to use for GUI updates
* and user cancellation notification (may be null).
* @exception EOFException if EOF is found before the variable is completely
* deserialized.
* @exception IOException thrown on any other InputStream exception.
* @exception DataReadException if an unexpected value was read.
* @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
*/
public synchronized void deserialize(DataInputStream source,
ServerVersion sv,
StatusUI statusUI)
throws IOException, DataReadException {
arrayVar.deserialize(source, sv, statusUI);
for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
if(statusUI != null && statusUI.userCancelled())
throw new DataReadException("DGrid.deserialize(): User cancelled");
ClientIO bt = (ClientIO)e.nextElement();
bt.deserialize(source, sv, statusUI);
}
}
/**
* Writes data to a <code>DataOutputStream</code>. This method is used
* primarily by GUI clients which need to download DODS data, manipulate
* it, and then re-save it as a binary file.
*
* @param sink a <code>DataOutputStream</code> to write to.
* @exception IOException thrown on any <code>OutputStream</code>
* exception.
*/
public void externalize(DataOutputStream sink) throws IOException {
arrayVar.externalize(sink);
for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
ClientIO bt = (ClientIO)e.nextElement();
bt.externalize(sink);
}
}
}