Home | History | Annotate | Download | only in file
      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      */
     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 }