diff --git a/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java b/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java new file mode 100644 index 0000000000..74edd66d5a --- /dev/null +++ b/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 exporters = new ArrayList<>(); + private static final List 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 こんにちは 你好 Здравствуйте 안녕하세요 🙋", + "' " < > &", // xml entities + // xml metacharacters + "\'", + "\"", + "<", + ">", + "&", + "", + "]]>" // 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 testByteBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createByteBuffer(testByteArray2D[0]), + null, + BufferUtils.createByteBuffer(testByteArray2D[2]) + )); + + private static final ArrayList testFloatBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createFloatBuffer(testFloatArray2D[0]), + null, + BufferUtils.createFloatBuffer(testFloatArray2D[2]) + )); + + private static final ArrayList testSavableArrayList = new ArrayList<>(Arrays.asList(testSavableArray)); + + @SuppressWarnings("unchecked") + private static final ArrayList[] testSavableArrayListArray = new ArrayList[] { + testSavableArrayList, + null, + new ArrayList() + }; + + // "array" and "list" don't sound like real words anymore + @SuppressWarnings("unchecked") + private static final ArrayList[][] testSavableArrayListArray2D = new ArrayList[][] { + testSavableArrayListArray, + null, + {}, + }; + + private static final Map testSavableMap = new HashMap() {{ + put(Vector3f.UNIT_X, Vector3f.UNIT_X); + put(Vector3f.UNIT_Y, Quaternion.IDENTITY); + put(Vector3f.UNIT_Z, null); + }}; + + private static final Map testStringSavableMap = new HashMap() {{ + put("v", Vector3f.UNIT_X); + put("q", Quaternion.IDENTITY); + put("n", null); + }}; + + private static final IntMap testIntSavableMap = new IntMap(); + 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())); + } + } + } +} diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java index 5e2466a6bb..1d91ca903b 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -55,11 +55,10 @@ * @author blaine */ public class DOMInputCapsule implements InputCapsule { - private static final Logger logger = - Logger.getLogger(DOMInputCapsule.class .getName()); + private static final Logger logger = Logger.getLogger(DOMInputCapsule.class .getName()); private Document doc; - private Element currentElem; + private Element currentElement; private XMLImporter importer; private boolean isAtRoot = true; private Map referencedSavables = new HashMap<>(); @@ -70,1370 +69,862 @@ public class DOMInputCapsule implements InputCapsule { public DOMInputCapsule(Document doc, XMLImporter importer) { this.doc = doc; this.importer = importer; - currentElem = doc.getDocumentElement(); + currentElement = doc.getDocumentElement(); // file version is always unprefixed for backwards compatibility - String version = currentElem.getAttribute("format_version"); + String version = currentElement.getAttribute("format_version"); importer.formatVersion = version.equals("") ? 0 : Integer.parseInt(version); } @Override public int getSavableVersion(Class desiredClass) { if (classHierarchyVersions != null){ - return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, - classHierarchyVersions, importer.getFormatVersion()); + return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, classHierarchyVersions, importer.getFormatVersion()); }else{ return 0; } } - - private static String decodeString(String s) { - if (s == null) { + + private Element findChildElement(String name) { + if (currentElement == null) { return null; } - s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); - return s; - } - - private Element findFirstChildElement(Element parent) { - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element))) { + Node ret = currentElement.getFirstChild(); + while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { ret = ret.getNextSibling(); } return (Element) ret; } - private Element findChildElement(Element parent, String name) { - if (parent == null) { + // helper method to reduce duplicate code. checks that number of tokens in the "data" attribute matches the "size" attribute + // and returns an array of parsed primitives. + private Object readPrimitiveArrayHelper(Element element, String primType) throws IOException { + if (element == null) { return null; } - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { - ret = ret.getNextSibling(); + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; } - return (Element) ret; - } - private Element findNextSiblingElement(Element current) { - Node ret = current.getNextSibling(); - while (ret != null) { - if (ret instanceof Element) { - return (Element) ret; + String[] tokens = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_DATA)); + + if(!sizeString.isEmpty()) { + try { + int requiredSize = Integer.parseInt(sizeString); + if (tokens.length != requiredSize) { + throw new IOException("Wrong token count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + tokens.length); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + } + + try { + switch (primType) { + case "byte": + byte[] byteArray = new byte[tokens.length]; + for (int i = 0; i < tokens.length; i++) byteArray[i] = Byte.parseByte(tokens[i]); + return byteArray; + case "short": + short[] shortArray = new short[tokens.length]; + for (int i = 0; i < tokens.length; i++) shortArray[i] = Short.parseShort(tokens[i]); + return shortArray; + case "int": + int[] intArray = new int[tokens.length]; + for (int i = 0; i < tokens.length; i++) intArray[i] = Integer.parseInt(tokens[i]); + return intArray; + case "long": + long[] longArray = new long[tokens.length]; + for (int i = 0; i < tokens.length; i++) longArray[i] = Long.parseLong(tokens[i]); + return longArray; + case "float": + float[] floatArray = new float[tokens.length]; + for (int i = 0; i < tokens.length; i++) floatArray[i] = Float.parseFloat(tokens[i]); + return floatArray; + case "double": + double[] doubleArray = new double[tokens.length]; + for (int i = 0; i < tokens.length; i++) doubleArray[i] = Double.parseDouble(tokens[i]); + return doubleArray; + case "boolean": + boolean[] booleanArray = new boolean[tokens.length]; + for (int i = 0; i < tokens.length; i++) booleanArray[i] = Boolean.parseBoolean(tokens[i]); + return booleanArray; + default: + throw new IOException(); // will never happen + } + } catch(NumberFormatException nfe) { + throw new IOException(nfe); + } + } + + // helper method to reduce duplicate code. checks that number of child elements matches the "size" attribute + // and returns a convenient list of child elements. + private List getObjectArrayElements(Element element) throws IOException { + if (element == null) { + return null; + } + + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; + } + + NodeList nodes = element.getChildNodes(); + + List elements = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node instanceof Element) { + elements.add((Element) node); } - ret = ret.getNextSibling(); } - return null; + + try { + int requiredSize = Integer.parseInt(sizeString); + if (elements.size() != requiredSize) { + throw new IOException("DOMInputCapsule.getObjectArrayElements(): Wrong element count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + elements.size()); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + + return elements; } @Override public byte readByte(String name, byte defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Byte.parseByte(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Byte.parseByte(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override public byte[] readByteArray(String name, byte[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bytes for '" + name - + "'. size says " + requiredSize - + ", data contains " - + strings.length); - } - byte[] tmp = new byte[strings.length]; - for (int i = 0; i < strings.length; i++) { - tmp[i] = Byte.parseByte(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + byte[] array = (byte[]) readPrimitiveArrayHelper(findChildElement(name), "byte"); + return array != null ? array : defVal; } @Override public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List byteArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - byteArrays.add(readByteArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (byteArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + byteArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return byteArrays.toArray(new byte[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + byte[][] arrays = new byte[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (byte[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "byte"); } + + return arrays; } @Override - public int readInt(String name, int defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Integer.parseInt(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public short readShort(String name, short defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } - } - @Override - public int[] readIntArray(String name, int[] defVal) throws IOException { try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of ints for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - int[] tmp = new int[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Integer.parseInt(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Short.parseShort(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - - + public short[] readShortArray(String name, short[] defVal) throws IOException { + short[] array = (short[]) readPrimitiveArrayHelper(findChildElement(name), "short"); + return array != null ? array : defVal; + } + @Override + public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - NodeList nodes = currentElem.getChildNodes(); - List intArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - intArrays.add(readIntArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (intArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + intArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return intArrays.toArray(new int[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + short[][] arrays = new short[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (short[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "short"); } + + return arrays; } @Override - public float readFloat(String name, float defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public int readInt(String name, int defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Float.parseFloat(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Integer.parseInt(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public float[] readFloatArray(String name, float[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of floats for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - float[] tmp = new float[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Float.parseFloat(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + public int[] readIntArray(String name, int[] defVal) throws IOException { + int[] array = (int[]) readPrimitiveArrayHelper(findChildElement(name), "int"); + return array != null ? array : defVal; } @Override - public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { - /* Why does this one method ignore the 'size attr.? */ - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - int size_outer = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); - int size_inner = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); + public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - float[][] tmp = new float[size_outer][size_inner]; + if (arrayEntryElements == null) { + return defVal; + } - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - for (int i = 0; i < size_outer; i++) { - tmp[i] = new float[size_inner]; - for (int k = 0; k < size_inner; k++) { - tmp[i][k] = Float.parseFloat(strings[i]); - } - } - return tmp; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + int[][] arrays = new int[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (int[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "int"); } + + return arrays; } @Override - public double readDouble(String name, double defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public long readLong(String name, long defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Double.parseDouble(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Long.parseLong(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public double[] readDoubleArray(String name, double[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of doubles for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - double[] tmp = new double[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Double.parseDouble(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public long[] readLongArray(String name, long[] defVal) throws IOException { + long[] array = (long[]) readPrimitiveArrayHelper(findChildElement(name), "long"); + return array != null ? array : defVal; } @Override - public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List doubleArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - doubleArrays.add(readDoubleArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (doubleArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + doubleArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return doubleArrays.toArray(new double[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + long[][] arrays = new long[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (long[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "long"); } + + return arrays; } @Override - public long readLong(String name, long defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public float readFloat(String name, float defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Long.parseLong(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Float.parseFloat(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public long[] readLongArray(String name, long[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of longs for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - long[] tmp = new long[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Long.parseLong(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public float[] readFloatArray(String name, float[] defVal) throws IOException { + float[] array = (float[]) readPrimitiveArrayHelper(findChildElement(name), "float"); + return array != null ? array : defVal; } @Override - public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List longArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - longArrays.add(readLongArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (longArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + longArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return longArrays.toArray(new long[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + float[][] arrays = new float[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (float[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "float"); } + + return arrays; } @Override - public short readShort(String name, short defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public double readDouble(String name, double defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Short.parseShort(tmpString); - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Double.parseDouble(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } } @Override - public short[] readShortArray(String name, short[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of shorts for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - short[] tmp = new short[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Short.parseShort(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + public double[] readDoubleArray(String name, double[] defVal) throws IOException { + double[] array = (double[]) readPrimitiveArrayHelper(findChildElement(name), "double"); + return array != null ? array : defVal; } @Override - public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List shortArrays = new ArrayList<>(); + if (arrayEntryElements == null) { + return defVal; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - shortArrays.add(readShortArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (shortArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + shortArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return shortArrays.toArray(new short[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + double[][] arrays = new double[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (double[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "double"); } + + return arrays; } @Override public boolean readBoolean(String name, boolean defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Boolean.parseBoolean(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } + + //parseBoolean doesn't throw anything, just returns false if the string isn't "true", ignoring case. + return Boolean.parseBoolean(attribute); } @Override public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bools for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - boolean[] tmp = new boolean[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Boolean.parseBoolean(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + boolean[] array = (boolean[]) readPrimitiveArrayHelper(findChildElement(name), "boolean"); + return array != null ? array : defVal; } @Override public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List booleanArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - booleanArrays.add(readBooleanArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (booleanArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + booleanArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return booleanArrays.toArray(new boolean[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + boolean[][] arrays = new boolean[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (boolean[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "boolean"); } + + return arrays; } @Override public String readString(String name, String defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return decodeString(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = null; + + // Element.getAttribute() returns an empty string if the specified attribute does not exist. + // see https://www.w3.org/2003/01/dom2-javadoc/org/w3c/dom/Element.html#getAttribute_java.lang.String_ + // somewhat confusing since the w3c JS api equivalent returns null as one would expect. + // https://www.w3schools.com/jsref/met_element_getattribute.asp + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, name)) { + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); } + + if (attribute == null) { + return defVal; + } + + return attribute; } @Override public String[] readStringArray(String name, String[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = tmpEl.getChildNodes(); - List strings = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("String")) { - // Very unsafe assumption - strings.add(((Element) n).getAttributeNode("value").getValue()); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + strings.size()); - } - return strings.toArray(new String[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + List arrayElements = getObjectArrayElements(findChildElement(name)); + + if (arrayElements == null) { + return defVal; } + + Element oldElement = currentElement; + + String[] array = new String[arrayElements.size()]; + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + array[i] = readString("value", null); + } + + currentElement = oldElement; + + return array; } @Override public String[][] readStringArray2D(String name, String[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - NodeList nodes = currentElem.getChildNodes(); - List stringArrays = new ArrayList<>(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - stringArrays.add(readStringArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (stringArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + stringArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return stringArrays.toArray(new String[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + Element outerArrayElement = findChildElement(name); + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + if (innerArrayElements == null) { + return defVal; } + + currentElement = outerArrayElement; + + String[][] arrays = new String[innerArrayElements.size()][]; + for (int i = 0; i < innerArrayElements.size(); i++) { + arrays[i] = readStringArray(innerArrayElements.get(i).getTagName(), null); + } + + currentElement = (Element) currentElement.getParentNode(); + + return arrays; } @Override - public BitSet readBitSet(String name, BitSet defVal) throws IOException { - String tmpString = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (tmpString == null || tmpString.length() < 1) return defVal; + public > T readEnum(String name, Class enumType, T defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - BitSet set = new BitSet(); - String[] strings = parseTokens(tmpString); - for (int i = 0; i < strings.length; i++) { - int isSet = Integer.parseInt(strings[i]); - if (isSet == 1) { - set.set(i); - } - } - return set; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + return Enum.valueOf(enumType, attribute); + } catch (IllegalArgumentException | NullPointerException e) { + throw new IOException(e); } } @Override - public Savable readSavable(String name, Savable defVal) throws IOException { - Savable ret = defVal; - if (name != null && name.equals("")) - logger.warning("Reading Savable String with name \"\"?"); - try { - Element tmpEl = null; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - } else if (isAtRoot) { - tmpEl = doc.getDocumentElement(); - isAtRoot = false; - } else { - tmpEl = findFirstChildElement(currentElem); - } - currentElem = tmpEl; - ret = readSavableFromCurrentElem(defVal); - if (currentElem.getParentNode() instanceof Element) { - currentElem = (Element) currentElem.getParentNode(); - } else { - currentElem = null; + public BitSet readBitSet(String name, BitSet defVal) throws IOException { + String attribute = null; + + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute == null || attribute.isEmpty()) { + return defVal; + } + + String[] strings = parseTokens(attribute); + BitSet bitSet = new BitSet(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].equals("1")) { + bitSet.set(i); } - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } - return ret; - } - private Savable readSavableFromCurrentElem(Savable defVal) throws - InstantiationException, ClassNotFoundException, - NoSuchMethodException, InvocationTargetException, - IOException, IllegalAccessException { - Savable ret = defVal; - Savable tmp = null; + return bitSet; + } - if (currentElem == null || currentElem.getNodeName().equals("null")) { - return null; + private Savable readSavableFromCurrentElement(Savable defVal) throws IOException { + if (currentElement == null || !currentElement.hasAttributes()) { + return defVal; } - String reference = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "ref"); - if (reference.length() > 0) { - ret = referencedSavables.get(reference); + + String reference = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE); + if (!reference.isEmpty()) { + return referencedSavables.get(reference); } else { - String className = currentElem.getNodeName(); - if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElem, "class")) { - className = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "class"); + // for backwards compatibility with old XML files. previous version of DOMOutputCapsule would only write the class attribute + // if the element name wasn't the class name. + String className = currentElement.getNodeName(); + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS)) { + className = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS); } else if (defVal != null) { className = defVal.getClass().getName(); } - tmp = SavableClassUtil.fromName(className); + + Savable tmp = null; + try { + tmp = SavableClassUtil.fromName(className); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new IOException(e); + } - String versionsStr = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "savable_versions"); + String versionsStr = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS); if (versionsStr != null && !versionsStr.equals("")){ String[] versionStr = versionsStr.split(","); classHierarchyVersions = new int[versionStr.length]; - for (int i = 0; i < classHierarchyVersions.length; i++){ - classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + try { + for (int i = 0; i < classHierarchyVersions.length; i++) { + classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + } + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } }else{ classHierarchyVersions = null; } - String refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "reference_ID"); - if (refID.length() < 1) refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "id"); - if (refID.length() > 0) referencedSavables.put(refID, tmp); + String refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE_ID); + + // what does this line do? guessing backwards compatibility? + if (refID.isEmpty()) refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "id"); + + if (!refID.isEmpty()) referencedSavables.put(refID, tmp); + if (tmp != null) { // Allows reading versions from this savable savable = tmp; + tmp.read(importer); - ret = tmp; + + return tmp; + } else { + return defVal; } } + } + + @Override + public Savable readSavable(String name, Savable defVal) throws IOException { + Savable ret = defVal; + + if (name != null && name.equals("")) + logger.warning("Reading Savable String with name \"\"?"); + + Element old = currentElement; + + if (isAtRoot) { + currentElement = doc.getDocumentElement(); + isAtRoot = false; + } else { + currentElement = findChildElement(name); + } + + ret = readSavableFromCurrentElement(defVal); + + currentElement = old; + return ret; } @Override public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException { - Savable[] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element arrayElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - List savables = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException("Wrong number of Savables for '" - + name + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - ret = savables.toArray(new Savable[0]); - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + if (arrayElement == null || !arrayElement.hasAttributes()) { + return defVal; + } + + List arrayElements = getObjectArrayElements(arrayElement); + + Savable[] savableArray = new Savable[arrayElements.size()]; + + Element old = currentElement; + + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + savableArray[i] = readSavableFromCurrentElement(null); } + + currentElement = old; + + return savableArray; } @Override public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { - Savable[][] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element outerArrayElement = findChildElement(name); - int size_outer = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); - int size_inner = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size_outer")); + if (outerArrayElement == null || !outerArrayElement.hasAttributes()) { + return defVal; + } - Savable[][] tmp = new Savable[size_outer][size_inner]; - currentElem = findFirstChildElement(tmpEl); - for (int i = 0; i < size_outer; i++) { - for (int j = 0; j < size_inner; j++) { - tmp[i][j] = (readSavableFromCurrentElem(null)); - if (i == size_outer - 1 && j == size_inner - 1) { - break; - } - currentElem = findNextSiblingElement(currentElem); - } + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + Savable[][] savableArray2D = new Savable[innerArrayElements.size()][]; + + Element old = currentElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + List savableElements = getObjectArrayElements(innerArrayElements.get(i)); + + if (savableElements == null) { + continue; + } + + savableArray2D[i] = new Savable[savableElements.size()]; + for (int j = 0; j < savableElements.size(); j++) { + currentElement = savableElements.get(j); + savableArray2D[i][j] = readSavableFromCurrentElement(null); } - ret = tmp; - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = old; + + return savableArray2D; } @Override - @SuppressWarnings("unchecked") - public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList savables = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException( - "Wrong number of Savable arrays for '" + name - + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return savables; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + byte[] array = (byte[]) readPrimitiveArrayHelper(element, "byte"); + + if (array == null) { + return defVal; } + + return (ByteBuffer) BufferUtils.createByteBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[] readSavableArrayListArray( - String name, ArrayList[] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - int requiredSize = (sizeString.length() > 0) - ? Integer.parseInt(sizeString) - : -1; - - ArrayList sal; - List> savableArrayLists = - new ArrayList<>(); - int i = -1; - while (true) { - sal = readSavableArrayList("SavableArrayList_" + ++i, null); - if (sal == null && savableArrayLists.size() >= requiredSize) - break; - savableArrayLists.add(sal); - } + public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { + Element element = findChildElement(name); + + short[] array = (short[]) readPrimitiveArrayHelper(element, "short"); - if (requiredSize > -1 && savableArrayLists.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + savableArrayLists.size()); - currentElem = (Element) tmpEl.getParentNode(); - return savableArrayLists.toArray(new ArrayList[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + if (array == null) { + return defVal; } + + return (ShortBuffer) BufferUtils.createShortBuffer(array.length).put(array).rewind(); } @Override - @SuppressWarnings("unchecked") - public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - - ArrayList[] arr; - List[]> sall = new ArrayList<>(); - int i = -1; - while ((arr = readSavableArrayListArray( - "SavableArrayListArray_" + ++i, null)) != null) sall.add(arr); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (sall.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + sall.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return sall.toArray(new ArrayList[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { + Element element = findChildElement(name); + + int[] array = (int[]) readPrimitiveArrayHelper(element, "int"); + + if (array == null) { + return defVal; } + + return (IntBuffer) BufferUtils.createIntBuffer(array.length).put(array).rewind(); } @Override - public ArrayList readFloatBufferArrayList( - String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList tmp = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readFloatBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + tmp.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; + float[] array = (float[]) readPrimitiveArrayHelper(element, "float"); + + if (array == null) { + return defVal; } + + return (FloatBuffer) BufferUtils.createFloatBuffer(array.length).put(array).rewind(); } @Override - public Map readSavableMap(String name, Map defVal) throws IOException { - Map ret; - Element tempEl; + public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { + byte[][] byteArray2D = readByteArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; + if (byteArray2D == null) { + return defVal; } - ret = new HashMap(); - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); - Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); - ret.put(key, val); - } + ArrayList byteBufferList = new ArrayList<>(byteArray2D.length); + for (byte[] byteArray : byteArray2D) { + if (byteArray == null) { + byteBufferList.add(null); + } else { + byteBufferList.add((ByteBuffer) BufferUtils.createByteBuffer(byteArray.length).put(byteArray).rewind()); + } } - currentElem = (Element) tempEl.getParentNode(); - return ret; + + return byteBufferList; } @Override - public Map readStringSavableMap(String name, Map defVal) throws IOException { - Map ret = null; - Element tempEl; + public ArrayList readFloatBufferArrayList(String name, ArrayList defVal) throws IOException { + float[][] floatArray2D = readFloatArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new HashMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "key"); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; + if (floatArray2D == null) { + return defVal; + } + + ArrayList floatBufferList = new ArrayList<>(floatArray2D.length); + for (float[] floatArray : floatArray2D) { + if (floatArray == null) { + floatBufferList.add(null); + } else { + floatBufferList.add((FloatBuffer) BufferUtils.createFloatBuffer(floatArray.length).put(floatArray).rewind()); } - currentElem = (Element) tempEl.getParentNode(); - return ret; + } + + return floatBufferList; } @Override - public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { - IntMap ret = null; - Element tempEl; + @SuppressWarnings("unchecked") + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { + Savable[] savableArray = readSavableArray(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new IntMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, "key")); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; - } - currentElem = (Element) tempEl.getParentNode(); - return ret; + if (savableArray == null) { + return defVal; + } + + return new ArrayList(Arrays.asList(savableArray)); } - /** - * reads from currentElem if name is null - */ @Override - public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of float buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + @SuppressWarnings("unchecked") + public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException { + Savable[][] savableArray2D = readSavableArray2D(name, null); + + if (savableArray2D == null) { + return defVal; + } + + ArrayList[] savableArrayListArray = new ArrayList[savableArray2D.length]; + for (int i = 0; i < savableArray2D.length; i++) { + if (savableArray2D[i] != null) { + savableArrayListArray[i] = new ArrayList(Arrays.asList(savableArray2D[i])); } - FloatBuffer tmp = BufferUtils.createFloatBuffer(strings.length); - for (String s : strings) tmp.put(Float.parseFloat(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + return savableArrayListArray; } @Override - public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @SuppressWarnings("unchecked") + public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { + Element outerArrayElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of int buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (outerArrayElement == null) { + return defVal; + } + + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + ArrayList[][] savableArrayListArray2D = new ArrayList[innerArrayElements.size()][]; + + Element old = currentElement; + currentElement = outerArrayElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + if (innerArrayElements.get(i) != null) { + + savableArrayListArray2D[i] = readSavableArrayListArray(innerArrayElements.get(i).getTagName(), null); } - IntBuffer tmp = BufferUtils.createIntBuffer(strings.length); - for (String s : strings) tmp.put(Integer.parseInt(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = old; + + return savableArrayListArray2D; } @Override - public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of byte buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); + Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); + ret.put(key, val); } - ByteBuffer tmp = BufferUtils.createByteBuffer(strings.length); - for (String s : strings) tmp.put(Byte.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public Map readStringSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - String[] strings = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key"); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - ShortBuffer tmp = BufferUtils.createShortBuffer(strings.length); - for (String s : strings) tmp.put(Short.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; } @Override - public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), tmpEl, "size"); - ArrayList tmp = new ArrayList<>(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readByteBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + tmp.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException | DOMException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } + if (mapElement == null) { + return defVal; } - @Override - public > T readEnum(String name, Class enumType, - T defVal) throws IOException { - T ret = defVal; - try { - String eVal = XMLUtils.getAttribute(importer.getFormatVersion(), currentElem, name); - if (eVal != null && eVal.length() > 0) { - ret = Enum.valueOf(enumType, eVal); + IntMap ret = new IntMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key")); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + return ret; - } + } private static final String[] zeroStrings = new String[0]; @@ -1443,5 +934,4 @@ protected String[] parseTokens(String inString) { ? zeroStrings : outStrings; } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java index fe02a2d0e7..58d1713de9 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java @@ -39,13 +39,13 @@ 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 @@ * @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 @@ private Element appendElement(String name) { return ret; } - private static String encodeString(String s) { - if (s == null) { - return null; - } - s = s.replaceAll("\\&", "&") - .replaceAll("\\\"", """) - .replaceAll("\\<", "<"); - 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 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 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 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 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= 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 void write(BitSet value, String name, BitSet defVal) throws IOException { } 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 void write(Savable object, String name, Savable defVal) throws IOExceptio 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 array, String name, ArrayList 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 map, String name, Map 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 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 map, String name, Map defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeByteBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - Iterator 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 map, String name, IntMap defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeFloatBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - for(Entry 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 map, String name, Map 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 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 map, String name, Map defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); + Element stringMap = appendElement(name); + + Iterator 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 array, - String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + public void writeIntSavableMap(IntMap map, String name, IntMap 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 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(); + } } \ No newline at end of file diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java index f0e664427b..af5f898bf8 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java @@ -46,7 +46,9 @@ * @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) */ diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java index add3f5ab70..bc4ef918b0 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -39,8 +39,15 @@ 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 OutputCapsule getCapsule(Savable object) { 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(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java index 7cf2a98cb2..90d9e6e057 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java @@ -80,8 +80,9 @@ public Object load(AssetInfo info) throws IOException { try { return load(in); } finally { - if (in != null) + if (in != null) { in.close(); + } } } @@ -115,5 +116,4 @@ public InputCapsule getCapsule(Savable id) { public static XMLImporter getInstance() { return new XMLImporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java index 3dd8d9bbff..fcec673553 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java +++ b/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 static boolean hasAttribute(int version, Element element, String name) { */ private XMLUtils() { } - }