/////////////////////////////////////////////////////////////////////////////
// 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.Enumeration;
import java.util.Vector;
/**
* A <code>DSequence</code> in DODS can hold <em>N</em> sequentially accessed
* instances of a set of variables. In relation to the <code>DStructure</code>
* datatype, a <code>DSequence</code> is a table
* of N instances of a <code>DStructure</code>. Data in a
* <code>DSequence</code> is accessed row by row.
* <p>
* Unlike its C++ counterpart, this class reads all of its rows on a
* <code>deserialize</code>, which gives <code>DSequence</code> the same
* semantics as the other <code>BaseType</code> classes, eliminating the need
* to worry about <code>DSequence</code> as a special case.
*
* @version $Revision: 1.3 $
* @author jehamby
* @see BaseType
* @see DConstructor
*/
public class DSequence extends DConstructor implements ClientIO {
/** The start of instance byte marker */
static protected byte START_OF_INSTANCE = 0x5A;
/** The end of sequence byte marker */
static protected byte END_OF_SEQUENCE = (byte)0xA5;
/**
* The variables in this <code>DSequence</code>, stored in a
* <code>Vector</code> of <code>BaseType</code> objects
* and used as a template for <code>deserialize</code>.
*/
protected Vector varTemplate;
/**
* The values in this <code>DSequence</code>, stored as a
* <code>Vector</code> of <code>Vector</code> of <code>BaseType</code>
* objects. */
protected Vector allValues;
/** Level number in a multilevel sequence. */
private int level;
/** Constructs a new <code>DSequence</code>. */
public DSequence() {
this(null);
}
/**
* Constructs a new <code>DSequence</code> with name <code>n</code>.
* @param n the name of the variable.
*/
public DSequence(String n) {
super(n);
varTemplate = new Vector();
allValues = new Vector();
level = 0;
}
/**
* Returns a clone of this <code>DSequence</code>. A deep copy is performed
* on all data inside the variable.
*
* @return a clone of this <code>DSequence</code>.
*/
public Object clone() {
DSequence s = (DSequence)super.clone();
s.varTemplate = new Vector();
for(int i=0; i<varTemplate.size(); i++) {
BaseType bt = (BaseType)varTemplate.elementAt(i);
s.varTemplate.addElement(bt.clone());
}
s.allValues = new Vector();
for(int i=0; i<allValues.size(); i++) {
Vector rowVec = (Vector)allValues.elementAt(i);
Vector newVec = new Vector();
for(int j=0; j<rowVec.size(); j++) {
BaseType bt = (BaseType)rowVec.elementAt(j);
newVec.addElement(bt.clone());
}
s.allValues.addElement(newVec);
}
return s;
}
/**
* 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 "Sequence";
}
/**
* Sets the level of this sequence.
* @param level the new level.
*/
protected final void setLevel(int level) {
this.level = level;
}
/**
* Returns the level of this sequence.
* @return the level of this sequence.
*/
protected final int getLevel() {
return level;
}
/**
* 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 varTemplate.size();
else {
int count = 0;
for (Enumeration e = varTemplate.elements() ; e.hasMoreElements() ;) {
BaseType bt = (BaseType)e.nextElement();
count += bt.elementCount(leaves);
}
return count;
}
}
/**
* Adds a variable to the container.
* @param v the variable to add.
* @param part ignored for <code>DSequence</code>.
*/
public void addVariable(BaseType v, int part) {
v.setParent(this);
varTemplate.addElement(v);
if (v instanceof DSequence)
((DSequence)v).setLevel(getLevel()+1);
}
/**
* Adds a row to the container. This is assumed to contain a
* <code>Vector</code> of variables of the same type and in the same order
* as the variable template added with the <code>addVariable</code> method.
*
* @param row the <code>Vector</code> to add.
*/
public final void addRow(Vector row) {
allValues.addElement(row);
}
/**
* Gets a row from the container. This returns a <code>Vector</code> of
* variables of the same type and in the same order as the variable template
* added with the <code>addVariable</code> method.
*
* @param row the row number to retrieve.
* @return the <code>Vector</code> of <code>BaseType</code> variables.
*/
public final Vector getRow(int row) {
return (Vector)allValues.elementAt(row);
}
/**
* Deletes a row from the container.
*
* @param row the row number to delete.
* @exception ArrayIndexOutOfBoundsException if the index was invalid.
*/
public final void delRow(int row) {
allValues.removeElementAt(row);
}
/**
* Returns the number of rows in this <code>Sequence</code>.
* @return the number of rows currently in this <code>Sequence</code>.
*/
public int getRowCount() {
return allValues.size();
}
/**
* Returns the named variable. <b>Note:</b> In <code>DSequence</code>,
* this method returns the template variable, which holds no data. If you
* need to get a variable containing data, use <code>getRow</code> or the
* <code>getVariable</code> method which takes a row number parameter.
*
* @param name the name of the variable.
* @return the named variable.
* @exception NoSuchVariableException if the named variable does not
* exist in this container.
* @see DSequence#getVariable(int, String)
*/
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 {
for (Enumeration e = varTemplate.elements(); e.hasMoreElements();) {
BaseType v = (BaseType)e.nextElement();
if (v.getName().equals(name))
return v;
}
}
throw new NoSuchVariableException("DSequence: getVariable()");
}
/**
* Gets the indexed variable. For a DSrquence this returns the <code>BaseType</code>
* from the <code>index</code>th column from the internal map <code>Vector</code>.
* @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 < varTemplate.size())
return((BaseType)varTemplate.elementAt(index));
else
throw new NoSuchVariableException("DSequence.getVariable(" + index + " - 1)");
}
/**
* Returns the named variable in the given row of the sequence.
*
* @param row the row number to retrieve.
* @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(int row, 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 {
Vector selectedRow = (Vector)allValues.elementAt(row);
for (Enumeration e = selectedRow.elements() ; e.hasMoreElements() ;) {
BaseType v = (BaseType)e.nextElement();
if (v.getName().equals(name))
return v;
}
}
throw new NoSuchVariableException("DSequence: getVariable()");
}
/** Return an Enumeration that can be used to iterate over the members of
* a Sequence. This implementation provides access to the template
* elements of the Sequence, not the entire sequence. Each Object
* returned by the Enumeration can be cast to a BaseType.
@return An Enumeration */
public Enumeration getVariables() {
return varTemplate.elements();
}
/**
* Checks for internal consistency. For <code>DSequence</code>, verify that
* the variables have unique names.
*
* @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(varTemplate, getName(), getTypeName());
if (all) {
for(Enumeration e = varTemplate.elements(); e.hasMoreElements(); ) {
BaseType bt = (BaseType)e.nextElement();
bt.checkSemantics(true);
}
}
}
/**
* 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) {
// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
// and all of the different signatures of printDecl() in BaseType
// lead to one signature, we must be careful to override that
// SAME signature here. That way all calls to printDecl() for
// this object lead to this implementation.
os.println(space + getTypeName() + " {");
for(Enumeration e = varTemplate.elements(); e.hasMoreElements(); ) {
BaseType bt = (BaseType)e.nextElement();
//os.println("Printing declaration for \""+bt.getName()+"\" constrained: "+constrained);
bt.printDecl(os, space + " ", true, constrained);
}
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("{ ");
for(Enumeration e1 = allValues.elements(); e1.hasMoreElements(); ) {
// get next instance vector
os.print("{ ");
Vector v = (Vector)e1.nextElement();
for(Enumeration e2 = v.elements(); e2.hasMoreElements(); ) {
// get next instance variable
BaseType bt = (BaseType)e2.nextElement();
bt.printVal(os, "", false);
if (e2.hasMoreElements())
os.print(", ");
}
os.print(" }");
if(e1.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, EOFException, DataReadException {
// check for old servers
if (sv.getMajor() < 2 || (sv.getMajor() == 2 && sv.getMinor() < 15)) {
oldDeserialize(source, sv, statusUI);
}
else {
// ************* Pulled out the getLevel() check in order to support the "new"
// and "improved" serialization of dods sequences. 8/31/01 ndp
// // top level of sequence handles start and end markers
// if (getLevel() == 0) {
// loop until end of sequence
for(;;) {
byte marker = readMarker(source);
if(statusUI != null)
statusUI.incrementByteCount(4);
if (marker == START_OF_INSTANCE)
deserializeSingle(source, sv, statusUI);
else if (marker == END_OF_SEQUENCE)
break;
else
throw new DataReadException("Sequence start marker not found");
}
// ************* Pulled out the getLevel() check in order to support the "new"
// and "improved" serialization of dods sequences. 8/31/01 ndp
// }
// else {
// // lower levels only deserialize a single instance at a time
// deserializeSingle(source, sv, statusUI);
// }
}
}
/**
* The old deserialize protocol has a number of limitations stemming from
* its inability to tell when the sequence is finished. It's really only
* good for a Dataset containing a single sequence, or where the sequence is
* the last thing in the dataset. To handle this, we just read single
* instances until we get an IOException, then stop.
*
* @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 IOException thrown on any InputStream exception other than EOF
* (which is trapped here).
* @exception DataReadException if an unexpected value was read.
*/
private void oldDeserialize(DataInputStream source, ServerVersion sv,
StatusUI statusUI)
throws IOException, DataReadException {
try {
for(;;) {
deserializeSingle(source, sv, statusUI);
}
}
catch (EOFException e) {}
}
/**
* Deserialize a single row of the <code>DSequence</code>.
*
* @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.
*/
private void deserializeSingle(DataInputStream source, ServerVersion sv,
StatusUI statusUI)
throws IOException, EOFException, DataReadException {
// create a new instance from the variable template Vector
Vector newInstance = new Vector();
for(int i=0; i<varTemplate.size(); i++) {
BaseType bt = (BaseType)varTemplate.elementAt(i);
newInstance.addElement(bt.clone());
}
// deserialize the new instance
for (Enumeration e = newInstance.elements(); e.hasMoreElements(); ) {
if(statusUI != null && statusUI.userCancelled())
throw new DataReadException("User cancelled");
ClientIO bt = (ClientIO)e.nextElement();
bt.deserialize(source, sv, statusUI);
}
// add the new instance to the allValues vector
allValues.addElement(newInstance);
}
/** Reads a marker byte from the input stream. */
private byte readMarker(DataInputStream source) throws IOException {
byte marker = source.readByte();
// pad out to a multiple of four bytes
byte unused;
for (int i=0; i<3; i++)
unused = source.readByte();
return marker;
}
/** Writes a marker byte to the output stream. */
protected void writeMarker(DataOutputStream sink, byte marker) throws IOException {
//for(int i=0; i<4; i++)
sink.writeByte(marker);
sink.writeByte((byte)0);
sink.writeByte((byte)0);
sink.writeByte((byte)0);
}
/**
* 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 {
// loop until end of sequence
for(int i=0; i<allValues.size(); i++) {
// ************* Pulled out the getLevel() check in order to support the "new"
// and "improved" serialization of dods sequences. 8/31/01 ndp
// if (getLevel() == 0)
writeMarker(sink, START_OF_INSTANCE);
Vector rowVec = (Vector)allValues.elementAt(i);
for(int j=0; j<rowVec.size(); j++) {
ClientIO bt = (ClientIO)rowVec.elementAt(j);
bt.externalize(sink);
}
}
// ************* Pulled out the getLevel() check in order to support the "new"
// and "improved" serialization of dods sequences. 8/31/01 ndp
// if (getLevel() == 0)
writeMarker(sink, END_OF_SEQUENCE);
}
}