1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.scene.plugins.blender.file; 33 34 import com.jme3.scene.plugins.blender.BlenderContext; 35 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 36 import java.util.HashMap; 37 import java.util.LinkedList; 38 import java.util.List; 39 import java.util.Map; 40 41 /** 42 * A class representing a single structure in the file. 43 * @author Marcin Roguski 44 */ 45 public class Structure implements Cloneable { 46 47 /** The blender context. */ 48 private BlenderContext blenderContext; 49 /** The address of the block that fills the structure. */ 50 private transient Long oldMemoryAddress; 51 /** The type of the structure. */ 52 private String type; 53 /** 54 * The fields of the structure. Each field consists of a pair: name-type. 55 */ 56 private Field[] fields; 57 58 /** 59 * Constructor that copies the data of the structure. 60 * @param structure 61 * the structure to copy. 62 * @param blenderContext 63 * the blender context of the structure 64 * @throws CloneNotSupportedException 65 * this exception should never be thrown 66 */ Structure(Structure structure, BlenderContext blenderContext)67 private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException { 68 type = structure.type; 69 fields = new Field[structure.fields.length]; 70 for (int i = 0; i < fields.length; ++i) { 71 fields[i] = (Field) structure.fields[i].clone(); 72 } 73 this.blenderContext = blenderContext; 74 this.oldMemoryAddress = structure.oldMemoryAddress; 75 } 76 77 /** 78 * Constructor. Loads the structure from the given stream during instance creation. 79 * @param inputStream 80 * the stream we read the structure from 81 * @param names 82 * the names from which the name of structure and its fields will be taken 83 * @param types 84 * the names of types for the structure 85 * @param blenderContext 86 * the blender context 87 * @throws BlenderFileException 88 * this exception occurs if the amount of fields, defined in the file, is negative 89 */ Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext)90 public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException { 91 int nameIndex = inputStream.readShort(); 92 type = types[nameIndex]; 93 this.blenderContext = blenderContext; 94 int fieldsAmount = inputStream.readShort(); 95 if (fieldsAmount < 0) { 96 throw new BlenderFileException("The amount of fields of " + this.type + " structure cannot be negative!"); 97 } 98 if (fieldsAmount > 0) { 99 fields = new Field[fieldsAmount]; 100 for (int i = 0; i < fieldsAmount; ++i) { 101 int typeIndex = inputStream.readShort(); 102 nameIndex = inputStream.readShort(); 103 fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext); 104 } 105 } 106 this.oldMemoryAddress = Long.valueOf(-1L); 107 } 108 109 /** 110 * This method fills the structure with data. 111 * @param inputStream 112 * the stream we read data from, its read cursor should be placed at the start position of the data for the 113 * structure 114 * @throws BlenderFileException 115 * an exception is thrown when the blend file is somehow invalid or corrupted 116 */ fill(BlenderInputStream inputStream)117 public void fill(BlenderInputStream inputStream) throws BlenderFileException { 118 int position = inputStream.getPosition(); 119 inputStream.setPosition(position - 8 - inputStream.getPointerSize()); 120 this.oldMemoryAddress = Long.valueOf(inputStream.readPointer()); 121 inputStream.setPosition(position); 122 for (Field field : fields) { 123 field.fill(inputStream); 124 } 125 } 126 127 /** 128 * This method returns the value of the filed with a given name. 129 * @param fieldName 130 * the name of the field 131 * @return the value of the field or null if no field with a given name is found 132 */ getFieldValue(String fieldName)133 public Object getFieldValue(String fieldName) { 134 for (Field field : fields) { 135 if (field.name.equalsIgnoreCase(fieldName)) { 136 return field.value; 137 } 138 } 139 return null; 140 } 141 142 /** 143 * This method returns the value of the filed with a given name. The structure is considered to have flat fields 144 * only (no substructures). 145 * @param fieldName 146 * the name of the field 147 * @return the value of the field or null if no field with a given name is found 148 */ getFlatFieldValue(String fieldName)149 public Object getFlatFieldValue(String fieldName) { 150 for (Field field : fields) { 151 Object value = field.value; 152 if (field.name.equalsIgnoreCase(fieldName)) { 153 return value; 154 } else if (value instanceof Structure) { 155 value = ((Structure) value).getFlatFieldValue(fieldName); 156 if (value != null) {//we can compare references here, since we use one static object as a NULL field value 157 return value; 158 } 159 } 160 } 161 return null; 162 } 163 164 /** 165 * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are 166 * held by this structure within the blend file. 167 * @param blenderContext 168 * the blender context 169 * @return a list of filled structures 170 * @throws BlenderFileException 171 * this exception is thrown when the blend file structure is somehow invalid or corrupted 172 * @throws IllegalArgumentException 173 * this exception is thrown if the type of the structure is not 'ListBase' 174 */ evaluateListBase(BlenderContext blenderContext)175 public List<Structure> evaluateListBase(BlenderContext blenderContext) throws BlenderFileException { 176 if (!"ListBase".equals(this.type)) { 177 throw new IllegalStateException("This structure is not of type: 'ListBase'"); 178 } 179 Pointer first = (Pointer) this.getFieldValue("first"); 180 Pointer last = (Pointer) this.getFieldValue("last"); 181 long currentAddress = 0; 182 long lastAddress = last.getOldMemoryAddress(); 183 List<Structure> result = new LinkedList<Structure>(); 184 while (currentAddress != lastAddress) { 185 currentAddress = first.getOldMemoryAddress(); 186 Structure structure = first.fetchData(blenderContext.getInputStream()).get(0); 187 result.add(structure); 188 first = (Pointer) structure.getFlatFieldValue("next"); 189 } 190 return result; 191 } 192 193 /** 194 * This method returns the type of the structure. 195 * @return the type of the structure 196 */ getType()197 public String getType() { 198 return type; 199 } 200 201 /** 202 * This method returns the amount of fields for the current structure. 203 * @return the amount of fields for the current structure 204 */ getFieldsAmount()205 public int getFieldsAmount() { 206 return fields.length; 207 } 208 209 /** 210 * This method returns the field name of the given index. 211 * @param fieldIndex 212 * the index of the field 213 * @return the field name of the given index 214 */ getFieldName(int fieldIndex)215 public String getFieldName(int fieldIndex) { 216 return fields[fieldIndex].name; 217 } 218 219 /** 220 * This method returns the field type of the given index. 221 * @param fieldIndex 222 * the index of the field 223 * @return the field type of the given index 224 */ getFieldType(int fieldIndex)225 public String getFieldType(int fieldIndex) { 226 return fields[fieldIndex].type; 227 } 228 229 /** 230 * This method returns the address of the structure. The strucutre should be filled with data otherwise an exception 231 * is thrown. 232 * @return the address of the feature stored in this structure 233 */ getOldMemoryAddress()234 public Long getOldMemoryAddress() { 235 if (oldMemoryAddress.longValue() == -1L) { 236 throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!"); 237 } 238 return oldMemoryAddress; 239 } 240 241 /** 242 * This method returns the name of the structure. If the structure has an ID field then the name is returned. 243 * Otherwise the name does not exists and the method returns null. 244 * @return the name of the structure read from the ID field or null 245 */ getName()246 public String getName() { 247 Object fieldValue = this.getFieldValue("ID"); 248 if(fieldValue instanceof Structure) { 249 Structure id = (Structure)fieldValue; 250 return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix 251 } 252 return null; 253 } 254 255 @Override toString()256 public String toString() { 257 StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); 258 for (int i = 0; i < fields.length; ++i) { 259 result.append(fields[i].toString()).append('\n'); 260 } 261 return result.append('}').toString(); 262 } 263 264 @Override clone()265 public Object clone() throws CloneNotSupportedException { 266 return new Structure(this, blenderContext); 267 } 268 269 /** 270 * This enum enumerates all known data types that can be found in the blend file. 271 * @author Marcin Roguski 272 */ 273 /*package*/ 274 static enum DataType { 275 276 CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; 277 /** The map containing the known primary types. */ 278 private static final Map<String, DataType> PRIMARY_TYPES = new HashMap<String, DataType>(10); 279 280 static { 281 PRIMARY_TYPES.put("char", CHARACTER); 282 PRIMARY_TYPES.put("uchar", CHARACTER); 283 PRIMARY_TYPES.put("short", SHORT); 284 PRIMARY_TYPES.put("ushort", SHORT); 285 PRIMARY_TYPES.put("int", INTEGER); 286 PRIMARY_TYPES.put("long", LONG); 287 PRIMARY_TYPES.put("ulong", LONG); 288 PRIMARY_TYPES.put("uint64_t", LONG); 289 PRIMARY_TYPES.put("float", FLOAT); 290 PRIMARY_TYPES.put("double", DOUBLE); 291 PRIMARY_TYPES.put("void", VOID); 292 } 293 294 /** 295 * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition 296 * is case sensitive! 297 * @param type 298 * the type name of the data 299 * @param blenderContext 300 * the blender context 301 * @return appropriate enum value to the given type name 302 * @throws BlenderFileException 303 * this exception is thrown if the given type name does not exist in the blend file 304 */ getDataType(String type, BlenderContext blenderContext)305 public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException { 306 DataType result = PRIMARY_TYPES.get(type); 307 if (result != null) { 308 return result; 309 } 310 if (blenderContext.getDnaBlockData().hasStructure(type)) { 311 return STRUCTURE; 312 } 313 throw new BlenderFileException("Unknown data type: " + type); 314 } 315 } 316 } 317