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.IOException;
025import java.io.OutputStream;
026import java.lang.reflect.InvocationTargetException;
027import java.util.Comparator;
028import java.util.HashMap;
029import java.util.IdentityHashMap;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.TreeSet;
033
034import org.granite.messaging.jmf.codec.StandardCodec;
035import org.granite.messaging.reflect.Property;
036import org.granite.messaging.reflect.Reflection;
037
038/**
039 * @author Franck WOLFF
040 */
041public class JMFSerializer implements OutputContext {
042        
043        ///////////////////////////////////////////////////////////////////////////
044        // Fields
045
046        protected final Map<String, Integer> classNames = new HashMap<String, Integer>(256);
047        protected final Map<String, Integer> strings = new HashMap<String, Integer>(256);
048        protected final Map<Object, Integer> objects = new IdentityHashMap<Object, Integer>(256);
049        
050    protected final OutputStream outputStream;
051    protected final SharedContext context;
052    
053    protected final CodecRegistry codecRegistry;
054        
055        ///////////////////////////////////////////////////////////////////////////
056        // Initialization
057
058        public JMFSerializer(OutputStream outputStream, SharedContext context) {
059                this.outputStream = outputStream;
060                this.codecRegistry = context.getCodecRegistry();
061                this.context = context;
062                
063                for (String s : context.getInitialClassNameDictionary())
064                        addToClassNames(s);
065        }
066        
067        ///////////////////////////////////////////////////////////////////////////
068        // ObjectOutput implementation
069
070        public void writeBoolean(boolean v) throws IOException {
071                codecRegistry.getBooleanCodec().encodePrimitive(this, v);
072        }
073
074        public void writeByte(int v) throws IOException {
075                codecRegistry.getByteCodec().encodePrimitive(this, v);
076        }
077
078        public void writeShort(int v) throws IOException {
079                codecRegistry.getShortCodec().encodePrimitive(this, v);
080        }
081
082        public void writeChar(int v) throws IOException {
083                codecRegistry.getCharacterCodec().encodePrimitive(this, v);
084        }
085        
086        public void writeInt(int v) throws IOException {
087                codecRegistry.getIntegerCodec().encodePrimitive(this, v);
088        }
089
090        public void writeLong(long v) throws IOException {
091                codecRegistry.getLongCodec().encodePrimitive(this, v);
092        }
093
094        public void writeFloat(float v) throws IOException {
095                codecRegistry.getFloatCodec().encodePrimitive(this, v);
096        }
097
098        public void writeDouble(double v) throws IOException {
099                codecRegistry.getDoubleCodec().encodePrimitive(this, v);
100        }
101
102        public void writeUTF(String s) throws IOException {
103                if (s == null)
104                        codecRegistry.getNullCodec().encode(this, s);
105                else
106                        codecRegistry.getStringCodec().encode(this, s);
107        }
108
109        public void writeObject(Object obj) throws IOException {
110                StandardCodec<Object> codec = codecRegistry.getCodec(obj);
111                if (codec == null)
112                        throw new JMFEncodingException("Unsupported Java class: " + obj);
113                
114                try {
115                        codec.encode(this, obj);
116                }
117                catch (IllegalAccessException e) {
118                        throw new IOException(e);
119                }
120                catch (InvocationTargetException e) {
121                        throw new IOException(e);
122                }
123        }
124
125        public void flush() throws IOException {
126                outputStream.flush();
127        }
128
129        public void close() throws IOException {
130                outputStream.close();
131        }
132        
133        ///////////////////////////////////////////////////////////////////////////
134        // ObjectOutput implementation (unsupported, marked at deprecated)
135
136        @Deprecated
137        public void write(int b) throws IOException {
138                throw new UnsupportedOperationException("Use writeByte(b)");
139        }
140
141        @Deprecated
142        public void write(byte[] b) throws IOException {
143                throw new UnsupportedOperationException("Use writeObject(b)");
144        }
145
146        @Deprecated
147        public void write(byte[] b, int off, int len) throws IOException {
148                throw new UnsupportedOperationException("Use writeObject(Arrays.copyOfRange(b, off, off+len))");
149        }
150
151        @Deprecated
152        public void writeBytes(String s) throws IOException {
153                throw new UnsupportedOperationException("Use writeUTF(s)");
154        }
155
156        @Deprecated
157        public void writeChars(String s) throws IOException {
158                throw new UnsupportedOperationException("Use writeUTF(s)");
159        }
160        
161        ///////////////////////////////////////////////////////////////////////////
162        // OutputContext implementation
163
164        public SharedContext getSharedContext() {
165                return context;
166        }
167
168        public OutputStream getOutputStream() {
169                return outputStream;
170        }
171
172        @Override
173        public void addToClassNames(String className) {
174        if (className != null && !classNames.containsKey(className)) {
175            Integer index = Integer.valueOf(classNames.size());
176            classNames.put(className, index);
177        }
178        }
179
180        @Override
181        public int indexOfClassName(String className) {
182        if (className != null) {
183                Integer index = classNames.get(className);
184                if (index != null)
185                        return index.intValue();
186        }
187        return -1;
188        }
189
190        public void addToStrings(String s) {
191        if (s != null && !strings.containsKey(s)) {
192            Integer index = Integer.valueOf(strings.size());
193            strings.put(s, index);
194        }
195    }
196
197        public int indexOfString(String s) {
198        if (s != null) {
199                Integer index = strings.get(s);
200                if (index != null)
201                        return index.intValue();
202        }
203        return -1;
204    }
205
206        public void addToObjects(Object o) {
207        if (o != null && !objects.containsKey(o)) {
208            Integer index = Integer.valueOf(objects.size());
209            objects.put(o, index);
210        }
211    }
212
213        public int indexOfObject(Object o) {
214        if (o != null) {
215                Integer index = objects.get(o);
216                if (index != null)
217                        return index.intValue();
218        }
219                return -1;
220    }
221        
222        ///////////////////////////////////////////////////////////////////////////
223        // ExtendedObjectOutput implementation
224
225        public Reflection getReflection() {
226                return context.getReflection();
227        }
228
229        public String getAlias(String className) {
230                return context.getRemoteAlias(className);
231        }
232
233        public void getAndWriteProperty(Object obj, Property property) throws IOException, IllegalAccessException, InvocationTargetException {
234                if (property.getType().isPrimitive())
235                        codecRegistry.getPrimitivePropertyCodec(property.getType()).encodePrimitive(this, obj, property);
236                else
237                        writeObject(property.getObject(obj));
238        }
239        
240        ///////////////////////////////////////////////////////////////////////////
241        // Debug
242
243        public String toDumpString() {
244                final Comparator<Map.Entry<?, Integer>> comparator = new Comparator<Map.Entry<?, Integer>>() {
245                        @Override
246                        public int compare(Entry<?, Integer> o1, Entry<?, Integer> o2) {
247                                return o1.getValue().compareTo(o2.getValue());
248                        }
249                };
250                
251                StringBuilder sb = new StringBuilder(getClass().getName());
252                sb.append(" {\n");
253                
254                TreeSet<Map.Entry<String, Integer>> setStringInteger = new TreeSet<Map.Entry<String, Integer>>(comparator);
255                setStringInteger.addAll(classNames.entrySet());
256                sb.append("    classNames=[\n");
257                for (Map.Entry<String, Integer> entry : setStringInteger)
258                        sb.append("        ").append(entry.getValue()).append(": \"").append(entry.getKey()).append("\"\n");
259                sb.append("    ],\n");
260                
261                setStringInteger = new TreeSet<Map.Entry<String, Integer>>(comparator);
262                setStringInteger.addAll(strings.entrySet());
263                sb.append("    strings=[\n");
264                for (Map.Entry<String, Integer> entry : setStringInteger)
265                        sb.append("        ").append(entry.getValue()).append(": \"").append(entry.getKey()).append("\"\n");
266                sb.append("    ],\n");
267                
268                TreeSet<Map.Entry<Object, Integer>> setObjectInteger = new TreeSet<Map.Entry<Object, Integer>>(comparator);
269                setObjectInteger.addAll(objects.entrySet());
270                sb.append("    objects=[\n");
271                for (Map.Entry<Object, Integer> entry : setObjectInteger) {
272                        sb.append("        ").append(entry.getValue()).append(": ").append(entry.getKey().getClass().getName())
273                          .append("@").append(System.identityHashCode(entry.getKey())).append("\n");
274                }
275                sb.append("    ]\n");
276                
277                sb.append("}");
278                return sb.toString();
279        }
280}