/////////////////////////////////////////////////////////////////////////////
// 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.util.Enumeration;
import java.util.Vector;
import java.util.Stack;
import java.io.*;
import dods.dap.parser.DDSParser;
import dods.dap.parser.ParseException;
/**
* The DODS Data Descriptor Object (DDS) is a data structure used by
* the DODS software to describe datasets and subsets of those
* datasets. The DDS may be thought of as the declarations for the
* data structures that will hold data requested by some DODS client.
* Part of the job of a DODS server is to build a suitable DDS for a
* specific dataset and to send it to the client. Depending on the
* data access API in use, this may involve reading part of the
* dataset and inferring the DDS. Other APIs may require the server
* simply to read some ancillary data file with the DDS in it.
* <p>
* On the server side, in addition to the data declarations, the DDS
* holds the clauses of any constraint expression that may have
* accompanied the data request from the DODS client. The DDS object
* includes methods for modifying the DDS according to the given
* constraint expression. It also has methods for directly modifying
* a DDS, and for transmitting it from a server to a client.
* <p>
* For the client, the DDS object includes methods for reading the
* persistent form of the object sent from a server. This includes parsing
* the ASCII representation of the object and, possibly, reading data
* received from a server into a data object.
* <p>
* Note that the class DDS is used to instantiate both DDS and DataDDS
* objects. A DDS that is empty (contains no actual data) is used by servers
* to send structural information to the client. The same DDS can becomes a
* DataDDS when data values are bound to the variables it defines.
* <p>
* For a complete description of the DDS layout and protocol, please
* refer to <em>The DODS User Guide</em>.
* <p>
* The DDS has an ASCII representation, which is what is transmitted
* from a DODS server to a client. Here is the DDS representation of
* an entire dataset containing a time series of worldwide grids of
* sea surface temperatures:
*
* <blockquote><pre>
* Dataset {
* Float64 lat[lat = 180];
* Float64 lon[lon = 360];
* Float64 time[time = 404];
* Grid {
* ARRAY:
* Int32 sst[time = 404][lat = 180][lon = 360];
* MAPS:
* Float64 time[time = 404];
* Float64 lat[lat = 180];
* Float64 lon[lon = 360];
* } sst;
* } weekly;
* </pre></blockquote>
*
* If the data request to this dataset includes a constraint
* expression, the corresponding DDS might be different. For
* example, if the request was only for northern hemisphere data
* at a specific time, the above DDS might be modified to appear like
* this:
*
* <blockquote><pre>
* Dataset {
* Grid {
* ARRAY:
* Int32 sst[time = 1][lat = 90][lon = 360];
* MAPS:
* Float64 time[time = 1];
* Float64 lat[lat = 90];
* Float64 lon[lon = 360];
* } sst;
* } weekly;
* </pre></blockquote>
*
* Since the constraint has narrowed the area of interest, the range
* of latitude values has been halved, and there is only one time
* value in the returned array. Note that the simple arrays (<em>lat</em>,
* <em>lon</em>, and <em>time</em>) described in the dataset are also part of
* the <em>sst</em> Grid object. They can be requested by themselves or as
* part of that larger object.
* <p>
* See <em>The DODS User Guide</em>, or the documentation of the
* BaseType class for descriptions of the DODS data types.
*
* @version $Revision: 1.3 $
* @author jehamby
* @see BaseType
* @see BaseTypeFactory
* @see DAS
*/
public class DDS implements Cloneable {
/** The dataset name. */
protected String name;
/** Variables at the top level. */
protected Vector vars;
/** Factory for new DAP variables. */
private BaseTypeFactory factory;
/** Creates an empty <code>DDS</code>. */
public DDS() {
this(null, new DefaultFactory());
}
/**
* Creates an empty <code>DDS</code> with the given dataset name.
* @param n the dataset name
*/
public DDS(String n) {
this(n, new DefaultFactory());
}
/**
* Creates an empty <code>DDS</code> with the given
* <code>BaseTypeFactory</code>. This will be used for DODS servers which
* need to construct subclasses of the various <code>BaseType</code> objects
* to hold additional server-side information.
*
* @param factory the server <code>BaseTypeFactory</code> object.
*/
public DDS(BaseTypeFactory factory) {
this(null, factory);
}
/**
* Creates an empty <code>DDS</code> with the given dataset name and
* <code>BaseTypeFactory</code>. This will be used for DODS servers which
* need to construct subclasses of the various <code>BaseType</code> objects
* to hold additional server-side information.
*
* @param n the dataset name
* @param factory the server <code>BaseTypeFactory</code> object.
*/
public DDS(String n, BaseTypeFactory factory) {
name = n;
vars = new Vector();
this.factory = factory;
}
/**
* Returns a clone of this <code>DDS</code>. A deep copy is performed on
* all variables inside the <code>DDS</code>.
*
* @return a clone of this <code>DDS</code>.
*/
public Object clone() {
try {
DDS d = (DDS)super.clone();
d.vars = new Vector();
for(int i=0; i<vars.size(); i++) {
BaseType element = (BaseType)vars.elementAt(i);
d.vars.addElement(element.clone());
}
d.name = new String(this.name);
// Question:
// What about copying the BaseTypeFactory?
// Do we want a reference to the same one? Or another instance?
// Is there a difference? Should we be building the clone
// using "new DDS(getFactory())"??
// Answer:
// Yes. Use the same type factory!
d.factory = this.factory;
return d;
}
catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* Get the Class factory. This is the machine that builds classes
* for the internal representation of the data set.
*
* @return the BaseTypeFactory.
*/
public final BaseTypeFactory getFactory() {
return factory;
}
/**
* Get the dataset's name. This is the name of the dataset
* itself, and is not to be confused with the name of the file or
* disk on which it is stored.
*
* @return the name of the dataset.
*/
public final String getName() {
return name;
}
/**
* Set the dataset's name. This is the name of the dataset
* itself, and is not to be confused with the name of the file or
* disk on which it is stored.
*
* @param name the name of the dataset.
*/
public final void setName(String name) {
this.name = name;
}
/**
* Adds a variable to the <code>DDS</code>.
*
* @param bt the new variable to add.
*/
public final void addVariable(BaseType bt) {
vars.addElement(bt);
}
/**
* Removes a variable from the <code>DDS</code>.
* Does nothing if the variable can't be found.
* If there are multiple variables with the same name, only the first
* will be removed. To detect this, call the <code>checkSemantics</code>
* method to verify that each variable has a unique name.
*
* @param name the name of the variable to remove.
* @see DDS#checkSemantics(boolean)
*/
public final void delVariable(String name) {
try {
BaseType bt = getVariable(name);
vars.removeElement(bt);
}
catch (NoSuchVariableException e) {}
}
/** Is the variable <code>var</code> a vector of DConstructors? Return
* true if it is, false otherwise. This mess will recurse into a
* DVector's template BaseType (which is a BaseTypePrimivitiveVector) and
* look to see if that is either a DConstructor or <em>contains</em> a
* DConstructor. So the <code>List Strucutre { ... } g[10];</code> should
* be handled correctly. <p>
*
* Note that the List type modifier may only appear once. */
private DConstructor isVectorOfDConstructor(BaseType var) {
if (!(var instanceof DVector))
return null;
if (!(((DVector)var).getPrimitiveVector()
instanceof BaseTypePrimitiveVector))
return null;
// OK. We have a DVector whose template is a BaseTypePrimitiveVector.
BaseTypePrimitiveVector btpv = (BaseTypePrimitiveVector)
((DVector)var).getPrimitiveVector();
// After that nasty cast, is the template a DConstructor?
if (btpv.getTemplate() instanceof DConstructor)
return (DConstructor)btpv.getTemplate();
else
return isVectorOfDConstructor(btpv.getTemplate());
}
/**
* Returns a reference to the named variable.
*
* @param name the name of the variable to return.
* @return the variable named <code>name</code>.
* @exception NoSuchVariableException if the variable isn't found.
*/
public BaseType getVariable(String name) throws NoSuchVariableException {
Stack s = new Stack();
s = search(name, s);
return (BaseType)s.pop();
}
/** Look for <code>name</code> in the DDS. Start the search using the
* ctor variable (or array/list of ctors) found on the top of the Stack
* <code>compStack</code> (for component stack). When the named variable
* is found, return the stack compStack modified so that it now contains
* each ctor-type variable that on the path to the named variable. If the
* variable is not found after exhausting all possibilities, throw
* NoSuchVariable.<p>
*
* Note: This method takes the stack as a parameter so that it can be
* used by a parser that is working through a list of identifiers that
* represents the path to a variable <em>as well as</em> a shorthand
* notation for the identifier that is the equivalent to the leaf node
* name alone. In the form case the caller helps build the stack by
* repeatedly calling <code>search</code>, in the latter case this method
* must build the stack itself. This method is over kill for the first
* case.
*
@param name Search for the named variable.
@param compStack The component stack. This holds the BaseType variables
* that match the each component of a specific variable's name. This
* method starts its search using the element at the top of the stack and
* adds to the stack. The order of BaseType variables on the stack is the
* reverse of the tree-traverse order. That is, the top most element on
* the stack is the BaseType for the named variable, <em>under</em> that
* is the named variable's parent and so on.
@return A stack of BaseType variables which hold the path from the top
* of the DDS to the named variable.
@exception NoSuchvariableException. */
public Stack search(String name, Stack compStack)
throws NoSuchVariableException
{
DDSSearch ddsSearch = new DDSSearch(compStack);
if (ddsSearch.deepSearch(name))
return ddsSearch.components;
else
throw new NoSuchVariableException("The variable `" + name
+ "' was not found in the dataset.");
}
/** Find variables in the DDS when users name them with either fully- or
* partially-qualified names. */
private final class DDSSearch {
Stack components;
DDSSearch(Stack c) {
components = c;
}
BaseType simpleSearch(String name, BaseType start) {
Enumeration e = null;
DConstructor dcv;
if (start == null)
e = getVariables(); // Start with the whole DDS
else if (start instanceof DConstructor)
e = ((DConstructor)start).getVariables();
else if ((dcv = isVectorOfDConstructor(start)) != null)
e = dcv.getVariables();
else
return null;
while (e.hasMoreElements()) {
BaseType v = (BaseType)e.nextElement();
if (v.getName().equals(name)) {
return v;
}
}
return null; // Not found
}
/** Look for the variable named <code>name</code>. First perform the
* shallow search (see simpleSearch) and then look through all the
* ctor variables. If there are no more ctors to check and the
* variable has not been found, return false.
*
* Note that this method uses the return value to indicate whether a
* particular invocation found <code>name</code>. */
boolean deepSearch(String name) throws NoSuchVariableException {
BaseType start = components.empty() ? null
: (BaseType)components.peek();
BaseType found;
if ((found = simpleSearch(name, start)) != null) {
components.push(found);
return true;
}
Enumeration e;
DConstructor dcv;
if (start == null)
e = getVariables(); // Start with the whole DDS
else if (start instanceof DConstructor)
e = ((DConstructor)start).getVariables();
else if ((dcv = isVectorOfDConstructor(start)) != null)
e = dcv.getVariables();
else
return false;
while (e.hasMoreElements()) {
BaseType v = (BaseType)e.nextElement();
components.push(v);
if (deepSearch(name))
return true;
else
components.pop();
}
// This second return takes care of the case where a dataset
// lists a bunch of ctor variable, one after another. Once the
// first ctor (say a Grid) has been searched returning false to
// the superior invocation of deepSearch pops it off the stack
// and the while loop will search starting with the next variable
// in the DDS.
return false;
}
}
/**
* Returns an <code>Enumeration</code> of the dataset variables.
*
* @return an <code>Enumeration</code> of <code>BaseType</code>.
*/
public final Enumeration getVariables() {
return vars.elements();
}
/**
* Returns the number of variables in the dataset.
*
* @return the number of variables in the dataset.
*/
public final int numVariables() {
return vars.size();
}
/**
* Reads a <code>DDS</code> from the named <code>InputStream</code>. This
* method calls a generated parser to interpret an ASCII representation of a
* <code>DDS</code>, and regenerate that <code>DDS</code> in memory.
*
* @param is the InputStream containing the <code>DDS</code> to parse.
* @exception ParseException thrown on a parser error.
* @exception DDSException thrown on an error constructing the
* <code>DDS</code>.
* @see dods.dap.parser.DDSParser
*/
public void parse(InputStream is) throws ParseException, DDSException {
DDSParser dp = new DDSParser(is);
dp.Dataset(this, factory);
}
/**
* Check the semantics of the <code>DDS</code>. If
* <code>all</code> is true, check not only the semantics of the
* <code>DDS</code> itself, but also recursively check all variables
* in the dataset.
*
* @param all this flag indicates whether to check the semantics of the
* member variables, too.
* @exception BadSemanticsException if semantics are bad
*/
public void checkSemantics(boolean all)
throws BadSemanticsException {
if (name == null) {
System.err.println("A dataset must have a name");
throw new BadSemanticsException("DDS.checkSemantics(): A dataset must have a name");
}
Util.uniqueNames(vars, name, "Dataset");
if (all) {
for (Enumeration e = vars.elements(); e.hasMoreElements() ;) {
BaseType bt = (BaseType)e.nextElement();
bt.checkSemantics(true);
}
}
}
/**
* Check the semantics of the <code>DDS</code>. Same as
* <code>checkSemantics(false)</code>.
*
* @exception BadSemanticsException if semantics are bad
* @see DDS#checkSemantics(boolean)
*/
public final void checkSemantics() throws BadSemanticsException {
checkSemantics(false);
}
/**
* Print the <code>DDS</code> on the given <code>PrintWriter</code>.
*
* @param os the <code>PrintWriter</code> to use for output.
*/
public void print(PrintWriter os) {
os.println("Dataset {");
for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {
BaseType bt = (BaseType)e.nextElement();
bt.printDecl(os);
}
os.print("} ");
if(name != null)
os.print(name);
os.println(";");
}
/**
* Print the <code>DDS</code> on the given <code>OutputStream</code>.
*
* @param os the <code>OutputStream</code> to use for output.
* @see DDS#print(PrintWriter)
*/
public final void print(OutputStream os) {
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
print(pw);
pw.flush();
}
/*
public void toASCII(PrintWriter pw, boolean addName){
String s = "";
Enumeration e = getVariables();
//System.out.println("DDS.toASCII("+addName+") getName(): "+getName());
while(e.hasMoreElements()){
BaseType bt = (BaseType)e.nextElement();
//System.out.println("Type: "+bt.getClass().getName());
//bt.toASCII(pw,addName,getNAme(),true);
bt.toASCII(pw,addName,null,true);
}
}
*/
}