Home | History | Annotate | Download | only in binary
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.export.binary;
     34 
     35 import com.jme3.asset.AssetInfo;
     36 import com.jme3.asset.AssetManager;
     37 import com.jme3.export.*;
     38 import com.jme3.math.FastMath;
     39 import java.io.*;
     40 import java.net.URL;
     41 import java.nio.ByteOrder;
     42 import java.util.HashMap;
     43 import java.util.IdentityHashMap;
     44 import java.util.logging.Level;
     45 import java.util.logging.Logger;
     46 
     47 /**
     48  * @author Joshua Slack
     49  * @author Kirill Vainer - Version number, Fast buffer reading
     50  */
     51 public final class BinaryImporter implements JmeImporter {
     52     private static final Logger logger = Logger.getLogger(BinaryImporter.class
     53             .getName());
     54 
     55     private AssetManager assetManager;
     56 
     57     //Key - alias, object - bco
     58     private HashMap<String, BinaryClassObject> classes
     59              = new HashMap<String, BinaryClassObject>();
     60     //Key - id, object - the savable
     61     private HashMap<Integer, Savable> contentTable
     62             = new HashMap<Integer, Savable>();
     63     //Key - savable, object - capsule
     64     private IdentityHashMap<Savable, BinaryInputCapsule> capsuleTable
     65              = new IdentityHashMap<Savable, BinaryInputCapsule>();
     66     //Key - id, opject - location in the file
     67     private HashMap<Integer, Integer> locationTable
     68              = new HashMap<Integer, Integer>();
     69 
     70     public static boolean debug = false;
     71 
     72     private byte[] dataArray;
     73     private int aliasWidth;
     74     private int formatVersion;
     75 
     76     private static final boolean fastRead = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
     77 
     78     public BinaryImporter() {
     79     }
     80 
     81     public int getFormatVersion(){
     82         return formatVersion;
     83     }
     84 
     85     public static boolean canUseFastBuffers(){
     86         return fastRead;
     87     }
     88 
     89     public static BinaryImporter getInstance() {
     90         return new BinaryImporter();
     91     }
     92 
     93     public void setAssetManager(AssetManager manager){
     94         this.assetManager = manager;
     95     }
     96 
     97     public AssetManager getAssetManager(){
     98         return assetManager;
     99     }
    100 
    101     public Object load(AssetInfo info){
    102 //        if (!(info.getKey() instanceof ModelKey))
    103 //            throw new IllegalArgumentException("Model assets must be loaded using a ModelKey");
    104 
    105         assetManager = info.getManager();
    106 
    107         InputStream is = null;
    108         try {
    109             is = info.openStream();
    110             Savable s = load(is);
    111 
    112             return s;
    113         } catch (IOException ex) {
    114             logger.log(Level.SEVERE, "An error occured while loading jME binary object", ex);
    115         } finally {
    116             if (is != null){
    117                 try {
    118                     is.close();
    119                 } catch (IOException ex) {}
    120             }
    121         }
    122         return null;
    123     }
    124 
    125     public Savable load(InputStream is) throws IOException {
    126         return load(is, null, null);
    127     }
    128 
    129     public Savable load(InputStream is, ReadListener listener) throws IOException {
    130         return load(is, listener, null);
    131     }
    132 
    133     public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream baos) throws IOException {
    134         contentTable.clear();
    135         BufferedInputStream bis = new BufferedInputStream(is);
    136 
    137         int numClasses;
    138 
    139         // Try to read signature
    140         int maybeSignature = ByteUtils.readInt(bis);
    141         if (maybeSignature == FormatVersion.SIGNATURE){
    142             // this is a new version J3O file
    143             formatVersion = ByteUtils.readInt(bis);
    144             numClasses = ByteUtils.readInt(bis);
    145 
    146             // check if this binary is from the future
    147             if (formatVersion > FormatVersion.VERSION){
    148                 throw new IOException("The binary file is of newer version than expected! " +
    149                                       formatVersion + " > " + FormatVersion.VERSION);
    150             }
    151         }else{
    152             // this is an old version J3O file
    153             // the signature was actually the class count
    154             numClasses = maybeSignature;
    155 
    156             // 0 indicates version before we started adding
    157             // version numbers
    158             formatVersion = 0;
    159         }
    160 
    161         int bytes = 4;
    162         aliasWidth = ((int)FastMath.log(numClasses, 256) + 1);
    163 
    164         classes.clear();
    165         for(int i = 0; i < numClasses; i++) {
    166             String alias = readString(bis, aliasWidth);
    167 
    168             // jME3 NEW: Read class version number
    169             int[] classHierarchyVersions;
    170             if (formatVersion >= 1){
    171                 int classHierarchySize = bis.read();
    172                 classHierarchyVersions = new int[classHierarchySize];
    173                 for (int j = 0; j < classHierarchySize; j++){
    174                     classHierarchyVersions[j] = ByteUtils.readInt(bis);
    175                 }
    176             }else{
    177                 classHierarchyVersions = new int[]{ 0 };
    178             }
    179 
    180             // read classname and classname size
    181             int classLength = ByteUtils.readInt(bis);
    182             String className = readString(bis, classLength);
    183 
    184             BinaryClassObject bco = new BinaryClassObject();
    185             bco.alias = alias.getBytes();
    186             bco.className = className;
    187             bco.classHierarchyVersions = classHierarchyVersions;
    188 
    189             int fields = ByteUtils.readInt(bis);
    190             bytes += (8 + aliasWidth + classLength);
    191 
    192             bco.nameFields = new HashMap<String, BinaryClassField>(fields);
    193             bco.aliasFields = new HashMap<Byte, BinaryClassField>(fields);
    194             for (int x = 0; x < fields; x++) {
    195                 byte fieldAlias = (byte)bis.read();
    196                 byte fieldType = (byte)bis.read();
    197 
    198                 int fieldNameLength = ByteUtils.readInt(bis);
    199                 String fieldName = readString(bis, fieldNameLength);
    200                 BinaryClassField bcf = new BinaryClassField(fieldName, fieldAlias, fieldType);
    201                 bco.nameFields.put(fieldName, bcf);
    202                 bco.aliasFields.put(fieldAlias, bcf);
    203                 bytes += (6 + fieldNameLength);
    204             }
    205             classes.put(alias, bco);
    206         }
    207         if (listener != null) listener.readBytes(bytes);
    208 
    209         int numLocs = ByteUtils.readInt(bis);
    210         bytes = 4;
    211 
    212         capsuleTable.clear();
    213         locationTable.clear();
    214         for(int i = 0; i < numLocs; i++) {
    215             int id = ByteUtils.readInt(bis);
    216             int loc = ByteUtils.readInt(bis);
    217             locationTable.put(id, loc);
    218             bytes += 8;
    219         }
    220 
    221         @SuppressWarnings("unused")
    222         int numbIDs = ByteUtils.readInt(bis); // XXX: NOT CURRENTLY USED
    223         int id = ByteUtils.readInt(bis);
    224         bytes += 8;
    225         if (listener != null) listener.readBytes(bytes);
    226 
    227         if (baos == null) {
    228                 baos = new ByteArrayOutputStream(bytes);
    229         } else {
    230                 baos.reset();
    231         }
    232         int size = -1;
    233         byte[] cache = new byte[4096];
    234         while((size = bis.read(cache)) != -1) {
    235             baos.write(cache, 0, size);
    236             if (listener != null) listener.readBytes(size);
    237         }
    238         bis = null;
    239 
    240         dataArray = baos.toByteArray();
    241         baos = null;
    242 
    243         Savable rVal = readObject(id);
    244         if (debug) {
    245             logger.info("Importer Stats: ");
    246             logger.log(Level.INFO, "Tags: {0}", numClasses);
    247             logger.log(Level.INFO, "Objects: {0}", numLocs);
    248             logger.log(Level.INFO, "Data Size: {0}", dataArray.length);
    249         }
    250         dataArray = null;
    251         return rVal;
    252     }
    253 
    254     public Savable load(URL f) throws IOException {
    255         return load(f, null);
    256     }
    257 
    258     public Savable load(URL f, ReadListener listener) throws IOException {
    259         InputStream is = f.openStream();
    260         Savable rVal = load(is, listener);
    261         is.close();
    262         return rVal;
    263     }
    264 
    265     public Savable load(File f) throws IOException {
    266         return load(f, null);
    267     }
    268 
    269     public Savable load(File f, ReadListener listener) throws IOException {
    270         FileInputStream fis = new FileInputStream(f);
    271         Savable rVal = load(fis, listener);
    272         fis.close();
    273         return rVal;
    274     }
    275 
    276     public Savable load(byte[] data) throws IOException {
    277         ByteArrayInputStream bais = new ByteArrayInputStream(data);
    278         Savable rVal = load(bais);
    279         bais.close();
    280         return rVal;
    281     }
    282 
    283     @Override
    284     public InputCapsule getCapsule(Savable id) {
    285         return capsuleTable.get(id);
    286     }
    287 
    288     protected String readString(InputStream f, int length) throws IOException {
    289         byte[] data = new byte[length];
    290         for(int j = 0; j < length; j++) {
    291             data[j] = (byte)f.read();
    292         }
    293 
    294         return new String(data);
    295     }
    296 
    297     protected String readString(int length, int offset) throws IOException {
    298         byte[] data = new byte[length];
    299         for(int j = 0; j < length; j++) {
    300             data[j] = dataArray[j+offset];
    301         }
    302 
    303         return new String(data);
    304     }
    305 
    306     public Savable readObject(int id) {
    307 
    308         if(contentTable.get(id) != null) {
    309             return contentTable.get(id);
    310         }
    311 
    312         try {
    313             int loc = locationTable.get(id);
    314 
    315             String alias = readString(aliasWidth, loc);
    316             loc+=aliasWidth;
    317 
    318             BinaryClassObject bco = classes.get(alias);
    319 
    320             if(bco == null) {
    321                 logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "NULL class object: " + alias);
    322                 return null;
    323             }
    324 
    325             int dataLength = ByteUtils.convertIntFromBytes(dataArray, loc);
    326             loc+=4;
    327 
    328             Savable out = null;
    329             if (assetManager != null) {
    330                 out = SavableClassUtil.fromName(bco.className, assetManager.getClassLoaders());
    331             } else {
    332                 out = SavableClassUtil.fromName(bco.className);
    333             }
    334 
    335             BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco);
    336             cap.setContent(dataArray, loc, loc+dataLength);
    337 
    338             capsuleTable.put(out, cap);
    339             contentTable.put(id, out);
    340 
    341             out.read(this);
    342 
    343             capsuleTable.remove(out);
    344 
    345             return out;
    346 
    347         } catch (IOException e) {
    348             logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
    349             return null;
    350         } catch (ClassNotFoundException e) {
    351             logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
    352             return null;
    353         } catch (InstantiationException e) {
    354             logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
    355             return null;
    356         } catch (IllegalAccessException e) {
    357             logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "Exception", e);
    358             return null;
    359         }
    360     }
    361 }