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 BinaryImporter()78 public BinaryImporter() { 79 } 80 getFormatVersion()81 public int getFormatVersion(){ 82 return formatVersion; 83 } 84 canUseFastBuffers()85 public static boolean canUseFastBuffers(){ 86 return fastRead; 87 } 88 getInstance()89 public static BinaryImporter getInstance() { 90 return new BinaryImporter(); 91 } 92 setAssetManager(AssetManager manager)93 public void setAssetManager(AssetManager manager){ 94 this.assetManager = manager; 95 } 96 getAssetManager()97 public AssetManager getAssetManager(){ 98 return assetManager; 99 } 100 load(AssetInfo info)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 load(InputStream is)125 public Savable load(InputStream is) throws IOException { 126 return load(is, null, null); 127 } 128 load(InputStream is, ReadListener listener)129 public Savable load(InputStream is, ReadListener listener) throws IOException { 130 return load(is, listener, null); 131 } 132 load(InputStream is, ReadListener listener, ByteArrayOutputStream baos)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 load(URL f)254 public Savable load(URL f) throws IOException { 255 return load(f, null); 256 } 257 load(URL f, ReadListener listener)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 load(File f)265 public Savable load(File f) throws IOException { 266 return load(f, null); 267 } 268 load(File f, ReadListener listener)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 load(byte[] data)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 getCapsule(Savable id)284 public InputCapsule getCapsule(Savable id) { 285 return capsuleTable.get(id); 286 } 287 readString(InputStream f, int length)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 readString(int length, int offset)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 readObject(int id)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 }