Skip to content

Commit

Permalink
votable: honour UBYTE_FLAG_INFO column annotation
Browse files Browse the repository at this point in the history
The VOTable table I/O handlers now mark unsignedByte columns with
UBYTE_FLAG_INFO on read, and if it's present on write the column
is written as unsignedByte rather than short.
  • Loading branch information
mbtaylor committed Feb 26, 2015
1 parent 37a06ea commit cc7abd5
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 9 deletions.
54 changes: 54 additions & 0 deletions votable/src/main/uk/ac/starlink/votable/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ public void setNullString( String nullString ) {
* If <tt>info</tt> is a ColumnInfo, then the preferred binary
* representation of bad values can be submitted in its auxiliary
* metadata under the key {@link Tables#NULL_VALUE_INFO}.
* Byte values will normally be serialised as short ints
* (since the java byte type is signed but the VOTable one is unsigned),
* but unsigned byte output can be forced by setting the
* {@link Tables#UBYTE_FLAG_INFO} aux metadata item to true.
*
* @param info a description of the type of value which needs to
* be encoded
Expand All @@ -255,6 +259,23 @@ public static Encoder getEncoder( ValueInfo info, boolean magicNulls,
&& dims.length > 0
&& dims[ dims.length - 1 ] < 0;

/* See if unsigned byte output is explicitly requested. */
boolean isUbyte = false;
if ( info instanceof ColumnInfo ) {
if ( Boolean.TRUE
.equals( ((ColumnInfo) info)
.getAuxDatumValue( Tables.UBYTE_FLAG_INFO,
Boolean.class ) ) ) {
if ( clazz == short[].class || clazz == Short.class ) {
isUbyte = true;
}
else {
logger.warning( "Ignoring " + Tables.UBYTE_FLAG_INFO
+ " on non-short column " + info );
}
}
}

/* Try to work out a representation to use for blank integer values. */
Number nullObj = null;
if ( info instanceof ColumnInfo ) {
Expand Down Expand Up @@ -287,6 +308,21 @@ public void encodeToStream( Object val, DataOutput out )
};
}

else if ( isUbyte && clazz == Short.class ) {
final int badVal = nullObj == null
? ( magicNulls ? 255 : 0 )
: nullObj.intValue();
String badString = isNullable ? Integer.toString( badVal ) : null;
return new ScalarEncoder( info, "unsignedByte", badString ) {
public void encodeToStream( Object val, DataOutput out )
throws IOException {
Number value = (Number) val;
out.writeByte( value == null ? badVal
: value.intValue() );
}
};
}

else if ( clazz == Byte.class ||
clazz == Short.class ) {
final int badVal = nullObj == null
Expand Down Expand Up @@ -412,6 +448,24 @@ public void pad1( DataOutput out ) throws IOException {
dims, enc1 );
}

else if ( isUbyte && clazz == short[].class ) {
Encoder1 enc1 = new Encoder1() {
public void encode1( Object array, int index, DataOutput out )
throws IOException {
int value = ((short[]) array)[ index ];
out.writeByte( value );
}
public void pad1( DataOutput out ) throws IOException {
out.writeByte( 0x0 );
}
};
return isVariable
? (Encoder) new VariableArrayEncoder( info, "unsignedByte",
dims, enc1 )
: (Encoder) new FixedArrayEncoder( info, "unsignedByte",
dims, enc1 );
}

else if ( clazz == byte[].class ) {
Encoder1 enc1 = new Encoder1() {
public void encode1( Object array, int index, DataOutput out )
Expand Down
9 changes: 8 additions & 1 deletion votable/src/main/uk/ac/starlink/votable/VOSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -502,17 +502,24 @@ private static StarTable prepareForSerializer( StarTable table,
boolean magicNulls,
boolean allowXtype ) {
ValueInfo badKey = Tables.NULL_VALUE_INFO;
ValueInfo ubyteKey = Tables.UBYTE_FLAG_INFO;
int ncol = table.getColumnCount();
final ColumnInfo[] colInfos = new ColumnInfo[ ncol ];
int modified = 0;
for ( int icol = 0; icol < ncol; icol++ ) {
ColumnInfo cinfo = new ColumnInfo( table.getColumnInfo( icol ) );
boolean isUbyte =
Boolean.TRUE
.equals( cinfo.getAuxDatumValue( ubyteKey, Boolean.class ) );
Class clazz = cinfo.getContentClass();
if ( magicNulls && cinfo.isNullable() &&
Number.class.isAssignableFrom( clazz ) &&
cinfo.getAuxDatum( badKey ) == null ) {
Number badValue;
if ( clazz == Byte.class || clazz == Short.class ) {
if ( isUbyte ) {
badValue = new Short( (short) 0xff );
}
else if ( clazz == Byte.class || clazz == Short.class ) {
badValue = new Short( Short.MIN_VALUE );
}
else if ( clazz == Integer.class ) {
Expand Down
7 changes: 6 additions & 1 deletion votable/src/main/uk/ac/starlink/votable/VOStarTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,10 @@ public class VOStarTable extends AbstractStarTable {
"Datatype", String.class, "VOTable data type name" );

private final static ValueInfo nullInfo = Tables.NULL_VALUE_INFO;
private final static ValueInfo ubyteInfo = Tables.UBYTE_FLAG_INFO;

private final static List auxDataInfos = Arrays.asList( new ValueInfo[] {
ID_INFO, DATATYPE_INFO, nullInfo, XTYPE_INFO,
ID_INFO, DATATYPE_INFO, nullInfo, XTYPE_INFO, ubyteInfo,
WIDTH_INFO, PRECISION_INFO, REF_INFO, TYPE_INFO,
} );

Expand Down Expand Up @@ -165,6 +166,10 @@ public ColumnInfo getColumnInfo( int icol ) {
String datatype = field.getAttribute( "datatype" );
auxdata.add( new DescribedValue( DATATYPE_INFO,
datatype ) );
if ( "unsignedByte".equals( datatype ) ) {
auxdata.add( new DescribedValue( ubyteInfo,
Boolean.TRUE ) );
}
}

String blankstr = field.getNull();
Expand Down
13 changes: 13 additions & 0 deletions votable/src/main/uk/ac/starlink/votable/VOTableWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@
* latter three cases may be either inline as base64 encoded CDATA or
* to a separate stream.
*
* <p>A couple of Auxiliary metadata items of the ColumnInfo metadata
* from written tables are respected:
* <ul>
* <li>{@link uk.ac.starlink.table.Tables#NULL_VALUE_INFO}:
* sets the value of "magic" blank value for
* integer columns</li>
* <li>{@link uk.ac.starlink.table.Tables#UBYTE_FLAG_INFO}:
* if set to <code>Boolean.TRUE</code> and if the column has content class
* <code>Short</code> or <code>short[]</code>, the data will be written
* with <code>datatype="unsignedByte"</code> instead of
* (signed 16-bit) <code>"short"</code>.</li>
* </ul>
*
* @author Mark Taylor (Starlink)
*/
public class VOTableWriter implements StarTableWriter, MultiStarTableWriter {
Expand Down
50 changes: 43 additions & 7 deletions votable/src/testcases/uk/ac/starlink/votable/AutoStarTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import uk.ac.starlink.table.DefaultValueInfo;
import uk.ac.starlink.table.DescribedValue;
import uk.ac.starlink.table.StarTable;
import uk.ac.starlink.table.Tables;
import uk.ac.starlink.util.TestCase;

/**
Expand All @@ -30,6 +31,8 @@ public class AutoStarTable extends ColumnStarTable {
new DefaultValueInfo( "Matrix", int[].class, "2xN matrix" );
private static final DefaultValueInfo SIZE_INFO =
new DefaultValueInfo( "Size", Double.class, null );
private static final DescribedValue UBYTE_AUXDATUM =
new DescribedValue( Tables.UBYTE_FLAG_INFO, Boolean.TRUE );
static {
NAMES_INFO.setElementSize( 16 );
}
Expand Down Expand Up @@ -63,6 +66,11 @@ public void addColumn( ColumnInfo colinfo ) {
final int[] shape = colinfo.getShape();
final int esize = colinfo.getElementSize();
final boolean isArray = colinfo.isArray();
final boolean isUbyte =
Boolean.TRUE
.equals( colinfo.getAuxDatumValue( Tables.UBYTE_FLAG_INFO,
Boolean.class ) );

final int icol = getColumnCount() + 1;
int n1 = 1;
if ( shape != null && shape.length > 0 ) {
Expand All @@ -87,6 +95,9 @@ public Object readValue( long lrow ) {
if ( ( irow + icol ) % 10 == 0 ) {
return null;
}
else if ( clazz == Short.class && isUbyte ) {
return Short.valueOf( (short) ( irow % 256 ) );
}
else if ( clazz == Boolean.class ) {
return Boolean.valueOf( icol + irow % 2 == 0 );
}
Expand All @@ -103,6 +114,9 @@ else if ( clazz == Short.class ) {
else if ( clazz == Integer.class ) {
return new Integer( icol + 100 * irow );
}
else if ( clazz == Long.class ) {
return new Long( icol + 1000 * irow );
}
else if ( clazz == Float.class ) {
if ( irow % 10 == 4 ) {
return new Float( Float.NaN );
Expand Down Expand Up @@ -130,11 +144,19 @@ else if ( clazz == boolean[].class ) {
else if ( clazz == byte[].class ||
clazz == short[].class ||
clazz == int[].class ||
clazz == long[].class ||
clazz == float[].class ||
clazz == double[].class ) {
Object array = Array.newInstance( clazz.getComponentType(),
nel );
testcase.fillCycle( array, -icol - irow, icol + irow );
if ( clazz == short[].class && isUbyte ) {
short[] sarray = (short[]) array;
for ( int i = 0; i < sarray.length; i++ ) {
sarray[ i ] =
(short) ( Math.abs( sarray[ i ] ) % 256 );
}
}
return array;
}
else if ( clazz == String[].class ) {
Expand All @@ -158,7 +180,8 @@ public static StarTable getDemoTable( long nrows ) {
table.setName( "Test Table" );
List params = new ArrayList();
params.add( new DescribedValue( NAMES_INFO,
new String[] { "Test", "Table", "x" } ) );
new String[] { "Test", "Table",
"x" } ) );
params.add( new DescribedValue( DRINK_INFO, "Cider" ) );
params.add( new DescribedValue( MATRIX_INFO,
new int[] { 4, 5, } ) );
Expand All @@ -182,30 +205,43 @@ public Object readValue( long lrow ) {
}
} );

Class[] ptypes = { byte.class, short.class, int.class, float.class,
double.class, };
Class[] ptypes = { byte.class, short.class, short.class, int.class,
long.class, float.class, double.class, };
for ( int i = 0; i < ptypes.length; i++ ) {
final Class ptype = ptypes[ i ];
String pname = ptype.getName();
ColumnInfo colinfo = new ColumnInfo( MATRIX_INFO );
if ( i == 1 ) {
assert short.class.equals( ptype );
pname = "ubyte";
colinfo.setAuxDatum( UBYTE_AUXDATUM );
}
colinfo.setContentClass( Array.newInstance( ptype, 0 ).getClass() );
colinfo.setName( ptype.getName() + "_matrix" );
colinfo.setName( pname + "_matrix" );
table.addColumn( colinfo );
ColumnInfo colinfo2 = new ColumnInfo( colinfo );
colinfo2.setName( ptype.getName() + "_vector" );
colinfo2.setName( pname + "_vector" );
final int nel = ( i + 2 ) % 4 + 2;
colinfo2.setShape( new int[] { nel } );
final int bs = i;
table.addColumn( colinfo2 );
}

Class[] stypes = { Byte.class, Short.class, Integer.class,
Float.class, Double.class, String.class };
Class[] stypes = { Byte.class, Short.class, Short.class, Integer.class,
Long.class, Float.class, Double.class,
String.class };
for ( int i = 0; i < stypes.length; i++ ) {
final int itype = i;
final Class stype = stypes[ i ];
String name = stype.getName().replaceFirst( "java.lang.", "" );
ColumnInfo colinfo = new ColumnInfo( name + "Scalar", stype,
name + " scalar data" );
if ( i == 1 ) {
assert Short.class.equals( stype );
colinfo.setAuxDatum( UBYTE_AUXDATUM );
colinfo.setName( "ubyteScalar" );
colinfo.setDescription( "Unsigned byte scalar data" );
}
table.addColumn( colinfo );
}

Expand Down

0 comments on commit cc7abd5

Please sign in to comment.