blob: edd665b4d3560b0784146f6598ddde3f5fc0778e [file] [log] [blame]
/*
* 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);
}
}