Przeglądaj źródła

Fix for XMLExporter issues in #2310 (#2313)

* #2176 Make LWJGLBufferAllocator use nmemCalloc() instead of nmemAlloc()

https://github.com/jMonkeyEngine/jmonkeyengine/issues/2176
LWJGLBufferAllocator.allocate() now always returns zero-initialized buffers.

* Added unit tests for JmeExporter/JmeImporter implementations

Tests all write* and read* methods of OutputCapsule and InputCapsule respectively.

* Fixed XMLExporter/XMLImporter BitSets

Previously DOMOutputCapsule was writing indices of set bits and DOMInputCapsule was reading values of each bit.  Changed DOMOutputCapsule to match the expected behavior of DOMInputCapsule.

* Fixed DOMInputCapsule.readString() returning defVal for empty strings

org.w3c.dom.Element.getAttribute() returns an empty string for attributes that are not found.  It looks like DOMInputCapsule.readString() was interpreting an empty string as the attribute not existing, and returning defVal even when the attribute did exist and was an empty string.  Now it checks explicitly whether the attribute exists.

* Deprecated DOMSerializer in favor of javax.xml.transform.Transformer

DOMSerializer contains several edge-case issues that were only partially worked around with the encodeString() and decodeString() helper methods.  Java has a robust built-in solution to serializing Document objects, and using that instead fixes several bugs.

* Fixed NullPointerException when XMLExporter writes a String[] with null

Also refactored all primitive array write and read methods to be more readable and reduce duplicate code.

* Made DOM capsules reuse write/read primitive array methods for buffers

Further reduces duplicate code

* Fixed DOMOutputCapsule.write(Savable[][]) NullPointerException

Refactored write and read methods for Savables and 1 and 2 dimensional Savable arrays.  Fixed NullPointerException when writing a 2d Savable array containing a null element in the outer array.

* Added Savable reference consistency test to InputOutputCapsuleTest

* Fixed DOMInputCapsule throwing NullPointerException when reading list

DOMInputCapsule used to throw a NullPointerException when reading an Arraylist containing a null element.  Also refactored list write and read methods to clean up a bit and accidentally also fixed an unrelated bug where reading ArrayList<ByteBuffer> would return a list containing all null elements.

* Made XMLExporter save and load buffer positions properly.

* Cleanup and formatting for XMLExporter related classes

* Undid XMLExporter saving buffer positions.

Not saving positions is intentional https://github.com/jMonkeyEngine/jmonkeyengine/issues/2312#issuecomment-2359149509

* Fixed infinite recursion with XMLExporter

Writing a Savable containing a reference loop caused infinite recursion due to bookkeeping being performed after the recursive call instead of before.  Also added a unit test for this to InputOutputCapsuleTest.
JosiahGoeman 10 miesięcy temu
rodzic
commit
eee43564c4

+ 717 - 0
jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java

