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;
023
024import java.io.EOFException;
025import java.io.IOException;
026import java.io.InputStream;
027import java.lang.reflect.InvocationTargetException;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.granite.messaging.annotations.Include;
034import org.granite.messaging.jmf.codec.StandardCodec;
035import org.granite.messaging.reflect.ClassDescriptor;
036import org.granite.messaging.reflect.NoopWritableProperty;
037import org.granite.messaging.reflect.Property;
038import org.granite.messaging.reflect.Reflection;
039
040/**
041 * @author Franck WOLFF
042 */
043public class JMFDeserializer implements InputContext {
044        
045        ///////////////////////////////////////////////////////////////////////////
046        // Fields
047        
048        protected final List<String> classNames = new ArrayList<String>(256);
049        protected final List<String> strings = new ArrayList<String>(256);
050        protected final List<Object> objects = new ArrayList<Object>(256);
051        
052        protected final Map<String, ClassDescriptor> classDescriptors = new HashMap<String, ClassDescriptor>(256);
053
054    protected final InputStream inputStream;
055    protected final SharedContext context;
056    
057    protected final CodecRegistry codecRegistry;
058        
059        ///////////////////////////////////////////////////////////////////////////
060        // Initialization
061        
062        public JMFDeserializer(InputStream is, SharedContext context) {
063                this.inputStream = is;
064                this.context = context;
065                this.codecRegistry = context.getCodecRegistry();
066                
067                this.classNames.addAll(context.getInitialClassNameDictionary());
068        }
069        
070        ///////////////////////////////////////////////////////////////////////////
071        // ObjectInput implementation
072
073        public boolean readBoolean() throws IOException {
074                return codecRegistry.getBooleanCodec().decodePrimitive(this);
075        }
076
077        public byte readByte() throws IOException {
078                return codecRegistry.getByteCodec().decodePrimitive(this);
079        }
080
081        public int readUnsignedByte() throws IOException {
082                return readByte() & 0xFF;
083        }
084
085        public short readShort() throws IOException {
086                return codecRegistry.getShortCodec().decodePrimitive(this);
087        }
088
089        public int readUnsignedShort() throws IOException {
090                return readShort() & 0xFFFF;
091        }
092
093        public char readChar() throws IOException {
094                return codecRegistry.getCharacterCodec().decodePrimitive(this);
095        }
096
097        public int readInt() throws IOException {
098                return codecRegistry.getIntegerCodec().decodePrimitive(this);
099        }
100
101        public long readLong() throws IOException {
102                return codecRegistry.getLongCodec().decodePrimitive(this);
103        }
104
105        public float readFloat() throws IOException {
106                return codecRegistry.getFloatCodec().decodePrimitive(this);
107        }
108
109        public double readDouble() throws IOException {
110                return codecRegistry.getDoubleCodec().decodePrimitive(this);
111        }
112
113        public String readUTF() throws IOException {
114                int parameterizedJmfType = safeRead();
115                
116                if (parameterizedJmfType == JMF_NULL)
117                        return (String)codecRegistry.getNullCodec().decode(this, parameterizedJmfType);
118                
119                return codecRegistry.getStringCodec().decode(this, parameterizedJmfType);
120        }
121
122        public Object readObject() throws ClassNotFoundException, IOException {
123                int parameterizedJmfType = safeRead();
124                
125                StandardCodec<?> codec = codecRegistry.getCodec(parameterizedJmfType);
126                if (codec == null)
127                        throw new JMFEncodingException("Unsupported JMF type: " + codecRegistry.extractJmfType(parameterizedJmfType));
128                
129                try {
130                        return codec.decode(this, parameterizedJmfType);
131                }
132                catch (InvocationTargetException e) {
133                        throw new IOException(e.getTargetException());
134                }
135                catch (IllegalAccessException e) {
136                        throw new IOException(e);
137                }
138                catch (InstantiationException e) {
139                        throw new IOException(e);
140                }
141                catch (NoSuchMethodException e) {
142                        throw new IOException(e);
143                }
144        }
145
146        public int available() throws IOException {
147                return inputStream.available();
148        }
149
150        public void close() throws IOException {
151                inputStream.close();
152        }
153        
154        ///////////////////////////////////////////////////////////////////////////
155        // ObjectInput implementation (unsupported, marked at deprecated)
156
157        @Deprecated
158        public int read() throws IOException {
159                throw new UnsupportedOperationException("Use readByte()");
160        }
161
162        @Deprecated
163        public int read(byte[] b) throws IOException {
164                throw new UnsupportedOperationException("Use (byte[])readObject()");
165        }
166
167        @Deprecated
168        public int read(byte[] b, int off, int len) throws IOException {
169                throw new UnsupportedOperationException("Use (byte[])readObject()");
170        }
171        
172        @Deprecated
173        public void readFully(byte[] b) throws IOException {
174                throw new UnsupportedOperationException("Use (byte[])readObject()");
175        }
176
177        @Deprecated
178        public void readFully(byte[] b, int off, int len) throws IOException {
179                throw new UnsupportedOperationException("Use (byte[])readObject()");
180        }
181        
182        @Deprecated
183        public String readLine() throws IOException {
184                throw new UnsupportedOperationException("Use readUTF()");
185        }
186
187        @Deprecated
188        public int skipBytes(int n) throws IOException {
189                throw new UnsupportedOperationException();
190        }
191
192        @Deprecated
193        public long skip(long n) throws IOException {
194                throw new UnsupportedOperationException();
195        }
196        
197        ///////////////////////////////////////////////////////////////////////////
198        // InputContext implementation
199
200        public SharedContext getSharedContext() {
201                return context;
202        }
203        
204        public InputStream getInputStream() {
205                return inputStream;
206        }
207
208        public int safeRead() throws IOException {
209                int b = inputStream.read();
210                if (b == -1)
211                        throw new EOFException();
212                return b;
213        }
214
215        public long safeReadLong() throws IOException {
216                return (long)safeRead();
217        }
218        
219        public void safeReadFully(byte[] b) throws IOException {
220                safeReadFully(b, 0, b.length);
221        }
222
223        public void safeReadFully(byte[] b, int off, int len) throws IOException {
224                if (off < 0 || len < 0 || off + len > b.length)
225                        throw new IndexOutOfBoundsException("b.length=" + b.length + ", off=" + off + ", len" + len);
226                
227                if (len == 0)
228                        return;
229                
230                do {
231                        int read = inputStream.read(b, off, len);
232                        if (read == -1)
233                                throw new EOFException();
234                        off += read;
235                        len -= read;
236                }
237                while (len > 0);
238        }
239
240        public void safeSkip(long n) throws IOException {
241                while (n > 0) {
242                        if (inputStream.read() == -1)
243                                throw new EOFException();
244                        n--;
245                }
246        }
247
248        @Override
249        public int addToClassNames(String cn) {
250                int index = classNames.size();
251                classNames.add(index, cn);
252                return index;
253        }
254
255        @Override
256        public String getClassName(int index) {
257                return classNames.get(index);
258        }
259
260        @Override
261        public ClassDescriptor getClassDescriptor(String className) throws ClassNotFoundException {
262                ClassDescriptor desc = classDescriptors.get(className);
263                if (desc == null) {
264                        Class<?> cls = context.getReflection().loadClass(className);
265                        desc = context.getReflection().getDescriptor(cls);
266                        classDescriptors.put(className, desc);
267                }
268                return desc;
269        }
270
271        @Override
272        public ClassDescriptor getClassDescriptor(Class<?> cls) {
273                ClassDescriptor desc = classDescriptors.get(cls.getName());
274                if (desc == null) {
275                        desc = context.getReflection().getDescriptor(cls);
276                        classDescriptors.put(cls.getName(), desc);
277                }
278                return desc;
279        }
280
281        public int addToStrings(String s) {
282                int index = strings.size();
283                strings.add(index, s);
284                return index;
285        }
286        
287        public String getString(int index) {
288                return strings.get(index);
289        }
290        
291        public int addToObjects(Object o) {
292                int index = objects.size();
293                objects.add(index, o);
294                return index;
295        }
296        
297        public Object getObject(int index) {
298                Object o = objects.get(index);
299                if (o instanceof UnresolvedSharedObject)
300                        throw new JMFUnresolvedSharedObjectException("Unresolved shared object: " + o);
301                return o;
302        }
303        
304        public int addToUnresolvedObjects(String className) {
305                int index = objects.size();
306                objects.add(index, new UnresolvedSharedObject(className, index));
307                return index;
308        }
309        
310        public Object setUnresolvedObject(int index, Object o) {
311                Object uso = objects.set(index, o);
312                if (!(uso instanceof UnresolvedSharedObject))
313                        throw new JMFUnresolvedSharedObjectException("Not an unresolved shared object: " + uso);
314                return uso;
315        }
316        
317        ///////////////////////////////////////////////////////////////////////////
318        // ExtendedObjectInput implementation
319
320        public Reflection getReflection() {
321                return context.getReflection();
322        }
323
324        public String getAlias(String remoteAlias) {
325                return context.getClassName(remoteAlias);
326        }
327
328        public void readAndSetProperty(Object obj, Property property) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
329                if (property.isAnnotationPresent(Include.class) && !property.isWritable())
330                        property = new NoopWritableProperty(property.getName(), property.getType());
331                
332                if (property.getType().isPrimitive())
333                        codecRegistry.getPrimitivePropertyCodec(property.getType()).decodePrimitive(this, obj, property);
334                else
335                        property.setObject(obj, readObject());
336        }
337        
338        static class UnresolvedSharedObject {
339                
340                private final String className;
341                private final int index;
342
343                public UnresolvedSharedObject(String className, int index) {
344                        this.className = className;
345                        this.index = index;
346                }
347
348                public String getClassName() {
349                        return className;
350                }
351
352                public int getIndex() {
353                        return index;
354                }
355
356                @Override
357                public String toString() {
358                        return UnresolvedSharedObject.class.getName() + " {className=" + className + ", index=" + index + "}";
359                }
360        }
361        
362        ///////////////////////////////////////////////////////////////////////////
363        // Debug
364
365        public String toDumpString() {
366                StringBuilder sb = new StringBuilder(getClass().getName());
367                sb.append(" {\n");
368                
369                sb.append("    storedClassNames=[\n");
370                for (int i = 0; i < classNames.size(); i++)
371                        sb.append("        ").append(i).append(": \"").append(classNames.get(i)).append("\"\n");
372                sb.append("    ],\n");
373                
374                
375                sb.append("    storedStrings=[\n");
376                for (int i = 0; i < strings.size(); i++)
377                        sb.append("        ").append(i).append(": \"").append(strings.get(i)).append("\"\n");
378                sb.append("    ],\n");
379                
380                sb.append("    storedObjects=[\n");
381                for (int i = 0; i < objects.size(); i++) {
382                        Object o = objects.get(i);
383                        sb.append("        ").append(i).append(": ").append(o.getClass().getName())
384                          .append("@").append(System.identityHashCode(o)).append("\n");
385                }
386                sb.append("    ],\n");
387                
388                sb.append("}");
389                return sb.toString();
390        }
391}