aboutsummaryrefslogtreecommitdiff
path: root/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java')
-rw-r--r--engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java383
1 files changed, 383 insertions, 0 deletions
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
new file mode 100644
index 0000000..0f58474
--- /dev/null
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.file;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * An input stream with random access to data.
+ * @author Marcin Roguski
+ */
+public class BlenderInputStream extends InputStream {
+
+ private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName());
+ /** The default size of the blender buffer. */
+ private static final int DEFAULT_BUFFER_SIZE = 1048576; //1MB
+ /** The application's asset manager. */
+ private AssetManager assetManager;
+ /**
+ * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.
+ */
+ private int pointerSize;
+ /**
+ * Type of byte ordering used; 'v' means little endian and 'V' means big endian.
+ */
+ private char endianess;
+ /** Version of Blender the file was created in; '248' means version 2.48. */
+ private String versionNumber;
+ /** The buffer we store the read data to. */
+ protected byte[] cachedBuffer;
+ /** The total size of the stored data. */
+ protected int size;
+ /** The current position of the read cursor. */
+ protected int position;
+ /** The input stream we read the data from. */
+ protected InputStream inputStream;
+
+ /**
+ * Constructor. The input stream is stored and used to read data.
+ * @param inputStream
+ * the stream we read data from
+ * @param assetManager
+ * the application's asset manager
+ * @throws BlenderFileException
+ * this exception is thrown if the file header has some invalid data
+ */
+ public BlenderInputStream(InputStream inputStream, AssetManager assetManager) throws BlenderFileException {
+ this.assetManager = assetManager;
+ this.inputStream = inputStream;
+ //the size value will canche while reading the file; the available() method cannot be counted on
+ try {
+ size = inputStream.available();
+ } catch (IOException e) {
+ size = 0;
+ }
+ if (size <= 0) {
+ size = BlenderInputStream.DEFAULT_BUFFER_SIZE;
+ }
+
+ //buffered input stream is used here for much faster file reading
+ BufferedInputStream bufferedInputStream;
+ if (inputStream instanceof BufferedInputStream) {
+ bufferedInputStream = (BufferedInputStream) inputStream;
+ } else {
+ bufferedInputStream = new BufferedInputStream(inputStream);
+ }
+
+ try {
+ this.readStreamToCache(bufferedInputStream);
+ } catch (IOException e) {
+ throw new BlenderFileException("Problems occured while caching the file!", e);
+ }
+
+ try {
+ this.readFileHeader();
+ } catch (BlenderFileException e) {//the file might be packed, don't panic, try one more time ;)
+ this.decompressFile();
+ this.position = 0;
+ this.readFileHeader();
+ }
+ }
+
+ /**
+ * This method reads the whole stream into a buffer.
+ * @param inputStream
+ * the stream to read the file data from
+ * @throws IOException
+ * an exception is thrown when data read from the stream is invalid or there are problems with i/o
+ * operations
+ */
+ private void readStreamToCache(InputStream inputStream) throws IOException {
+ int data = inputStream.read();
+ cachedBuffer = new byte[size];
+ size = 0;//this will count the actual size
+ while (data != -1) {
+ cachedBuffer[size++] = (byte) data;
+ if (size >= cachedBuffer.length) {//widen the cached array
+ byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];
+ System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
+ cachedBuffer = newBuffer;
+ }
+ data = inputStream.read();
+ }
+ }
+
+ /**
+ * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the
+ * cachedBuffer field.
+ */
+ private void decompressFile() {
+ GZIPInputStream gis = null;
+ try {
+ gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
+ this.readStreamToCache(gis);
+ } catch (IOException e) {
+ throw new IllegalStateException("IO errors occured where they should NOT! "
+ + "The data is already buffered at this point!", e);
+ } finally {
+ try {
+ if (gis != null) {
+ gis.close();
+ }
+ } catch (IOException e) {
+ LOGGER.warning(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * This method loads the header from the given stream during instance creation.
+ * @param inputStream
+ * the stream we read the header from
+ * @throws BlenderFileException
+ * this exception is thrown if the file header has some invalid data
+ */
+ private void readFileHeader() throws BlenderFileException {
+ byte[] identifier = new byte[7];
+ int bytesRead = this.readBytes(identifier);
+ if (bytesRead != 7) {
+ throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!");
+ }
+ String strIdentifier = new String(identifier);
+ if (!"BLENDER".equals(strIdentifier)) {
+ throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!");
+ }
+ char pointerSizeSign = (char) this.readByte();
+ if (pointerSizeSign == '-') {
+ pointerSize = 8;
+ } else if (pointerSizeSign == '_') {
+ pointerSize = 4;
+ } else {
+ throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign);
+ }
+ endianess = (char) this.readByte();
+ if (endianess != 'v' && endianess != 'V') {
+ throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess);
+ }
+ byte[] versionNumber = new byte[3];
+ bytesRead = this.readBytes(versionNumber);
+ if (bytesRead != 3) {
+ throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!");
+ }
+ this.versionNumber = new String(versionNumber);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return this.readByte();
+ }
+
+ /**
+ * This method reads 1 byte from the stream.
+ * It works just in the way the read method does.
+ * It just not throw an exception because at this moment the whole file
+ * is loaded into buffer, so no need for IOException to be thrown.
+ * @return a byte from the stream (1 bytes read)
+ */
+ public int readByte() {
+ return cachedBuffer[position++] & 0xFF;
+ }
+
+ /**
+ * This method reads a bytes number big enough to fill the table.
+ * It does not throw exceptions so it is for internal use only.
+ * @param bytes
+ * an array to be filled with data
+ * @return number of read bytes (a length of array actually)
+ */
+ private int readBytes(byte[] bytes) {
+ for (int i = 0; i < bytes.length; ++i) {
+ bytes[i] = (byte) this.readByte();
+ }
+ return bytes.length;
+ }
+
+ /**
+ * This method reads 2-byte number from the stream.
+ * @return a number from the stream (2 bytes read)
+ */
+ public int readShort() {
+ int part1 = this.readByte();
+ int part2 = this.readByte();
+ if (endianess == 'v') {
+ return (part2 << 8) + part1;
+ } else {
+ return (part1 << 8) + part2;
+ }
+ }
+
+ /**
+ * This method reads 4-byte number from the stream.
+ * @return a number from the stream (4 bytes read)
+ */
+ public int readInt() {
+ int part1 = this.readByte();
+ int part2 = this.readByte();
+ int part3 = this.readByte();
+ int part4 = this.readByte();
+ if (endianess == 'v') {
+ return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1;
+ } else {
+ return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4;
+ }
+ }
+
+ /**
+ * This method reads 4-byte floating point number (float) from the stream.
+ * @return a number from the stream (4 bytes read)
+ */
+ public float readFloat() {
+ int intValue = this.readInt();
+ return Float.intBitsToFloat(intValue);
+ }
+
+ /**
+ * This method reads 8-byte number from the stream.
+ * @return a number from the stream (8 bytes read)
+ */
+ public long readLong() {
+ long part1 = this.readInt();
+ long part2 = this.readInt();
+ long result = -1;
+ if (endianess == 'v') {
+ result = part2 << 32 | part1;
+ } else {
+ result = part1 << 32 | part2;
+ }
+ return result;
+ }
+
+ /**
+ * This method reads 8-byte floating point number (double) from the stream.
+ * @return a number from the stream (8 bytes read)
+ */
+ public double readDouble() {
+ long longValue = this.readLong();
+ return Double.longBitsToDouble(longValue);
+ }
+
+ /**
+ * This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either
+ * 4 or 8 bytes of data.
+ * @return the pointer value
+ */
+ public long readPointer() {
+ if (pointerSize == 4) {
+ return this.readInt();
+ }
+ return this.readLong();
+ }
+
+ /**
+ * This method reads the string. It assumes the string is terminated with zero in the stream.
+ * @return the string read from the stream
+ */
+ public String readString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ int data = this.readByte();
+ while (data != 0) {
+ stringBuilder.append((char) data);
+ data = this.readByte();
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * This method sets the current position of the read cursor.
+ * @param position
+ * the position of the read cursor
+ */
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ /**
+ * This method returns the position of the read cursor.
+ * @return the position of the read cursor
+ */
+ public int getPosition() {
+ return position;
+ }
+
+ /**
+ * This method returns the blender version number where the file was created.
+ * @return blender version number
+ */
+ public String getVersionNumber() {
+ return versionNumber;
+ }
+
+ /**
+ * This method returns the size of the pointer.
+ * @return the size of the pointer
+ */
+ public int getPointerSize() {
+ return pointerSize;
+ }
+
+ /**
+ * This method returns the application's asset manager.
+ * @return the application's asset manager
+ */
+ public AssetManager getAssetManager() {
+ return assetManager;
+ }
+
+ /**
+ * This method aligns cursor position forward to a given amount of bytes.
+ * @param bytesAmount
+ * the byte amount to which we aligh the cursor
+ */
+ public void alignPosition(int bytesAmount) {
+ if (bytesAmount <= 0) {
+ throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!");
+ }
+ long move = position % bytesAmount;
+ if (move > 0) {
+ position += bytesAmount - move;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ inputStream.close();
+// cachedBuffer = null;
+// size = position = 0;
+ }
+}