TableWrapper.java
/*
** Module : TableWrapper.java
** Abstract : A wrapper for table result sets, used to serialize a table data and send it to a remote side.
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130620 Created initial version.
** 002 SVL 20140611 Fixed NPE in writeExternal when no result set is transferred. Added tableName.
** 003 SVL 20140797 Added tableHandle field.
** 004 IAS 20160919 Serialization optimization.
** 005 OM 20190716 Updated Javadoc.
** CA 20190731 Added more table-related state (xmlns, xmlprefix, indexes).
** CA 20190812 Added toString().
** 006 CA 20191119 Added support for before-table.
** 007 CA 20200427 Allow a TABLE to be read from JSON argument, in case of a remote REST call.
** BLOB fields must be serialized via MemoryBuffer.
** CA 20200514 In some cases, BLOB fields are already MemoryBuffer.
** 008 ECF 20200906 New ORM implementation.
** 009 IAS 20200908 Rework (de)serialization.
** OM 20201120 Added implementation of XML-NODE-NAME and ERROR-STRING attributes
** OM 20210309 Do not use DmoMeta as key in TableMapper because temp-tables might share the same
** DmoMeta instance.
** SVL 20210517 Added isValid().
** SVL 20210625 Set table name to the result set structure name. That will allow to create a dynamic
** table on the remote side.
** CA 20211112 Allow the DATASET and TABLE parameters for remote SOAP calls to be transferred via XML
** (so that relations, schema, etc is done on the server-side).
** CA 20220428 Allow null peerDef at setPeerDef.
** CA 20220602 Added basic support for transport of object instances over appserver call.
** IAS 20221029 Added support for the SCHEMA-MARSHAL attribute.
** IAS 20221102 Re-worked (de)serialization.
** IAS 20221104 Added structured indexes data.
** IAS 20221111 Fixed 'schemaMarshalLevel' support.
** SVL 20230113 Improved performance by replacing some "for-each" loops with indexed "for" loops.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 010 CA 20230215 Use PayloadSerializer for serialization.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist;
import static com.goldencode.util.NativeTypeSerializer.*;
import static com.goldencode.p2j.persist.AbstractTempTable.*;
import java.io.*;
import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
/**
* Any {@link TableResultSet} instance will be wrapped in a {@link TableWrapper} instance before
* sending it to a remote side. This class is intended to be used only by P2J runtime.
*/
public class TableWrapper
implements Externalizable
{
/** The result set to be sent to the remote side. */
private TableResultSet resultSet = null;
/** The list of read properties. */
private List<PropertyDefinition> properties;
/** The read data. */
private List<Object[]> rows;
/** A set of meta information for each row, as given by {@link TempRecord#toArray()}. */
private List<Object[]> rowsMeta;
/** Flag indicating this table is sent in INPUT or INPUT-OUTPUT mode. */
private boolean input;
/** Flag indicating this table is sent in OUTPUT or INPUT-OUTPUT mode. */
private boolean output;
/** Flag indicating this table is sent in APPEND mode. */
private boolean append;
/** Table name. */
private String tableName;
/** Flag indicating that table schema was received */
boolean receivedSchema = false;
/** Table SCHEMA-MARSHAL attribute value encodes as int */
private int schemaMarshalLevel;
/**
* Determines if this wrapper wraps data from a TABLE-HANDLE parameter. Necessary because of
* quirks in error handling: calling side needs to know if it is a TABLE-HANDLE parameter (not
* a TABLE parameter) on the called side.
*/
private boolean tableHandle = false;
/** The table BEFORE/AFTER type. */
private int tableType = TempTable.BeforeType.SIMPLE.ordinal();
/** The peer table. For an AFTER-TABLE, the peer-table is the BEFORE-TABLE. And vice-versa. */
private TableWrapper peerDef = null;
/** The number of indexes in this table definition. */
private int numIndexes = 0;
/** The XML namespace for this table definition. */
private String xmlns = null;
/** The XML prefix for this table definition. */
private String xmlPrefix = null;
/** The XML-NODE-NAME attribute for this table definition. */
private String xmlNodeName = null;
/** The ERROR-STRING attribute for this table definition. */
private String errorString = null;
/**
* A string with all indexes of this table definition encoded as a {@code String}.
*
* See multiIxCols syntax on
* https://documentation.progress.com/output/ua/OpenEdge_latest/dvjav/Constructor_2.html
*/
private String indexes = null;
/** List of indexes' descriptors */
private List<IndexDefinition> indexDefs = null;
/** Flag indicating if the source is a JSON table. */
private boolean fromJson = false;
/**
* For SOAP web services, the TABLE is sent (and received) serialized as XML. This includes the schema,
* also, for TABLE-HANDLE.
*/
private String xmlTable = null;
/** Flag indicating if the transfer is made via XML. */
private boolean asXml = false;
/**
* Default c'tor, explicitly added to allow instances of this class to be created on
* deserialization. Not for public use.
*/
public TableWrapper()
{
}
/**
* Create a new wrapper.
*
* @param input
* Flag indicating the wrapper is in INPUT mode.
* @param output
* Flag indicating the wrapper is in OUTPUT mode.
* @param tableHandle
* Flag indicating the wrapper is for a TABLE-HANDLE.
*/
public TableWrapper(boolean input, boolean output, boolean tableHandle)
{
this.input = input;
this.output = output;
this.tableHandle = tableHandle;
this.fromJson = true;
}
/**
* Wrap a custom result set in a class known to P2J, so the remote side can deserialize it.
*
* @param resultSet
* The result set, which is a subclass of {@link TableResultSet}.
*/
public TableWrapper(TableResultSet resultSet)
{
this.resultSet = resultSet;
this.tableName = resultSet.getStructureName();
}
/**
* Wrap a custom result set in a class known to P2J, so the remote side can deserialize it.
*
* @param fromJson
* Flag indicating if the table is from a JSON source.
* @param resultSet
* The result set, which is a subclass of {@link TableResultSet}.
*/
public TableWrapper(boolean fromJson, TableResultSet resultSet)
{
this(resultSet);
this.fromJson = fromJson;
}
/**
* Set the {@link #xmlTable}.
*
* @param xmlTable
* The XML representation of the table.
*/
public void setXmlTable(String xmlTable)
{
this.xmlTable = xmlTable;
}
/**
* Get the {@link #xmlTable}.
*
* @return See above.
*/
public String getXmlTable()
{
return xmlTable;
}
/**
* Set the {@link #asXml} flag.
*
* @param asXml
* When <code>true</code>, the table is transferred via its XML representation.
*/
public void setAsXml(boolean asXml)
{
this.asXml = asXml;
}
/**
* Get the {@link #asXml} flag.
*
* @return See above.
*/
public boolean isAsXml()
{
return asXml;
}
/**
* Check whether this is a {@code TableWrapper} for an AFTER-TABLE.
*
* @return {@code true} if this is a {@code TableWrapper} for an AFTER-TABLE.
*/
public boolean isAfterTable()
{
return tableType == TempTable.BeforeType.AFTER.ordinal();
}
/**
* Configures this object as a {@code TableWrapper} for an AFTER-TABLE.
* <p>
* Note: the {@code peerDef} is set-up as a BEFORE wrapper but the link to this object (its
* AFTER-TABLE wrapper) is not created (added as a comment).
*
* @param peerDef
* The {@code TableWrapper} for existing temp-table that will be the new BEFORE-TABLE.
*/
public void setPeerDef(TableWrapper peerDef)
{
this.peerDef = peerDef;
if (peerDef != null)
{
this.peerDef.tableType = TempTable.BeforeType.BEFORE.ordinal();
this.tableType = TempTable.BeforeType.AFTER.ordinal();
// this.peerDef.peerDef = peerDef;
}
}
/**
* Get the {@link #peerDef}.
*
* @return See above.
*/
public TableWrapper getPeerDef()
{
return peerDef;
}
/**
* Check if this table is in INPUT or INPUT-OUTPUT mode. This is used to access the data sent
* by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet} is
* set.
*
* @return The {@link #input} flag.
*/
public boolean isInput()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return input;
}
/**
* Check if this table is in OUTPUT or INPUT-OUTPUT mode. This is used to access the data sent
* by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet} is
* set.
*
* @return The {@link #output} flag.
*/
public boolean isOutput()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return output;
}
/**
* Check if this table is in APPEND mode. This is used to access the data sent
* by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet} is
* set.
*
* @return The {@link #append} flag.
*/
public boolean isAppend()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return append;
}
/**
* Returns table name.
*
* @return table name.
*/
public String getTableName()
{
return tableName;
}
/**
* Set table name.
*
* @param tableName
* table name.
*/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/**
* Get the {@link #numIndexes}.
*
* @return See above.
*/
public int getNumIndexes()
{
return numIndexes;
}
/**
* Set the {@link #numIndexes}.
*
* @param numIndexes
* The number of indexes in this table.
*/
public void setNumIndexes(int numIndexes)
{
this.numIndexes = numIndexes;
}
/**
* Get the {@link #indexes}.
*
* @return See above.
*/
public String getIndexes()
{
return indexes;
}
/**
* Set the {@link #indexes}.
*
* @param indexes
* The String representation of the indexes.
*/
public void setIndexes(String indexes)
{
this.indexes = indexes;
}
/**
* Get List of indexes' descriptors.
* @return the List of indexes' descriptors.
*/
public List<IndexDefinition> getIndexDefs()
{
return indexDefs;
}
/**
* Get the {@link #xmlns XML namespace}.
*
* @return See above.
*/
public String getXmlns()
{
return xmlns;
}
/**
* Set the {@link #xmlns XML namespace}
*
* @param xmlns
* The XML namespace.
*/
public void setXmlns(String xmlns)
{
this.xmlns = xmlns;
}
/**
* Get the {@link #xmlPrefix XML prefix}.
*
* @return See above.
*/
public String getXmlPrefix()
{
return xmlPrefix;
}
/**
* Set the {@link #xmlPrefix XML prefix}
*
* @param xmlPrefix
* The XML prefix.
*/
public void setXmlPrefix(String xmlPrefix)
{
this.xmlPrefix = xmlPrefix;
}
/**
* Obtain the XML node name for this table definition.
*
* @return the XML node name for this table definition.
*/
public String getXmlNodeName()
{
return xmlNodeName;
}
/**
* Sets the XML node name for this table definition.
*
* @param xmlNodeName
* XML prefix for this table definition.
*/
public void setXmlNodeName(String xmlNodeName)
{
this.xmlNodeName = xmlNodeName;
}
/**
* Obtain the ERROR-STRING attribute for this table definition.
*
* @return the ERROR-STRING attribute for this table definition.
*/
public String getErrorString()
{
return errorString;
}
/**
* Sets the ERROR-STRING attribute for this table definition.
*
* @param err
* ERROR-STRING attribute for this table definition.
*/
public void setErrorString(String err)
{
this.errorString = err;
}
/**
* Provides an iterator over the read {@link #properties}. This is used to access the data
* sent by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet}
* is set.
*
* @return See above.
*/
public Iterator<PropertyDefinition> propertyIterator()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return (properties == null ? Collections.EMPTY_LIST : properties).iterator();
}
/**
* Provides an iterator over the read {@link #rows}. This is used to access the data sent by a
* remote side, via {@link #readExternal}. Must not be called when {@link #resultSet} is set.
*
* @return See above.
*/
public Iterator<Object[]> rowIterator()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return (rows == null ? Collections.EMPTY_LIST : rows).iterator();
}
/**
* Provides an iterator over the read {@link #rowsMeta}. This is used to access the row meta
* information sent by a remote side, via {@link #readExternal}. Must not be called when
* {@link #resultSet} is set.
*
* @return See above.
*/
public Iterator<Object[]> rowMetaIterator()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return (rowsMeta == null ? Collections.EMPTY_LIST : rowsMeta).iterator();
}
/**
* Get the list of read {@link #properties}. This is used to access the data sent by a remote
* side, via {@link #readExternal}. Must not be called when {@link #resultSet} is set.
*
* @return See above.
*/
public ArrayList<PropertyDefinition> getProperties()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return properties != null ? new ArrayList<>(properties) : new ArrayList<>(0);
}
/**
* Get the list of read {@link #rows}. This is used to access the data sent by a remote side,
* via {@link #readExternal}. Must not be called when {@link #resultSet} is set.
*
* @return See above.
*/
public List<Object[]> getRows()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return new ArrayList<>(rows);
}
/**
* Get the list of read {@link #rowsMeta}. This is used to access the row meta information
* sent by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet}
* is set.
*
* @return See above.
*/
public List<Object[]> getRowsMeta()
{
if (resultSet != null)
{
throw new IllegalStateException(
"A custom result set must not be specified when calling this method!");
}
return new ArrayList<>(rowsMeta);
}
/**
* Set the table's properties.
*
* @param properties
* The property definitions.
*/
public void setProperties(List<PropertyDefinition> properties)
{
this.properties = properties;
}
/**
* Set the table's rows.
*
* @param rows
* The table data.
*/
public void setRows(List<Object[]> rows)
{
this.rows = rows;
}
/**
* Set the table rows' meta information.
*
* @param rowsMeta
* The meta information for each row.
*/
public void setRowsMeta(List<Object[]> rowsMeta)
{
this.rowsMeta = rowsMeta;
}
/**
* Send the result set (metadata and rows) to the specified output destination.
*
* @param out
* The output destination to which the result set will be sent.
*
* @throws IOException
* In case of I/O errors.
*/
public final void writeExternal(ObjectOutput out)
throws IOException
{
out.writeByte(fromJson ? 1 : 0);
out.writeByte(asXml ? 1 : 0);
out.writeByte(tableHandle ? 1 : 0);
// write modes
if (resultSet != null)
{
out.writeByte(resultSet.isInput() ? 1 : 0);
out.writeByte(resultSet.isOutput() ? 1 : 0);
out.writeByte(resultSet.isAppend() ? 1 : 0);
}
else
{
out.writeByte(input ? 1 : 0);
out.writeByte(output ? 1 : 0);
out.writeByte(append ? 1 : 0);
}
if (asXml)
{
// nothing else is sent,
writeString(out, xmlTable);
return;
}
boolean writeSchema = schemaMarshalLevel != SM_NONE;
out.writeByte(schemaMarshalLevel);
if (writeSchema)
{
writeString(out, tableName);
out.writeByte(tableType);
out.writeByte(numIndexes);
writeString(out,indexes);
writeList(out, indexDefs);
writeString(out,xmlns);
writeString(out,xmlPrefix);
}
// write properties
Set<Integer> blobProps = null;
Set<Integer> objectProps = null;
if (resultSet != null)
{
int idx = 0;
while (resultSet.hasMoreProperties())
{
PropertyDefinition property = resultSet.nextProperty();
if (writeSchema)
{
property.setMarshalLevel(schemaMarshalLevel);
out.writeByte(1);
property.writeExternal(out);
}
if (property.getType() == blob.class)
{
if (blobProps == null)
{
blobProps = new HashSet<>();
}
blobProps.add(idx);
}
else if (object.class.isAssignableFrom(property.getType()))
{
if (objectProps == null)
{
objectProps = new HashSet<>();
}
objectProps.add(idx);
}
idx = idx + 1;
}
}
if (writeSchema)
{
out.writeByte(0);
}
writeIntSet(out, blobProps);
writeIntSet(out, objectProps);
// write data
if (resultSet != null)
{
while (resultSet.hasMoreRows())
{
Object[] row = resultSet.nextRow();
if (blobProps != null)
{
Object[] row2 = new Object[row.length];
System.arraycopy(row, 0, row2, 0, row2.length);
for (int idx : blobProps)
{
if (row2[idx] instanceof blob)
{
row2[idx] = new MemoryBuffer(((blob) row2[idx]).getByteArray());
}
}
row = row2;
}
if (objectProps != null)
{
Object[] row2 = new Object[row.length];
System.arraycopy(row, 0, row2, 0, row2.length);
for (int idx : objectProps)
{
if (row2[idx] instanceof object)
{
row2[idx] = new LegacyObject((object<?>) row2[idx]);
}
}
row = row2;
}
PayloadSerializer.writePayload(out, row);
Object[] rowMeta = resultSet.nextRowMeta();
PayloadSerializer.writePayload(out, rowMeta);
}
}
PayloadSerializer.writePayload(out, null);
// last thing to do:
if (isAfterTable())
{
peerDef.writeExternal(out);
}
}
/**
* Read the result set (metadata and rows) from the specified input source.
*
* @param in
* The input source from which the result set will be read.
*
* @throws IOException
* In case of I/O errors.
* @throws ClassNotFoundException
* If the class of property could not be found/loaded.
*/
@Override
public final void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
{
fromJson = in.readByte() != 0;
asXml = in.readByte() != 0;
tableHandle = in.readByte() != 0;
// read modes
input = in.readByte() != 0;
output = in.readByte() != 0;
append = in.readByte() != 0;
if (asXml)
{
// nothing else is sent
xmlTable = readString(in);
return;
}
schemaMarshalLevel = in.readByte();
receivedSchema = schemaMarshalLevel != SM_NONE;
if (receivedSchema)
{
tableName = readString(in);
tableType = in.readByte();
numIndexes = in.readByte();
indexes = readString(in);
indexDefs = readList(in, () -> new IndexDefinition());
xmlns = readString(in);
xmlPrefix = readString(in);
properties = new ArrayList<>();
while (in.readByte() != 0)
{
PropertyDefinition pd = new PropertyDefinition();
pd.readExternal(in);
properties.add(pd);
}
}
Set<Integer> blobProps = readIntSet(in);
Set<Integer> objectProps = readIntSet(in);
// read data
rows = new ArrayList<>();
rowsMeta = new ArrayList<>();
do
{
Object next = PayloadSerializer.readPayload(in);
if (next == null)
{
break;
}
Object[] row = (Object[]) next;
rows.add(row);
if (blobProps != null)
{
blobProps.stream().forEach(i -> row[i] = new blob(((MemoryBuffer) row[i]).getValue()));
}
if (objectProps != null)
{
objectProps.stream().forEach(i -> {
LegacyObject ref = (LegacyObject) row[i];
ObjectVar<? extends _BaseObject_> v = new ObjectVar<>(ref.getType());
v.assign(ref.restore());
row[i] = v;
});
}
next = PayloadSerializer.readPayload(in);
rowsMeta.add((Object[]) next);
}
while (true);
// last thing to do:
if (isAfterTable())
{
// read the before-table
peerDef = new TableWrapper();
peerDef.readExternal(in);
peerDef.peerDef = this;
}
}
/**
* Check that table schema was received.
*
* @return <code>true</code> if table schema was received.
*/
public boolean isReceivedSchema()
{
return receivedSchema;
}
/**
* Determines if this wrapper wraps data from a TABLE-HANDLE parameter.
*
* @return <code>true</code> if this wrapper wraps data from a TABLE-HANDLE parameter.
*/
public boolean isTableHandle()
{
return tableHandle;
}
/**
* Check if the source is a JSON.
*
* @return The {@link #fromJson} flag.
*/
public boolean isFromJson()
{
return fromJson;
}
/**
* Set if this wrapper wraps data from a TABLE-HANDLE parameter.
*
* @param tableHandle
* <code>true</code> if this wrapper wraps data from a TABLE-HANDLE parameter.
*/
public void setTableHandle(boolean tableHandle)
{
this.tableHandle = tableHandle;
}
/** Get encoded table SCHEMA-MARSHAL attribute value.
*
* @return encoded table SCHEMA-MARSHAL attribute value.
*/
public int getSchemaMarshalLevel()
{
return schemaMarshalLevel;
}
/** Set encoded table SCHEMA-MARSHAL attribute value.
*
* @param schemaMarshalLevel
* encoded table SCHEMA-MARSHAL attribute value.
*/
public void setSchemaMarshalLevel(int schemaMarshalLevel)
{
this.schemaMarshalLevel = schemaMarshalLevel;
}
/**
* Initialize this wrapper with the info from the given temp-table.
*
* @param table
* The table to copy state from.
*/
public void init(TempTable table)
{
this.tableName = ((AbstractTempTable) table)._name();
Buffer defBuff = (Buffer) table.defaultBufferHandleNative();
this.indexes = TableMapper.getLegacyIndexInfo(table);
this.numIndexes = indexes.split("\\.").length; // TODO: "." or "\\." ?
this.indexDefs = TableMapper.getLegacyIndexes(table).map(lif -> new IndexDefinition(lif)).
collect(Collectors.toList());
this.xmlns = defBuff.namespaceURI().toStringMessage();
this.xmlPrefix = defBuff.namespacePrefix().toStringMessage();
}
/**
* Get a string representation of this table.
*
* @return See above.
*/
@Override
public String toString()
{
StringBuilder s = new StringBuilder();
s.append("TABLE[").append(this.tableName).append("]\n");
s.append("PROPERTIES:\n");
Iterator<PropertyDefinition> iter = propertyIterator();
while (iter.hasNext())
{
s.append(iter.next().toString()).append("\n");
}
s.append("\nROWS:\n");
Iterator<Object[]> rowIter = rowIterator();
while (rowIter.hasNext())
{
Object[] data = rowIter.next();
for (Object d : data)
{
s.append("[");
if (d instanceof BaseDataType)
{
s.append(((BaseDataType) d).toStringMessage());
}
else if (d.getClass().isArray())
{
int size = Array.getLength(d);
s.append("{");
for (int f = 0; f < size; f++)
{
s.append(f).append(":[").append(Array.get(d, f).toString()).append("]; ");
}
s.append("}");
}
else
{
s.append(d.toString());
}
s.append("]; ");
}
s.append("\n");
}
if (peerDef != null && isAfterTable())
{
s.append("BEFORE-").append(peerDef.toString());
}
return s.toString();
}
/**
* Returns <code>true</code> if this wrapper contains a valid data set.
*
* @return <code>true</code> if this wrapper contains a valid data set.
*/
public boolean isValid()
{
return resultSet != null || rows != null;
}
/**
* Explicitly set the result set for this wrapper.
*
* @param resultSet
* The result set to be sent to the remote side.
*/
public void setResultSet(TableResultSet resultSet)
{
this.resultSet = resultSet;
}
}