diff options
Diffstat (limited to 'engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java')
-rw-r--r-- | engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java b/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java new file mode 100644 index 0000000..b9ec78d --- /dev/null +++ b/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.export.binary; + +import com.jme3.export.FormatVersion; +import com.jme3.export.JmeExporter; +import com.jme3.export.Savable; +import com.jme3.export.SavableClassUtil; +import com.jme3.math.FastMath; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Exports to the jME Binary Format. Format descriptor: (each numbered item + * denotes a series of bytes that follows sequentially one after the next.) + * <p> + * 1. "number of classes" - four bytes - int value representing the number of + * entries in the class lookup table. + * </p> + * <p> + * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9, + * where X = the number read in 1. + * </p> + * <p> + * 2. "class alias" - 1...X bytes, where X = ((int) FastMath.log(aliasCount, + * 256) + 1) - an alias used when writing object data to match an object to its + * appropriate object class type. + * </p> + * <p> + * 3. "full class name size" - four bytes - int value representing number of + * bytes to read in for next field. + * </p> + * <p> + * 4. "full class name" - 1...X bytes representing a String value, where X = the + * number read in 3. The String is the fully qualified class name of the Savable + * class, eg "<code>com.jme.math.Vector3f</code>" + * </p> + * <p> + * 5. "number of fields" - four bytes - int value representing number of blocks + * to read in next (numbers 6 - 9), where each block represents a field in this + * class. + * </p> + * <p> + * 6. "field alias" - 1 byte - the alias used when writing out fields in a + * class. Because it is a single byte, a single class can not save out more than + * a total of 256 fields. + * </p> + * <p> + * 7. "field type" - 1 byte - a value representing the type of data a field + * contains. This value is taken from the static fields of + * <code>com.jme.util.export.binary.BinaryClassField</code>. + * </p> + * <p> + * 8. "field name size" - 4 bytes - int value representing the size of the next + * field. + * </p> + * <p> + * 9. "field name" - 1...X bytes representing a String value, where X = the + * number read in 8. The String is the full String value used when writing the + * current field. + * </p> + * <p> + * 10. "number of unique objects" - four bytes - int value representing the + * number of data entries in this file. + * </p> + * <p> + * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and + * 12, where X = the number read in 10. + * </p> + * <p> + * 11. "data id" - four bytes - int value identifying a single unique object + * that was saved in this data file. + * </p> + * <p> + * 12. "data location" - four bytes - int value representing the offset in the + * object data portion of this file where the object identified in 11 is + * located. + * </p> + * <p> + * 13. "future use" - four bytes - hardcoded int value 1. + * </p> + * <p> + * 14. "root id" - four bytes - int value identifying the top level object. + * </p> + * <p> + * OBJECT DATA SECTION: There will be X blocks each consisting of numbers 15 + * thru 19, where X = the number of unique location values named in 12. + * <p> + * 15. "class alias" - see 2. + * </p> + * <p> + * 16. "data length" - four bytes - int value representing the length in bytes + * of data stored in fields 17 and 18 for this object. + * </p> + * <p> + * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19 + * </p> + * <p> + * 17. "field alias" - see 6. + * </p> + * <p> + * 18. "field data" - 1...X bytes representing the field data. The data length + * is dependent on the field type and contents. + * </p> + * + * @author Joshua Slack + */ + +public class BinaryExporter implements JmeExporter { + private static final Logger logger = Logger.getLogger(BinaryExporter.class + .getName()); + + protected int aliasCount = 1; + protected int idCount = 1; + + protected IdentityHashMap<Savable, BinaryIdContentPair> contentTable + = new IdentityHashMap<Savable, BinaryIdContentPair>(); + + protected HashMap<Integer, Integer> locationTable + = new HashMap<Integer, Integer>(); + + // key - class name, value = bco + private HashMap<String, BinaryClassObject> classes + = new HashMap<String, BinaryClassObject>(); + + private ArrayList<Savable> contentKeys = new ArrayList<Savable>(); + + public static boolean debug = false; + public static boolean useFastBufs = true; + + public BinaryExporter() { + } + + public static BinaryExporter getInstance() { + return new BinaryExporter(); + } + + public boolean save(Savable object, OutputStream os) throws IOException { + // reset some vars + aliasCount = 1; + idCount = 1; + classes.clear(); + contentTable.clear(); + locationTable.clear(); + contentKeys.clear(); + + // write signature and version + os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE)); + os.write(ByteUtils.convertToBytes(FormatVersion.VERSION)); + + int id = processBinarySavable(object); + + // write out tag table + int classTableSize = 0; + int classNum = classes.keySet().size(); + int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all + // aliases a + // fixed width + + os.write(ByteUtils.convertToBytes(classNum)); + for (String key : classes.keySet()) { + BinaryClassObject bco = classes.get(key); + + // write alias + byte[] aliasBytes = fixClassAlias(bco.alias, + aliasSize); + os.write(aliasBytes); + classTableSize += aliasSize; + + // jME3 NEW: Write class hierarchy version numbers + os.write( bco.classHierarchyVersions.length ); + for (int version : bco.classHierarchyVersions){ + os.write(ByteUtils.convertToBytes(version)); + } + classTableSize += 1 + bco.classHierarchyVersions.length * 4; + + // write classname size & classname + byte[] classBytes = key.getBytes(); + os.write(ByteUtils.convertToBytes(classBytes.length)); + os.write(classBytes); + classTableSize += 4 + classBytes.length; + + // for each field, write alias, type, and name + os.write(ByteUtils.convertToBytes(bco.nameFields.size())); + for (String fieldName : bco.nameFields.keySet()) { + BinaryClassField bcf = bco.nameFields.get(fieldName); + os.write(bcf.alias); + os.write(bcf.type); + + // write classname size & classname + byte[] fNameBytes = fieldName.getBytes(); + os.write(ByteUtils.convertToBytes(fNameBytes.length)); + os.write(fNameBytes); + classTableSize += 2 + 4 + fNameBytes.length; + } + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // write out data to a seperate stream + int location = 0; + // keep track of location for each piece + HashMap<String, ArrayList<BinaryIdContentPair>> alreadySaved = new HashMap<String, ArrayList<BinaryIdContentPair>>( + contentTable.size()); + for (Savable savable : contentKeys) { + // look back at previous written data for matches + String savableName = savable.getClass().getName(); + BinaryIdContentPair pair = contentTable.get(savable); + ArrayList<BinaryIdContentPair> bucket = alreadySaved + .get(savableName + getChunk(pair)); + int prevLoc = findPrevMatch(pair, bucket); + if (prevLoc != -1) { + locationTable.put(pair.getId(), prevLoc); + continue; + } + + locationTable.put(pair.getId(), location); + if (bucket == null) { + bucket = new ArrayList<BinaryIdContentPair>(); + alreadySaved.put(savableName + getChunk(pair), bucket); + } + bucket.add(pair); + byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasSize); + out.write(aliasBytes); + location += aliasSize; + BinaryOutputCapsule cap = contentTable.get(savable).getContent(); + out.write(ByteUtils.convertToBytes(cap.bytes.length)); + location += 4; // length of bytes + out.write(cap.bytes); + location += cap.bytes.length; + } + + // write out location table + // tag/location + int numLocations = locationTable.keySet().size(); + os.write(ByteUtils.convertToBytes(numLocations)); + int locationTableSize = 0; + for (Integer key : locationTable.keySet()) { + os.write(ByteUtils.convertToBytes(key)); + os.write(ByteUtils.convertToBytes(locationTable.get(key))); + locationTableSize += 8; + } + + // write out number of root ids - hardcoded 1 for now + os.write(ByteUtils.convertToBytes(1)); + + // write out root id + os.write(ByteUtils.convertToBytes(id)); + + // append stream to the output stream + out.writeTo(os); + + + out = null; + os = null; + + if (debug ) { + logger.info("Stats:"); + logger.log(Level.INFO, "classes: {0}", classNum); + logger.log(Level.INFO, "class table: {0} bytes", classTableSize); + logger.log(Level.INFO, "objects: {0}", numLocations); + logger.log(Level.INFO, "location table: {0} bytes", locationTableSize); + logger.log(Level.INFO, "data: {0} bytes", location); + } + + return true; + } + + protected String getChunk(BinaryIdContentPair pair) { + return new String(pair.getContent().bytes, 0, Math.min(64, pair + .getContent().bytes.length)); + } + + protected int findPrevMatch(BinaryIdContentPair oldPair, + ArrayList<BinaryIdContentPair> bucket) { + if (bucket == null) + return -1; + for (int x = bucket.size(); --x >= 0;) { + BinaryIdContentPair pair = bucket.get(x); + if (pair.getContent().equals(oldPair.getContent())) + return locationTable.get(pair.getId()); + } + return -1; + } + + protected byte[] fixClassAlias(byte[] bytes, int width) { + if (bytes.length != width) { + byte[] newAlias = new byte[width]; + for (int x = width - bytes.length; x < width; x++) + newAlias[x] = bytes[x - bytes.length]; + return newAlias; + } + return bytes; + } + + public boolean save(Savable object, File f) throws IOException { + File parentDirectory = f.getParentFile(); + if(parentDirectory != null && !parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + FileOutputStream fos = new FileOutputStream(f); + boolean rVal = save(object, fos); + fos.close(); + return rVal; + } + + public BinaryOutputCapsule getCapsule(Savable object) { + return contentTable.get(object).getContent(); + } + + private BinaryClassObject createClassObject(Class clazz) throws IOException{ + BinaryClassObject bco = new BinaryClassObject(); + bco.alias = generateTag(); + bco.nameFields = new HashMap<String, BinaryClassField>(); + bco.classHierarchyVersions = SavableClassUtil.getSavableVersions(clazz); + + classes.put(clazz.getName(), bco); + + return bco; + } + + public int processBinarySavable(Savable object) throws IOException { + if (object == null) { + return -1; + } + Class<? extends Savable> clazz = object.getClass(); + BinaryClassObject bco = classes.get(object.getClass().getName()); + // is this class been looked at before? in tagTable? + if (bco == null) { + bco = createClassObject(object.getClass()); + } + + // is object in contentTable? + if (contentTable.get(object) != null) { + return (contentTable.get(object).getId()); + } + BinaryIdContentPair newPair = generateIdContentPair(bco); + BinaryIdContentPair old = contentTable.put(object, newPair); + if (old == null) { + contentKeys.add(object); + } + object.write(this); + newPair.getContent().finish(); + return newPair.getId(); + + } + + protected byte[] generateTag() { + int width = ((int) FastMath.log(aliasCount, 256) + 1); + int count = aliasCount; + aliasCount++; + byte[] bytes = new byte[width]; + for (int x = width - 1; x >= 0; x--) { + int pow = (int) FastMath.pow(256, x); + int factor = count / pow; + bytes[width - x - 1] = (byte) factor; + count %= pow; + } + return bytes; + } + + protected BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) { + BinaryIdContentPair pair = new BinaryIdContentPair(idCount++, + new BinaryOutputCapsule(this, bco)); + return pair; + } +}
\ No newline at end of file |