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.DataInputStream;
025    import java.io.EOFException;
026    import java.io.Externalizable;
027    import java.io.IOException;
028    import java.io.InputStream;
029    import java.io.ObjectInput;
030    import java.io.UTFDataFormatException;
031    import java.util.ArrayList;
032    import java.util.Date;
033    import java.util.HashMap;
034    import java.util.List;
035    import java.util.Map;
036    
037    import org.granite.context.GraniteContext;
038    import org.granite.logging.Logger;
039    import org.granite.messaging.amf.AMF3Constants;
040    import org.granite.messaging.amf.io.util.ActionScriptClassDescriptor;
041    import org.granite.messaging.amf.io.util.DefaultActionScriptClassDescriptor;
042    import org.granite.messaging.amf.io.util.externalizer.Externalizer;
043    import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator;
044    import org.granite.util.TypeUtil;
045    import org.granite.util.XMLUtil;
046    import org.granite.util.XMLUtilFactory;
047    import org.w3c.dom.Document;
048    
049    /**
050     * @author Franck WOLFF
051     */
052    public class AMF3Deserializer extends DataInputStream implements ObjectInput, AMF3Constants {
053    
054        ///////////////////////////////////////////////////////////////////////////
055        // Fields.
056    
057        protected static final Logger log = Logger.getLogger(AMF3Deserializer.class);
058        protected static final Logger logMore = Logger.getLogger(AMF3Deserializer.class.getName() + "_MORE");
059    
060        protected final List<String> storedStrings = new ArrayList<String>();
061        protected final List<Object> storedObjects = new ArrayList<Object>();
062        protected final List<ActionScriptClassDescriptor> storedClassDescriptors = new ArrayList<ActionScriptClassDescriptor>();
063    
064        protected final GraniteContext context = GraniteContext.getCurrentInstance();
065    
066        protected final AMF3DeserializerSecurizer securizer = context.getGraniteConfig().getAmf3DeserializerSecurizer();
067    
068        protected final XMLUtil xmlUtil = XMLUtilFactory.getXMLUtil();
069    
070        protected final boolean debug;
071        protected final boolean debugMore;
072    
073        ///////////////////////////////////////////////////////////////////////////
074        // Constructor.
075    
076        public AMF3Deserializer(InputStream in) {
077            super(in);
078    
079            debug = log.isDebugEnabled();
080            debugMore = logMore.isDebugEnabled();
081    
082            if (debugMore) logMore.debug("new AMF3Deserializer(in=%s)", in);
083        }
084    
085        ///////////////////////////////////////////////////////////////////////////
086        // ObjectInput implementation.
087    
088        public Object readObject() throws IOException {
089            if (debugMore) logMore.debug("readObject()...");
090    
091            try {
092                    int type = readAMF3Integer();
093                    return readObject(type);
094            }
095            catch (IOException e) {
096                    throw e;
097            }
098            catch (Exception e) {
099                    throw new AMF3SerializationException(e);
100            }
101        }
102    
103        ///////////////////////////////////////////////////////////////////////////
104        // AMF3 deserialization.
105    
106        protected Object readObject(int type) throws IOException {
107    
108            if (debugMore) logMore.debug("readObject(type=0x%02X)", type);
109    
110            switch (type) {
111            case AMF3_UNDEFINED: // 0x00;
112            case AMF3_NULL: // 0x01;
113                return null;
114            case AMF3_BOOLEAN_FALSE: // 0x02;
115                return Boolean.FALSE;
116            case AMF3_BOOLEAN_TRUE: // 0x03;
117                return Boolean.TRUE;
118            case AMF3_INTEGER: // 0x04;
119                return Integer.valueOf(readAMF3Integer());
120            case AMF3_NUMBER: // 0x05;
121                return readAMF3Double();
122            case AMF3_STRING: // 0x06;
123                return readAMF3String();
124            case AMF3_XML: // 0x07;
125                return readAMF3Xml();
126            case AMF3_DATE: // 0x08;
127                return readAMF3Date();
128            case AMF3_ARRAY: // 0x09;
129                return readAMF3Array();
130            case AMF3_OBJECT: // 0x0A;
131                return readAMF3Object();
132            case AMF3_XMLSTRING: // 0x0B;
133                return readAMF3XmlString();
134            case AMF3_BYTEARRAY: // 0x0C;
135                return readAMF3ByteArray();
136            case AMF3_VECTOR_INT: // 0x0D;
137                    return readAMF3VectorInt();
138            case AMF3_VECTOR_UINT: // 0x0E;
139                    return readAMF3VectorUint();
140            case AMF3_VECTOR_NUMBER: // 0x0F;
141                    return readAMF3VectorNumber();
142            case AMF3_VECTOR_OBJECT: // 0x10;
143                    return readAMF3VectorObject();
144            case AMF3_DICTIONARY: // 0x11;
145                    return readAMF3Dictionary();
146    
147            default:
148                throw new IllegalArgumentException("Unknown type: " + type);
149            }
150        }
151    
152        protected int readAMF3Integer() throws IOException {
153            int result = 0;
154    
155            int n = 0;
156            int b = readUnsignedByte();
157            while ((b & 0x80) != 0 && n < 3) {
158                result <<= 7;
159                result |= (b & 0x7f);
160                b = readUnsignedByte();
161                n++;
162            }
163            if (n < 3) {
164                result <<= 7;
165                result |= b;
166            } else {
167                result <<= 8;
168                result |= b;
169                if ((result & 0x10000000) != 0)
170                    result |= 0xe0000000;
171            }
172    
173            if (debugMore) logMore.debug("readAMF3Integer() -> %d", result);
174    
175            return result;
176        }
177    
178        protected Double readAMF3Double() throws IOException {
179            double d = readDouble();
180            Double result = (Double.isNaN(d) ? null : Double.valueOf(d));
181    
182            if (debugMore) logMore.debug("readAMF3Double() -> %f", result);
183    
184            return result;
185        }
186    
187        protected String readAMF3String() throws IOException {
188            String result = null;
189    
190            if (debugMore) logMore.debug("readAMF3String()...");
191    
192            int type = readAMF3Integer();
193            if ((type & 0x01) == 0) // stored string
194                result = getFromStoredStrings(type >> 1);
195            else {
196                int length = type >> 1;
197                if (debugMore) logMore.debug("readAMF3String() - length=%d", length);
198    
199                if (length > 0) {
200    
201                    byte[] utfBytes = new byte[length];
202                    char[] utfChars = new char[length];
203    
204                    readFully(utfBytes);
205    
206                    int c, c2, c3, iBytes = 0, iChars = 0;
207                    while (iBytes < length) {
208                        c = utfBytes[iBytes++] & 0xFF;
209                        if (c <= 0x7F)
210                            utfChars[iChars++] = (char)c;
211                        else {
212                            switch (c >> 4) {
213                            case 12: case 13:
214                                c2 = utfBytes[iBytes++];
215                                if ((c2 & 0xC0) != 0x80)
216                                    throw new UTFDataFormatException("Malformed input around byte " + (iBytes-2));
217                                utfChars[iChars++] = (char)(((c & 0x1F) << 6) | (c2 & 0x3F));
218                                break;
219                            case 14:
220                                c2 = utfBytes[iBytes++];
221                                c3 = utfBytes[iBytes++];
222                                if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80))
223                                    throw new UTFDataFormatException("Malformed input around byte " + (iBytes-3));
224                                utfChars[iChars++] = (char)(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
225                                break;
226                            default:
227                                throw new UTFDataFormatException("Malformed input around byte " + (iBytes-1));
228                            }
229                        }
230                    }
231                    result = new String(utfChars, 0, iChars);
232    
233                    if (debugMore) logMore.debug("readAMF3String() - result=%s", result);
234    
235                    addToStoredStrings(result);
236                } else
237                    result = "";
238            }
239    
240            if (debugMore) logMore.debug("readAMF3String() -> %s", result);
241    
242            return result;
243        }
244    
245    
246        protected Date readAMF3Date() throws IOException {
247            Date result = null;
248    
249            int type = readAMF3Integer();
250            if ((type & 0x01) == 0) // stored Date
251                result = (Date)getFromStoredObjects(type >> 1);
252            else {
253                result = new Date((long)readDouble());
254                addToStoredObjects(result);
255            }
256    
257            if (debugMore) logMore.debug("readAMF3Date() -> %s", result);
258    
259            return result;
260        }
261    
262        protected Object readAMF3Array() throws IOException {
263            Object result = null;
264    
265            int type = readAMF3Integer();
266            if ((type & 0x01) == 0) // stored array.
267                result = getFromStoredObjects(type >> 1);
268            else {
269                final int size = type >> 1;
270    
271                String key = readAMF3String();
272                if (key.length() == 0) {
273                    Object[] objects = new Object[size];
274                    addToStoredObjects(objects);
275    
276                    for (int i = 0; i < size; i++)
277                        objects[i] = readObject();
278    
279                    result = objects;
280                }
281                else {
282                    Map<Object, Object> map = new HashMap<Object, Object>();
283                    addToStoredObjects(map);
284    
285                    while(key.length() > 0) {
286                        map.put(key, readObject());
287                        key = readAMF3String();
288                    }
289                    for (int i = 0; i < size; i++)
290                        map.put(Integer.valueOf(i), readObject());
291    
292                    result = map;
293                }
294            }
295    
296            if (debugMore) logMore.debug("readAMF3Array() -> %s", result);
297    
298            return result;
299        }
300    
301            protected int[] readAMF3VectorInt() throws IOException {
302            int[] vector = null;
303    
304            int type = readAMF3Integer();
305            if ((type & 0x01) == 0) // stored vector.
306                    vector = (int[])getFromStoredObjects(type >> 1);
307            else {
308                    final int length = type >> 1;
309                vector = new int[length];
310                
311                addToStoredObjects(vector);
312                
313                @SuppressWarnings("unused")
314                            boolean fixedLength = readAMF3Integer() == 1;
315                
316                for (int i = 0; i < length; i++)
317                    vector[i] = readInt();
318            }
319            
320            if (debugMore) logMore.debug("readAMF3VectorInt() -> %s", vector);
321    
322            return vector;
323        }
324    
325            protected long[] readAMF3VectorUint() throws IOException {
326                    long[] vector = null;
327    
328            int type = readAMF3Integer();
329            if ((type & 0x01) == 0) // stored vector.
330                    vector = (long[])getFromStoredObjects(type >> 1);
331            else {
332                    final int length = type >> 1;
333                vector = new long[length];
334                
335                addToStoredObjects(vector);
336                
337                @SuppressWarnings("unused")
338                            boolean fixedLength = readAMF3Integer() == 1;
339                
340                for (int i = 0; i < length; i++)
341                    vector[i] = (readInt() & 0xffffffffL);
342            }
343            
344            if (debugMore) logMore.debug("readAMF3VectorUInt() -> %s", vector);
345    
346            return vector;
347        }
348    
349            protected double[] readAMF3VectorNumber() throws IOException {
350                    double[] vector = null;
351    
352            int type = readAMF3Integer();
353            if ((type & 0x01) == 0) // stored vector.
354                    vector = (double[])getFromStoredObjects(type >> 1);
355            else {
356                    final int length = type >> 1;
357                vector = new double[length];
358                
359                addToStoredObjects(vector);
360    
361                @SuppressWarnings("unused")
362                            boolean fixedLength = readAMF3Integer() == 1;
363                
364                for (int i = 0; i < length; i++)
365                    vector[i] = readDouble();
366            }
367            
368            if (debugMore) logMore.debug("readAMF3VectorDouble() -> %s", vector);
369    
370            return vector;
371        }
372    
373            @SuppressWarnings("unchecked")
374            protected List<Object> readAMF3VectorObject() throws IOException {
375            List<Object> vector = null;
376    
377            int type = readAMF3Integer();
378            if ((type & 0x01) == 0) // stored vector.
379                    vector = (List<Object>)getFromStoredObjects(type >> 1);
380            else {
381                    final int length = type >> 1;
382                vector = new ArrayList<Object>(length);
383                
384                addToStoredObjects(vector);
385                
386                @SuppressWarnings("unused")
387                            boolean fixedLength = readAMF3Integer() == 1;
388                @SuppressWarnings("unused")
389                            String componentClassName = readAMF3String();
390                
391                for (int i = 0; i < length; i++)
392                    vector.add(readObject());
393            }
394            
395            if (debugMore) logMore.debug("readAMF3VectorObject() -> %s", vector);
396    
397            return vector;
398        }
399        
400        @SuppressWarnings("unchecked")
401            protected Map<Object, Object> readAMF3Dictionary() throws IOException {
402            Map<Object, Object> dictionary = null;
403    
404            int type = readAMF3Integer();
405            if ((type & 0x01) == 0) // stored dictionary.
406                    dictionary = (Map<Object, Object>)getFromStoredObjects(type >> 1);
407            else {
408                    final int length = type >> 1;
409                    
410                    // AS3 Dictionary doesn't have a strict Java equivalent: use an HashMap, which
411                    // could (unlikely) lead to duplicated keys collision...
412                    dictionary = new HashMap<Object, Object>(length);
413                
414                addToStoredObjects(dictionary);
415                
416                @SuppressWarnings("unused")
417                            boolean weakKeys = readAMF3Integer() == 1;
418                
419                for (int i = 0; i < length; i++) {
420                    Object key = readObject();
421                    Object value = readObject();
422                    dictionary.put(key, value);
423                }
424            }
425            
426            if (debugMore) logMore.debug("readAMF3Dictionary() -> %s", dictionary);
427    
428            return dictionary;
429        }
430    
431        protected Object readAMF3Object() throws IOException {
432            if (debug) log.debug("readAMF3Object()...");
433    
434            Object result = null;
435    
436            int type = readAMF3Integer();
437            if (debug) log.debug("readAMF3Object() - type=0x%02X", type);
438    
439            if ((type & 0x01) == 0) // stored object.
440                result = getFromStoredObjects(type >> 1);
441            else {
442                boolean inlineClassDef = (((type >> 1) & 0x01) != 0);
443                if (debug) log.debug("readAMF3Object() - inlineClassDef=%b", inlineClassDef);
444    
445                // read class decriptor.
446                ActionScriptClassDescriptor desc = null;
447                if (inlineClassDef) {
448                    int propertiesCount = type >> 4;
449                    if (debug) log.debug("readAMF3Object() - propertiesCount=%d", propertiesCount);
450    
451                    byte encoding = (byte)((type >> 2) & 0x03);
452                    if (debug) log.debug("readAMF3Object() - encoding=%d", encoding);
453    
454                    String alias = readAMF3String();
455                    String className = context.getGraniteConfig().getAliasRegistry().getTypeForAlias(alias);
456                    if (debug) log.debug("readAMF3Object() - alias=%, className=%s", alias, className);
457                    
458                    // Check if the class is allowed to be instantiated.
459                    if (securizer != null && !securizer.allowInstantiation(className))
460                            throw new SecurityException("Illegal attempt to instantiate class: " + className + ", securizer: " + securizer.getClass());
461    
462                    // try to find out custom AS3 class descriptor
463                    Class<? extends ActionScriptClassDescriptor> descriptorType = null;
464                    if (!"".equals(className))
465                        descriptorType = context.getGraniteConfig().getActionScriptDescriptor(className);
466                    if (debug) log.debug("readAMF3Object() - descriptorType=%s", descriptorType);
467    
468                    if (descriptorType != null) {
469                        // instantiate descriptor
470                        Class<?>[] argsDef = new Class[]{String.class, byte.class};
471                        Object[] argsVal = new Object[]{className, Byte.valueOf(encoding)};
472                        try {
473                            desc = TypeUtil.newInstance(descriptorType, argsDef, argsVal);
474                        } catch (Exception e) {
475                            throw new RuntimeException("Could not instantiate AS descriptor: " + descriptorType, e);
476                        }
477                    }
478                    if (desc == null)
479                        desc = new DefaultActionScriptClassDescriptor(className, encoding);
480                    addToStoredClassDescriptors(desc);
481    
482                    if (debug) log.debug("readAMF3Object() - defining %d properties...", propertiesCount);
483                    for (int i = 0; i < propertiesCount; i++) {
484                        String name = readAMF3String();
485                        if (debug) log.debug("readAMF3Object() - defining property name=%s", name);
486                        desc.defineProperty(name);
487                    }
488                } else
489                    desc = getFromStoredClassDescriptors(type >> 2);
490    
491                if (debug) log.debug("readAMF3Object() - actionScriptClassDescriptor=%s", desc);
492    
493                int objectEncoding = desc.getEncoding();
494    
495                // Find externalizer and create Java instance.
496                Externalizer externalizer = desc.getExternalizer();
497                if (externalizer != null) {
498                    try {
499                        result = externalizer.newInstance(desc.getType(), this);
500                    } catch (Exception e) {
501                        throw new RuntimeException("Could not instantiate type: " + desc.getType(), e);
502                    }
503                } else
504                    result = desc.newJavaInstance();
505    
506                int index = addToStoredObjects(result);
507                
508                // Entity externalizers (eg. OpenJPA) may return null values for non-null AS3 objects (ie. proxies).
509                if (result == null) {
510                    if (debug) log.debug("readAMF3Object() - Added null object to stored objects for actionScriptClassDescriptor=%s", desc);
511                    return null;
512                }
513    
514                // read object content...
515                if ((objectEncoding & 0x01) != 0) {
516                    // externalizer.
517                    if (externalizer != null) {
518                        if (debug) log.debug("readAMF3Object() - using externalizer=%s", externalizer);
519                        try {
520                            externalizer.readExternal(result, this);
521                        } catch (IOException e) {
522                            throw e;
523                        } catch (Exception e) {
524                            throw new RuntimeException("Could not read externalized object: " + result, e);
525                        }
526                    }
527                    // legacy externalizable.
528                    else {
529                            if (debug) log.debug("readAMF3Object() - legacy Externalizable=%s", result.getClass());
530                            if (!(result instanceof Externalizable)) {
531                                    throw new RuntimeException(
532                                            "The ActionScript3 class bound to " + result.getClass().getName() +
533                                            " (ie: [RemoteClass(alias=\"" + result.getClass().getName() + "\")])" +
534                                            " implements flash.utils.IExternalizable but this Java class neither" +
535                                            " implements java.io.Externalizable nor is in the scope of a configured" +
536                                            " externalizer (please fix your granite-config.xml)"
537                                    );
538                            }
539                        try {
540                            ((Externalizable)result).readExternal(this);
541                        } catch (IOException e) {
542                            throw e;
543                        } catch (Exception e) {
544                            throw new RuntimeException("Could not read externalizable object: " + result, e);
545                        }
546                    }
547                }
548                else {
549                    // defined values...
550                    if (desc.getPropertiesCount() > 0) {
551                        if (debug) log.debug("readAMF3Object() - reading defined properties...");
552                        for (int i = 0; i < desc.getPropertiesCount(); i++) {
553                            byte vType = readByte();
554                            Object value = readObject(vType);
555                            if (debug) log.debug("readAMF3Object() - setting defined property: %s=%s", desc.getPropertyName(i), value);
556                            desc.setPropertyValue(i, result, value);
557                        }
558                    }
559    
560                    // dynamic values...
561                    if (objectEncoding == 0x02) {
562                        if (debug) log.debug("readAMF3Object() - reading dynamic properties...");
563                        while (true) {
564                            String name = readAMF3String();
565                            if (name.length() == 0)
566                                break;
567                            byte vType = readByte();
568                            Object value = readObject(vType);
569                            if (debug) log.debug("readAMF3Object() - setting dynamic property: %s=%s", name, value);
570                            desc.setPropertyValue(name, result, value);
571                        }
572                    }
573                }
574    
575                if (result instanceof AbstractInstantiator<?>) {
576                    if (debug) log.debug("readAMF3Object() - resolving instantiator...");
577                    try {
578                        result = ((AbstractInstantiator<?>)result).resolve();
579                    } catch (Exception e) {
580                        throw new RuntimeException("Could not instantiate object: " + result, e);
581                    }
582                    setStoredObject(index, result);
583                }
584            }
585    
586            if (debug) log.debug("readAMF3Object() -> %s", result);
587    
588            return result;
589        }
590    
591        protected Document readAMF3Xml() throws IOException {
592            String xml = readAMF3XmlString();
593            Document result = xmlUtil.buildDocument(xml);
594    
595            if (debugMore) logMore.debug("readAMF3Xml() -> %s", result);
596    
597            return result;
598        }
599    
600        protected String readAMF3XmlString() throws IOException {
601            String result = null;
602    
603            int type = readAMF3Integer();
604            if ((type & 0x01) == 0) // stored object
605                result = (String)getFromStoredObjects(type >> 1);
606            else {
607                byte[] bytes = readBytes(type >> 1);
608                result = new String(bytes, "UTF-8");
609                addToStoredObjects(result);
610            }
611    
612            if (debugMore) logMore.debug("readAMF3XmlString() -> %s", result);
613    
614            return result;
615        }
616    
617        protected byte[] readAMF3ByteArray() throws IOException {
618            byte[] result = null;
619    
620            int type = readAMF3Integer();
621            if ((type & 0x01) == 0) // stored object.
622                result = (byte[])getFromStoredObjects(type >> 1);
623            else {
624                result = readBytes(type >> 1);
625                addToStoredObjects(result);
626            }
627    
628            if (debugMore) logMore.debug("readAMF3ByteArray() -> %s", result);
629    
630            return result;
631        }
632    
633        ///////////////////////////////////////////////////////////////////////////
634        // Cached objects methods.
635    
636        protected void addToStoredStrings(String s) {
637            if (debug) log.debug("addToStoredStrings(s=%s) at index=%d", s, storedStrings.size());
638            storedStrings.add(s);
639        }
640    
641        protected String getFromStoredStrings(int index) {
642            if (debug) log.debug("getFromStoredStrings(index=%d)", index);
643            String s = storedStrings.get(index);
644            if (debug) log.debug("getFromStoredStrings() -> %s", s);
645            return s;
646        }
647    
648        protected int addToStoredObjects(Object o) {
649            int index = storedObjects.size();
650            if (debug) log.debug("addToStoredObjects(o=%s) at index=%d", o, index);
651            storedObjects.add(o);
652            return index;
653        }
654    
655        protected void setStoredObject(int index, Object o) {
656            if (debug) log.debug("setStoredObject(index=%d, o=%s)", index, o);
657            storedObjects.set(index, o);
658        }
659    
660        protected Object getFromStoredObjects(int index) {
661            if (debug) log.debug("getFromStoredObjects(index=%d)", index);
662            Object o = storedObjects.get(index);
663            if (debug) log.debug("getFromStoredObjects() -> %s", o);
664            return o;
665        }
666    
667        protected void addToStoredClassDescriptors(ActionScriptClassDescriptor desc) {
668            if (debug) log.debug("addToStoredClassDescriptors(desc=%s) at index=%d", desc, storedClassDescriptors.size());
669            storedClassDescriptors.add(desc);
670        }
671    
672        protected ActionScriptClassDescriptor getFromStoredClassDescriptors(int index) {
673            if (debug) log.debug("getFromStoredClassDescriptors(index=%d)", index);
674            ActionScriptClassDescriptor desc = storedClassDescriptors.get(index);
675            if (debug) log.debug("getFromStoredClassDescriptors() -> %s", desc);
676            return desc;
677        }
678    
679        ///////////////////////////////////////////////////////////////////////////
680        // Utilities.
681    
682        protected byte[] readBytes(int count) throws IOException {
683            byte[] bytes = new byte[count];
684            //readFully(bytes);
685            
686            int b = -1;
687            for (int i = 0; i < count; i++) {
688                    b = in.read();
689                    if (b == -1)
690                            throw new EOFException();
691                    bytes[i] = (byte)b;
692            }
693            return bytes;
694        }
695    }