diff --git a/lib/uploadUtilsSrc/cos.jar b/lib/cos.jar similarity index 100% rename from lib/uploadUtilsSrc/cos.jar rename to lib/cos.jar diff --git a/lib/stil3.0-5.jar b/lib/stil3.0-5.jar new file mode 100644 index 0000000000000000000000000000000000000000..d9f1a4a798e8f62f884580bc10ab9662eb441c64 Binary files /dev/null and b/lib/stil3.0-5.jar differ diff --git a/lib/uploadUtils.jar b/lib/uploadUtils.jar deleted file mode 100644 index 074638e30fbd33bca8d7bf6593d0920dbcc91fdd..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtils.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/binarySavot.jar b/lib/uploadUtilsSrc/binarySavot.jar deleted file mode 100644 index 175bfaf7b60d030f0600a80f826bbf338c8c19c3..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/binarySavot.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/buildJar.xml b/lib/uploadUtilsSrc/buildJar.xml deleted file mode 100644 index de5beb64495e00d108e5252a31d495d2846cb7df..0000000000000000000000000000000000000000 --- a/lib/uploadUtilsSrc/buildJar.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE project> -<project name="TAPUploaderTest" basedir="." default="jarBuild"> - <target name="init"> - <property name="srcRep" value="src" /> - <property name="buildRep" value="build"/> - <property name="jarFile" value="uploadUtils.jar" /> - <property name="jarDest" value=".." /> - </target> - - <target name="jarBuild" depends="init" > - <echo>Création du jar (${jarDest}/${jarFile})...</echo> - <jar destfile="${jarDest}/${jarFile}"> - <zipfileset src="binarySavot.jar" excludes="META-INF/*" /> - <zipfileset src="cds.savot.common.jar" excludes="META-INF/*" /> - <zipfileset src="cds.savot.model.jar" excludes="META-INF/*" /> - <zipfileset src="cds.savot.pull.jar" excludes="META-INF/*" /> - <zipfileset src="cds.savot.writer.jar" excludes="META-INF/*" /> - <zipfileset src="kxml2-min.jar" excludes="META-INF/*" /> - <zipfileset src="cos.jar" excludes="META-INF/*" /> - </jar> - </target> -</project> \ No newline at end of file diff --git a/lib/uploadUtilsSrc/cds.savot.common.jar b/lib/uploadUtilsSrc/cds.savot.common.jar deleted file mode 100644 index 9966915834caa384e344a8668a5f0f2dac02be09..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/cds.savot.common.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/cds.savot.model.jar b/lib/uploadUtilsSrc/cds.savot.model.jar deleted file mode 100644 index 00aac3bb37db258347bec5768027c82a70e90ff3..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/cds.savot.model.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/cds.savot.pull.jar b/lib/uploadUtilsSrc/cds.savot.pull.jar deleted file mode 100644 index fb8dc22d327c808110a07324a3bd8beee9643560..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/cds.savot.pull.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/cds.savot.writer.jar b/lib/uploadUtilsSrc/cds.savot.writer.jar deleted file mode 100644 index 92113cdb854ccfa625a7f70f37975fdc5b5feb73..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/cds.savot.writer.jar and /dev/null differ diff --git a/lib/uploadUtilsSrc/kxml2-min.jar b/lib/uploadUtilsSrc/kxml2-min.jar deleted file mode 100644 index a77dd1d0063db3d18e1a81cb5a895c45531eccc5..0000000000000000000000000000000000000000 Binary files a/lib/uploadUtilsSrc/kxml2-min.jar and /dev/null differ diff --git a/src/tap/data/VOTableIterator.java b/src/tap/data/VOTableIterator.java index 00349c24714987c12681a093967334cae95d1a18..13628d57be9073f710c101a02d5d99b38fa9006b 100644 --- a/src/tap/data/VOTableIterator.java +++ b/src/tap/data/VOTableIterator.java @@ -21,40 +21,45 @@ package tap.data; import java.io.IOException; import java.io.InputStream; -import java.util.Collection; -import java.util.Iterator; import java.util.NoSuchElementException; +import tap.TAPException; import tap.metadata.TAPColumn; import tap.metadata.TAPType; import tap.metadata.VotType; import tap.metadata.VotType.VotDatatype; -import cds.savot.model.DataBinaryReader; -import cds.savot.model.FieldSet; -import cds.savot.model.SavotBinary; -import cds.savot.model.SavotField; -import cds.savot.model.SavotResource; -import cds.savot.model.SavotTD; -import cds.savot.model.SavotTR; -import cds.savot.model.SavotTableData; -import cds.savot.pull.SavotPullEngine; -import cds.savot.pull.SavotPullParser; +import uk.ac.starlink.table.ColumnInfo; +import uk.ac.starlink.table.DescribedValue; +import uk.ac.starlink.table.OnceRowPipe; +import uk.ac.starlink.table.RowSequence; +import uk.ac.starlink.table.StarTable; +import uk.ac.starlink.table.StarTableFactory; +import uk.ac.starlink.table.TableBuilder; /** - * <p>{@link TableIterator} which lets iterate over a VOTable input stream using Savot ({@link SavotPullParser} more exactly).</p> + * <p>{@link TableIterator} which lets iterate over a VOTable input stream using STIL.</p> * * <p>{@link #getColType()} will return TAP type based on the type declared in the VOTable metadata part.</p> * - * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 2.0 (06/2014) + * @author Grégory Mantelet (ARI) + * @version 2.0 (07/2014) * @since 2.0 */ public class VOTableIterator implements TableIterator { /** Metadata of all columns identified before the iteration. */ private final TAPColumn[] colMeta; - /** Inner TableIterator. It lets iterate over a binary or a table data set in a transparent way. */ - private final TableIterator it; + /** Number of columns to read. */ + private final int nbColumns; + /** Sequence of rows over which we must iterate. */ + private final RowSequence rowSeq; + + /** Indicate whether the row iteration has already started. */ + private boolean iterationStarted = false; + /** Indicate whether the last row has already been reached. */ + private boolean endReached = false; + /** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}, ={@link #nbColumns} after the last column has been read). */ + private int colIndex; /** * Build a TableIterator able to read rows and columns inside the given VOTable input stream. @@ -70,51 +75,66 @@ public class VOTableIterator implements TableIterator { throw new NullPointerException("Missing VOTable document input stream over which to iterate!"); try{ - // Start parsing the VOTable: - SavotPullParser parser = new SavotPullParser(input, SavotPullEngine.SEQUENTIAL, null); - - // Get the first resource: - SavotResource resource = parser.getNextResource(); - if (resource == null) - throw new DataReadException("Incorrect VOTable format: missing resource node!"); - - // Extract the metadata about all fields: - FieldSet fields = resource.getFieldSet(0); - colMeta = extractColMeta(fields); - - // Build the iterator over the data: - SavotBinary binary = resource.getData(0).getBinary(); - if (binary != null) - it = new BinaryVOTableIterator(binary, fields, colMeta); - else - it = new DataVOTableIterator(resource.getData(0).getTableData(), colMeta); + + // Set the VOTable builder/interpreter: + TableBuilder tb = (new StarTableFactory()).getTableBuilder("votable"); + + // Set the TableSink to use in order to stream the data: + OnceRowPipe rowPipe = new OnceRowPipe(); + + // Initiate the stream process: + tb.streamStarTable(input, rowPipe, null); + + // Start by reading just the metadata: + StarTable table = rowPipe.waitForStarTable(); + + // Convert columns' information into TAPColumn object: + colMeta = extractColMeta(table); + nbColumns = colMeta.length; + + // Set the sequence of rows on which this iterator will iterate: + rowSeq = table.getRowSequence(); + + }catch(TAPException te){ + throw new DataReadException("Unexpected field datatype: " + te.getMessage(), te); }catch(Exception ex){ throw new DataReadException("Unable to parse/read the given VOTable input stream!", ex); } } /** - * Extract an array of {@link TAPColumn} objects. Each corresponds to one of the fields given in parameter, + * Extract an array of {@link TAPColumn} objects. Each corresponds to one of the columns listed in the given table, * and so corresponds to the metadata of a column. * - * @param fields List of metadata fields provided in a VOTable. + * @param table {@link StarTable} which contains only the columns' information. * * @return The corresponding list of {@link TAPColumn} objects. + * + * @throws TAPException If there is a problem while resolving the field datatype (for instance: unknown datatype, a multi-dimensional array is provided, a bad number format for the arraysize). */ - private static final TAPColumn[] extractColMeta(final FieldSet fields){ + private static final TAPColumn[] extractColMeta(final StarTable table) throws TAPException{ // Count the number columns and initialize the array: - TAPColumn[] columns = new TAPColumn[fields.getItemCount()]; + TAPColumn[] columns = new TAPColumn[table.getColumnCount()]; // Add all columns meta: - for(int i = 0; i < fields.getItemCount(); i++){ + for(int i = 0; i < columns.length; i++){ // get the field: - SavotField field = (SavotField)fields.getItemAt(i); + ColumnInfo colInfo = table.getColumnInfo(i); + + // get the datatype: + String datatype = getAuxDatumValue(colInfo, "Datatype"); + + // get the arraysize: + String arraysize = ColumnInfo.formatShape(colInfo.getShape()); + + // get the xtype: + String xtype = getAuxDatumValue(colInfo, "xtype"); // Resolve the field type: - TAPType type = resolveVotType(field.getDataType(), field.getArraySize(), field.getXtype()).toTAPType(); + TAPType type = resolveVotType(datatype, arraysize, xtype).toTAPType(); // build the TAPColumn object: - TAPColumn col = new TAPColumn(field.getName(), type, field.getDescription(), field.getUnit(), field.getUcd(), field.getUtype()); + TAPColumn col = new TAPColumn(colInfo.getName(), type, colInfo.getDescription(), colInfo.getUnitString(), colInfo.getUCD(), colInfo.getUtype()); col.setPrincipal(false); col.setIndexed(false); col.setStd(false); @@ -126,6 +146,19 @@ public class VOTableIterator implements TableIterator { return columns; } + /** + * Extract the specified auxiliary datum value from the given {@link ColumnInfo}. + * + * @param colInfo {@link ColumnInfo} from which the auxiliary datum must be extracted. + * @param auxDatumName The name of the datum to extract. + * + * @return The extracted value as String. + */ + private static final String getAuxDatumValue(final ColumnInfo colInfo, final String auxDatumName){ + DescribedValue value = colInfo.getAuxDatumByName(auxDatumName); + return (value != null) ? value.getValue().toString() : null; + } + /** * Resolve a VOTable field type by using the datatype, arraysize and xtype strings as specified in a VOTable document. * @@ -134,53 +167,24 @@ public class VOTableIterator implements TableIterator { * @param xtype Attribute value of VOTable corresponding to the xtype. * * @return The resolved VOTable field type, or a CHAR(*) type if the specified type can not be resolved. + * + * @throws TAPException If a field datatype is unknown. */ - private static VotType resolveVotType(final String datatype, final String arraysize, final String xtype){ + private static VotType resolveVotType(final String datatype, final String arraysize, final String xtype) throws TAPException{ // If no datatype is specified, return immediately a CHAR(*) type: if (datatype == null || datatype.trim().length() == 0) - return new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true); - - // 1. IDENTIFY THE DATATYPE: + return new VotType(VotDatatype.CHAR, "*"); // Identify the specified datatype: VotDatatype votdatatype; try{ votdatatype = VotDatatype.valueOf(datatype.toUpperCase()); }catch(IllegalArgumentException iae){ - // if it can't be identified, return immediately a CHAR(*) type: - return new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true); + throw new TAPException("unknown field datatype: \"" + datatype + "\""); } - // 2. DETERMINE ITS ARRAYSIZE: - - int votarraysize = VotType.NO_SIZE; - boolean votunlimitedSize = false; - - // If no arraysize is specified, let's set it to 1 (for an elementary value): - if (arraysize == null || arraysize.trim().isEmpty()) - votarraysize = 1; - - // Otherwise, get it: - else{ - String str = arraysize.trim(); - - // Determine whether an "unlimited size" character is specified: - votunlimitedSize = str.endsWith("*"); - - // If one is specified, remove it from the arraysize string: - if (votunlimitedSize) - str = str.substring(0, str.length() - 1); - - // If a size is really specified (more characters than "*"), get the arraysize value: - if (str.length() > 0){ - try{ - votarraysize = Integer.parseInt(str); - }catch(NumberFormatException nfe){} - } - } - - // And finally build the VOTable type: - return new VotType(votdatatype, votarraysize, votunlimitedSize, xtype); + // Build the VOTable type: + return new VotType(votdatatype, arraysize, xtype); } /** @@ -191,11 +195,11 @@ public class VOTableIterator implements TableIterator { * </ul> * @throws IllegalStateException */ - private static void checkReadState(final boolean iterationStarted, final boolean endReached) throws IllegalStateException{ + private void checkReadState() throws IllegalStateException{ if (!iterationStarted) throw new IllegalStateException("No row has yet been read!"); else if (endReached) - throw new IllegalStateException("End of ResultSet already reached!"); + throw new IllegalStateException("End of VOTable file already reached!"); } @Override @@ -205,263 +209,55 @@ public class VOTableIterator implements TableIterator { @Override public boolean nextRow() throws DataReadException{ - return it.nextRow(); + try{ + // go to the next row: + boolean rowFetched = rowSeq.next(); + endReached = !rowFetched; + // prepare the iteration over its columns: + colIndex = 0; + iterationStarted = true; + return rowFetched; + }catch(IOException e){ + throw new DataReadException("Unable to read the next VOTable row!", e); + } } @Override public boolean hasNextCol() throws IllegalStateException, DataReadException{ - return it.hasNextCol(); - } + // Check the read state: + checkReadState(); - @Override - public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{ - return it.nextCol(); + // Determine whether the last column has been reached or not: + return (colIndex < nbColumns); } @Override - public TAPType getColType() throws IllegalStateException, DataReadException{ - return it.getColType(); - } - - /** - * <p>{@link TableIterator} which lets iterate over a VOTable binary data part.</p> - * - * <p>This {@link TableIterator} is only usable by {@link VOTableIterator}.</p> - * - * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 2.0 (Jun 27, 2014) - * @since 2.0 - */ - private static class BinaryVOTableIterator implements TableIterator { - - /** Binary data reader which lets read rows and columns, and thus iterate over them. */ - private final DataBinaryReader reader; - /** Metadata of all columns identified before the iteration. <i>(In this TableIterator, they are completely provided by {@link VOTableIterator}).</i> */ - private final TAPColumn[] colMeta; - - /** The last read row. Each item is a column value. */ - private Object[] row; - - /** Indicate whether the row iteration has already started. */ - private boolean iterationStarted = false; - /** Indicate whether the last row has already been reached. */ - private boolean endReached = false; - /** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}). */ - private int colIndex; - - /** - * Build a TableIterator on the given binary data part of a VOTable whose fields are also described in parameter. - * - * @param binary Binary data part of a VOTable document. - * @param fields Description of all the fields that should be read. - * @param columnsMeta Metadata information extracted from the VOTable metadata part. - * - * @throws DataReadException If there is an error while starting reading the given binary data. - */ - public BinaryVOTableIterator(final SavotBinary binary, final FieldSet fields, final TAPColumn[] columnsMeta) throws DataReadException{ - try{ - reader = new DataBinaryReader(binary.getStream(), fields, false); - colMeta = columnsMeta; - }catch(IOException ioe){ - throw new DataReadException("Can not open a stream to decode the binary VOTable data!", ioe); - } - } - - @Override - public TAPColumn[] getMetadata(){ - return null; - } - - @Override - public boolean nextRow() throws DataReadException{ - try{ - // Go to the next row: - boolean rowFetched = reader.next(); - // prepare the iteration over its columns: - if (rowFetched){ - row = reader.getRow(); - colIndex = -1; - iterationStarted = true; - }else{ - row = null; - colIndex = -1; - endReached = true; - } - return rowFetched; - }catch(IOException e){ - throw new DataReadException("Unable to read a VOTable row!", e); - } - } - - @Override - public boolean hasNextCol() throws IllegalStateException, DataReadException{ - // Check the read state: - checkReadState(iterationStarted, endReached); - - // Determine whether the last column has been reached or not: - return (colIndex + 1 < row.length); - } - - @Override - public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{ - // Check the read state and ensure there is still at least one column to read: - if (!hasNextCol()) - throw new NoSuchElementException("No more column to read!"); - - // Get the column value: - return row[++colIndex]; - } - - @Override - public TAPType getColType() throws IllegalStateException, DataReadException{ - // Basically check the read state (for rows iteration): - checkReadState(iterationStarted, endReached); - - // Check deeper the read state (for columns iteration): - if (colIndex < 0) - throw new IllegalStateException("No column has yet been read!"); - else if (colIndex >= colMeta.length) - return null; - - // Get the column value: - return colMeta[colIndex].getDatatype(); - } - - } - - /** - * <p>{@link TableIterator} which lets iterate over a VOTable table data part.</p> - * - * <p>This {@link TableIterator} is only usable by {@link VOTableIterator}.</p> - * - * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 2.0 (Jun 27, 2014) - * @since 2.0 - */ - private static class DataVOTableIterator implements TableIterator { - - /** Iterator over the rows contained in the VOTable data part. */ - private final Iterator<Object> data; - /** Metadata of all columns identified before the iteration. <i>(In this TableIterator, they are completely provided by {@link VOTableIterator}).</i> */ - private final TAPColumn[] colMeta; - - /** Iterator over the columns contained in the last read row. */ - private Iterator<Object> colsIt; - - /** Indicate whether the row iteration has already started. */ - private boolean iterationStarted = false; - /** Indicate whether the last row has already been reached. */ - private boolean endReached = false; - /** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}). */ - private int colIndex; - - /** - * Build a TableIterator on the given table data part of a VOTable. - * - * @param dataset Table data part of a VOTable document. - * @param columnsMeta Metadata information extracted from the VOTable metadata part. - */ - public DataVOTableIterator(final SavotTableData dataset, final TAPColumn[] columnsMeta){ - Collection<Object> trset = dataset.getTRs().getItems(); - if (trset == null){ - data = new NullIterator(); - colMeta = columnsMeta; - iterationStarted = true; - endReached = true; - }else{ - data = trset.iterator(); - colMeta = columnsMeta; - } - } - - @Override - public TAPColumn[] getMetadata(){ - return null; - } - - @Override - public boolean nextRow() throws DataReadException{ - if (data.hasNext()){ - // Go to the next row: - SavotTR row = (SavotTR)data.next(); - - // Prepare the iteration over its columns: - Collection<Object> tdset = row.getTDSet().getItems(); - if (tdset == null) - colsIt = new NullIterator(); - else - colsIt = tdset.iterator(); - - colIndex = -1; - iterationStarted = true; - - return true; - }else{ - // No more row to read => end of VOTable reached: - endReached = true; - return false; - } - } - - @Override - public boolean hasNextCol() throws IllegalStateException, DataReadException{ - // Check the read state: - checkReadState(iterationStarted, endReached); - - // Determine whether the last column has been reached or not: - return colsIt.hasNext(); - } - - @Override - public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{ - // Check the read state and ensure there is still at least one column to read: - if (!hasNextCol()) - throw new NoSuchElementException("No more column to read!"); - - // Get the column value: - Object value = ((SavotTD)colsIt.next()).getContent(); - colIndex++; - return value; - } - - @Override - public TAPType getColType() throws IllegalStateException, DataReadException{ - // Basically check the read state (for rows iteration): - checkReadState(iterationStarted, endReached); - - // Check deeper the read state (for columns iteration): - if (colIndex < 0) - throw new IllegalStateException("No column has yet been read!"); - else if (colIndex >= colMeta.length) - return null; + public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{ + // Check the read state and ensure there is still at least one column to read: + if (!hasNextCol()) + throw new NoSuchElementException("No more field to read!"); - // Get the column value: - return colMeta[colIndex].getDatatype(); + // Get the column value: + try{ + return rowSeq.getCell(colIndex++); + }catch(IOException se){ + throw new DataReadException("Can not read the value of the " + colIndex + "-th field!", se); } - } - /** - * Iterator over nothing. - * - * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de - * @version 2.0 (06/2014) - * @version 2.0 - */ - private final static class NullIterator implements Iterator<Object> { - @Override - public boolean hasNext(){ - return false; - } - - @Override - public Object next(){ - return null; - } + @Override + public TAPType getColType() throws IllegalStateException, DataReadException{ + // Basically check the read state (for rows iteration): + checkReadState(); - @Override - public void remove(){} + // Check deeper the read state (for columns iteration): + if (colIndex <= 0) + throw new IllegalStateException("No field has yet been read!"); + else if (colIndex > nbColumns) + throw new IllegalStateException("All fields have already been read!"); + // Return the column type: + return colMeta[colIndex - 1].getDatatype(); } } diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java index 974df0546c6108419ef1489066e7d37d842d039f..1e87e5d17689fc0ee97138d49cf791724d4df1ca 100644 --- a/src/tap/formatter/JSONFormat.java +++ b/src/tap/formatter/JSONFormat.java @@ -106,7 +106,7 @@ public class JSONFormat implements OutputFormat { // { out.object(); - // "metadata": {...} + // "metadata": [...] out.key("metadata"); // Write metadata part: @@ -114,7 +114,7 @@ public class JSONFormat implements OutputFormat { writer.flush(); - // "data": {...} + // "data": [...] out.key("data"); // Write the data part: @@ -229,16 +229,11 @@ public class JSONFormat implements OutputFormat { out.key("description").value(tapCol.getDescription()); // "datatype": "..." - VotType votType = tapCol.getVotType(); + VotType votType = tapCol.getDatatype().toVotType(); out.key("datatype").value(votType.datatype); // "arraysize": "..." (if any) - if (votType.unlimitedArraysize){ - if (votType.arraysize > 0) - out.key("arraysize").value(votType.arraysize + "*"); - else - out.key("arraysize").value("*"); - }else if (votType.arraysize > 0) + if (votType.arraysize != null) out.key("arraysize").value(votType.arraysize); // "xtype": "..." (if any) diff --git a/src/tap/formatter/OutputFormat.java b/src/tap/formatter/OutputFormat.java index a13383a1900eb7b6fd62f555b4b149c8091fbee8..a881a2acf2e1dd26a3ae9e232df05612901572b6 100644 --- a/src/tap/formatter/OutputFormat.java +++ b/src/tap/formatter/OutputFormat.java @@ -64,7 +64,9 @@ public interface OutputFormat { public String getFileExtension(); /** - * Formats the given query result and writes it in the given output stream. + * <p>Formats the given query result and writes it in the given output stream.</p> + * + * <p><i>Note: the given output stream should not be closed at the end of this function. It is up to the called to do it.</i></p> * * @param result The raw (table) result to format. * @param output The output stream (a ServletOutputStream or a stream on a file) in which the formatted result must be written. diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java index 04ca76a0508114add3e6b4d5cabcfe6e4722f78b..16d5e321ad79819e94586ae4a6298a503b6679cb 100644 --- a/src/tap/formatter/VOTableFormat.java +++ b/src/tap/formatter/VOTableFormat.java @@ -20,31 +20,49 @@ package tap.formatter; * Astronomisches Rechen Institut (ARI) */ +import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import tap.ServiceConnection; import tap.TAPException; import tap.TAPExecutionReport; -import tap.TAPJob; +import tap.data.DataReadException; import tap.data.TableIterator; import tap.metadata.TAPColumn; import tap.metadata.TAPType; import tap.metadata.TAPType.TAPDatatype; import tap.metadata.VotType; import tap.metadata.VotType.VotDatatype; +import uk.ac.starlink.table.AbstractStarTable; +import uk.ac.starlink.table.ColumnInfo; +import uk.ac.starlink.table.DefaultValueInfo; +import uk.ac.starlink.table.DescribedValue; +import uk.ac.starlink.table.RowSequence; +import uk.ac.starlink.table.StarTable; +import uk.ac.starlink.votable.DataFormat; +import uk.ac.starlink.votable.VOSerializer; +import uk.ac.starlink.votable.VOTableVersion; import adql.db.DBColumn; -import cds.savot.writer.SavotWriter; /** * <p>Format any given query (table) result into VOTable.</p> + * * <p> - * Attributes of the VOTable node are by default set by this class but can be overridden if necessary thanks to the corresponding class attributes: - * {@link #votTableVersion}, {@link #xmlnsXsi}, {@link #xsiNoNamespaceSchemaLocation}, {@link #xsiSchemaLocation} and - * {@link #xmlns}. + * Format and version of the resulting VOTable can be provided in parameters at the construction time. + * This formatter is using STIL. So all formats and versions managed by STIL are also here. + * Basically, you have the following formats: TABLEDATA, BINARY, BINARY2 (only when using VOTable v1.3) and FITS. + * The versions are: 1.0, 1.1, 1.2 and 1.3. * </p> * + * <p>In addition of the INFO elements for QUERY_STATUS="OK" and QUERY_STATUS="OVERFLOW", two additional INFO elements are written:</p> + * <ul> + * <li>PROVIDER = {@link ServiceConnection#getProviderName()} and {@link ServiceConnection#getProviderDescription()}</li> + * <li>QUERY = the ADQL query at the origin of this result.</li> + * </ul> + * * @author Grégory Mantelet (CDS;ARI) * @version 2.0 (07/2014) */ @@ -56,11 +74,11 @@ public class VOTableFormat implements OutputFormat { /** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */ protected final ServiceConnection service; - protected String votTableVersion = "1.2"; - protected String xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance"; - protected String xsiSchemaLocation = "http://www.ivoa.net/xml/VOTable/v1.2"; - protected String xsiNoNamespaceSchemaLocation = null; - protected String xmlns = "http://www.ivoa.net/xml/VOTable/v1.2"; + /** Format of the VOTable data part in which data must be formatted. Possible values are: TABLEDATA, BINARY, BINARY2 or FITS. By default, it is set to BINARY. */ + protected final DataFormat votFormat; + + /** VOTable version in which table data must be formatted. By default, it is set to v13. */ + protected final VOTableVersion votVersion; /** * Creates a VOTable formatter without format report. @@ -68,13 +86,36 @@ public class VOTableFormat implements OutputFormat { * @param service The service to use (for the log and to have some information about the service (particularly: name, description). * * @throws NullPointerException If the given service connection is <code>null</code>. - * - * @see #VOTableFormat(ServiceConnection, boolean) */ public VOTableFormat(final ServiceConnection service) throws NullPointerException{ this(service, false); } + /** + * Creates a VOTable formatter. + * + * @param service The service to use (for the log and to have some information about the service (particularly: name, description). + * @param votFormat Format of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS). + * + * @throws NullPointerException If the given service connection is <code>null</code>. + */ + public VOTableFormat(final ServiceConnection service, final DataFormat votFormat) throws NullPointerException{ + this(service, votFormat, null, false); + } + + /** + * Creates a VOTable formatter. + * + * @param service The service to use (for the log and to have some information about the service (particularly: name, description). + * @param votFormat Format of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS). + * @param votVersion Version of the resulting VOTable. + * + * @throws NullPointerException If the given service connection is <code>null</code>. + */ + public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion) throws NullPointerException{ + this(service, votFormat, votVersion, false); + } + /** * Creates a VOTable formatter. * @@ -84,10 +125,27 @@ public class VOTableFormat implements OutputFormat { * @throws NullPointerException If the given service connection is <code>null</code>. */ public VOTableFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{ + this(service, null, null, logFormatReport); + } + + /** + * Creates a VOTable formatter. + * + * @param service The service to use (for the log and to have some information about the service (particularly: name, description). + * @param votFormat Format of the VOTable data part. (TABLEDATA, BINARY, BINARY2 or FITS). + * @param votVersion Version of the resulting VOTable. + * @param logFormatReport <code>true</code> to append a format report (start and end date/time) in the log output, <code>false</code> otherwise. + * + * @throws NullPointerException If the given service connection is <code>null</code>. + */ + public VOTableFormat(final ServiceConnection service, final DataFormat votFormat, final VOTableVersion votVersion, final boolean logFormatReport) throws NullPointerException{ if (service == null) throw new NullPointerException("The given service connection is NULL !"); + this.service = service; this.logFormatReport = logFormatReport; + this.votFormat = (votFormat == null) ? DataFormat.BINARY : votFormat; + this.votVersion = (votVersion == null) ? VOTableVersion.V13 : votVersion; } @Override @@ -110,87 +168,85 @@ public class VOTableFormat implements OutputFormat { return "xml"; } - /** - * <p>The skeleton of the resulting VOTable is written in this method:</p> - * <ul> - * <li><?xml version="1.0" encoding="UTF-8"></li> - * <li><i>{@link #writeHeader(PrintWriter, TAPJob)}</i></li> - * <li><TABLE></li> - * <li><DATA></li> - * <li><i>{@link #writeData(Object, DBColumn[], OutputStream, TAPJob)}</i></li> - * <li></DATA></li> - * <li><i>if (nbRows >= job.getMaxRec()) </i><INFO name="QUERY_STATUS" value="OVERFLOW" /></li> - * <li></RESOURCE></li> - * <li></VOTABLE></li> - * </ul> - * - * @see tap.formatter.OutputFormat#writeResult(Object, OutputStream, TAPExecutionReport) - */ @Override public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{ try{ long start = System.currentTimeMillis(); - PrintWriter out = new PrintWriter(output); - out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); - writeHeader(out, execReport); - out.println("\t\t<TABLE>"); - DBColumn[] columns = writeMetadata(queryResult, out, execReport, thread); - out.println("\t\t\t<DATA>"); + ColumnInfo[] colInfos = toColumnInfos(queryResult, execReport, thread); + + /* Turns the result set into a table. */ + LimitedStarTable table = new LimitedStarTable(queryResult, colInfos, execReport.parameters.getMaxRec()); + + /* Prepares the object that will do the serialization work. */ + VOSerializer voser = VOSerializer.makeSerializer(votFormat, votVersion, table); + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(output)); + + /* Write header. */ + writeHeader(votVersion, execReport, out); + + /* Write table element. */ + voser.writeInlineTableElement(out); out.flush(); - int nbRows = writeData(queryResult, columns, output, execReport, thread); - output.flush(); - out.println("\t\t\t</DATA>"); - out.println("\t\t</TABLE>"); - // OVERFLOW ? - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) - out.println("\t\t<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\" />"); - out.println("\t</RESOURCE>"); - out.println("</VOTABLE>"); + + /* Check for overflow and write INFO if required. */ + if (table.lastSequenceOverflowed()){ + out.write("<INFO name=\"QUERY_STATUS\" value=\"OVERFLOW\"/>"); + out.newLine(); + } + + /* Write footer. */ + out.write("</RESOURCE>"); + out.newLine(); + out.write("</VOTABLE>"); + out.newLine(); + out.flush(); if (logFormatReport) - service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in VOTable ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + " ms !"); + service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in VOTable ; " + table.getNbReadRows() + " rows ; " + table.getColumnCount() + " columns) in " + (System.currentTimeMillis() - start) + " ms !"); }catch(IOException ioe){ throw new TAPException("Error while writing a query result in VOTable !", ioe); } } /** - * <p>Writes the root node of the VOTable: <VOTABLE>.</p> - * <p> - * Attributes of this node are written thanks to their corresponding attributes in this class: - * {@link #votTableVersion}, {@link #xmlnsXsi}, {@link #xsiNoNamespaceSchemaLocation}, {@link #xsiSchemaLocation} and {@link #xmlns}. - * They are written only if different from <code>null</code>. - * </p> + * <p>Writes the first VOTable nodes/elements preceding the data: VOTABLE, RESOURCE and 3 INFOS (QUERY_STATUS, PROVIDER, QUERY).</p> * - * @param output Writer in which the root node must be written. + * @param votVersion Target VOTable version. * @param execReport The report of the query execution. + * @param out Writer in which the root node must be written. * * @throws IOException If there is an error while writing the root node in the given Writer. * @throws TAPException If there is any other error (by default: never happen). */ - protected void writeHeader(final PrintWriter output, final TAPExecutionReport execReport) throws IOException, TAPException{ - StringBuffer strBuf = new StringBuffer("<VOTABLE"); - if (votTableVersion != null) - strBuf.append(" version=\"").append(SavotWriter.encodeAttribute(votTableVersion)).append('\"'); - if (xmlnsXsi != null) - strBuf.append(" xmlns:xsi=\"").append(SavotWriter.encodeAttribute(xmlnsXsi)).append('\"'); - if (xsiSchemaLocation != null) - strBuf.append(" xsi:schemaLocation=\"").append(SavotWriter.encodeAttribute(xsiSchemaLocation)).append('\"'); - if (xsiNoNamespaceSchemaLocation != null) - strBuf.append(" xsi:noNamespaceSchemaLocation=\"").append(SavotWriter.encodeAttribute(xsiNoNamespaceSchemaLocation)).append('\"'); - if (xmlns != null) - strBuf.append(" xmlns=\"").append(SavotWriter.encodeAttribute(xmlns)).append('\"'); - strBuf.append('>'); - output.println(strBuf); - - output.println("\t<RESOURCE type=\"results\">"); - - // INFO items: - output.println("\t\t<INFO name=\"QUERY_STATUS\" value=\"OK\" />"); - output.println("\t\t<INFO name=\"PROVIDER\" value=\"" + ((service.getProviderName() == null) ? "" : SavotWriter.encodeAttribute(service.getProviderName())) + "\">" + ((service.getProviderDescription() == null) ? "" : SavotWriter.encodeElement(service.getProviderDescription())) + "</INFO>"); - output.println("\t\t<INFO name=\"QUERY\"><![CDATA[" + execReport.parameters.getQuery() + "]]></INFO>"); + protected void writeHeader(final VOTableVersion votVersion, final TAPExecutionReport execReport, final BufferedWriter out) throws IOException, TAPException{ + // Set the root VOTABLE node: + out.write("<VOTABLE" + VOSerializer.formatAttribute("version", votVersion.getVersionNumber()) + VOSerializer.formatAttribute("xmlns", votVersion.getXmlNamespace()) + ">"); + out.newLine(); + + // The RESOURCE note MUST have a type "results": [REQUIRED] + out.write("<RESOURCE type=\"results\">"); + out.newLine(); + + // Indicate that the query has been successfully processed: [REQUIRED] + out.write("<INFO name=\"QUERY_STATUS\" value=\"OK\"/>"); + out.newLine(); + + // Append the PROVIDER information (if any): [OPTIONAL] + if (service.getProviderName() != null){ + out.write("<INFO name=\"PROVIDER\"" + VOSerializer.formatAttribute("value", service.getProviderName()) + ">" + ((service.getProviderDescription() == null) ? "" : VOSerializer.formatText(service.getProviderDescription())) + "</INFO>"); + out.newLine(); + } + + // Append the ADQL query at the origin of this result: [OPTIONAL] + String adqlQuery = execReport.parameters.getQuery(); + if (adqlQuery != null){ + out.write("<INFO name=\"QUERY\">" + VOSerializer.formatText(adqlQuery) + "</INFO>"); + out.newLine(); + } + + out.flush(); } /** @@ -202,13 +258,13 @@ public class VOTableFormat implements OutputFormat { * @param execReport The report of the query execution. * @param thread The thread which asked for the result writing. * - * @return Extracted field's metadata. + * @return Extracted field's metadata, or NULL if no metadata have been found (theoretically, it never happens). * * @throws IOException If there is an error while writing the metadata in the given Writer. * @throws TAPException If there is any other error. * @throws InterruptedException If the given thread has been interrupted. */ - protected DBColumn[] writeMetadata(final TableIterator result, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ + protected ColumnInfo[] toColumnInfos(final TableIterator result, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ // Get the metadata extracted/guesses from the ADQL query: DBColumn[] columnsFromQuery = execReport.resultingColumns; @@ -218,6 +274,9 @@ public class VOTableFormat implements OutputFormat { int indField = 0; if (columnsFromQuery != null){ + // Initialize the resulting array: + ColumnInfo[] colInfos = new ColumnInfo[columnsFromQuery.length]; + // For each column: for(DBColumn field : columnsFromQuery){ @@ -225,20 +284,18 @@ public class VOTableFormat implements OutputFormat { TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null; TAPColumn tapCol = getValidColMeta(field, colFromResult); - // Ensure these metadata are well returned at the end of this function: - columnsFromQuery[indField] = tapCol; + // Build the corresponding ColumnInfo object: + colInfos[indField] = getColumnInfo(tapCol); - // Write the field/column metadata in the JSON output: - writeFieldMeta(tapCol, output); indField++; if (thread.isInterrupted()) throw new InterruptedException(); } - }else - output.println("<INFO name=\"WARNING\" value=\"MISSING_META\">Error while getting field(s) metadata</INFO>"); - return columnsFromQuery; + return colInfos; + }else + return null; } /** @@ -249,7 +306,7 @@ public class VOTableFormat implements OutputFormat { * * @return The most appropriate metadata. */ - protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ + protected final TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){ if (typeFromQuery != null && typeFromQuery instanceof TAPColumn) return (TAPColumn)typeFromQuery; else if (typeFromResult != null){ @@ -262,173 +319,238 @@ public class VOTableFormat implements OutputFormat { } /** - * <p>Formats in a VOTable field and writes the given {@link TAPColumn} in the given Writer.</p> + * Convert the given {@link TAPColumn} object into a {@link ColumnInfo} object. * - * <p><i><u>Note:</u> If the VOTable datatype is <code>int</code>, <code>short</code> or <code>long</code> a NULL values is set by adding a node VALUES: <VALUES null="..." /></i></p> + * @param tapCol {@link TAPColumn} to convert into {@link ColumnInfo}. * - * @param col The column metadata to format into a VOTable field. - * @param out The stream in which the formatted column metadata must be written. - * - * @throws IOException If there is an error while writing the field metadata. - * @throws TAPException If there is any other error (by default: never happen). + * @return The corresponding {@link ColumnInfo}. */ - protected void writeFieldMeta(TAPColumn col, PrintWriter out) throws IOException, TAPException{ - StringBuffer fieldline = new StringBuffer("\t\t\t"); - - // <FIELD ID="..." - fieldline.append("<FIELD ID=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"'); - - // name="..." - fieldline.append(" name=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"'); - - // datatype="..." - VotType type = col.getVotType(); - String nullVal = getNullValue(type.datatype); - fieldline.append(' ').append(type.toString()); - - // ucd="..." (if any) - if (col.getUcd() != null && col.getUcd().length() > 0) - fieldline.append(" ucd=").append('"').append(SavotWriter.encodeAttribute(col.getUcd())).append('"'); - - // utype="..." (if any) - if (col.getUtype() != null && col.getUtype().length() > 0) - fieldline.append(" utype=").append('"').append(SavotWriter.encodeAttribute(col.getUtype())).append('"'); - - // unit="..." (if any) - if (col.getUnit() != null && col.getUnit().length() > 0) - fieldline.append(" unit=").append('"').append(SavotWriter.encodeAttribute(col.getUnit())).append('"'); - - // Get the description (or NULL if none is provided): - String description = null; - if (col.getDescription() != null && !col.getDescription().trim().isEmpty()) - description = col.getDescription().trim(); - else - description = null; - - // Declares NULL values and write the description: - if (nullVal != null || description != null){ - fieldline.append(">\n"); - - // <VALUES null="..." /> (if needed) - if (nullVal != null) - fieldline.append("<VALUES null=\"" + nullVal + "\" />\n"); - - // <DESCRIPTION>...</DESCRIPTION> (if any) - if (description != null) - fieldline.append("<DESCRIPTION>").append(SavotWriter.encodeElement(description)).append("</DESCRIPTION>\n"); - - // </FIELD> - fieldline.append("</FIELD>"); - out.println(fieldline); - }else{ - fieldline.append("/>"); - out.println(fieldline); - } + protected ColumnInfo getColumnInfo(final TAPColumn tapCol){ + // Get the VOTable type: + VotType votType = tapCol.getDatatype().toVotType(); + + // Build a ColumnInfo with the name, type and description: + ColumnInfo colInfo = new ColumnInfo(tapCol.getADQLName(), getDatatypeClass(votType.datatype, votType.arraysize), tapCol.getDescription()); + + // Set the shape (VOTable arraysize): + colInfo.setShape(getShape(votType.arraysize)); + + // Set this value may be NULL (note: it is not really necessary since STIL set this flag to TRUE by default): + colInfo.setNullable(true); + + // Set the XType (if any): + if (votType.xtype != null) + colInfo.setAuxDatum(new DescribedValue(new DefaultValueInfo("xtype", String.class, "VOTable xtype attribute"), votType.xtype)); + + // Set the additional information: unit, UCD and UType: + colInfo.setUnitString(tapCol.getUnit()); + colInfo.setUCD(tapCol.getUcd()); + colInfo.setUtype(tapCol.getUtype()); + + return colInfo; } /** - * Write the whole data part (TABLEDATA) of the VOTable file. - * - * @param result The query result which contains the data to write. - * @param selectedColumns The columns selected by the query. - * @param output The stream in which the data must be written. - * @param execReport The report of the query execution (which contains the maximum allowed number of records to output). - * @param thread The thread which asked for the result writing. + * Convert the VOTable datatype string into a corresponding {@link Class} object. * - * @return The number of written rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>) + * @param datatype Value of the VOTable attribute "datatype". + * @param arraysize Value of the VOTable attribute "arraysize". * - * @throws IOException If there is an error while writing the data in the given stream. - * @throws InterruptedException If the given thread has been interrupted. - * @throws TAPException If there is any other error. + * @return The corresponding {@link Class} object. */ - protected int writeData(final TableIterator result, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{ - // <TABLEDATA> - output.write("\t\t\t\t<TABLEDATA>\n".getBytes()); - - int nbRows = 0; - while(result.nextRow()){ - // Deal with OVERFLOW, if needed: - if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) - break; - - // <TR> - output.write("\t\t\t\t\t<TR>\n".getBytes()); - int indCol = 0; - while(result.hasNextCol()){ - // <TD> - output.write("\t\t\t\t\t\t<TD>".getBytes()); - // ... - writeFieldValue(result.nextCol(), selectedColumns[indCol++], output); - // </TD> - output.write("</TD>\n".getBytes()); - - if (thread.isInterrupted()) - throw new InterruptedException(); - } + protected static final Class<?> getDatatypeClass(final VotDatatype datatype, final String arraysize){ + // Determine whether this type is an array or not: + boolean isScalar = arraysize == null || (arraysize.length() == 1 && arraysize.equals("1")); - // </TR> - output.write("\t\t\t\t\t</TR>\n".getBytes()); - nbRows++; - - if (thread.isInterrupted()) - throw new InterruptedException(); + // Guess the corresponding Class object (see section "7.1.4 Data Types" of the STIL documentation): + switch(datatype){ + case BIT: + return boolean[].class; + case BOOLEAN: + return isScalar ? Boolean.class : boolean[].class; + case DOUBLE: + return isScalar ? Double.class : double[].class; + case DOUBLE_COMPLEX: + return double[].class; + case FLOAT: + return isScalar ? Float.class : float[].class; + case FLOAT_COMPLEX: + return float[].class; + case INT: + return isScalar ? Integer.class : int[].class; + case LONG: + return isScalar ? Long.class : long[].class; + case SHORT: + return isScalar ? Short.class : short[].class; + case UNSIGNED_BYTE: + return isScalar ? Short.class : short[].class; + case CHAR: + case UNICODE_CHAR: + default: /* If the type is not know (theoretically, never happens), return char[*] by default. */ + return isScalar ? Character.class : String.class; } - - // </TABLEDATA> - output.write("\t\t\t\t</TABLEDATA>\n".getBytes()); - return nbRows; } /** - * <p>Writes the given field value in the given OutputStream.</p> + * Convert the given VOTable arraysize into a {@link ColumnInfo} shape. * - * <p> - * The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}. - * Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>, - * <code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written. - * </p> - * - * @param value The value to write. - * @param column The corresponding column metadata. - * @param output The stream in which the field value must be written. + * @param arraysize Value of the VOTable attribute "arraysize". * - * @throws IOException If there is an error while writing the given field value in the given stream. - * @throws TAPException If there is any other error (by default: never happen). + * @return The corresponding {@link ColumnInfo} shape. */ - protected void writeFieldValue(final Object value, final DBColumn column, final OutputStream output) throws IOException, TAPException{ - String fieldValue = (value == null) ? null : value.toString(); - if (fieldValue == null && column instanceof TAPColumn) - fieldValue = getNullValue(((TAPColumn)column).getVotType().datatype); - if (fieldValue != null) - output.write(SavotWriter.encodeElement(fieldValue).getBytes()); + protected static final int[] getShape(final String arraysize){ + /* + * Note: multi-dimensional arrays are forbidden in the TAP library, + * so no 'nxm...' is possible. + */ + + // No arraysize => empty array: + if (arraysize == null) + return new int[0]; + + // '*' or 'n*' => {-1}: + else if (arraysize.charAt(arraysize.length() - 1) == '*') + return new int[]{-1}; + + // 'n' => {n}: + else{ + try{ + return new int[]{Integer.parseInt(arraysize)}; + }catch(NumberFormatException nfe){ + // if the given arraysize is incorrect (theoretically, never happens), it is like no arraysize has been provided: + return new int[0]; + } + } } /** - * <p>Gets the NULL value corresponding to the given datatype:</p> - * <ul> - * <li>for <code>int</code>: {@link Integer#MIN_VALUE}</li> - * <li>for <code>short</code>: {@link Short#MIN_VALUE}</li> - * <li>for <code>long</code>: {@link Long#MIN_VALUE}</li> - * <li>for anything else, <code>null</code> will be returned.</li> - * </ul> - * - * @param datatype A VOTable datatype. + * <p> + * Special {@link StarTable} able to read a fixed maximum number of rows {@link TableIterator}. + * However, if no limit is provided, all rows are read. + * </p> * - * @return The corresponding NULL value, or <code>null</code> if there is none. + * @author Grégory Mantelet (CDS;ARI) + * @version 2.0 (07/2014) + * @since 2.0 */ - public static final String getNullValue(VotDatatype datatype){ - if (datatype == null) - return null; + private static class LimitedStarTable extends AbstractStarTable { + + /** Number of columns to read. */ + private final int nbCol; + + /** Information about all columns to read. */ + private final ColumnInfo[] columnInfos; + + /** Iterator over the data to read using this special {@link StarTable} */ + private final TableIterator tableIt; + + /** Limit on the number of rows to read. Over this limit, an "overflow" event occurs and {@link #overflow} is set to TRUE. */ + private final long maxrec; + + /** Indicates whether the maximum allowed number of rows has already been read or not. When true, no more row can be read. */ + private boolean overflow; + + /** Last read row. If NULL, no row has been read or no more row is available. */ + private Object[] row = null; + + /** Number of rows read until now. */ + private int nbRows; + + /** + * Build this special {@link StarTable}. + * + * @param tableIt Data on which to iterate using this special {@link StarTable}. + * @param colInfos Information about all columns. + * @param maxrec Limit on the number of rows to read. <i>(if negative, there will be no limit)</i> + */ + LimitedStarTable(final TableIterator tableIt, final ColumnInfo[] colInfos, final long maxrec){ + this.tableIt = tableIt; + nbCol = colInfos.length; + columnInfos = colInfos; + this.maxrec = maxrec; + overflow = false; + } - switch(datatype){ - case SHORT: - return "" + Short.MIN_VALUE; - case INT: - return "" + Integer.MIN_VALUE; - case LONG: - return "" + Long.MIN_VALUE; - default: - return null; + /** + * Indicates whether the last row sequence dispensed by + * this table's getRowSequence method was truncated at maxrec rows. + * + * @return true if the last row sequence overflowed + */ + public boolean lastSequenceOverflowed(){ + return overflow; + } + + /** + * Get the number of rows that have been successfully read until now. + * + * @return Number of all read rows. + */ + public int getNbReadRows(){ + return nbRows; + } + + @Override + public int getColumnCount(){ + return nbCol; + } + + @Override + public ColumnInfo getColumnInfo(final int colInd){ + return columnInfos[colInd]; + } + + @Override + public long getRowCount(){ + return -1; + } + + @Override + public RowSequence getRowSequence() throws IOException{ + overflow = false; + row = new Object[nbCol]; + + return new RowSequence(){ + long irow = -1; + + @Override + public boolean next() throws IOException{ + irow++; + try{ + if (maxrec <= 0 || irow < maxrec){ + boolean hasNext = tableIt.nextRow(); + if (hasNext){ + for(int i = 0; i < nbCol && tableIt.hasNextCol(); i++) + row[i] = tableIt.nextCol(); + }else + row = null; + return hasNext; + }else{ + overflow = tableIt.nextRow(); + row = null; + return false; + } + }catch(DataReadException dre){ + if (dre.getCause() != null && dre.getCause() instanceof IOException) + throw (IOException)(dre.getCause()); + else + throw new IOException(dre); + } + } + + @Override + public Object[] getRow() throws IOException{ + return row; + } + + @Override + public Object getCell(int cellIndex) throws IOException{ + return row[cellIndex]; + } + + @Override + public void close() throws IOException{} + }; } } } diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java index 2fca36dc6a8001457cf77d0766a95c18ce1843a4..9b91e8593410fb1337ef28dde9a44f7e6e984a2a 100644 --- a/src/tap/metadata/TAPColumn.java +++ b/src/tap/metadata/TAPColumn.java @@ -44,8 +44,6 @@ public class TAPColumn implements DBColumn { private TAPType datatype = new TAPType(TAPDatatype.VARCHAR); - private VotType votType = datatype.toVotType(); - private boolean principal = false; private boolean indexed = false; @@ -194,14 +192,6 @@ public class TAPColumn implements DBColumn { */ public final void setDatatype(final TAPType type){ datatype = type; - votType = datatype.toVotType(); - } - - /** - * @return The votType. - */ - public final VotType getVotType(){ - return votType; } /** diff --git a/src/tap/metadata/TAPType.java b/src/tap/metadata/TAPType.java index a36c1c3dee169a161810b4d1e34bd1e1866b2443..90643543d9eee044730bb105c6b3984c1f129a33 100644 --- a/src/tap/metadata/TAPType.java +++ b/src/tap/metadata/TAPType.java @@ -19,8 +19,6 @@ package tap.metadata; * Copyright 2014 - Astronomishes Rechen Institute (ARI) */ -import tap.metadata.VotType.VotDatatype; - /** * * <p> @@ -86,10 +84,10 @@ public class TAPType { * * @return The corresponding VOTable field type. * - * @see #convertIntoVotType(TAPType) + * @see VotType#VotType(TAPType) */ public VotType toVotType(){ - return convertIntoVotType(this); + return new VotType(this); } @Override @@ -100,158 +98,4 @@ public class TAPType { return type.toString(); } - /** - * Convert the given TAP column type into a VOTable field type. - * - * @param taptype The TAP column type to convert. - * - * @return The corresponding VOTable field type. - */ - public static VotType convertIntoVotType(final TAPType taptype){ - VotType vot = new VotType(VotDatatype.CHAR, VotType.NO_SIZE, false); - - switch(taptype.type){ - case SMALLINT: - vot = new VotType(VotDatatype.SHORT, 1, false); - break; - - case INTEGER: - vot = new VotType(VotDatatype.INT, 1, false); - break; - - case BIGINT: - vot = new VotType(VotDatatype.LONG, 1, false); - break; - - case REAL: - vot = new VotType(VotDatatype.FLOAT, 1, false); - break; - - case DOUBLE: - vot = new VotType(VotDatatype.DOUBLE, 1, false); - break; - - case CHAR: - vot = new VotType(VotDatatype.CHAR, (taptype.length > 0 ? taptype.length : 1), false); - break; - - case BINARY: - vot = new VotType(VotDatatype.UNSIGNED_BYTE, (taptype.length > 0 ? taptype.length : VotType.NO_SIZE), false); - break; - - case VARBINARY: - vot = new VotType(VotDatatype.UNSIGNED_BYTE, (taptype.length > 0 ? taptype.length : VotType.NO_SIZE), (taptype.length > 0)); - break; - - case BLOB: - vot = new VotType(VotDatatype.UNSIGNED_BYTE, VotType.NO_SIZE, true, VotType.XTYPE_BLOB); - break; - - case CLOB: - vot = new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true, VotType.XTYPE_CLOB); - break; - - case TIMESTAMP: - vot = new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true, VotType.XTYPE_TIMESTAMP); - break; - - case POINT: - vot = new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true, VotType.XTYPE_POINT); - break; - - case REGION: - vot = new VotType(VotDatatype.CHAR, VotType.NO_SIZE, true, VotType.XTYPE_REGION); - break; - - case VARCHAR: - default: - vot = new VotType(VotDatatype.CHAR, (taptype.length > 0 ? taptype.length : VotType.NO_SIZE), (taptype.length > 0), null); - break; - } - - return vot; - } - - /** - * Convert the given VOTable field type into a TAP column type. - * - * @param vottype The VOTable field type to convert. - * - * @return The corresponding TAP column type. - */ - public static TAPType convertFromVotType(final VotType vottype){ - if (vottype == null) - return new TAPType(TAPDatatype.VARCHAR); - - switch(vottype.datatype){ - case SHORT: - case BOOLEAN: - if ((vottype.arraysize <= 1 || vottype.arraysize == VotType.NO_SIZE) && !vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.SMALLINT); - else - return new TAPType(TAPDatatype.VARBINARY); - - case INT: - if ((vottype.arraysize <= 1 || vottype.arraysize == VotType.NO_SIZE) && !vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.INTEGER); - else - return new TAPType(TAPDatatype.VARBINARY); - - case LONG: - if ((vottype.arraysize <= 1 || vottype.arraysize == VotType.NO_SIZE) && !vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.BIGINT); - else - return new TAPType(TAPDatatype.VARBINARY); - - case FLOAT: - if ((vottype.arraysize <= 1 || vottype.arraysize == VotType.NO_SIZE) && !vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.REAL); - else - return new TAPType(TAPDatatype.VARBINARY); - - case DOUBLE: - if ((vottype.arraysize <= 1 || vottype.arraysize == VotType.NO_SIZE) && !vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.DOUBLE); - else - return new TAPType(TAPDatatype.VARBINARY); - - case UNSIGNED_BYTE: - if (vottype.arraysize > 0){ - if (vottype.unlimitedArraysize) - return new TAPType(TAPDatatype.VARBINARY, vottype.arraysize); - else - return new TAPType(TAPDatatype.BINARY, vottype.arraysize); - }else - return new TAPType(TAPDatatype.VARBINARY); - - case CHAR: - default: - TAPType taptype = null; - if (vottype.xtype != null && vottype.xtype.trim().length() > 0){ - if (vottype.xtype.equalsIgnoreCase(VotType.XTYPE_BLOB)) - taptype = new TAPType(TAPDatatype.BLOB); - else if (vottype.xtype.equalsIgnoreCase(VotType.XTYPE_CLOB)) - taptype = new TAPType(TAPDatatype.CLOB); - else if (vottype.xtype.equalsIgnoreCase(VotType.XTYPE_TIMESTAMP)) - taptype = new TAPType(TAPDatatype.TIMESTAMP); - else if (vottype.xtype.equalsIgnoreCase(VotType.XTYPE_POINT)) - taptype = new TAPType(TAPDatatype.POINT); - else if (vottype.xtype.equalsIgnoreCase(VotType.XTYPE_REGION)) - taptype = new TAPType(TAPDatatype.REGION); - } - if (taptype == null){ - if (vottype.unlimitedArraysize) - taptype = new TAPType(TAPDatatype.VARCHAR, (vottype.arraysize > 0) ? vottype.arraysize : NO_LENGTH); - else{ - if (vottype.arraysize <= 0 || vottype.arraysize == VotType.NO_SIZE) - taptype = new TAPType(TAPDatatype.VARCHAR); - else if (vottype.arraysize == 1) - taptype = new TAPType(TAPDatatype.CHAR, 1); - else - taptype = new TAPType(TAPDatatype.CHAR, vottype.arraysize); - } - } - return taptype; - } - } } diff --git a/src/tap/metadata/VotType.java b/src/tap/metadata/VotType.java index 000f5f81add035c5f1f6a8acce2c22c719726a30..16332eb9be6938434cbfc8a7012f23271af93eee 100644 --- a/src/tap/metadata/VotType.java +++ b/src/tap/metadata/VotType.java @@ -20,7 +20,9 @@ package tap.metadata; * Astronomishes Rechen Institute (ARI) */ -import cds.savot.writer.SavotWriter; +import tap.TAPException; +import tap.metadata.TAPType.TAPDatatype; +import uk.ac.starlink.votable.VOSerializer; /** * <p>Describes a full VOTable type. Thus it includes the following field attributes:</p> @@ -72,18 +74,11 @@ public final class VotType { * @since 2.0 */ public final static String XTYPE_REGION = "adql:REGION"; - /** No array size. - * @since 2.0 */ - public static final int NO_SIZE = -1; - /** VOTable datatype * @since 2.0 */ public final VotDatatype datatype; - /** A negative or null value means "*" (that's to say: an undetermined arraysize). */ - public final int arraysize; - /** If true, it means either "n*" (where n is the arraysize when > 0) or "*". - * @since 2.0*/ - public final boolean unlimitedArraysize; + /** Arraysize string of a VOTable field element. */ + public final String arraysize; /** Special type specification (i.e. POINT, TIMESTAMP, ...). */ public final String xtype; @@ -91,28 +86,134 @@ public final class VotType { * Build a VOTable field type. * * @param datatype A datatype. <b>Null value forbidden</b> - * @param arraysize A non-null positive integer. (any value ≤ 0 will be considered as an undetermined arraysize, that's to say {@link #NO_SIZE}). - * @param unlimitedSize Indicate whether a * must be appended at the end of the arraysize attribute (so in these 2 cases: "n*" or "*"). + * @param arraysize VOTable arraysize string (<i>may be NULL</i>). */ - public VotType(final VotDatatype datatype, final int arraysize, final boolean unlimitedSize){ - this(datatype, arraysize, unlimitedSize, null); + public VotType(final VotDatatype datatype, final String arraysize){ + this(datatype, arraysize, null); } /** * Build a VOTable field type. * * @param datatype A datatype. <b>Null value forbidden</b> - * @param arraysize A non-null positive integer. (any value ≤ 0 will be considered as an undetermined arraysize, that's to say {@link #NO_SIZE}). - * @param unlimitedSize Indicate whether a * must be appended at the end of the arraysize attribute (so in these 2 cases: "n*" or "*"). - * @param xtype A special type (ex: adql:POINT, adql:TIMESTAMP, ...). Null value allowed. + * @param arraysize VOTable arraysize string (<i>may be NULL</i>). + * @param xtype A special type (ex: adql:POINT, adql:TIMESTAMP, ...). (<i>may be NULL</i>). */ - public VotType(final VotDatatype datatype, final int arraysize, final boolean unlimitedSize, final String xtype){ + public VotType(final VotDatatype datatype, final String arraysize, final String xtype){ + // set the datatype: if (datatype == null) - throw new NullPointerException("Missing VOTable datatype !"); - this.datatype = datatype; - this.arraysize = (arraysize > 0) ? arraysize : NO_SIZE; - this.unlimitedArraysize = unlimitedSize; - this.xtype = xtype; + throw new NullPointerException("missing VOTable datatype !"); + else + this.datatype = datatype; + + // set the array-size: + if (arraysize != null && arraysize.trim().length() > 0) + this.arraysize = arraysize.trim(); + else + this.arraysize = null; + + // set the xtype: + if (xtype != null && xtype.trim().length() > 0) + this.xtype = xtype.trim(); + else + this.xtype = null; + } + + /** + * Build a {@link VotType} object by converting the given {@link TAPType}. + * + * @param tapType {@link TAPType} to convert. + */ + public VotType(final TAPType tapType){ + switch(tapType.type){ + case SMALLINT: + this.datatype = VotDatatype.SHORT; + this.arraysize = "1"; + this.xtype = null; + break; + + case INTEGER: + this.datatype = VotDatatype.INT; + this.arraysize = "1"; + this.xtype = null; + break; + + case BIGINT: + this.datatype = VotDatatype.LONG; + this.arraysize = "1"; + this.xtype = null; + break; + + case REAL: + this.datatype = VotDatatype.FLOAT; + this.arraysize = "1"; + this.xtype = null; + break; + + case DOUBLE: + this.datatype = VotDatatype.DOUBLE; + this.arraysize = "1"; + this.xtype = null; + break; + + case CHAR: + this.datatype = VotDatatype.CHAR; + this.arraysize = Integer.toString(tapType.length > 0 ? tapType.length : 1); + this.xtype = null; + break; + + case BINARY: + this.datatype = VotDatatype.UNSIGNED_BYTE; + this.arraysize = Integer.toString(tapType.length > 0 ? tapType.length : 1); + this.xtype = null; + break; + + case VARBINARY: + /* TODO HOW TO MANAGE VALUES WHICH WHERE ORIGINALLY NUMERIC ARRAYS ? + * (cf the IVOA document TAP#Upload: votable numeric arrays should be converted into VARBINARY...no more array information and particularly the datatype) + */ + this.datatype = VotDatatype.UNSIGNED_BYTE; + this.arraysize = (tapType.length > 0 ? tapType.length + "*" : "*"); + this.xtype = null; + break; + + case BLOB: + this.datatype = VotDatatype.UNSIGNED_BYTE; + this.arraysize = "*"; + this.xtype = VotType.XTYPE_BLOB; + break; + + case CLOB: + this.datatype = VotDatatype.CHAR; + this.arraysize = "*"; + this.xtype = VotType.XTYPE_CLOB; + break; + + case TIMESTAMP: + this.datatype = VotDatatype.CHAR; + this.arraysize = "*"; + this.xtype = VotType.XTYPE_TIMESTAMP; + break; + + case POINT: + this.datatype = VotDatatype.CHAR; + this.arraysize = "*"; + this.xtype = VotType.XTYPE_POINT; + break; + + case REGION: + this.datatype = VotDatatype.CHAR; + this.arraysize = "*"; + this.xtype = VotType.XTYPE_REGION; + break; + + case VARCHAR: + default: + this.datatype = VotDatatype.CHAR; + this.arraysize = (tapType.length > 0 ? tapType.length + "*" : "*"); + this.xtype = null; + break; + } } @Override @@ -134,19 +235,14 @@ public final class VotType { @Override public String toString(){ - StringBuffer str = new StringBuffer("datatype=\""); - str.append(datatype).append('"'); + StringBuffer str = new StringBuffer(VOSerializer.formatAttribute("datatype", datatype.toString())); + str.deleteCharAt(0); - if (arraysize > 0){ - str.append(" arraysize=\"").append(SavotWriter.encodeAttribute("" + arraysize)); - if (unlimitedArraysize) - str.append("*"); - str.append('"'); - }else if (unlimitedArraysize) - str.append(" arraysize=\"*\""); + if (arraysize != null) + str.append(VOSerializer.formatAttribute("arraysize", arraysize)); if (xtype != null) - str.append(" xtype=\"").append(SavotWriter.encodeAttribute(xtype)).append('"'); + str.append(VOSerializer.formatAttribute("xtype", xtype)); return str.toString(); } @@ -155,9 +251,126 @@ public final class VotType { * Convert this VOTable type definition into a TAPColumn type. * * @return The corresponding {@link TAPType}. + * + * @throws TAPException If the conversion is impossible (particularly if the array-size refers to a multi-dimensional array ; only 1D arrays are allowed). + */ + public TAPType toTAPType() throws TAPException{ + + /* Stop immediately if the arraysize refers to a multi-dimensional array: + * (Note: 'x' is the dimension separator of the VOTable attribute 'arraysize') */ + if (arraysize != null && arraysize.indexOf('x') >= 0) + throw new TAPException("failed conversion of a VOTable datatype: multi-dimensional arrays (" + datatype + "[" + arraysize + "]) are not allowed!"); + + // Convert the VOTable datatype into TAP datatype: + switch(datatype){ + /* NUMERIC TYPES */ + case SHORT: + case BOOLEAN: + return convertNumericType(TAPDatatype.SMALLINT); + + case INT: + return convertNumericType(TAPDatatype.INTEGER); + + case LONG: + return convertNumericType(TAPDatatype.BIGINT); + + case FLOAT: + return convertNumericType(TAPDatatype.REAL); + + case DOUBLE: + return convertNumericType(TAPDatatype.DOUBLE); + + /* BINARY TYPES */ + case UNSIGNED_BYTE: + // BLOB exception: + if (xtype != null && xtype.equalsIgnoreCase(XTYPE_BLOB)) + return new TAPType(TAPDatatype.BLOB); + + // Or else, just (var)binary: + else + return convertVariableLengthType(TAPDatatype.VARBINARY, TAPDatatype.BINARY); + + /* CHARACTER TYPES */ + case CHAR: + default: + /* Special type cases: */ + if (xtype != null){ + if (xtype.equalsIgnoreCase(VotType.XTYPE_CLOB)) + return new TAPType(TAPDatatype.CLOB); + else if (xtype.equalsIgnoreCase(VotType.XTYPE_TIMESTAMP)) + return new TAPType(TAPDatatype.TIMESTAMP); + else if (xtype.equalsIgnoreCase(VotType.XTYPE_POINT)) + return new TAPType(TAPDatatype.POINT); + else if (xtype.equalsIgnoreCase(VotType.XTYPE_REGION)) + return new TAPType(TAPDatatype.REGION); + } + + // Or if not known or missing, just a (var)char: + return convertVariableLengthType(TAPDatatype.VARCHAR, TAPDatatype.CHAR); + } + } + + /** + * <p>Convert this numeric {@link VotType} object into a corresponding {@link TAPType} whose the datatype is provided in parameter.</p> + * + * <p> + * Thus, just the arraysize must be managed here. If there is no arraysize or if equals to '1', the given datatype will be used. + * Otherwise, it is ignored and a {@link TAPType} with VARBINARY is returned. + * </p> + * + * @param tapDatatype TAP datatype corresponding to this {@link VotType} (only when arraysize != '*' and 'n'). + * + * @return The corresponding {@link TAPType}. + */ + protected TAPType convertNumericType(final TAPDatatype tapDatatype){ + // If no arraysize: + if (arraysize == null || arraysize.equals("1")) + return new TAPType(tapDatatype); + + // If only one dimension: + else + return new TAPType(TAPDatatype.VARBINARY); + + /* Note: The test of multi-dimensional array should have been already done at the beginning of #toTAPType(). */ + } + + /** + * <p> + * Convert this variable length {@link VotType} (unsignedByte and char) object into a corresponding {@link TAPType} + * whose the variable length and fixed length versions are given in parameters. + * </p> + * + * <p>Thus, just the arraysize must be managed here. The following cases are taken into account:</p> + * <ul> + * <li><i>No arraysize or '*'</i>: variable length type (i.e. VARCHAR, VARBINARY),</li> + * <li><i>'n*'</i>: variable length type with the maximal length (i.e. VARCHAR(n), VARBINARY(n)),</li> + * <li><i>'n'</i>: fixed length type with the exact length (i.e. CHAR(n), BINARY(n)).</li> + * </ul> + * + * @param varType Variable length type (i.e. VARCHAR, VARBINARY). + * @param fixedType Fixed length type (i.e. CHAR, BINARY). + * + * @return The corresponding {@link TAPType}. + * + * @throws TAPException If the arraysize is not valid (that's to say, different from the following syntaxes: NULL, '*', 'n' or 'n*' (where n is a positive and not-null integer)). */ - public TAPType toTAPType(){ - return TAPType.convertFromVotType(this); + protected TAPType convertVariableLengthType(final TAPDatatype varType, final TAPDatatype fixedType) throws TAPException{ + try{ + // no arraysize or '*' => VARCHAR or VARBINARY + if (arraysize == null || arraysize.equals("*")) + return new TAPType(varType); + + // 'n*' => VARCHAR(n) or VARBINARY(n) + else if (arraysize.charAt(arraysize.length() - 1) == '*') + return new TAPType(varType, Integer.parseInt(arraysize.substring(0, arraysize.length() - 1))); + + // 'n' => CHAR(n) or BINARY(n) + else + return new TAPType(fixedType, Integer.parseInt(arraysize)); + + }catch(NumberFormatException nfe){ + throw new TAPException("failed conversion of a VOTable datatype: non-numeric arraysize (" + arraysize + ")!"); + } } } diff --git a/test/tap/formatter/VOTableFormatTest.java b/test/tap/formatter/VOTableFormatTest.java index f5f0d174554b2c8cfbad9fa07f4a55b805172663..4cbb8416c078b257d48afa3bdcee25056bc7d9d4 100644 --- a/test/tap/formatter/VOTableFormatTest.java +++ b/test/tap/formatter/VOTableFormatTest.java @@ -1,6 +1,6 @@ package tap.formatter; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.BufferedOutputStream; @@ -34,6 +34,7 @@ import tap.metadata.TAPType; import tap.metadata.TAPType.TAPDatatype; import tap.parameters.TAPParameters; import testtools.DBTools; +import uk.ac.starlink.votable.DataFormat; import uws.service.UserIdentifier; /** @@ -85,17 +86,17 @@ public class VOTableFormatTest { TableIterator it = new ResultSetTableIterator(rs); - VOTableFormat formatter = new VOTableFormat(serviceConn); + VOTableFormat formatter = new VOTableFormat(serviceConn, DataFormat.FITS); OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile)); formatter.writeResult(it, output, report, Thread.currentThread()); output.close(); // note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l": String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; - assertTrue(executeCommand(cmd).trim().equals("10")); + assertEquals("10", executeCommand(cmd).trim()); - cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; - assertTrue(executeCommand(cmd).trim().equals("0")); + cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertEquals("0", executeCommand(cmd).trim()); }catch(Exception t){ t.printStackTrace(); @@ -117,17 +118,17 @@ public class VOTableFormatTest { TableIterator it = new ResultSetTableIterator(rs); - VOTableFormat formatter = new VOTableFormat(serviceConn); + VOTableFormat formatter = new VOTableFormat(serviceConn, DataFormat.FITS); OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile)); formatter.writeResult(it, output, report, Thread.currentThread()); output.close(); // note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l": String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; - assertTrue(executeCommand(cmd).trim().equals("5")); + assertEquals("5", executeCommand(cmd).trim()); - cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; - assertTrue(executeCommand(cmd).trim().equals("1")); + cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\"/>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"}; + assertEquals("1", executeCommand(cmd).trim()); }catch(Exception t){ t.printStackTrace();