@@ -0,0 +1,717 @@
+/*
+ * Copyright (c) 2023 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;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.BitSet;
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.File;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.nio.IntBuffer;
+import java.nio.FloatBuffer;
+import java.util.Iterator;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.asset.AssetKey;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.export.xml.XMLExporter;
+import com.jme3.export.xml.XMLImporter;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Matrix4f;
+
+/**
+ * Test suite for implementations of the JmeExporter and JmeImporter interfaces.
+ * There are tests here for all write* and read* methods of the OutputCapsule and InputCapsule interfaces respectively.
+ */
+public class InputOutputCapsuleTest {
+    private static final List<JmeExporter> exporters = new ArrayList<>();
+    private static final List<JmeImporter> importers = new ArrayList<>();
+    static {
+        exporters.add(new BinaryExporter());
+        importers.add(new BinaryImporter());
+
+        exporters.add(new XMLExporter());
+        importers.add(new XMLImporter());
+
+        // add any future implementations here
+    }
+
+    @Test
+    public void testPrimitives() {
+        saveAndLoad(new TestPrimitives());
+    }
+
+    @Test
+    public void testStrings() {
+        saveAndLoad(new TestStrings());
+    }
+
+    @Test
+    public void testEnums() {
+        saveAndLoad(new TestEnums());
+    }
+
+    @Test
+    public void testBitSets() {
+        saveAndLoad(new TestBitSets());
+    }
+
+    @Test
+    public void testSavables() {
+        saveAndLoad(new TestSavables());
+    }
+
+    @Test
+    public void testSavableReferences() {
+        saveAndLoad(new TestSavableReferences());
+    }
+
+    @Test
+    public void testArrays() {
+        saveAndLoad(new TestArrays());
+    }
+
+    @Test
+    public void testBuffers() {
+        saveAndLoad(new TestBuffers());
+    }
+
+    @Test
+    public void testLists() {
+        saveAndLoad(new TestLists());
+    }
+
+    @Test
+    public void testMaps() {
+        saveAndLoad(new TestMaps());
+    }
+
+    // attempts to save and load a Savable using the JmeExporter/JmeImporter implementations listed at the top of this class.
+    // the Savable inner classes in this file run assertions in their read() methods
+    // to ensure the data loaded is the same as what was written.  more or less stole this from JmeExporterTest.java
+    private static void saveAndLoad(Savable savable) {
+        for (int i = 0; i < exporters.size(); i++) {
+            JmeExporter exporter = exporters.get(i);
+            JmeImporter importer = importers.get(i);
+
+            // export
+            byte[] exportedBytes = null;
+            try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
+                exporter.save(savable, outStream);
+                exportedBytes = outStream.toByteArray();
+            } catch (IOException e) {
+                Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString());
+            }
+
+            // write the xml into files for debugging.
+            // leave this commented out unless you need it since it makes a mess of the jme3-plugins directory.
+            /*if (exporter instanceof XMLExporter) {
+                try {
+                    File outFile = new File(savable.getClass().getSimpleName() + ".xml");
+                    outFile.createNewFile();
+                    PrintWriter out = new PrintWriter(outFile);
+                    out.print(new String(exportedBytes));
+                    out.close();
+                } catch(IOException ioEx) {
+
+                }
+            }*/
+
+            // import
+            try (ByteArrayInputStream inStream = new ByteArrayInputStream(exportedBytes)) {
+                AssetInfo info = new AssetInfo(null, null) {
+                    @Override
+                    public InputStream openStream() {
+                        return inStream;
+                    }
+                };
+                importer.load(info);    // this is where assertions will fail if loaded data does not match saved data.
+            } catch (IOException e) {
+                Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString());
+            }
+        }
+    }
+
+    // test data.  I tried to include as many edge cases as I could think of.
+    private static final byte[] testByteArray = new byte[] {Byte.MIN_VALUE, Byte.MAX_VALUE};
+    private static final short[] testShortArray = new short[] {Short.MIN_VALUE, Short.MAX_VALUE};
+    private static final int[] testIntArray = new int[] {Integer.MIN_VALUE, Integer.MAX_VALUE};
+    private static final long[] testLongArray = new long[] {Long.MIN_VALUE, Long.MAX_VALUE};
+    private static final float[] testFloatArray = new float[] {
+        Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL,
+        Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN
+    };
+    private static final double[] testDoubleArray = new double[] {
+        Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL,
+        Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN
+    };
+    private static final boolean[] testBooleanArray = new boolean[] {false, true};
+    private static final String[] testStringArray = new String[] {
+        "hello, world!",
+        null,
+        "",
+        " ",   // blank string (whitespace)
+        "mind    the gap",   // multiple consecutive spaces (some xml processors would normalize this to a single space)
+        //new String(new char[10_000_000]).replace('\0', 'a'),    // long string. kinda slows down the test too much so I'm leaving it out for now.
+        "\t",
+        "\n",
+        "\r",
+        "hello  こんにちは 你好  Здравствуйте  안녕하세요  🙋",
+        "&apos; &quot; &lt; &gt; &amp;", // xml entities
+        // xml metacharacters
+        "\'",
+        "\"",
+        "<",
+        ">",
+        "&",
+        "<!--", // xml comment
+        "-->",
+        "]]>"   // xml close cdata
+    };
+
+    private static final Savable[] testSavableArray = new Savable[] {
+        new Vector3f(0f, 1f, 2f),
+        null,
+        new Quaternion(0f, 1f, 2f, 3f),
+        new Transform(new Vector3f(1f, 2f, 3f), new Quaternion(1f, 2f, 3f, 4f), new Vector3f(1f, 2f, 3f)),
+        new Matrix4f()
+    };
+
+    private static final byte[][] testByteArray2D = new byte[][] {
+        testByteArray,
+        null,
+        new byte[0]
+    };
+
+    private static final short[][] testShortArray2D = new short[][] {
+        testShortArray,
+        null,
+        new short[0]
+    };
+
+    private static final int[][] testIntArray2D = new int[][] {
+        testIntArray,
+        null,
+        new int[0]
+    };
+
+    private static final long[][] testLongArray2D = new long[][] {
+        testLongArray,
+        null,
+        new long[0]
+    };
+
+    private static final float[][] testFloatArray2D = new float[][] {
+        testFloatArray,
+        null,
+        new float[0]
+    };
+
+    private static final double[][] testDoubleArray2D = new double[][] {
+        testDoubleArray,
+        null,
+        new double[0]
+    };
+
+    private static final boolean[][] testBooleanArray2D = new boolean[][] {
+        testBooleanArray,
+        null,
+        new boolean[0]
+    };
+
+    private static final String[][] testStringArray2D = new String[][] {
+        testStringArray,
+        null,
+        new String[0]
+    };
+
+    private static final CullHint[] testEnumArray = new CullHint[] {
+        CullHint.Never,
+        null,
+        CullHint.Always,
+        CullHint.Inherit
+    };
+
+    private static final BitSet[] testBitSetArray = new BitSet[] {
+        BitSet.valueOf("BitSet".getBytes()),
+        null,
+        new BitSet()
+    };
+
+    private static final Savable[][] testSavableArray2D = new Savable[][] {
+        testSavableArray,
+        null,
+        new Savable[0]
+    };
+
+    private static final ByteBuffer testByteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(testByteArray).rewind();
+    private static final ShortBuffer testShortBuffer = (ShortBuffer) BufferUtils.createShortBuffer(testShortArray).rewind();
+    private static final IntBuffer testIntBuffer = (IntBuffer) BufferUtils.createIntBuffer(testIntArray).rewind();
+    private static final FloatBuffer testFloatBuffer = (FloatBuffer) BufferUtils.createFloatBuffer(testFloatArray).rewind();
+
+    private static final ArrayList<ByteBuffer> testByteBufferArrayList = new ArrayList<>(Arrays.asList(
+        BufferUtils.createByteBuffer(testByteArray2D[0]),
+        null,
+        BufferUtils.createByteBuffer(testByteArray2D[2])
+    ));
+
+    private static final ArrayList<FloatBuffer> testFloatBufferArrayList = new ArrayList<>(Arrays.asList(
+        BufferUtils.createFloatBuffer(testFloatArray2D[0]),
+        null,
+        BufferUtils.createFloatBuffer(testFloatArray2D[2])
+    ));
+
+    private static final ArrayList<Savable> testSavableArrayList = new ArrayList<>(Arrays.asList(testSavableArray));
+
+    @SuppressWarnings("unchecked")
+    private static final ArrayList<Savable>[] testSavableArrayListArray = new ArrayList[] {
+        testSavableArrayList,
+        null,
+        new ArrayList()
+    };
+
+    // "array" and "list" don't sound like real words anymore
+    @SuppressWarnings("unchecked")
+    private static final ArrayList<Savable>[][] testSavableArrayListArray2D = new ArrayList[][] {
+        testSavableArrayListArray,
+        null,
+        {},
+    };
+
+    private static final Map<Savable, Savable> testSavableMap = new HashMap<Savable, Savable>() {{
+        put(Vector3f.UNIT_X, Vector3f.UNIT_X);
+        put(Vector3f.UNIT_Y, Quaternion.IDENTITY);
+        put(Vector3f.UNIT_Z, null);
+    }};
+
+    private static final Map<String, Savable> testStringSavableMap = new HashMap<String, Savable>() {{
+        put("v", Vector3f.UNIT_X);
+        put("q", Quaternion.IDENTITY);
+        put("n", null);
+    }};
+
+    private static final IntMap<Savable> testIntSavableMap = new IntMap<Savable>();
+    static {    //IntMap is final so we gotta use a static block here.
+        testIntSavableMap.put(0, Vector3f.UNIT_X);
+        testIntSavableMap.put(1, Quaternion.IDENTITY);
+        testIntSavableMap.put(2, null);
+    }
+
+    // the rest of this file is inner classes that implement Savable.
+    // these classes write the test data, then verify that it's the same data in their read() methods.
+    private static class TestPrimitives implements Savable {
+        TestPrimitives() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            for (int i = 0; i < testByteArray.length; i++)
+                capsule.write(testByteArray[i], "test_byte_" + i, (byte) 0);
+
+            for (int i = 0; i < testShortArray.length; i++)
+                capsule.write(testShortArray[i], "test_short_" + i, (short) 0);
+
+            for (int i = 0; i < testIntArray.length; i++)
+                capsule.write(testIntArray[i], "test_int_" + i, 0);
+
+            for (int i = 0; i < testLongArray.length; i++)
+                capsule.write(testLongArray[i], "test_long_" + i, 0l);
+
+            for (int i = 0; i < testFloatArray.length; i++)
+                capsule.write(testFloatArray[i], "test_float_" + i, 0f);
+
+            for (int i = 0; i < testDoubleArray.length; i++)
+                capsule.write(testDoubleArray[i], "test_double_" + i, 0d);
+
+            for (int i = 0; i < testBooleanArray.length; i++)
+                capsule.write(testBooleanArray[i], "test_boolean_" + i, false);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            for (int i = 0; i < testByteArray.length; i++)
+                Assert.assertEquals("readByte()", testByteArray[i], capsule.readByte("test_byte_" + i, (byte) 0));
+
+            for (int i = 0; i < testShortArray.length; i++)
+                Assert.assertEquals("readShort()", testShortArray[i], capsule.readShort("test_short_" + i, (short) 0));
+
+            for (int i = 0; i < testIntArray.length; i++)
+                Assert.assertEquals("readInt()", testIntArray[i], capsule.readInt("test_int_" + i, 0));
+
+            for (int i = 0; i < testLongArray.length; i++)
+                Assert.assertEquals("readLong()", testLongArray[i], capsule.readLong("test_long_" + i, 0l));
+
+            for (int i = 0; i < testFloatArray.length; i++)
+                Assert.assertEquals("readFloat()", testFloatArray[i], capsule.readFloat("test_float_" + i, 0f), 0f);
+
+            for (int i = 0; i < testDoubleArray.length; i++)
+                Assert.assertEquals("readDouble()", testDoubleArray[i], capsule.readDouble("test_double_" + i, 0d), 0d);
+
+            for (int i = 0; i < testBooleanArray.length; i++)
+                Assert.assertEquals("readBoolean()", testBooleanArray[i], capsule.readBoolean("test_boolean_" + i, false));
+        }
+    }
+
+    private static class TestStrings implements Savable {
+        TestStrings() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            for (int i = 0; i < testStringArray.length; i++) {
+                capsule.write(testStringArray[i], "test_string_" + i, null);
+            }
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            for (int i = 0; i < testStringArray.length; i++) {
+                Assert.assertEquals("readString()", testStringArray[i], capsule.readString("test_string_" + i, null));
+            }
+        }
+    }
+
+    private static class TestEnums implements Savable {
+        TestEnums() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            for (int i = 0; i < testEnumArray.length; i++) {
+                capsule.write(testEnumArray[i], "test_enum_" + i, null);
+            }
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            for (int i = 0; i < testEnumArray.length; i++) {
+                Assert.assertEquals("readEnum()", testEnumArray[i], capsule.readEnum("test_enum_" + i, CullHint.class, null));
+            }
+        }
+    }
+
+    private static class TestBitSets implements Savable {
+        TestBitSets() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            for (int i = 0; i < testBitSetArray.length; i++) {
+                capsule.write(testBitSetArray[i], "test_bit_set_" + i, null);
+            }
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            for (int i = 0; i < testBitSetArray.length; i++) {
+                Assert.assertEquals("readBitSet()", testBitSetArray[i], capsule.readBitSet("test_bit_set_" + i, null));
+            }
+        }
+    }
+
+    private static class TestSavables implements Savable {
+        TestSavables() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            for(int i = 0; i < testSavableArray.length; i++)
+                capsule.write(testSavableArray[i], "test_savable_" + i, null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            for(int i = 0; i < testSavableArray.length; i++)
+                Assert.assertEquals("readSavable()", testSavableArray[i], capsule.readSavable("test_savable_" + i, null));
+        }
+    }
+
+    private static class TestSavableReferences implements Savable {
+        TestSavableReferences() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            Vector3f v1 = new Vector3f(1f, 2f, 3f);
+            Vector3f notV1 = v1.clone();
+
+            capsule.write(v1, "v1", null);
+            capsule.write(v1, "also_v1", null);
+            capsule.write(notV1, "not_v1", null);
+
+            // testing reference loop.  this used to cause infinite recursion.
+            Node n1 = new Node("node_1");
+            Node n2 = new Node("node_2");
+
+            n1.setUserData("node_2", n2);
+            n2.setUserData("node_1", n1);
+
+            capsule.write(n1, "node_1", null);
+            capsule.write(n2, "node_2", null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            Vector3f v1 = (Vector3f) capsule.readSavable("v1", null);
+            Vector3f alsoV1 = (Vector3f) capsule.readSavable("also_v1", null);
+            Vector3f notV1 = (Vector3f) capsule.readSavable("not_v1", null);
+
+            Assert.assertTrue("readSavable() savable duplicated, references not preserved.", v1 == alsoV1);
+            Assert.assertTrue("readSavable() unique savables merged, unexpected shared references.", v1 != notV1);
+
+            Node n1 = (Node) capsule.readSavable("node_1", null);
+            Node n2 = (Node) capsule.readSavable("node_2", null);
+
+            Assert.assertTrue("readSavable() reference loop not preserved.", n1.getUserData("node_2") == n2 && n2.getUserData("node_1") == n1);
+        }
+    }
+
+    private static class TestArrays implements Savable {
+        TestArrays() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            capsule.write(testByteArray, "testByteArray", null);
+            capsule.write(testShortArray, "testShortArray", null);
+            capsule.write(testIntArray, "testIntArray", null);
+            capsule.write(testLongArray, "testLongArray", null);
+            capsule.write(testFloatArray, "testFloatArray", null);
+            capsule.write(testDoubleArray, "testDoubleArray", null);
+            capsule.write(testBooleanArray, "testBooleanArray", null);
+            capsule.write(testStringArray, "testStringArray", null);
+            capsule.write(testSavableArray, "testSavableArray", null);
+
+            capsule.write(new byte[0], "emptyByteArray", null);
+            capsule.write(new short[0], "emptyShortArray", null);
+            capsule.write(new int[0], "emptyIntArray", null);
+            capsule.write(new long[0], "emptyLongArray", null);
+            capsule.write(new float[0], "emptyFloatArray", null);
+            capsule.write(new double[0], "emptyDoubleArray", null);
+            capsule.write(new boolean[0], "emptyBooleanArray", null);
+            capsule.write(new String[0], "emptyStringArray", null);
+            capsule.write(new Savable[0], "emptySavableArray", null);
+            
+            capsule.write(testByteArray2D, "testByteArray2D", null);
+            capsule.write(testShortArray2D, "testShortArray2D", null);
+            capsule.write(testIntArray2D, "testIntArray2D", null);
+            capsule.write(testLongArray2D, "testLongArray2D", null);
+            capsule.write(testFloatArray2D, "testFloatArray2D", null);
+            capsule.write(testDoubleArray2D, "testDoubleArray2D", null);
+            capsule.write(testBooleanArray2D, "testBooleanArray2D", null);
+            capsule.write(testStringArray2D, "testStringArray2D", null);
+            capsule.write(testSavableArray2D, "testSavableArray2D", null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            Assert.assertArrayEquals("readByteArray()", testByteArray, capsule.readByteArray("testByteArray", null));
+            Assert.assertArrayEquals("readShortArray()", testShortArray, capsule.readShortArray("testShortArray", null));
+            Assert.assertArrayEquals("readIntArray()", testIntArray, capsule.readIntArray("testIntArray", null));
+            Assert.assertArrayEquals("readLongArray()", testLongArray, capsule.readLongArray("testLongArray", null));
+            Assert.assertArrayEquals("readFloatArray()", testFloatArray, capsule.readFloatArray("testFloatArray", null), 0f);
+            Assert.assertArrayEquals("readDoubleArray()", testDoubleArray, capsule.readDoubleArray("testDoubleArray", null), 0d);
+            Assert.assertArrayEquals("readBooleanArray()", testBooleanArray, capsule.readBooleanArray("testBooleanArray", null));
+            Assert.assertArrayEquals("readStringArray()", testStringArray, capsule.readStringArray("testStringArray", null));
+            Assert.assertArrayEquals("readSavableArray()", testSavableArray, capsule.readSavableArray("testSavableArray", null));
+
+            Assert.assertArrayEquals("readByteArray()", new byte[0], capsule.readByteArray("emptyByteArray", null));
+            Assert.assertArrayEquals("readShortArray()", new short[0], capsule.readShortArray("emptyShortArray", null));
+            Assert.assertArrayEquals("readIntArray()", new int[0], capsule.readIntArray("emptyIntArray", null));
+            Assert.assertArrayEquals("readLongArray()", new long[0], capsule.readLongArray("emptyLongArray", null));
+            Assert.assertArrayEquals("readFloatArray()", new float[0], capsule.readFloatArray("emptyFloatArray", null), 0f);
+            Assert.assertArrayEquals("readDoubleArray()", new double[0], capsule.readDoubleArray("emptyDoubleArray", null), 0d);
+            Assert.assertArrayEquals("readBooleanArray()", new boolean[0], capsule.readBooleanArray("emptyBooleanArray", null));
+            Assert.assertArrayEquals("readStringArray()", new String[0], capsule.readStringArray("emptyStringArray", null));
+            Assert.assertArrayEquals("readSavableArray()", new Savable[0], capsule.readSavableArray("emptySavableArray", null));
+
+            Assert.assertArrayEquals("readByteArray2D()", testByteArray2D, capsule.readByteArray2D("testByteArray2D", null));
+            Assert.assertArrayEquals("readShortArray2D()", testShortArray2D, capsule.readShortArray2D("testShortArray2D", null));
+            Assert.assertArrayEquals("readIntArray2D()", testIntArray2D, capsule.readIntArray2D("testIntArray2D", null));
+            Assert.assertArrayEquals("readLongArray2D()", testLongArray2D, capsule.readLongArray2D("testLongArray2D", null));
+            Assert.assertArrayEquals("readFloatArray2D()", testFloatArray2D, capsule.readFloatArray2D("testFloatArray2D", null));
+            Assert.assertArrayEquals("readDoubleArray2D()", testDoubleArray2D, capsule.readDoubleArray2D("testDoubleArray2D", null));
+            Assert.assertArrayEquals("readBooleanArray2D()", testBooleanArray2D, capsule.readBooleanArray2D("testBooleanArray2D", null));
+            Assert.assertArrayEquals("readStringArray2D()", testStringArray2D, capsule.readStringArray2D("testStringArray2D", null));
+            Assert.assertArrayEquals("readSavableArray2D()", testSavableArray2D, capsule.readSavableArray2D("testSavableArray2D", null));
+        }
+    }
+
+    private static class TestBuffers implements Savable {
+        TestBuffers() {
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            capsule.write(testByteBuffer, "testByteBuffer", null);
+            capsule.write(testShortBuffer, "testShortBuffer", null);
+            capsule.write(testIntBuffer, "testIntBuffer", null);
+            capsule.write(testFloatBuffer, "testFloatBuffer", null);
+
+            capsule.write(BufferUtils.createByteBuffer(0), "emptyByteBuffer", null);
+            capsule.write(BufferUtils.createShortBuffer(0), "emptyShortBuffer", null);
+            capsule.write(BufferUtils.createIntBuffer(0), "emptyIntBuffer", null);
+            capsule.write(BufferUtils.createFloatBuffer(0), "emptyFloatBuffer", null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            Assert.assertEquals("readByteBuffer()", testByteBuffer, capsule.readByteBuffer("testByteBuffer", null));
+            Assert.assertEquals("readShortBuffer()", testShortBuffer, capsule.readShortBuffer("testShortBuffer", null));
+            Assert.assertEquals("readIntBuffer()", testIntBuffer, capsule.readIntBuffer("testIntBuffer", null));
+            Assert.assertEquals("readFloatBuffer()", testFloatBuffer, capsule.readFloatBuffer("testFloatBuffer", null));
+
+            Assert.assertEquals("readByteBuffer()", BufferUtils.createByteBuffer(0), capsule.readByteBuffer("emptyByteBuffer", null));
+            Assert.assertEquals("readShortBuffer()", BufferUtils.createShortBuffer(0), capsule.readShortBuffer("emptyShortBuffer", null));
+            Assert.assertEquals("readIntBuffer()", BufferUtils.createIntBuffer(0), capsule.readIntBuffer("emptyIntBuffer", null));
+            Assert.assertEquals("readFloatBuffer()", BufferUtils.createFloatBuffer(0), capsule.readFloatBuffer("emptyFloatBuffer", null));
+        }
+    }
+
+    private static class TestLists implements Savable {
+        TestLists() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            capsule.writeByteBufferArrayList(testByteBufferArrayList, "testByteBufferArrayList", null);
+            capsule.writeFloatBufferArrayList(testFloatBufferArrayList, "testFloatBufferArrayList", null);
+            capsule.writeSavableArrayList(testSavableArrayList, "testSavableArrayList", null);
+            capsule.writeSavableArrayListArray(testSavableArrayListArray, "testSavableArrayListArray", null);
+            capsule.writeSavableArrayListArray2D(testSavableArrayListArray2D, "testSavableArrayListArray2D", null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            Assert.assertEquals("readByteBufferArrayList()", testByteBufferArrayList, capsule.readByteBufferArrayList("testByteBufferArrayList", null));
+            Assert.assertEquals("readFloatBufferArrayList()", testFloatBufferArrayList, capsule.readFloatBufferArrayList("testFloatBufferArrayList", null));
+            Assert.assertEquals("readSavableArrayList()", testSavableArrayList, capsule.readSavableArrayList("testSavableArrayList", null));
+            Assert.assertEquals("readSavableArrayListArray()", testSavableArrayListArray, capsule.readSavableArrayListArray("testSavableArrayListArray", null));
+            Assert.assertEquals("readSavableArrayListArray2D()", testSavableArrayListArray2D, capsule.readSavableArrayListArray2D("testSavableArrayListArray2D", null));
+        }
+    }
+
+    private static class TestMaps implements Savable {
+        TestMaps() {
+
+        }
+
+        @Override
+        public void write(JmeExporter je) throws IOException {
+            OutputCapsule capsule = je.getCapsule(this);
+
+            capsule.writeSavableMap(testSavableMap, "testSavableMap", null);
+            capsule.writeStringSavableMap(testStringSavableMap, "testStringSavableMap", null);
+            capsule.writeIntSavableMap(testIntSavableMap, "testIntSavableMap", null);
+        }
+
+        @Override
+        public void read(JmeImporter ji) throws IOException {
+            InputCapsule capsule = ji.getCapsule(this);
+
+            Assert.assertEquals("readSavableMap()", testSavableMap, capsule.readSavableMap("testSavableMap", null));
+            Assert.assertEquals("readStringSavableMap()", testStringSavableMap, capsule.readStringSavableMap("testStringSavableMap", null));
+
+            // IntMap does not implement equals() so we have to do it manually
+            IntMap loadedIntMap = capsule.readIntSavableMap("testIntSavableMap", null);
+            Iterator iterator = testIntSavableMap.iterator();
+            while(iterator.hasNext()) {
+                IntMap.Entry entry = (IntMap.Entry) iterator.next();
+                Assert.assertEquals("readIntSavableMap()", entry.getValue(), loadedIntMap.get(entry.getKey()));
+            }
+        }
+    }
+}

