/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.isis.core.commons.encoding; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Typesafe writing and reading of fields, providing some level of integrity * checking of encoded messages. * * <p> * The {@link #write(DataOutputExtended, Object)} writes out field type and then * the data for that field type. The field type is represented by this * enumberation, with the {@link FieldType#getIdx() index} being what is written * to the stream (hence of type <tt>byte</tt> to keep small). * * <p> * Conversely, the {@link #read(DataInputExtended)} reads the field type and * then the data for that field type. */ public abstract class FieldType<T> { private static Logger LOG = LoggerFactory.getLogger(FieldType.class); private static String LOG_INDENT = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "; private static final int NULL_BIT = 64; // 2 to the 6 private static Map<Byte, FieldType<?>> cache = new HashMap<Byte, FieldType<?>>(); private static int next = 0; static enum Indenting { INDENT_ONLY, INDENT_AND_OUTDENT; } public static FieldType<Boolean> BOOLEAN = new FieldType<Boolean>((byte) next++, Boolean.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Boolean value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeBoolean(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Boolean doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final boolean value = inputStream.readBoolean(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<boolean[]> BOOLEAN_ARRAY = new FieldType<boolean[]>((byte) next++, boolean[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final boolean[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeBoolean(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected boolean[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final boolean[] values = new boolean[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readBoolean(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Byte> BYTE = new FieldType<Byte>((byte) next++, Byte.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Byte value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeByte(value.byteValue()); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Byte doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final byte value = inputStream.readByte(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<byte[]> BYTE_ARRAY = new FieldType<byte[]>((byte) next++, byte[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final byte[] values) throws IOException { try { final DataOutputStream outputStream = output.getDataOutputStream(); final int length = values.length; outputStream.writeInt(length); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("length:").append(length).append(" [BYTE ARRAY]")); } // rather than looping through the array, // we take advantage of optimization built into DataOutputStream outputStream.write(values); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected byte[] doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { final StringBuilder msg = new StringBuilder().append("length:").append(length).append(" [BYTE ARRAY]"); log(this, msg); } final byte[] bytes = new byte[length]; readBytes(inputStream, bytes); return bytes; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } // rather than looping through the array, // we take advantage of optimization built into DataInputStream private void readBytes(final DataInputStream inputStream, final byte[] bytes) throws IOException { inputStream.read(bytes); } }; public static FieldType<Short> SHORT = new FieldType<Short>((byte) next++, Short.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Short value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeShort(value.shortValue()); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Short doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final short value = inputStream.readShort(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<short[]> SHORT_ARRAY = new FieldType<short[]>((byte) next++, short[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final short[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeShort(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected short[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final short[] values = new short[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readShort(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Integer> INTEGER = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(value.intValue()); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Integer doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int value = inputStream.readInt(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Integer> UNSIGNED_BYTE = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeByte(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Integer doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int value = inputStream.readUnsignedByte(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Integer> UNSIGNED_SHORT = new FieldType<Integer>((byte) next++, Integer.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Integer value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeShort(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Integer doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int value = inputStream.readUnsignedShort(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<int[]> INTEGER_ARRAY = new FieldType<int[]>((byte) next++, int[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final int[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeInt(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected int[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final int[] values = new int[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Long> LONG = new FieldType<Long>((byte) next++, Long.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Long value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeLong(value.intValue()); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Long doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final long value = inputStream.readLong(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<long[]> LONG_ARRAY = new FieldType<long[]>((byte) next++, long[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final long[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeLong(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected long[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final long[] values = new long[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readLong(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Character> CHAR = new FieldType<Character>((byte) next++, Character.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Character value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeLong(value.charValue()); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Character doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final char value = inputStream.readChar(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<char[]> CHAR_ARRAY = new FieldType<char[]>((byte) next++, char[].class, Indenting.INDENT_AND_OUTDENT) { // TODO: could perhaps optimize by writing out as a string @Override protected void doWrite(final DataOutputExtended output, final char[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeChar(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected char[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final char[] values = new char[length]; for (int i = 0; i < values.length; i++) { if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } values[i] = inputStream.readChar(); } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Float> FLOAT = new FieldType<Float>((byte) next++, Float.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Float value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeFloat(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Float doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final float value = inputStream.readFloat(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<float[]> FLOAT_ARRAY = new FieldType<float[]>((byte) next++, float[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final float[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeFloat(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected float[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final float[] values = new float[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readFloat(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Double> DOUBLE = new FieldType<Double>((byte) next++, Double.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Double value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeDouble(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Double doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final double value = inputStream.readDouble(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<double[]> DOUBLE_ARRAY = new FieldType<double[]>((byte) next++, double[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final double[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { outputStream.writeDouble(values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected double[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final double[] values = new double[length]; for (int i = 0; i < values.length; i++) { values[i] = inputStream.readDouble(); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<String> STRING = new FieldType<String>((byte) next++, String.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final String value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeUTF(value); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected String doRead(final DataInputExtended input) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final String value = inputStream.readUTF(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(value)); } return value; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<String[]> STRING_ARRAY = new FieldType<String[]>((byte) next++, String[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final String[] values) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { buf.append("length: ").append(values.length); } for (int i = 0; i < values.length; i++) { // using FieldType to write out takes care of null handling FieldType.STRING.write(output, values[i]); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected String[] doRead(final DataInputExtended input) throws IOException { try { final StringBuilder buf = new StringBuilder(); final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { buf.append("length: ").append(length); } final String[] values = new String[length]; for (int i = 0; i < values.length; i++) { // using FieldType to read in takes care of null handling values[i] = FieldType.STRING.read(input); if (LOG.isDebugEnabled()) { buf.append(i == 0 ? ": " : ", "); buf.append(values[i]); } } if (LOG.isDebugEnabled()) { log(this, buf); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } }; public static FieldType<Encodable> ENCODABLE = new FieldType<Encodable>((byte) next++, Encodable.class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final Encodable encodable) throws IOException { try { // write out class final String className = encodable.getClass().getName(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(className)); } output.writeUTF(className); // recursively encode encodable.encode(output); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Encodable doRead(final DataInputExtended input) throws IOException { try { // read in class name ... final String className = input.readUTF(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append(className)); } Class<?> cls; try { // ...obtain constructor cls = Thread.currentThread().getContextClassLoader().loadClass(className); final Constructor<?> constructor = cls.getConstructor(new Class[] { DataInputExtended.class }); // recursively decode return (Encodable) constructor.newInstance(new Object[] { input }); } catch (final ClassNotFoundException ex) { throw new FailedToDecodeException(ex); } catch (final IllegalArgumentException ex) { throw new FailedToDecodeException(ex); } catch (final InstantiationException ex) { throw new FailedToDecodeException(ex); } catch (final IllegalAccessException ex) { throw new FailedToDecodeException(ex); } catch (final InvocationTargetException ex) { throw new FailedToDecodeException(ex); } catch (final SecurityException ex) { throw new FailedToDecodeException(ex); } catch (final NoSuchMethodException ex) { throw new FailedToDecodeException(ex); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected boolean checksStream() { return false; } }; public static FieldType<Encodable[]> ENCODABLE_ARRAY = new FieldType<Encodable[]>((byte) next++, Encodable[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final Encodable[] values) throws IOException { try { final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("length: ").append(values.length)); } for (final Encodable encodable : values) { // using FieldType to write out takes care of null handling FieldType.ENCODABLE.write(output, encodable); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @SuppressWarnings("unchecked") @Override protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("length: ").append(length)); } final Q[] values = (Q[]) Array.newInstance(elementType, length); for (int i = 0; i < values.length; i++) { // using FieldType to read in takes care of null handling values[i] = (Q) FieldType.ENCODABLE.read(input); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected boolean checksStream() { return false; } }; public static FieldType<Serializable> SERIALIZABLE = new FieldType<Serializable>((byte) next++, Serializable.class, Indenting.INDENT_ONLY) { @Override protected void doWrite(final DataOutputExtended output, final Serializable value) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("[SERIALIZABLE]")); } // write out as blob of bytes final ObjectOutputStream oos = new ObjectOutputStream(output.getDataOutputStream()); oos.writeObject(value); oos.flush(); } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected Serializable doRead(final DataInputExtended input) throws IOException { try { if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("[SERIALIZABLE]")); } // read in a blob of bytes final ObjectInputStream ois = new ObjectInputStream(input.getDataInputStream()); try { return (Serializable) ois.readObject(); } catch (final ClassNotFoundException ex) { throw new FailedToDeserializeException(ex); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected boolean checksStream() { return false; } }; public static FieldType<Serializable[]> SERIALIZABLE_ARRAY = new FieldType<Serializable[]>((byte) next++, Serializable[].class, Indenting.INDENT_AND_OUTDENT) { @Override protected void doWrite(final DataOutputExtended output, final Serializable[] values) throws IOException { try { final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.writeInt(values.length); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("length: ").append(values.length)); } for (final Serializable value : values) { // using FieldType to write out takes care of null handling FieldType.SERIALIZABLE.write(output, value); } } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override @SuppressWarnings("unchecked") protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { try { final DataInputStream inputStream = input.getDataInputStream(); final int length = inputStream.readInt(); if (LOG.isDebugEnabled()) { log(this, new StringBuilder().append("length: ").append(length)); } final Q[] values = (Q[]) Array.newInstance(elementType, length); for (int i = 0; i < values.length; i++) { // using FieldType to read in takes care of null handling values[i] = (Q) FieldType.SERIALIZABLE.read(input); } return values; } finally { if (LOG.isDebugEnabled()) { unlog(this); } } } @Override protected boolean checksStream() { return false; } }; public static FieldType<?> get(final byte idx) { return cache.get(idx); } private final byte idx; private final Class<T> cls; private final Indenting indenting; private FieldType(final byte idx, final Class<T> cls, final Indenting indenting) { this.idx = idx; this.cls = cls; this.indenting = indenting; cache.put(idx, this); } public byte getIdx() { return idx; } public Class<T> getCls() { return cls; } /** * Whether this implementation checks ordering in the stream. * * <p> * Broadly, the type safe ones do, the {@link Encodable} and * {@link Serializable} ones do not. */ protected boolean checksStream() { return true; } public final T read(final DataInputExtended input) throws IOException { final DataInputStream inputStream = input.getDataInputStream(); final byte fieldTypeIdxAndNullability = inputStream.readByte(); final boolean isNull = fieldTypeIdxAndNullability >= NULL_BIT; final byte fieldTypeIdx = (byte) (fieldTypeIdxAndNullability - (isNull ? NULL_BIT : 0)); try { final FieldType<?> fieldType = FieldType.get(fieldTypeIdx); if (fieldType == null || (fieldType.checksStream() && fieldType != this)) { throw new IllegalStateException("Mismatch in stream: expected " + this + " but got " + fieldType + " (" + fieldTypeIdx + ")"); } if (isNull && LOG.isDebugEnabled()) { // only log if reading a null; otherwise actual value read // logged later log(this, new StringBuilder().append("(null)")); } if (isNull) { return null; } else { return doRead(input); } } finally { if (isNull && LOG.isDebugEnabled()) { // only unlog if reading a null unlog(this); } } } public final <Q> Q[] readArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { final DataInputStream inputStream = input.getDataInputStream(); final byte fieldTypeIdxAndNullability = inputStream.readByte(); final boolean isNull = fieldTypeIdxAndNullability >= NULL_BIT; final byte fieldTypeIdx = (byte) (fieldTypeIdxAndNullability - (isNull ? NULL_BIT : 0)); try { final FieldType<?> fieldType = FieldType.get(fieldTypeIdx); if (fieldType.checksStream() && fieldType != this) { throw new IllegalStateException("Mismatch in stream: expected " + this + " but got " + fieldType); } if (isNull && LOG.isDebugEnabled()) { // only log if reading a null; otherwise actual value read // logged later log(this, new StringBuilder().append("(null)")); } if (isNull) { return null; } else { return doReadArray(input, elementType); } } finally { if (isNull && LOG.isDebugEnabled()) { // only unlog if reading a null unlog(this); } } } public final void write(final DataOutputExtended output, final T value) throws IOException { byte fieldTypeIdxAndNullability = getIdx(); final boolean isNull = value == null; if (isNull) { // set high order bit fieldTypeIdxAndNullability += NULL_BIT; } try { final DataOutputStream outputStream = output.getDataOutputStream(); outputStream.write(fieldTypeIdxAndNullability); if (isNull && LOG.isDebugEnabled()) { // only log if writing a null; otherwise actual value logged // later log(this, new StringBuilder().append("(null)")); } if (!isNull) { doWrite(output, value); } } finally { if (isNull && LOG.isDebugEnabled()) { // only unlog if writing a null unlog(this); } } } protected T doRead(final DataInputExtended input) throws IOException { throw new UnsupportedOperationException("not supported for this field type"); } protected <Q> Q[] doReadArray(final DataInputExtended input, final Class<Q> elementType) throws IOException { throw new UnsupportedOperationException("not supported for this field type"); } protected abstract void doWrite(DataOutputExtended output, T value) throws IOException; private boolean isIndentingAndOutdenting() { return indenting == Indenting.INDENT_AND_OUTDENT; } // /////////////////////////////////////////////////////// // debugging // /////////////////////////////////////////////////////// private static ThreadLocal<int[]> debugIndent = new ThreadLocal<int[]>(); private static void log(final FieldType<?> fieldType, final StringBuilder buf) { buf.insert(0, ": "); buf.insert(0, fieldType); if (fieldType.isIndentingAndOutdenting()) { buf.insert(0, "> "); } buf.insert(0, spaces(currentDebugLevel())); incrementDebugLevel(); LOG.debug(buf.toString()); } private static void unlog(final FieldType<?> fieldType) { unlog(fieldType, new StringBuilder()); } private static void unlog(final FieldType<?> fieldType, final StringBuilder buf) { if (fieldType.isIndentingAndOutdenting()) { buf.insert(0, "< "); } decrementDebugLevel(); if (fieldType.isIndentingAndOutdenting()) { buf.insert(0, spaces(currentDebugLevel())); LOG.debug(buf.toString()); } } private static String spaces(final int num) { return LOG_INDENT.substring(0, num); } private static int currentDebugLevel() { return debugIndent()[0]; } private static void incrementDebugLevel() { final int[] indentLevel = debugIndent(); indentLevel[0] += 2; } private static void decrementDebugLevel() { final int[] indentLevel = debugIndent(); indentLevel[0] -= 2; } private static int[] debugIndent() { int[] indentLevel = debugIndent.get(); if (indentLevel == null) { indentLevel = new int[1]; debugIndent.set(indentLevel); } return indentLevel; } // /////////////////////////////////////////////////////// // toString // /////////////////////////////////////////////////////// @Override public String toString() { return getCls().getSimpleName(); } }