Home | History | Annotate | Download | only in objects
      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