/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services 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.
*
* Granite Data Services 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.messaging.jmf.codec.std.impl;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import org.granite.messaging.jmf.CodecRegistry;
import org.granite.messaging.jmf.DumpContext;
import org.granite.messaging.jmf.InputContext;
import org.granite.messaging.jmf.JMFEncodingException;
import org.granite.messaging.jmf.JMFObjectInputStream;
import org.granite.messaging.jmf.JMFObjectOutputStream;
import org.granite.messaging.jmf.OutputContext;
import org.granite.messaging.jmf.codec.ExtendedObjectCodec;
import org.granite.messaging.jmf.codec.StandardCodec;
import org.granite.messaging.jmf.codec.std.ObjectCodec;
import org.granite.messaging.jmf.codec.std.impl.util.ClassNameUtil;
import org.granite.messaging.jmf.codec.std.impl.util.IntegerUtil;
import org.granite.messaging.reflect.ClassDescriptor;
import org.granite.messaging.reflect.Property;
/**
* @author Franck WOLFF
*/
public class ObjectCodecImpl extends AbstractStandardCodec<Object> implements ObjectCodec {
protected static final int REFERENCE_BYTE_COUNT_OFFSET = 5;
public int getObjectType() {
return JMF_OBJECT;
}
public boolean canEncode(Object v) {
Class<?> cls = v.getClass();
return !cls.isArray() && !cls.isEnum() && !(v instanceof Class);
}
public void encode(OutputContext ctx, Object v) throws IOException, IllegalAccessException, InvocationTargetException {
final OutputStream os = ctx.getOutputStream();
int indexOfStoredObject = ctx.indexOfObject(v);
if (indexOfStoredObject >= 0) {
int count = IntegerUtil.significantIntegerBytesCount0(indexOfStoredObject);
os.write(0x80 | (count << REFERENCE_BYTE_COUNT_OFFSET) | JMF_OBJECT);
IntegerUtil.encodeInteger(ctx, indexOfStoredObject, count);
}
else {
if (!(v instanceof Serializable))
throw new NotSerializableException(v.getClass().getName());
ctx.addToObjects(v);
ExtendedObjectCodec extendedCodec = ctx.getSharedContext().getCodecRegistry().findExtendedEncoder(ctx, v);
if (extendedCodec != null) {
String className = extendedCodec.getEncodedClassName(ctx, v);
os.write(JMF_OBJECT);
ClassNameUtil.encodeClassName(ctx, className);
extendedCodec.encode(ctx, v);
os.write(JMF_OBJECT_END);
}
else {
ClassDescriptor desc = ctx.getReflection().getDescriptor(v.getClass());
while (desc != null && desc.hasWriteReplaceMethod()) {
Object replacement = desc.invokeWriteReplaceMethod(v);
if (replacement == null)
throw new JMFEncodingException(desc.getCls() + ".writeReplace() method returned null");
if (replacement.getClass() == v.getClass())
throw new JMFEncodingException(desc.getCls() + ".writeReplace() method returned an instance of the same class");
ClassDescriptor replacementDesc = ctx.getReflection().getDescriptor(replacement.getClass());
if (replacementDesc == null || !replacementDesc.hasReadResolveMethod()) {
throw new JMFEncodingException(
desc.getCls() +
".writeReplace() method returned an object that has no readResolve() method: " +
(replacementDesc == null ? "null" : replacementDesc.getCls())
);
}
v = replacement;
desc = replacementDesc;
}
String className = ctx.getAlias(v.getClass().getName());
os.write(JMF_OBJECT);
ClassNameUtil.encodeClassName(ctx, className);
if (v instanceof Externalizable && !Proxy.isProxyClass(v.getClass()))
((Externalizable)v).writeExternal(ctx);
else
encodeSerializable(ctx, (Serializable)v, desc);
os.write(JMF_OBJECT_END);
}
}
}
protected void encodeSerializable(OutputContext ctx, Serializable v, ClassDescriptor desc)
throws IOException, IllegalAccessException, InvocationTargetException {
ClassDescriptor parentDesc = desc.getParent();
if (parentDesc != null)
encodeSerializable(ctx, v, parentDesc);
if (desc.hasWriteObjectMethod())
desc.invokeWriteObjectMethod(new JMFObjectOutputStream(ctx, desc, v), v);
else {
for (Property property : desc.getSerializableProperties())
ctx.getAndWriteProperty(v, property);
}
}
public Object decode(InputContext ctx, int parameterizedJmfType)
throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
InvocationTargetException, SecurityException, NoSuchMethodException {
final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry();
Object v = null;
if ((parameterizedJmfType & 0x80) != 0) {
int indexOfStoredObject = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> REFERENCE_BYTE_COUNT_OFFSET) & 0x03);
v = ctx.getObject(indexOfStoredObject);
}
else {
String className = ClassNameUtil.decodeClassName(ctx);
ExtendedObjectCodec extendedCodec = codecRegistry.findExtendedDecoder(ctx, className);
if (extendedCodec != null) {
className = extendedCodec.getDecodedClassName(ctx, className);
int index = ctx.addToUnresolvedObjects(className);
v = extendedCodec.newInstance(ctx, className);
ctx.setUnresolvedObject(index, v);
extendedCodec.decode(ctx, v);
}
else {
className = ctx.getAlias(className);
ClassDescriptor desc = ctx.getClassDescriptor(className);
Class<?> cls = desc.getCls();
if (!Serializable.class.isAssignableFrom(cls))
throw new NotSerializableException(cls.getName());
v = desc.newInstance();
if (desc == null || !desc.hasReadResolveMethod()) {
ctx.addToObjects(v);
if (Externalizable.class.isAssignableFrom(cls))
((Externalizable)v).readExternal(ctx);
else
decodeSerializable(ctx, (Serializable)v);
}
else {
int index = ctx.addToUnresolvedObjects(className);
if (Externalizable.class.isAssignableFrom(cls))
((Externalizable)v).readExternal(ctx);
else
decodeSerializable(ctx, (Serializable)v);
do {
Object resolved = desc.invokeReadResolveMethod(v);
if (resolved == null)
throw new JMFEncodingException(desc.getCls() + ".readResolve() method returned null");
if (resolved.getClass() == v.getClass())
throw new JMFEncodingException(desc.getCls() + ".readResolve() method returned an instance of the same class");
ClassDescriptor resolvedDesc = ctx.getClassDescriptor(resolved.getClass());
if (resolvedDesc == null || !resolvedDesc.hasWriteReplaceMethod()) {
throw new JMFEncodingException(
desc.getCls() +
".readResolve() method returned an object that has no writeReplace() method: " +
(resolvedDesc == null ? "null" : resolvedDesc.getCls())
);
}
v = resolved;
desc = resolvedDesc;
}
while (desc.hasReadResolveMethod());
ctx.setUnresolvedObject(index, v);
}
}
int mark = ctx.safeRead();
if (mark != JMF_OBJECT_END)
throw new JMFEncodingException("Not a Object end marker: " + mark);
}
return v;
}
protected void decodeSerializable(InputContext ctx, Serializable v)
throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
ClassDescriptor desc = ctx.getClassDescriptor(v.getClass());
decodeSerializable(ctx, v, desc);
}
protected void decodeSerializable(InputContext ctx, Serializable v, ClassDescriptor desc)
throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
ClassDescriptor parentDesc = desc.getParent();
if (parentDesc != null)
decodeSerializable(ctx, v, parentDesc);
if (desc.hasReadObjectMethod())
desc.invokeReadObjectMethod(new JMFObjectInputStream(ctx, desc, v), v);
else {
for (Property property : desc.getSerializableProperties())
ctx.readAndSetProperty(v, property);
}
}
public void dump(DumpContext ctx, int parameterizedJmfType) throws IOException {
final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry();
int jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
if (jmfType != JMF_OBJECT)
throw newBadTypeJMFEncodingException(jmfType, parameterizedJmfType);
if ((parameterizedJmfType & 0x80) != 0) {
int indexOfStoredObject = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> REFERENCE_BYTE_COUNT_OFFSET) & 0x03);
String className = (String)ctx.getObject(indexOfStoredObject);
ctx.indentPrintLn("<" + className + "@" + indexOfStoredObject + ">");
}
else {
String className = ClassNameUtil.decodeClassName(ctx);
int indexOfStoredObject = ctx.addToObjects(className);
ctx.indentPrintLn(className + "@" + indexOfStoredObject + " {");
ctx.incrIndent(1);
while ((parameterizedJmfType = ctx.safeRead()) != JMF_OBJECT_END) {
jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
StandardCodec<?> codec = codecRegistry.getCodec(jmfType);
if (codec == null)
throw new JMFEncodingException("No codec for JMF type: " + jmfType);
codec.dump(ctx, parameterizedJmfType);
}
ctx.incrIndent(-1);
ctx.indentPrintLn("}");
}
}
}