001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.messaging.jmf.codec.std.impl; 023 024import java.io.Externalizable; 025import java.io.IOException; 026import java.io.NotSerializableException; 027import java.io.OutputStream; 028import java.io.Serializable; 029import java.lang.reflect.InvocationTargetException; 030import java.lang.reflect.Proxy; 031 032import org.granite.messaging.jmf.CodecRegistry; 033import org.granite.messaging.jmf.DumpContext; 034import org.granite.messaging.jmf.InputContext; 035import org.granite.messaging.jmf.JMFEncodingException; 036import org.granite.messaging.jmf.JMFObjectInputStream; 037import org.granite.messaging.jmf.JMFObjectOutputStream; 038import org.granite.messaging.jmf.OutputContext; 039import org.granite.messaging.jmf.codec.ExtendedObjectCodec; 040import org.granite.messaging.jmf.codec.StandardCodec; 041import org.granite.messaging.jmf.codec.std.ObjectCodec; 042import org.granite.messaging.jmf.codec.std.impl.util.ClassNameUtil; 043import org.granite.messaging.jmf.codec.std.impl.util.IntegerUtil; 044import org.granite.messaging.reflect.ClassDescriptor; 045import org.granite.messaging.reflect.Property; 046 047/** 048 * @author Franck WOLFF 049 */ 050public class ObjectCodecImpl extends AbstractStandardCodec<Object> implements ObjectCodec { 051 052 protected static final int REFERENCE_BYTE_COUNT_OFFSET = 5; 053 054 public int getObjectType() { 055 return JMF_OBJECT; 056 } 057 058 public boolean canEncode(Object v) { 059 Class<?> cls = v.getClass(); 060 return !cls.isArray() && !cls.isEnum() && !(v instanceof Class); 061 } 062 063 public void encode(OutputContext ctx, Object v) throws IOException, IllegalAccessException, InvocationTargetException { 064 final OutputStream os = ctx.getOutputStream(); 065 066 int indexOfStoredObject = ctx.indexOfObject(v); 067 if (indexOfStoredObject >= 0) { 068 int count = IntegerUtil.significantIntegerBytesCount0(indexOfStoredObject); 069 os.write(0x80 | (count << REFERENCE_BYTE_COUNT_OFFSET) | JMF_OBJECT); 070 IntegerUtil.encodeInteger(ctx, indexOfStoredObject, count); 071 } 072 else { 073 if (!(v instanceof Serializable)) 074 throw new NotSerializableException(v.getClass().getName()); 075 076 ctx.addToObjects(v); 077 078 ExtendedObjectCodec extendedCodec = ctx.getSharedContext().getCodecRegistry().findExtendedEncoder(ctx, v); 079 if (extendedCodec != null) { 080 String className = extendedCodec.getEncodedClassName(ctx, v); 081 082 os.write(JMF_OBJECT); 083 ClassNameUtil.encodeClassName(ctx, className); 084 extendedCodec.encode(ctx, v); 085 os.write(JMF_OBJECT_END); 086 } 087 else { 088 ClassDescriptor desc = ctx.getReflection().getDescriptor(v.getClass()); 089 090 while (desc != null && desc.hasWriteReplaceMethod()) { 091 Object replacement = desc.invokeWriteReplaceMethod(v); 092 if (replacement == null) 093 throw new JMFEncodingException(desc.getCls() + ".writeReplace() method returned null"); 094 if (replacement.getClass() == v.getClass()) 095 throw new JMFEncodingException(desc.getCls() + ".writeReplace() method returned an instance of the same class"); 096 097 ClassDescriptor replacementDesc = ctx.getReflection().getDescriptor(replacement.getClass()); 098 if (replacementDesc == null || !replacementDesc.hasReadResolveMethod()) { 099 throw new JMFEncodingException( 100 desc.getCls() + 101 ".writeReplace() method returned an object that has no readResolve() method: " + 102 (replacementDesc == null ? "null" : replacementDesc.getCls()) 103 ); 104 } 105 106 v = replacement; 107 desc = replacementDesc; 108 } 109 110 String className = ctx.getAlias(v.getClass().getName()); 111 112 os.write(JMF_OBJECT); 113 ClassNameUtil.encodeClassName(ctx, className); 114 if (v instanceof Externalizable && !Proxy.isProxyClass(v.getClass())) 115 ((Externalizable)v).writeExternal(ctx); 116 else 117 encodeSerializable(ctx, (Serializable)v, desc); 118 os.write(JMF_OBJECT_END); 119 } 120 } 121 } 122 123 protected void encodeSerializable(OutputContext ctx, Serializable v, ClassDescriptor desc) 124 throws IOException, IllegalAccessException, InvocationTargetException { 125 126 ClassDescriptor parentDesc = desc.getParent(); 127 128 if (parentDesc != null) 129 encodeSerializable(ctx, v, parentDesc); 130 131 if (desc.hasWriteObjectMethod()) 132 desc.invokeWriteObjectMethod(new JMFObjectOutputStream(ctx, desc, v), v); 133 else { 134 for (Property property : desc.getSerializableProperties()) 135 ctx.getAndWriteProperty(v, property); 136 } 137 } 138 139 public Object decode(InputContext ctx, int parameterizedJmfType) 140 throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, 141 InvocationTargetException, SecurityException, NoSuchMethodException { 142 143 final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry(); 144 145 Object v = null; 146 147 if ((parameterizedJmfType & 0x80) != 0) { 148 int indexOfStoredObject = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> REFERENCE_BYTE_COUNT_OFFSET) & 0x03); 149 v = ctx.getObject(indexOfStoredObject); 150 } 151 else { 152 String className = ClassNameUtil.decodeClassName(ctx); 153 154 ExtendedObjectCodec extendedCodec = codecRegistry.findExtendedDecoder(ctx, className); 155 if (extendedCodec != null) { 156 className = extendedCodec.getDecodedClassName(ctx, className); 157 int index = ctx.addToUnresolvedObjects(className); 158 v = extendedCodec.newInstance(ctx, className); 159 ctx.setUnresolvedObject(index, v); 160 extendedCodec.decode(ctx, v); 161 } 162 else { 163 className = ctx.getAlias(className); 164 165 ClassDescriptor desc = ctx.getClassDescriptor(className); 166 Class<?> cls = desc.getCls(); 167 168 if (!Serializable.class.isAssignableFrom(cls)) 169 throw new NotSerializableException(cls.getName()); 170 171 v = desc.newInstance(); 172 173 if (desc == null || !desc.hasReadResolveMethod()) { 174 ctx.addToObjects(v); 175 176 if (Externalizable.class.isAssignableFrom(cls)) 177 ((Externalizable)v).readExternal(ctx); 178 else 179 decodeSerializable(ctx, (Serializable)v); 180 } 181 else { 182 int index = ctx.addToUnresolvedObjects(className); 183 184 if (Externalizable.class.isAssignableFrom(cls)) 185 ((Externalizable)v).readExternal(ctx); 186 else 187 decodeSerializable(ctx, (Serializable)v); 188 189 do { 190 Object resolved = desc.invokeReadResolveMethod(v); 191 if (resolved == null) 192 throw new JMFEncodingException(desc.getCls() + ".readResolve() method returned null"); 193 if (resolved.getClass() == v.getClass()) 194 throw new JMFEncodingException(desc.getCls() + ".readResolve() method returned an instance of the same class"); 195 196 ClassDescriptor resolvedDesc = ctx.getClassDescriptor(resolved.getClass()); 197 if (resolvedDesc == null || !resolvedDesc.hasWriteReplaceMethod()) { 198 throw new JMFEncodingException( 199 desc.getCls() + 200 ".readResolve() method returned an object that has no writeReplace() method: " + 201 (resolvedDesc == null ? "null" : resolvedDesc.getCls()) 202 ); 203 } 204 205 v = resolved; 206 desc = resolvedDesc; 207 } 208 while (desc.hasReadResolveMethod()); 209 210 ctx.setUnresolvedObject(index, v); 211 } 212 } 213 214 int mark = ctx.safeRead(); 215 if (mark != JMF_OBJECT_END) 216 throw new JMFEncodingException("Not a Object end marker: " + mark); 217 } 218 219 return v; 220 } 221 222 protected void decodeSerializable(InputContext ctx, Serializable v) 223 throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException { 224 225 ClassDescriptor desc = ctx.getClassDescriptor(v.getClass()); 226 decodeSerializable(ctx, v, desc); 227 } 228 229 protected void decodeSerializable(InputContext ctx, Serializable v, ClassDescriptor desc) 230 throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException { 231 232 ClassDescriptor parentDesc = desc.getParent(); 233 234 if (parentDesc != null) 235 decodeSerializable(ctx, v, parentDesc); 236 237 if (desc.hasReadObjectMethod()) 238 desc.invokeReadObjectMethod(new JMFObjectInputStream(ctx, desc, v), v); 239 else { 240 for (Property property : desc.getSerializableProperties()) 241 ctx.readAndSetProperty(v, property); 242 } 243 } 244 245 public void dump(DumpContext ctx, int parameterizedJmfType) throws IOException { 246 final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry(); 247 248 int jmfType = codecRegistry.extractJmfType(parameterizedJmfType); 249 250 if (jmfType != JMF_OBJECT) 251 throw newBadTypeJMFEncodingException(jmfType, parameterizedJmfType); 252 253 if ((parameterizedJmfType & 0x80) != 0) { 254 int indexOfStoredObject = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> REFERENCE_BYTE_COUNT_OFFSET) & 0x03); 255 String className = (String)ctx.getObject(indexOfStoredObject); 256 ctx.indentPrintLn("<" + className + "@" + indexOfStoredObject + ">"); 257 } 258 else { 259 String className = ClassNameUtil.decodeClassName(ctx); 260 261 int indexOfStoredObject = ctx.addToObjects(className); 262 ctx.indentPrintLn(className + "@" + indexOfStoredObject + " {"); 263 ctx.incrIndent(1); 264 265 while ((parameterizedJmfType = ctx.safeRead()) != JMF_OBJECT_END) { 266 jmfType = codecRegistry.extractJmfType(parameterizedJmfType); 267 StandardCodec<?> codec = codecRegistry.getCodec(jmfType); 268 269 if (codec == null) 270 throw new JMFEncodingException("No codec for JMF type: " + jmfType); 271 272 codec.dump(ctx, parameterizedJmfType); 273 } 274 275 ctx.incrIndent(-1); 276 ctx.indentPrintLn("}"); 277 } 278 } 279}