/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.commons.serialization.serial;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.IdentityHashMap;
import org.exoplatform.commons.serialization.SerializationContext;
import org.exoplatform.commons.serialization.api.TypeConverter;
import org.exoplatform.commons.serialization.model.ClassTypeModel;
import org.exoplatform.commons.serialization.model.ConvertedTypeModel;
import org.exoplatform.commons.serialization.model.FieldModel;
import org.exoplatform.commons.serialization.model.SerializationMode;
import org.exoplatform.commons.serialization.model.TypeModel;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class ObjectWriter extends ObjectOutputStream {
/** . */
private static final Logger log = LoggerFactory.getLogger(ObjectWriter.class);
/** . */
private final SerializationContext context;
/** . */
private final IdentityHashMap<Object, Integer> objectToId;
public ObjectWriter(SerializationContext context, OutputStream out) throws IOException {
super(out);
//
enableReplaceObject(true);
//
this.context = context;
this.objectToId = new IdentityHashMap<Object, Integer>();
}
private int register(Object o) {
int nextId = objectToId.size();
objectToId.put(o, nextId);
return nextId;
}
private void write(Object obj, DataContainer output) throws IOException {
Class objClass = obj.getClass();
TypeModel typeModel = context.getTypeDomain().getTypeModel(objClass);
//
if (typeModel == null) {
throw new NotSerializableException("Object " + obj + " does not have its type described");
}
//
if (typeModel instanceof ClassTypeModel) {
// This is obviously unchecked because of the fact that Object.getClass() returns Class<?>
// need to find a work around for that
write((ClassTypeModel) typeModel, obj, output);
} else {
write((ConvertedTypeModel) typeModel, obj, output);
}
}
private <O, T> void write(ConvertedTypeModel<O, T> typeModel, O obj, DataContainer output) throws IOException {
Class<? extends TypeConverter<O, T>> converterClass = typeModel.getConverterJavaType();
//
TypeConverter<O, T> converter;
try {
converter = converterClass.newInstance();
} catch (Exception e) {
throw new AssertionError(e);
}
//
T target;
try {
target = converter.write(obj);
} catch (Exception e) {
InvalidObjectException ioe = new InvalidObjectException("The object " + obj + " conversion threw an exception ");
ioe.initCause(e);
throw ioe;
}
if (target == null) {
throw new InvalidObjectException("The object " + obj + " was converted to null by converter " + converter);
}
//
output.writeInt(DataKind.CONVERTED_OBJECT);
output.writeObject(typeModel.getJavaType());
write(target, output);
}
private <O> void write(ClassTypeModel<O> typeModel, O obj, DataContainer output) throws IOException {
if (typeModel.getSerializationMode() == SerializationMode.SERIALIZED) {
//
output.writeInt(DataKind.OBJECT);
output.writeInt(register(obj));
output.writeObject(typeModel.getJavaType());
//
SerializationStatus status = SerializationStatus.NONE;
for (ClassTypeModel<? super O> currentTypeModel = typeModel; currentTypeModel != null; currentTypeModel = currentTypeModel
.getSuperType()) {
if (currentTypeModel instanceof ClassTypeModel<?>) {
for (FieldModel<?, ?> fieldModel : currentTypeModel.getFields()) {
if (!fieldModel.isTransient()) {
// System.out.println("About to serialize field " + fieldModel + " of type " + currentTypeModel);
Object fieldValue = fieldModel.get(obj);
if (fieldValue == null) {
output.writeObject(DataKind.NULL_VALUE);
} else {
Integer fieldValueId = objectToId.get(fieldValue);
if (fieldValueId != null) {
output.writeObject(DataKind.OBJECT_REF);
output.writeInt(fieldValueId);
} else {
output.writeObject(DataKind.OBJECT);
output.writeObject(fieldValue);
}
}
}
}
switch (status) {
case NONE:
status = SerializationStatus.FULL;
break;
}
} else {
if (!currentTypeModel.getFields().isEmpty()) {
switch (status) {
case FULL:
status = SerializationStatus.PARTIAL;
break;
}
}
}
}
//
switch (status) {
case FULL:
break;
case PARTIAL:
log.debug("Partial serialization of object " + obj);
break;
case NONE:
throw new NotSerializableException("Type " + typeModel + " is not serializable");
}
} else if (typeModel.getSerializationMode() == SerializationMode.SERIALIZABLE) {
output.writeInt(DataKind.SERIALIZED_OBJECT);
output.writeObject(obj);
} else {
throw new NotSerializableException("Type " + typeModel + " is not serializable");
}
}
@Override
protected Object replaceObject(final Object obj) throws IOException {
if (obj == null) {
return null;
}
if (obj instanceof Serializable) {
return obj;
}
//
DataContainer output = new DataContainer();
//
Integer id = objectToId.get(obj);
if (id != null) {
output = new DataContainer();
output.writeInt(DataKind.OBJECT_REF);
output.writeObject(id);
} else {
write(obj, output);
}
//
return output;
}
}