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