blob: 45237fb86f10d8cb6c5138956e3fb67baf7f561d [file] [log] [blame]
/* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.etch.bindings.java.transport.fmt.binary;
import java.io.IOException;
import org.apache.etch.bindings.java.msg.ComboValidator;
import org.apache.etch.bindings.java.msg.Field;
import org.apache.etch.bindings.java.msg.Message;
import org.apache.etch.bindings.java.msg.StructValue;
import org.apache.etch.bindings.java.msg.Type;
import org.apache.etch.bindings.java.msg.Validator;
import org.apache.etch.bindings.java.msg.ValueFactory;
import org.apache.etch.bindings.java.support.Validator_int;
import org.apache.etch.bindings.java.support.Validator_object;
import org.apache.etch.bindings.java.support.Validator_string;
import org.apache.etch.bindings.java.transport.ArrayValue;
import org.apache.etch.bindings.java.transport.TaggedDataInput;
import org.apache.etch.bindings.java.transport.fmt.TypeCode;
import org.apache.etch.util.Assertion;
import org.apache.etch.util.FlexBuffer;
/**
* BinaryTaggedDataInput has methods to support reading tagged
* values from an input buffer.
*/
final public class BinaryTaggedDataInput extends BinaryTaggedData
implements TaggedDataInput
{
/**
* Constructs the BinaryTaggedDataInput with a null buffer.
*
* @param vf the value factory for the service.
* @param uri the uri used to construct the transport stack.
*/
public BinaryTaggedDataInput( ValueFactory vf, String uri )
{
super( vf );
// don't have anything to do with uri yet.
}
private FlexBuffer buf;
private int lengthBudget;
/////////////////////////////
// TaggedDataInput methods //
/////////////////////////////
public Message readMessage( FlexBuffer buf ) throws IOException
{
this.buf = buf;
// lengthBudget is how many array elements total are reasonable to
// allocate while parsing this message. the largest value comes as each
// byte of the incoming message turned into an array element. the total
// will always be lower than this as often it takes multiple bytes to
// make a single array element. so, each time we make an array, we
// deduct the specified length from lengthBudget. if the result is
// negative, then either the incoming message is misformed or someone is
// trying to spoof us.
lengthBudget = buf.avail();
try
{
Message msg = startMessage();
readKeysAndValues( msg );
endMessage( msg );
return msg;
}
finally
{
this.buf = null;
lengthBudget = 0;
}
}
private StructValue readStruct() throws IOException
{
StructValue sv = startStruct();
readKeysAndValues( sv );
endStruct( sv );
return sv;
}
private ArrayValue readArray( Validator v ) throws IOException
{
ArrayValue av = startArray();
readValues( av, v );
endArray( av );
return av;
}
private void readKeysAndValues( StructValue sv )
throws IOException
{
Type t = sv.type();
while (true)
{
Field key = readField( t );
if (key == null)
break;
Validator v = t.getValidator( key );
if (v != null)
{
sv.put( key, readValue( v ) );
}
else
{
// read the value but ignore it.
Object obj = readValue( Validator_object.get( 0 ) );
if (false)
sv.put( key, obj );
}
}
}
private void readValues( ArrayValue av, Validator v ) throws IOException
{
Validator ev = v.elementValidator();
while (true)
{
Object value = readValue( ev, true );
if (value == NONE)
break;
av.add( value );
}
}
////////////////////////
// Main input methods //
////////////////////////
private Message startMessage() throws IOException
{
byte version = buf.getByte();
if (version != VERSION)
throw new IOException(
String.format(
"binary tagged data version mismatch: got %d expected %d",
version, VERSION ) );
Type t = readType();
int length = readLength();
return new Message( t, vf, length );
}
private void endMessage( Message msg )
{
// nothing to do, readKeysAndValues swallowed the NONE.
}
private StructValue startStruct() throws IOException
{
Type t = readType();
int length = readLength();
return new StructValue( t, vf, length );
}
private void endStruct( StructValue struct )
{
// nothing to do, readKeysAndValues swallowed the NONE.
}
@SuppressWarnings("deprecation")
private ArrayValue startArray() throws IOException
{
byte type = buf.getByte();
Type customStructType;
if (type == TypeCode.CUSTOM || type == TypeCode.STRUCT)
customStructType = readType();
else
customStructType = null;
int dim = readIntegerValue();
if (dim <= 0 || dim > Validator.MAX_NDIMS)
throw new IllegalArgumentException(
"dim <= 0 || dim > Validator.MAX_NDIMS" );
int length = readLength();
Object array = allocArrayValue( type, customStructType, dim, length );
return new ArrayValue( array, type, customStructType, dim );
}
private void endArray( ArrayValue array )
{
array.compact();
}
private Type readType() throws IOException
{
Object obj = readValue( intOrStrValidator, false );
if (obj instanceof Integer)
{
Integer id = (Integer) obj;
Type type = vf.getType( id );
if (type == null)
type = new Type( id, id.toString() );
return type;
}
Assertion.check( obj instanceof String, "obj instanceof String" );
String name = (String) obj;
Type type = vf.getType( name );
if (type == null)
type = new Type( name );
return type;
}
private Field readField( Type type ) throws IOException
{
Object obj = readValue( intOrStrValidator, true );
if (obj == NONE)
return null;
if (obj instanceof Integer)
{
Integer id = (Integer) obj;
Field field = type.getField( id );
if (field == null)
field = new Field( id, id.toString() );
return field;
}
Assertion.check( obj instanceof String, "obj instanceof String" );
String name = (String) obj;
Field field = type.getField( name );
if (field == null)
field = new Field( name );
return field;
}
private final Validator intOrStrValidator =
new ComboValidator( Validator_int.get( 0 ), Validator_string.get( 0 ) );
private int readLength() throws IOException
{
int length = readIntegerValue();
if (length < 0 || length > lengthBudget)
throw new IllegalArgumentException(
"length < 0 || length > lengthBudget" );
lengthBudget -= length;
return length;
}
private Integer readIntegerValue() throws IOException
{
return (Integer) readValue( intValidator );
}
private final Validator intValidator = Validator_int.get( 0 );
///////////////////////////
// LOCAL UTILITY METHODS //
///////////////////////////
private Object validateValue( Validator v, Object value )
{
// v == null more or less implies that a field is not known
// for a type. thus we don't care about the field value as
// we are going to ignore it. therefore, return null.
if (v == null)
return null;
if (value == null)
return null;
return v.validateValue( value );
}
private Object validateValue( Validator v, boolean noneOk, Object value )
{
if (noneOk && value == NONE)
return value;
return validateValue( v, value );
}
private Object readValue( Validator v ) throws IOException
{
return readValue( v, false );
}
@SuppressWarnings("deprecation")
private Object readValue( Validator v, boolean noneOk ) throws IOException
{
byte type = buf.getByte();
switch (type)
{
case TypeCode.NULL:
return validateValue( v, null );
case TypeCode.NONE:
return validateValue( v, noneOk, NONE );
case TypeCode.BOOLEAN_FALSE:
return validateValue( v, Boolean.FALSE );
case TypeCode.BOOLEAN_TRUE:
return validateValue( v, Boolean.TRUE );
case TypeCode.BYTE:
return validateValue( v, buf.getByte() );
case TypeCode.SHORT:
return validateValue( v, buf.getShort() );
case TypeCode.INT:
return validateValue( v, buf.getInt() );
case TypeCode.LONG:
return validateValue( v, buf.getLong() );
case TypeCode.FLOAT:
return validateValue( v, buf.getFloat() );
case TypeCode.DOUBLE:
return validateValue( v, buf.getDouble() );
case TypeCode.BYTES:
return validateValue( v, readBytes() );
// reserved for future use:
// case TypeCode.BOOLS:
// case TypeCode.SHORTS:
// case TypeCode.INTS:
// case TypeCode.LONGS:
// case TypeCode.FLOATS:
// case TypeCode.DOUBLES:
// throw new UnsupportedOperationException( "unsupported type code "+type );
case TypeCode.ARRAY:
return validateValue( v, fromArrayValue( readArray( v ) ) );
case TypeCode.EMPTY_STRING:
return validateValue( v, "" );
case TypeCode.STRING:
return validateValue( v, new String( readBytes(), vf.getStringEncoding() ) );
case TypeCode.STRUCT:
case TypeCode.CUSTOM:
return validateValue( v, vf.importCustomValue( readStruct() ) );
default:
if (type >= TypeCode.MIN_TINY_INT && type <= TypeCode.MAX_TINY_INT)
return validateValue( v, type );
throw new UnsupportedOperationException( "unsupported type code "+type );
}
}
private byte[] readBytes() throws IOException
{
int length = readLength();
byte[] b = new byte[length];
buf.getFully( b );
return b;
}
}