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;
031import java.util.List;
032
033import org.granite.messaging.jmf.CodecRegistry;
034import org.granite.messaging.jmf.DumpContext;
035import org.granite.messaging.jmf.InputContext;
036import org.granite.messaging.jmf.JMFEncodingException;
037import org.granite.messaging.jmf.OutputContext;
038import org.granite.messaging.jmf.codec.ExtendedObjectCodec;
039import org.granite.messaging.jmf.codec.StandardCodec;
040import org.granite.messaging.jmf.codec.std.ObjectCodec;
041import org.granite.messaging.reflect.Property;
042
043/**
044 * @author Franck WOLFF
045 */
046public class ObjectCodecImpl extends AbstractIntegerStringCodec<Object> implements ObjectCodec {
047
048        protected static final StringTypeHandler TYPE_HANDLER = new StringTypeHandler() {
049
050                public int type(IntegerComponents ics, boolean reference) {
051                        if (reference)
052                                return 0x40 | (ics.length << 4) | JMF_OBJECT;
053                        return (ics.length << 4) | JMF_OBJECT;
054                }
055
056                public int indexOrLengthBytesCount(int parameterizedJmfType) {
057                        return (parameterizedJmfType >> 4) & 0x03;
058                }
059
060                public boolean isReference(int parameterizedJmfType) {
061                        return (parameterizedJmfType & 0x40) != 0;
062                }
063        };
064
065        public int getObjectType() {
066                return JMF_OBJECT;
067        }
068
069        public boolean canEncode(Object v) {
070                Class<?> cls = v.getClass();
071                return !cls.isArray() && !cls.isEnum() && !(v instanceof Class);
072        }
073
074        public void encode(OutputContext ctx, Object v) throws IOException, IllegalAccessException, InvocationTargetException {
075                final OutputStream os = ctx.getOutputStream();
076                
077                int indexOfStoredObject = ctx.indexOfStoredObjects(v);
078                if (indexOfStoredObject >= 0) {
079                        IntegerComponents ics = intComponents(indexOfStoredObject);
080                        os.write(0x80 | (ics.length << 4) | JMF_OBJECT);
081                        writeIntData(ctx, ics);
082                }
083                else {                  
084                        if (!(v instanceof Serializable))
085                                throw new NotSerializableException(v.getClass().getName());
086                        
087                        ctx.addToStoredObjects(v);
088                        
089                        ExtendedObjectCodec extendedCodec = ctx.getSharedContext().getCodecRegistry().findExtendedEncoder(ctx, v);
090                        
091                        String className = (
092                                extendedCodec != null ?
093                                extendedCodec.getEncodedClassName(ctx, v) :
094                                ctx.getAlias(v.getClass().getName())
095                        );
096                        
097                        writeString(ctx, className, TYPE_HANDLER);
098                        
099                        if (extendedCodec != null)
100                                extendedCodec.encode(ctx, v);
101                        else if (v instanceof Externalizable && !Proxy.isProxyClass(v.getClass()))
102                                ((Externalizable)v).writeExternal(ctx);
103                        else
104                                encodeSerializable(ctx, (Serializable)v);
105
106                        os.write(JMF_OBJECT_END);
107                }
108        }
109        
110        protected void encodeSerializable(OutputContext ctx, Serializable v) throws IOException, IllegalAccessException, InvocationTargetException {
111                List<Property> properties = ctx.getReflection().findSerializableProperties(v.getClass());
112                for (Property property : properties)
113                        ctx.getAndWriteProperty(v, property);
114        }
115        
116        public Object decode(InputContext ctx, int parameterizedJmfType)
117                throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
118                InvocationTargetException, SecurityException, NoSuchMethodException {
119                
120                final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry();
121                
122                int jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
123                if (jmfType != JMF_OBJECT)
124                        throw newBadTypeJMFEncodingException(jmfType, parameterizedJmfType);
125
126                Object v = null;
127
128                int indexOrLength = readIntData(ctx, (parameterizedJmfType >> 4) & 0x03, false);
129                if ((parameterizedJmfType & 0x80) != 0)
130                        v = ctx.getSharedObject(indexOrLength);
131                else {
132                        String className = readString(ctx, parameterizedJmfType, indexOrLength, TYPE_HANDLER);
133                        
134                        ExtendedObjectCodec extendedCodec = codecRegistry.findExtendedDecoder(ctx, className);
135                        if (extendedCodec != null) {
136                                className = extendedCodec.getDecodedClassName(ctx, className);
137                                int index = ctx.addUnresolvedSharedObject(className);
138                                v = extendedCodec.newInstance(ctx, className);
139                                ctx.setUnresolvedSharedObject(index, v);
140                                extendedCodec.decode(ctx, v);
141                        }
142                        else {
143                                className = ctx.getAlias(className);
144                                Class<?> cls = ctx.getSharedContext().getReflection().loadClass(className);
145                                
146                                if (!Serializable.class.isAssignableFrom(cls))
147                                        throw new NotSerializableException(cls.getName());
148                                
149                                if (Externalizable.class.isAssignableFrom(cls)) {
150                                        v = ctx.getReflection().newInstance(cls);
151                                        ctx.addSharedObject(v);
152                                        ((Externalizable)v).readExternal(ctx);
153                                }
154                                else {
155                                        v = ctx.getReflection().newInstance(cls);
156                                        ctx.addSharedObject(v);
157                                        decodeSerializable(ctx, (Serializable)v);
158                                }
159                        }
160                        
161                        int mark = ctx.safeRead();
162                        if (mark != JMF_OBJECT_END)
163                                throw new JMFEncodingException("Not a Object end marker: " + mark);
164                }
165                
166                return v;
167        }
168        
169        protected void decodeSerializable(InputContext ctx, Serializable v)
170                throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
171
172                List<Property> properties = ctx.getReflection().findSerializableProperties(v.getClass());
173                for (Property property : properties)
174                        ctx.readAndSetProperty(v, property);
175        }
176
177        public void dump(DumpContext ctx, int parameterizedJmfType) throws IOException {
178                final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry();
179
180                int jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
181                
182                if (jmfType != JMF_OBJECT)
183                        throw newBadTypeJMFEncodingException(jmfType, parameterizedJmfType);
184
185                int indexOrLength = readIntData(ctx, (parameterizedJmfType >> 4) & 0x03, false);
186                
187                if ((parameterizedJmfType & 0x80) != 0) {
188                        String className = (String)ctx.getSharedObject(indexOrLength);
189                        ctx.indentPrintLn("<" + className + "@" + indexOrLength + ">");
190                }
191                else {
192                        String className = readString(ctx, parameterizedJmfType, indexOrLength, TYPE_HANDLER);
193                        int indexOfStoredObject = ctx.addSharedObject(className);
194                        ctx.indentPrintLn(className + "@" + indexOfStoredObject + " {");
195                        ctx.incrIndent(1);
196                        
197                        while ((parameterizedJmfType = ctx.safeRead()) != JMF_OBJECT_END) {
198                                jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
199                                StandardCodec<?> codec = codecRegistry.getCodec(jmfType);
200                                
201                                if (codec == null)
202                                        throw new JMFEncodingException("No codec for JMF type: " + jmfType);
203                                
204                                codec.dump(ctx, parameterizedJmfType);
205                        }
206                        
207                        ctx.incrIndent(-1);
208                        ctx.indentPrintLn("}");
209                }
210        }
211}