/* * #%L * Nazgul Project: nazgul-core-reflection-api * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * 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. * #L% * */ package se.jguru.nazgul.core.reflection.api.serialization; import se.jguru.nazgul.core.algorithms.api.Validate; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * Trivial implementations of Java serialization and de-serialization into * ASCII-armoured byte[] instances. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid, jGuru Europe AB</a> */ public abstract class Serializer { // Internal state private static final int BASELENGTH = 128; private static final int LOOKUPLENGTH = 16; private static final byte[] HEX_NUMBER_TABLE = new byte[BASELENGTH]; private static final char[] LOOK_UP_HEX_ALPHABET = new char[LOOKUPLENGTH]; private static final String ERROR_PREFIX = "Incorrect ASCII-armoured instance: "; static { final int numDigits = 10; // Setup the Hex number table for (int i = 0; i < BASELENGTH; i++) { HEX_NUMBER_TABLE[i] = -1; } for (int i = '9'; i >= '0'; i--) { HEX_NUMBER_TABLE[i] = (byte) (i - '0'); } for (int i = 'F'; i >= 'A'; i--) { HEX_NUMBER_TABLE[i] = (byte) (i - 'A' + numDigits); } for (int i = 'f'; i >= 'a'; i--) { HEX_NUMBER_TABLE[i] = (byte) (i - 'a' + numDigits); } // Setup the Hex alphabet. for (int i = 0; i < numDigits; i++) { LOOK_UP_HEX_ALPHABET[i] = (char) ('0' + i); } for (int i = numDigits; i <= 15; i++) { LOOK_UP_HEX_ALPHABET[i] = (char) ('A' + i - numDigits); } } /** * Serializes the provided object to an ASCII-armoured byte array. * * @param toSerialize The object to serialize and wrap in an ASCII-armoured String. * @return The ASCII-armoured byte array from the provided {@code toSerialize} object, * or {@code null} if given a null argument. */ public static String serialize(final Serializable toSerialize) { // Check sanity String toReturn = null; if (toSerialize != null) { try { // Serialize the object to a byte array ByteArrayOutputStream bits = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bits); oos.writeObject(toSerialize); oos.flush(); // Wrap the byte array in ASCII armour. toReturn = encode(bits.toByteArray()); } catch (Exception e) { throw new IllegalArgumentException("Could not serialize object of type [" + toSerialize.getClass().getName() + "]", e); } } // All done. return toReturn; } /** * De-serializes the provided ASCII-armoured byte array to a Java Object. * * @param serializedInstance an ASCII-armoured byte array being a serialized Java Object. * @return The de-serialized Java Object. */ public static Object deSerialize(final String serializedInstance) { // Check sanity Object toReturn = null; if (serializedInstance != null) { try { ByteArrayInputStream decoded = new ByteArrayInputStream(decode(serializedInstance)); ObjectInputStream ois = new ObjectInputStream(decoded); toReturn = ois.readObject(); ois.close(); } catch (IllegalArgumentException e) { throw e; } catch (Exception e) { throw new IllegalArgumentException("Could not deserialize object.", e); } } // All done. return toReturn; } // // Private helpers // /** * Decodes the provided ASCII-armoured String into a byte array. * * @param encoded encoded string * @return return array of byte to encode */ private static byte[] decode(final String encoded) throws IllegalArgumentException { // Check sanity final int lengthData = encoded.length(); if (lengthData % 2 != 0) { throw new IllegalArgumentException(ERROR_PREFIX + "Must be an even number of bytes."); } final int returnSize = lengthData / 2; final byte[] toReturn = new byte[returnSize]; final char[] binaryData = encoded.toCharArray(); char char1, char2; for (int i = 0; i < returnSize; i++) { // Read the bytes in order char1 = binaryData[i * 2]; char2 = binaryData[i * 2 + 1]; // Check sanity Validate.isTrue(char1 < BASELENGTH, ERROR_PREFIX + "Each byte must be < " + BASELENGTH + " [Got: " + char1 + "]."); Validate.isTrue(char2 < BASELENGTH, ERROR_PREFIX + "Each byte must be < " + BASELENGTH + " [Got: " + char2 + "]."); // Restore the byte. toReturn[i] = (byte) ((HEX_NUMBER_TABLE[char1] << 4) | HEX_NUMBER_TABLE[char2]); } // All done. return toReturn; } /** * Encodes the provided byte array to an ASCII-armoured String. * * @param toEncode array of byte to encode * @return the ASCII encoded String */ private static String encode(final byte[] toEncode) { // toEncode should not be null final int returnSize = toEncode.length * 2; char[] toReturn = new char[returnSize]; int current; for (int i = 0; i < toEncode.length; i++) { // Get the data, and compensate for the signed nature of Java's bytes. current = toEncode[i]; if (current < 0) { current += 256; } // The bytes are assumed to be sent in LSB order. toReturn[i * 2] = LOOK_UP_HEX_ALPHABET[current >> 4]; toReturn[i * 2 + 1] = LOOK_UP_HEX_ALPHABET[current & 0xf]; } // All done. return new String(toReturn); } }