blob: fadbec61fad02f5a70e45d1525043e7222329ad0 [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.ignite.internal.marshaller.optimized;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotActiveException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.util.GridHandleTable;
import org.apache.ignite.internal.util.io.GridDataOutput;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.marshaller.MarshallerContext;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.HANDLE;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.JDK;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.NULL;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.classDescriptor;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getBoolean;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getByte;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getChar;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getDouble;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getFloat;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getInt;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getLong;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getObject;
import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.getShort;
/**
* Optimized object output stream.
*/
class OptimizedObjectOutputStream extends ObjectOutputStream {
/** */
private final GridHandleTable handles = new GridHandleTable(10, 3.00f);
/** */
private final GridDataOutput out;
/** */
private MarshallerContext ctx;
/** */
private OptimizedMarshallerIdMapper mapper;
/** */
private boolean requireSer;
/** */
private Object curObj;
/** */
private OptimizedClassDescriptor.ClassFields curFields;
/** */
private PutFieldImpl curPut;
/** */
private ConcurrentMap<Class, OptimizedClassDescriptor> clsMap;
/**
* @param out Output.
* @throws IOException In case of error.
*/
OptimizedObjectOutputStream(GridDataOutput out) throws IOException {
this.out = out;
}
/**
* @param clsMap Class descriptors by class map.
* @param ctx Context.
* @param mapper ID mapper.
* @param requireSer Require {@link Serializable} flag.
*/
void context(ConcurrentMap<Class, OptimizedClassDescriptor> clsMap,
MarshallerContext ctx,
OptimizedMarshallerIdMapper mapper,
boolean requireSer) {
this.clsMap = clsMap;
this.ctx = ctx;
this.mapper = mapper;
this.requireSer = requireSer;
}
/**
* @return Require {@link Serializable} flag.
*/
boolean requireSerializable() {
return requireSer;
}
/**
* @return Output.
*/
public GridDataOutput out() {
return out;
}
/** {@inheritDoc} */
@Override public void close() throws IOException {
reset();
ctx = null;
clsMap = null;
}
/** {@inheritDoc} */
@Override public void write(byte[] b) throws IOException {
out.write(b);
}
/** {@inheritDoc} */
@Override public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
/** {@inheritDoc} */
@Override protected void writeObjectOverride(Object obj) throws IOException {
Object oldObj = curObj;
OptimizedClassDescriptor.ClassFields oldFields = curFields;
PutFieldImpl oldPut = curPut;
try {
writeObject0(obj);
}
finally {
curObj = oldObj;
curFields = oldFields;
curPut = oldPut;
}
}
/**
* Writes object to stream.
*
* @param obj Object.
* @throws IOException In case of error.
*/
private void writeObject0(Object obj) throws IOException {
curObj = null;
curFields = null;
curPut = null;
if (obj == null)
writeByte(NULL);
else {
if (obj instanceof Throwable && !(obj instanceof Externalizable) || U.isEnum(obj.getClass())) {
// Avoid problems with differing Enum objects or Enum implementation class deadlocks.
writeByte(JDK);
try {
ctx.jdkMarshaller().marshal(obj, this);
}
catch (IgniteCheckedException e) {
IOException ioEx = e.getCause(IOException.class);
if (ioEx != null)
throw ioEx;
else
throw new IOException("Failed to serialize object with JDK marshaller: " + obj, e);
}
}
else {
OptimizedClassDescriptor desc = classDescriptor(
clsMap,
obj instanceof Object[] ? Object[].class : obj.getClass(),
ctx,
mapper);
if (desc.excluded()) {
writeByte(NULL);
return;
}
Object obj0 = desc.replace(obj);
if (obj0 == null) {
writeByte(NULL);
return;
}
int handle = -1;
if (!desc.isPrimitive() && !desc.isEnum() && !desc.isClass() && !desc.isProxy())
handle = handles.lookup(obj);
if (obj0 != obj) {
obj = obj0;
desc = classDescriptor(clsMap,
obj instanceof Object[] ? Object[].class : obj.getClass(),
ctx,
mapper);
}
try {
if (handle >= 0) {
writeByte(HANDLE);
writeInt(handle);
}
else
desc.write(this, obj);
}
catch (IOException e){
throw new IOException("Failed to serialize object [typeName=" +
desc.describedClass().getName() + ']', e);
}
}
}
}
/**
* Writes array to this stream.
*
* @param arr Array.
* @throws IOException In case of error.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
void writeArray(Object[] arr) throws IOException {
int len = arr.length;
writeInt(len);
for (int i = 0; i < len; i++) {
Object obj = arr[i];
writeObject0(obj);
}
}
/**
* Writes {@link UUID} to this stream.
*
* @param uuid UUID.
* @throws IOException In case of error.
*/
void writeUuid(UUID uuid) throws IOException {
writeLong(uuid.getMostSignificantBits());
writeLong(uuid.getLeastSignificantBits());
}
/**
* Writes {@link Properties} to this stream.
*
* @param props Properties.
* @param dfltsFieldOff Defaults field offset.
* @throws IOException In case of error.
*/
void writeProperties(Properties props, long dfltsFieldOff) throws IOException {
Properties dflts = (Properties)getObject(props, dfltsFieldOff);
if (dflts == null)
writeBoolean(true);
else {
writeBoolean(false);
writeObject0(dflts);
}
Set<String> names = props.stringPropertyNames();
writeInt(names.size());
for (String name : names) {
writeUTF(name);
writeUTF(props.getProperty(name));
}
}
/**
* Writes externalizable object.
*
* @param obj Object.
* @throws IOException In case of error.
*/
void writeExternalizable(Object obj) throws IOException {
Externalizable extObj = (Externalizable)obj;
extObj.writeExternal(this);
}
/**
* Writes serializable object.
*
* @param obj Object.
* @param mtds {@code writeObject} methods.
* @param fields class fields details.
* @throws IOException In case of error.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
void writeSerializable(Object obj, List<Method> mtds, OptimizedClassDescriptor.Fields fields)
throws IOException {
for (int i = 0; i < mtds.size(); i++) {
Method mtd = mtds.get(i);
if (mtd != null) {
curObj = obj;
curFields = fields.fields(i);
try {
mtd.invoke(obj, this);
}
catch (IllegalAccessException e) {
throw new IOException(e);
}
catch (InvocationTargetException e) {
throw new IOException(e.getCause());
}
}
else
writeFields(obj, fields.fields(i));
}
}
/**
* Writes {@link ArrayList}.
*
* @param list List.
* @throws IOException In case of error.
*/
@SuppressWarnings({"ForLoopReplaceableByForEach", "TypeMayBeWeakened"})
void writeArrayList(ArrayList<?> list) throws IOException {
int size = list.size();
writeInt(size);
for (int i = 0; i < size; i++)
writeObject0(list.get(i));
}
/**
* Writes {@link HashMap}.
*
* @param map Map.
* @param loadFactorFieldOff Load factor field offset.
* @param set Whether writing underlying map from {@link HashSet}.
* @throws IOException In case of error.
*/
@SuppressWarnings("TypeMayBeWeakened")
void writeHashMap(HashMap<?, ?> map, long loadFactorFieldOff, boolean set) throws IOException {
int size = map.size();
writeInt(size);
writeFloat(getFloat(map, loadFactorFieldOff));
for (Map.Entry<?, ?> e : map.entrySet()) {
writeObject0(e.getKey());
if (!set)
writeObject0(e.getValue());
}
}
/**
* Writes {@link HashSet}.
*
* @param set Set.
* @param mapFieldOff Map field offset.
* @param loadFactorFieldOff Load factor field offset.
* @throws IOException In case of error.
*/
void writeHashSet(HashSet<?> set, long mapFieldOff, long loadFactorFieldOff) throws IOException {
writeHashMap((HashMap<?, ?>)getObject(set, mapFieldOff), loadFactorFieldOff, true);
}
/**
* Writes {@link LinkedList}.
*
* @param list List.
* @throws IOException In case of error.
*/
@SuppressWarnings("TypeMayBeWeakened")
void writeLinkedList(LinkedList<?> list) throws IOException {
int size = list.size();
writeInt(size);
for (Object obj : list)
writeObject0(obj);
}
/**
* Writes {@link LinkedHashMap}.
*
* @param map Map.
* @param loadFactorFieldOff Load factor field offset.
* @param accessOrderFieldOff access order field offset.
* @param set Whether writing underlying map from {@link LinkedHashSet}.
* @throws IOException In case of error.
*/
@SuppressWarnings("TypeMayBeWeakened")
void writeLinkedHashMap(LinkedHashMap<?, ?> map, long loadFactorFieldOff, long accessOrderFieldOff, boolean set)
throws IOException {
int size = map.size();
writeInt(size);
writeFloat(getFloat(map, loadFactorFieldOff));
if (accessOrderFieldOff >= 0)
writeBoolean(getBoolean(map, accessOrderFieldOff));
else
writeBoolean(false);
for (Map.Entry<?, ?> e : map.entrySet()) {
writeObject0(e.getKey());
if (!set)
writeObject0(e.getValue());
}
}
/**
* Writes {@link LinkedHashSet}.
*
* @param set Set.
* @param mapFieldOff Map field offset.
* @param loadFactorFieldOff Load factor field offset.
* @throws IOException In case of error.
*/
void writeLinkedHashSet(LinkedHashSet<?> set, long mapFieldOff, long loadFactorFieldOff) throws IOException {
LinkedHashMap<?, ?> map = (LinkedHashMap<?, ?>)getObject(set, mapFieldOff);
writeLinkedHashMap(map, loadFactorFieldOff, -1, true);
}
/**
* Writes {@link Date}.
*
* @param date Date.
* @throws IOException In case of error.
*/
void writeDate(Date date) throws IOException {
writeLong(date.getTime());
}
/**
* Writes all non-static and non-transient field values to this stream.
*
* @param obj Object.
* @param fields Fields.
* @throws IOException In case of error.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
private void writeFields(Object obj, OptimizedClassDescriptor.ClassFields fields) throws IOException {
for (int i = 0; i < fields.size(); i++) {
OptimizedClassDescriptor.FieldInfo t = fields.get(i);
try {
switch (t.type()) {
case BYTE:
if (t.field() != null)
writeByte(getByte(obj, t.offset()));
break;
case SHORT:
if (t.field() != null)
writeShort(getShort(obj, t.offset()));
break;
case INT:
if (t.field() != null)
writeInt(getInt(obj, t.offset()));
break;
case LONG:
if (t.field() != null)
writeLong(getLong(obj, t.offset()));
break;
case FLOAT:
if (t.field() != null)
writeFloat(getFloat(obj, t.offset()));
break;
case DOUBLE:
if (t.field() != null)
writeDouble(getDouble(obj, t.offset()));
break;
case CHAR:
if (t.field() != null)
writeChar(getChar(obj, t.offset()));
break;
case BOOLEAN:
if (t.field() != null)
writeBoolean(getBoolean(obj, t.offset()));
break;
case OTHER:
if (t.field() != null)
writeObject0(getObject(obj, t.offset()));
}
}
catch (IOException e) {
throw new IOException("Failed to serialize field [name=" + t.name() + ']', e);
}
}
}
/**
* Writes array of {@code byte}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeByteArray(byte[] arr) throws IOException {
out.writeByteArray(arr);
}
/**
* Writes array of {@code short}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeShortArray(short[] arr) throws IOException {
out.writeShortArray(arr);
}
/**
* Writes array of {@code int}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeIntArray(int[] arr) throws IOException {
out.writeIntArray(arr);
}
/**
* Writes array of {@code long}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeLongArray(long[] arr) throws IOException {
out.writeLongArray(arr);
}
/**
* Writes array of {@code float}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeFloatArray(float[] arr) throws IOException {
out.writeFloatArray(arr);
}
/**
* Writes array of {@code double}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeDoubleArray(double[] arr) throws IOException {
out.writeDoubleArray(arr);
}
/**
* Writes array of {@code char}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeCharArray(char[] arr) throws IOException {
out.writeCharArray(arr);
}
/**
* Writes array of {@code boolean}s.
*
* @param arr Array.
* @throws IOException In case of error.
*/
void writeBooleanArray(boolean[] arr) throws IOException {
out.writeBooleanArray(arr);
}
/**
* Writes {@link String}.
*
* @param str String.
* @throws IOException In case of error.
*/
void writeString(String str) throws IOException {
out.writeUTF(str);
}
/** {@inheritDoc} */
@Override public void writeBoolean(boolean v) throws IOException {
out.writeBoolean(v);
}
/** {@inheritDoc} */
@Override public void writeByte(int v) throws IOException {
out.writeByte(v);
}
/** {@inheritDoc} */
@Override public void writeShort(int v) throws IOException {
out.writeShort(v);
}
/** {@inheritDoc} */
@Override public void writeChar(int v) throws IOException {
out.writeChar(v);
}
/** {@inheritDoc} */
@Override public void writeInt(int v) throws IOException {
out.writeInt(v);
}
/** {@inheritDoc} */
@Override public void writeLong(long v) throws IOException {
out.writeLong(v);
}
/** {@inheritDoc} */
@Override public void writeFloat(float v) throws IOException {
out.writeFloat(v);
}
/** {@inheritDoc} */
@Override public void writeDouble(double v) throws IOException {
out.writeDouble(v);
}
/** {@inheritDoc} */
@Override public void write(int b) throws IOException {
writeByte(b);
}
/** {@inheritDoc} */
@Override public void writeBytes(String s) throws IOException {
out.writeBytes(s);
}
/** {@inheritDoc} */
@Override public void writeChars(String s) throws IOException {
out.writeChars(s);
}
/** {@inheritDoc} */
@Override public void writeUTF(String s) throws IOException {
out.writeUTF(s);
}
/** {@inheritDoc} */
@Override public void useProtocolVersion(int ver) throws IOException {
// No-op.
}
/** {@inheritDoc} */
@Override public void writeUnshared(Object obj) throws IOException {
writeObject0(obj);
}
/** {@inheritDoc} */
@Override public void defaultWriteObject() throws IOException {
if (curObj == null)
throw new NotActiveException("Not in writeObject() call.");
writeFields(curObj, curFields);
}
/** {@inheritDoc} */
@Override public ObjectOutputStream.PutField putFields() throws IOException {
if (curObj == null)
throw new NotActiveException("Not in writeObject() call or fields already written.");
if (curPut == null)
curPut = new PutFieldImpl(this);
return curPut;
}
/** {@inheritDoc} */
@Override public void writeFields() throws IOException {
if (curObj == null)
throw new NotActiveException("Not in writeObject() call.");
if (curPut == null)
throw new NotActiveException("putFields() was not called.");
for (IgniteBiTuple<OptimizedFieldType, Object> t : curPut.objs) {
switch (t.get1()) {
case BYTE:
writeByte((Byte)t.get2());
break;
case SHORT:
writeShort((Short)t.get2());
break;
case INT:
writeInt((Integer)t.get2());
break;
case LONG:
writeLong((Long)t.get2());
break;
case FLOAT:
writeFloat((Float)t.get2());
break;
case DOUBLE:
writeDouble((Double)t.get2());
break;
case CHAR:
writeChar((Character)t.get2());
break;
case BOOLEAN:
writeBoolean((Boolean)t.get2());
break;
case OTHER:
writeObject0(t.get2());
}
}
}
/** {@inheritDoc} */
@Override public void reset() throws IOException {
out.reset();
handles.clear();
curObj = null;
curFields = null;
curPut = null;
}
/** {@inheritDoc} */
@Override public void flush() throws IOException {
// No-op.
}
/** {@inheritDoc} */
@Override public void drain() throws IOException {
// No-op.
}
/**
* Returns objects that were added to handles table.
* Used ONLY for test purposes.
*
* @return Handled objects.
*/
Object[] handledObjects() {
return handles.objects();
}
/**
* {@link PutField} implementation.
*/
private static class PutFieldImpl extends PutField {
/** Stream. */
private final OptimizedObjectOutputStream out;
/** Fields info. */
private final OptimizedClassDescriptor.ClassFields curFields;
/** Values. */
private final IgniteBiTuple<OptimizedFieldType, Object>[] objs;
/**
* @param out Output stream.
*/
@SuppressWarnings("unchecked")
private PutFieldImpl(OptimizedObjectOutputStream out) {
this.out = out;
curFields = out.curFields;
objs = new IgniteBiTuple[curFields.size()];
}
/** {@inheritDoc} */
@Override public void put(String name, boolean val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, byte val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, char val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, short val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, int val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, long val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, float val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, double val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void put(String name, Object val) {
value(name, val);
}
/** {@inheritDoc} */
@Override public void write(ObjectOutput out) throws IOException {
if (out != this.out)
throw new IllegalArgumentException("Wrong stream.");
this.out.writeFields();
}
/**
* @param name Field name.
* @param val Value.
*/
private void value(String name, Object val) {
int i = curFields.getIndex(name);
OptimizedClassDescriptor.FieldInfo info = curFields.get(i);
objs[i] = F.t(info.type(), val);
}
}
}