From 96fe2e2bfec7f4f19350043e82b45eea3108bd51 Mon Sep 17 00:00:00 2001 From: Maciej Lach Date: Thu, 24 Sep 2015 13:49:50 +0200 Subject: [PATCH 1/5] Improved performance while iterating over QTable, QDictionary and QKeyedTable: partial reimplementation of java.lang.reflect.Array benefiting from Hot Spot VM optimizations. --- CHANGELOG.txt | 8 + src/main/java/com/exxeleron/qjava/Array.java | 425 ++++++++++++++++++ .../java/com/exxeleron/qjava/QDictionary.java | 1 - src/main/java/com/exxeleron/qjava/QTable.java | 1 - .../java/com/exxeleron/qjava/QWriter.java | 1 - src/main/java/com/exxeleron/qjava/Utils.java | 1 - 6 files changed, 433 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/exxeleron/qjava/Array.java diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b63a8f0..a235080 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,11 @@ +------------------------------------------------------------------------------ + qJava 2.2.1 [2015.x.x] +------------------------------------------------------------------------------ + + - Improved performance while iterating over QTable, QDictionary + and QKeyedTable: partial reimplementation of java.lang.reflect.Array + benefiting from Hot Spot VM optimizations. + ------------------------------------------------------------------------------ qJava 2.2.0 [2015.08.14] ------------------------------------------------------------------------------ diff --git a/src/main/java/com/exxeleron/qjava/Array.java b/src/main/java/com/exxeleron/qjava/Array.java new file mode 100644 index 0000000..8ad7a31 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/Array.java @@ -0,0 +1,425 @@ +/** + * Copyright (c) 2011-2014 Exxeleron GmbH + * + * Licensed 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 com.exxeleron.qjava; + +/** + * Partial reimplementation of {@link java.lang.reflect.Array} class due to performance issues. + */ +final class Array { + + private static RuntimeException illegalArgumentArray( final Object array ) { + if ( array == null ) { + return new NullPointerException("Array argument is null"); + } else if ( !array.getClass().isArray() ) { + return new IllegalArgumentException("Argument is not an array"); + } else { + return new IllegalArgumentException("Array is of incompatible type"); + } + } + + /** + * Returns the length of the specified array object, as an int. + * + * @param array + * the array + * @return the length of the array + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * if the object argument is not an array + */ + public static int getLength( final Object array ) { + if ( array instanceof Object[] ) { + return ((Object[]) array).length; + } + if ( array instanceof boolean[] ) { + return ((boolean[]) array).length; + } + if ( array instanceof byte[] ) { + return ((byte[]) array).length; + } + if ( array instanceof char[] ) { + return ((char[]) array).length; + } + if ( array instanceof short[] ) { + return ((short[]) array).length; + } + if ( array instanceof int[] ) { + return ((int[]) array).length; + } + if ( array instanceof long[] ) { + return ((long[]) array).length; + } + if ( array instanceof float[] ) { + return ((float[]) array).length; + } + if ( array instanceof double[] ) { + return ((double[]) array).length; + } + throw illegalArgumentArray(array); + } + + /** + * Returns the value of the indexed component in the specified array object. The value is automatically wrapped in + * an object if it has a primitive type. + * + * @param array + * the array + * @param index + * the index + * @return the (possibly wrapped) value of the indexed component in the specified array + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static Object get( final Object array, final int index ) { + if ( array instanceof Object[] ) { + return ((Object[]) array)[index]; + } + if ( array instanceof boolean[] ) { + return ((boolean[]) array)[index]; + } + if ( array instanceof byte[] ) { + return ((byte[]) array)[index]; + } + if ( array instanceof char[] ) { + return ((char[]) array)[index]; + } + if ( array instanceof short[] ) { + return ((short[]) array)[index]; + } + if ( array instanceof int[] ) { + return ((int[]) array)[index]; + } + if ( array instanceof long[] ) { + return ((long[]) array)[index]; + } + if ( array instanceof float[] ) { + return ((float[]) array)[index]; + } + if ( array instanceof double[] ) { + return ((double[]) array)[index]; + } + throw illegalArgumentArray(array); + } + + /** + * Sets the value of the indexed component of the specified array object to the specified boolean value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setBoolean( final Object array, final int index, final boolean value ) { + if ( array instanceof boolean[] ) { + ((boolean[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified byte value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setByte( final Object array, final int index, final byte value ) { + if ( array instanceof byte[] ) { + ((byte[]) array)[index] = value; + } else if ( array instanceof short[] ) { + ((short[]) array)[index] = value; + } else if ( array instanceof int[] ) { + ((int[]) array)[index] = value; + } else if ( array instanceof long[] ) { + ((long[]) array)[index] = value; + } else if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified char value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setChar( final Object array, final int index, final char value ) { + if ( array instanceof char[] ) { + ((char[]) array)[index] = value; + } else if ( array instanceof int[] ) { + ((int[]) array)[index] = value; + } else if ( array instanceof long[] ) { + ((long[]) array)[index] = value; + } else if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified short value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setShort( final Object array, final int index, final short value ) { + if ( array instanceof short[] ) { + ((short[]) array)[index] = value; + } else if ( array instanceof int[] ) { + ((int[]) array)[index] = value; + } else if ( array instanceof long[] ) { + ((long[]) array)[index] = value; + } else if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified int value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setInt( final Object array, final int index, final int value ) { + if ( array instanceof int[] ) { + ((int[]) array)[index] = value; + } else if ( array instanceof long[] ) { + ((long[]) array)[index] = value; + } else if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified long value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setLong( final Object array, final int index, final long value ) { + if ( array instanceof long[] ) { + ((long[]) array)[index] = value; + } else if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified float value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setFloat( final Object array, final int index, final float value ) { + if ( array instanceof float[] ) { + ((float[]) array)[index] = value; + } else if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified double value. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the specified value cannot be converted to the + * underlying array's component type by an identity or a primitive widening conversion + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void setDouble( final Object array, final int index, final double value ) { + if ( array instanceof double[] ) { + ((double[]) array)[index] = value; + } else { + throw illegalArgumentArray(array); + } + } + + /** + * Sets the value of the indexed component of the specified array object to the specified new value. The new value + * is first automatically unwrapped if the array has a primitive component type. + * + * @param array + * the array + * @param index + * the index into the array + * @param value + * the new value of the indexed component + * + * @throws NullPointerException + * If the specified object is null + * @throws IllegalArgumentException + * If the specified object is not an array, or if the array component type is primitive and an unwrapping conversion fails + * @throws ArrayIndexOutOfBoundsException + * If the specified index argument is negative, or if it is greater than or equal to the length of the + * specified array + */ + public static void set( final Object array, final int index, final Object value ) { + if ( array instanceof Object[] ) { + try { + ((Object[]) array)[index] = value; + } catch ( final ArrayStoreException e ) { + throw illegalArgumentArray(array); + } + } else if ( value instanceof Boolean ) { + setBoolean(array, index, (Boolean) value); + } else if ( value instanceof Byte ) { + setByte(array, index, (Byte) value); + } else if ( value instanceof Short ) { + setShort(array, index, (Short) value); + } else if ( value instanceof Character ) { + setChar(array, index, (Character) value); + } else if ( value instanceof Integer ) { + setInt(array, index, (Integer) value); + } else if ( value instanceof Long ) { + setLong(array, index, (Long) value); + } else if ( value instanceof Float ) { + setFloat(array, index, (Float) value); + } else if ( value instanceof Double ) { + setDouble(array, index, (Double) value); + } else { + throw illegalArgumentArray(array); + } + } +} diff --git a/src/main/java/com/exxeleron/qjava/QDictionary.java b/src/main/java/com/exxeleron/qjava/QDictionary.java index 90a7d9f..34b945a 100644 --- a/src/main/java/com/exxeleron/qjava/QDictionary.java +++ b/src/main/java/com/exxeleron/qjava/QDictionary.java @@ -15,7 +15,6 @@ */ package com.exxeleron.qjava; -import java.lang.reflect.Array; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/exxeleron/qjava/QTable.java b/src/main/java/com/exxeleron/qjava/QTable.java index 00d2f14..14aefdf 100644 --- a/src/main/java/com/exxeleron/qjava/QTable.java +++ b/src/main/java/com/exxeleron/qjava/QTable.java @@ -15,7 +15,6 @@ */ package com.exxeleron.qjava; -import java.lang.reflect.Array; import java.util.HashMap; import java.util.Iterator; import java.util.Map; diff --git a/src/main/java/com/exxeleron/qjava/QWriter.java b/src/main/java/com/exxeleron/qjava/QWriter.java index 402f639..542388f 100644 --- a/src/main/java/com/exxeleron/qjava/QWriter.java +++ b/src/main/java/com/exxeleron/qjava/QWriter.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.lang.reflect.Array; import java.util.UUID; /** diff --git a/src/main/java/com/exxeleron/qjava/Utils.java b/src/main/java/com/exxeleron/qjava/Utils.java index 5aeb173..7b24753 100644 --- a/src/main/java/com/exxeleron/qjava/Utils.java +++ b/src/main/java/com/exxeleron/qjava/Utils.java @@ -15,7 +15,6 @@ */ package com.exxeleron.qjava; -import java.lang.reflect.Array; import java.util.Arrays; import java.util.TimeZone; From 5211417e61f047f3f0e721300d59472f9308e5d6 Mon Sep 17 00:00:00 2001 From: Maciej Lach Date: Tue, 29 Sep 2015 11:44:45 +0200 Subject: [PATCH 2/5] API redesign: enable custom serializers & deserializers --- CHANGELOG.txt | 8 +- pom.xml | 2 +- .../com/exxeleron/qjava/ByteInputStream.java | 147 +++++ .../com/exxeleron/qjava/ByteOutputStream.java | 190 ++++++ .../com/exxeleron/qjava/DefaultQReader.java | 377 +++++++++++ .../com/exxeleron/qjava/DefaultQWriter.java | 495 ++++++++++++++ .../com/exxeleron/qjava/QBasicConnection.java | 53 +- .../java/com/exxeleron/qjava/QConnection.java | 2 +- .../java/com/exxeleron/qjava/QReader.java | 428 ++---------- src/main/java/com/exxeleron/qjava/QType.java | 163 +---- .../java/com/exxeleron/qjava/QWriter.java | 622 ++---------------- .../java/com/exxeleron/qjava/TestQReader.java | 18 +- .../java/com/exxeleron/qjava/TestQWriter.java | 4 +- 13 files changed, 1435 insertions(+), 1074 deletions(-) create mode 100644 src/main/java/com/exxeleron/qjava/ByteInputStream.java create mode 100644 src/main/java/com/exxeleron/qjava/ByteOutputStream.java create mode 100644 src/main/java/com/exxeleron/qjava/DefaultQReader.java create mode 100644 src/main/java/com/exxeleron/qjava/DefaultQWriter.java diff --git a/CHANGELOG.txt b/CHANGELOG.txt index a235080..8b231a0 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,10 +1,12 @@ ------------------------------------------------------------------------------ - qJava 2.2.1 [2015.x.x] + qJava 2.3.0 [2015.x.x] ------------------------------------------------------------------------------ - - Improved performance while iterating over QTable, QDictionary + - API redesign: enable custom serializers & deserializers + - Improve memory reuse in QWriter + - Improve performance while iterating over QTable, QDictionary and QKeyedTable: partial reimplementation of java.lang.reflect.Array - benefiting from Hot Spot VM optimizations. + benefiting from Hot Spot VM optimizations ------------------------------------------------------------------------------ qJava 2.2.0 [2015.08.14] diff --git a/pom.xml b/pom.xml index 368ab64..2e54921 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 2014 bundle - 2.2.1-SNAPSHOT + 2.3.0-SNAPSHOT exxeleron diff --git a/src/main/java/com/exxeleron/qjava/ByteInputStream.java b/src/main/java/com/exxeleron/qjava/ByteInputStream.java new file mode 100644 index 0000000..c2006bc --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/ByteInputStream.java @@ -0,0 +1,147 @@ +package com.exxeleron.qjava; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteOrder; + +/** + * Convenience class for converting wrapped byte buffer to primitive types. + */ +public final class ByteInputStream { + + private byte[] buffer; + private ByteOrder endianess; + private int position; + private String encoding; + + /** + * Creates new {@link ByteInputStream}. + * + * @param encoding + * encoding for symbols convertion + * @param endianess + * byte order of the input stream + */ + public ByteInputStream(final String encoding, final ByteOrder endianess) { + this.encoding = encoding; + this.endianess = endianess; + } + + /** + * Wraps byte buffer and resets reading position. + * + * @param newBuffer + * byte buffer to be wrapped + */ + public void wrap( final byte[] newBuffer ) { + buffer = newBuffer; + position = 0; + } + + /** + * Copies part of the wrapped byte buffer into a new array. + * + * @param dest + * destination buffer + * @param start + * start position in the wrapped buffer + * @param length + * number of bytes to be copied + */ + public void get( final byte[] dest, final int start, final int length ) { + System.arraycopy(buffer, position, dest, start, length); + position += length; + } + + /** + * Retrieves single byte from the wrapped buffer. + * + * @return the byte + */ + public byte get() { + return buffer[position++]; + } + + /** + * Retrieves single short value from the wrapped buffer. + * + * @return the short + */ + public short getShort() { + final int x = buffer[position++], y = buffer[position++]; + return (short) (endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xff | y << 8 : x << 8 | y & 0xff); + } + + /** + * Retrieves single int value from the wrapped buffer. + * + * @return the int + */ + public int getInt() { + final int x = getShort(), y = getShort(); + return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffff | y << 16 : x << 16 | y & 0xffff; + } + + /** + * Retrieves single long value from the wrapped buffer. + * + * @return the long + */ + public long getLong() { + final int x = getInt(), y = getInt(); + return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffffffffL | (long) y << 32 : (long) x << 32 | y & 0xffffffffL; + } + + /** + * Retrieves single float value from the wrapped buffer. + * + * @return the float + */ + public float getFloat() { + return Float.intBitsToFloat(getInt()); + } + + /** + * Retrieves single double value from the wrapped buffer. + * + * @return the double + */ + public Double getDouble() { + return Double.longBitsToDouble(getLong()); + } + + /** + * Retrieves single symbol (Java {@link String}) from the byte buffer. + * + * @return {@link String} + * @throws UnsupportedEncodingException + * if the encoding is unsupported + */ + public String getSymbol() throws UnsupportedEncodingException { + final int p = position; + + for ( ; buffer[position++] != 0; ) { + // empty; + } + return (p == position - 1) ? "" : new String(buffer, p, position - 1 - p, encoding); + } + + /** + * Retrieves byte order for the wrapped buffer. + * + * @return {@link ByteOrder} + */ + public ByteOrder getOrder() { + return endianess; + } + + /** + * Sets the byte order for reading the wrapeed buffer. + * + * @param endianess + * byte order + */ + public void setOrder( final ByteOrder endianess ) { + this.endianess = endianess; + } + +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/ByteOutputStream.java b/src/main/java/com/exxeleron/qjava/ByteOutputStream.java new file mode 100644 index 0000000..f80ca61 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/ByteOutputStream.java @@ -0,0 +1,190 @@ +package com.exxeleron.qjava; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * {@link OutputStream} wrapping resizable byte buffer. Provides convenient methods for serializing data to IPC stream. + */ +public class ByteOutputStream extends OutputStream { + public static final int BUFFER_SIZE = 65536; + private static final double BUFFER_GROWTH_FACTOR = 1.5; + + protected byte buffer[]; + + protected int count; + + /** + * Creates new {@link ByteOutputStream} with default size. + */ + public ByteOutputStream() { + buffer = new byte[BUFFER_SIZE]; + } + + /** + * Creates new {@link ByteOutputStream} with specified size. + * + * @param bufferSize + * initial size of the buffer + */ + public ByteOutputStream(final int bufferSize) { + buffer = new byte[bufferSize]; + } + + private void resizeBuffer( final int newcount ) { + if ( newcount > buffer.length ) { + final byte[] copy = new byte[Math.max(newcount + BUFFER_SIZE, (int) (buffer.length * BUFFER_GROWTH_FACTOR) + 1)]; + System.arraycopy(buffer, 0, copy, 0, buffer.length); + buffer = copy; + } + } + + /** + * Resets the buffer writing position. + */ + public void reset() { + count = 0; + } + + /** + * Writes the specified short to this output stream. + * + * @param value + * the short + */ + public void writeShort( final short value ) { + writeByte((byte) value); + writeByte((byte) (value >> 8)); + } + + /** + * Writes the specified int to this output stream. + * + * @param value + * the int + */ + public void writeInt( final int value ) { + writeShort((short) value); + writeShort((short) (value >> 16)); + } + + /** + * Writes the specified long to this output stream. + * + * @param value + * the long + */ + public void writeLong( final long value ) { + writeInt((int) value); + writeInt((int) (value >> 32)); + } + + /** + * Writes the specified long to this output stream using big endian padding. + * + * @param value + * the long + */ + public void writeLongBigEndian( final long value ) { + final byte[] arr = new byte[] { (byte) ((value >> 56) & 0xff), (byte) ((value >> 48) & 0xff), (byte) ((value >> 40) & 0xff), + (byte) ((value >> 32) & 0xff), (byte) ((value >> 24) & 0xff), (byte) ((value >> 16) & 0xff), + (byte) ((value >> 8) & 0xff), (byte) ((value >> 0) & 0xff) }; + for ( byte anArr : arr ) { + writeByte(anArr); + } + } + + /** + * Writes the specified float to this output stream. + * + * @param value + * the float + */ + public void writeFloat( final float value ) { + writeInt(Float.floatToIntBits(value)); + } + + /** + * Writes the specified double to this output stream. + * + * @param value + * the double + */ + public void writeDouble( final double value ) { + writeLong(Double.doubleToLongBits(value)); + } + + /** + * Writes the specified byte to this output stream. + * + * @param value + * the byte + */ + public void writeByte( final byte value ) { + final int newcount = count + 1; + resizeBuffer(newcount); + buffer[count] = value; + count = newcount; + } + + /** + * Writes the specified byte to this output stream. + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write( final int b ) { + final int newcount = count + 1; + resizeBuffer(newcount); + buffer[count] = (byte) b; + count = newcount; + } + + @Override + public void write( final byte b[], final int off, final int len ) { + if ( (off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) ) { + throw new IndexOutOfBoundsException("Attempt to write outside of the buffer. Offset: " + off + ", Size: " + len + ", Buffer: " + b.length + "."); + } else if ( len == 0 ) { + return; + } + final int newcount = count + len; + resizeBuffer(newcount); + System.arraycopy(b, off, buffer, count, len); + count = newcount; + } + + /** + * Numbers of written bytes. + * + * @return number of bytes + */ + public int count() { + return count; + } + + @Override + public void close() throws IOException { + // + } + + /** + * Creates copy of internal buffer. + * + * @return copy of the internal buffer as byte[] + */ + public byte[] toByteArray() { + final byte[] copy = new byte[count]; + System.arraycopy(buffer, 0, copy, 0, Math.min(buffer.length, count)); + return copy; + } + + /** + * Provides direct access to underlying buffer. + * + * @return buffer + */ + public byte[] buffer() { + return buffer; + } + +} \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/DefaultQReader.java b/src/main/java/com/exxeleron/qjava/DefaultQReader.java new file mode 100644 index 0000000..64f3009 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/DefaultQReader.java @@ -0,0 +1,377 @@ +/** + * Copyright (c) 2011-2015 Exxeleron GmbH + * + * Licensed 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 com.exxeleron.qjava; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteOrder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Default {@link QReader} implementation. + */ +public class DefaultQReader extends QReader { + + /** + * @see com.exxeleron.qjava.QReader#readObject() + */ + protected Object readObject() throws QException, IOException { + final QType qtype = QType.getQType(reader.get()); + + if ( qtype == QType.GENERAL_LIST ) { + return readGeneralList(); + } else if ( qtype == QType.ERROR ) { + throw readError(); + } else if ( qtype == QType.DICTIONARY ) { + return readDictionary(); + } else if ( qtype == QType.TABLE ) { + return readTable(); + } else if ( qtype.getTypeCode() < 0 ) { + return readAtom(qtype); + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + return readList(qtype); + } else if ( qtype.getTypeCode() >= QType.LAMBDA.getTypeCode() ) { + return readFunction(qtype); + } + + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + @SuppressWarnings("incomplete-switch") + protected Object readAtom( final QType qtype ) throws QException, UnsupportedEncodingException { + switch ( qtype ) { + case BOOL: + return reader.get() == 1 ? true : false; + case GUID: + return readGuid(); + case BYTE: + return reader.get(); + case SHORT: + return reader.getShort(); + case INT: + return reader.getInt(); + case LONG: + return reader.getLong(); + case FLOAT: + return reader.getFloat(); + case DOUBLE: + return reader.getDouble(); + case CHAR: + return (char) reader.get(); + case SYMBOL: + return reader.getSymbol(); + case TIMESTAMP: + return new QTimestamp(reader.getLong()); + case MONTH: + return new QMonth(reader.getInt()); + case DATE: + return new QDate(reader.getInt()); + case DATETIME: + return new QDateTime(reader.getDouble()); + case TIMESPAN: + return new QTimespan(reader.getLong()); + case MINUTE: + return new QMinute(reader.getInt()); + case SECOND: + return new QSecond(reader.getInt()); + case TIME: + return new QTime(reader.getInt()); + } + + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + @SuppressWarnings("incomplete-switch") + protected Object readList( final QType qtype ) throws QException, UnsupportedEncodingException { + reader.get(); // ignore attributes + final int length = reader.getInt(); + + switch ( qtype ) { + case BOOL_LIST: { + final boolean[] list = new boolean[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.get() == 1 ? true : false; + } + return list; + } + + case GUID_LIST: { + final UUID[] list = new UUID[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = readGuid(); + } + return list; + } + + case BYTE_LIST: { + final byte[] list = new byte[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.get(); + } + return list; + } + case SHORT_LIST: { + final short[] list = new short[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getShort(); + } + return list; + } + case INT_LIST: { + final int[] list = new int[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getInt(); + } + return list; + } + case LONG_LIST: { + final long[] list = new long[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getLong(); + } + return list; + } + case FLOAT_LIST: { + final float[] list = new float[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getFloat(); + } + return list; + } + case DOUBLE_LIST: { + final double[] list = new double[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getDouble(); + } + return list; + } + case STRING: { + final byte[] buffer = new byte[length]; + reader.get(buffer, 0, length); + return new String(buffer, getEncoding()).toCharArray(); + } + case SYMBOL_LIST: { + final String[] list = new String[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = reader.getSymbol(); + } + return list; + } + case TIMESTAMP_LIST: { + final QTimestamp[] list = new QTimestamp[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTimestamp(reader.getLong()); + } + return list; + } + case MONTH_LIST: { + final QMonth[] list = new QMonth[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QMonth(reader.getInt()); + } + return list; + } + case DATE_LIST: { + final QDate[] list = new QDate[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QDate(reader.getInt()); + } + return list; + } + case DATETIME_LIST: { + final QDateTime[] list = new QDateTime[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QDateTime(reader.getDouble()); + } + return list; + } + case TIMESPAN_LIST: { + final QTimespan[] list = new QTimespan[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTimespan(reader.getLong()); + } + return list; + } + case MINUTE_LIST: { + final QMinute[] list = new QMinute[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QMinute(reader.getInt()); + } + return list; + } + case SECOND_LIST: { + final QSecond[] list = new QSecond[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QSecond(reader.getInt()); + } + return list; + } + case TIME_LIST: { + final QTime[] list = new QTime[length]; + for ( int i = 0; i < length; i++ ) { + list[i] = new QTime(reader.getInt()); + } + return list; + } + } + + throw new QReaderException("Unable to deserialize q type: " + qtype); + } + + protected UUID readGuid() { + final ByteOrder currentOrder = reader.getOrder(); + reader.setOrder(ByteOrder.BIG_ENDIAN); + final long l1 = reader.getLong(); + final long l2 = reader.getLong(); + reader.setOrder(currentOrder); + return new UUID(l1, l2); + } + + protected Object[] readGeneralList() throws QException, IOException { + reader.get(); // ignore attributes + final int length = reader.getInt(); + final Object[] list = new Object[length]; + + for ( int i = 0; i < length; i++ ) { + list[i] = readObject(); + } + + return list; + } + + protected QException readError() throws IOException { + return new QException(reader.getSymbol()); + } + + protected Object readDictionary() throws QException, IOException { + final Object keys = readObject(); + final Object values = readObject(); + + if ( keys != null && keys.getClass().isArray() && (values != null && values.getClass().isArray() || values instanceof QTable) ) { + return new QDictionary(keys, values); + } else if ( keys instanceof QTable && values instanceof QTable ) { + return new QKeyedTable((QTable) keys, (QTable) values); + } + + throw new QReaderException("Cannot create valid dictionary object from mapping: " + keys + " to " + values); + } + + protected QTable readTable() throws QException, IOException { + reader.get(); // attributes + reader.get(); // dict type stamp + return new QTable((String[]) readObject(), (Object[]) readObject()); + } + + protected QFunction readFunction( final QType qtype ) throws QException, IOException { + if ( qtype == QType.LAMBDA ) { + reader.getSymbol(); // ignore context + final String expression = new String((char[]) readObject()); + return new QLambda(expression); + } else if ( qtype == QType.PROJECTION ) { + final int length = reader.getInt(); + final Object[] parameters = new Object[length]; + for ( int i = 0; i < length; i++ ) { + parameters[i] = readObject(); + } + return new QProjection(parameters); + } else if ( qtype == QType.UNARY_PRIMITIVE_FUNC ) { + final byte code = reader.get(); + return code == 0 ? null : new QFunction(qtype.getTypeCode()); + } else if ( qtype.getTypeCode() < QType.PROJECTION.getTypeCode() ) { + reader.get(); // ignore function code + return new QFunction(qtype.getTypeCode()); + } else if ( qtype == QType.COMPOSITION_FUNC ) { + final int length = reader.getInt(); + final Object[] parameters = new Object[length]; + for ( int i = 0; i < length; i++ ) { + parameters[i] = readObject(); + } + return new QFunction(qtype.getTypeCode()); + } else { + readObject(); // ignore function object + return new QFunction(qtype.getTypeCode()); + } + } + + @SuppressWarnings("rawtypes") + private static final Map fromQ = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029445L; + + { + put(QType.GENERAL_LIST, Object[].class); + put(QType.BOOL, Boolean.class); + put(QType.BOOL_LIST, boolean[].class); + put(QType.BYTE, Byte.class); + put(QType.BYTE_LIST, byte[].class); + put(QType.GUID, UUID.class); + put(QType.GUID_LIST, UUID[].class); + put(QType.SHORT, Short.class); + put(QType.SHORT_LIST, short[].class); + put(QType.INT, Integer.class); + put(QType.INT_LIST, int[].class); + put(QType.LONG, Long.class); + put(QType.LONG_LIST, long[].class); + put(QType.FLOAT, Float.class); + put(QType.FLOAT_LIST, float[].class); + put(QType.DOUBLE, Double.class); + put(QType.DOUBLE_LIST, double[].class); + put(QType.CHAR, Character.class); + put(QType.STRING, char[].class); + put(QType.SYMBOL, String.class); + put(QType.SYMBOL_LIST, String[].class); + put(QType.TIMESTAMP, QTimestamp.class); + put(QType.TIMESTAMP_LIST, QTimestamp[].class); + put(QType.MONTH, QMonth.class); + put(QType.MONTH_LIST, QMonth[].class); + put(QType.DATE, QDate.class); + put(QType.DATE_LIST, QDate[].class); + put(QType.DATETIME, QDateTime.class); + put(QType.DATETIME_LIST, QDateTime[].class); + put(QType.TIMESPAN, QTimespan.class); + put(QType.TIMESPAN_LIST, QTimespan[].class); + put(QType.MINUTE, QMinute.class); + put(QType.MINUTE_LIST, QMinute[].class); + put(QType.SECOND, QSecond.class); + put(QType.SECOND_LIST, QSecond[].class); + put(QType.TIME, QTime.class); + put(QType.TIME_LIST, QTime[].class); + put(QType.ERROR, QException.class); + put(QType.DICTIONARY, QDictionary.class); + put(QType.TABLE, QTable.class); + put(QType.KEYED_TABLE, QKeyedTable.class); + put(QType.LAMBDA, QLambda.class); + } + }); + + /** + * Returns default mapping for particular q type. + * + * @param type + * Requested q type + * @return type of the object being a result of q message deserialization + * @throws QReaderException + */ + public static Class getType( final QType type ) throws QReaderException { + if ( fromQ.containsKey(type) ) { + return fromQ.get(type); + } else { + throw new QReaderException("Cannot deserialize object of type: " + type); + } + } +} diff --git a/src/main/java/com/exxeleron/qjava/DefaultQWriter.java b/src/main/java/com/exxeleron/qjava/DefaultQWriter.java new file mode 100644 index 0000000..b13e7a8 --- /dev/null +++ b/src/main/java/com/exxeleron/qjava/DefaultQWriter.java @@ -0,0 +1,495 @@ +/** + * Copyright (c) 2011-2015 Exxeleron GmbH + * + * Licensed 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 com.exxeleron.qjava; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Default {@link QWriter} implementation. + */ +public class DefaultQWriter extends QWriter { + + /** + * @see com.exxeleron.qjava.QWriter#writeObject(java.lang.Object) + */ + protected void writeObject( final Object obj ) throws IOException, QException { + final QType qtype = getQType(obj); + + if ( qtype == QType.STRING ) { + writeString((char[]) obj); + } else if ( qtype == QType.GENERAL_LIST ) { + writeGeneralList((Object[]) obj); + } else if ( qtype == QType.NULL_ITEM ) { + writeNullItem(); + } else if ( qtype == QType.ERROR ) { + writeError((Exception) obj); + } else if ( qtype == QType.DICTIONARY ) { + writeDictionary((QDictionary) obj); + } else if ( qtype == QType.TABLE ) { + writeTable((QTable) obj); + } else if ( qtype == QType.KEYED_TABLE ) { + writeKeyedTable((QKeyedTable) obj); + } else if ( qtype.getTypeCode() < 0 ) { + writeAtom(obj, qtype); + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + writeList(obj, qtype); + } else if ( qtype == QType.LAMBDA ) { + writeLambda((QLambda) obj); + } else if ( qtype == QType.PROJECTION ) { + writeProjection((QProjection) obj); + } else { + throw new QWriterException("Unable to serialize q type: " + qtype); + } + } + + @SuppressWarnings("incomplete-switch") + protected void writeAtom( final Object obj, final QType qtype ) throws IOException, QException { + writer.writeByte(qtype.getTypeCode()); + switch ( qtype ) { + case BOOL: + writer.writeByte((byte) ((Boolean) obj ? 1 : 0)); + break; + case GUID: + if ( protocolVersion < 3 ) { + throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); + } + writeGuid((UUID) obj); + break; + case BYTE: + writer.writeByte((Byte) obj); + break; + case SHORT: + writer.writeShort((Short) obj); + break; + case INT: + writer.writeInt((Integer) obj); + break; + case LONG: + writer.writeLong((Long) obj); + break; + case FLOAT: + writer.writeFloat((Float) obj); + break; + case DOUBLE: + writer.writeDouble((Double) obj); + break; + case CHAR: + writer.writeByte((byte) (char) (Character) obj); + break; + case SYMBOL: + writeSymbol((String) obj); + break; + case TIMESTAMP: + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); + } + writer.writeLong(((QTimestamp) obj).getValue()); + break; + case MONTH: + writer.writeInt(((QMonth) obj).getValue()); + break; + case DATE: + writer.writeInt(((QDate) obj).getValue()); + break; + case DATETIME: + writer.writeDouble(((QDateTime) obj).getValue()); + break; + case TIMESPAN: + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); + } + writer.writeLong(((QTimespan) obj).getValue()); + break; + case MINUTE: + writer.writeInt(((QMinute) obj).getValue()); + break; + case SECOND: + writer.writeInt(((QSecond) obj).getValue()); + break; + case TIME: + writer.writeInt(((QTime) obj).getValue()); + break; + } + } + + @SuppressWarnings("incomplete-switch") + protected void writeList( final Object obj, final QType qtype ) throws IOException, QException { + writer.writeByte(qtype.getTypeCode()); + writer.writeByte((byte) 0); // attributes + + switch ( qtype ) { + case BOOL_LIST: { + if ( obj instanceof boolean[] ) { + final boolean[] list = (boolean[]) obj; + writer.writeInt(list.length); + for ( final boolean a : list ) { + writer.writeByte((byte) (a ? 1 : 0)); + } + } else if ( obj instanceof Boolean[] ) { + final Boolean[] list = (Boolean[]) obj; + writer.writeInt(list.length); + for ( final Boolean a : list ) { + writer.writeByte((byte) (a ? 1 : 0)); + } + } + break; + } + case GUID_LIST: { + if ( protocolVersion < 3 ) { + throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); + } + final UUID[] list = (UUID[]) obj; + writer.writeInt(list.length); + for ( final UUID a : list ) { + writeGuid(a); + } + break; + } + case BYTE_LIST: { + if ( obj instanceof byte[] ) { + final byte[] list = (byte[]) obj; + writer.writeInt(list.length); + for ( final byte a : list ) { + writer.writeByte(a); + } + } else if ( obj instanceof Byte[] ) { + final Byte[] list = (Byte[]) obj; + writer.writeInt(list.length); + for ( final Byte a : list ) { + writer.writeByte(a); + } + } + break; + } + case SHORT_LIST: { + if ( obj instanceof short[] ) { + final short[] list = (short[]) obj; + writer.writeInt(list.length); + for ( final short a : list ) { + writer.writeShort(a); + } + } else if ( obj instanceof Short[] ) { + final Short[] list = (Short[]) obj; + writer.writeInt(list.length); + for ( final Short a : list ) { + writer.writeShort(a); + } + } + break; + } + case INT_LIST: { + if ( obj instanceof int[] ) { + final int[] list = (int[]) obj; + writer.writeInt(list.length); + for ( final int a : list ) { + writer.writeInt(a); + } + } else if ( obj instanceof Integer[] ) { + final Integer[] list = (Integer[]) obj; + writer.writeInt(list.length); + for ( final Integer a : list ) { + writer.writeInt(a); + } + } + break; + } + case LONG_LIST: { + if ( obj instanceof long[] ) { + final long[] list = (long[]) obj; + writer.writeInt(list.length); + for ( final long a : list ) { + writer.writeLong(a); + } + } else if ( obj instanceof Long[] ) { + final Long[] list = (Long[]) obj; + writer.writeInt(list.length); + for ( final Long a : list ) { + writer.writeLong(a); + } + } + break; + } + case FLOAT_LIST: { + if ( obj instanceof float[] ) { + final float[] list = (float[]) obj; + writer.writeInt(list.length); + for ( final float a : list ) { + writer.writeFloat(a); + } + } else if ( obj instanceof Float[] ) { + final Float[] list = (Float[]) obj; + writer.writeInt(list.length); + for ( final Float a : list ) { + writer.writeFloat(a); + } + } + break; + } + case DOUBLE_LIST: { + if ( obj instanceof double[] ) { + final double[] list = (double[]) obj; + writer.writeInt(list.length); + for ( final double a : list ) { + writer.writeDouble(a); + } + } else if ( obj instanceof Double[] ) { + final Double[] list = (Double[]) obj; + writer.writeInt(list.length); + for ( final Double a : list ) { + writer.writeDouble(a); + } + } + break; + } + case SYMBOL_LIST: { + final String[] list = (String[]) obj; + writer.writeInt(list.length); + for ( final String a : list ) { + writeSymbol(a); + } + break; + } + case TIMESTAMP_LIST: { + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); + } + final QTimestamp[] list = (QTimestamp[]) obj; + writer.writeInt(list.length); + for ( final QTimestamp a : list ) { + writer.writeLong(a.getValue()); + } + break; + } + case MONTH_LIST: { + final QMonth[] list = (QMonth[]) obj; + writer.writeInt(list.length); + for ( final QMonth a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case DATE_LIST: { + final QDate[] list = (QDate[]) obj; + writer.writeInt(list.length); + for ( final QDate a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case DATETIME_LIST: { + final QDateTime[] list = (QDateTime[]) obj; + writer.writeInt(list.length); + for ( final QDateTime a : list ) { + writer.writeDouble(a.getValue()); + } + break; + } + case TIMESPAN_LIST: { + if ( protocolVersion < 1 ) { + throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); + } + final QTimespan[] list = (QTimespan[]) obj; + writer.writeInt(list.length); + for ( final QTimespan a : list ) { + writer.writeLong(a.getValue()); + } + break; + } + case MINUTE_LIST: { + final QMinute[] list = (QMinute[]) obj; + writer.writeInt(list.length); + for ( final QMinute a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case SECOND_LIST: { + final QSecond[] list = (QSecond[]) obj; + writer.writeInt(list.length); + for ( final QSecond a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + case TIME_LIST: { + final QTime[] list = (QTime[]) obj; + writer.writeInt(list.length); + for ( final QTime a : list ) { + writer.writeInt(a.getValue()); + } + break; + } + } + } + + protected void writeGeneralList( final Object[] list ) throws IOException, QException { + writer.writeByte(QType.GENERAL_LIST.getTypeCode()); + writer.writeByte((byte) 0); // attributes + writer.writeInt(list.length); + for ( final Object obj : list ) { + writeObject(obj); + } + } + + protected void writeSymbol( final String s ) throws IOException { + writer.write(s.getBytes(getEncoding())); + writer.writeByte((byte) 0); + } + + protected void writeGuid( final UUID obj ) throws QException { + if ( protocolVersion < 3 ) { + throw new QWriterException("kdb+ protocol version violation: Guid not supported pre kdb+ v3.0"); + } + + writer.writeLongBigEndian(obj.getMostSignificantBits()); + writer.writeLongBigEndian(obj.getLeastSignificantBits()); + } + + protected void writeString( final char[] s ) throws IOException { + writer.writeByte(QType.STRING.getTypeCode()); + writer.writeByte((byte) 0); // attributes + final byte[] encoded = String.valueOf(s).getBytes(getEncoding()); + writer.writeInt(encoded.length); + writer.write(encoded); + } + + protected void writeNullItem() { + writer.writeByte(QType.NULL_ITEM.getTypeCode()); + writer.writeByte((byte) 0); + } + + protected void writeError( final Exception e ) throws IOException { + writer.writeByte(QType.ERROR.getTypeCode()); + writeSymbol(e.getMessage()); + } + + protected void writeDictionary( final QDictionary d ) throws IOException, QException { + writer.writeByte(QType.DICTIONARY.getTypeCode()); + writeObject(d.getKeys()); + writeObject(d.getValues()); + } + + protected void writeTable( final QTable t ) throws IOException, QException { + writer.writeByte(QType.TABLE.getTypeCode()); + writer.writeByte((byte) 0); // attributes + writer.writeByte(QType.DICTIONARY.getTypeCode()); + writeObject(t.getColumns()); + writeObject(t.getData()); + } + + protected void writeKeyedTable( final QKeyedTable t ) throws IOException, QException { + writer.writeByte(QType.KEYED_TABLE.getTypeCode()); + writeObject(t.getKeys()); + writeObject(t.getValues()); + } + + protected void writeLambda( final QLambda l ) throws IOException { + writer.writeByte(QType.LAMBDA.getTypeCode()); + writer.writeByte((byte) 0); + writeString(l.getExpression().toCharArray()); + } + + protected void writeProjection( final QProjection p ) throws IOException, QException { + writer.writeByte(QType.PROJECTION.getTypeCode()); + final int length = p.getParameters().length; + writer.writeInt(length); + + for ( int i = 0; i < length; i++ ) { + writeObject(p.getParameters()[i]); + } + } + + @SuppressWarnings("rawtypes") + private static final Map toQ = Collections.unmodifiableMap(new HashMap() { + private static final long serialVersionUID = 7199217298785029447L; + + { + put(Object[].class, QType.GENERAL_LIST); + put(Boolean.class, QType.BOOL); + put(boolean[].class, QType.BOOL_LIST); + put(Boolean[].class, QType.BOOL_LIST); + put(Byte.class, QType.BYTE); + put(byte[].class, QType.BYTE_LIST); + put(Byte[].class, QType.BYTE_LIST); + put(UUID.class, QType.GUID); + put(UUID[].class, QType.GUID_LIST); + put(Short.class, QType.SHORT); + put(short[].class, QType.SHORT_LIST); + put(Short[].class, QType.SHORT_LIST); + put(Integer.class, QType.INT); + put(int[].class, QType.INT_LIST); + put(Integer[].class, QType.INT_LIST); + put(Long.class, QType.LONG); + put(long[].class, QType.LONG_LIST); + put(Long[].class, QType.LONG_LIST); + put(Float.class, QType.FLOAT); + put(float[].class, QType.FLOAT_LIST); + put(Float[].class, QType.FLOAT_LIST); + put(Double.class, QType.DOUBLE); + put(double[].class, QType.DOUBLE_LIST); + put(Double[].class, QType.DOUBLE_LIST); + put(Character.class, QType.CHAR); + put(char[].class, QType.STRING); + put(char[][].class, QType.GENERAL_LIST); + put(String.class, QType.SYMBOL); + put(String[].class, QType.SYMBOL_LIST); + put(QTimestamp.class, QType.TIMESTAMP); + put(QTimestamp[].class, QType.TIMESTAMP_LIST); + put(QMonth.class, QType.MONTH); + put(QMonth[].class, QType.MONTH_LIST); + put(QDate.class, QType.DATE); + put(QDate[].class, QType.DATE_LIST); + put(QDateTime.class, QType.DATETIME); + put(QDateTime[].class, QType.DATETIME_LIST); + put(QTimespan.class, QType.TIMESPAN); + put(QTimespan[].class, QType.TIMESPAN_LIST); + put(QMinute.class, QType.MINUTE); + put(QMinute[].class, QType.MINUTE_LIST); + put(QSecond.class, QType.SECOND); + put(QSecond[].class, QType.SECOND_LIST); + put(QTime.class, QType.TIME); + put(QTime[].class, QType.TIME_LIST); + put(QException.class, QType.ERROR); + put(QDictionary.class, QType.DICTIONARY); + put(QTable.class, QType.TABLE); + put(QKeyedTable.class, QType.KEYED_TABLE); + put(QLambda.class, QType.LAMBDA); + put(QProjection.class, QType.PROJECTION); + } + }); + + /** + * Returns default mapping for particular java object to representative q type. + * + * @param obj + * Requested object + * @return {@link QType} enum being a result of q serialization + * @throws QWriterException + */ + public static QType getQType( final Object obj ) throws QWriterException { + if ( obj == null ) { + return QType.NULL_ITEM; + } else if ( toQ.containsKey(obj.getClass()) ) { + return toQ.get(obj.getClass()); + } else { + throw new QWriterException("Cannot serialize object of type: " + obj.getClass().getCanonicalName()); + } + } +} diff --git a/src/main/java/com/exxeleron/qjava/QBasicConnection.java b/src/main/java/com/exxeleron/qjava/QBasicConnection.java index 9e56b33..a5e74fc 100644 --- a/src/main/java/com/exxeleron/qjava/QBasicConnection.java +++ b/src/main/java/com/exxeleron/qjava/QBasicConnection.java @@ -27,6 +27,8 @@ */ public class QBasicConnection implements QConnection { + public static final String DEFAULT_ENCODING = "ISO-8859-1"; + private final String host; private final int port; private final String username; @@ -61,6 +63,9 @@ public QBasicConnection(final String host, final int port, final String username this.username = username; this.password = password; this.encoding = encoding; + + this.reader = new DefaultQReader(); + this.writer = new DefaultQWriter(); } /** @@ -76,7 +81,7 @@ public QBasicConnection(final String host, final int port, final String username * Password for remote authorization */ public QBasicConnection(final String host, final int port, final String username, final String password) { - this(host, port, username, password, "ISO-8859-1"); + this(host, port, username, password, DEFAULT_ENCODING); } /** @@ -88,8 +93,12 @@ public void open() throws IOException, QException { initSocket(); initialize(); - reader = new QReader(inputStream, encoding); - writer = new QWriter(outputStream, encoding, protocolVersion); + reader.setStream(inputStream); + reader.setEncoding(encoding); + + writer.setStream(outputStream); + writer.setEncoding(encoding); + writer.setProtocolVersion(protocolVersion); } else { throw new QConnectionException("Host cannot be null"); } @@ -268,4 +277,42 @@ public int getProtocolVersion() { return protocolVersion; } + /** + * Retrieves {@link QReader} for IPC stream deserializing. + * + * @return {@link QReader} used for deserialization + */ + public QReader getReader() { + return reader; + } + + /** + * Sets the {@link QReader} for IPC stream deserialization + * + * @param reader + * the {@link QReader} + */ + public void setReader( QReader reader ) { + this.reader = reader; + } + + /** + * Retrieves {@link QWriter} used for serialization into IPC stream. + * + * @return {@link QWriter} used for serialization + */ + public QWriter getWriter() { + return writer; + } + + /** + * Sets the {@link QWriter} used for serialization into IPC stream. + * + * @param writer + * the {@link QWriter} + */ + public void setWriter( QWriter writer ) { + this.writer = writer; + } + } \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/QConnection.java b/src/main/java/com/exxeleron/qjava/QConnection.java index 10e165b..e5af663 100644 --- a/src/main/java/com/exxeleron/qjava/QConnection.java +++ b/src/main/java/com/exxeleron/qjava/QConnection.java @@ -192,7 +192,7 @@ public static MessageType getMessageType( final byte i ) { public String getEncoding(); /** - * Retrives version of the IPC protocol for an established connection. + * Retrieves version of the IPC protocol for an established connection. * * @return protocol version */ diff --git a/src/main/java/com/exxeleron/qjava/QReader.java b/src/main/java/com/exxeleron/qjava/QReader.java index f62a8f1..3c39830 100644 --- a/src/main/java/com/exxeleron/qjava/QReader.java +++ b/src/main/java/com/exxeleron/qjava/QReader.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2014 Exxeleron GmbH + * Copyright (c) 2011-2015 Exxeleron GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,39 +19,54 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.UUID; /** - * Provides deserialization from q IPC protocol.
- * + * Provides deserialization from q IPC protocol. + *

* Methods of {@link QReader} are not thread safe. + *

*/ -public final class QReader { +public abstract class QReader { private final static String PROTOCOL_DEBUG_ENV = "QJAVA_PROTOCOL_DEBUG"; - private final DataInputStream stream; - private final String encoding; - private final ByteInputStream reader; + protected DataInputStream stream; + protected ByteInputStream reader; + private String encoding; - private byte[] header; - private byte[] rawData; + protected byte[] header; + protected byte[] rawData; /** - * Initializes a new {@link QReader} instance. - * - * @param inputStream + * Sets the input stream for deserialization. + * + * @param stream * Input stream containing serialized messages + */ + void setStream( final DataInputStream stream ) { + this.stream = stream; + } + + /** + * Sets the string encoding for deserialization. + * * @param encoding * Encoding used for deserialization of string data */ - public QReader(final DataInputStream inputStream, final String encoding) { - stream = inputStream; + void setEncoding( final String encoding ) { this.encoding = encoding; - reader = new ByteInputStream(ByteOrder.nativeOrder()); + reader = new ByteInputStream(encoding, ByteOrder.nativeOrder()); + } + + /** + * Retrieves string encoding + * + * @return charset name + */ + protected String getEncoding() { + return encoding; } /** @@ -69,12 +84,13 @@ public QMessage read( final boolean raw ) throws IOException, QException { stream.readFully(header, 0, 8); reader.wrap(header); + // TODO: create 2 instances per each endian and select one based on a flag final ByteOrder endianess = reader.get() == 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; final QConnection.MessageType messageType = QConnection.MessageType.getMessageType(reader.get()); final boolean compressed = reader.get() == 1; reader.get(); // skip 1 byte - reader.order(endianess); + reader.setOrder(endianess); final int messageSize = reader.getInt(); int dataSize = Math.max(messageSize - 8, 0); @@ -93,7 +109,7 @@ public QMessage read( final boolean raw ) throws IOException, QException { } reader.wrap(data); - reader.order(endianess); + reader.setOrder(endianess); try { return new QMessage(readObject(), messageType, endianess, compressed, raw, messageSize, dataSize); @@ -106,7 +122,13 @@ public QMessage read( final boolean raw ) throws IOException, QException { } } - private void protocolDebug( final Exception e ) { + /** + * Conditionally dumps IPC stream to file in case of exception while parsing. + * + * @param e + * thrown exception + */ + protected void protocolDebug( final Exception e ) { if ( System.getenv().containsKey(PROTOCOL_DEBUG_ENV) ) { final String debugPath = System.getenv(PROTOCOL_DEBUG_ENV) + File.separator + PROTOCOL_DEBUG_ENV + "." + System.currentTimeMillis(); PrintWriter out = null; @@ -131,7 +153,18 @@ private void protocolDebug( final Exception e ) { } } - private byte[] uncompress( final byte[] compressedData, final ByteOrder endianess ) throws QException { + /** + * Uncompresses the IPC stream. + * + * @param compressedData + * compressed data + * @param endianess + * endianess of the stream + * @return uncompressed stream + * @throws QException + * in case of uncompression error + */ + protected byte[] uncompress( final byte[] compressedData, final ByteOrder endianess ) throws QException { // size of the uncompressed message is encoded on first 4 bytes // size has to be decreased by header length (8 bytes) final ByteBuffer byteBuffer = ByteBuffer.wrap(compressedData, 0, 4); @@ -178,347 +211,14 @@ private byte[] uncompress( final byte[] compressedData, final ByteOrder endianes return uncompressed; } - private Object readObject() throws QException, IOException { - final QType qtype = QType.getQType(reader.get()); - - if ( qtype == QType.GENERAL_LIST ) { - return readGeneralList(); - } else if ( qtype == QType.ERROR ) { - throw readError(); - } else if ( qtype == QType.DICTIONARY ) { - return readDictionary(); - } else if ( qtype == QType.TABLE ) { - return readTable(); - } else if ( qtype.getTypeCode() < 0 ) { - return readAtom(qtype); - } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { - return readList(qtype); - } else if ( qtype.getTypeCode() >= QType.LAMBDA.getTypeCode() ) { - return readFunction(qtype); - } - - throw new QReaderException("Unable to deserialize q type: " + qtype); - } - - @SuppressWarnings("incomplete-switch") - private Object readAtom( final QType qtype ) throws QException, UnsupportedEncodingException { - switch ( qtype ) { - case BOOL: - return reader.get() == 1 ? true : false; - case GUID: - return readGuid(); - case BYTE: - return reader.get(); - case SHORT: - return reader.getShort(); - case INT: - return reader.getInt(); - case LONG: - return reader.getLong(); - case FLOAT: - return reader.getFloat(); - case DOUBLE: - return reader.getDouble(); - case CHAR: - return (char) reader.get(); - case SYMBOL: - return reader.getSymbol(); - case TIMESTAMP: - return new QTimestamp(reader.getLong()); - case MONTH: - return new QMonth(reader.getInt()); - case DATE: - return new QDate(reader.getInt()); - case DATETIME: - return new QDateTime(reader.getDouble()); - case TIMESPAN: - return new QTimespan(reader.getLong()); - case MINUTE: - return new QMinute(reader.getInt()); - case SECOND: - return new QSecond(reader.getInt()); - case TIME: - return new QTime(reader.getInt()); - } - - throw new QReaderException("Unable to deserialize q type: " + qtype); - } - - @SuppressWarnings("incomplete-switch") - private Object readList( final QType qtype ) throws QException, UnsupportedEncodingException { - reader.get(); // ignore attributes - final int length = reader.getInt(); - - switch ( qtype ) { - case BOOL_LIST: { - final boolean[] list = new boolean[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.get() == 1 ? true : false; - } - return list; - } - - case GUID_LIST: { - final UUID[] list = new UUID[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = readGuid(); - } - return list; - } - - case BYTE_LIST: { - final byte[] list = new byte[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.get(); - } - return list; - } - case SHORT_LIST: { - final short[] list = new short[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getShort(); - } - return list; - } - case INT_LIST: { - final int[] list = new int[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getInt(); - } - return list; - } - case LONG_LIST: { - final long[] list = new long[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getLong(); - } - return list; - } - case FLOAT_LIST: { - final float[] list = new float[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getFloat(); - } - return list; - } - case DOUBLE_LIST: { - final double[] list = new double[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getDouble(); - } - return list; - } - case STRING: { - final byte[] buffer = new byte[length]; - reader.get(buffer, 0, length); - return new String(buffer, encoding).toCharArray(); - } - case SYMBOL_LIST: { - final String[] list = new String[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = reader.getSymbol(); - } - return list; - } - case TIMESTAMP_LIST: { - final QTimestamp[] list = new QTimestamp[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QTimestamp(reader.getLong()); - } - return list; - } - case MONTH_LIST: { - final QMonth[] list = new QMonth[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QMonth(reader.getInt()); - } - return list; - } - case DATE_LIST: { - final QDate[] list = new QDate[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QDate(reader.getInt()); - } - return list; - } - case DATETIME_LIST: { - final QDateTime[] list = new QDateTime[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QDateTime(reader.getDouble()); - } - return list; - } - case TIMESPAN_LIST: { - final QTimespan[] list = new QTimespan[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QTimespan(reader.getLong()); - } - return list; - } - case MINUTE_LIST: { - final QMinute[] list = new QMinute[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QMinute(reader.getInt()); - } - return list; - } - case SECOND_LIST: { - final QSecond[] list = new QSecond[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QSecond(reader.getInt()); - } - return list; - } - case TIME_LIST: { - final QTime[] list = new QTime[length]; - for ( int i = 0; i < length; i++ ) { - list[i] = new QTime(reader.getInt()); - } - return list; - } - } - - throw new QReaderException("Unable to deserialize q type: " + qtype); - } - - private UUID readGuid() { - final ByteOrder currentOrder = reader.order(); - reader.order(ByteOrder.BIG_ENDIAN); - final long l1 = reader.getLong(); - final long l2 = reader.getLong(); - reader.order(currentOrder); - return new UUID(l1, l2); - } - - private Object[] readGeneralList() throws QException, IOException { - reader.get(); // ignore attributes - final int length = reader.getInt(); - final Object[] list = new Object[length]; - - for ( int i = 0; i < length; i++ ) { - list[i] = readObject(); - } - - return list; - } - - private QException readError() throws IOException { - return new QException(reader.getSymbol()); - } - - private Object readDictionary() throws QException, IOException { - final Object keys = readObject(); - final Object values = readObject(); - - if ( keys != null && keys.getClass().isArray() && (values != null && values.getClass().isArray() || values instanceof QTable) ) { - return new QDictionary(keys, values); - } else if ( keys instanceof QTable && values instanceof QTable ) { - return new QKeyedTable((QTable) keys, (QTable) values); - } - - throw new QReaderException("Cannot create valid dictionary object from mapping: " + keys + " to " + values); - } - - private QTable readTable() throws QException, IOException { - reader.get(); // attributes - reader.get(); // dict type stamp - return new QTable((String[]) readObject(), (Object[]) readObject()); - } - - private QFunction readFunction( final QType qtype ) throws QException, IOException { - if ( qtype == QType.LAMBDA ) { - reader.getSymbol(); // ignore context - final String expression = new String((char[]) readObject()); - return new QLambda(expression); - } else if ( qtype == QType.PROJECTION ) { - final int length = reader.getInt(); - final Object[] parameters = new Object[length]; - for ( int i = 0; i < length; i++ ) { - parameters[i] = readObject(); - } - return new QProjection(parameters); - } else if ( qtype == QType.UNARY_PRIMITIVE_FUNC ) { - final byte code = reader.get(); - return code == 0 ? null : new QFunction(qtype.getTypeCode()); - } else if ( qtype.getTypeCode() < QType.PROJECTION.getTypeCode() ) { - reader.get(); // ignore function code - return new QFunction(qtype.getTypeCode()); - } else if ( qtype == QType.COMPOSITION_FUNC ) { - final int length = reader.getInt(); - final Object[] parameters = new Object[length]; - for ( int i = 0; i < length; i++ ) { - parameters[i] = readObject(); - } - return new QFunction(qtype.getTypeCode()); - } else { - readObject(); // ignore function object - return new QFunction(qtype.getTypeCode()); - } - } - - private final class ByteInputStream { - - private byte[] buffer; - private ByteOrder endianess; - private int position; - - public ByteInputStream(final ByteOrder endianess) { - this.endianess = endianess; - } - - protected void wrap( final byte[] newBuffer ) { - buffer = newBuffer; - position = 0; - } - - public void get( final byte[] dest, final int start, final int length ) { - System.arraycopy(buffer, position, dest, start, length); - position += length; - } - - public byte get() { - return buffer[position++]; - } - - public short getShort() { - final int x = buffer[position++], y = buffer[position++]; - return (short) (endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xff | y << 8 : x << 8 | y & 0xff); - } - - public int getInt() { - final int x = getShort(), y = getShort(); - return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffff | y << 16 : x << 16 | y & 0xffff; - } - - public long getLong() { - final int x = getInt(), y = getInt(); - return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffffffffL | (long) y << 32 : (long) x << 32 | y & 0xffffffffL; - } - - public float getFloat() { - return Float.intBitsToFloat(getInt()); - } - - public Double getDouble() { - return Double.longBitsToDouble(getLong()); - } - - public ByteOrder order() { - return endianess; - } - - public void order( @SuppressWarnings("hiding") final ByteOrder endianess ) { - this.endianess = endianess; - } - - private String getSymbol() throws UnsupportedEncodingException { - final int p = position; - - for ( ; buffer[position++] != 0; ) { - // empty; - } - return (p == position - 1) ? "" : new String(buffer, p, position - 1 - p, encoding); - } - } - + /** + * Parses a Java object from the IPC stream. + * + * @return parsed representation + * @throws QException + * in case of parsing error + * @throws IOException + * in case of IO error + */ + protected abstract Object readObject() throws QException, IOException; } diff --git a/src/main/java/com/exxeleron/qjava/QType.java b/src/main/java/com/exxeleron/qjava/QType.java index e7c5470..9110309 100644 --- a/src/main/java/com/exxeleron/qjava/QType.java +++ b/src/main/java/com/exxeleron/qjava/QType.java @@ -92,115 +92,6 @@ byte getTypeCode() { return code; } - @SuppressWarnings("rawtypes") - private static final Map toQ = Collections.unmodifiableMap(new HashMap() { - private static final long serialVersionUID = 7199217298785029447L; - - { - put(Object[].class, GENERAL_LIST); - put(Boolean.class, BOOL); - put(boolean[].class, BOOL_LIST); - put(Boolean[].class, BOOL_LIST); - put(Byte.class, BYTE); - put(byte[].class, BYTE_LIST); - put(Byte[].class, BYTE_LIST); - put(UUID.class, GUID); - put(UUID[].class, GUID_LIST); - put(Short.class, SHORT); - put(short[].class, SHORT_LIST); - put(Short[].class, SHORT_LIST); - put(Integer.class, INT); - put(int[].class, INT_LIST); - put(Integer[].class, INT_LIST); - put(Long.class, LONG); - put(long[].class, LONG_LIST); - put(Long[].class, LONG_LIST); - put(Float.class, FLOAT); - put(float[].class, FLOAT_LIST); - put(Float[].class, FLOAT_LIST); - put(Double.class, DOUBLE); - put(double[].class, DOUBLE_LIST); - put(Double[].class, DOUBLE_LIST); - put(Character.class, CHAR); - put(char[].class, STRING); - put(char[][].class, GENERAL_LIST); - put(String.class, SYMBOL); - put(String[].class, SYMBOL_LIST); - put(QTimestamp.class, TIMESTAMP); - put(QTimestamp[].class, TIMESTAMP_LIST); - put(QMonth.class, MONTH); - put(QMonth[].class, MONTH_LIST); - put(QDate.class, DATE); - put(QDate[].class, DATE_LIST); - put(QDateTime.class, DATETIME); - put(QDateTime[].class, DATETIME_LIST); - put(QTimespan.class, TIMESPAN); - put(QTimespan[].class, TIMESPAN_LIST); - put(QMinute.class, MINUTE); - put(QMinute[].class, MINUTE_LIST); - put(QSecond.class, SECOND); - put(QSecond[].class, SECOND_LIST); - put(QTime.class, TIME); - put(QTime[].class, TIME_LIST); - put(QException.class, ERROR); - put(QDictionary.class, DICTIONARY); - put(QTable.class, TABLE); - put(QKeyedTable.class, KEYED_TABLE); - put(QLambda.class, LAMBDA); - put(QProjection.class, PROJECTION); - } - }); - - @SuppressWarnings("rawtypes") - private static final Map fromQ = Collections.unmodifiableMap(new HashMap() { - private static final long serialVersionUID = 7199217298785029445L; - - { - put(GENERAL_LIST, Object[].class); - put(BOOL, Boolean.class); - put(BOOL_LIST, boolean[].class); - put(BYTE, Byte.class); - put(BYTE_LIST, byte[].class); - put(GUID, UUID.class); - put(GUID_LIST, UUID[].class); - put(SHORT, Short.class); - put(SHORT_LIST, short[].class); - put(INT, Integer.class); - put(INT_LIST, int[].class); - put(LONG, Long.class); - put(LONG_LIST, long[].class); - put(FLOAT, Float.class); - put(FLOAT_LIST, float[].class); - put(DOUBLE, Double.class); - put(DOUBLE_LIST, double[].class); - put(CHAR, Character.class); - put(STRING, char[].class); - put(SYMBOL, String.class); - put(SYMBOL_LIST, String[].class); - put(TIMESTAMP, QTimestamp.class); - put(TIMESTAMP_LIST, QTimestamp[].class); - put(MONTH, QMonth.class); - put(MONTH_LIST, QMonth[].class); - put(DATE, QDate.class); - put(DATE_LIST, QDate[].class); - put(DATETIME, QDateTime.class); - put(DATETIME_LIST, QDateTime[].class); - put(TIMESPAN, QTimespan.class); - put(TIMESPAN_LIST, QTimespan[].class); - put(MINUTE, QMinute.class); - put(MINUTE_LIST, QMinute[].class); - put(SECOND, QSecond.class); - put(SECOND_LIST, QSecond[].class); - put(TIME, QTime.class); - put(TIME_LIST, QTime[].class); - put(ERROR, QException.class); - put(DICTIONARY, QDictionary.class); - put(TABLE, QTable.class); - put(KEYED_TABLE, QKeyedTable.class); - put(LAMBDA, QLambda.class); - } - }); - private static final Map qNulls = Collections.unmodifiableMap(new HashMap() { private static final long serialVersionUID = 7199217298785029443L; @@ -253,6 +144,22 @@ public static QType getQType( final Byte typecode ) throws QReaderException { } } + /** + * Returns object representing q null of particular type. + * + * @param type + * Requested null type + * @return object representing q null + * @throws QException + */ + public static Object getQNull( final QType type ) throws QException { + if ( qNulls.containsKey(type) ) { + return qNulls.get(type); + } else { + throw new QException("Cannot find null value of type: " + type); + } + } + /** * Returns default mapping for particular java object to representative q type. * @@ -260,46 +167,26 @@ public static QType getQType( final Byte typecode ) throws QReaderException { * Requested object * @return {@link QType} enum being a result of q serialization * @throws QWriterException + * + * @deprecated As of release 2.3, incompatible with configurable serialization */ + @Deprecated public static QType getQType( final Object obj ) throws QWriterException { - if ( obj == null ) { - return QType.NULL_ITEM; - } else if ( toQ.containsKey(obj.getClass()) ) { - return toQ.get(obj.getClass()); - } else { - throw new QWriterException("Cannot serialize object of type: " + obj.getClass().getCanonicalName()); - } + return DefaultQWriter.getQType(obj); } /** * Returns default mapping for particular q type. - * + * * @param type * Requested q type * @return type of the object being a result of q message deserialization * @throws QReaderException + * + * @deprecated As of release 2.3, incompatible with configurable serialization */ + @Deprecated public static Class getType( final QType type ) throws QReaderException { - if ( fromQ.containsKey(type) ) { - return fromQ.get(type); - } else { - throw new QReaderException("Cannot deserialize object of type: " + type); - } - } - - /** - * Returns object representing q null of particular type. - * - * @param type - * Requested null type - * @return object representing q null - * @throws QException - */ - public static Object getQNull( final QType type ) throws QException { - if ( qNulls.containsKey(type) ) { - return qNulls.get(type); - } else { - throw new QException("Cannot find null value of type: " + type); - } + return DefaultQReader.getType(type); } } diff --git a/src/main/java/com/exxeleron/qjava/QWriter.java b/src/main/java/com/exxeleron/qjava/QWriter.java index 542388f..df2aba7 100644 --- a/src/main/java/com/exxeleron/qjava/QWriter.java +++ b/src/main/java/com/exxeleron/qjava/QWriter.java @@ -1,12 +1,12 @@ /** * Copyright (c) 2011-2014 Exxeleron GmbH - * + * * Licensed 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. @@ -17,41 +17,74 @@ import java.io.IOException; import java.io.OutputStream; -import java.util.UUID; /** - * Provides serialization to q IPC protocol.
- * + * Provides serialization to q IPC protocol. + *

* Methods of {@link QWriter} are not thread safe. + *

*/ -public final class QWriter { +public abstract class QWriter { - private final OutputStream stream; - private ByteOutputStream writer; - private final String encoding; + protected OutputStream stream; + private String encoding; + protected ByteOutputStream writer = new ByteOutputStream(); + protected ByteOutputStream header = new ByteOutputStream(8); - private int messageSize; - private final int protocolVersion; + protected int messageSize; + protected int protocolVersion = 3; /** - * Initializes a new {@link QWriter} instance. - * + * Sets the output stream for serialization. + * * @param outputStream * Output stream for serialized messages + */ + void setStream( final OutputStream stream ) { + this.stream = stream; + } + + /** + * Sets encoding for string data serialization. + * * @param encoding * Encoding used for serialization of string data + */ + void setEncoding( final String encoding ) { + this.encoding = encoding; + } + + /** + * Retrieves string encoding + * + * @return charset name + */ + protected String getEncoding() { + return encoding; + } + + /** + * Set the kdb+ protocol version. + * * @param protocolVersion * kdb+ protocol version */ - public QWriter(final OutputStream outputStream, final String encoding, int protocolVersion) { - this.stream = outputStream; - this.encoding = encoding; + public void setProtocolVersion( final int protocolVersion ) { this.protocolVersion = protocolVersion; } + /** + * Retrieves kdb+ protocol version. + * + * @return {@link int} kdb+ protocol version + */ + public int getProtocolVersion() { + return protocolVersion; + } + /** * Serializes object to q IPC protocol and writes as a message to the output stream. - * + * * @param obj * Object to be serialized * @param msgType @@ -62,559 +95,34 @@ public QWriter(final OutputStream outputStream, final String encoding, int proto */ public int write( final Object obj, final QConnection.MessageType msgType ) throws IOException, QException { // serialize object - writer = new ByteOutputStream(calculateDataSize(obj)); + writer.reset(); writeObject(obj); - messageSize = writer.size() + 8; + messageSize = writer.count() + 8; // write header - @SuppressWarnings("resource") - final ByteOutputStream header = new ByteOutputStream(8); + header.reset(); header.write((byte) 1); // endianness header.write((byte) msgType.ordinal()); header.writeShort((short) 0); header.writeInt(messageSize); // write message - stream.write(header.buffer, 0, 8); - stream.write(writer.buffer, 0, writer.count); + stream.write(header.buffer(), 0, 8); + stream.write(writer.buffer(), 0, writer.count()); return messageSize; } - private static int[] atomSize = { 0, 1, 16, 0, 1, 2, 4, 8, 4, 8, 1, 0, 8, 4, 4, 8, 8, 4, 4, 4 }; - /** - * Calculates approximation of the object size + * Serializes object into an IPC stream. + * + * @param obj + * object to be serialized + * @throws IOException + * in case of IO error + * @throws QException + * in case object cannot be serialized */ - private int calculateDataSize( final Object obj ) throws QException { - final QType qtype = QType.getQType(obj); - if ( qtype == QType.KEYED_TABLE ) { - return 1 + calculateDataSize(((QKeyedTable) obj).getKeys()) + calculateDataSize(((QKeyedTable) obj).getValues()); - } else if ( qtype == QType.TABLE ) { - return 3 + calculateDataSize(((QTable) obj).getColumns()) + calculateDataSize(((QTable) obj).getData()); - } else if ( qtype == QType.DICTIONARY ) { - return 1 + calculateDataSize(((QDictionary) obj).getKeys()) + calculateDataSize(((QDictionary) obj).getValues()); - } else if ( qtype == QType.SYMBOL ) { - // approximation - actual string encoding is an expensive operation - return 2 + 2 * ((String) obj).length(); - } else if ( qtype.getTypeCode() <= QType.BOOL.getTypeCode() && qtype.getTypeCode() >= QType.TIME.getTypeCode() ) { - return 1 + atomSize[-qtype.getTypeCode()]; - } else if ( qtype == QType.GENERAL_LIST ) { - final Object[] list = (Object[]) obj; - int size = 6; - for ( final Object object : list ) { - size += calculateDataSize(object); - } - return size; - } else if ( qtype == QType.SYMBOL_LIST ) { - final String[] list = (String[]) obj; - int size = 6; - for ( final String object : list ) { - // approximation - actual string encoding is an expensive - // operation - size += 1 + 2 * object.length(); - } - return size; - } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { - return 6 + atomSize[qtype.getTypeCode()] * Array.getLength(obj); - } - - return ByteOutputStream.BUFFER_SIZE; - } - - private void writeObject( final Object obj ) throws IOException, QException { - final QType qtype = QType.getQType(obj); - - if ( qtype == QType.STRING ) { - writeString((char[]) obj); - } else if ( qtype == QType.GENERAL_LIST ) { - writeGeneralList((Object[]) obj); - } else if ( qtype == QType.NULL_ITEM ) { - writeNullItem(); - } else if ( qtype == QType.ERROR ) { - writeError((Exception) obj); - } else if ( qtype == QType.DICTIONARY ) { - writeDictionary((QDictionary) obj); - } else if ( qtype == QType.TABLE ) { - writeTable((QTable) obj); - } else if ( qtype == QType.KEYED_TABLE ) { - writeKeyedTable((QKeyedTable) obj); - } else if ( qtype.getTypeCode() < 0 ) { - writeAtom(obj, qtype); - } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { - writeList(obj, qtype); - } else if ( qtype == QType.LAMBDA ) { - writeLambda((QLambda) obj); - } else if ( qtype == QType.PROJECTION ) { - writeProjection((QProjection) obj); - } else { - throw new QWriterException("Unable to serialize q type: " + qtype); - } - } - - @SuppressWarnings("incomplete-switch") - private void writeAtom( final Object obj, final QType qtype ) throws IOException, QException { - writer.writeByte(qtype.getTypeCode()); - switch ( qtype ) { - case BOOL: - writer.writeByte((byte) ((Boolean) obj ? 1 : 0)); - break; - case GUID: - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); - } - writeGuid((UUID) obj); - break; - case BYTE: - writer.writeByte((Byte) obj); - break; - case SHORT: - writer.writeShort((Short) obj); - break; - case INT: - writer.writeInt((Integer) obj); - break; - case LONG: - writer.writeLong((Long) obj); - break; - case FLOAT: - writer.writeFloat((Float) obj); - break; - case DOUBLE: - writer.writeDouble((Double) obj); - break; - case CHAR: - writer.writeByte((byte) (char) (Character) obj); - break; - case SYMBOL: - writeSymbol((String) obj); - break; - case TIMESTAMP: - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); - } - writer.writeLong(((QTimestamp) obj).getValue()); - break; - case MONTH: - writer.writeInt(((QMonth) obj).getValue()); - break; - case DATE: - writer.writeInt(((QDate) obj).getValue()); - break; - case DATETIME: - writer.writeDouble(((QDateTime) obj).getValue()); - break; - case TIMESPAN: - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); - } - writer.writeLong(((QTimespan) obj).getValue()); - break; - case MINUTE: - writer.writeInt(((QMinute) obj).getValue()); - break; - case SECOND: - writer.writeInt(((QSecond) obj).getValue()); - break; - case TIME: - writer.writeInt(((QTime) obj).getValue()); - break; - } - } - - @SuppressWarnings("incomplete-switch") - private void writeList( final Object obj, final QType qtype ) throws IOException, QException { - writer.writeByte(qtype.getTypeCode()); - writer.writeByte((byte) 0); // attributes - - switch ( qtype ) { - case BOOL_LIST: { - if ( obj instanceof boolean[] ) { - final boolean[] list = (boolean[]) obj; - writer.writeInt(list.length); - for ( final boolean a : list ) { - writer.writeByte((byte) (a ? 1 : 0)); - } - } else if ( obj instanceof Boolean[] ) { - final Boolean[] list = (Boolean[]) obj; - writer.writeInt(list.length); - for ( final Boolean a : list ) { - writer.writeByte((byte) (a ? 1 : 0)); - } - } - break; - } - case GUID_LIST: { - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); - } - final UUID[] list = (UUID[]) obj; - writer.writeInt(list.length); - for ( final UUID a : list ) { - writeGuid(a); - } - break; - } - case BYTE_LIST: { - if ( obj instanceof byte[] ) { - final byte[] list = (byte[]) obj; - writer.writeInt(list.length); - for ( final byte a : list ) { - writer.writeByte(a); - } - } else if ( obj instanceof Byte[] ) { - final Byte[] list = (Byte[]) obj; - writer.writeInt(list.length); - for ( final Byte a : list ) { - writer.writeByte(a); - } - } - break; - } - case SHORT_LIST: { - if ( obj instanceof short[] ) { - final short[] list = (short[]) obj; - writer.writeInt(list.length); - for ( final short a : list ) { - writer.writeShort(a); - } - } else if ( obj instanceof Short[] ) { - final Short[] list = (Short[]) obj; - writer.writeInt(list.length); - for ( final Short a : list ) { - writer.writeShort(a); - } - } - break; - } - case INT_LIST: { - if ( obj instanceof int[] ) { - final int[] list = (int[]) obj; - writer.writeInt(list.length); - for ( final int a : list ) { - writer.writeInt(a); - } - } else if ( obj instanceof Integer[] ) { - final Integer[] list = (Integer[]) obj; - writer.writeInt(list.length); - for ( final Integer a : list ) { - writer.writeInt(a); - } - } - break; - } - case LONG_LIST: { - if ( obj instanceof long[] ) { - final long[] list = (long[]) obj; - writer.writeInt(list.length); - for ( final long a : list ) { - writer.writeLong(a); - } - } else if ( obj instanceof Long[] ) { - final Long[] list = (Long[]) obj; - writer.writeInt(list.length); - for ( final Long a : list ) { - writer.writeLong(a); - } - } - break; - } - case FLOAT_LIST: { - if ( obj instanceof float[] ) { - final float[] list = (float[]) obj; - writer.writeInt(list.length); - for ( final float a : list ) { - writer.writeFloat(a); - } - } else if ( obj instanceof Float[] ) { - final Float[] list = (Float[]) obj; - writer.writeInt(list.length); - for ( final Float a : list ) { - writer.writeFloat(a); - } - } - break; - } - case DOUBLE_LIST: { - if ( obj instanceof double[] ) { - final double[] list = (double[]) obj; - writer.writeInt(list.length); - for ( final double a : list ) { - writer.writeDouble(a); - } - } else if ( obj instanceof Double[] ) { - final Double[] list = (Double[]) obj; - writer.writeInt(list.length); - for ( final Double a : list ) { - writer.writeDouble(a); - } - } - break; - } - case SYMBOL_LIST: { - final String[] list = (String[]) obj; - writer.writeInt(list.length); - for ( final String a : list ) { - writeSymbol(a); - } - break; - } - case TIMESTAMP_LIST: { - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); - } - final QTimestamp[] list = (QTimestamp[]) obj; - writer.writeInt(list.length); - for ( final QTimestamp a : list ) { - writer.writeLong(a.getValue()); - } - break; - } - case MONTH_LIST: { - final QMonth[] list = (QMonth[]) obj; - writer.writeInt(list.length); - for ( final QMonth a : list ) { - writer.writeInt(a.getValue()); - } - break; - } - case DATE_LIST: { - final QDate[] list = (QDate[]) obj; - writer.writeInt(list.length); - for ( final QDate a : list ) { - writer.writeInt(a.getValue()); - } - break; - } - case DATETIME_LIST: { - final QDateTime[] list = (QDateTime[]) obj; - writer.writeInt(list.length); - for ( final QDateTime a : list ) { - writer.writeDouble(a.getValue()); - } - break; - } - case TIMESPAN_LIST: { - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); - } - final QTimespan[] list = (QTimespan[]) obj; - writer.writeInt(list.length); - for ( final QTimespan a : list ) { - writer.writeLong(a.getValue()); - } - break; - } - case MINUTE_LIST: { - final QMinute[] list = (QMinute[]) obj; - writer.writeInt(list.length); - for ( final QMinute a : list ) { - writer.writeInt(a.getValue()); - } - break; - } - case SECOND_LIST: { - final QSecond[] list = (QSecond[]) obj; - writer.writeInt(list.length); - for ( final QSecond a : list ) { - writer.writeInt(a.getValue()); - } - break; - } - case TIME_LIST: { - final QTime[] list = (QTime[]) obj; - writer.writeInt(list.length); - for ( final QTime a : list ) { - writer.writeInt(a.getValue()); - } - break; - } - } - } - - private void writeGeneralList( final Object[] list ) throws IOException, QException { - writer.writeByte(QType.GENERAL_LIST.getTypeCode()); - writer.writeByte((byte) 0); // attributes - writer.writeInt(list.length); - for ( final Object obj : list ) { - writeObject(obj); - } - } + protected abstract void writeObject( final Object obj ) throws IOException, QException; - private void writeSymbol( final String s ) throws IOException { - writer.write(s.getBytes(encoding)); - writer.writeByte((byte) 0); - } - - private void writeGuid( final UUID obj ) throws QException { - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: Guid not supported pre kdb+ v3.0"); - } - - writer.writeLongBigEndian(obj.getMostSignificantBits()); - writer.writeLongBigEndian(obj.getLeastSignificantBits()); - } - - private void writeString( final char[] s ) throws IOException { - writer.writeByte(QType.STRING.getTypeCode()); - writer.writeByte((byte) 0); // attributes - final byte[] encoded = String.valueOf(s).getBytes(encoding); - writer.writeInt(encoded.length); - writer.write(encoded); - } - - private void writeNullItem() { - writer.writeByte(QType.NULL_ITEM.getTypeCode()); - writer.writeByte((byte) 0); - } - - private void writeError( final Exception e ) throws IOException { - writer.writeByte(QType.ERROR.getTypeCode()); - writeSymbol(e.getMessage()); - } - - private void writeDictionary( final QDictionary d ) throws IOException, QException { - writer.writeByte(QType.DICTIONARY.getTypeCode()); - writeObject(d.getKeys()); - writeObject(d.getValues()); - } - - private void writeTable( final QTable t ) throws IOException, QException { - writer.writeByte(QType.TABLE.getTypeCode()); - writer.writeByte((byte) 0); // attributes - writer.writeByte(QType.DICTIONARY.getTypeCode()); - writeObject(t.getColumns()); - writeObject(t.getData()); - } - - private void writeKeyedTable( final QKeyedTable t ) throws IOException, QException { - writer.writeByte(QType.KEYED_TABLE.getTypeCode()); - writeObject(t.getKeys()); - writeObject(t.getValues()); - } - - private void writeLambda( final QLambda l ) throws IOException { - writer.writeByte(QType.LAMBDA.getTypeCode()); - writer.writeByte((byte) 0); - writeString(l.getExpression().toCharArray()); - } - - private void writeProjection( final QProjection p ) throws IOException, QException { - writer.writeByte(QType.PROJECTION.getTypeCode()); - final int length = p.getParameters().length; - writer.writeInt(length); - - for ( int i = 0; i < length; i++ ) { - writeObject(p.getParameters()[i]); - } - } - - static class ByteOutputStream extends OutputStream { - private static final int BUFFER_SIZE = 128; - - protected byte buffer[]; - - protected int count; - - ByteOutputStream() { - buffer = new byte[BUFFER_SIZE]; - } - - ByteOutputStream(final int bufferSize) { - buffer = new byte[bufferSize]; - } - - public void writeShort( final short value ) { - writeByte((byte) value); - writeByte((byte) (value >> 8)); - } - - public void writeInt( final int value ) { - writeShort((short) value); - writeShort((short) (value >> 16)); - } - - public void writeLong( final long value ) { - writeInt((int) value); - writeInt((int) (value >> 32)); - } - - public void writeLongBigEndian( final long value ) { - final byte[] arr = new byte[] { (byte) ((value >> 56) & 0xff), (byte) ((value >> 48) & 0xff), (byte) ((value >> 40) & 0xff), - (byte) ((value >> 32) & 0xff), (byte) ((value >> 24) & 0xff), (byte) ((value >> 16) & 0xff), - (byte) ((value >> 8) & 0xff), (byte) ((value >> 0) & 0xff) }; - for (byte anArr : arr) { - writeByte(anArr); - } - } - - public void writeFloat( final float value ) { - writeInt(Float.floatToIntBits(value)); - } - - public void writeDouble( final double value ) { - writeLong(Double.doubleToLongBits(value)); - } - - public void writeByte( final byte value ) { - final int newcount = count + 1; - if ( newcount > buffer.length ) { - final byte[] copy = new byte[count + BUFFER_SIZE]; - System.arraycopy(buffer, 0, copy, 0, buffer.length); - buffer = copy; - } - buffer[count] = value; - count = newcount; - } - - @Override - public void write( final int b ) { - final int newcount = count + 1; - if ( newcount > buffer.length ) { - final byte[] copy = new byte[count + BUFFER_SIZE]; - System.arraycopy(buffer, 0, copy, 0, buffer.length); - buffer = copy; - } - buffer[count] = (byte) b; - count = newcount; - } - - @Override - public void write( final byte b[], final int off, final int len ) { - if ( (off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) ) { - throw new IndexOutOfBoundsException("Attempt to write outside of the buffer. Offset: " + off + ", Size: " + len + ", Buffer: " + b.length + "."); - } else if ( len == 0 ) { - return; - } - final int newcount = count + len; - if ( newcount > buffer.length ) { - final byte[] copy = new byte[Math.max(count + BUFFER_SIZE, newcount)]; - System.arraycopy(buffer, 0, copy, 0, buffer.length); - buffer = copy; - } - System.arraycopy(b, off, buffer, count, len); - count = newcount; - } - - public int size() { - return count; - } - - @Override - public void close() throws IOException { - // - } - - /** - * Creates copy of internal buffer. - * - * @return copy of the internal buffer as byte[] - */ - public byte[] toByteArray() { - final byte[] copy = new byte[count]; - System.arraycopy(buffer, 0, copy, 0, Math.min(buffer.length, count)); - return copy; - } - - } } diff --git a/src/test/java/com/exxeleron/qjava/TestQReader.java b/src/test/java/com/exxeleron/qjava/TestQReader.java index cd738d9..a08d131 100644 --- a/src/test/java/com/exxeleron/qjava/TestQReader.java +++ b/src/test/java/com/exxeleron/qjava/TestQReader.java @@ -36,7 +36,7 @@ public void testDeserialization() throws IOException, QException { final QExpressions qe = new QExpressions("src/test/resources/QExpressions.out"); for ( final String expr : qe.getExpressions() ) { - final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final ByteOutputStream writer = new ByteOutputStream(); final byte[] binaryExpr = qe.getBinaryExpression(expr); writer.writeByte((byte) 1); // little endian writer.writeByte((byte) 0); @@ -46,7 +46,9 @@ public void testDeserialization() throws IOException, QException { writer.write(binaryExpr); writer.flush(); - final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + final QReader reader = new DefaultQReader(); + reader.setStream(new DataInputStream(new ByteArrayInputStream(writer.toByteArray()))); + reader.setEncoding("ISO-8859-1"); try { final Object obj = reader.read(false).getData(); @@ -108,7 +110,7 @@ public void testFunctionsDeserialization() throws IOException, QException { }; for ( final String expr : qe.getExpressions() ) { - final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final ByteOutputStream writer = new ByteOutputStream(); final byte[] binaryExpr = qe.getBinaryExpression(expr); writer.writeByte((byte) 1); // little endian writer.writeByte((byte) 0); @@ -118,7 +120,9 @@ public void testFunctionsDeserialization() throws IOException, QException { writer.write(binaryExpr); writer.flush(); - final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + final QReader reader = new DefaultQReader(); + reader.setStream(new DataInputStream(new ByteArrayInputStream(writer.toByteArray()))); + reader.setEncoding("ISO-8859-1"); try { final Object obj = reader.read(false).getData(); @@ -161,7 +165,7 @@ public void testCompressedDeserialization() throws IOException, QException { reference.put("([] a:til 200;b:25+til 200;c:200#`a)", new QTable(new String[] { "a", "b", "c" }, q200)); for ( final String expr : qe.getExpressions() ) { - final QWriter.ByteOutputStream writer = new QWriter.ByteOutputStream(); + final ByteOutputStream writer = new ByteOutputStream(); final byte[] binaryExpr = qe.getBinaryExpression(expr); writer.writeByte((byte) 1); // little endian writer.writeByte((byte) 0); @@ -171,7 +175,9 @@ public void testCompressedDeserialization() throws IOException, QException { writer.write(binaryExpr); writer.flush(); - final QReader reader = new QReader(new DataInputStream(new ByteArrayInputStream(writer.toByteArray())), "ISO-8859-1"); + final QReader reader = new DefaultQReader(); + reader.setStream(new DataInputStream(new ByteArrayInputStream(writer.toByteArray()))); + reader.setEncoding("ISO-8859-1"); final Object obj = reader.read(false).getData(); diff --git a/src/test/java/com/exxeleron/qjava/TestQWriter.java b/src/test/java/com/exxeleron/qjava/TestQWriter.java index c221791..f8c11e5 100644 --- a/src/test/java/com/exxeleron/qjava/TestQWriter.java +++ b/src/test/java/com/exxeleron/qjava/TestQWriter.java @@ -41,7 +41,9 @@ public void testSerialization() throws IOException, QException { protected void serializeObject( final Object referenceObject, final QExpressions qe, final String expr ) throws IOException, QException, ArrayComparisonFailure { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final QWriter writer = new QWriter(stream, "ISO-8859-1", 3); + final QWriter writer = new DefaultQWriter(); + writer.setStream(stream); + writer.setEncoding("ISO-8859-1"); writer.write(referenceObject, QConnection.MessageType.SYNC); final byte[] out = stream.toByteArray(); From 58dada53ca45f97f43559df72ee76740592f8cf7 Mon Sep 17 00:00:00 2001 From: Maciej Lach Date: Tue, 6 Oct 2015 10:55:39 +0200 Subject: [PATCH 3/5] Tests refactoring --- .../com/exxeleron/qjava/QExpressions.java | 235 +++++++++--------- .../java/com/exxeleron/qjava/TestQWriter.java | 6 +- 2 files changed, 115 insertions(+), 126 deletions(-) diff --git a/src/test/java/com/exxeleron/qjava/QExpressions.java b/src/test/java/com/exxeleron/qjava/QExpressions.java index d2a974c..2360514 100644 --- a/src/test/java/com/exxeleron/qjava/QExpressions.java +++ b/src/test/java/com/exxeleron/qjava/QExpressions.java @@ -28,124 +28,119 @@ */ class QExpressions { - private final Map reference = new LinkedHashMap(); - private final Map referenceSerializationAlt = new LinkedHashMap(); + private final Map reference = new LinkedHashMap(); private void initExpressions() throws QException { - reference.put("1+`", new QException("type")); - reference.put("()", new Object[0]); - reference.put("::", null); - reference.put("1", 1L); - reference.put("1i", 1); - reference.put("-234h", (short) -234); - reference.put("1b", true); - reference.put("0x2a", (byte) 0x2a); - reference.put("89421099511627575j", 89421099511627575L); - reference.put("3.234", 3.234); - reference.put("5.5e", (float) 5.5); - reference.put("\"0\"", '0'); - reference.put("\"abc\"", "abc".toCharArray()); - reference.put("\"\"", "".toCharArray()); - reference.put("\"quick brown fox jumps over a lazy dog\"", "quick brown fox jumps over a lazy dog".toCharArray()); - reference.put("`abc", "abc"); - reference.put("`quickbrownfoxjumpsoveralazydog", "quickbrownfoxjumpsoveralazydog"); - reference.put("2000.01.04D05:36:57.600", new QTimestamp(279417600000000L)); - reference.put("2001.01m", new QMonth(12)); - reference.put("2001.01.01", new QDate(366)); - reference.put("2000.05.01", new QDate(121)); - reference.put("2000.01.04T05:36:57.600", new QDateTime(3.234)); - reference.put("0D05:36:57.600", new QTimespan(20217600000000L)); - reference.put("12:01", new QMinute(721)); - reference.put("12:05:00", new QSecond(43500)); - reference.put("12:04:59.123", new QTime(43499123)); - reference.put("0b", QType.getQNull(QType.BOOL)); - reference.put("0x00", QType.getQNull(QType.BYTE)); - reference.put("0Nh", QType.getQNull(QType.SHORT)); - reference.put("0N", QType.getQNull(QType.LONG)); - reference.put("0Ni", QType.getQNull(QType.INT)); - reference.put("0Nj", QType.getQNull(QType.LONG)); - reference.put("0Ne", QType.getQNull(QType.FLOAT)); - reference.put("0n", QType.getQNull(QType.DOUBLE)); - reference.put("\" \"", QType.getQNull(QType.CHAR)); - reference.put("`", QType.getQNull(QType.SYMBOL)); - reference.put("0Np", QType.getQNull(QType.TIMESTAMP)); - reference.put("0Nm", QType.getQNull(QType.MONTH)); - reference.put("0Nd", QType.getQNull(QType.DATE)); - reference.put("0Nz", QType.getQNull(QType.DATETIME)); - reference.put("0Nn", QType.getQNull(QType.TIMESPAN)); - reference.put("0Nu", QType.getQNull(QType.MINUTE)); - reference.put("0Nv", QType.getQNull(QType.SECOND)); - reference.put("0Nt", QType.getQNull(QType.TIME)); - reference.put("(0b;1b;0b)", new boolean[] { false, true, false }); - referenceSerializationAlt.put("(0b;1b;0b)", new Boolean[] { false, true, false }); - reference.put("(0x01;0x02;0xff)", new byte[] { 1, 2, (byte) 255 }); - referenceSerializationAlt.put("(0x01;0x02;0xff)", new Byte[] { 1, 2, (byte) 255 }); - reference.put("(1h;2h;3h)", new short[] { 1, 2, 3 }); - referenceSerializationAlt.put("(1h;2h;3h)", new Short[] { 1, 2, 3 }); - reference.put("1 2 3", new long[] { 1, 2, 3 }); - referenceSerializationAlt.put("1 2 3", new Long[] { 1L, 2L, 3L }); - reference.put("(1i;2i;3i)", new int[] { 1, 2, 3 }); - referenceSerializationAlt.put("(1i;2i;3i)", new Integer[] { 1, 2, 3 }); - reference.put("(1j;2j;3j)", new long[] { 1, 2, 3 }); - referenceSerializationAlt.put("(1j;2j;3j)", new Long[] { 1L, 2L, 3L }); - reference.put("(5.5e; 8.5e)", new float[] { 5.5f, 8.5f }); - referenceSerializationAlt.put("(5.5e; 8.5e)", new Float[] { 5.5f, 8.5f }); - reference.put("3.23 6.46", new double[] { 3.23, 6.46 }); - referenceSerializationAlt.put("3.23 6.46", new Double[] { 3.23, 6.46 }); - reference.put("(1;`bcd;\"0bc\";5.5e)", new Object[] { 1L, "bcd", "0bc".toCharArray(), (float) 5.5 }); - reference.put("(42;::;`foo)", new Object[] { 42L, null, "foo" }); - reference.put("(enlist 1h; 2; enlist 3j)", new Object[] { new short[] { 1 }, 2L, new long[] { 3 } }); - reference.put("`the`quick`brown`fox", new String[] { "the", "quick", "brown", "fox" }); - reference.put("``quick``fox", new String[] { "", "quick", "", "fox" }); - reference.put("``", new String[] { "", "" }); - reference.put("(\"quick\"; \"brown\"; \"fox\"; \"jumps\"; \"over\"; \"a lazy\"; \"dog\")", new Object[] {"quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray(), "jumps".toCharArray(), "over".toCharArray(), "a lazy".toCharArray(), "dog".toCharArray() }); - reference.put("(\"quick\"; \"brown\"; \"fox\")", new char[][] {"quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray() }); - reference.put("2000.01.04D05:36:57.600 0Np", new QTimestamp[] { new QTimestamp(279417600000000L), new QTimestamp(Long.MIN_VALUE) }); - reference.put("(2001.01m; 0Nm)", new QMonth[] { new QMonth(12), new QMonth(Integer.MIN_VALUE) }); - reference.put("2001.01.01 2000.05.01 0Nd", new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) }); - reference.put("2000.01.04T05:36:57.600 0Nz", new QDateTime[] { new QDateTime(3.234), new QDateTime(Double.NaN) }); - reference.put("0D05:36:57.600 0Nn", new QTimespan[] { new QTimespan(20217600000000L), new QTimespan(Long.MIN_VALUE) }); - reference.put("12:01 0Nu", new QMinute[] { new QMinute(721), new QMinute(Integer.MIN_VALUE) }); - reference.put("12:05:00 0Nv", new QSecond[] { new QSecond(43500), new QSecond(Integer.MIN_VALUE) }); - reference.put("12:04:59.123 0Nt", new QTime[] { new QTime(43499123), new QTime(Integer.MIN_VALUE) }); - reference.put("(enlist `a)!(enlist 1)", new QDictionary(new String[] { "a" }, new long[] { 1 })); - reference.put("1 2!`abc`cdefgh", new QDictionary(new long[] { 1, 2 }, new String[] { "abc", "cdefgh" })); - reference.put("(`x`y!(`a;2))", new QDictionary(new String[] { "x", "y" }, new Object[] { "a", 2L })); - reference.put("`abc`def`gh!([] one: 1 2 3; two: 4 5 6)", new QDictionary(new String[] { "abc", "def", "gh" }, new QTable(new String[] { "one", "two" }, - new Object[] { new long[] { 1, 2, 3 }, new long[] { 4, 5, 6 } }))); - reference.put("(1;2h;3.3;\"4\")!(`one;2 3;\"456\";(7;8 9))", new QDictionary(new Object[] { 1L, (short) 2, 3.3, '4' }, - new Object[] { "one", new long[] { 2, 3 }, "456".toCharArray(), new Object[] { 7L, new long[] { 8, 9 } } })); - reference.put("(0 1; 2 3)!`first`second", new QDictionary(new Object[] { new long[] { 0, 1 }, new long[] { 2, 3 } }, new String[] { "first", "second" })); - reference.put("`A`B`C!((1;2.2;3);(`x`y!(`a;2));5.5)", new QDictionary(new String[] { "A", "B", "C" }, new Object[] { - new Object[] { 1L, 2.2, 3L }, - new QDictionary( - new String[] { "x", "y" }, - new Object[] { "a", 2L }), - 5.5 })); - reference.put("flip `abc`def!(1 2 3; 4 5 6)", new QTable(new String[] { "abc", "def" }, new Object[] { new long[] { 1, 2, 3 }, new long[] { 4, 5, 6 } })); - reference.put("flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126)", - new QTable(new String[] { "name", "iq" }, new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new long[] { 98, 42, 126 } })); - reference.put("flip `name`iq`grade!(`Dent`Beeblebrox`Prefect;98 42 126;\"a c\")", - new QTable(new String[] { "name", "iq", "grade" }, new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new long[] { 98, 42, 126 }, new char[]{'a', ' ', 'c' }})); - reference.put("flip `name`iq`fullname!(`Dent`Beeblebrox`Prefect;98 42 126;(\"Arthur Dent\"; \"Zaphod Beeblebrox\"; \"Ford Prefect\"))", - new QTable(new String[] { "name", "iq", "fullname" }, new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new long[] { 98, 42, 126 }, new Object[]{"Arthur Dent".toCharArray(), "Zaphod Beeblebrox".toCharArray(), "Ford Prefect".toCharArray()} })); - reference.put("([] sc:1 2 3; nsc:(1 2; 3 4; 5 6 7))", new QTable(new String[] { "sc", "nsc" }, new Object[] { - new long[] { 1, 2, 3 }, - new Object[] { new long[] { 1, 2 }, - new long[] { 3, 4 }, - new long[] { 5, 6, 7 } } })); - reference.put("([] name:`symbol$(); iq:`int$())", new QTable(new String[] { "name", "iq" }, new Object[] { new String[] {}, new int[] {} })); - reference.put("([] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new QTable(new String[] { "pos", "dates" }, - new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } })); - reference.put("([eid:1001 1002 1003] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new QKeyedTable(new QTable(new String[] { "eid" }, - new Object[] { new long[] { 1001, 1002, 1003 } }), new QTable(new String[] { "pos", "dates" }, - new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } }))); - reference.put("{x+y}", new QLambda("{x+y}")); - reference.put("{x+y}[3]", new QProjection(new Object[] {new QLambda("{x+y}"), 3L })); - reference.put("0Ng", new UUID(0, 0)); - reference.put("\"G\"$\"8c680a01-5a49-5aab-5a65-d4bfddb6a661\"", UUID.fromString("8c680a01-5a49-5aab-5a65-d4bfddb6a661")); + reference.put("1+`", new Object[] { new QException("type") }); + reference.put("()", new Object[] { new Object[0] }); + reference.put("::", new Object[] { null }); + reference.put("1", new Object[] { 1L }); + reference.put("1i", new Object[] { 1 }); + reference.put("-234h", new Object[] { (short) -234 }); + reference.put("1b", new Object[] { true }); + reference.put("0x2a", new Object[] { (byte) 0x2a }); + reference.put("89421099511627575j", new Object[] { 89421099511627575L }); + reference.put("3.234", new Object[] { 3.234 }); + reference.put("5.5e", new Object[] { (float) 5.5 }); + reference.put("\"0\"", new Object[] { '0' }); + reference.put("\"abc\"", new Object[] { "abc".toCharArray() }); + reference.put("\"\"", new Object[] { "".toCharArray() }); + reference.put("\"quick brown fox jumps over a lazy dog\"", new Object[] { "quick brown fox jumps over a lazy dog".toCharArray() }); + reference.put("`abc", new Object[] { "abc" }); + reference.put("`quickbrownfoxjumpsoveralazydog", new Object[] { "quickbrownfoxjumpsoveralazydog" }); + reference.put("2000.01.04D05:36:57.600", new Object[] { new QTimestamp(279417600000000L) }); + reference.put("2001.01m", new Object[] { new QMonth(12) }); + reference.put("2001.01.01", new Object[] { new QDate(366) }); + reference.put("2000.05.01", new Object[] { new QDate(121) }); + reference.put("2000.01.04T05:36:57.600", new Object[] { new QDateTime(3.234) }); + reference.put("0D05:36:57.600", new Object[] { new QTimespan(20217600000000L) }); + reference.put("12:01", new Object[] { new QMinute(721) }); + reference.put("12:05:00", new Object[] { new QSecond(43500) }); + reference.put("12:04:59.123", new Object[] { new QTime(43499123) }); + reference.put("0b", new Object[] { QType.getQNull(QType.BOOL) }); + reference.put("0x00", new Object[] { QType.getQNull(QType.BYTE) }); + reference.put("0Nh", new Object[] { QType.getQNull(QType.SHORT) }); + reference.put("0N", new Object[] { QType.getQNull(QType.LONG) }); + reference.put("0Ni", new Object[] { QType.getQNull(QType.INT) }); + reference.put("0Nj", new Object[] { QType.getQNull(QType.LONG) }); + reference.put("0Ne", new Object[] { QType.getQNull(QType.FLOAT) }); + reference.put("0n", new Object[] { QType.getQNull(QType.DOUBLE) }); + reference.put("\" \"", new Object[] { QType.getQNull(QType.CHAR) }); + reference.put("`", new Object[] { QType.getQNull(QType.SYMBOL) }); + reference.put("0Np", new Object[] { QType.getQNull(QType.TIMESTAMP) }); + reference.put("0Nm", new Object[] { QType.getQNull(QType.MONTH) }); + reference.put("0Nd", new Object[] { QType.getQNull(QType.DATE) }); + reference.put("0Nz", new Object[] { QType.getQNull(QType.DATETIME) }); + reference.put("0Nn", new Object[] { QType.getQNull(QType.TIMESPAN) }); + reference.put("0Nu", new Object[] { QType.getQNull(QType.MINUTE) }); + reference.put("0Nv", new Object[] { QType.getQNull(QType.SECOND) }); + reference.put("0Nt", new Object[] { QType.getQNull(QType.TIME) }); + reference.put("(0b;1b;0b)", new Object[] { new boolean[] { false, true, false }, new Boolean[] { false, true, false } }); + reference.put("(0x01;0x02;0xff)", new Object[] { new byte[] { 1, 2, (byte) 255 }, new Byte[] { 1, 2, (byte) 255 } }); + reference.put("(1h;2h;3h)", new Object[] { new short[] { 1, 2, 3 }, new Short[] { 1, 2, 3 } }); + reference.put("1 2 3", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L } }); + reference.put("(1i;2i;3i)", new Object[] { new int[] { 1, 2, 3 }, new Integer[] { 1, 2, 3 } }); + reference.put("(1j;2j;3j)", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L } }); + reference.put("(5.5e; 8.5e)", new Object[] { new float[] { 5.5f, 8.5f }, new Float[] { 5.5f, 8.5f } }); + reference.put("3.23 6.46", new Object[] { new double[] { 3.23, 6.46 }, new Double[] { 3.23, 6.46 } }); + reference.put("(1;`bcd;\"0bc\";5.5e)", new Object[] { new Object[] { 1L, "bcd", "0bc".toCharArray(), (float) 5.5 } }); + reference.put("(42;::;`foo)", new Object[] { new Object[] { 42L, null, "foo" } }); + reference.put("(enlist 1h; 2; enlist 3j)", new Object[] { new Object[] { new short[] { 1 }, 2L, new long[] { 3 } } }); + reference.put("`the`quick`brown`fox", new Object[] { new String[] { "the", "quick", "brown", "fox" } }); + reference.put("``quick``fox", new Object[] { new String[] { "", "quick", "", "fox" } }); + reference.put("``", new Object[] { new String[] { "", "" } }); + reference.put("(\"quick\"; \"brown\"; \"fox\"; \"jumps\"; \"over\"; \"a lazy\"; \"dog\")", + new Object[] { new Object[] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray(), "jumps".toCharArray(), "over".toCharArray(), + "a lazy".toCharArray(), "dog".toCharArray() } }); + reference.put("(\"quick\"; \"brown\"; \"fox\")", new Object[] { new char[][] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray() } }); + reference.put("2000.01.04D05:36:57.600 0Np", new Object[] { new QTimestamp[] { new QTimestamp(279417600000000L), new QTimestamp(Long.MIN_VALUE) } }); + reference.put("(2001.01m; 0Nm)", new Object[] { new QMonth[] { new QMonth(12), new QMonth(Integer.MIN_VALUE) } }); + reference.put("2001.01.01 2000.05.01 0Nd", new Object[] { new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } }); + reference.put("2000.01.04T05:36:57.600 0Nz", new Object[] { new QDateTime[] { new QDateTime(3.234), new QDateTime(Double.NaN) } }); + reference.put("0D05:36:57.600 0Nn", new Object[] { new QTimespan[] { new QTimespan(20217600000000L), new QTimespan(Long.MIN_VALUE) } }); + reference.put("12:01 0Nu", new Object[] { new QMinute[] { new QMinute(721), new QMinute(Integer.MIN_VALUE) } }); + reference.put("12:05:00 0Nv", new Object[] { new QSecond[] { new QSecond(43500), new QSecond(Integer.MIN_VALUE) } }); + reference.put("12:04:59.123 0Nt", new Object[] { new QTime[] { new QTime(43499123), new QTime(Integer.MIN_VALUE) } }); + reference.put("(enlist `a)!(enlist 1)", new Object[] { new QDictionary(new String[] { "a" }, new long[] { 1 }) }); + reference.put("1 2!`abc`cdefgh", new Object[] { new QDictionary(new long[] { 1, 2 }, new String[] { "abc", "cdefgh" }) }); + reference.put("(`x`y!(`a;2))", new Object[] { new QDictionary(new String[] { "x", "y" }, new Object[] { "a", 2L }) }); + reference.put("`abc`def`gh!([] one: 1 2 3; two: 4 5 6)", new Object[] { new QDictionary(new String[] { "abc", "def", "gh" }, new QTable( + new String[] { "one", "two" }, new Object[] { new long[] { 1, 2, 3 }, new long[] { 4, 5, 6 } })) }); + reference.put("(1;2h;3.3;\"4\")!(`one;2 3;\"456\";(7;8 9))", new Object[] { new QDictionary(new Object[] { 1L, (short) 2, 3.3, '4' }, + new Object[] { "one", new long[] { 2, 3 }, "456".toCharArray(), new Object[] { 7L, new long[] { 8, 9 } } }) }); + reference.put("(0 1; 2 3)!`first`second", new Object[] { new QDictionary(new Object[] { new long[] { 0, 1 }, new long[] { 2, 3 } }, + new String[] { "first", "second" }) }); + reference.put("`A`B`C!((1;2.2;3);(`x`y!(`a;2));5.5)", new Object[] { new QDictionary(new String[] { "A", "B", "C" }, + new Object[] { new Object[] { 1L, 2.2, 3L }, new QDictionary(new String[] { "x", "y" }, new Object[] { "a", 2L }), 5.5 }) }); + reference.put("flip `abc`def!(1 2 3; 4 5 6)", new Object[] { new QTable(new String[] { "abc", "def" }, new Object[] { new long[] { 1, 2, 3 }, + new long[] { 4, 5, 6 } }) }); + reference.put("flip `name`iq!(`Dent`Beeblebrox`Prefect;98 42 126)", new Object[] { new QTable(new String[] { "name", "iq" }, + new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new long[] { 98, 42, 126 } }) }); + reference.put("flip `name`iq`grade!(`Dent`Beeblebrox`Prefect;98 42 126;\"a c\")", new Object[] { new QTable(new String[] { "name", "iq", "grade" }, + new Object[] { new String[] { "Dent", "Beeblebrox", "Prefect" }, new long[] { 98, 42, 126 }, new char[] { 'a', ' ', 'c' } }) }); + reference.put( + "flip `name`iq`fullname!(`Dent`Beeblebrox`Prefect;98 42 126;(\"Arthur Dent\"; \"Zaphod Beeblebrox\"; \"Ford Prefect\"))", + new Object[] { new QTable(new String[] { "name", "iq", "fullname" }, new Object[] { + new String[] { "Dent", "Beeblebrox", "Prefect" }, + new long[] { 98, 42, 126 }, + new Object[] { "Arthur Dent".toCharArray(), + "Zaphod Beeblebrox".toCharArray(), + "Ford Prefect".toCharArray() } }) }); + reference.put("([] sc:1 2 3; nsc:(1 2; 3 4; 5 6 7))", new Object[] { new QTable(new String[] { "sc", "nsc" }, + new Object[] { new long[] { 1, 2, 3 }, new Object[] { new long[] { 1, 2 }, new long[] { 3, 4 }, new long[] { 5, 6, 7 } } }) }); + reference.put("([] name:`symbol$(); iq:`int$())", new Object[] { new QTable(new String[] { "name", "iq" }, + new Object[] { new String[] {}, new int[] {} }) }); + reference.put("([] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new Object[] { new QTable(new String[] { "pos", "dates" }, + new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } }) }); + reference.put("([eid:1001 1002 1003] pos:`d1`d2`d3;dates:(2001.01.01;2000.05.01;0Nd))", new Object[] { new QKeyedTable(new QTable( + new String[] { "eid" }, new Object[] { new long[] { 1001, 1002, 1003 } }), new QTable(new String[] { "pos", "dates" }, + new Object[] { new String[] { "d1", "d2", "d3" }, new QDate[] { new QDate(366), new QDate(121), new QDate(Integer.MIN_VALUE) } })) }); + reference.put("{x+y}", new Object[] { new QLambda("{x+y}") }); + reference.put("{x+y}[3]", new Object[] { new QProjection(new Object[] { new QLambda("{x+y}"), 3L }) }); + reference.put("0Ng", new Object[] { new UUID(0, 0) }); + reference.put("\"G\"$\"8c680a01-5a49-5aab-5a65-d4bfddb6a661\"", new Object[] { UUID.fromString("8c680a01-5a49-5aab-5a65-d4bfddb6a661") }); reference.put("(\"G\"$\"8c680a01-5a49-5aab-5a65-d4bfddb6a661\"; 0Ng)", - new UUID[] { UUID.fromString("8c680a01-5a49-5aab-5a65-d4bfddb6a661"), new UUID(0, 0) }); + new Object[] { new UUID[] { UUID.fromString("8c680a01-5a49-5aab-5a65-d4bfddb6a661"), new UUID(0, 0) } }); } @@ -186,15 +181,11 @@ Set getExpressions() { } Object getReferenceObject( final String expression ) { - return reference.get(expression); - } - - boolean hasReferenceObjectAlt( final String expression ) { - return referenceSerializationAlt.containsKey(expression); + return reference.get(expression)[0]; } - Object getReferenceObjectAlt( final String expression ) { - return referenceSerializationAlt.get(expression); + Object[] getReferenceObjects( final String expression ) { + return reference.get(expression); } public byte[] getBinaryExpression( final String expression ) { diff --git a/src/test/java/com/exxeleron/qjava/TestQWriter.java b/src/test/java/com/exxeleron/qjava/TestQWriter.java index f8c11e5..f038b72 100644 --- a/src/test/java/com/exxeleron/qjava/TestQWriter.java +++ b/src/test/java/com/exxeleron/qjava/TestQWriter.java @@ -30,10 +30,8 @@ public void testSerialization() throws IOException, QException { final QExpressions qe = new QExpressions("src/test/resources/QExpressions.out"); for ( final String expr : qe.getExpressions() ) { - serializeObject(qe.getReferenceObject(expr), qe, expr); - - if ( qe.hasReferenceObjectAlt(expr) ) { - serializeObject(qe.getReferenceObjectAlt(expr), qe, expr); + for ( final Object obj : qe.getReferenceObjects(expr) ) { + serializeObject(obj, qe, expr); } } } From 8997c3b60d356c108fc4d797b5b01b11527f5232 Mon Sep 17 00:00:00 2001 From: Maciej Lach Date: Tue, 6 Oct 2015 11:50:15 +0200 Subject: [PATCH 4/5] Collections serialization support in DefaultQWriter --- CHANGELOG.txt | 1 + doc/Type-conversion.md | 10 + .../com/exxeleron/qjava/DefaultQWriter.java | 315 ++++++++++++------ src/main/java/com/exxeleron/qjava/QType.java | 17 + .../java/com/exxeleron/qjava/QWriter.java | 22 ++ .../com/exxeleron/qjava/QExpressions.java | 56 ++-- 6 files changed, 299 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8b231a0..bdffabf 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -3,6 +3,7 @@ ------------------------------------------------------------------------------ - API redesign: enable custom serializers & deserializers + - Collections serialization support in DefaultQWriter - Improve memory reuse in QWriter - Improve performance while iterating over QTable, QDictionary and QKeyedTable: partial reimplementation of java.lang.reflect.Array diff --git a/doc/Type-conversion.md b/doc/Type-conversion.md index 5ad1d93..a76d7d9 100644 --- a/doc/Type-conversion.md +++ b/doc/Type-conversion.md @@ -58,6 +58,16 @@ Note that q list are represented as arrays of primitive type by the qJava library. It is possible to send to q arrays of primitive type (e.g. `int[]`) as well as of boxed type (e.g. `Integer[]`). +`DefaultQWriter` supports serialization of Java `Collection`s. Q type is +derived from the first element of the `Collection`, e.g.: + +```java +new ArrayList(Arrays.asList(new Integer[] { 0, 1, 2, 3 })); // serialized as integer list +new ArrayList(Arrays.asList(new Object[] { null, 2, 3, "test" })); // serialized as general list + +new ArrayList(Arrays.asList(new Object[] { 1, "test" })); // serialization error, assumed to be integer-only list +``` + ### Temporal types q language provides multiple types for operating on temporal data. The qJava library provides a corresponding temporal class for each q temporal type. diff --git a/src/main/java/com/exxeleron/qjava/DefaultQWriter.java b/src/main/java/com/exxeleron/qjava/DefaultQWriter.java index b13e7a8..55d017e 100644 --- a/src/main/java/com/exxeleron/qjava/DefaultQWriter.java +++ b/src/main/java/com/exxeleron/qjava/DefaultQWriter.java @@ -16,8 +16,10 @@ package com.exxeleron.qjava; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.UUID; @@ -29,47 +31,50 @@ public class DefaultQWriter extends QWriter { /** * @see com.exxeleron.qjava.QWriter#writeObject(java.lang.Object) */ + @Override protected void writeObject( final Object obj ) throws IOException, QException { - final QType qtype = getQType(obj); - - if ( qtype == QType.STRING ) { - writeString((char[]) obj); - } else if ( qtype == QType.GENERAL_LIST ) { - writeGeneralList((Object[]) obj); - } else if ( qtype == QType.NULL_ITEM ) { - writeNullItem(); - } else if ( qtype == QType.ERROR ) { - writeError((Exception) obj); - } else if ( qtype == QType.DICTIONARY ) { - writeDictionary((QDictionary) obj); - } else if ( qtype == QType.TABLE ) { - writeTable((QTable) obj); - } else if ( qtype == QType.KEYED_TABLE ) { - writeKeyedTable((QKeyedTable) obj); - } else if ( qtype.getTypeCode() < 0 ) { - writeAtom(obj, qtype); - } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { - writeList(obj, qtype); - } else if ( qtype == QType.LAMBDA ) { - writeLambda((QLambda) obj); - } else if ( qtype == QType.PROJECTION ) { - writeProjection((QProjection) obj); + if ( obj instanceof Collection ) { + writeCollection((Collection) obj); } else { - throw new QWriterException("Unable to serialize q type: " + qtype); + final QType qtype = getQType(obj); + checkProtocolVersionCompatibility(qtype); + + if ( qtype == QType.STRING ) { + writeString((char[]) obj); + } else if ( qtype == QType.GENERAL_LIST ) { + writeGeneralList((Object[]) obj); + } else if ( qtype == QType.NULL_ITEM ) { + writeNullItem(); + } else if ( qtype == QType.ERROR ) { + writeError((Exception) obj); + } else if ( qtype == QType.DICTIONARY ) { + writeDictionary((QDictionary) obj); + } else if ( qtype == QType.TABLE ) { + writeTable((QTable) obj); + } else if ( qtype == QType.KEYED_TABLE ) { + writeKeyedTable((QKeyedTable) obj); + } else if ( qtype.getTypeCode() < 0 ) { + writeAtom(obj, qtype); + } else if ( qtype.getTypeCode() >= QType.BOOL_LIST.getTypeCode() && qtype.getTypeCode() <= QType.TIME_LIST.getTypeCode() ) { + writeList(obj, qtype); + } else if ( qtype == QType.LAMBDA ) { + writeLambda((QLambda) obj); + } else if ( qtype == QType.PROJECTION ) { + writeProjection((QProjection) obj); + } else { + throw new QWriterException("Unable to serialize q type: " + qtype); + } } } @SuppressWarnings("incomplete-switch") - protected void writeAtom( final Object obj, final QType qtype ) throws IOException, QException { + protected void writeAtom( final Object obj, final QType qtype ) throws IOException { writer.writeByte(qtype.getTypeCode()); switch ( qtype ) { case BOOL: writer.writeByte((byte) ((Boolean) obj ? 1 : 0)); break; case GUID: - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); - } writeGuid((UUID) obj); break; case BYTE: @@ -97,9 +102,6 @@ protected void writeAtom( final Object obj, final QType qtype ) throws IOExcepti writeSymbol((String) obj); break; case TIMESTAMP: - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); - } writer.writeLong(((QTimestamp) obj).getValue()); break; case MONTH: @@ -112,9 +114,6 @@ protected void writeAtom( final Object obj, final QType qtype ) throws IOExcepti writer.writeDouble(((QDateTime) obj).getValue()); break; case TIMESPAN: - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); - } writer.writeLong(((QTimespan) obj).getValue()); break; case MINUTE: @@ -130,7 +129,7 @@ protected void writeAtom( final Object obj, final QType qtype ) throws IOExcepti } @SuppressWarnings("incomplete-switch") - protected void writeList( final Object obj, final QType qtype ) throws IOException, QException { + protected void writeList( final Object obj, final QType qtype ) throws IOException { writer.writeByte(qtype.getTypeCode()); writer.writeByte((byte) 0); // attributes @@ -139,26 +138,23 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof boolean[] ) { final boolean[] list = (boolean[]) obj; writer.writeInt(list.length); - for ( final boolean a : list ) { - writer.writeByte((byte) (a ? 1 : 0)); + for ( final boolean e : list ) { + writer.writeByte((byte) (e ? 1 : 0)); } } else if ( obj instanceof Boolean[] ) { final Boolean[] list = (Boolean[]) obj; writer.writeInt(list.length); - for ( final Boolean a : list ) { - writer.writeByte((byte) (a ? 1 : 0)); + for ( final Boolean e : list ) { + writer.writeByte((byte) (e ? 1 : 0)); } } break; } case GUID_LIST: { - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); - } final UUID[] list = (UUID[]) obj; writer.writeInt(list.length); - for ( final UUID a : list ) { - writeGuid(a); + for ( final UUID e : list ) { + writeGuid(e); } break; } @@ -166,14 +162,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof byte[] ) { final byte[] list = (byte[]) obj; writer.writeInt(list.length); - for ( final byte a : list ) { - writer.writeByte(a); + for ( final byte e : list ) { + writer.writeByte(e); } } else if ( obj instanceof Byte[] ) { final Byte[] list = (Byte[]) obj; writer.writeInt(list.length); - for ( final Byte a : list ) { - writer.writeByte(a); + for ( final Byte e : list ) { + writer.writeByte(e); } } break; @@ -182,14 +178,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof short[] ) { final short[] list = (short[]) obj; writer.writeInt(list.length); - for ( final short a : list ) { - writer.writeShort(a); + for ( final short e : list ) { + writer.writeShort(e); } } else if ( obj instanceof Short[] ) { final Short[] list = (Short[]) obj; writer.writeInt(list.length); - for ( final Short a : list ) { - writer.writeShort(a); + for ( final Short e : list ) { + writer.writeShort(e); } } break; @@ -198,14 +194,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof int[] ) { final int[] list = (int[]) obj; writer.writeInt(list.length); - for ( final int a : list ) { - writer.writeInt(a); + for ( final int e : list ) { + writer.writeInt(e); } } else if ( obj instanceof Integer[] ) { final Integer[] list = (Integer[]) obj; writer.writeInt(list.length); - for ( final Integer a : list ) { - writer.writeInt(a); + for ( final Integer e : list ) { + writer.writeInt(e); } } break; @@ -214,14 +210,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof long[] ) { final long[] list = (long[]) obj; writer.writeInt(list.length); - for ( final long a : list ) { - writer.writeLong(a); + for ( final long e : list ) { + writer.writeLong(e); } } else if ( obj instanceof Long[] ) { final Long[] list = (Long[]) obj; writer.writeInt(list.length); - for ( final Long a : list ) { - writer.writeLong(a); + for ( final Long e : list ) { + writer.writeLong(e); } } break; @@ -230,14 +226,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof float[] ) { final float[] list = (float[]) obj; writer.writeInt(list.length); - for ( final float a : list ) { - writer.writeFloat(a); + for ( final float e : list ) { + writer.writeFloat(e); } } else if ( obj instanceof Float[] ) { final Float[] list = (Float[]) obj; writer.writeInt(list.length); - for ( final Float a : list ) { - writer.writeFloat(a); + for ( final Float e : list ) { + writer.writeFloat(e); } } break; @@ -246,14 +242,14 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti if ( obj instanceof double[] ) { final double[] list = (double[]) obj; writer.writeInt(list.length); - for ( final double a : list ) { - writer.writeDouble(a); + for ( final double e : list ) { + writer.writeDouble(e); } } else if ( obj instanceof Double[] ) { final Double[] list = (Double[]) obj; writer.writeInt(list.length); - for ( final Double a : list ) { - writer.writeDouble(a); + for ( final Double e : list ) { + writer.writeDouble(e); } } break; @@ -261,78 +257,203 @@ protected void writeList( final Object obj, final QType qtype ) throws IOExcepti case SYMBOL_LIST: { final String[] list = (String[]) obj; writer.writeInt(list.length); - for ( final String a : list ) { - writeSymbol(a); + for ( final String e : list ) { + writeSymbol(e); } break; } case TIMESTAMP_LIST: { - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); - } final QTimestamp[] list = (QTimestamp[]) obj; writer.writeInt(list.length); - for ( final QTimestamp a : list ) { - writer.writeLong(a.getValue()); + for ( final QTimestamp e : list ) { + writer.writeLong(e.getValue()); } break; } case MONTH_LIST: { final QMonth[] list = (QMonth[]) obj; writer.writeInt(list.length); - for ( final QMonth a : list ) { - writer.writeInt(a.getValue()); + for ( final QMonth e : list ) { + writer.writeInt(e.getValue()); } break; } case DATE_LIST: { final QDate[] list = (QDate[]) obj; writer.writeInt(list.length); - for ( final QDate a : list ) { - writer.writeInt(a.getValue()); + for ( final QDate e : list ) { + writer.writeInt(e.getValue()); } break; } case DATETIME_LIST: { final QDateTime[] list = (QDateTime[]) obj; writer.writeInt(list.length); - for ( final QDateTime a : list ) { - writer.writeDouble(a.getValue()); + for ( final QDateTime e : list ) { + writer.writeDouble(e.getValue()); } break; } case TIMESPAN_LIST: { - if ( protocolVersion < 1 ) { - throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); - } final QTimespan[] list = (QTimespan[]) obj; writer.writeInt(list.length); - for ( final QTimespan a : list ) { - writer.writeLong(a.getValue()); + for ( final QTimespan e : list ) { + writer.writeLong(e.getValue()); } break; } case MINUTE_LIST: { final QMinute[] list = (QMinute[]) obj; writer.writeInt(list.length); - for ( final QMinute a : list ) { - writer.writeInt(a.getValue()); + for ( final QMinute e : list ) { + writer.writeInt(e.getValue()); } break; } case SECOND_LIST: { final QSecond[] list = (QSecond[]) obj; writer.writeInt(list.length); - for ( final QSecond a : list ) { - writer.writeInt(a.getValue()); + for ( final QSecond e : list ) { + writer.writeInt(e.getValue()); } break; } case TIME_LIST: { final QTime[] list = (QTime[]) obj; writer.writeInt(list.length); - for ( final QTime a : list ) { - writer.writeInt(a.getValue()); + for ( final QTime e : list ) { + writer.writeInt(e.getValue()); + } + break; + } + } + } + + @SuppressWarnings("incomplete-switch") + protected void writeCollection( final Collection collection ) throws IOException, QException { + final Iterator it = collection.iterator(); + QType qtype = QType.GENERAL_LIST; + if ( it.hasNext() ) { + final Object firstElem = it.next(); + qtype = firstElem != null && toQ.containsKey(firstElem.getClass()) ? toQ.get(firstElem.getClass()) : QType.GENERAL_LIST; + if ( qtype != QType.STRING ) { + qtype = QType.valueOf((byte) (-1 * qtype.getTypeCode())); + } else { + qtype = QType.GENERAL_LIST; + } + } + + checkProtocolVersionCompatibility(qtype); + + writer.writeByte(qtype.getTypeCode()); + writer.writeByte((byte) 0); // attributes + writer.writeInt(collection.size()); + + switch ( qtype ) { + case BOOL_LIST: { + for ( final Object e : collection ) { + writer.writeByte((byte) ((Boolean) e ? 1 : 0)); + } + break; + } + case GUID_LIST: { + for ( final Object e : collection ) { + writeGuid((UUID) e); + } + break; + } + case BYTE_LIST: { + for ( final Object e : collection ) { + writer.writeByte((Byte) e); + } + break; + } + case SHORT_LIST: { + for ( final Object e : collection ) { + writer.writeShort((Short) e); + } + break; + } + case INT_LIST: { + for ( final Object e : collection ) { + writer.writeInt((Integer) e); + } + break; + } + case LONG_LIST: { + for ( final Object e : collection ) { + writer.writeLong((Long) e); + } + break; + } + case FLOAT_LIST: { + for ( final Object e : collection ) { + writer.writeFloat((Float) e); + } + break; + } + case DOUBLE_LIST: { + for ( final Object e : collection ) { + writer.writeDouble((Double) e); + } + break; + } + case SYMBOL_LIST: { + for ( final Object e : collection ) { + writeSymbol((String) e); + } + break; + } + case TIMESTAMP_LIST: { + for ( final Object e : collection ) { + writer.writeLong(((QTimestamp) e).getValue()); + } + break; + } + case MONTH_LIST: + for ( final Object e : collection ) { + writer.writeInt(((QMonth) e).getValue()); + } + break; + case DATE_LIST: { + for ( final Object e : collection ) { + writer.writeInt(((QDate) e).getValue()); + } + break; + } + case DATETIME_LIST: { + for ( final Object e : collection ) { + writer.writeDouble(((QDateTime) e).getValue()); + } + break; + } + case TIMESPAN_LIST: { + for ( final Object e : collection ) { + writer.writeLong(((QTimespan) e).getValue()); + } + break; + } + case MINUTE_LIST: { + for ( final Object e : collection ) { + writer.writeInt(((QMinute) e).getValue()); + } + break; + } + case SECOND_LIST: { + for ( final Object e : collection ) { + writer.writeInt(((QSecond) e).getValue()); + } + break; + } + case TIME_LIST: { + for ( final Object e : collection ) { + writer.writeInt(((QTime) e).getValue()); + } + break; + } + case GENERAL_LIST: { + for ( final Object e : collection ) { + writeObject(e); } break; } @@ -353,11 +474,7 @@ protected void writeSymbol( final String s ) throws IOException { writer.writeByte((byte) 0); } - protected void writeGuid( final UUID obj ) throws QException { - if ( protocolVersion < 3 ) { - throw new QWriterException("kdb+ protocol version violation: Guid not supported pre kdb+ v3.0"); - } - + protected void writeGuid( final UUID obj ) { writer.writeLongBigEndian(obj.getMostSignificantBits()); writer.writeLongBigEndian(obj.getLeastSignificantBits()); } @@ -477,7 +594,7 @@ protected void writeProjection( final QProjection p ) throws IOException, QExcep /** * Returns default mapping for particular java object to representative q type. - * + * * @param obj * Requested object * @return {@link QType} enum being a result of q serialization diff --git a/src/main/java/com/exxeleron/qjava/QType.java b/src/main/java/com/exxeleron/qjava/QType.java index 9110309..168dd7c 100644 --- a/src/main/java/com/exxeleron/qjava/QType.java +++ b/src/main/java/com/exxeleron/qjava/QType.java @@ -128,6 +128,23 @@ byte getTypeCode() { }); + /** + * Returns {@link QType} based on type code identifier. + * + * @param typecode + * type code identifier + * @return {@link QType} enum bound with type code identifier + * @throws IllegalArgumentException + * in case q type code is unknown + */ + public static QType valueOf( final byte typecode ) { + if ( lookup.containsKey(typecode) ) { + return lookup.get(typecode); + } else { + throw new IllegalArgumentException("Invalid q type code: " + typecode); + } + } + /** * Returns {@link QType} based on type code identifier. * diff --git a/src/main/java/com/exxeleron/qjava/QWriter.java b/src/main/java/com/exxeleron/qjava/QWriter.java index df2aba7..aa70a6c 100644 --- a/src/main/java/com/exxeleron/qjava/QWriter.java +++ b/src/main/java/com/exxeleron/qjava/QWriter.java @@ -125,4 +125,26 @@ public int write( final Object obj, final QConnection.MessageType msgType ) thro */ protected abstract void writeObject( final Object obj ) throws IOException, QException; + /** + * Verifies whether specified q type is compatible with current protocol version. + * + * @param qtype + * qtype to be checked + * @throws QWriterException + * if specified q type is not valid in current protocol + */ + protected void checkProtocolVersionCompatibility( final QType qtype ) throws QWriterException { + if ( protocolVersion < 3 && (qtype == QType.GUID || qtype == QType.GUID_LIST) ) { + throw new QWriterException("kdb+ protocol version violation: guid not supported pre kdb+ v3.0"); + } + + if ( protocolVersion < 1 && (qtype == QType.TIMESPAN || qtype == QType.TIMESPAN_LIST) ) { + throw new QWriterException("kdb+ protocol version violation: timespan not supported pre kdb+ v2.6"); + } + + if ( protocolVersion < 1 && (qtype == QType.TIMESTAMP || qtype == QType.TIMESTAMP_LIST) ) { + throw new QWriterException("kdb+ protocol version violation: timestamp not supported pre kdb+ v2.6"); + } + } + } diff --git a/src/test/java/com/exxeleron/qjava/QExpressions.java b/src/test/java/com/exxeleron/qjava/QExpressions.java index 2360514..2267c5f 100644 --- a/src/test/java/com/exxeleron/qjava/QExpressions.java +++ b/src/test/java/com/exxeleron/qjava/QExpressions.java @@ -18,6 +18,7 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -34,15 +35,15 @@ private void initExpressions() throws QException { reference.put("1+`", new Object[] { new QException("type") }); reference.put("()", new Object[] { new Object[0] }); reference.put("::", new Object[] { null }); - reference.put("1", new Object[] { 1L }); - reference.put("1i", new Object[] { 1 }); - reference.put("-234h", new Object[] { (short) -234 }); - reference.put("1b", new Object[] { true }); - reference.put("0x2a", new Object[] { (byte) 0x2a }); - reference.put("89421099511627575j", new Object[] { 89421099511627575L }); - reference.put("3.234", new Object[] { 3.234 }); - reference.put("5.5e", new Object[] { (float) 5.5 }); - reference.put("\"0\"", new Object[] { '0' }); + reference.put("1", new Object[] { 1L, Long.valueOf(1) }); + reference.put("1i", new Object[] { 1, Integer.valueOf(1) }); + reference.put("-234h", new Object[] { (short) -234, Short.valueOf((short) -234) }); + reference.put("1b", new Object[] { true, Boolean.TRUE }); + reference.put("0x2a", new Object[] { (byte) 0x2a, Byte.valueOf((byte) 0x2a) }); + reference.put("89421099511627575j", new Object[] { 89421099511627575L, Long.valueOf(89421099511627575L) }); + reference.put("3.234", new Object[] { 3.234, Double.valueOf(3.234) }); + reference.put("5.5e", new Object[] { (float) 5.5, Float.valueOf((float) 5.5) }); + reference.put("\"0\"", new Object[] { '0', Character.valueOf('0') }); reference.put("\"abc\"", new Object[] { "abc".toCharArray() }); reference.put("\"\"", new Object[] { "".toCharArray() }); reference.put("\"quick brown fox jumps over a lazy dog\"", new Object[] { "quick brown fox jumps over a lazy dog".toCharArray() }); @@ -75,23 +76,32 @@ private void initExpressions() throws QException { reference.put("0Nu", new Object[] { QType.getQNull(QType.MINUTE) }); reference.put("0Nv", new Object[] { QType.getQNull(QType.SECOND) }); reference.put("0Nt", new Object[] { QType.getQNull(QType.TIME) }); - reference.put("(0b;1b;0b)", new Object[] { new boolean[] { false, true, false }, new Boolean[] { false, true, false } }); - reference.put("(0x01;0x02;0xff)", new Object[] { new byte[] { 1, 2, (byte) 255 }, new Byte[] { 1, 2, (byte) 255 } }); - reference.put("(1h;2h;3h)", new Object[] { new short[] { 1, 2, 3 }, new Short[] { 1, 2, 3 } }); - reference.put("1 2 3", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L } }); - reference.put("(1i;2i;3i)", new Object[] { new int[] { 1, 2, 3 }, new Integer[] { 1, 2, 3 } }); - reference.put("(1j;2j;3j)", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L } }); - reference.put("(5.5e; 8.5e)", new Object[] { new float[] { 5.5f, 8.5f }, new Float[] { 5.5f, 8.5f } }); - reference.put("3.23 6.46", new Object[] { new double[] { 3.23, 6.46 }, new Double[] { 3.23, 6.46 } }); + reference + .put("(0b;1b;0b)", + new Object[] { new boolean[] { false, true, false }, new Boolean[] { false, true, false }, + Arrays.asList(new Boolean[] { false, true, false }) }); + reference.put("(0x01;0x02;0xff)", + new Object[] { new byte[] { 1, 2, (byte) 255 }, new Byte[] { 1, 2, (byte) 255 }, Arrays.asList(new Byte[] { 1, 2, (byte) 255 }) }); + reference.put("(1h;2h;3h)", new Object[] { new short[] { 1, 2, 3 }, new Short[] { 1, 2, 3 }, Arrays.asList(new Short[] { 1, 2, 3 }) }); + reference.put("1 2 3", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L }, Arrays.asList(new Long[] { 1L, 2L, 3L }) }); + reference.put("(1i;2i;3i)", new Object[] { new int[] { 1, 2, 3 }, new Integer[] { 1, 2, 3 }, Arrays.asList(new Integer[] { 1, 2, 3 }) }); + reference.put("(1j;2j;3j)", new Object[] { new long[] { 1, 2, 3 }, new Long[] { 1L, 2L, 3L }, Arrays.asList(new Long[] { 1L, 2L, 3L }) }); + reference.put("(5.5e; 8.5e)", new Object[] { new float[] { 5.5f, 8.5f }, new Float[] { 5.5f, 8.5f }, Arrays.asList(new Float[] { 5.5f, 8.5f }) }); + reference.put("3.23 6.46", new Object[] { new double[] { 3.23, 6.46 }, new Double[] { 3.23, 6.46 }, Arrays.asList(new Double[] { 3.23, 6.46 }) }); reference.put("(1;`bcd;\"0bc\";5.5e)", new Object[] { new Object[] { 1L, "bcd", "0bc".toCharArray(), (float) 5.5 } }); reference.put("(42;::;`foo)", new Object[] { new Object[] { 42L, null, "foo" } }); reference.put("(enlist 1h; 2; enlist 3j)", new Object[] { new Object[] { new short[] { 1 }, 2L, new long[] { 3 } } }); - reference.put("`the`quick`brown`fox", new Object[] { new String[] { "the", "quick", "brown", "fox" } }); - reference.put("``quick``fox", new Object[] { new String[] { "", "quick", "", "fox" } }); - reference.put("``", new Object[] { new String[] { "", "" } }); - reference.put("(\"quick\"; \"brown\"; \"fox\"; \"jumps\"; \"over\"; \"a lazy\"; \"dog\")", - new Object[] { new Object[] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray(), "jumps".toCharArray(), "over".toCharArray(), - "a lazy".toCharArray(), "dog".toCharArray() } }); + reference.put("`the`quick`brown`fox", + new Object[] { new String[] { "the", "quick", "brown", "fox" }, Arrays.asList(new String[] { "the", "quick", "brown", "fox" }) }); + reference.put("``quick``fox", new Object[] { new String[] { "", "quick", "", "fox" }, Arrays.asList(new String[] { "", "quick", "", "fox" }) }); + reference.put("``", new Object[] { new String[] { "", "" }, Arrays.asList(new String[] { "", "" }) }); + reference.put( + "(\"quick\"; \"brown\"; \"fox\"; \"jumps\"; \"over\"; \"a lazy\"; \"dog\")", + new Object[] { + new Object[] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray(), "jumps".toCharArray(), "over".toCharArray(), + "a lazy".toCharArray(), "dog".toCharArray() }, + Arrays.asList(new Object[] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray(), "jumps".toCharArray(), + "over".toCharArray(), "a lazy".toCharArray(), "dog".toCharArray() }) }); reference.put("(\"quick\"; \"brown\"; \"fox\")", new Object[] { new char[][] { "quick".toCharArray(), "brown".toCharArray(), "fox".toCharArray() } }); reference.put("2000.01.04D05:36:57.600 0Np", new Object[] { new QTimestamp[] { new QTimestamp(279417600000000L), new QTimestamp(Long.MIN_VALUE) } }); reference.put("(2001.01m; 0Nm)", new Object[] { new QMonth[] { new QMonth(12), new QMonth(Integer.MIN_VALUE) } }); From f67447a73ab0f8449ad63a76e679cd5cd0c198c8 Mon Sep 17 00:00:00 2001 From: Maciej Lach Date: Mon, 16 Nov 2015 09:05:21 +0100 Subject: [PATCH 5/5] Refactoring: ByteInputStream little/big endian reading --- .../com/exxeleron/qjava/ByteInputStream.java | 95 ++++++++++++++++--- .../com/exxeleron/qjava/DefaultQReader.java | 8 +- .../java/com/exxeleron/qjava/QReader.java | 1 - 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/exxeleron/qjava/ByteInputStream.java b/src/main/java/com/exxeleron/qjava/ByteInputStream.java index c2006bc..53c56a5 100644 --- a/src/main/java/com/exxeleron/qjava/ByteInputStream.java +++ b/src/main/java/com/exxeleron/qjava/ByteInputStream.java @@ -2,6 +2,7 @@ import java.io.UnsupportedEncodingException; import java.nio.ByteOrder; +import java.util.UUID; /** * Convenience class for converting wrapped byte buffer to primitive types. @@ -9,21 +10,27 @@ public final class ByteInputStream { private byte[] buffer; - private ByteOrder endianess; private int position; private String encoding; + private ByteInputStreamReader reader; + private ByteInputStreamReader readerLittleEndian; + private ByteInputStreamReader readerBigEndian; + /** * Creates new {@link ByteInputStream}. * * @param encoding - * encoding for symbols convertion + * encoding for symbols conversion * @param endianess * byte order of the input stream */ public ByteInputStream(final String encoding, final ByteOrder endianess) { this.encoding = encoding; - this.endianess = endianess; + this.readerLittleEndian = new ByteLittleEndianInputStream(); + this.readerBigEndian = new ByteBigEndianInputStream(); + + setOrder(endianess); } /** @@ -67,8 +74,7 @@ public byte get() { * @return the short */ public short getShort() { - final int x = buffer[position++], y = buffer[position++]; - return (short) (endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xff | y << 8 : x << 8 | y & 0xff); + return reader.getShort(); } /** @@ -77,8 +83,7 @@ public short getShort() { * @return the int */ public int getInt() { - final int x = getShort(), y = getShort(); - return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffff | y << 16 : x << 16 | y & 0xffff; + return reader.getInt(); } /** @@ -87,8 +92,7 @@ public int getInt() { * @return the long */ public long getLong() { - final int x = getInt(), y = getInt(); - return endianess == ByteOrder.LITTLE_ENDIAN ? x & 0xffffffffL | (long) y << 32 : (long) x << 32 | y & 0xffffffffL; + return reader.getLong(); } /** @@ -125,13 +129,24 @@ public String getSymbol() throws UnsupportedEncodingException { return (p == position - 1) ? "" : new String(buffer, p, position - 1 - p, encoding); } + /** + * Retrieves single {@link UUID} from the byte buffer. + * + * @return {@link UUID} + */ + public UUID getUUID() { + final long l1 = readerBigEndian.getLong(); + final long l2 = readerBigEndian.getLong(); + return new UUID(l1, l2); + } + /** * Retrieves byte order for the wrapped buffer. * * @return {@link ByteOrder} */ public ByteOrder getOrder() { - return endianess; + return reader.getOrder(); } /** @@ -141,7 +156,65 @@ public ByteOrder getOrder() { * byte order */ public void setOrder( final ByteOrder endianess ) { - this.endianess = endianess; + this.reader = endianess.equals(ByteOrder.LITTLE_ENDIAN) ? readerLittleEndian : readerBigEndian; + } + + private interface ByteInputStreamReader { + + public abstract short getShort(); + + public abstract int getInt(); + + public abstract long getLong(); + + public abstract ByteOrder getOrder(); + + } + + private class ByteBigEndianInputStream implements ByteInputStreamReader { + + public short getShort() { + final int x = buffer[position++], y = buffer[position++]; + return (short) (x << 8 | y & 0xff); + } + + public int getInt() { + final int x = getShort(), y = getShort(); + return x << 16 | y & 0xffff; + } + + public long getLong() { + final int x = getInt(), y = getInt(); + return (long) x << 32 | y & 0xffffffffL; + } + + public ByteOrder getOrder() { + return ByteOrder.BIG_ENDIAN; + } + + } + + private class ByteLittleEndianInputStream implements ByteInputStreamReader { + + public short getShort() { + final int x = buffer[position++], y = buffer[position++]; + return (short) (x & 0xff | y << 8); + } + + public int getInt() { + final int x = getShort(), y = getShort(); + return (x & 0xffff | y << 16); + } + + public long getLong() { + final int x = getInt(), y = getInt(); + return (x & 0xffffffffL | (long) y << 32); + } + + public ByteOrder getOrder() { + return ByteOrder.BIG_ENDIAN; + } + } } \ No newline at end of file diff --git a/src/main/java/com/exxeleron/qjava/DefaultQReader.java b/src/main/java/com/exxeleron/qjava/DefaultQReader.java index 64f3009..162370c 100644 --- a/src/main/java/com/exxeleron/qjava/DefaultQReader.java +++ b/src/main/java/com/exxeleron/qjava/DefaultQReader.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.nio.ByteOrder; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -235,12 +234,7 @@ protected Object readList( final QType qtype ) throws QException, UnsupportedEnc } protected UUID readGuid() { - final ByteOrder currentOrder = reader.getOrder(); - reader.setOrder(ByteOrder.BIG_ENDIAN); - final long l1 = reader.getLong(); - final long l2 = reader.getLong(); - reader.setOrder(currentOrder); - return new UUID(l1, l2); + return reader.getUUID(); } protected Object[] readGeneralList() throws QException, IOException { diff --git a/src/main/java/com/exxeleron/qjava/QReader.java b/src/main/java/com/exxeleron/qjava/QReader.java index 3c39830..1db65be 100644 --- a/src/main/java/com/exxeleron/qjava/QReader.java +++ b/src/main/java/com/exxeleron/qjava/QReader.java @@ -84,7 +84,6 @@ public QMessage read( final boolean raw ) throws IOException, QException { stream.readFully(header, 0, 8); reader.wrap(header); - // TODO: create 2 instances per each endian and select one based on a flag final ByteOrder endianess = reader.get() == 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN; final QConnection.MessageType messageType = QConnection.MessageType.getMessageType(reader.get()); final boolean compressed = reader.get() == 1;