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.IOException;
025import java.io.OutputStream;
026import java.lang.reflect.Array;
027
028import org.granite.messaging.jmf.CodecRegistry;
029import org.granite.messaging.jmf.DumpContext;
030import org.granite.messaging.jmf.InputContext;
031import org.granite.messaging.jmf.JMFEncodingException;
032import org.granite.messaging.jmf.OutputContext;
033import org.granite.messaging.jmf.codec.StandardCodec;
034import org.granite.messaging.jmf.codec.std.ObjectArrayCodec;
035import org.granite.messaging.jmf.codec.std.impl.util.ClassNameUtil;
036import org.granite.messaging.jmf.codec.std.impl.util.IntegerUtil;
037
038/**
039 * @author Franck WOLFF
040 */
041public class ObjectArrayCodecImpl extends AbstractArrayCodec implements ObjectArrayCodec {
042        
043        public int getObjectType() {
044                return JMF_OBJECT_ARRAY;
045        }
046
047        public boolean canEncode(Object v) {
048                return v.getClass().isArray() && !getComponentType(v).isPrimitive();
049        }
050
051        public void encode(OutputContext ctx, Object v) throws IOException {
052                int indexOfStoredObject = ctx.indexOfObject(v);
053                if (indexOfStoredObject != -1) {
054                        int count = IntegerUtil.significantIntegerBytesCount0(indexOfStoredObject);
055                        // Write the index to a stored array:
056                        // [parameterized type{1}, index{1,4}]
057                        ctx.getOutputStream().write(0x80 | (count << 4) | JMF_OBJECT_ARRAY);
058                        IntegerUtil.encodeInteger(ctx, indexOfStoredObject, count);
059                }
060                else {
061                        ctx.addToObjects(v);
062                        
063                        ArrayStructure structure = new ArrayStructure(v);
064                        String className = ctx.getAlias(structure.componentType.getName());
065                        int length = Array.getLength(v);
066                        int count = IntegerUtil.significantIntegerBytesCount0(length);
067
068                        final OutputStream os = ctx.getOutputStream();
069                        if (structure.dimensions == 0) {
070                                // Write the length and component type of the array:
071                                // [parameterized type{1}, length{1,4}, component type name{1+}]
072                                os.write((count << 4) | JMF_OBJECT_ARRAY);
073                                IntegerUtil.encodeInteger(ctx, length, count);
074                                ClassNameUtil.encodeClassName(ctx, className);
075                                
076                                writeObjectArrayContent0(ctx, v);
077                        }
078                        else {
079                                // Write the length, component type and dimensions of the array:
080                                // [parameterized type{1}, length{1,4}, component type name{2+}, dimensions{1}]
081                                os.write(0x40 | (count << 4) | JMF_OBJECT_ARRAY);
082                                IntegerUtil.encodeInteger(ctx, length, count);
083                                ClassNameUtil.encodeClassName(ctx, className);
084                                os.write(structure.dimensions);
085
086                                writeObjectArrayContent(ctx, v, structure.componentType, structure.dimensions);
087                                
088                        }
089                }
090        }
091
092        protected void writeObjectArrayContent(OutputContext ctx, Object v, Class<?> componentType, int dimensions) throws IOException {
093                final int length = Array.getLength(v);
094                if (length == 0)
095                        return;
096                
097                dimensions--;
098                final boolean writeObjectArrayContent0 = (dimensions == 0);
099                final OutputStream os = ctx.getOutputStream();
100
101                for (int index = 0; index < length; index++) {
102                        Object item = Array.get(v, index);
103                        if (item == null)
104                                os.write(JMF_NULL);
105                        else {
106                                int indexOfStoredObject = ctx.indexOfObject(item);
107                                if (indexOfStoredObject != -1) {
108                                        int count = IntegerUtil.significantIntegerBytesCount0(indexOfStoredObject);
109                                        // Write the index to a stored array:
110                                        // [parameterized type{1}, index{1,4}]
111                                        ctx.getOutputStream().write(0x80 | (count << 4) | JMF_OBJECT_ARRAY);
112                                        IntegerUtil.encodeInteger(ctx, indexOfStoredObject, count);
113                                }
114                                else {
115                                        ctx.addToObjects(item);
116                                        
117                                        int itemLength = Array.getLength(item);
118                                        int count = IntegerUtil.significantIntegerBytesCount0(itemLength);
119
120                                        // Write the length of the array:
121                                        // [parameterized type{1}, item length{1,4}]
122                                        os.write((count << 4) | JMF_OBJECT_ARRAY);
123                                        IntegerUtil.encodeInteger(ctx, itemLength, count);
124                                        
125                                        // Write item class name:
126                                        // [item type name{2+}]
127                                        Class<?> itemType = getComponentType(item);
128                                        String className = ctx.getAlias(itemType.getName());
129                                        ClassNameUtil.encodeClassName(ctx, className);
130                                        
131                                        if (writeObjectArrayContent0)
132                                                writeObjectArrayContent0(ctx, item);
133                                        else
134                                                writeObjectArrayContent(ctx, item, itemType, dimensions);
135                                }
136                        }
137                }
138        }
139        
140        protected void writeObjectArrayContent0(OutputContext ctx, Object v) throws IOException {
141                final int length = Array.getLength(v);
142                if (length == 0)
143                        return;
144                
145                for (int index = 0; index < length; index++)
146                        ctx.writeObject(Array.get(v, index));
147        }
148
149        public Object decode(InputContext ctx, int parameterizedJmfType) throws IOException, ClassNotFoundException {
150                Object v = null;
151                
152                // Read the index (stored array) or length of the array:
153                // [index or length{1,4}]
154                int indexOrLength = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >> 4) & 0x03);
155                if ((parameterizedJmfType & 0x80) != 0)
156                        v = ctx.getObject(indexOrLength);
157                else {
158                        // Read the element class name and, if the 0x40 flag is set, the dimensions of the array:
159                        // [element class name{2+}, dimensions{0,1}]
160                        String componentTypeName = ctx.getAlias(ClassNameUtil.decodeClassName(ctx));
161                        int dimensions = ((parameterizedJmfType & 0x40) == 0 ? 0 : ctx.safeRead());
162                        
163                        Class<?> componentType = ctx.getSharedContext().getReflection().loadClass(componentTypeName);
164                        if (dimensions == 0)
165                                v = readObjectArray0(ctx, componentType, indexOrLength);
166                        else
167                                v = readObjectArray(ctx, componentType, indexOrLength, dimensions);
168                }
169                
170                return v;
171        }
172        
173        protected Object readObjectArray(InputContext ctx, Class<?> componentType, int length, int dimensions) throws IOException, ClassNotFoundException {
174                Object v = newArray(componentType, length, dimensions);
175                ctx.addToObjects(v);
176
177                dimensions--;
178                
179                final boolean readObjectArray0 = (dimensions == 0);
180                for (int index = 0; index < length; index++) {
181                        // Read the type of the element (must be JMF_NULL or JMF_OBJECT_ARRAY):
182                        // [array element type{1}]
183                        int eltParameterizedJmfType = ctx.safeRead();
184
185                        if (eltParameterizedJmfType == JMF_NULL)
186                                Array.set(v, index, null);
187                        else {
188                                // Read the length of the element (sub array):
189                                // [length{1, 4}]
190                                int eltIndexOrLength = IntegerUtil.decodeInteger(ctx, (eltParameterizedJmfType >>> 4) & 0x03);
191                                if ((eltParameterizedJmfType & 0x80) != 0)
192                                        Array.set(v, index, ctx.getObject(eltIndexOrLength));
193                                else {
194                                        // Read element class name:
195                                        // [element class name{2+}]
196                                        String eltClassName = ctx.getAlias(ClassNameUtil.decodeClassName(ctx));
197                                        Class<?> eltComponentType = ctx.getReflection().loadClass(eltClassName);
198                                        
199                                        if (readObjectArray0)
200                                                Array.set(v, index, readObjectArray0(ctx, eltComponentType, eltIndexOrLength));
201                                        else
202                                                Array.set(v, index, readObjectArray(ctx, eltComponentType, eltIndexOrLength, dimensions));
203                                }
204                        }
205                }
206                
207                return v;
208        }
209        
210        protected Object readObjectArray0(InputContext ctx, Class<?> componentType, int length) throws IOException, ClassNotFoundException {
211                Object v = Array.newInstance(componentType, length);
212                ctx.addToObjects(v);
213
214                for (int index = 0; index < length; index++)
215                        Array.set(v, index, ctx.readObject());
216
217                return v;
218        }
219
220        public void dump(DumpContext ctx, int parameterizedJmfType) throws IOException {
221                final CodecRegistry codecRegistry = ctx.getSharedContext().getCodecRegistry();
222
223                int jmfType = codecRegistry.extractJmfType(parameterizedJmfType);
224                
225                if (jmfType != JMF_OBJECT_ARRAY)
226                        throw newBadTypeJMFEncodingException(jmfType, parameterizedJmfType);
227                
228                // Read the index (stored array) or length of the array:
229                // [index or length{1,4}]
230                int indexOrLength = IntegerUtil.decodeInteger(ctx, (parameterizedJmfType >>> 4) & 0x03);
231                if ((parameterizedJmfType & 0x80) != 0)
232                        ctx.indentPrintLn("<" + ctx.getObject(indexOrLength) + "@" + indexOrLength + ">");
233                else {
234                        // Read the element class name and, if the 0x40 flag is set, the dimensions of the array:
235                        // [element class name{2+}, dimensions{0,1}]
236                        String eltClassName = ctx.getAlias(ClassNameUtil.decodeClassName(ctx));
237                        int dimensions = ((parameterizedJmfType & 0x40) == 0 ? 0 : ctx.safeRead());
238                        
239                        if (dimensions == 0)
240                                dumpObjectArray0(ctx, eltClassName, indexOrLength);
241                        else
242                                dumpObjectArray(ctx, eltClassName, indexOrLength, dimensions);
243                }
244        }
245        
246        protected void dumpObjectArray(DumpContext ctx, String componentType, int length, int dimensions) throws IOException {
247                String v = newDumpObjectArray(componentType, length, 0);
248                int indexOfStoredObject = ctx.addToObjects(v);
249                ctx.indentPrintLn(v + "@" + indexOfStoredObject + ": {");
250                ctx.incrIndent(1);
251
252                dimensions--;
253                
254                final boolean dumpObjectArray0 = (dimensions == 0);
255                for (int index = 0; index < length; index++) {
256                        // Read the type of the element (must be JMF_NULL or JMF_OBJECT_ARRAY):
257                        // [array element type{1}]
258                        int eltParameterizedJmfType = ctx.safeRead();
259                        int eltJmfType = ctx.getSharedContext().getCodecRegistry().extractJmfType(eltParameterizedJmfType);
260
261                        if (eltJmfType == JMF_NULL)
262                                ctx.indentPrintLn("null");
263                        else if (eltJmfType == JMF_OBJECT_ARRAY) {
264                                // Read the length of the element (sub array):
265                                // [length{1, 4}]
266                                int eltIndexOrLength = IntegerUtil.decodeInteger(ctx, (eltParameterizedJmfType >>> 4) & 0x03);
267                                if ((eltParameterizedJmfType & 0x80) != 0)
268                                        ctx.indentPrintLn("<" + ctx.getObject(eltIndexOrLength) + "@" + eltIndexOrLength + ">");
269                                else {
270                                        // Read element class name:
271                                        // [element class name{2+}]
272                                        String eltClassName = ctx.getAlias(ClassNameUtil.decodeClassName(ctx));
273                                        
274                                        if (dumpObjectArray0)
275                                                dumpObjectArray0(ctx, eltClassName, eltIndexOrLength);
276                                        else
277                                                dumpObjectArray(ctx, eltClassName, eltIndexOrLength, dimensions);
278                                }
279                        }
280                        else
281                                newBadTypeJMFEncodingException(eltJmfType, eltParameterizedJmfType);
282                }
283
284                ctx.incrIndent(-1);
285                ctx.indentPrintLn("}");
286        }
287        
288        protected void dumpObjectArray0(DumpContext ctx, String componentType, int length) throws IOException {
289                String v = newDumpObjectArray(componentType, length, 0);
290                int indexOfStoredObject = ctx.addToObjects(v);
291                ctx.indentPrintLn(v + "@" + indexOfStoredObject + ": {");
292                ctx.incrIndent(1);
293                
294                for (int index = 0; index < length; index++) {
295                        int parameterizedJmfType = ctx.safeRead();
296                        int jmfType = ctx.getSharedContext().getCodecRegistry().extractJmfType(parameterizedJmfType);
297                        StandardCodec<?> codec = ctx.getSharedContext().getCodecRegistry().getCodec(jmfType);
298                        
299                        if (codec == null)
300                                throw new JMFEncodingException("No codec for JMF type: " + jmfType);
301                        
302                        codec.dump(ctx, parameterizedJmfType);
303                }
304
305                ctx.incrIndent(-1);
306                ctx.indentPrintLn("}");
307        }
308        
309        protected String newDumpObjectArray(String componentTypeName, int length, int dimensions) {
310                StringBuilder sb = new StringBuilder(componentTypeName);
311                
312                sb.append('[').append(length).append(']');
313                
314                for (int i = 0; i < dimensions; i++)
315                        sb.append("[]");
316                
317                return sb.toString();
318                
319        }
320}