aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java
diff options
context:
space:
mode:
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.java401
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