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}