javauis/m3g_qt/javasrc/javax/microedition/m3g/Loader.java
changeset 35 85266cc22c7f
child 40 c6043ea9b06a
equal deleted inserted replaced
26:dc7c549001d5 35:85266cc22c7f
       
     1 /*
       
     2 * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description:
       
    15 *
       
    16 */
       
    17 
       
    18 
       
    19 package javax.microedition.m3g;
       
    20 
       
    21 import java.io.*;
       
    22 import java.io.IOException;
       
    23 
       
    24 import java.util.Vector;
       
    25 import java.util.Hashtable;
       
    26 
       
    27 import javax.microedition.io.*;
       
    28 import javax.microedition.lcdui.Image;
       
    29 import javax.microedition.io.Connector;
       
    30 import javax.microedition.io.HttpConnection;
       
    31 //#ifdef RD_JAVA_OMJ
       
    32 import com.nokia.mj.impl.rt.support.Finalizer;
       
    33 //#endif // RD_JAVA_OMJ
       
    34 
       
    35 public class Loader
       
    36 {
       
    37     // M3G
       
    38     static final byte[] M3G_FILE_IDENTIFIER =
       
    39     {
       
    40         -85, 74, 83, 82, 49, 56, 52, -69, 13, 10, 26, 10
       
    41     };
       
    42     // PNG
       
    43     static final byte[] PNG_FILE_IDENTIFIER =
       
    44     {
       
    45         -119, 80, 78, 71, 13, 10, 26, 10
       
    46     };
       
    47     static final int PNG_IHDR = ((73 << 24) + (72 << 16) + (68 << 8) + 82);
       
    48     static final int PNG_tRNS = ((116 << 24) + (82 << 16) + (78 << 8) + 83);
       
    49     static final int PNG_IDAT = ((73 << 24) + (68 << 16) + (65 << 8) + 84);
       
    50 
       
    51     // JPEG
       
    52     static final byte[] JPEG_FILE_IDENTIFIER =
       
    53     {
       
    54         -1, -40
       
    55     };
       
    56     static final int JPEG_JFIF = ((74 << 24) + (70 << 16) + (73 << 8) + 70);
       
    57     // Bytes before colour info in a frame header 'SOFn':
       
    58     // length (2 bytes), precision (1 byte), image height & width (4 bytes)
       
    59     static final int JPEG_SOFn_DELTA = 7;
       
    60     static final int JPEG_INVALID_COLOUR_FORMAT = -1;
       
    61 
       
    62     // File identifier types
       
    63     private static final int INVALID_HEADER_TYPE = -1;
       
    64     private static final int M3G_TYPE = 0;
       
    65     private static final int PNG_TYPE = 1;
       
    66     private static final int JPEG_TYPE = 2;
       
    67 
       
    68     // Misc.
       
    69     private static final int MAX_IDENTIFIER_LENGTH  = M3G_FILE_IDENTIFIER.length;
       
    70 
       
    71     // Initial buffer length for the header
       
    72     private static final int AVG_HEADER_SEC_LENGTH  = 64;
       
    73 
       
    74     // Initial buffer length for the xref section
       
    75     private static final int AVG_XREF_SEC_LENGTH    = 128;
       
    76 
       
    77     // Instance specific
       
    78     int handle;
       
    79 
       
    80     private Vector iLoadedObjects = new Vector();
       
    81     private Vector iFileHistory = new Vector();
       
    82     private String iResourceName = null;
       
    83     private String iParentResourceName = null;
       
    84 
       
    85     private int iTotalFileSize = 0;
       
    86     private int iBytesRead = M3G_FILE_IDENTIFIER.length;
       
    87 
       
    88     private byte[] iStreamData = null;
       
    89     private int iStreamOffset = 0;
       
    90 
       
    91     private Interface iInterface;
       
    92 
       
    93 //#ifdef RD_JAVA_OMJ
       
    94     private Finalizer mFinalizer = new Finalizer()
       
    95     {
       
    96         public void finalizeImpl()
       
    97         {
       
    98             doFinalize();
       
    99         }
       
   100     };
       
   101 //#endif // RD_JAVA_OMJ
       
   102 
       
   103     /**
       
   104      * Default ctor
       
   105      */
       
   106     private Loader()
       
   107     {
       
   108         iInterface = Interface.getInstance();
       
   109     }
       
   110 
       
   111     /**
       
   112      * Ctor
       
   113      * @param aFileHistory File storage
       
   114      * @param aParentResourceName Resource name
       
   115      */
       
   116     private Loader(Vector aFileHistory, String aParentResourceName)
       
   117     {
       
   118         iParentResourceName = aParentResourceName;
       
   119         iFileHistory        = aFileHistory;
       
   120         iInterface          = Interface.getInstance();
       
   121     }
       
   122 
       
   123     public static Object3D[] load(String name) throws IOException
       
   124     {
       
   125         if (name == null)
       
   126         {
       
   127             throw new NullPointerException();
       
   128         }
       
   129 
       
   130         try
       
   131         {
       
   132             return (new Loader()).loadFromStream(name);
       
   133         }
       
   134         catch (SecurityException e)
       
   135         {
       
   136             throw e;
       
   137         }
       
   138         catch (IOException e)
       
   139         {
       
   140             throw e;
       
   141         }
       
   142         catch (Exception e)
       
   143         {
       
   144             throw new IOException("Load error " + e);
       
   145         }
       
   146     }
       
   147 
       
   148     public static Object3D[] load(byte[] data, int offset) throws IOException
       
   149     {
       
   150         if (data == null)
       
   151         {
       
   152             throw new NullPointerException();
       
   153         }
       
   154 
       
   155         if (offset < 0 || offset >= data.length)
       
   156         {
       
   157             throw new IndexOutOfBoundsException();
       
   158         }
       
   159         try
       
   160         {
       
   161             return (new Loader()).loadFromByteArray(data, offset);
       
   162         }
       
   163         catch (SecurityException e)
       
   164         {
       
   165             throw e;
       
   166         }
       
   167         catch (IOException e)
       
   168         {
       
   169             throw e;
       
   170         }
       
   171         catch (Exception e)
       
   172         {
       
   173             throw new IOException("Load error " + e);
       
   174         }
       
   175     }
       
   176 
       
   177     /**
       
   178      * @see javax.microedition.m3g.Loader#load(String)
       
   179      */
       
   180     private Object3D[] loadFromStream(String aName) throws IOException
       
   181     {
       
   182         if (aName == null)
       
   183         {
       
   184             throw new NullPointerException();
       
   185         }
       
   186 
       
   187         if (inFileHistory(aName))
       
   188         {
       
   189             throw new IOException("Reference loop detected.");
       
   190         }
       
   191         iResourceName = aName;
       
   192         iFileHistory.addElement(aName);
       
   193         PeekInputStream stream = new PeekInputStream(
       
   194             getInputStream(aName), MAX_IDENTIFIER_LENGTH);
       
   195         // png, jpeg or m3g
       
   196         int type = getIdentifierType(stream);
       
   197         stream.rewind();
       
   198         iStreamData = null;
       
   199         iStreamOffset = 0;
       
   200 
       
   201         Object3D[] objects;
       
   202         try
       
   203         {
       
   204             objects = doLoad(stream, type);
       
   205         }
       
   206         finally
       
   207         {
       
   208             try
       
   209             {
       
   210                 stream.close();
       
   211                 stream = null;
       
   212             }
       
   213             catch (Exception e) {}
       
   214         }
       
   215         // Finally, remove file from history
       
   216         iFileHistory.removeElement(aName);
       
   217         return objects;
       
   218     }
       
   219 
       
   220     /**
       
   221      * @see javax.microedition.m3g.Loader#load(byte[], int)
       
   222      */
       
   223     private Object3D[] loadFromByteArray(byte[] aData, int aOffset) throws IOException
       
   224     {
       
   225         if (aData == null)
       
   226         {
       
   227             throw new NullPointerException("Resource byte array is null.");
       
   228         }
       
   229         int type = getIdentifierType(aData, aOffset);
       
   230         ByteArrayInputStream stream =
       
   231             new ByteArrayInputStream(aData, aOffset, aData.length - aOffset);
       
   232         iStreamData = aData;
       
   233         iStreamOffset = aOffset;
       
   234         iResourceName = "ByteArray";
       
   235 
       
   236         Object3D[] objects;
       
   237         try
       
   238         {
       
   239             objects = doLoad(stream, type);
       
   240         }
       
   241         finally
       
   242         {
       
   243             try
       
   244             {
       
   245                 stream.close();
       
   246                 stream = null;
       
   247             }
       
   248             catch (Exception e) {}
       
   249         }
       
   250         return objects;
       
   251     }
       
   252 
       
   253     /**
       
   254      * Dispatcher
       
   255      * @param aStream Source stream
       
   256      * @param aType Resource type
       
   257      */
       
   258     private Object3D[] doLoad(InputStream aStream, int aType) throws IOException
       
   259     {
       
   260         if (aType == M3G_TYPE)
       
   261         {
       
   262             return loadM3G(aStream);
       
   263         }
       
   264         else if (aType == PNG_TYPE)
       
   265         {
       
   266             return loadPNG(aStream);
       
   267         }
       
   268         else if (aType == JPEG_TYPE)
       
   269         {
       
   270             return loadJPEG(aStream);
       
   271         }
       
   272         throw new IOException("File not recognized.");
       
   273     }
       
   274 
       
   275     /**
       
   276      * PNG resource loader
       
   277      * @param aStream Resource stream
       
   278      * @return An array of newly created Object3D instances
       
   279      */
       
   280     private Object3D[] loadPNG(InputStream aStream) throws IOException
       
   281     {
       
   282         int format = Image2D.RGB;
       
   283         DataInputStream in = new DataInputStream(aStream);
       
   284 
       
   285         // Scan chuncs that have effect on Image2D format
       
   286         in.skip(PNG_FILE_IDENTIFIER.length);
       
   287 
       
   288         try
       
   289         {
       
   290             while (true)
       
   291             {
       
   292                 int length = in.readInt();
       
   293                 int type = in.readInt();
       
   294                 // IHDR
       
   295                 if (type == PNG_IHDR)
       
   296                 {
       
   297                     in.skip(9);
       
   298                     int colourType = in.readUnsignedByte();
       
   299                     length -= 10;
       
   300 
       
   301                     switch (colourType)
       
   302                     {
       
   303                     case 0:
       
   304                         format = Image2D.LUMINANCE;
       
   305                         break;
       
   306                     case 2:
       
   307                         format = Image2D.RGB;
       
   308                         break;
       
   309                     case 3:
       
   310                         format = Image2D.RGB;
       
   311                         break;
       
   312                     case 4:
       
   313                         format = Image2D.LUMINANCE_ALPHA;
       
   314                         break;
       
   315                     case 6:
       
   316                         format = Image2D.RGBA;
       
   317                         break;
       
   318                     }
       
   319                 }
       
   320                 // tRNS
       
   321                 if (type == PNG_tRNS)
       
   322                 {
       
   323                     switch (format)
       
   324                     {
       
   325                     case Image2D.LUMINANCE:
       
   326                         format = Image2D.LUMINANCE_ALPHA;
       
   327                         break;
       
   328                     case Image2D.RGB:
       
   329                         format = Image2D.RGBA;
       
   330                         break;
       
   331                     }
       
   332                 }
       
   333                 // IDAT
       
   334                 if (type == PNG_IDAT)
       
   335                 {
       
   336                     break;
       
   337                 }
       
   338 
       
   339                 in.skip(length + 4);
       
   340             }
       
   341         }
       
   342         // EOF
       
   343         catch (Exception e)
       
   344         {
       
   345         }
       
   346         // Close the data stream
       
   347         try
       
   348         {
       
   349             in.close();
       
   350             in = null;
       
   351         }
       
   352         catch (Exception e) {}
       
   353         return buildImage2D(format);
       
   354     }
       
   355 
       
   356     /**
       
   357      * JPEG (with the same detailed definitions about the JPEG image format as defined in the
       
   358      * JSR 118 MIDP 2.1 specification for LCDUI) MUST be supported by compliant
       
   359      * implementations as a 2D bitmap image format for the Image2D class using the
       
   360      * javax.microedition.m3g.Loader class, and for M3G content files referencing bitmap images.
       
   361      * For colour JPEG images, the pixel format of the returned Image2D object MUST be
       
   362      * Image2D.RGB and for monochrome JPEG images, the pixel format MUST be
       
   363      * Image2D.LUMINANCE.
       
   364      *
       
   365      * JPEG marker: A two-byte code in which the first byte is 0xFF and the second
       
   366      * byte is a value between 1 and 0xFE.
       
   367      *
       
   368      * A JFIF file uses APP0 (0xe0) marker segments and constrains certain parameters in the frame.
       
   369      *
       
   370      * A frame header:
       
   371      * - 0xff, 'SOFn'
       
   372      * - length (2 bytes, Hi-Lo)
       
   373      * - data precision (1 byte)
       
   374      * - image height (2 bytes, Hi-Lo)
       
   375      * - image width (2 bytes, Hi-Lo)
       
   376      * - number of components (1 byte): 1 = grey scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
       
   377      *
       
   378      * @param aStream Resource stream
       
   379      * @return An array of newly created Object3D instances
       
   380      */
       
   381     private Object3D[] loadJPEG(InputStream aStream) throws IOException
       
   382     {
       
   383         int format = JPEG_INVALID_COLOUR_FORMAT;
       
   384         DataInputStream in = new DataInputStream(aStream);
       
   385         // Skip file identifier
       
   386         in.skip(JPEG_FILE_IDENTIFIER.length);
       
   387         try
       
   388         {
       
   389             int marker;
       
   390             do
       
   391             {
       
   392                 // Find marker
       
   393                 while (in.readUnsignedByte() != 0xff);
       
   394                 do
       
   395                 {
       
   396                     marker = in.readUnsignedByte();
       
   397                 }
       
   398                 while (marker == 0xff);
       
   399 
       
   400                 // Parse marker
       
   401                 switch (marker)
       
   402                 {
       
   403                     // 'SOFn' (Start Of Frame n)
       
   404                 case 0xC0:
       
   405                 case 0xC1:
       
   406                 case 0xC2:
       
   407                 case 0xC3:
       
   408                 case 0xC5:
       
   409                 case 0xC6:
       
   410                 case 0xC7:
       
   411                 case 0xC9:
       
   412                 case 0xCA:
       
   413                 case 0xCB:
       
   414                 case 0xCD:
       
   415                 case 0xCE:
       
   416                 case 0xCF:
       
   417                     // Skip length(2), precicion(1), width(2), height(2)
       
   418                     in.skip(JPEG_SOFn_DELTA);
       
   419                     switch (in.readUnsignedByte())
       
   420                     {
       
   421                     case 1:
       
   422                         format = Image2D.LUMINANCE;
       
   423                         break;
       
   424                     case 3:
       
   425                         format = Image2D.RGB;
       
   426                         break;
       
   427                     default:
       
   428                         throw new IOException("Unknown JPG format.");
       
   429                     }
       
   430                     break;
       
   431                     // APP0 (0xe0) marker segments and constrains certain parameters in the frame.
       
   432                 case 0xe0:
       
   433                     int length = in.readUnsignedShort();
       
   434                     if (JPEG_JFIF != in.readInt())
       
   435                     {
       
   436                         throw new IOException("Not a valid JPG file.");
       
   437                     }
       
   438                     in.skip(length - 4 - 2);
       
   439                     break;
       
   440                 default:
       
   441                     // Skip variable data
       
   442                     in.skip(in.readUnsignedShort() - 2);
       
   443                     break;
       
   444                 }
       
   445             }
       
   446             while (format == JPEG_INVALID_COLOUR_FORMAT);
       
   447         }
       
   448         catch (Exception e) {}
       
   449         // Close the data stream
       
   450         try
       
   451         {
       
   452             in.close();
       
   453             in = null;
       
   454         }
       
   455         catch (Exception e) {}
       
   456         return buildImage2D(format);
       
   457     }
       
   458 
       
   459     /**
       
   460      * Image2D builder
       
   461      * @param aColourFormat Colour format
       
   462      * @return An array of newly created Object3D instances
       
   463      */
       
   464     private Object3D[] buildImage2D(int aColourFormat) throws IOException
       
   465     {
       
   466         InputStream stream;
       
   467         if (iStreamData == null)
       
   468         {
       
   469             stream = getInputStream(iResourceName);
       
   470         }
       
   471         else
       
   472         {
       
   473             stream = (InputStream) new ByteArrayInputStream(
       
   474                          iStreamData, iStreamOffset, iStreamData.length - iStreamOffset);
       
   475         }
       
   476         // Create an image object
       
   477         Image2D i2d;
       
   478         try
       
   479         {
       
   480             i2d = new Image2D(aColourFormat, Image.createImage(stream));
       
   481         }
       
   482         finally
       
   483         {
       
   484             try
       
   485             {
       
   486                 stream.close();
       
   487             }
       
   488             catch (Exception e) {}
       
   489         }
       
   490         return new Object3D[] { i2d };
       
   491     }
       
   492 
       
   493 
       
   494     /**
       
   495      * M3G resource loader
       
   496      * @param aStream Resource stream
       
   497      * @return An array of newly created Object3D instances
       
   498      */
       
   499     private Object3D[] loadM3G(InputStream aStream) throws IOException
       
   500     {
       
   501         aStream.skip(M3G_FILE_IDENTIFIER.length);
       
   502         if (aStream instanceof PeekInputStream)
       
   503             ((PeekInputStream)aStream).increasePeekBuffer(AVG_HEADER_SEC_LENGTH);
       
   504 
       
   505         // Read header
       
   506         int compressionScheme = readByte(aStream);
       
   507         int totalSectionLength = readUInt32(aStream);
       
   508         if (aStream instanceof PeekInputStream && totalSectionLength > AVG_HEADER_SEC_LENGTH)
       
   509             ((PeekInputStream)aStream).increasePeekBuffer(totalSectionLength - AVG_HEADER_SEC_LENGTH);
       
   510         int uncompressedLength = readUInt32(aStream);
       
   511 
       
   512         int objectType = readByte(aStream);
       
   513         int length    = readUInt32(aStream);
       
   514 
       
   515         byte vMajor = (byte) readByte(aStream);
       
   516         byte vMinor = (byte) readByte(aStream);
       
   517         boolean externalLinks = readBoolean(aStream);
       
   518         iTotalFileSize = readUInt32(aStream);
       
   519         int approximateContentSize = readUInt32(aStream);
       
   520         String authoringField = readString(aStream);
       
   521 
       
   522         int checksum = readUInt32(aStream);
       
   523 
       
   524         /* Create and register a new native Loader */
       
   525         handle = _ctor(Interface.getHandle());
       
   526         Interface.register(this);
       
   527 
       
   528         if (externalLinks)
       
   529         {
       
   530             if (aStream instanceof PeekInputStream)
       
   531                 ((PeekInputStream)aStream).increasePeekBuffer(AVG_XREF_SEC_LENGTH);
       
   532             loadExternalRefs(aStream);
       
   533             if (iLoadedObjects.size() > 0)   // Load and set external references
       
   534             {
       
   535                 int[] xRef = new int[iLoadedObjects.size()];
       
   536                 for (int i = 0; i < xRef.length; i++)
       
   537                     xRef[i] = ((Object3D)iLoadedObjects.elementAt(i)).handle;
       
   538                 _setExternalReferences(handle, xRef);
       
   539             }
       
   540             else
       
   541             {
       
   542                 throw new IOException("No external sections [" + iResourceName + "].");
       
   543             }
       
   544         }
       
   545 
       
   546         // Reset stream
       
   547         if (aStream instanceof PeekInputStream)
       
   548             ((PeekInputStream)aStream).rewind();
       
   549         else if (aStream.markSupported())
       
   550             aStream.reset(); // Reset is supported in ByteArrayInputStreams
       
   551 
       
   552         int read = 0;
       
   553         int size = aStream.available();
       
   554 
       
   555         if (size == 0)
       
   556         {
       
   557             size = 2048;    // start with some size
       
   558         }
       
   559 
       
   560         while (read < iTotalFileSize)
       
   561         {
       
   562             if (read + size > iTotalFileSize)
       
   563             {
       
   564                 size = iTotalFileSize - read;
       
   565             }
       
   566             // Use native loader to load objects
       
   567             byte[] data = new byte[size];
       
   568             if (aStream.read(data) == -1)
       
   569             {
       
   570                 break;
       
   571             }
       
   572             read += size;
       
   573 
       
   574             size = _decodeData(handle, 0, data);
       
   575             if (size > 0 && aStream.available() > size)
       
   576             {
       
   577                 size = aStream.available();
       
   578             }
       
   579         }
       
   580         if (size != 0 || read != iTotalFileSize)
       
   581         {
       
   582             throw new IOException("Invalid file length [" + iResourceName + "].");
       
   583         }
       
   584 
       
   585         Object3D[] objects = null;
       
   586         int num = _getLoadedObjects(handle, null);
       
   587         if (num > 0)
       
   588         {
       
   589             int[] obj = new int[num];
       
   590             _getLoadedObjects(handle, obj);
       
   591             objects = new Object3D[num];
       
   592             for (int i = 0; i < objects.length; i++)
       
   593             {
       
   594                 objects[i] = Interface.getObjectInstance(obj[i]);
       
   595             }
       
   596             setUserObjects();
       
   597         }
       
   598         return objects;
       
   599     }
       
   600 
       
   601     /**
       
   602      *
       
   603      */
       
   604     private void setUserObjects() throws IOException
       
   605     {
       
   606         int numObjects = _getObjectsWithUserParameters(handle, null);
       
   607         int[] obj = null;
       
   608         if (numObjects > 0)
       
   609         {
       
   610             obj = new int[numObjects];
       
   611             _getObjectsWithUserParameters(handle, obj);
       
   612         }
       
   613         for (int i = 0; i < numObjects; i++)
       
   614         {
       
   615             int num = _getNumUserParameters(handle, i);
       
   616             if (num > 0)
       
   617             {
       
   618                 Hashtable hash = new Hashtable();
       
   619                 for (int j = 0; j < num; j++)
       
   620                 {
       
   621                     int len = _getUserParameter(handle, i, j, null);
       
   622                     byte[] data = new byte[len];
       
   623                     int id = _getUserParameter(handle, i, j, data);
       
   624                     if (hash.put(new Integer(id), data) != null)
       
   625                         throw new IOException("Duplicate id in user data [" + iResourceName + "].");;
       
   626                 }
       
   627                 Object3D object = Interface.getObjectInstance(obj[i]);
       
   628                 object.setUserObject(hash);
       
   629             }
       
   630         }
       
   631     }
       
   632 
       
   633     /**
       
   634      * Load external resources
       
   635      */
       
   636     private void loadExternalRefs(InputStream aStream) throws IOException
       
   637     {
       
   638         // Check for the end of the aStream or file
       
   639         int firstByte = readByte(aStream);
       
   640         if (firstByte == -1 || (iTotalFileSize != 0 && iBytesRead >= iTotalFileSize))
       
   641         {
       
   642             return;
       
   643         }
       
   644 
       
   645         int compressionScheme = firstByte;
       
   646 
       
   647         int totalSectionLength = readUInt32(aStream);
       
   648         iBytesRead += totalSectionLength;
       
   649         if (aStream instanceof PeekInputStream && totalSectionLength > AVG_XREF_SEC_LENGTH)
       
   650             ((PeekInputStream)aStream).increasePeekBuffer(totalSectionLength - AVG_XREF_SEC_LENGTH);
       
   651         int uncompressedLength = readUInt32(aStream);
       
   652         int expectedCount = totalSectionLength;
       
   653 
       
   654         // Decompress data if necessary
       
   655         CountedInputStream uncompressedStream = null;
       
   656         if (compressionScheme == 0)
       
   657         {
       
   658             uncompressedStream = new CountedInputStream(aStream);
       
   659             if (uncompressedLength != totalSectionLength - 13)
       
   660             {
       
   661                 throw new IOException("Section length mismatch [" + iResourceName + "].");
       
   662             }
       
   663         }
       
   664         else if (compressionScheme == 1)
       
   665         {
       
   666             if (uncompressedLength == 0 && totalSectionLength - 13 == 0)
       
   667             {
       
   668                 uncompressedStream = new CountedInputStream(null);
       
   669             }
       
   670             else
       
   671             {
       
   672                 if (uncompressedLength <= 0 || totalSectionLength - 13 <= 0)
       
   673                 {
       
   674                     throw new IOException("Section length mismatch [" + iResourceName + "].");
       
   675                 }
       
   676                 byte[] compressed = new byte[(int) totalSectionLength - 13];
       
   677                 aStream.read(compressed);
       
   678 
       
   679                 byte[] uncompressed = new byte[(int) uncompressedLength];
       
   680 
       
   681                 // zlib decompression
       
   682                 if (!_inflate(compressed, uncompressed))
       
   683                 {
       
   684                     throw new IOException("Decompression error.");
       
   685                 }
       
   686                 uncompressedStream = new CountedInputStream(
       
   687                     new ByteArrayInputStream(uncompressed));
       
   688             }
       
   689         }
       
   690         else
       
   691         {
       
   692             throw new IOException("Unrecognized compression scheme [" + iResourceName + "].");
       
   693         }
       
   694 
       
   695         // load all objects in this section
       
   696         uncompressedStream.resetCounter();
       
   697 
       
   698         while (uncompressedStream.getCounter() < uncompressedLength)
       
   699         {
       
   700             iLoadedObjects.addElement(loadObject(uncompressedStream));
       
   701         }
       
   702 
       
   703         if (uncompressedStream.getCounter() != uncompressedLength)
       
   704         {
       
   705             throw new IOException("Section length mismatch [" + iResourceName + "].");
       
   706         }
       
   707 
       
   708         // read checksum
       
   709         int checksum = readUInt32(aStream);
       
   710     }
       
   711 
       
   712     private Object3D loadObject(CountedInputStream aStream) throws IOException
       
   713     {
       
   714         int objectType = readByte(aStream);
       
   715         int length    = readUInt32(aStream);
       
   716 
       
   717         int expectedCount = aStream.getCounter() + length;
       
   718         Object3D newObject = null;
       
   719 
       
   720         if (objectType == 255)
       
   721         {
       
   722             String xref = readString(aStream);
       
   723             newObject = (new Loader(iFileHistory, iResourceName)).loadFromStream(xref)[0];
       
   724         }
       
   725         else
       
   726         {
       
   727             throw new IOException("Invalid external section [" + iResourceName + "].");
       
   728         }
       
   729 
       
   730         if (expectedCount != aStream.getCounter())
       
   731         {
       
   732             throw new IOException("Object length mismatch [" + iResourceName + "].");
       
   733         }
       
   734 
       
   735         return newObject;
       
   736     }
       
   737 
       
   738     /**
       
   739      * Read a byte integer from a stream
       
   740      */
       
   741     private static final int readByte(InputStream aStream) throws IOException
       
   742     {
       
   743         return aStream.read();
       
   744     }
       
   745 
       
   746     /**
       
   747      * Read a boolean from a stream
       
   748      */
       
   749     private static boolean readBoolean(InputStream aStream) throws IOException
       
   750     {
       
   751         int b = aStream.read();
       
   752         if (b == 0)
       
   753         {
       
   754             return false;
       
   755         }
       
   756         if (b != 1)
       
   757         {
       
   758             throw new IOException("Malformed boolean.");
       
   759         }
       
   760         return true;
       
   761     }
       
   762 
       
   763     /**
       
   764      * Read a unsigned integer from a stream
       
   765      */
       
   766     private static final int readUInt32(InputStream aStream) throws IOException
       
   767     {
       
   768         return aStream.read()
       
   769                + (aStream.read() << 8)
       
   770                + (aStream.read() << 16)
       
   771                + (aStream.read() << 24);
       
   772     }
       
   773 
       
   774     /**
       
   775      * Read a string from a stream
       
   776      */
       
   777     private static String readString(InputStream aStream) throws IOException
       
   778     {
       
   779         StringBuffer result = new StringBuffer();
       
   780         int i = 0;
       
   781         for (int c = aStream.read(); c != 0; c = aStream.read())
       
   782         {
       
   783             if ((c & 0x80) == 0)   // 0xxxxxxx => 1 byte
       
   784             {
       
   785                 result.append((char)(c & 0x00FF));
       
   786             }
       
   787             else if ((c & 0xE0) == 0xC0)   // 110xxxxx => 2 bytes
       
   788             {
       
   789                 int c2 = aStream.read();
       
   790                 if ((c2 & 0xC0) != 0x80)   // second byte is not 10yyyyyy
       
   791                 {
       
   792                     throw new IOException("Invalid UTF-8 string.");
       
   793                 }
       
   794                 else   // 110xxxxx 10yyyyyy
       
   795                 {
       
   796                     result.append((char)(((c & 0x1F) << 6) | (c2 & 0x3F)));
       
   797                 }
       
   798             }
       
   799             else if ((c & 0xF0) == 0xE0)   // 1110 xxxx => 3 bytes
       
   800             {
       
   801                 int c2 = aStream.read();
       
   802                 int c3 = aStream.read();
       
   803                 if (((c2 & 0xC0) != 0x80) || // second byte is not 10yyyyyy
       
   804                         ((c3 & 0xC0) != 0x80))   // third byte is not 10zzzzzz
       
   805                 {
       
   806                     throw new IOException("Invalid UTF-8 string.");
       
   807                 }
       
   808                 else   // 1110xxxx 10yyyyyy 10zzzzzz
       
   809                 {
       
   810                     result.append((char)(((c & 0x0F) << 12) |
       
   811                                          ((c2 & 0x3F) <<6) |
       
   812                                          (c3 & 0x3F)));
       
   813                 }
       
   814             }
       
   815             else   // none of above
       
   816             {
       
   817                 throw new IOException("Invalid UTF-8 string.");
       
   818             }
       
   819         }
       
   820 
       
   821         return result.toString();
       
   822     }
       
   823 
       
   824     /**
       
   825      * Solve an identifier of the given data
       
   826      * @param aStream Stream
       
   827      * @return solved identifier.
       
   828      */
       
   829     private int getIdentifierType(InputStream aStream) throws IOException
       
   830     {
       
   831         byte[] data = new byte[MAX_IDENTIFIER_LENGTH];
       
   832         aStream.read(data);
       
   833         return getIdentifierType(data, 0);
       
   834     }
       
   835 
       
   836     /**
       
   837      * Solve an identifier of the given data
       
   838      * @param aData Data
       
   839      * @param aOffset Data offset
       
   840      * @return solved identifier.
       
   841      */
       
   842     private static int getIdentifierType(byte[] aData, int aOffset)
       
   843     {
       
   844         // Try the JPEG/JFIF identifier
       
   845         if (parseIdentifier(aData, aOffset, JPEG_FILE_IDENTIFIER))
       
   846         {
       
   847             return JPEG_TYPE;
       
   848         }
       
   849         // Try the PNG identifier
       
   850         else if (parseIdentifier(aData, aOffset, PNG_FILE_IDENTIFIER))
       
   851         {
       
   852             return PNG_TYPE;
       
   853         }
       
   854         // Try the M3G identifier
       
   855         else if (parseIdentifier(aData, aOffset, M3G_FILE_IDENTIFIER))
       
   856         {
       
   857             return M3G_TYPE;
       
   858         }
       
   859         return INVALID_HEADER_TYPE;
       
   860     }
       
   861 
       
   862     /**
       
   863      * Parse identifier from a data
       
   864      * @param aData Source data
       
   865      * @param aOffset Source data offset
       
   866      * @param aIdentifier Identifier
       
   867      * @return true if the data contains the given identifier
       
   868      */
       
   869     private static boolean parseIdentifier(byte[] aData, int aOffset, byte[] aIdentifier)
       
   870     {
       
   871         if ((aData.length - aOffset) < aIdentifier.length)
       
   872         {
       
   873             return false;
       
   874         }
       
   875         for (int index = 0; index < aIdentifier.length; index++)
       
   876         {
       
   877             if (aData[index + aOffset] != aIdentifier[index])
       
   878             {
       
   879                 return false;
       
   880             }
       
   881         }
       
   882         return true;
       
   883     }
       
   884 
       
   885     /**
       
   886      * File name storage for preventing multiple referencing
       
   887      * @param name File name
       
   888      * @return true if the storage contains the given file name
       
   889      */
       
   890     private boolean inFileHistory(String name)
       
   891     {
       
   892         for (int i = 0; i < iFileHistory.size(); i++)
       
   893             if (((String)iFileHistory.elementAt(i)).equals(name))
       
   894             {
       
   895                 return true;
       
   896             }
       
   897         return false;
       
   898     }
       
   899 
       
   900     /*
       
   901      * InputStream-related helper functions
       
   902      */
       
   903 
       
   904     /**
       
   905      * Open a HTTP stream and check its MIME type
       
   906      * @param name Resource name
       
   907      * @return a http stream and checks the MIME type
       
   908      */
       
   909     private InputStream getHttpInputStream(String name) throws IOException
       
   910     {
       
   911         InputConnection ic = (InputConnection)Connector.open(name);
       
   912         // Content-Type is available for http and https connections
       
   913         if (ic instanceof HttpConnection)
       
   914         {
       
   915             HttpConnection hc = (HttpConnection) ic;
       
   916             // Check MIME type
       
   917             String type = hc.getHeaderField("Content-Type");
       
   918             if (type != null &&
       
   919                     !type.equals("application/m3g") &&
       
   920                     !type.equals("image/png") &&
       
   921                     !type.equals("image/jpeg"))
       
   922             {
       
   923                 throw new IOException("Wrong MIME type: " + type + ".");
       
   924             }
       
   925         }
       
   926 
       
   927         InputStream is;
       
   928         try
       
   929         {
       
   930             is = ic.openInputStream();
       
   931         }
       
   932         finally
       
   933         {
       
   934             try
       
   935             {
       
   936                 ic.close();
       
   937                 ic = null;
       
   938             }
       
   939             catch (Exception e) {}
       
   940         }
       
   941         return is;
       
   942     }
       
   943 
       
   944     // returns a stream built from the specified file or URI
       
   945     private InputStream getInputStream(String name) throws IOException
       
   946     {
       
   947         if (name.indexOf(':') != -1)   // absolute URI reference
       
   948         {
       
   949             return getHttpInputStream(name);
       
   950         }
       
   951 
       
   952         if (name.charAt(0) == '/')   // absolute file reference
       
   953         {
       
   954             return (new Object()).getClass().getResourceAsStream(name);
       
   955         }
       
   956 
       
   957         if (iParentResourceName == null)
       
   958         {
       
   959             throw new IOException("Relative URI.");
       
   960         }
       
   961 
       
   962         String uri = iParentResourceName.substring(0, iParentResourceName.lastIndexOf('/') + 1) + name;
       
   963 
       
   964         if (uri.charAt(0) == '/')
       
   965         {
       
   966             return (new Object()).getClass().getResourceAsStream(uri);
       
   967         }
       
   968         else
       
   969         {
       
   970             return getHttpInputStream(uri);
       
   971         }
       
   972     }
       
   973 
       
   974     class PeekInputStream extends InputStream
       
   975     {
       
   976         private int[] iPeekBuffer;
       
   977         private InputStream iStream;
       
   978         private int iBuffered;
       
   979         private int iCounter;
       
   980 
       
   981         PeekInputStream(InputStream aStream, int aLength)
       
   982         {
       
   983             iStream = aStream;
       
   984             iPeekBuffer = new int[aLength];
       
   985         }
       
   986 
       
   987         public int read() throws IOException
       
   988         {
       
   989             if (iCounter < iBuffered)
       
   990             {
       
   991                 return iPeekBuffer[iCounter++];
       
   992             }
       
   993 
       
   994             int nv = iStream.read();
       
   995 
       
   996             if (iBuffered < iPeekBuffer.length)
       
   997             {
       
   998                 iPeekBuffer[iBuffered] = nv;
       
   999                 iBuffered++;
       
  1000             }
       
  1001 
       
  1002             iCounter++;
       
  1003             return nv;
       
  1004         }
       
  1005 
       
  1006         public void increasePeekBuffer(int aLength)
       
  1007         {
       
  1008             int[] temp = new int[iPeekBuffer.length + aLength];
       
  1009             for (int i = 0; i < iBuffered; i++)
       
  1010                 temp[i] = iPeekBuffer[i];
       
  1011             iPeekBuffer = temp;
       
  1012         }
       
  1013 
       
  1014         public int available() throws IOException
       
  1015         {
       
  1016             if (iCounter < iBuffered)
       
  1017             {
       
  1018                 return iBuffered - iCounter + iStream.available();
       
  1019             }
       
  1020             return iStream.available();
       
  1021         }
       
  1022 
       
  1023         public void close()
       
  1024         {
       
  1025             try
       
  1026             {
       
  1027                 iStream.close();
       
  1028             }
       
  1029             catch (IOException ioe)
       
  1030             {
       
  1031                 // Intentionally left empty
       
  1032             }
       
  1033         }
       
  1034 
       
  1035         public void rewind() throws IOException
       
  1036         {
       
  1037             if (iCounter > iBuffered)
       
  1038             {
       
  1039                 throw new IOException("Peek buffer overrun.");
       
  1040             }
       
  1041             iCounter = 0;
       
  1042         }
       
  1043     }
       
  1044 
       
  1045     class CountedInputStream extends InputStream
       
  1046     {
       
  1047         private InputStream iStream;
       
  1048         private int iCounter;
       
  1049 
       
  1050         public CountedInputStream(InputStream aStream)
       
  1051         {
       
  1052             iStream = aStream;
       
  1053             resetCounter();
       
  1054         }
       
  1055 
       
  1056         public int read() throws IOException
       
  1057         {
       
  1058             iCounter++;
       
  1059             return iStream.read();
       
  1060         }
       
  1061 
       
  1062         public void resetCounter()
       
  1063         {
       
  1064             iCounter = 0;
       
  1065         }
       
  1066         public int getCounter()
       
  1067         {
       
  1068             return iCounter;
       
  1069         }
       
  1070 
       
  1071         public void close()
       
  1072         {
       
  1073             try
       
  1074             {
       
  1075                 iStream.close();
       
  1076             }
       
  1077             catch (IOException ioe)
       
  1078             {
       
  1079                 // Intentionally left empty
       
  1080             }
       
  1081         }
       
  1082 
       
  1083         public int available() throws IOException
       
  1084         {
       
  1085             return iStream.available();
       
  1086         }
       
  1087     }
       
  1088 
       
  1089 //#ifdef RD_JAVA_OMJ
       
  1090     private void doFinalize()
       
  1091     {
       
  1092         if (mFinalizer != null)
       
  1093         {
       
  1094             registeredFinalize();
       
  1095             mFinalizer = null;
       
  1096         }
       
  1097     }
       
  1098 //#endif // RD_JAVA_OMJ
       
  1099 
       
  1100     // Finalization method for Symbian
       
  1101     final private void registeredFinalize()
       
  1102     {
       
  1103         if (handle != 0)
       
  1104         {
       
  1105             Platform.finalizeObject(handle, iInterface);
       
  1106             iInterface.deregister(this, iInterface);
       
  1107             iInterface = null;
       
  1108             handle = 0;
       
  1109         }
       
  1110     }
       
  1111 
       
  1112     // zlib decompression
       
  1113     private native static boolean _inflate(byte[] data, byte[] buffer);
       
  1114 
       
  1115     // native loader
       
  1116     private native static int _ctor(int handle);
       
  1117     private native static int _decodeData(int handle, int offset, byte[] data);
       
  1118     private native static void _setExternalReferences(int handle, int[] references);
       
  1119     private native static int _getLoadedObjects(int handle, int[] objects);
       
  1120     private native static int _getObjectsWithUserParameters(int handle, int[] objects);
       
  1121     private native static int _getNumUserParameters(int handle, int obj);
       
  1122     private native static int _getUserParameter(int handle, int obj, int index, byte[] data);
       
  1123 }