| /* |
| * 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.geode.internal.serialization; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.UnknownHostException; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| |
| public class StaticSerialization { |
| // array is null |
| public static final byte NULL_ARRAY = -1; |
| /** |
| * array len encoded as int in next 4 bytes |
| * |
| * @since GemFire 5.7 |
| */ |
| public static final byte INT_ARRAY_LEN = -3; |
| |
| public static final byte TIME_UNIT_NANOSECONDS = -1; |
| public static final byte TIME_UNIT_MICROSECONDS = -2; |
| public static final byte TIME_UNIT_MILLISECONDS = -3; |
| public static final byte TIME_UNIT_SECONDS = -4; |
| /** |
| * array len encoded as unsigned short in next 2 bytes |
| * |
| * @since GemFire 5.7 |
| */ |
| public static final byte SHORT_ARRAY_LEN = -2; |
| public static final int MAX_BYTE_ARRAY_LEN = (byte) -4 & 0xFF; |
| // Variable Length long encoded as int in next 4 bytes |
| public static final byte INT_VL = 126; |
| // Variable Length long encoded as long in next 8 bytes |
| public static final byte LONG_VL = 127; |
| public static final int MAX_BYTE_VL = 125; |
| |
| @MakeNotStatic("not tied to the cache lifecycle") |
| private static final ThreadLocalByteArrayCache threadLocalByteArrayCache = |
| new ThreadLocalByteArrayCache(65535); |
| |
| public static void writeInetAddress(InetAddress address, DataOutput out) throws IOException { |
| writeByteArray((address != null) ? address.getAddress() : null, out); |
| } |
| |
| public static void writeByteArray(byte[] array, DataOutput out) throws IOException { |
| int len = 0; |
| if (array != null) { |
| len = array.length; |
| } |
| writeByteArray(array, len, out); |
| } |
| |
| public static void writeByteArray(byte[] array, int len, DataOutput out) throws IOException { |
| |
| int length = len; // to avoid warnings about parameter assignment |
| |
| if (array == null) { |
| length = -1; |
| } else { |
| if (length > array.length) { |
| length = array.length; |
| } |
| } |
| writeArrayLength(length, out); |
| if (length > 0) { |
| out.write(array, 0, length); |
| } |
| } |
| |
| public static void writeArrayLength(int len, DataOutput out) throws IOException { |
| if (len == -1) { |
| out.writeByte(NULL_ARRAY); |
| } else if (len <= MAX_BYTE_ARRAY_LEN) { |
| out.writeByte(len); |
| } else if (len <= 0xFFFF) { |
| out.writeByte(SHORT_ARRAY_LEN); |
| out.writeShort(len); |
| } else { |
| out.writeByte(INT_ARRAY_LEN); |
| out.writeInt(len); |
| } |
| } |
| |
| public static int readArrayLength(DataInput in) throws IOException { |
| byte code = in.readByte(); |
| if (code == NULL_ARRAY) { |
| return -1; |
| } else { |
| int result = ubyteToInt(code); |
| if (result > MAX_BYTE_ARRAY_LEN) { |
| if (code == SHORT_ARRAY_LEN) { |
| return in.readUnsignedShort(); |
| } else if (code == INT_ARRAY_LEN) { |
| return in.readInt(); |
| } else { |
| throw new IllegalStateException("unexpected array length code=" + code); |
| } |
| } |
| return result; |
| } |
| } |
| |
| |
| private static int ubyteToInt(byte ub) { |
| return ub & 0xFF; |
| } |
| |
| public static void writeString(String value, DataOutput out) throws IOException { |
| |
| if (value == null) { |
| out.writeByte(DSCODE.NULL_STRING.toByte()); |
| |
| } else { |
| // writeUTF is expensive - it creates a char[] to fetch |
| // the string's contents, iterates over the array to compute the |
| // encoded length, creates a byte[] to hold the encoded bytes, |
| // iterates over the char[] again to create the encode bytes, |
| // then writes the bytes. Since we usually deal with ISO-8859-1 |
| // strings, we can accelerate this by accessing chars directly |
| // with charAt and fill a single-byte buffer. If we run into |
| // a multibyte char, we revert to using writeUTF() |
| int len = value.length(); |
| int utfLen = len; // added for bug 40932 |
| for (int i = 0; i < len; i++) { |
| char c = value.charAt(i); |
| if ((c <= 0x007F) && (c >= 0x0001)) { |
| // nothing needed |
| } else if (c > 0x07FF) { |
| utfLen += 2; |
| } else { |
| utfLen += 1; |
| } |
| // Note we no longer have an early out when we detect the first |
| // non-ascii char because we need to compute the utfLen for bug 40932. |
| // This is not a performance problem because most strings are ascii |
| // and they never did the early out. |
| } |
| boolean writeUTF = utfLen > len; |
| if (writeUTF) { |
| if (utfLen > 0xFFFF) { |
| out.writeByte(DSCODE.HUGE_STRING.toByte()); |
| out.writeInt(len); |
| out.writeChars(value); |
| } else { |
| out.writeByte(DSCODE.STRING.toByte()); |
| out.writeUTF(value); |
| } |
| } else { |
| if (len > 0xFFFF) { |
| out.writeByte(DSCODE.HUGE_STRING_BYTES.toByte()); |
| out.writeInt(len); |
| out.writeBytes(value); |
| } else { |
| out.writeByte(DSCODE.STRING_BYTES.toByte()); |
| out.writeShort(len); |
| out.writeBytes(value); |
| } |
| } |
| } |
| } |
| |
| public static String readString(DataInput in, byte header) throws IOException { |
| return readString(in, DscodeHelper.toDSCODE(header)); |
| } |
| |
| private static String readString(DataInput in, DSCODE dscode) throws IOException { |
| switch (dscode) { |
| case STRING_BYTES: |
| return readStringBytesFromDataInput(in, in.readUnsignedShort()); |
| case STRING: |
| return readStringUTFFromDataInput(in); |
| case NULL_STRING: { |
| return null; |
| } |
| case HUGE_STRING_BYTES: |
| return readStringBytesFromDataInput(in, in.readInt()); |
| case HUGE_STRING: |
| return readHugeStringFromDataInput(in); |
| default: |
| throw new IOException("Unknown String header " + dscode); |
| } |
| } |
| |
| private static String readHugeStringFromDataInput(DataInput in) throws IOException { |
| int len = in.readInt(); |
| char[] buf = new char[len]; |
| for (int i = 0; i < len; i++) { |
| buf[i] = in.readChar(); |
| } |
| return new String(buf); |
| } |
| |
| public static String[] readStringArray(DataInput in) throws IOException { |
| |
| int length = readArrayLength(in); |
| if (length == -1) { |
| return null; |
| } else { |
| String[] array = new String[length]; |
| for (int i = 0; i < length; i++) { |
| array[i] = readString(in); |
| } |
| return array; |
| } |
| } |
| |
| private static String readStringUTFFromDataInput(DataInput in) throws IOException { |
| return in.readUTF(); |
| } |
| |
| |
| |
| private static String readStringBytesFromDataInput(DataInput dataInput, int len) |
| throws IOException { |
| if (len == 0) { |
| return ""; |
| } |
| byte[] buf = getThreadLocalByteArray(len); |
| dataInput.readFully(buf, 0, len); |
| return new String(buf, 0, 0, len); // intentionally using deprecated constructor |
| } |
| |
| /** |
| * Reads an instance of <code>String</code> from a <code>DataInput</code>. The return value may be |
| * <code>null</code>. |
| * |
| * @throws IOException A problem occurs while reading from <code>in</code> |
| * |
| * @see #writeString |
| */ |
| public static String readString(DataInput in) throws IOException { |
| return readString(in, in.readByte()); |
| } |
| |
| public static InetAddress readInetAddress(DataInput in) throws IOException { |
| |
| byte[] address = readByteArray(in); |
| if (address == null) { |
| return null; |
| } |
| |
| try { |
| InetAddress addr = InetAddress.getByAddress(address); |
| return addr; |
| } catch (UnknownHostException ex) { |
| throw new IOException("While reading an InetAddress", ex); |
| } |
| |
| } |
| |
| public static void writeStringArray(String[] array, DataOutput out) throws IOException { |
| int length; |
| if (array == null) { |
| length = -1; |
| } else { |
| length = array.length; |
| } |
| writeArrayLength(length, out); |
| if (length > 0) { |
| for (int i = 0; i < length; i++) { |
| writeString(array[i], out); |
| } |
| } |
| } |
| |
| public static void writeInteger(Integer value, DataOutput out) throws IOException { |
| out.writeInt(value); |
| } |
| |
| public static <K, V> HashMap<K, V> readHashMap(DataInput in, DeserializationContext context) |
| throws IOException, ClassNotFoundException { |
| |
| int size = readArrayLength(in); |
| if (size == -1) { |
| return null; |
| } else { |
| HashMap<K, V> map = new HashMap<>(size); |
| for (int i = 0; i < size; i++) { |
| K key = (K) context.getDeserializer().readObject(in); |
| V value = (V) context.getDeserializer().readObject(in); |
| map.put(key, value); |
| } |
| |
| return map; |
| } |
| } |
| |
| |
| public static int[] readIntArray(DataInput in) throws IOException { |
| int length = readArrayLength(in); |
| if (length == -1) { |
| return null; |
| } else { |
| int[] array = new int[length]; |
| for (int i = 0; i < length; i++) { |
| array[i] = in.readInt(); |
| } |
| return array; |
| } |
| } |
| |
| public static byte[] readByteArray(DataInput in) throws IOException { |
| |
| int length = readArrayLength(in); |
| if (length == -1) { |
| return null; |
| } else { |
| byte[] array = new byte[length]; |
| in.readFully(array, 0, length); |
| return array; |
| } |
| } |
| |
| public static void writeIntArray(int[] array, DataOutput out) throws IOException { |
| int length; |
| if (array == null) { |
| length = -1; |
| } else { |
| length = array.length; |
| } |
| writeArrayLength(length, out); |
| |
| if (length > 0) { |
| for (int i = 0; i < length; i++) { |
| out.writeInt(array[i]); |
| } |
| } |
| } |
| |
| |
| public static void writeHashMap(Map<?, ?> map, DataOutput out, SerializationContext context) |
| throws IOException { |
| int size; |
| if (map == null) { |
| size = -1; |
| } else { |
| size = map.size(); |
| } |
| writeArrayLength(size, out); |
| if (size > 0) { |
| for (Map.Entry<?, ?> entry : map.entrySet()) { |
| context.getSerializer().writeObject(entry.getKey(), out); |
| context.getSerializer().writeObject(entry.getValue(), out); |
| } |
| } |
| } |
| |
| |
| |
| /** |
| * Returns a byte array for use by the calling thread. |
| * The returned byte array may be longer than minimumLength. |
| * The byte array belongs to the calling thread but callers must |
| * be careful to not call other methods that may also use this |
| * byte array. |
| */ |
| public static byte[] getThreadLocalByteArray(int minimumLength) { |
| return threadLocalByteArrayCache.get(minimumLength); |
| } |
| } |