package org.infinispan.marshall.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.function.BiConsumer;
import org.infinispan.commands.RemoteCommandsFactory;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.io.ByteBuffer;
import org.infinispan.commons.io.ExposedByteArrayOutputStream;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.commons.marshall.BufferSizePredictor;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.MarshallableTypeHints;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.marshall.NotSerializableException;
import org.infinispan.commons.marshall.SerializeFunctionWith;
import org.infinispan.commons.marshall.SerializeWith;
import org.infinispan.commons.marshall.StreamingMarshaller;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.factories.GlobalComponentRegistry;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.factories.annotations.Stop;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.marshall.core.ClassToExternalizerMap.IdToExternalizerMap;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* A globally-scoped marshaller. This is needed so that the transport layer
* can unmarshall requests even before it's known which cache's marshaller can
* do the job.
*
* @author Galder ZamarreƱo
* @since 5.0
*/
@Scope(Scopes.GLOBAL)
public class GlobalMarshaller implements StreamingMarshaller {
private static final Log log = LogFactory.getLog(GlobalMarshaller.class);
private static final boolean trace = log.isTraceEnabled();
static final int NOT_FOUND = -1;
static final int ID_NULL = 0x00;
static final int ID_PRIMITIVE = 0x01;
static final int ID_INTERNAL = 0x02;
static final int ID_EXTERNAL = 0x03;
static final int ID_ANNOTATED = 0x04;
static final int ID_UNKNOWN = 0x05;
/**
* The array will be encoded as follows:
*
* ID_ARRAY | flags + component type | component type info | array length | [element type | element type info] | array elements |
* 00000110 | ssncieee | depends on type | 0 - 4 bytes | [00000eee | depends on type ] | ... |
*
* Flags:
* ss: {@link #FLAG_ARRAY_EMPTY}, {@link #FLAG_ARRAY_SMALL}, {@link #FLAG_ARRAY_MEDIUM}, {@link #FLAG_ARRAY_LARGE}
* n: {@link #FLAG_ALL_NULL}
* i: {@link #FLAG_SINGLE_TYPE}
* c: {@link #FLAG_COMPONENT_TYPE_MATCH}
* eee: {@link #ID_INTERNAL}, {@link #ID_EXTERNAL}, {@link #ID_ANNOTATED}, {@link #ID_CLASS}
*
* Element type will be present only if the {@link #FLAG_SINGLE_TYPE} is set. In that case the array elements
* won't be preceded by the instance type identifier, this will be common for all elements.
*
* Multidimensional arrays are not supported ATM.
*/
static final int ID_ARRAY = 0x06;
static final int ID_CLASS = 0x07;
// Type is in last 3 bits
static final int TYPE_MASK = 0x07;
// Array size is in first 2 bits
static final int ARRAY_SIZE_MASK = 0xC0;
// All elements are of the same type
static final int FLAG_SINGLE_TYPE = 0x08;
// Component type matches to instance type
static final int FLAG_COMPONENT_TYPE_MATCH = 0x10;
// All elements of the array are null
static final int FLAG_ALL_NULL = 0x20;
// Length of the array
static final int FLAG_ARRAY_EMPTY = 0x00;
static final int FLAG_ARRAY_SMALL = 0x40;
static final int FLAG_ARRAY_MEDIUM = 0x80;
static final int FLAG_ARRAY_LARGE = 0xC0;
private final MarshallableTypeHints marshallableTypeHints = new MarshallableTypeHints();
private GlobalComponentRegistry gcr;
private RemoteCommandsFactory cmdFactory;
private ClassToExternalizerMap internalExts;
private IdToExternalizerMap reverseInternalExts;
private ClassToExternalizerMap externalExts;
private IdToExternalizerMap reverseExternalExts;
private ClassIdentifiers classIdentifiers;
private Marshaller external;
public GlobalMarshaller() {
}
public GlobalMarshaller(Marshaller external) {
this.external = external;
}
@Inject
public void inject(GlobalComponentRegistry gcr, RemoteCommandsFactory cmdFactory) {
this.gcr = gcr;
this.cmdFactory = cmdFactory;
}
@Override
@Start(priority = 8) // Should start after the externalizer table and before transport
public void start() {
internalExts = InternalExternalizers.load(this, gcr, cmdFactory);
reverseInternalExts = internalExts.reverseMap(128);
if (trace) {
log.tracef("Internal class to externalizer ids: %s", internalExts);
log.tracef("Internal reverse externalizers: %s", reverseInternalExts);
}
externalExts = ExternalExternalizers.load(gcr.getGlobalConfiguration());
reverseExternalExts = externalExts.reverseMap();
if (trace) {
log.tracef("External class to externalizer ids: %s", externalExts);
log.tracef("External reverse externalizers: %s", reverseExternalExts);
}
if (external == null) {
this.external = startDefaultExternalMarshaller(gcr.getGlobalConfiguration());
}
classIdentifiers = ClassIdentifiers.load(gcr.getGlobalConfiguration());
}
public Marshaller startDefaultExternalMarshaller(GlobalConfiguration globalCfg) {
StreamingMarshaller marshaller = new ExternalJBossMarshaller(this, globalCfg);
marshaller.start();
return marshaller;
}
@Override
@Stop(priority = 11) // Stop after transport to avoid send/receive and marshaller not being ready
public void stop() {
internalExts.clear();
reverseInternalExts.clear();
externalExts.clear();
reverseExternalExts.clear();
classIdentifiers = null;
stopDefaultExternalMarshaller();
}
public void stopDefaultExternalMarshaller() {
if (external instanceof StreamingMarshaller)
((StreamingMarshaller) external).stop();
}
@Override
public byte[] objectToByteBuffer(Object obj) throws IOException, InterruptedException {
try {
BytesObjectOutput out = writeObjectOutput(obj);
return out.toBytes(); // trim out unused bytes
} catch (java.io.NotSerializableException nse) {
if (log.isDebugEnabled()) log.debug("Object is not serializable", nse);
throw new NotSerializableException(nse.getMessage(), nse.getCause());
}
}
private BytesObjectOutput writeObjectOutput(Object obj) throws IOException {
BufferSizePredictor sizePredictor = marshallableTypeHints.getBufferSizePredictor(obj);
BytesObjectOutput out = writeObjectOutput(obj, sizePredictor.nextSize(obj));
sizePredictor.recordSize(out.pos);
return out;
}
private BytesObjectOutput writeObjectOutput(Object obj, int estimatedSize) throws IOException {
BytesObjectOutput out = new BytesObjectOutput(estimatedSize, this);
writeNullableObject(obj, out);
return out;
}
@Override
public Object objectFromByteBuffer(byte[] buf) throws IOException, ClassNotFoundException {
BytesObjectInput in = BytesObjectInput.from(buf, this);
return objectFromObjectInput(in);
}
private Object objectFromObjectInput(BytesObjectInput in) throws IOException, ClassNotFoundException {
return readNullableObject(in);
}
@Override
public ObjectOutput startObjectOutput(OutputStream os, boolean isReentrant, int estimatedSize) throws IOException {
BytesObjectOutput out = new BytesObjectOutput(estimatedSize, this);
return new StreamBytesObjectOutput(os, out);
}
@Override
public void objectToObjectStream(Object obj, ObjectOutput out) throws IOException {
out.writeObject(obj);
}
@Override
public void finishObjectOutput(ObjectOutput oo) {
try {
oo.flush();
} catch (IOException e) {
// ignored
}
}
@Override
public Object objectFromByteBuffer(byte[] bytes, int offset, int len) throws IOException, ClassNotFoundException {
// Ignore length since boundary checks are not so useful here where the
// unmarshalling code knows what to expect specifically. E.g. if reading
// a byte[] subset within it, it's always appended with length.
BytesObjectInput in = BytesObjectInput.from(bytes, offset, this);
return objectFromObjectInput(in);
}
@Override
public Object objectFromInputStream(InputStream is) throws IOException, ClassNotFoundException {
// This is a very limited use case, e.g. reading from a JDBC ResultSet InputStream
// So, this copying of the stream into a byte[] has not been problematic so far,
// though it's not really ideal.
int len = is.available();
ExposedByteArrayOutputStream bytes;
byte[] buf;
if(len > 0) {
bytes = new ExposedByteArrayOutputStream(len);
buf = new byte[Math.min(len, 1024)];
} else {
// Some input stream providers do not implement available()
bytes = new ExposedByteArrayOutputStream();
buf = new byte[1024];
}
int bytesRead;
while ((bytesRead = is.read(buf, 0, buf.length)) != -1) bytes.write(buf, 0, bytesRead);
return objectFromByteBuffer(bytes.getRawBuffer(), 0, bytes.size());
}
@Override
public boolean isMarshallable(Object o) throws Exception {
Class<?> clazz = o.getClass();
boolean containsMarshallable = marshallableTypeHints.isKnownMarshallable(clazz);
if (containsMarshallable) {
boolean marshallable = marshallableTypeHints.isMarshallable(clazz);
if (trace)
log.tracef("Marshallable type '%s' known and is marshallable=%b",
clazz.getName(), marshallable);
return marshallable;
} else {
if (isMarshallableCandidate(o)) {
boolean isMarshallable = true;
try {
objectToBuffer(o);
} catch (Exception e) {
isMarshallable = false;
throw e;
} finally {
marshallableTypeHints.markMarshallable(clazz, isMarshallable);
}
return isMarshallable;
}
return false;
}
}
private boolean isMarshallableCandidate(Object o) {
return o instanceof Serializable
|| internalExts.get(o.getClass()) != null
|| externalExts.get(o.getClass()) != null
|| o.getClass().getAnnotation(SerializeWith.class) != null
|| isExternalMarshallable(o);
}
private boolean isExternalMarshallable(Object o) {
try {
return external.isMarshallable(o);
} catch (Exception e) {
throw new NotSerializableException(
"Object of type " + o.getClass() + " expected to be marshallable", e);
}
}
@Override
public BufferSizePredictor getBufferSizePredictor(Object o) {
return marshallableTypeHints.getBufferSizePredictor(o.getClass());
}
@Override
public ByteBuffer objectToBuffer(Object o) throws IOException, InterruptedException {
try {
BytesObjectOutput out = writeObjectOutput(o);
return out.toByteBuffer();
} catch (java.io.NotSerializableException nse) {
if (log.isDebugEnabled()) log.debug("Object is not serializable", nse);
throw new NotSerializableException(nse.getMessage(), nse.getCause());
}
}
@Override
public byte[] objectToByteBuffer(Object obj, int estimatedSize) throws IOException, InterruptedException {
try {
BytesObjectOutput out = writeObjectOutput(obj, estimatedSize);
return out.toBytes();
} catch (java.io.NotSerializableException nse) {
if (log.isDebugEnabled()) log.debug("Object is not serializable", nse);
throw new NotSerializableException(nse.getMessage(), nse.getCause());
}
}
@Override
public ObjectInput startObjectInput(InputStream is, boolean isReentrant) {
throw new UnsupportedOperationException("No longer in use");
}
@Override
public void finishObjectInput(ObjectInput oi) {
throw new UnsupportedOperationException("No longer in use");
}
@Override
public Object objectFromObjectStream(ObjectInput in) {
throw new UnsupportedOperationException("No longer in use");
}
public <T> Externalizer<T> findExternalizerFor(Object obj) {
Class<?> clazz = obj.getClass();
Externalizer ext = internalExts.get(clazz);
if (ext == null) {
ext = externalExts.get(clazz);
if (ext == null)
ext = findAnnotatedExternalizer(clazz);
}
return ext;
}
void writeNullableObject(Object obj, BytesObjectOutput out) throws IOException {
if (obj == null)
out.writeByte(ID_NULL);
else
writeNonNullableObject(obj, out);
}
Object readNullableObject(BytesObjectInput in) throws IOException, ClassNotFoundException {
int type = in.readUnsignedByte();
return type == ID_NULL ? null : readNonNullableObject(type, in);
}
BiConsumer<ObjectOutput, Object> findWriter(Object obj) {
Class<?> clazz = obj.getClass();
AdvancedExternalizer internalExt = internalExts.get(clazz);
if (internalExt != null)
return (out, object) -> {
writeInternalClean(object, internalExt, out);
};
AdvancedExternalizer externalExt = externalExts.get(clazz);
if (externalExt != null)
return (out, object) -> writeExternalClean(object, externalExt, out);
return null;
}
<T> AdvancedExternalizer<T> findExternalizerIn(ObjectInput in) throws IOException {
int type = in.readUnsignedByte();
switch (type) {
case ID_INTERNAL:
return reverseInternalExts.get(in.readUnsignedByte());
case ID_EXTERNAL:
return reverseExternalExts.get(in.readInt());
default:
return null;
}
}
private void writeNonNullableObject(Object obj, BytesObjectOutput out) throws IOException {
Class<?> clazz = obj.getClass();
int id = Primitives.PRIMITIVES.get(clazz, NOT_FOUND);
if (id != NOT_FOUND) {
writePrimitive(obj, out, id);
} else if (clazz.isArray()) {
writeArray(clazz, obj, out);
} else {
AdvancedExternalizer ext = internalExts.get(clazz);
if (ext != null) {
writeInternal(obj, ext, out);
} else {
ext = externalExts.get(clazz);
if (ext != null) {
writeExternal(obj, ext, out);
} else {
Externalizer annotExt = findAnnotatedExternalizer(clazz);
if (annotExt != null)
writeAnnotated(obj, out, annotExt);
else
writeUnknown(obj, out);
}
}
}
}
private void writeArray(Class<?> clazz, Object array, BytesObjectOutput out) throws IOException {
out.writeByte(ID_ARRAY);
Class<?> componentType = clazz.getComponentType();
int length = Array.getLength(array);
boolean singleType = true;
Class<?> elementType = null;
int flags;
if (length == 0) {
flags = FLAG_ARRAY_EMPTY;
} else {
if (length <= Primitives.SMALL_ARRAY_MAX) {
flags = FLAG_ARRAY_SMALL;
} else if (length <= Primitives.MEDIUM_ARRAY_MAX) {
flags = FLAG_ARRAY_MEDIUM;
} else {
flags = FLAG_ARRAY_LARGE;
}
Object firstElement = Array.get(array, 0);
if (firstElement != null) {
elementType = firstElement.getClass();
}
for (int i = 1; i < length; ++i) {
Object element = Array.get(array, i);
if (element == null) {
if (elementType != null) {
singleType = false;
break;
}
} else if (element.getClass() != elementType) {
singleType = false;
break;
}
}
}
boolean componentTypeMatch = false;
if (singleType) {
flags |= FLAG_SINGLE_TYPE;
if (elementType == null) {
flags |= FLAG_ALL_NULL;
} else if (elementType == componentType) {
flags |= FLAG_COMPONENT_TYPE_MATCH;
componentTypeMatch = true;
}
}
AdvancedExternalizer ext;
if ((ext = internalExts.get(componentType)) != null) {
writeFlagsWithExternalizer(out, componentType, componentTypeMatch, ext, flags, ID_INTERNAL);
} else if ((ext = externalExts.get(componentType)) != null) {
writeFlagsWithExternalizer(out, componentType, componentTypeMatch, ext, flags, ID_EXTERNAL);
} else {
// We cannot use annotated externalizer to specify the component type, so we will
// clear the component type match flag and use class identifier or full name.
// Theoretically we could write annotated externalizer and component type saving one byte
// but it's not worth the complexity.
componentTypeMatch = false;
flags &= ~FLAG_COMPONENT_TYPE_MATCH;
int classId;
if ((classId = classIdentifiers.getId(componentType)) != -1) {
out.writeByte(flags | ID_CLASS);
if (classId < ClassIds.MAX_ID) {
out.writeByte(classId);
} else {
out.writeByte(ClassIds.MAX_ID);
out.writeInt(classId);
}
} else {
out.writeByte(flags | ID_UNKNOWN);
out.writeObject(componentType);
}
}
if (length == 0) {
} else if (length <= Primitives.SMALL_ARRAY_MAX) {
out.writeByte(length - Primitives.SMALL_ARRAY_MIN);
} else if (length <= Primitives.MEDIUM_ARRAY_MAX) {
out.writeShort(length - Primitives.MEDIUM_ARRAY_MIN);
} else {
out.writeInt(length);
}
if (singleType) {
Externalizer elementExt;
int primitiveId;
if (elementType == null) {
return;
} else if (componentTypeMatch) {
// Note: ext can be null here!
elementExt = ext;
} else if ((elementExt = internalExts.get(elementType)) != null) {
out.writeByte(ID_INTERNAL);
out.writeByte(((AdvancedExternalizer) elementExt).getId());
} else if ((elementExt = externalExts.get(elementType)) != null) {
out.writeByte(ID_EXTERNAL);
out.writeInt(((AdvancedExternalizer) elementExt).getId());
} else if ((elementExt = findAnnotatedExternalizer(elementType)) != null) {
// We could try if the externalizer class is registered in ClassIdentifier but most likely it is not,
// because if an user registered it, he could rather explicitly register AdvancedExternalizer instead.
out.writeByte(ID_ANNOTATED);
out.writeObject(elementExt.getClass());
} else if ((primitiveId = Primitives.PRIMITIVES.get(elementType, NOT_FOUND)) != NOT_FOUND) {
out.writeByte(ID_PRIMITIVE);
out.writeByte(primitiveId);
for (int i = 0; i < length; ++i) {
Object element = Array.get(array, i);
assert element != null;
Primitives.writeRawPrimitive(element, out, primitiveId);
}
// We are finished
return;
} else {
out.writeByte(ID_UNKNOWN);
// Do not write element type!
}
if (elementExt != null) {
for (int i = 0; i < length; ++i) {
Object element = Array.get(array, i);
assert element != null;
elementExt.writeObject(out, element);
}
} else {
// component type matches but this type does not have an externalizer
for (int i = 0; i < length; ++i) {
Object element = Array.get(array, i);
assert element != null;
writeRawUnknown(element, out);
}
}
} else {
for (int i = 0; i < length; ++i) {
Object element = Array.get(array, i);
writeNullableObject(element, out);
}
}
}
private void writeFlagsWithExternalizer(BytesObjectOutput out, Class<?> componentType, boolean componentTypeMatch, AdvancedExternalizer ext, int flags, int externalizerType) throws IOException {
// If the class can be identified by its externalizer, do that.
// If there's a component type match, write the externalizer here anyway, otherwise we would
// have to write it as element type later
// If the class cannot be uniquely identified by its externalizer, try class identifiers
boolean hasSingleClass = ext.getTypeClasses().size() == 1;
int classId = -1;
if (componentTypeMatch || hasSingleClass) {
out.writeByte(flags | externalizerType);
switch (externalizerType) {
case ID_INTERNAL:
out.writeByte(ext.getId());
break;
case ID_EXTERNAL:
out.writeInt(ext.getId());
break;
default:
throw new IllegalStateException();
}
if (!hasSingleClass) {
classId = classIdentifiers.getId(componentType);
}
} else if ((classId = classIdentifiers.getId(componentType)) >= 0) {
out.writeByte(flags | ID_CLASS);
} else {
out.writeByte(flags | ID_UNKNOWN);
}
if (!hasSingleClass) {
if (classId < 0) {
out.writeObject(componentType);
} else if (classId < ClassIds.MAX_ID){
out.writeByte(classId);
} else {
out.writeByte(ClassIds.MAX_ID);
out.writeInt(classId);
}
}
}
private void writeUnknown(Object obj, BytesObjectOutput out) throws IOException {
assert ExternallyMarshallable.isAllowed(obj) : "Check support for: " + obj.getClass();
out.writeByte(ID_UNKNOWN);
writeRawUnknown(obj, out);
}
private void writeRawUnknown(Object obj, BytesObjectOutput out) throws IOException {
if (external instanceof StreamingMarshaller)
((StreamingMarshaller) external).objectToObjectStream(obj, out);
else {
try {
byte[] bytes = external.objectToByteBuffer(obj);
out.writeInt(bytes.length);
out.write(bytes);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private void writeAnnotated(Object obj, BytesObjectOutput out, Externalizer ext) throws IOException {
out.writeByte(ID_ANNOTATED);
out.writeObject(ext.getClass());
ext.writeObject(out, obj);
}
private void writeInternal(Object obj, AdvancedExternalizer ext, ObjectOutput out) throws IOException {
out.writeByte(ID_INTERNAL);
out.writeByte(ext.getId());
ext.writeObject(out, obj);
}
private void writeInternalClean(Object obj, AdvancedExternalizer ext, ObjectOutput out) {
try {
writeInternal(obj, ext, out);
} catch (IOException e) {
throw new CacheException(e);
}
}
private void writeExternal(Object obj, AdvancedExternalizer ext, ObjectOutput out) throws IOException {
out.writeByte(ID_EXTERNAL);
out.writeInt(ext.getId());
ext.writeObject(out, obj);
}
private void writeExternalClean(Object obj, AdvancedExternalizer ext, ObjectOutput out) {
try {
writeExternal(obj, ext, out);
} catch (IOException e) {
throw new CacheException(e);
}
}
private void writePrimitive(Object obj, BytesObjectOutput out, int id) throws IOException {
out.writeByte(ID_PRIMITIVE);
Primitives.writePrimitive(obj, out, id);
}
private <T> Externalizer<T> findAnnotatedExternalizer(Class<?> clazz) {
try {
SerializeWith serialAnn = clazz.getAnnotation(SerializeWith.class);
if (serialAnn != null) {
return (Externalizer<T>) serialAnn.value().newInstance();
} else {
SerializeFunctionWith funcSerialAnn = clazz.getAnnotation(SerializeFunctionWith.class);
if (funcSerialAnn != null)
return (Externalizer<T>) funcSerialAnn.value().newInstance();
}
return null;
} catch (Exception e) {
throw new IllegalArgumentException(String.format(
"Cannot instantiate externalizer for %s", clazz), e);
}
}
private Object readNonNullableObject(int type, BytesObjectInput in) throws IOException, ClassNotFoundException {
switch (type) {
case ID_PRIMITIVE:
return Primitives.readPrimitive(in);
case ID_INTERNAL:
return readWithExternalizer(in.readUnsignedByte(), reverseInternalExts, in);
case ID_EXTERNAL:
return readWithExternalizer(in.readInt(), reverseExternalExts, in);
case ID_ANNOTATED:
return readAnnotated(in);
case ID_UNKNOWN:
return readUnknown(in);
case ID_ARRAY:
return readArray(in);
default:
throw new IOException("Unknown type: " + type);
}
}
private Object readWithExternalizer(int id, IdToExternalizerMap reverseMap, BytesObjectInput in)
throws IOException, ClassNotFoundException {
AdvancedExternalizer ext = reverseMap.get(id);
return ext.readObject(in);
}
private Object readAnnotated(BytesObjectInput in) throws IOException, ClassNotFoundException {
Class<? extends Externalizer> clazz =
(Class<? extends Externalizer>) in.readObject();
try {
Externalizer ext = clazz.newInstance();
return ext.readObject(in);
} catch (Exception e) {
throw new CacheException("Error instantiating class: " + clazz, e);
}
}
private Object readArray(BytesObjectInput in) throws IOException, ClassNotFoundException {
int flags = in.readByte();
int type = flags & TYPE_MASK;
AdvancedExternalizer<?> componentExt = null;
Class<?> extClazz = null;
Class<?> componentType;
switch (type) {
case ID_NULL:
case ID_PRIMITIVE:
case ID_ARRAY:
throw new IOException("Unexpected component type: " + type);
case ID_INTERNAL:
componentExt = reverseInternalExts.get(in.readByte());
componentType = getOrReadClass(in, componentExt);
break;
case ID_EXTERNAL:
componentExt = reverseExternalExts.get(in.readInt());
componentType = getOrReadClass(in, componentExt);
break;
case ID_ANNOTATED:
extClazz = (Class<?>) in.readObject();
// intentional no break
case ID_UNKNOWN:
componentType = (Class<?>) in.readObject();
break;
case ID_CLASS:
int classId = in.readByte();
if (classId < ClassIds.MAX_ID) {
componentType = classIdentifiers.getClass(classId);
} else {
componentType = classIdentifiers.getClass(in.readInt());
}
break;
default:
throw new IOException("Unknown component type: " + type);
}
int length;
int maskedSize = flags & ARRAY_SIZE_MASK;
switch (maskedSize) {
case FLAG_ARRAY_EMPTY:
length = 0;
break;
case FLAG_ARRAY_SMALL:
length = in.readUnsignedByte() + Primitives.SMALL_ARRAY_MIN;
break;
case FLAG_ARRAY_MEDIUM:
length = in.readUnsignedShort() + Primitives.MEDIUM_ARRAY_MIN;
break;
case FLAG_ARRAY_LARGE:
length = in.readInt();
break;
default:
throw new IOException("Unknown array size: " + maskedSize);
}
Object array = Array.newInstance(componentType, length);
if ((flags & FLAG_ALL_NULL) != 0) {
return array;
}
boolean singleType = (flags & FLAG_SINGLE_TYPE) != 0;
boolean componentTypeMatch = (flags & FLAG_COMPONENT_TYPE_MATCH) != 0;
// If component type match is set, this must be a single type
assert componentTypeMatch ? singleType : true;
if (singleType) {
Externalizer<?> ext;
if (componentTypeMatch) {
ext = getExternalizer(type, componentExt, extClazz);
} else {
type = in.readByte();
ext = readExternalizer(in, type);
}
if (ext != null) {
for (int i = 0; i < length; ++i) {
Array.set(array, i, ext.readObject(in));
}
} else {
switch (type) {
case ID_UNKNOWN:
for (int i = 0; i < length; ++i) {
Array.set(array, i, readUnknown(in));
}
return array;
case ID_PRIMITIVE:
int primitiveId = in.readByte();
for (int i = 0; i < length; ++i) {
Array.set(array, i, Primitives.readRawPrimitive(in, primitiveId));
}
return array;
default:
throw new IllegalStateException();
}
}
} else {
for (int i = 0; i < length; ++i) {
Array.set(array, i, readNullableObject(in));
}
}
return array;
}
private Externalizer<?> getExternalizer(int type, AdvancedExternalizer<?> componentExt, Class<?> extClazz) throws IOException {
switch (type) {
case ID_INTERNAL:
case ID_EXTERNAL:
return componentExt;
case ID_ANNOTATED:
try {
return (Externalizer<?>) extClazz.newInstance();
} catch (Exception e) {
throw new CacheException("Error instantiating class: " + extClazz, e);
}
case ID_UNKNOWN:
return null;
default:
throw new IOException("Unexpected component type: " + type);
}
}
private Externalizer<?> readExternalizer(BytesObjectInput in, int type) throws ClassNotFoundException, IOException {
Class<?> extClazz;
switch (type) {
case ID_INTERNAL:
return reverseInternalExts.get(in.readByte());
case ID_EXTERNAL:
return reverseExternalExts.get(in.readInt());
case ID_ANNOTATED:
extClazz = (Class<?>) in.readObject();
try {
return (Externalizer<?>) extClazz.newInstance();
} catch (Exception e) {
throw new CacheException("Error instantiating class: " + extClazz, e);
}
case ID_UNKNOWN:
case ID_PRIMITIVE:
return null;
default:
throw new IOException("Unexpected component type: " + type);
}
}
private Class<?> getOrReadClass(BytesObjectInput in, AdvancedExternalizer<?> componentExt) throws ClassNotFoundException, IOException {
if (componentExt.getTypeClasses().size() == 1) {
return componentExt.getTypeClasses().iterator().next();
} else {
return (Class<?>) in.readObject();
}
}
private Object readUnknown(BytesObjectInput in) throws IOException, ClassNotFoundException {
if (external instanceof StreamingMarshaller) {
try {
return ((StreamingMarshaller) external).objectFromObjectStream(in);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
} else {
int length = in.readInt();
byte[] bytes = new byte[length];
in.readFully(bytes);
return external.objectFromByteBuffer(bytes);
}
}
}