1 package com.jme3.scene.plugins.blender.objects; 2 3 import com.jme3.export.*; 4 import com.jme3.scene.plugins.blender.BlenderContext; 5 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 6 import com.jme3.scene.plugins.blender.file.BlenderInputStream; 7 import com.jme3.scene.plugins.blender.file.FileBlockHeader; 8 import com.jme3.scene.plugins.blender.file.Pointer; 9 import com.jme3.scene.plugins.blender.file.Structure; 10 import java.io.IOException; 11 import java.util.ArrayList; 12 import java.util.HashMap; 13 import java.util.List; 14 import java.util.Map; 15 import java.util.logging.Logger; 16 17 /** 18 * The blender object's custom properties. 19 * This class is valid for all versions of blender. 20 * @author Marcin Roguski (Kaelthas) 21 */ 22 public class Properties implements Cloneable, Savable { 23 private static final Logger LOGGER = Logger.getLogger(Properties.class.getName()); 24 25 // property type 26 public static final int IDP_STRING = 0; 27 public static final int IDP_INT = 1; 28 public static final int IDP_FLOAT = 2; 29 public static final int IDP_ARRAY = 5; 30 public static final int IDP_GROUP = 6; 31 // public static final int IDP_ID = 7;//this is not implemented in blender (yet) 32 public static final int IDP_DOUBLE = 8; 33 // the following are valid for blender 2.5x+ 34 public static final int IDP_IDPARRAY = 9; 35 public static final int IDP_NUMTYPES = 10; 36 37 protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; 38 /** Default name of the property (used if the name is not specified in blender file). */ 39 protected static final String DEFAULT_NAME = "Unnamed property"; 40 41 /** The name of the property. */ 42 private String name; 43 /** The type of the property. */ 44 private int type; 45 /** The subtype of the property. Defines the type of array's elements. */ 46 private int subType; 47 /** The value of the property. */ 48 private Object value; 49 /** The description of the property. */ 50 private String description; 51 52 /** 53 * This method loads the property from the belnder file. 54 * @param idPropertyStructure 55 * the ID structure constining the property 56 * @param blenderContext 57 * the blender context 58 * @throws BlenderFileException 59 * an exception is thrown when the belnder file is somehow invalid 60 */ 61 public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { 62 name = idPropertyStructure.getFieldValue("name").toString(); 63 if (name == null || name.length() == 0) { 64 name = DEFAULT_NAME; 65 } 66 subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); 67 type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); 68 69 // reading the data 70 Structure data = (Structure) idPropertyStructure.getFieldValue("data"); 71 int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); 72 switch (type) { 73 case IDP_STRING: { 74 Pointer pointer = (Pointer) data.getFieldValue("pointer"); 75 BlenderInputStream bis = blenderContext.getInputStream(); 76 FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); 77 bis.setPosition(dataFileBlock.getBlockPosition()); 78 value = bis.readString(); 79 break; 80 } 81 case IDP_INT: 82 int intValue = ((Number) data.getFieldValue("val")).intValue(); 83 value = Integer.valueOf(intValue); 84 break; 85 case IDP_FLOAT: 86 int floatValue = ((Number) data.getFieldValue("val")).intValue(); 87 value = Float.valueOf(Float.intBitsToFloat(floatValue)); 88 break; 89 case IDP_ARRAY: { 90 Pointer pointer = (Pointer) data.getFieldValue("pointer"); 91 BlenderInputStream bis = blenderContext.getInputStream(); 92 FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); 93 bis.setPosition(dataFileBlock.getBlockPosition()); 94 int elementAmount = dataFileBlock.getSize(); 95 switch (subType) { 96 case IDP_INT: 97 elementAmount /= 4; 98 int[] intList = new int[elementAmount]; 99 for (int i = 0; i < elementAmount; ++i) { 100 intList[i] = bis.readInt(); 101 } 102 value = intList; 103 break; 104 case IDP_FLOAT: 105 elementAmount /= 4; 106 float[] floatList = new float[elementAmount]; 107 for (int i = 0; i < elementAmount; ++i) { 108 floatList[i] = bis.readFloat(); 109 } 110 value = floatList; 111 break; 112 case IDP_DOUBLE: 113 elementAmount /= 8; 114 double[] doubleList = new double[elementAmount]; 115 for (int i = 0; i < elementAmount; ++i) { 116 doubleList[i] = bis.readDouble(); 117 } 118 value = doubleList; 119 break; 120 default: 121 throw new IllegalStateException("Invalid array subtype: " + subType); 122 } 123 } 124 case IDP_GROUP: 125 Structure group = (Structure) data.getFieldValue("group"); 126 List<Structure> dataList = group.evaluateListBase(blenderContext); 127 List<Properties> subProperties = new ArrayList<Properties>(len); 128 for (Structure d : dataList) { 129 Properties properties = new Properties(); 130 properties.load(d, blenderContext); 131 subProperties.add(properties); 132 } 133 value = subProperties; 134 break; 135 case IDP_DOUBLE: 136 int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); 137 int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); 138 long doubleVal = (long) doublePart2 << 32 | doublePart1; 139 value = Double.valueOf(Double.longBitsToDouble(doubleVal)); 140 break; 141 case IDP_IDPARRAY: { 142 Pointer pointer = (Pointer) data.getFieldValue("pointer"); 143 List<Structure> arrays = pointer.fetchData(blenderContext.getInputStream()); 144 List<Object> result = new ArrayList<Object>(arrays.size()); 145 Properties temp = new Properties(); 146 for (Structure array : arrays) { 147 temp.load(array, blenderContext); 148 result.add(temp.value); 149 } 150 this.value = result; 151 break; 152 } 153 case IDP_NUMTYPES: 154 throw new UnsupportedOperationException(); 155 // case IDP_ID://not yet implemented in blender 156 // return null; 157 default: 158 throw new IllegalStateException("Unknown custom property type: " + type); 159 } 160 this.completeLoading(); 161 } 162 163 /** 164 * This method returns the name of the property. 165 * @return the name of the property 166 */ 167 public String getName() { 168 return name; 169 } 170 171 /** 172 * This method returns the description of the property. 173 * @return the description of the property 174 */ 175 public String getDescription() { 176 return description; 177 } 178 179 /** 180 * This method returns the type of the property. 181 * @return the type of the property 182 */ 183 public int getType() { 184 return type; 185 } 186 187 /** 188 * This method returns the value of the property. 189 * The type of the value depends on the type of the property. 190 * @return the value of the property 191 */ 192 public Object getValue() { 193 return value; 194 } 195 196 /** 197 * This method returns the same as getValue if the current property is of 198 * other type than IDP_GROUP and its name matches 'propertyName' param. If 199 * this property is a group property the method tries to find subproperty 200 * value of the given name. The first found value is returnes os <b>use this 201 * method wisely</b>. If no property of a given name is foung - <b>null</b> 202 * is returned. 203 * 204 * @param propertyName 205 * the name of the property 206 * @return found property value or <b>null</b> 207 */ 208 @SuppressWarnings("unchecked") 209 public Object findValue(String propertyName) { 210 if (name.equals(propertyName)) { 211 return value; 212 } else { 213 if (type == IDP_GROUP) { 214 List<Properties> props = (List<Properties>) value; 215 for (Properties p : props) { 216 Object v = p.findValue(propertyName); 217 if (v != null) { 218 return v; 219 } 220 } 221 } 222 } 223 return null; 224 } 225 226 @Override 227 public String toString() { 228 StringBuilder sb = new StringBuilder(); 229 this.append(sb, new StringBuilder()); 230 return sb.toString(); 231 } 232 233 /** 234 * This method appends the data of the property to the given string buffer. 235 * @param sb 236 * string buffer 237 * @param indent 238 * indent buffer 239 */ 240 @SuppressWarnings("unchecked") 241 private void append(StringBuilder sb, StringBuilder indent) { 242 sb.append(indent).append("name: ").append(name).append("\n\r"); 243 sb.append(indent).append("type: ").append(type).append("\n\r"); 244 sb.append(indent).append("subType: ").append(subType).append("\n\r"); 245 sb.append(indent).append("description: ").append(description).append("\n\r"); 246 indent.append('\t'); 247 sb.append(indent).append("value: "); 248 if (value instanceof Properties) { 249 ((Properties) value).append(sb, indent); 250 } else if (value instanceof List) { 251 for (Object v : (List<Object>) value) { 252 if (v instanceof Properties) { 253 sb.append(indent).append("{\n\r"); 254 indent.append('\t'); 255 ((Properties) v).append(sb, indent); 256 indent.deleteCharAt(indent.length() - 1); 257 sb.append(indent).append("}\n\r"); 258 } else { 259 sb.append(v); 260 } 261 } 262 } else { 263 sb.append(value); 264 } 265 sb.append("\n\r"); 266 indent.deleteCharAt(indent.length() - 1); 267 } 268 269 /** 270 * This method should be called after the properties loading. 271 * It loads the properties from the _RNA_UI property and removes this property from the 272 * result list. 273 */ 274 @SuppressWarnings("unchecked") 275 protected void completeLoading() { 276 if (this.type == IDP_GROUP) { 277 List<Properties> groupProperties = (List<Properties>) this.value; 278 Properties rnaUI = null; 279 for (Properties properties : groupProperties) { 280 if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) { 281 rnaUI = properties; 282 break; 283 } 284 } 285 if (rnaUI != null) { 286 // removing the RNA from the result list 287 groupProperties.remove(rnaUI); 288 289 // loading the descriptions 290 Map<String, String> descriptions = new HashMap<String, String>(groupProperties.size()); 291 List<Properties> propertiesRNA = (List<Properties>) rnaUI.value; 292 for (Properties properties : propertiesRNA) { 293 String name = properties.name; 294 String description = null; 295 List<Properties> rnaData = (List<Properties>) properties.value; 296 for (Properties rna : rnaData) { 297 if ("description".equalsIgnoreCase(rna.name)) { 298 description = (String) rna.value; 299 break; 300 } 301 } 302 descriptions.put(name, description); 303 } 304 305 // applying the descriptions 306 for (Properties properties : groupProperties) { 307 properties.description = descriptions.get(properties.name); 308 } 309 } 310 } 311 } 312 313 @Override 314 @SuppressWarnings({ "rawtypes", "unchecked" }) 315 public void write(JmeExporter ex) throws IOException { 316 OutputCapsule oc = ex.getCapsule(this); 317 oc.write(name, "name", DEFAULT_NAME); 318 oc.write(type, "type", 0); 319 oc.write(subType, "subtype", 0); 320 oc.write(description, "description", null); 321 switch (type) { 322 case IDP_STRING: 323 oc.write((String) value, "value", null); 324 break; 325 case IDP_INT: 326 oc.write((Integer) value, "value", 0); 327 break; 328 case IDP_FLOAT: 329 oc.write((Float) value, "value", 0); 330 break; 331 case IDP_ARRAY: 332 switch (subType) { 333 case IDP_INT: 334 oc.write((int[]) value, "value", null); 335 break; 336 case IDP_FLOAT: 337 oc.write((float[]) value, "value", null); 338 break; 339 case IDP_DOUBLE: 340 oc.write((double[]) value, "value", null); 341 break; 342 default: 343 LOGGER.warning("Cannot save the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType); 344 } 345 case IDP_GROUP: 346 oc.writeSavableArrayList((ArrayList<Properties>) value, "value", null); 347 break; 348 case IDP_DOUBLE: 349 oc.write((Double) value, "value", 0); 350 break; 351 case IDP_IDPARRAY: 352 oc.writeSavableArrayList((ArrayList) value, "value", null); 353 break; 354 case IDP_NUMTYPES: 355 LOGGER.warning("Numtypes value not supported! Cannot write it!"); 356 break; 357 // case IDP_ID://not yet implemented in blender 358 // break; 359 default: 360 LOGGER.warning("Cannot save the property's value! Invalid type! Property: name: " + name + "; type: " + type); 361 } 362 } 363 364 @Override 365 public void read(JmeImporter im) throws IOException { 366 InputCapsule ic = im.getCapsule(this); 367 name = ic.readString("name", DEFAULT_NAME); 368 type = ic.readInt("type", 0); 369 subType = ic.readInt("subtype", 0); 370 description = ic.readString("description", null); 371 switch (type) { 372 case IDP_STRING: 373 value = ic.readString("value", null); 374 break; 375 case IDP_INT: 376 value = Integer.valueOf(ic.readInt("value", 0)); 377 break; 378 case IDP_FLOAT: 379 value = Float.valueOf(ic.readFloat("value", 0.0f)); 380 break; 381 case IDP_ARRAY: 382 switch (subType) { 383 case IDP_INT: 384 value = ic.readIntArray("value", null); 385 break; 386 case IDP_FLOAT: 387 value = ic.readFloatArray("value", null); 388 break; 389 case IDP_DOUBLE: 390 value = ic.readDoubleArray("value", null); 391 break; 392 default: 393 LOGGER.warning("Cannot read the property's value! Invalid array subtype! Property: name: " + name + "; subtype: " + subType); 394 } 395 case IDP_GROUP: 396 value = ic.readSavable("value", null); 397 break; 398 case IDP_DOUBLE: 399 value = Double.valueOf(ic.readDouble("value", 0.0)); 400 break; 401 case IDP_IDPARRAY: 402 value = ic.readSavableArrayList("value", null); 403 break; 404 case IDP_NUMTYPES: 405 LOGGER.warning("Numtypes value not supported! Cannot read it!"); 406 break; 407 // case IDP_ID://not yet implemented in blender 408 // break; 409 default: 410 LOGGER.warning("Cannot read the property's value! Invalid type! Property: name: " + name + "; type: " + type); 411 } 412 } 413 414 @Override 415 public int hashCode() { 416 final int prime = 31; 417 int result = 1; 418 result = prime * result + (description == null ? 0 : description.hashCode()); 419 result = prime * result + (name == null ? 0 : name.hashCode()); 420 result = prime * result + subType; 421 result = prime * result + type; 422 result = prime * result + (value == null ? 0 : value.hashCode()); 423 return result; 424 } 425 426 @Override 427 public boolean equals(Object obj) { 428 if (this == obj) { 429 return true; 430 } 431 if (obj == null) { 432 return false; 433 } 434 if (this.getClass() != obj.getClass()) { 435 return false; 436 } 437 Properties other = (Properties) obj; 438 if (description == null) { 439 if (other.description != null) { 440 return false; 441 } 442 } else if (!description.equals(other.description)) { 443 return false; 444 } 445 if (name == null) { 446 if (other.name != null) { 447 return false; 448 } 449 } else if (!name.equals(other.name)) { 450 return false; 451 } 452 if (subType != other.subType) { 453 return false; 454 } 455 if (type != other.type) { 456 return false; 457 } 458 if (value == null) { 459 if (other.value != null) { 460 return false; 461 } 462 } else if (!value.equals(other.value)) { 463 return false; 464 } 465 return true; 466 } 467 } 468