Plik diff jest za duży
+ 607 - 1116
jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java


+ 360 - 586
jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java

@@ -39,13 +39,13 @@ import com.jme3.export.Savable;
 import com.jme3.export.SavableClassUtil;
 import com.jme3.util.IntMap;
 import com.jme3.util.IntMap.Entry;
+import java.lang.reflect.Array;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 import java.util.*;
-import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -56,8 +56,6 @@ import org.w3c.dom.Element;
  * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5
  */
 public class DOMOutputCapsule implements OutputCapsule {
-    
-    private static final String dataAttributeName = "data";
     private Document doc;
     private Element currentElement;
     private JmeExporter exporter;
@@ -90,397 +88,258 @@ public class DOMOutputCapsule implements OutputCapsule {
         return ret;
     }
 
-    private static String encodeString(String s) {
-        if (s == null) {
-            return null;
-        }
-        s =     s.replaceAll("\\&", "&amp;")
-                 .replaceAll("\\\"", "&quot;")
-                 .replaceAll("\\<", "&lt;");
-        return s;
-    }
+    // helper function to reduce duplicate code.  uses reflection to write an array of any primitive type.
+    // also has optional position argument for writing buffers.
+    private void writePrimitiveArrayHelper(Object value, String name) throws IOException {
+        StringBuilder sb = new StringBuilder();
 
-    @Override
-    public void write(byte value, String name, byte defVal) throws IOException {
-        if (value == defVal) {
-            return;
+        for(int i = 0; i < Array.getLength(value); i++) {
+            sb.append(Array.get(value, i));
+            sb.append(" ");
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
-    }
 
-    @Override
-    public void write(byte[] value, String name, byte[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (byte b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
-        }
+        // remove last space
+        sb.setLength(Math.max(0, sb.length() - 1));
+
+        appendElement(name);
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(Array.getLength(value)));
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_DATA, sb.toString());
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
         currentElement = (Element) currentElement.getParentNode();
     }
 
-    @Override
-    public void write(byte[][] value, String name, byte[][] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (byte[] bs : value) {
-            for (byte b : bs) {
-                buf.append(b);
-                buf.append(" ");
+    // helper function to reduce duplicate code.  uses the above helper to write a 2d array of any primitive type.
+    private void writePrimitiveArray2DHelper(Object[] value, String name) throws IOException {
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+
+        // tag names are not used for anything by DOMInputCapsule, but for the sake of readability, it's nice to know what type of array this is.
+        String childNamePrefix = value.getClass().getComponentType().getSimpleName().toLowerCase();
+        childNamePrefix = childNamePrefix.replace("[]", "_array_");
+
+        for (int i = 0; i < value.length; i++) {
+            String childName = childNamePrefix + i;
+
+            if (value[i] != null) {
+                writePrimitiveArrayHelper(value[i], childName);
+            } else {
+                // empty tag
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
             }
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 1) {
-            //remove last spaces
-            buf.setLength(buf.length() - 2);
-        } else if (buf.length() > 0) {
-            buf.setLength(buf.length() - 1);
         }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
         currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(int value, String name, int defVal) throws IOException {
-        if (value == defVal) {
-            return;
+    public void write(byte value, String name, byte defVal) throws IOException {
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
-    public void write(int[] value, String name, int[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) { return; }
-        if (Arrays.equals(value, defVal)) { return; }
-
-        for (int b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+    public void write(byte[] value, String name, byte[] defVal) throws IOException {
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(int[][] value, String name, int[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-
-        for (int i=0; i<value.length; i++) {
-                int[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
+    public void write(byte[][] value, String name, byte[][] defVal) throws IOException {
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
         }
-        currentElement = (Element) el.getParentNode();
     }
 
     @Override
-    public void write(float value, String name, float defVal) throws IOException {
-        if (value == defVal) {
-            return;
+    public void write(short value, String name, short defVal) throws IOException {
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
-    public void write(float[] value, String name, float[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        if (value != null) {
-            for (float b : value) {
-                buf.append(b);
-                buf.append(" ");
-            }
-            
-            if (buf.length() > 0) {
-                //remove last space
-                buf.setLength(buf.length() - 1);
-            }
+    public void write(short[] value, String name, short[] defVal) throws IOException {
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", value == null ? "0" : String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(float[][] value, String name, float[][] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
-
-        for (float[] bs : value) {
-            for(float b : bs){
-                buf.append(b);
-                buf.append(" ");
-            }
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+    public void write(short[][] value, String name, short[][] defVal) throws IOException {
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(double value, String name, double defVal) throws IOException {
-        if (value == defVal) {
-            return;
+    public void write(int value, String name, int defVal) throws IOException {
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
-    public void write(double[] value, String name, double[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (double b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+    public void write(int[] value, String name, int[] defVal) throws IOException {
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(double[][] value, String name, double[][] defVal) throws IOException {
-            if (value == null) return;
-            if(Arrays.deepEquals(value, defVal)) return;
-
-            Element el = appendElement(name);
-            XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-
-            for (int i=0; i<value.length; i++) {
-                double[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
-            }
-            currentElement = (Element) el.getParentNode();
+    public void write(int[][] value, String name, int[][] defVal) throws IOException {
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
+        }
     }
 
     @Override
     public void write(long value, String name, long defVal) throws IOException {
-        if (value == defVal) {
-            return;
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
     public void write(long[] value, String name, long[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (long b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
     public void write(long[][] value, String name, long[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-
-        for (int i=0; i<value.length; i++) {
-                long[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
         }
-        currentElement = (Element) el.getParentNode();
     }
 
     @Override
-    public void write(short value, String name, short defVal) throws IOException {
-        if (value == defVal) {
-            return;
+    public void write(float value, String name, float defVal) throws IOException {
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
-    public void write(short[] value, String name, short[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (short b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+    public void write(float[] value, String name, float[] defVal) throws IOException {
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
+    }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
+    @Override
+    public void write(float[][] value, String name, float[][] defVal) throws IOException {
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
+        }
     }
 
     @Override
-    public void write(short[][] value, String name, short[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
+    public void write(double value, String name, double defVal) throws IOException {
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
+        }
+    }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
+    @Override
+    public void write(double[] value, String name, double[] defVal) throws IOException {
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
+        }
+    }
 
-        for (int i=0; i<value.length; i++) {
-                short[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
+    @Override
+    public void write(double[][] value, String name, double[][] defVal) throws IOException {
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
         }
-        currentElement = (Element) el.getParentNode();
     }
 
     @Override
     public void write(boolean value, String name, boolean defVal) throws IOException {
-        if (value == defVal) {
-            return;
+        if (value != defVal) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
     }
 
     @Override
     public void write(boolean[] value, String name, boolean[] defVal) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        if (value == null) {
-            value = defVal;
-        }
-        for (boolean b : value) {
-            buf.append(b);
-            buf.append(" ");
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+        if (!Arrays.equals(value, defVal)) {
+            writePrimitiveArrayHelper(value, name);
         }
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
     public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
-
-        for (int i=0; i<value.length; i++) {
-                boolean[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        if (!Arrays.deepEquals(value, defVal)) {
+            writePrimitiveArray2DHelper(value, name);
         }
-        currentElement = (Element) el.getParentNode();
     }
 
     @Override
     public void write(String value, String name, String defVal) throws IOException {
-        if (value == null || value.equals(defVal)) {
-            return;
+        if (value != null && !value.equals(defVal)) {
+            XMLUtils.setAttribute(currentElement, name, value);
         }
-        XMLUtils.setAttribute(currentElement, name, encodeString(value));
     }
 
     @Override
     public void write(String[] value, String name, String[] defVal) throws IOException {
-        Element el = appendElement(name);
-
-        if (value == null) {
-            value = defVal;
+        if (value == null || Arrays.equals(value, defVal)) {
+            return;
         }
 
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
+        appendElement(name);
 
-        for (int i=0; i<value.length; i++) {
-                String b = value[i];
-                appendElement("String_"+i);
-            String val = encodeString(b);
-            XMLUtils.setAttribute(currentElement, "value", val);
-            currentElement = el;
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+
+        for (int i = 0; i < value.length; i++) {
+            appendElement("string_" + i);
+
+            if (value[i] != null) {
+                XMLUtils.setAttribute(currentElement, "value", value[i]);
+            }
+            
+            currentElement = (Element) currentElement.getParentNode();
         }
+
         currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
     public void write(String[][] value, String name, String[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
+        if (value == null || Arrays.deepEquals(value, defVal)) {
+            return;
+        }
+
+        appendElement(name);
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.length));
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
 
-        for (int i=0; i<value.length; i++) {
-                String[] array = value[i];
-                write(array, "array_"+i, defVal==null?null:defVal[i]);
+        for (int i = 0; i < value.length; i++) {
+            String childName = "string_array_" + i;
+
+            if (value[i] != null) {
+                write(value[i], childName, defVal != null ? defVal[i] : null);
+            } else {
+                // empty tag
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
+            }
+        }
+
+        currentElement = (Element) currentElement.getParentNode();
+    }
+
+    @Override
+    public void write(Enum value, String name, Enum defVal) throws IOException {
+        if (value != null && !value.equals(defVal)) {
+            XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
         }
-        currentElement = (Element) el.getParentNode();
     }
 
     @Override
@@ -488,10 +347,10 @@ public class DOMOutputCapsule implements OutputCapsule {
         if (value == null || value.equals(defVal)) {
             return;
         }
+
         StringBuilder buf = new StringBuilder();
-        for (int i = value.nextSetBit(0); i >= 0; i = value.nextSetBit(i + 1)) {
-            buf.append(i);
-            buf.append(" ");
+        for (int i = 0; i < value.size(); i++) {
+            buf.append(value.get(i) ? "1 " : "0 ");
         }
         
         if (buf.length() > 0) {
@@ -500,49 +359,41 @@ public class DOMOutputCapsule implements OutputCapsule {
         }
         
         XMLUtils.setAttribute(currentElement, name, buf.toString());
-
     }
 
     @Override
-    public void write(Savable object, String name, Savable defVal) throws IOException {
-        if (object == null) {
-            return;
-        }
-        if (object.equals(defVal)) {
+    public void write(Savable value, String name, Savable defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
 
         Element old = currentElement;
-        Element el = writtenSavables.get(object);
-
-        String className = null;
-        if(!object.getClass().getName().equals(name)){
-            className = object.getClass().getName();
-        }
-        try {
-            doc.createElement(name);
-        } catch (DOMException e) {
-            // Ridiculous fallback behavior.
-            // Would be far better to throw than to totally disregard the
-            // specified "name" and write a class name instead!
-            // (Besides the fact we are clobbering the managed .getClassTag()).
-            name = "Object";
-            className = object.getClass().getName();
-        }
-
-        if (el != null) {
-            String refID = el.getAttribute("reference_ID");
-            if (refID.length() == 0) {
-                refID = object.getClass().getName() + "@" + object.hashCode();
-                XMLUtils.setAttribute(el, "reference_ID", refID);
+
+        // no longer tries to use class name as element name.  that makes things unnecessarily complicated.
+
+        Element refElement = writtenSavables.get(value);
+        // this object has already been written, so make an element that refers to the existing one.
+        if (refElement != null) {
+            String refID = XMLUtils.getAttribute(FormatVersion.VERSION, refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID);
+
+            // add the reference_ID to the referenced element if it didn't already have it
+            if (refID.isEmpty()) {
+                refID = value.getClass().getName() + "@" + value.hashCode();
+                XMLUtils.setAttribute(refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID, refID);
             }
-            el = appendElement(name);
-            XMLUtils.setAttribute(el, "ref", refID);
+
+            appendElement(name);
+            XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_REFERENCE, refID);
         } else {
-            el = appendElement(name);
+            appendElement(name);
+
+            // this now always writes the class attribute even if the class name is also the element name.
+            // for backwards compatibility, DOMInputCapsule will still try to get the class name from the element name if the
+            // attribute isn't found.
+            XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_CLASS, value.getClass().getName());
             
             // jME3 NEW: Append version number(s)
-            int[] versions = SavableClassUtil.getSavableVersions(object.getClass());
+            int[] versions = SavableClassUtil.getSavableVersions(value.getClass());
             StringBuilder sb = new StringBuilder();
             for (int i = 0; i < versions.length; i++){
                 sb.append(versions[i]);
@@ -550,376 +401,299 @@ public class DOMOutputCapsule implements OutputCapsule {
                     sb.append(", ");
                 }
             }
-            XMLUtils.setAttribute(el, "savable_versions", sb.toString());
+            XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS, sb.toString());
             
-            writtenSavables.put(object, el);
-            object.write(exporter);
-        }
-        if(className != null){
-            XMLUtils.setAttribute(el, "class", className);
+            writtenSavables.put(value, currentElement);
+
+            value.write(exporter);
         }
 
         currentElement = old;
     }
 
     @Override
-    public void write(Savable[] objects, String name, Savable[] defVal) throws IOException {
-        if (objects == null) {
-            return;
-        }
-        if (Arrays.equals(objects, defVal)) {
+    public void write(Savable[] value, String name, Savable[] defVal) throws IOException {
+        if (value == null || Arrays.equals(value, defVal)) {
             return;
         }
 
         Element old = currentElement;
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(objects.length));
-        for (int i = 0; i < objects.length; i++) {
-            Savable o = objects[i];
+        
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+        for (int i = 0; i < value.length; i++) {
+            Savable o = value[i];
+            String elementName = "savable_" + i;
             if(o == null){
-                //renderStateList has special loading code, so we can leave out the null values
+                // renderStateList has special loading code, so we can leave out the null values
                 if(!name.equals("renderStateList")){
-                Element before = currentElement;
-                appendElement("null");
-                currentElement = before;
+                    Element before = currentElement;
+                    appendElement(elementName);
+                    currentElement = before;
                 }
             }else{
-                write(o, o.getClass().getName(), null);
+                write(o, elementName, null);
             }
         }
+
         currentElement = old;
     }
 
     @Override
     public void write(Savable[][] value, String name, Savable[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size_outer", String.valueOf(value.length));
-        XMLUtils.setAttribute(el, "size_inner", String.valueOf(value[0].length));
-        for (Savable[] bs : value) {
-            for(Savable b : bs){
-                write(b, b.getClass().getSimpleName(), null);
-            }
-        }
-        currentElement = (Element) currentElement.getParentNode();
-    }
-
-    @Override
-    public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
-        if (array == null) {
-            return;
-        }
-        if (array.equals(defVal)) {
+        if (value == null || Arrays.deepEquals(value, defVal)) {
             return;
         }
+
         Element old = currentElement;
-        Element el = appendElement(name);
-        currentElement = el;
-        XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
-        for (Object o : array) {
-                if(o == null) {
-                        continue;
-                }
-                else if (o instanceof Savable) {
-                Savable s = (Savable) o;
-                write(s, s.getClass().getName(), null);
+
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+        for (int i = 0; i < value.length; i++) {
+            String childName = "savable_array_" + i;
+            if (value[i] != null) {
+                write(value[i], childName, null);
             } else {
-                throw new ClassCastException("Not a Savable instance: " + o);
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
             }
         }
+
         currentElement = old;
     }
 
     @Override
-    public void writeSavableArrayListArray(ArrayList[] objects, String name, ArrayList[] defVal) throws IOException {
-        if (objects == null) {return;}
-        if (Arrays.equals(objects, defVal)) {return;}
-
-        Element old = currentElement;
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length));
-        for (int i = 0; i < objects.length; i++) {
-            ArrayList o = objects[i];
-            if(o == null){
-                Element before = currentElement;
-                appendElement("null");
-                currentElement = before;
-            }else{
-                StringBuilder buf = new StringBuilder("SavableArrayList_");
-                buf.append(i);
-                writeSavableArrayList(o, buf.toString(), null);
-            }
+    public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
+            return;
         }
-        currentElement = old;
+        
+        int position = value.position();
+        value.rewind();
+        byte[] array = new byte[value.remaining()];
+        value.get(array);
+        value.position(position);
+
+        writePrimitiveArrayHelper(array, name);
     }
 
     @Override
-    public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException {
-        if (value == null) return;
-        if(Arrays.deepEquals(value, defVal)) return;
+    public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
+            return;
+        }
 
-        Element el = appendElement(name);
-        int size = value.length;
-        XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size));
+        int position = value.position();
+        value.rewind();
+        short[] array = new short[value.remaining()];
+        value.get(array);
+        value.position(position);
 
-        for (int i=0; i< size; i++) {
-            ArrayList[] vi = value[i];
-            writeSavableArrayListArray(vi, "SavableArrayListArray_"+i, null);
-        }
-        currentElement = (Element) el.getParentNode();
+        writePrimitiveArrayHelper(array, name);
     }
 
     @Override
-    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array, String name, ArrayList<FloatBuffer> defVal) throws IOException {
-        if (array == null) {
-            return;
-        }
-        if (array.equals(defVal)) {
+    public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
-        for (FloatBuffer o : array) {
-            write(o, XMLExporter.ELEMENT_FLOATBUFFER, null);
-        }
-        currentElement = (Element) el.getParentNode();
+
+        int position = value.position();
+        value.rewind();
+        int[] array = new int[value.remaining()];
+        value.get(array);
+        value.position(position);
+
+        writePrimitiveArrayHelper(array, name);
     }
 
     @Override
-    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException {
-        if (map == null) {
-            return;
-        }
-        if (map.equals(defVal)) {
+    public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
-                Element stringMap = appendElement(name);
 
-                Iterator<? extends Savable> keyIterator = map.keySet().iterator();
-                while(keyIterator.hasNext()) {
-                        Savable key = keyIterator.next();
-                        Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY);
-                        write(key, XMLExporter.ELEMENT_KEY, null);
-                        Savable value = map.get(key);
-                        write(value, XMLExporter.ELEMENT_VALUE, null);
-                        currentElement = stringMap;
-                }
+        int position = value.position();
+        value.rewind();
+        float[] array = new float[value.remaining()];
+        value.get(array);
+        value.position(position);
 
-                currentElement = (Element) stringMap.getParentNode();
+        writePrimitiveArrayHelper(array, name);
     }
 
     @Override
-    public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException {
-        if (map == null) {
-            return;
-        }
-        if (map.equals(defVal)) {
+    public void writeByteBufferArrayList(ArrayList<ByteBuffer> value, String name, ArrayList<ByteBuffer> defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
-                Element stringMap = appendElement(name);
 
-                Iterator<String> keyIterator = map.keySet().iterator();
-                while(keyIterator.hasNext()) {
-                        String key = keyIterator.next();
-                        Element mapEntry = appendElement("MapEntry");
-                        XMLUtils.setAttribute(mapEntry, "key", key);
-                        Savable s = map.get(key);
-                        write(s, "Savable", null);
-                        currentElement = stringMap;
-                }
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size()));
+        for (int i = 0; i < value.size(); i++) {
+            String childName = "byte_buffer_" + i;
+            if (value.get(i) != null) {
+                write(value.get(i), childName, null);
+            } else {
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
+            }
+        }
 
-                currentElement = (Element) stringMap.getParentNode();
+        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException {
-        if (map == null) {
-            return;
-        }
-        if (map.equals(defVal)) {
+    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> value, String name, ArrayList<FloatBuffer> defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
-                Element stringMap = appendElement(name);
 
-                for(Entry<? extends Savable> entry : map) {
-                        int key = entry.getKey();
-                        Element mapEntry = appendElement("MapEntry");
-                        XMLUtils.setAttribute(mapEntry, "key", Integer.toString(key));
-                        Savable s = entry.getValue();
-                        write(s, "Savable", null);
-                        currentElement = stringMap;
-                }
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size()));
+        for (int i = 0; i < value.size(); i++) {
+            String childName = "float_buffer_" + i;
+            if (value.get(i) != null) {
+                write(value.get(i), childName, null);
+            } else {
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
+            }
+        }
 
-                currentElement = (Element) stringMap.getParentNode();
+        currentElement = (Element) currentElement.getParentNode();
     }
 
     @Override
-    public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException {
-        if (value == null) {
+    public void writeSavableArrayList(ArrayList value, String name, ArrayList defVal) throws IOException {
+        if (value == null || value.equals(defVal)) {
             return;
         }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.limit()));
-        StringBuilder buf = new StringBuilder();
-        int pos = value.position();
-        value.rewind();
-        int ctr = 0;
-        while (value.hasRemaining()) {
-            ctr++;
-            buf.append(value.get());
-            buf.append(" ");
-        }
-        if (ctr != value.limit()) {
-            throw new IOException("'" + name
-                + "' buffer contention resulted in write data consistency.  "
-                + ctr + " values written when should have written "
-                + value.limit());
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+        Savable[] savableArray = new Savable[value.size()];
+        for (int i = 0; i < value.size(); i++) {
+            Object o = value.get(i);
+
+            if (o != null && !(o instanceof Savable)) {
+                throw new IOException(new ClassCastException("Not a Savable instance: " + o));
+            }
+
+            savableArray[i] = (Savable) o;
         }
-        
-        value.position(pos);
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) el.getParentNode();
+
+        write(savableArray, name, null);
     }
 
     @Override
-    public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException {
-        if (value == null) {
-            return;
-        }
-        if (value.equals(defVal)) {
+    public void writeSavableArrayListArray(ArrayList[] value, String name, ArrayList[] defVal) throws IOException {
+        if (value == null || Arrays.equals(value, defVal)) {
             return;
         }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.limit()));
-        StringBuilder buf = new StringBuilder();
-        int pos = value.position();
-        value.rewind();
-        int ctr = 0;
-        while (value.hasRemaining()) {
-            ctr++;
-            buf.append(value.get());
-            buf.append(" ");
-        }
-        if (ctr != value.limit()) {
-            throw new IOException("'" + name
-                + "' buffer contention resulted in write data consistency.  "
-                + ctr + " values written when should have written "
-                + value.limit());
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+        Element old = currentElement;
+
+        appendElement(name);
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+        for (int i = 0; i < value.length; i++) {
+            String childName = "savable_list_" + i;
+            if(value[i] != null){
+                writeSavableArrayList(value[i], childName, null);
+            }else{
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
+            }
         }
-        value.position(pos);
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) el.getParentNode();
+
+        currentElement = old;
     }
 
     @Override
-    public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException {
-        if (value == null) return;
-        if (value.equals(defVal)) return;
-
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.limit()));
-        StringBuilder buf = new StringBuilder();
-        int pos = value.position();
-        value.rewind();
-        int ctr = 0;
-        while (value.hasRemaining()) {
-            ctr++;
-            buf.append(value.get());
-            buf.append(" ");
-        }
-        if (ctr != value.limit()) {
-            throw new IOException("'" + name
-                + "' buffer contention resulted in write data consistency.  "
-                + ctr + " values written when should have written "
-                + value.limit());
+    public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException {
+        if (value == null || Arrays.equals(value, defVal)) {
+            return;
         }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+
+        Element old = currentElement;
+
+        appendElement(name);
+
+        XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length));
+        for (int i = 0; i < value.length; i++) {
+            String childName = "savable_list_array_" + i;
+            if(value[i] != null){
+                writeSavableArrayListArray(value[i], childName, null);
+            }else{
+                appendElement(childName);
+                currentElement = (Element) currentElement.getParentNode();
+            }
         }
-        
-        value.position(pos);
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) el.getParentNode();
+
+        currentElement = old;
     }
 
     @Override
-    public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException {
-        if (value == null) {
-            return;
-        }
-        if (value.equals(defVal)) {
+    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+        if (map == null || map.equals(defVal)) {
             return;
         }
 
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(value.limit()));
-        StringBuilder buf = new StringBuilder();
-        int pos = value.position();
-        value.rewind();
-        int ctr = 0;
-        while (value.hasRemaining()) {
-            ctr++;
-            buf.append(value.get());
-            buf.append(" ");
-        }
-        if (ctr != value.limit()) {
-            throw new IOException("'" + name
-                + "' buffer contention resulted in write data consistency.  "
-                + ctr + " values written when should have written "
-                + value.limit());
-        }
-        
-        if (buf.length() > 0) {
-            //remove last space
-            buf.setLength(buf.length() - 1);
+        Element stringMap = appendElement(name);
+
+        Iterator<? extends Savable> keyIterator = map.keySet().iterator();
+        while(keyIterator.hasNext()) {
+            Savable key = keyIterator.next();
+            Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY);
+            write(key, XMLExporter.ELEMENT_KEY, null);
+            Savable value = map.get(key);
+            write(value, XMLExporter.ELEMENT_VALUE, null);
+            currentElement = stringMap;
         }
-        
-        value.position(pos);
-        XMLUtils.setAttribute(el, dataAttributeName, buf.toString());
-        currentElement = (Element) el.getParentNode();
+
+        currentElement = (Element) stringMap.getParentNode();
     }
 
     @Override
-        public void write(Enum value, String name, Enum defVal) throws IOException {
-        if (value == defVal) {
+    public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException {
+        if (map == null || map.equals(defVal)) {
             return;
         }
-        XMLUtils.setAttribute(currentElement, name, String.valueOf(value));
 
+        Element stringMap = appendElement(name);
+
+        Iterator<String> keyIterator = map.keySet().iterator();
+        while(keyIterator.hasNext()) {
+            String key = keyIterator.next();
+            Element mapEntry = appendElement("MapEntry");
+            XMLUtils.setAttribute(mapEntry, "key", key);
+            Savable s = map.get(key);
+            write(s, "Savable", null);
+            currentElement = stringMap;
         }
 
+        currentElement = (Element) stringMap.getParentNode();
+    }
+
     @Override
-        public void writeByteBufferArrayList(ArrayList<ByteBuffer> array,
-                        String name, ArrayList<ByteBuffer> defVal) throws IOException {
-        if (array == null) {
-            return;
-        }
-        if (array.equals(defVal)) {
+    public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException {
+        if (map == null || map.equals(defVal)) {
             return;
         }
-        Element el = appendElement(name);
-        XMLUtils.setAttribute(el, "size", String.valueOf(array.size()));
-        for (ByteBuffer o : array) {
-            write(o, "ByteBuffer", null);
-        }
-        currentElement = (Element) el.getParentNode();
 
+        Element stringMap = appendElement(name);
+
+        for(Entry<? extends Savable> entry : map) {
+            int key = entry.getKey();
+            Element mapEntry = appendElement("MapEntry");
+            XMLUtils.setAttribute(mapEntry, "key", Integer.toString(key));
+            Savable s = entry.getValue();
+            write(s, "Savable", null);
+            currentElement = stringMap;
         }
-    
+
+        currentElement = (Element) stringMap.getParentNode();
+    }
 }

+ 2 - 0
jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java

@@ -46,7 +46,9 @@ import org.w3c.dom.*;
  * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book.
  * @author Doug Daniels (dougnukem) - adjustments for XML formatting
  * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $
+ * @deprecated This class was only used in XMLExporter and has been replaced by javax.xml.transform.Transformer
  */
+@Deprecated
 public class DOMSerializer {
 
     /** The encoding to use for output (default is UTF-8) */

+ 52 - 10
jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java

@@ -39,8 +39,15 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import org.w3c.dom.Document;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
 
 /**
  * Part of the jME XML IO system as introduced in the Google Code jmexml project.
@@ -53,27 +60,54 @@ public class XMLExporter implements JmeExporter {
     public static final String ELEMENT_MAPENTRY = "MapEntry";
     public static final String ELEMENT_KEY = "Key";
     public static final String ELEMENT_VALUE = "Value";
-    public static final String ELEMENT_FLOATBUFFER = "FloatBuffer";
     public static final String ATTRIBUTE_SIZE = "size";
+    public static final String ATTRIBUTE_DATA = "data";
+    public static final String ATTRIBUTE_CLASS = "class";
+    public static final String ATTRIBUTE_REFERENCE_ID = "reference_ID";
+    public static final String ATTRIBUTE_REFERENCE = "ref";
+    public static final String ATTRIBUTE_SAVABLE_VERSIONS = "savable_versions";
 
     private DOMOutputCapsule domOut;
+
+    private int indentSpaces = 4;
     
     public XMLExporter() {
        
     }
 
     @Override
-    public void save(Savable object, OutputStream f) throws IOException {
+    public void save(Savable object, OutputStream outputStream) throws IOException {
+        Document document = null;
         try {
-            // Initialize the Document when saving, so we don't retain state of previous exports.
-            this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this);
-            domOut.write(object, object.getClass().getName(), null);
-            DOMSerializer serializer = new DOMSerializer();
-            serializer.serialize(domOut.getDoc(), f);
-            f.flush();
+            document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
         } catch (ParserConfigurationException ex) {
             throw new IOException(ex);
         }
+        document.setXmlStandalone(true);    // for some reason the transformer output property below doesn't work unless the document is set to standalone
+
+        // Initialize the DOMOutputCapsule when saving, so we don't retain state of previous exports.
+        domOut = new DOMOutputCapsule(document, this);
+
+        domOut.write(object, "savable", null);
+
+        DOMSource source = new DOMSource(domOut.getDoc());
+        StreamResult result = new StreamResult(outputStream);
+
+        try {
+            TransformerFactory tfFactory = TransformerFactory.newInstance();
+            tfFactory.setAttribute("indent-number", indentSpaces);
+
+            Transformer transformer = tfFactory.newTransformer();
+            transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
+
+            if (indentSpaces > 0) {
+                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            }
+
+            transformer.transform(source, result);
+        } catch (TransformerException ex) {
+            throw new IOException(ex);
+        }
     }
 
     @Override
@@ -96,8 +130,16 @@ public class XMLExporter implements JmeExporter {
         return domOut;
     }
 
+    /**
+     * Sets the number of spaces used to indent nested tags.
+     * @param indentSpaces The number of spaces to indent for each level of nesting.  Default is 4.
+     * Pass 0 to disable pretty printing and write document without adding any whitespace.
+     */
+    public void setIndentSpaces(int indentSpaces) {
+        this.indentSpaces = indentSpaces;
+    }
+
     public static XMLExporter getInstance() {
-            return new XMLExporter();
+        return new XMLExporter();
     }
-    
 }

+ 2 - 2
jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java

@@ -80,8 +80,9 @@ public class XMLImporter implements JmeImporter {
         try {
             return load(in);
         } finally {
-            if (in != null)
+            if (in != null) {
                 in.close();
+            }
         }
     }
     
@@ -115,5 +116,4 @@ public class XMLImporter implements JmeImporter {
     public static XMLImporter getInstance() {
         return new XMLImporter();
     }
-
 }

+ 11 - 3
jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java

@@ -31,7 +31,9 @@
  */
 package com.jme3.export.xml;
 
+import java.io.IOException;
 import org.w3c.dom.Element;
+import org.w3c.dom.DOMException;
 
 /**
  * Utilities for reading and writing XML files.
@@ -58,9 +60,16 @@ public class XMLUtils {
      * @param element element to set the attribute in
      * @param name name of the attribute (without prefix)
      * @param attribute attribute to save
+     * 
+     * @throws java.io.IOException wraps DOMException in IOException for convenience since everywhere this method
+     * is used is expected to throw only IOException.
      */
-    public static void setAttribute(Element element, String name, String attribute) {
-        element.setAttribute(PREFIX+name, attribute);
+    public static void setAttribute(Element element, String name, String attribute) throws IOException {
+        try {
+            element.setAttribute(PREFIX+name, attribute);
+        } catch (DOMException domEx) {
+            throw new IOException(domEx);
+        }
     }
     
     /**
@@ -106,5 +115,4 @@ public class XMLUtils {
      */
     private XMLUtils() {
     }
-    
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików