1package com.jme3.scene.plugins.blender.file;
2
3import com.jme3.scene.plugins.blender.BlenderContext;
4import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
5import com.jme3.scene.plugins.blender.file.Structure.DataType;
6import java.util.ArrayList;
7import 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*/
15class 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     */
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     */
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
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     */
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     */
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     */
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     */
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
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}