/* * Copyright (c) 2009-2012 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.scene; import com.jme3.export.*; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * <code>UserData</code> is used to contain user data objects * set on spatials (primarily primitives) that do not implement * the {@link Savable} interface. Note that attempting * to export any models which have non-savable objects * attached to them will fail. */ public final class UserData implements Savable { /** * Boolean type on Geometries to indicate that physics collision * shape generation should ignore them. */ public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore"; /** * For geometries using shared mesh, this will specify the shared * mesh reference. */ public static final String JME_SHAREDMESH = "JmeSharedMesh"; private static final int TYPE_INTEGER = 0; private static final int TYPE_FLOAT = 1; private static final int TYPE_BOOLEAN = 2; private static final int TYPE_STRING = 3; private static final int TYPE_LONG = 4; private static final int TYPE_SAVABLE = 5; private static final int TYPE_LIST = 6; private static final int TYPE_MAP = 7; private static final int TYPE_ARRAY = 8; protected byte type; protected Object value; public UserData() { } /** * Creates a new <code>UserData</code> with the given * type and value. * * @param type * Type of data, should be between 0 and 8. * @param value * Value of the data */ public UserData(byte type, Object value) { assert type >= 0 && type <= 8; this.type = type; this.value = value; } public Object getValue() { return value; } @Override public String toString() { return value.toString(); } public static byte getObjectType(Object type) { if (type instanceof Integer) { return TYPE_INTEGER; } else if (type instanceof Float) { return TYPE_FLOAT; } else if (type instanceof Boolean) { return TYPE_BOOLEAN; } else if (type instanceof String) { return TYPE_STRING; } else if (type instanceof Long) { return TYPE_LONG; } else if (type instanceof Savable) { return TYPE_SAVABLE; } else if (type instanceof List) { return TYPE_LIST; } else if (type instanceof Map) { return TYPE_MAP; } else if (type instanceof Object[]) { return TYPE_ARRAY; } else { throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName()); } } public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(type, "type", (byte) 0); switch (type) { case TYPE_INTEGER: int i = (Integer) value; oc.write(i, "intVal", 0); break; case TYPE_FLOAT: float f = (Float) value; oc.write(f, "floatVal", 0f); break; case TYPE_BOOLEAN: boolean b = (Boolean) value; oc.write(b, "boolVal", false); break; case TYPE_STRING: String s = (String) value; oc.write(s, "strVal", null); break; case TYPE_LONG: Long l = (Long) value; oc.write(l, "longVal", 0l); break; case TYPE_SAVABLE: Savable sav = (Savable) value; oc.write(sav, "savableVal", null); break; case TYPE_LIST: this.writeList(oc, (List<?>) value, "0"); break; case TYPE_MAP: Map<?, ?> map = (Map<?, ?>) value; this.writeList(oc, map.keySet(), "0"); this.writeList(oc, map.values(), "1"); break; case TYPE_ARRAY: this.writeList(oc, Arrays.asList((Object[]) value), "0"); break; default: throw new UnsupportedOperationException("Unsupported value type: " + value.getClass()); } } public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); switch (type) { case TYPE_INTEGER: value = ic.readInt("intVal", 0); break; case TYPE_FLOAT: value = ic.readFloat("floatVal", 0f); break; case TYPE_BOOLEAN: value = ic.readBoolean("boolVal", false); break; case TYPE_STRING: value = ic.readString("strVal", null); break; case TYPE_LONG: value = ic.readLong("longVal", 0l); break; case TYPE_SAVABLE: value = ic.readSavable("savableVal", null); break; case TYPE_LIST: value = this.readList(ic, "0"); break; case TYPE_MAP: Map<Object, Object> map = new HashMap<Object, Object>(); List<?> keys = this.readList(ic, "0"); List<?> values = this.readList(ic, "1"); for (int i = 0; i < keys.size(); ++i) { map.put(keys.get(i), values.get(i)); } value = map; break; case TYPE_ARRAY: value = this.readList(ic, "0").toArray(); break; default: throw new UnsupportedOperationException("Unknown type of stored data: " + type); } } /** * The method stores a list in the capsule. * @param oc * output capsule * @param list * the list to be stored * @throws IOException */ private void writeList(OutputCapsule oc, Collection<?> list, String listName) throws IOException { if (list != null) { oc.write(list.size(), listName + "size", 0); int counter = 0; for (Object o : list) { // t is for 'type'; v is for 'value' if (o instanceof Integer) { oc.write(TYPE_INTEGER, listName + "t" + counter, 0); oc.write((Integer) o, listName + "v" + counter, 0); } else if (o instanceof Float) { oc.write(TYPE_FLOAT, listName + "t" + counter, 0); oc.write((Float) o, listName + "v" + counter, 0f); } else if (o instanceof Boolean) { oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0); oc.write((Boolean) o, listName + "v" + counter, false); } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended oc.write(TYPE_STRING, listName + "t" + counter, 0); oc.write((String) o, listName + "v" + counter, null); } else if (o instanceof Long) { oc.write(TYPE_LONG, listName + "t" + counter, 0); oc.write((Long) o, listName + "v" + counter, 0L); } else if (o instanceof Savable) { oc.write(TYPE_SAVABLE, listName + "t" + counter, 0); oc.write((Savable) o, listName + "v" + counter, null); } else if(o instanceof Object[]) { oc.write(TYPE_ARRAY, listName + "t" + counter, 0); this.writeList(oc, Arrays.asList((Object[]) o), listName + "v" + counter); } else if(o instanceof List) { oc.write(TYPE_LIST, listName + "t" + counter, 0); this.writeList(oc, (List<?>) o, listName + "v" + counter); } else if(o instanceof Map) { oc.write(TYPE_MAP, listName + "t" + counter, 0); Map<?, ?> map = (Map<?, ?>) o; this.writeList(oc, map.keySet(), listName + "v(keys)" + counter); this.writeList(oc, map.values(), listName + "v(vals)" + counter); } else { throw new UnsupportedOperationException("Unsupported type stored in the list: " + o.getClass()); } ++counter; } } else { oc.write(0, "size", 0); } } /** * The method loads a list from the given input capsule. * @param ic * the input capsule * @return loaded list (an empty list in case its size is 0) * @throws IOException */ private List<?> readList(InputCapsule ic, String listName) throws IOException { int size = ic.readInt(listName + "size", 0); List<Object> list = new ArrayList<Object>(size); for (int i = 0; i < size; ++i) { int type = ic.readInt(listName + "t" + i, 0); switch (type) { case TYPE_INTEGER: list.add(ic.readInt(listName + "v" + i, 0)); break; case TYPE_FLOAT: list.add(ic.readFloat(listName + "v" + i, 0)); break; case TYPE_BOOLEAN: list.add(ic.readBoolean(listName + "v" + i, false)); break; case TYPE_STRING: list.add(ic.readString(listName + "v" + i, null)); break; case TYPE_LONG: list.add(ic.readLong(listName + "v" + i, 0L)); break; case TYPE_SAVABLE: list.add(ic.readSavable(listName + "v" + i, null)); break; case TYPE_ARRAY: list.add(this.readList(ic, listName + "v" + i).toArray()); break; case TYPE_LIST: list.add(this.readList(ic, listName + "v" + i)); break; case TYPE_MAP: Map<Object, Object> map = new HashMap<Object, Object>(); List<?> keys = this.readList(ic, listName + "v(keys)" + i); List<?> values = this.readList(ic, listName + "v(vals)" + i); for (int j = 0; j < keys.size(); ++j) { map.put(keys.get(j), values.get(j)); } list.add(map); break; default: throw new UnsupportedOperationException("Unknown type of stored data in a list: " + type); } } return list; } }