• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.jme3.scene.plugins.blender.file;
2 
3 import com.jme3.scene.plugins.blender.BlenderContext;
4 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
5 import com.jme3.scene.plugins.blender.file.Structure.DataType;
6 import java.util.ArrayList;
7 import java.util.List;
8 
9 /**
10  * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to
11  * another structure.
12  * @author Marcin Roguski
13  */
14 /*package*/
15 class Field implements Cloneable {
16 
17     private static final int NAME_LENGTH = 24;
18     private static final int TYPE_LENGTH = 16;
19     /** The blender context. */
20     public BlenderContext blenderContext;
21     /** The type of the field. */
22     public String type;
23     /** The name of the field. */
24     public String name;
25     /** The value of the field. Filled during data reading. */
26     public Object value;
27     /** This variable indicates the level of the pointer. */
28     public int pointerLevel;
29     /**
30      * This variable determines the sizes of the array. If the value is null the n the field is not an array.
31      */
32     public int[] tableSizes;
33     /** This variable indicates if the field is a function pointer. */
34     public boolean function;
35 
36     /**
37      * Constructor. Saves the field data and parses its name.
38      * @param name
39      *        the name of the field
40      * @param type
41      *        the type of the field
42      * @param blenderContext
43      *        the blender context
44      * @throws BlenderFileException
45      *         this exception is thrown if the names contain errors
46      */
Field(String name, String type, BlenderContext blenderContext)47     public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException {
48         this.type = type;
49         this.blenderContext = blenderContext;
50         this.parseField(new StringBuilder(name));
51     }
52 
53     /**
54      * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we
55      * have a clead empty copy of the filed to fill with data.
56      * @param field
57      *        the object that we copy
58      */
Field(Field field)59     private Field(Field field) {
60         type = field.type;
61         name = field.name;
62         blenderContext = field.blenderContext;
63         pointerLevel = field.pointerLevel;
64         if (field.tableSizes != null) {
65             tableSizes = field.tableSizes.clone();
66         }
67         function = field.function;
68     }
69 
70     @Override
clone()71     public Object clone() throws CloneNotSupportedException {
72         return new Field(this);
73     }
74 
75     /**
76      * This method fills the field wth data read from the input stream.
77      * @param blenderInputStream
78      *        the stream we read data from
79      * @throws BlenderFileException
80      *         an exception is thrown when the blend file is somehow invalid or corrupted
81      */
fill(BlenderInputStream blenderInputStream)82     public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {
83         int dataToRead = 1;
84         if (tableSizes != null && tableSizes.length > 0) {
85             for (int size : tableSizes) {
86                 if (size <= 0) {
87                     throw new BlenderFileException("The field " + name + " has invalid table size: " + size);
88                 }
89                 dataToRead *= size;
90             }
91         }
92         DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER;
93         switch (dataType) {
94             case POINTER:
95                 if (dataToRead == 1) {
96                     Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
97                     pointer.fill(blenderInputStream);
98                     value = pointer;
99                 } else {
100                     Pointer[] data = new Pointer[dataToRead];
101                     for (int i = 0; i < dataToRead; ++i) {
102                         Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
103                         pointer.fill(blenderInputStream);
104                         data[i] = pointer;
105                     }
106                     value = new DynamicArray<Pointer>(tableSizes, data);
107                 }
108                 break;
109             case CHARACTER:
110                 //character is also stored as a number, because sometimes the new blender version uses
111                 //other number type instead of character as a field type
112                 //and characters are very often used as byte number stores instead of real chars
113                 if (dataToRead == 1) {
114                     value = Byte.valueOf((byte) blenderInputStream.readByte());
115                 } else {
116                     Character[] data = new Character[dataToRead];
117                     for (int i = 0; i < dataToRead; ++i) {
118                         data[i] = Character.valueOf((char) blenderInputStream.readByte());
119                     }
120                     value = new DynamicArray<Character>(tableSizes, data);
121                 }
122                 break;
123             case SHORT:
124                 if (dataToRead == 1) {
125                     value = Integer.valueOf(blenderInputStream.readShort());
126                 } else {
127                     Number[] data = new Number[dataToRead];
128                     for (int i = 0; i < dataToRead; ++i) {
129                         data[i] = Integer.valueOf(blenderInputStream.readShort());
130                     }
131                     value = new DynamicArray<Number>(tableSizes, data);
132                 }
133                 break;
134             case INTEGER:
135                 if (dataToRead == 1) {
136                     value = Integer.valueOf(blenderInputStream.readInt());
137                 } else {
138                     Number[] data = new Number[dataToRead];
139                     for (int i = 0; i < dataToRead; ++i) {
140                         data[i] = Integer.valueOf(blenderInputStream.readInt());
141                     }
142                     value = new DynamicArray<Number>(tableSizes, data);
143                 }
144                 break;
145             case LONG:
146                 if (dataToRead == 1) {
147                     value = Long.valueOf(blenderInputStream.readLong());
148                 } else {
149                     Number[] data = new Number[dataToRead];
150                     for (int i = 0; i < dataToRead; ++i) {
151                         data[i] = Long.valueOf(blenderInputStream.readLong());
152                     }
153                     value = new DynamicArray<Number>(tableSizes, data);
154                 }
155                 break;
156             case FLOAT:
157                 if (dataToRead == 1) {
158                     value = Float.valueOf(blenderInputStream.readFloat());
159                 } else {
160                     Number[] data = new Number[dataToRead];
161                     for (int i = 0; i < dataToRead; ++i) {
162                         data[i] = Float.valueOf(blenderInputStream.readFloat());
163                     }
164                     value = new DynamicArray<Number>(tableSizes, data);
165                 }
166                 break;
167             case DOUBLE:
168                 if (dataToRead == 1) {
169                     value = Double.valueOf(blenderInputStream.readDouble());
170                 } else {
171                     Number[] data = new Number[dataToRead];
172                     for (int i = 0; i < dataToRead; ++i) {
173                         data[i] = Double.valueOf(blenderInputStream.readDouble());
174                     }
175                     value = new DynamicArray<Number>(tableSizes, data);
176                 }
177                 break;
178             case VOID:
179                 break;
180             case STRUCTURE:
181                 if (dataToRead == 1) {
182                     Structure structure = blenderContext.getDnaBlockData().getStructure(type);
183                     structure.fill(blenderInputStream);
184                     value = structure;
185                 } else {
186                     Structure[] data = new Structure[dataToRead];
187                     for (int i = 0; i < dataToRead; ++i) {
188                         Structure structure = blenderContext.getDnaBlockData().getStructure(type);
189                         structure.fill(blenderInputStream);
190                         data[i] = structure;
191                     }
192                     value = new DynamicArray<Structure>(tableSizes, data);
193                 }
194                 break;
195             default:
196                 throw new IllegalStateException("Unimplemented filling of type: " + type);
197         }
198     }
199 
200     /**
201      * This method parses the field name to determine how the field should be used.
202      * @param nameBuilder
203      *        the name of the field (given as StringBuilder)
204      * @throws BlenderFileException
205      *         this exception is thrown if the names contain errors
206      */
parseField(StringBuilder nameBuilder)207     private void parseField(StringBuilder nameBuilder) throws BlenderFileException {
208         this.removeWhitespaces(nameBuilder);
209         //veryfying if the name is a pointer
210         int pointerIndex = nameBuilder.indexOf("*");
211         while (pointerIndex >= 0) {
212             ++pointerLevel;
213             nameBuilder.deleteCharAt(pointerIndex);
214             pointerIndex = nameBuilder.indexOf("*");
215         }
216         //veryfying if the name is a function pointer
217         if (nameBuilder.indexOf("(") >= 0) {
218             function = true;
219             this.removeCharacter(nameBuilder, '(');
220             this.removeCharacter(nameBuilder, ')');
221         } else {
222             //veryfying if the name is a table
223             int tableStartIndex = 0;
224             List<Integer> lengths = new ArrayList<Integer>(3);//3 dimensions will be enough in most cases
225             do {
226                 tableStartIndex = nameBuilder.indexOf("[");
227                 if (tableStartIndex > 0) {
228                     int tableStopIndex = nameBuilder.indexOf("]");
229                     if (tableStopIndex < 0) {
230                         throw new BlenderFileException("Invalid structure name: " + name);
231                     }
232                     try {
233                         lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex)));
234                     } catch (NumberFormatException e) {
235                         throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e);
236                     }
237                     nameBuilder.delete(tableStartIndex, tableStopIndex + 1);
238                 }
239             } while (tableStartIndex > 0);
240             if (!lengths.isEmpty()) {
241                 tableSizes = new int[lengths.size()];
242                 for (int i = 0; i < tableSizes.length; ++i) {
243                     tableSizes[i] = lengths.get(i).intValue();
244                 }
245             }
246         }
247         name = nameBuilder.toString();
248     }
249 
250     /**
251      * This method removes the required character from the text.
252      * @param text
253      *        the text we remove characters from
254      * @param toRemove
255      *        the character to be removed
256      */
removeCharacter(StringBuilder text, char toRemove)257     private void removeCharacter(StringBuilder text, char toRemove) {
258         for (int i = 0; i < text.length(); ++i) {
259             if (text.charAt(i) == toRemove) {
260                 text.deleteCharAt(i);
261                 --i;
262             }
263         }
264     }
265 
266     /**
267      * This method removes all whitespaces from the text.
268      * @param text
269      *        the text we remove whitespaces from
270      */
removeWhitespaces(StringBuilder text)271     private void removeWhitespaces(StringBuilder text) {
272         for (int i = 0; i < text.length(); ++i) {
273             if (Character.isWhitespace(text.charAt(i))) {
274                 text.deleteCharAt(i);
275                 --i;
276             }
277         }
278     }
279 
280     @Override
toString()281     public String toString() {
282         StringBuilder result = new StringBuilder();
283         if (function) {
284             result.append('(');
285         }
286         for (int i = 0; i < pointerLevel; ++i) {
287             result.append('*');
288         }
289         result.append(name);
290         if (tableSizes != null) {
291             for (int i = 0; i < tableSizes.length; ++i) {
292                 result.append('[').append(tableSizes[i]).append(']');
293             }
294         }
295         if (function) {
296             result.append(")()");
297         }
298         //insert appropriate amount of spaces to format the output corrently
299         int nameLength = result.length();
300         result.append(' ');//at least one space is a must
301         for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added
302             result.append(' ');
303         }
304         result.append(type);
305         nameLength = result.length();
306         for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) {
307             result.append(' ');
308         }
309         if (value instanceof Character) {
310             result.append(" = ").append((int) ((Character) value).charValue());
311         } else {
312             result.append(" = ").append(value != null ? value.toString() : "null");
313         }
314         return result.toString();
315     }
316 }