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 }