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     */
022    package org.granite.messaging.amf.io;
023    
024    import java.io.DataOutputStream;
025    import java.io.Externalizable;
026    import java.io.IOException;
027    import java.io.ObjectOutput;
028    import java.io.OutputStream;
029    import java.lang.reflect.Array;
030    import java.math.BigDecimal;
031    import java.math.BigInteger;
032    import java.util.Calendar;
033    import java.util.Collection;
034    import java.util.Date;
035    import java.util.HashMap;
036    import java.util.IdentityHashMap;
037    import java.util.Map;
038    
039    import org.granite.config.flex.Channel;
040    import org.granite.context.GraniteContext;
041    import org.granite.logging.Logger;
042    import org.granite.messaging.amf.AMF3Constants;
043    import org.granite.messaging.amf.io.convert.Converters;
044    import org.granite.messaging.amf.io.util.ClassGetter;
045    import org.granite.messaging.amf.io.util.DefaultJavaClassDescriptor;
046    import org.granite.messaging.amf.io.util.IndexedJavaClassDescriptor;
047    import org.granite.messaging.amf.io.util.JavaClassDescriptor;
048    import org.granite.messaging.amf.io.util.externalizer.Externalizer;
049    import org.granite.messaging.amf.types.AMFDictionaryValue;
050    import org.granite.messaging.amf.types.AMFSpecialValue;
051    import org.granite.messaging.amf.types.AMFSpecialValueFactory;
052    import org.granite.messaging.amf.types.AMFVectorIntValue;
053    import org.granite.messaging.amf.types.AMFVectorNumberValue;
054    import org.granite.messaging.amf.types.AMFVectorObjectValue;
055    import org.granite.messaging.amf.types.AMFVectorUintValue;
056    import org.granite.util.TypeUtil;
057    import org.granite.util.XMLUtil;
058    import org.granite.util.XMLUtilFactory;
059    import org.w3c.dom.Document;
060    
061    import flex.messaging.io.ArrayCollection;
062    
063    /**
064     * @author Franck WOLFF
065     */
066    public class AMF3Serializer extends DataOutputStream implements ObjectOutput, AMF3Constants {
067    
068        ///////////////////////////////////////////////////////////////////////////
069        // Fields.
070    
071        protected static final Logger log = Logger.getLogger(AMF3Serializer.class);
072        protected static final Logger logMore = Logger.getLogger(AMF3Serializer.class.getName() + "_MORE");
073    
074        protected final Map<String, Integer> storedStrings = new HashMap<String, Integer>();
075        protected final Map<Object, Integer> storedObjects = new IdentityHashMap<Object, Integer>();
076        protected final Map<String, IndexedJavaClassDescriptor> storedClassDescriptors
077            = new HashMap<String, IndexedJavaClassDescriptor>();
078    
079        protected final GraniteContext context = GraniteContext.getCurrentInstance();
080        protected final Converters converters = context.getGraniteConfig().getConverters();
081    
082        protected final boolean externalizeLong
083                    = (context.getGraniteConfig().getExternalizer(Long.class.getName()) != null);
084        protected final boolean externalizeBigInteger
085                    = (context.getGraniteConfig().getExternalizer(BigInteger.class.getName()) != null);
086        protected final boolean externalizeBigDecimal
087            = (context.getGraniteConfig().getExternalizer(BigDecimal.class.getName()) != null);
088    
089        protected final XMLUtil xmlUtil = XMLUtilFactory.getXMLUtil();
090        
091        // TODO: allow aliaser configuration in granite-config.xml
092        protected final AMFSpecialValueFactory specialValueFactory = new AMFSpecialValueFactory();
093        
094        protected final boolean debug = log.isDebugEnabled();
095        protected final boolean debugMore = logMore.isDebugEnabled();
096    
097        protected Channel channel = null;
098    
099        ///////////////////////////////////////////////////////////////////////////
100        // Constructor.
101    
102        public AMF3Serializer(OutputStream out) {
103            super(out);
104    
105            if (debugMore) logMore.debug("new AMF3Serializer(out=%s)", out);
106        }
107    
108        ///////////////////////////////////////////////////////////////////////////
109        // ObjectOutput implementation.
110    
111        public void writeObject(Object o) throws IOException {
112            if (debugMore) logMore.debug("writeObject(o=%s)", o);
113    
114            try {
115                    if (o == null)
116                        write(AMF3_NULL);
117                    else if (o instanceof AMFSpecialValue)
118                            writeAMF3SpecialValue((AMFSpecialValue<?>)o);
119                    else if (!(o instanceof Externalizable)) {
120            
121                        if (converters.hasReverters())
122                            o = converters.revert(o);
123            
124                            if (o == null)
125                                write(AMF3_NULL);
126                            else if (o instanceof String || o instanceof Character)
127                            writeAMF3String(o.toString());
128                        else if (o instanceof Boolean)
129                            write(((Boolean)o).booleanValue() ? AMF3_BOOLEAN_TRUE : AMF3_BOOLEAN_FALSE);
130                        else if (o instanceof Number) {
131                            if (o instanceof Integer || o instanceof Short || o instanceof Byte)
132                                writeAMF3Integer(((Number)o).intValue());
133                            else if (externalizeLong && o instanceof Long)
134                                    writeAMF3Object(o);
135                            else if (externalizeBigInteger && o instanceof BigInteger)
136                                    writeAMF3Object(o);
137                            else if (externalizeBigDecimal && o instanceof BigDecimal)
138                                    writeAMF3Object(o);
139                            else
140                                writeAMF3Number(((Number)o).doubleValue());
141                        }
142                        else if (o instanceof Date)
143                            writeAMF3Date((Date)o);
144                        else if (o instanceof Calendar)
145                            writeAMF3Date(((Calendar)o).getTime());
146                        else if (o instanceof Document)
147                            writeAMF3Xml((Document)o);
148                        else if (o instanceof Collection<?>)
149                            writeAMF3Collection((Collection<?>)o);
150                        else if (o.getClass().isArray()) {
151                            if (o.getClass().getComponentType() == Byte.TYPE)
152                                writeAMF3ByteArray((byte[])o);
153                            else
154                                writeAMF3Array(o);
155                        }
156                        else
157                            writeAMF3Object(o);
158                    }
159                    else
160                        writeAMF3Object(o);
161            }
162            catch (IOException e) {
163                    throw e;
164            }
165            catch (Exception e) {
166                    throw new AMF3SerializationException(e);
167            }
168        }
169    
170        ///////////////////////////////////////////////////////////////////////////
171        // AMF3 serialization.
172    
173        protected void writeAMF3SpecialValue(AMFSpecialValue<?> value) throws IOException {
174            if (debugMore) logMore.debug("writeAMF3SpecialValue(value=%s)", value);
175    
176            switch (value.type) {
177            case AMF3_DICTIONARY:
178                            writeAMF3Dictionary((AMFDictionaryValue)value);
179                            break;
180            case AMF3_VECTOR_INT:
181                            writeAMF3VectorInt((AMFVectorIntValue)value);
182                    break;
183            case AMF3_VECTOR_NUMBER:
184                            writeAMF3VectorNumber((AMFVectorNumberValue)value);
185                    break;
186            case AMF3_VECTOR_UINT:
187                            writeAMF3VectorUint((AMFVectorUintValue)value);
188                    break;
189            case AMF3_VECTOR_OBJECT:
190                            writeAMF3VectorObject((AMFVectorObjectValue)value);
191                    break;
192            default:
193                            throw new RuntimeException("Unsupported AMF special value: " + value);
194            }
195        }
196        
197        protected void writeAMF3VectorObject(AMFVectorObjectValue value) throws IOException {
198            if (debugMore) logMore.debug("writeAMF3VectorObject(value=%s)", value);
199    
200            write(AMF3_VECTOR_OBJECT);
201    
202            Object o = value.value;
203            
204            int index = indexOfStoredObjects(o);
205            if (index >= 0)
206                writeAMF3IntegerData(index << 1);
207            else {
208                addToStoredObjects(o);
209    
210                int length = getArrayOrCollectionLength(o);
211                writeAMF3IntegerData(length << 1 | 0x01);
212                write(value.fixed ? 0x01 : 0x00);
213                writeAMF3StringData(value.type);
214    
215                if (o.getClass().isArray()) {
216                    for (int i = 0; i < length; i++)
217                        writeObject(Array.get(o, i));
218                }
219                else {
220                    for (Object item : (Collection<?>)o)
221                            writeObject(item);
222                }
223            }
224        }
225        
226        protected void writeAMF3VectorInt(AMFVectorIntValue value) throws IOException {
227            if (debugMore) logMore.debug("writeAMF3VectorInt(value=%s)", value);
228    
229            write(AMF3_VECTOR_INT);
230    
231            Object o = value.value;
232    
233            int index = indexOfStoredObjects(o);
234            if (index >= 0)
235                writeAMF3IntegerData(index << 1);
236            else {
237                addToStoredObjects(o);
238    
239                int length = getArrayOrCollectionLength(o);
240                writeAMF3IntegerData(length << 1 | 0x01);
241                write(value.fixed ? 0x01 : 0x00);
242    
243                if (o.getClass().isArray()) {
244                    for (int i = 0; i < length; i++)
245                            writeInt(((Number)Array.get(o, i)).intValue());
246                }
247                else {
248                    for (Object item : (Collection<?>)o)
249                            writeInt(((Number)item).intValue());
250                }
251            }
252        }
253        
254        protected void writeAMF3VectorNumber(AMFVectorNumberValue value) throws IOException {
255            if (debugMore) logMore.debug("writeAMF3VectorNumber(value=%s)", value);
256    
257            write(AMF3_VECTOR_NUMBER);
258    
259            Object o = value.value;
260    
261            int index = indexOfStoredObjects(o);
262            if (index >= 0)
263                writeAMF3IntegerData(index << 1);
264            else {
265                addToStoredObjects(o);
266    
267                int length = getArrayOrCollectionLength(o);
268                writeAMF3IntegerData(length << 1 | 0x01);
269                write(value.fixed ? 0x01 : 0x00);
270    
271                if (o.getClass().isArray()) {
272                    for (int i = 0; i < length; i++)
273                            writeDouble(((Number)Array.get(o, i)).doubleValue());
274                }
275                else {
276                    for (Object item : (Collection<?>)o)
277                            writeDouble(((Number)item).doubleValue());
278                }
279            }
280        }
281        
282        protected void writeAMF3VectorUint(AMFVectorUintValue value) throws IOException {
283            if (debugMore) logMore.debug("writeAMF3VectorUint(value=%s)", value);
284    
285            write(AMF3_VECTOR_UINT);
286    
287            Object o = value.value;
288    
289            int index = indexOfStoredObjects(o);
290            if (index >= 0)
291                writeAMF3IntegerData(index << 1);
292            else {
293                addToStoredObjects(o);
294    
295                int length = getArrayOrCollectionLength(o);
296                writeAMF3IntegerData(length << 1 | 0x01);
297                write(value.fixed ? 0x01 : 0x00);
298    
299                if (o.getClass().isArray()) {
300                    for (int i = 0; i < length; i++)
301                            writeInt(((Number)Array.get(o, i)).intValue());
302                }
303                else {
304                    for (Object item : (Collection<?>)o)
305                            writeInt(((Number)item).intValue());
306                }
307            }
308        }
309    
310        protected void writeAMF3Dictionary(AMFDictionaryValue value) throws IOException {
311            if (debugMore) logMore.debug("writeAMFDictionary(value=%s)", value);
312    
313            write(AMF3_DICTIONARY);
314    
315            Map<?, ?> o = value.value;
316    
317            int index = indexOfStoredObjects(o);
318            if (index >= 0)
319                writeAMF3IntegerData(index << 1);
320            else {
321                addToStoredObjects(o);
322    
323                int length = o.size();
324                writeAMF3IntegerData(length << 1 | 0x01);
325                write(value.weakKeys ? 0x01 : 0x00);
326    
327                for (Map.Entry<?, ?> entry : o.entrySet()) {
328                    writeObject(entry.getKey());
329                    writeObject(entry.getValue());
330                }
331            }
332        }
333        
334        protected int getArrayOrCollectionLength(Object o) {
335            if (o.getClass().isArray())
336                    return Array.getLength(o);
337            return ((Collection<?>)o).size();
338        }
339    
340        protected void writeAMF3Integer(int i) throws IOException {
341            if (debugMore) logMore.debug("writeAMF3Integer(i=%d)", i);
342    
343            if (i < AMF3_INTEGER_MIN || i > AMF3_INTEGER_MAX) {
344                if (debugMore) logMore.debug("writeAMF3Integer() - %d is out of AMF3 int range, writing it as a Number", i);
345                writeAMF3Number(i);
346            }
347            else {
348                write(AMF3_INTEGER);
349                writeAMF3IntegerData(i);
350            }
351        }
352    
353        protected void writeAMF3IntegerData(int i) throws IOException {
354            if (debugMore) logMore.debug("writeAMF3IntegerData(i=%d)", i);
355    
356            if (i < AMF3_INTEGER_MIN || i > AMF3_INTEGER_MAX)
357                throw new IllegalArgumentException("Integer out of range: " + i);
358    
359            if (i < 0 || i >= 0x200000) {
360                write(((i >> 22) & 0x7F) | 0x80);
361                write(((i >> 15) & 0x7F) | 0x80);
362                write(((i >> 8) & 0x7F) | 0x80);
363                write(i & 0xFF);
364            }
365            else {
366                if (i >= 0x4000)
367                    write(((i >> 14) & 0x7F) | 0x80);
368                if (i >= 0x80)
369                    write(((i >> 7) & 0x7F) | 0x80);
370                write(i & 0x7F);
371            }
372        }
373    
374        protected void writeAMF3Number(double d) throws IOException {
375            if (debugMore) logMore.debug("writeAMF3Number(d=%f)", d);
376    
377            write(AMF3_NUMBER);
378            writeDouble(d);
379        }
380    
381        protected void writeAMF3String(String s) throws IOException {
382            if (debugMore) logMore.debug("writeAMF3String(s=%s)", s);
383    
384            write(AMF3_STRING);
385            writeAMF3StringData(s);
386        }
387    
388        protected void writeAMF3StringData(String s) throws IOException {
389            if (debugMore) logMore.debug("writeAMF3StringData(s=%s)", s);
390    
391            if (s.length() == 0) {
392                write(0x01);
393                return;
394            }
395    
396            int index = indexOfStoredStrings(s);
397    
398            if (index >= 0)
399                writeAMF3IntegerData(index << 1);
400            else {
401                addToStoredStrings(s);
402    
403                final int sLength = s.length();
404    
405                // Compute and write modified UTF-8 string length.
406                int uLength = 0;
407                for (int i = 0; i < sLength; i++) {
408                    int c = s.charAt(i);
409                    if ((c >= 0x0001) && (c <= 0x007F))
410                        uLength++;
411                    else if (c > 0x07FF)
412                        uLength += 3;
413                    else
414                        uLength += 2;
415                }
416                writeAMF3IntegerData((uLength << 1) | 0x01);
417    
418                // Write modified UTF-8 bytes.
419                for (int i = 0; i < sLength; i++) {
420                    int c = s.charAt(i);
421                    if ((c >= 0x0001) && (c <= 0x007F)) {
422                        write(c);
423                    }
424                    else if (c > 0x07FF) {
425                        write(0xE0 | ((c >> 12) & 0x0F));
426                        write(0x80 | ((c >>  6) & 0x3F));
427                        write(0x80 | ((c >>  0) & 0x3F));
428                    }
429                    else {
430                        write(0xC0 | ((c >>  6) & 0x1F));
431                        write(0x80 | ((c >>  0) & 0x3F));
432                    }
433                }
434            }
435        }
436    
437        protected void writeAMF3Xml(Document doc) throws IOException {
438            if (debugMore) logMore.debug("writeAMF3Xml(doc=%s)", doc);
439    
440            byte xmlType = AMF3_XMLSTRING;
441            Channel channel = getChannel();
442            if (channel != null && channel.isLegacyXmlSerialization())
443                xmlType = AMF3_XML;
444            write(xmlType);
445    
446            int index = indexOfStoredObjects(doc);
447            if (index >= 0)
448                writeAMF3IntegerData(index << 1);
449            else {
450                addToStoredObjects(doc);
451    
452                byte[] bytes = xmlUtil.toString(doc).getBytes("UTF-8");
453                writeAMF3IntegerData((bytes.length << 1) | 0x01);
454                write(bytes);
455            }
456        }
457    
458        protected void writeAMF3Date(Date date) throws IOException {
459            if (debugMore) logMore.debug("writeAMF3Date(date=%s)", date);
460    
461            write(AMF3_DATE);
462    
463            int index = indexOfStoredObjects(date);
464            if (index >= 0)
465                writeAMF3IntegerData(index << 1);
466            else {
467                addToStoredObjects(date);
468                writeAMF3IntegerData(0x01);
469                writeDouble(date.getTime());
470            }
471        }
472    
473        protected void writeAMF3Array(Object array) throws IOException {
474            if (debugMore) logMore.debug("writeAMF3Array(array=%s)", array);
475    
476            write(AMF3_ARRAY);
477    
478            int index = indexOfStoredObjects(array);
479            if (index >= 0)
480                writeAMF3IntegerData(index << 1);
481            else {
482                addToStoredObjects(array);
483    
484                int length = Array.getLength(array);
485                writeAMF3IntegerData(length << 1 | 0x01);
486                write(0x01);
487                for (int i = 0; i < length; i++)
488                    writeObject(Array.get(array, i));
489            }
490        }
491    
492        protected void writeAMF3ByteArray(byte[] bytes) throws IOException {
493            if (debugMore) logMore.debug("writeAMF3ByteArray(bytes=%s)", bytes);
494    
495            write(AMF3_BYTEARRAY);
496    
497            int index = indexOfStoredObjects(bytes);
498            if (index >= 0)
499                writeAMF3IntegerData(index << 1);
500            else {
501                addToStoredObjects(bytes);
502    
503                writeAMF3IntegerData(bytes.length << 1 | 0x01);
504                //write(bytes);
505    
506                for (int i = 0; i < bytes.length; i++)
507                    out.write(bytes[i]);
508            }
509        }
510    
511        protected void writeAMF3Collection(Collection<?> c) throws IOException {
512            if (debugMore) logMore.debug("writeAMF3Collection(c=%s)", c);
513    
514            Channel channel = getChannel();
515            if (channel != null && channel.isLegacyCollectionSerialization())
516                writeAMF3Array(c.toArray());
517            else {
518                ArrayCollection ac = (c instanceof ArrayCollection ? (ArrayCollection)c : new ArrayCollection(c));
519                writeAMF3Object(ac);
520            }
521        }
522    
523        protected void writeAMF3Object(Object o) throws IOException {
524            if (debug) log.debug("writeAMF3Object(o=%s)...", o);
525    
526            write(AMF3_OBJECT);
527    
528            int index = indexOfStoredObjects(o);
529            if (index >= 0)
530                writeAMF3IntegerData(index << 1);
531            else {
532                addToStoredObjects(o);
533    
534                ClassGetter classGetter = context.getGraniteConfig().getClassGetter();
535                if (debug) log.debug("writeAMF3Object() - classGetter=%s", classGetter);
536    
537                Class<?> oClass = classGetter.getClass(o);
538                if (debug) log.debug("writeAMF3Object() - oClass=%s", oClass);
539    
540                JavaClassDescriptor desc = null;
541    
542                // write class description.
543                IndexedJavaClassDescriptor iDesc = getFromStoredClassDescriptors(oClass);
544                if (iDesc != null) {
545                    desc = iDesc.getDescriptor();
546                    writeAMF3IntegerData(iDesc.getIndex() << 2 | 0x01);
547                }
548                else {
549                    iDesc = addToStoredClassDescriptors(oClass);
550                    desc = iDesc.getDescriptor();
551    
552                    writeAMF3IntegerData((desc.getPropertiesCount() << 4) | (desc.getEncoding() << 2) | 0x03);
553                    writeAMF3StringData(desc.getName());
554    
555                    for (int i = 0; i < desc.getPropertiesCount(); i++)
556                        writeAMF3StringData(desc.getPropertyName(i));
557                }
558                if (debug) log.debug("writeAMF3Object() - desc=%s", desc);
559    
560                // write object content.
561                if (desc.isExternalizable()) {
562                    Externalizer externalizer = desc.getExternalizer();
563    
564                    if (externalizer != null) {
565                        if (debug) log.debug("writeAMF3Object() - using externalizer=%s", externalizer);
566                        try {
567                            externalizer.writeExternal(o, this);
568                        }
569                        catch (IOException e) {
570                            throw e;
571                        }
572                        catch (Exception e) {
573                            throw new RuntimeException("Could not externalize object: " + o, e);
574                        }
575                    }
576                    else {
577                        if (debug) log.debug("writeAMF3Object() - legacy Externalizable=%s", o);
578                        ((Externalizable)o).writeExternal(this);
579                    }
580                }
581                else {
582                    if (debug) log.debug("writeAMF3Object() - writing defined properties...");
583                    for (int i = 0; i < desc.getPropertiesCount(); i++) {
584                        Object obj = desc.getPropertyValue(i, o);
585                        if (debug) log.debug("writeAMF3Object() - writing defined property: %s=%s", desc.getPropertyName(i), obj);
586                        writeObject(specialValueFactory.createSpecialValue(desc.getProperty(i), obj));
587                    }
588    
589                    if (desc.isDynamic()) {
590                        if (debug) log.debug("writeAMF3Object() - writing dynamic properties...");
591                        Map<?, ?> oMap = (Map<?, ?>)o;
592                        for (Map.Entry<?, ?> entry : oMap.entrySet()) {
593                            Object key = entry.getKey();
594                            if (key != null) {
595                                String propertyName = key.toString();
596                                if (propertyName.length() > 0) {
597                                    if (debug) log.debug(
598                                        "writeAMF3Object() - writing dynamic property: %s=%s",
599                                        propertyName, entry.getValue()
600                                    );
601                                    writeAMF3StringData(propertyName);
602                                    writeObject(entry.getValue());
603                                }
604                            }
605                        }
606                        writeAMF3StringData("");
607                    }
608                }
609            }
610    
611            if (debug) log.debug("writeAMF3Object(o=%s) - Done", o);
612        }
613    
614        ///////////////////////////////////////////////////////////////////////////
615        // Cached objects methods.
616    
617        protected void addToStoredStrings(String s) {
618            if (!storedStrings.containsKey(s)) {
619                Integer index = Integer.valueOf(storedStrings.size());
620                if (debug) log.debug("addToStoredStrings(s=%s) at index=%d", s, index);
621                storedStrings.put(s, index);
622            }
623        }
624    
625        protected int indexOfStoredStrings(String s) {
626            Integer index = storedStrings.get(s);
627            if (debug) log.debug("indexOfStoredStrings(s=%s) -> %d", s, (index != null ? index : -1));
628            return (index != null ? index : -1);
629        }
630    
631        protected void addToStoredObjects(Object o) {
632            if (o != null && !storedObjects.containsKey(o)) {
633                Integer index = Integer.valueOf(storedObjects.size());
634                if (debug) log.debug("addToStoredObjects(o=%s) at index=%d", o, index);
635                storedObjects.put(o, index);
636            }
637        }
638    
639        protected int indexOfStoredObjects(Object o) {
640            Integer index = storedObjects.get(o);
641            if (debug) log.debug("indexOfStoredObjects(o=%s) -> %d", o, (index != null ? index : -1));
642            return (index != null ? index : -1);
643        }
644    
645        protected IndexedJavaClassDescriptor addToStoredClassDescriptors(Class<?> clazz) {
646            final String name = JavaClassDescriptor.getClassName(clazz);
647    
648            if (debug) log.debug("addToStoredClassDescriptors(clazz=%s)", clazz);
649    
650            if (storedClassDescriptors.containsKey(name))
651                throw new RuntimeException(
652                    "Descriptor of \"" + name + "\" is already stored at index: " +
653                    getFromStoredClassDescriptors(clazz).getIndex()
654                );
655    
656            // find custom class descriptor and instantiate if any
657            JavaClassDescriptor desc = null;
658    
659            Class<? extends JavaClassDescriptor> descriptorType
660                = context.getGraniteConfig().getJavaDescriptor(clazz.getName());
661            if (descriptorType != null) {
662                Class<?>[] argsDef = new Class[]{Class.class};
663                Object[] argsVal = new Object[]{clazz};
664                try {
665                    desc = TypeUtil.newInstance(descriptorType, argsDef, argsVal);
666                }
667                catch (Exception e) {
668                    throw new RuntimeException("Could not instantiate Java descriptor: " + descriptorType);
669                }
670            }
671    
672            if (desc == null)
673                desc = new DefaultJavaClassDescriptor(clazz);
674    
675            IndexedJavaClassDescriptor iDesc = new IndexedJavaClassDescriptor(storedClassDescriptors.size(), desc);
676    
677            if (debug) log.debug("addToStoredClassDescriptors() - putting: name=%s, iDesc=%s", name, iDesc);
678    
679            storedClassDescriptors.put(name, iDesc);
680    
681            return iDesc;
682        }
683    
684        protected IndexedJavaClassDescriptor getFromStoredClassDescriptors(Class<?> clazz) {
685            if (debug) log.debug("getFromStoredClassDescriptors(clazz=%s)", clazz);
686    
687            String name = JavaClassDescriptor.getClassName(clazz);
688            IndexedJavaClassDescriptor iDesc = storedClassDescriptors.get(name);
689    
690            if (debug) log.debug("getFromStoredClassDescriptors() -> %s", iDesc);
691    
692            return iDesc;
693        }
694    
695        ///////////////////////////////////////////////////////////////////////////
696        // Utilities.
697    
698        protected Channel getChannel() {
699            if (channel == null) {
700                String channelId = context.getAMFContext().getChannelId();
701                if (channelId != null)
702                    channel = context.getServicesConfig().findChannelById(channelId);
703                if (channel == null)
704                    log.debug("Could not get channel for channel id: %s", channelId);
705            }
706            return channel;
707        }
708    }