• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 	 */
load(Structure idPropertyStructure, BlenderContext blenderContext)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 	 */
getName()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 	 */
getDescription()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 	 */
getType()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 	 */
getValue()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")
findValue(String propertyName)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
toString()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")
append(StringBuilder sb, StringBuilder indent)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")
completeLoading()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" })
write(JmeExporter ex)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
read(JmeImporter im)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
hashCode()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
equals(Object obj)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