package com.jme3.scene.plugins.blender.file; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.Structure.DataType; import java.util.ArrayList; import java.util.List; /** * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to * another structure. * @author Marcin Roguski */ /* package */ class Field implements Cloneable { private static final int NAME_LENGTH = 24; private static final int TYPE_LENGTH = 16; /** The blender context. */ public BlenderContext blenderContext; /** The type of the field. */ public String type; /** The name of the field. */ public String name; /** The value of the field. Filled during data reading. */ public Object value; /** This variable indicates the level of the pointer. */ public int pointerLevel; /** * This variable determines the sizes of the array. If the value is null the n the field is not an array. */ public int[] tableSizes; /** This variable indicates if the field is a function pointer. */ public boolean function; /** * Constructor. Saves the field data and parses its name. * @param name * the name of the field * @param type * the type of the field * @param blenderContext * the blender context * @throws BlenderFileException * this exception is thrown if the names contain errors */ public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException { this.type = type; this.blenderContext = blenderContext; this.parseField(new StringBuilder(name)); } /** * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we * have a clead empty copy of the filed to fill with data. * @param field * the object that we copy */ private Field(Field field) { type = field.type; name = field.name; blenderContext = field.blenderContext; pointerLevel = field.pointerLevel; if (field.tableSizes != null) { tableSizes = field.tableSizes.clone(); } function = field.function; } @Override public Object clone() throws CloneNotSupportedException { return new Field(this); } /** * This method fills the field wth data read from the input stream. * @param blenderInputStream * the stream we read data from * @throws BlenderFileException * an exception is thrown when the blend file is somehow invalid or corrupted */ public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { int dataToRead = 1; if (tableSizes != null && tableSizes.length > 0) { for (int size : tableSizes) { if (size <= 0) { throw new BlenderFileException("The field " + name + " has invalid table size: " + size); } dataToRead *= size; } } DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER; switch (dataType) { case POINTER: if (dataToRead == 1) { Pointer pointer = new Pointer(pointerLevel, function, blenderContext); pointer.fill(blenderInputStream); value = pointer; } else { Pointer[] data = new Pointer[dataToRead]; for (int i = 0; i < dataToRead; ++i) { Pointer pointer = new Pointer(pointerLevel, function, blenderContext); pointer.fill(blenderInputStream); data[i] = pointer; } value = new DynamicArray<Pointer>(tableSizes, data); } break; case CHARACTER: // character is also stored as a number, because sometimes the new blender version uses // other number type instead of character as a field type // and characters are very often used as byte number stores instead of real chars if (dataToRead == 1) { value = Byte.valueOf((byte) blenderInputStream.readByte()); } else { Character[] data = new Character[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Character.valueOf((char) blenderInputStream.readByte()); } value = new DynamicArray<Character>(tableSizes, data); } break; case SHORT: if (dataToRead == 1) { value = Integer.valueOf(blenderInputStream.readShort()); } else { Number[] data = new Number[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Integer.valueOf(blenderInputStream.readShort()); } value = new DynamicArray<Number>(tableSizes, data); } break; case INTEGER: if (dataToRead == 1) { value = Integer.valueOf(blenderInputStream.readInt()); } else { Number[] data = new Number[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Integer.valueOf(blenderInputStream.readInt()); } value = new DynamicArray<Number>(tableSizes, data); } break; case LONG: if (dataToRead == 1) { value = Long.valueOf(blenderInputStream.readLong()); } else { Number[] data = new Number[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Long.valueOf(blenderInputStream.readLong()); } value = new DynamicArray<Number>(tableSizes, data); } break; case FLOAT: if (dataToRead == 1) { value = Float.valueOf(blenderInputStream.readFloat()); } else { Number[] data = new Number[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Float.valueOf(blenderInputStream.readFloat()); } value = new DynamicArray<Number>(tableSizes, data); } break; case DOUBLE: if (dataToRead == 1) { value = Double.valueOf(blenderInputStream.readDouble()); } else { Number[] data = new Number[dataToRead]; for (int i = 0; i < dataToRead; ++i) { data[i] = Double.valueOf(blenderInputStream.readDouble()); } value = new DynamicArray<Number>(tableSizes, data); } break; case VOID: break; case STRUCTURE: if (dataToRead == 1) { Structure structure = blenderContext.getDnaBlockData().getStructure(type); structure.fill(blenderContext.getInputStream()); value = structure; } else { Structure[] data = new Structure[dataToRead]; for (int i = 0; i < dataToRead; ++i) { Structure structure = blenderContext.getDnaBlockData().getStructure(type); structure.fill(blenderContext.getInputStream()); data[i] = structure; } value = new DynamicArray<Structure>(tableSizes, data); } break; default: throw new IllegalStateException("Unimplemented filling of type: " + type); } } /** * This method parses the field name to determine how the field should be used. * @param nameBuilder * the name of the field (given as StringBuilder) * @throws BlenderFileException * this exception is thrown if the names contain errors */ private void parseField(StringBuilder nameBuilder) throws BlenderFileException { this.removeWhitespaces(nameBuilder); // veryfying if the name is a pointer int pointerIndex = nameBuilder.indexOf("*"); while (pointerIndex >= 0) { ++pointerLevel; nameBuilder.deleteCharAt(pointerIndex); pointerIndex = nameBuilder.indexOf("*"); } // veryfying if the name is a function pointer if (nameBuilder.indexOf("(") >= 0) { function = true; this.removeCharacter(nameBuilder, '('); this.removeCharacter(nameBuilder, ')'); } else { // veryfying if the name is a table int tableStartIndex = 0; List<Integer> lengths = new ArrayList<Integer>(3);// 3 dimensions will be enough in most cases do { tableStartIndex = nameBuilder.indexOf("["); if (tableStartIndex > 0) { int tableStopIndex = nameBuilder.indexOf("]"); if (tableStopIndex < 0) { throw new BlenderFileException("Invalid structure name: " + name); } try { lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex))); } catch (NumberFormatException e) { throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e); } nameBuilder.delete(tableStartIndex, tableStopIndex + 1); } } while (tableStartIndex > 0); if (!lengths.isEmpty()) { tableSizes = new int[lengths.size()]; for (int i = 0; i < tableSizes.length; ++i) { tableSizes[i] = lengths.get(i).intValue(); } } } name = nameBuilder.toString(); } /** * This method removes the required character from the text. * @param text * the text we remove characters from * @param toRemove * the character to be removed */ private void removeCharacter(StringBuilder text, char toRemove) { for (int i = 0; i < text.length(); ++i) { if (text.charAt(i) == toRemove) { text.deleteCharAt(i); --i; } } } /** * This method removes all whitespaces from the text. * @param text * the text we remove whitespaces from */ private void removeWhitespaces(StringBuilder text) { for (int i = 0; i < text.length(); ++i) { if (Character.isWhitespace(text.charAt(i))) { text.deleteCharAt(i); --i; } } } /** * This method builds the full name of the field (with function, pointer and table indications). * @return the full name of the field */ /*package*/ String getFullName() { StringBuilder result = new StringBuilder(); if (function) { result.append('('); } for (int i = 0; i < pointerLevel; ++i) { result.append('*'); } result.append(name); if (tableSizes != null) { for (int i = 0; i < tableSizes.length; ++i) { result.append('[').append(tableSizes[i]).append(']'); } } if (function) { result.append(")()"); } return result.toString(); } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append(this.getFullName()); // insert appropriate amount of spaces to format the output corrently int nameLength = result.length(); result.append(' ');// at least one space is a must for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added result.append(' '); } result.append(type); nameLength = result.length(); for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) { result.append(' '); } if (value instanceof Character) { result.append(" = ").append((int) ((Character) value).charValue()); } else { result.append(" = ").append(value != null ? value.toString() : "null"); } return result.toString(); } }