Added supported for custom IO Systems in Java. Implemented ClassLoader IO System
authorJesper Smith <jsmith@ihmc.us>
Thu, 27 Jul 2017 22:42:01 +0000 (17:42 -0500)
committerJesper Smith <jsmith@ihmc.us>
Thu, 27 Jul 2017 22:42:01 +0000 (17:42 -0500)
port/jassimp/jassimp-native/src/jassimp.cpp
port/jassimp/jassimp-native/src/jassimp.h
port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java [new file with mode: 0644]
port/jassimp/jassimp/src/jassimp/AiIOStream.java [new file with mode: 0644]
port/jassimp/jassimp/src/jassimp/AiIOSystem.java [new file with mode: 0644]
port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java [new file with mode: 0644]
port/jassimp/jassimp/src/jassimp/Jassimp.java

index 226e416..475f6c5 100644 (file)
@@ -1,7 +1,9 @@
 #include "jassimp.h"
 
-#include <assimp/cimport.h>
+#include <assimp/Importer.hpp>
 #include <assimp/scene.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
 
 
 #ifdef JNI_LOG
 #define lprintf(...) printf (__VA_ARGS__)
 #endif /* ANDROID */
 #else
-#define lprintf
+#define lprintf 
 #endif
 
+static std::string gLastErrorString;
+
 // Automatically deletes a local ref when it goes out of scope
 class SmartLocalRef {
 private:
@@ -270,6 +274,81 @@ static bool callv(JNIEnv *env, jobject object, const char* typeName,
        return true;
 }
 
+static jobject callo(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+       const char* signature,/* const*/ jvalue* params)
+{
+       jclass clazz = env->FindClass(typeName);
+       SmartLocalRef clazzRef(env, clazz);
+
+       if (NULL == clazz)
+       {
+               lprintf("could not find class %s\n", typeName);
+               return NULL;
+       }
+
+       jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+       if (NULL == mid)
+       {
+               lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+               return NULL;
+       }
+
+       jobject jReturnValue = env->CallObjectMethodA(object, mid, params);
+
+       return jReturnValue;
+}
+
+static int calli(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+       const char* signature)
+{
+       jclass clazz = env->FindClass(typeName);
+       SmartLocalRef clazzRef(env, clazz);
+
+       if (NULL == clazz)
+       {
+               lprintf("could not find class %s\n", typeName);
+               return false;
+       }
+
+       jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+       if (NULL == mid)
+       {
+               lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+               return false;
+       }
+
+       jint jReturnValue = env->CallIntMethod(object, mid);
+
+       return (int) jReturnValue;
+}
+
+static int callc(JNIEnv *env, jobject object, const char* typeName, const char* methodName, 
+       const char* signature)
+{
+       jclass clazz = env->FindClass(typeName);
+       SmartLocalRef clazzRef(env, clazz);
+
+       if (NULL == clazz)
+       {
+               lprintf("could not find class %s\n", typeName);
+               return false;
+       }
+
+       jmethodID mid = env->GetMethodID(clazz, methodName, signature);
+
+       if (NULL == mid)
+       {
+               lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName);
+               return false;
+       }
+
+       jint jReturnValue = env->CallCharMethod(object, mid);
+
+       return (int) jReturnValue;
+}
+
 
 static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, 
        const char* signature,/* const*/ jvalue* params, jobject& returnValue)
@@ -359,6 +438,155 @@ static bool copyBufferArray(JNIEnv *env, jobject jMesh, const char* jBufferName,
        return true;
 }
 
+class JavaIOStream : public Assimp::IOStream
+{
+private:       
+       size_t pos;
+       size_t size;
+       char* buffer;
+       jobject jIOStream;
+
+       
+public:
+       JavaIOStream(size_t size, char* buffer, jobject jIOStream) :
+       pos(0),
+       size(size),
+       buffer(buffer),
+       jIOStream(jIOStream)
+       {};
+       
+       
+    ~JavaIOStream(void) 
+    {
+       free(buffer);
+    }; 
+
+    size_t Read(void* pvBuffer, size_t pSize, size_t pCount)
+    {
+       const size_t cnt = std::min(pCount,(size - pos)/pSize);
+               const size_t ofs = pSize*cnt;
+       
+           memcpy(pvBuffer, buffer + pos, ofs);
+           pos += ofs;
+       
+           return cnt;
+    };
+    size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) {};
+    
+    aiReturn Seek(size_t pOffset, aiOrigin pOrigin)
+    {
+           if (aiOrigin_SET == pOrigin) {
+               if (pOffset >= size) {
+                   return AI_FAILURE;
+               }
+               pos = pOffset;
+           }
+           else if (aiOrigin_END == pOrigin) {
+               if (pOffset >= size) {
+                   return AI_FAILURE;
+               }
+               pos = size-pOffset;
+           }
+           else {
+               if (pOffset + pos >= size) {
+                   return AI_FAILURE;
+               }
+               pos += pOffset;
+           }
+           return AI_SUCCESS;
+    };
+    
+    size_t Tell(void) const
+    {
+       return pos;
+    };
+    
+    size_t FileSize() const
+    {
+       return size;
+    };
+    
+    void Flush() {};
+    
+    
+    jobject javaObject()
+    {
+       return jIOStream;
+    };
+    
+    
+};
+
+class JavaIOSystem : public Assimp::IOSystem {
+       private:
+    JNIEnv* mJniEnv;
+       jobject& mJavaIOSystem;
+       
+       public:
+       JavaIOSystem(JNIEnv* env, jobject& javaIOSystem) :
+               mJniEnv(env),
+               mJavaIOSystem(javaIOSystem)
+       {};
+       
+    bool Exists( const char* pFile) const
+    {
+       jvalue params[1];
+               params[0].l = mJniEnv->NewStringUTF(pFile);
+           return call(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "exists", "(Ljava/lang/String;)Z", params);
+
+    };
+    char getOsSeparator() const
+    {
+           return (char) callc(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "getOsSeparator", "()C");
+    };
+    
+    Assimp::IOStream* Open(const char* pFile,const char* pMode = "rb")
+    {
+        jvalue params[2];
+               params[0].l = mJniEnv->NewStringUTF(pFile);
+               params[1].l = mJniEnv->NewStringUTF(pMode);
+               
+               
+           jobject jStream = callo(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "open", "(Ljava/lang/String;Ljava/lang/String;)Ljassimp/AiIOStream;", params);
+           if(NULL == jStream)
+           {
+               lprintf("NULL object from AiIOSystem.open\n");
+               return NULL;
+           }
+           
+           size_t size = calli(mJniEnv, jStream, "jassimp/AiIOStream", "getFileSize", "()I");
+           lprintf("Model file size is %d\n", size);
+           
+           char* buffer = (char*)malloc(size);
+           jobject javaBuffer = mJniEnv->NewDirectByteBuffer(buffer, size);
+           
+           jvalue readParams[1];
+           readParams[0].l = javaBuffer;
+           if(call(mJniEnv, jStream, "jassimp/AiIOStream", "read", "(Ljava/nio/ByteBuffer;)Z", readParams))
+           {
+               return new JavaIOStream(size, buffer, jStream);
+               }
+               else
+               {
+                       lprintf("Read failure on AiIOStream.read");
+                       free(buffer);
+                       return NULL;
+               }
+
+    };
+    void Close( Assimp::IOStream* pFile)
+    {
+       
+               jvalue params[1];
+               params[0].l = ((JavaIOStream*) pFile)->javaObject();
+               callv(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "close", "(Ljassimp/AiIOStream;)V", params);
+       delete pFile;
+    };
+    
+
+       
+};
 
 
 static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene)
@@ -1474,7 +1702,7 @@ JNIEXPORT jint JNICALL Java_jassimp_Jassimp_getlongsize
 JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
   (JNIEnv *env, jclass jClazz)
 {
-       const char *err = aiGetErrorString();
+       const char *err = gLastErrorString.c_str();
 
        if (NULL == err)
        {
@@ -1486,18 +1714,26 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
 
 
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess)
+  (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem)
 {
        jobject jScene = NULL; 
 
        /* convert params */
        const char* cFilename = env->GetStringUTFChars(jFilename, NULL);
+       
+    Assimp::Importer imp;
 
-
+       
+       if(ioSystem != NULL)
+       {
+               imp.SetIOHandler(new JavaIOSystem(env, ioSystem));              
+               lprintf("Created aiFileIO\n");
+       }
+       
        lprintf("opening file: %s\n", cFilename);
 
        /* do import */
-       const aiScene *cScene = aiImportFile(cFilename, (unsigned int) postProcess);
+       const aiScene *cScene = imp.ReadFile(cFilename, (unsigned int) postProcess);
 
        if (!cScene)
        {
@@ -1552,19 +1788,13 @@ error:
                /* thats really a problem because we cannot throw in this case */
                env->FatalError("could not throw java.io.IOException");
        }
-
-       env->ThrowNew(exception, aiGetErrorString());
+       gLastErrorString = imp.GetErrorString();
+       env->ThrowNew(exception, gLastErrorString.c_str());
 
        lprintf("problem detected\n");
        }
 
 end:
-       /* 
-        * NOTE: this releases all memory used in the native domain.
-        * Ensure all data has been passed to java before!
-        */
-       aiReleaseImport(cScene);
-
 
        /* free params */
        env->ReleaseStringUTFChars(jFilename, cFilename);
index f448dc2..7d4b66e 100644 (file)
@@ -39,7 +39,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
  * Signature: (Ljava/lang/String;J)Ljassimp/AiScene;
  */
 JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
-  (JNIEnv *, jclass, jstring, jlong);
+  (JNIEnv *, jclass, jstring, jlong, jobject);
 
 #ifdef __cplusplus
 }
diff --git a/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java b/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java
new file mode 100644 (file)
index 0000000..04d6386
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * IOSystem based on the Java classloader.<p>
+ * 
+ * This IOSystem allows loading models directly from the 
+ * classpath. No extraction to the file system is 
+ * necessary.
+ * 
+ * @author Jesper Smith
+ *
+ */
+public class AiClassLoaderIOSystem implements AiIOSystem<AiInputStreamIOStream>
+{
+   private final Class<?> clazz;
+   private final ClassLoader classLoader;
+  
+   /**
+    * Construct a new AiClassLoaderIOSystem.<p>
+    * 
+    * This constructor uses a ClassLoader to resolve
+    * resources.
+    * 
+    * @param classLoader classLoader to resolve resources.
+    */
+   public AiClassLoaderIOSystem(ClassLoader classLoader) {
+      this.clazz = null;
+      this.classLoader = classLoader;
+   }
+
+   /**
+    * Construct a new AiClassLoaderIOSystem.<p>
+    * 
+    * This constructor uses a Class to resolve
+    * resources.
+    * 
+    * @param class<?> class to resolve resources.
+    */
+   public AiClassLoaderIOSystem(Class<?> clazz) {
+      this.clazz = clazz;
+      this.classLoader = null;
+   }
+   
+
+   @Override
+   public AiInputStreamIOStream open(String filename, String ioMode) {
+      try {
+         
+         InputStream is;
+         
+         if(clazz != null) {
+            is = clazz.getResourceAsStream(filename);
+         }
+         else if (classLoader != null) {
+            is = classLoader.getResourceAsStream(filename);
+         }
+         else {
+            System.err.println("[" + getClass().getSimpleName() + 
+                "] No class or classLoader provided to resolve " + filename);
+            return null;
+         }
+         
+         if(is != null) {
+            return new AiInputStreamIOStream(is);
+         }
+         else {
+            System.err.println("[" + getClass().getSimpleName() + 
+                               "] Cannot find " + filename);
+            return null;
+         }
+      }
+      catch (IOException e) {
+         e.printStackTrace();
+         return null;
+      }
+   }
+
+   @Override
+   public void close(AiInputStreamIOStream file) {
+   }
+
+   @Override
+   public boolean exists(String path)
+   {
+      URL url = null;
+      if(clazz != null) {
+         url = clazz.getResource(path);
+      }
+      else if (classLoader != null) {
+         url = classLoader.getResource(path);
+      }
+
+      
+      if(url == null)
+      {
+         return false;
+      }
+      else
+      {
+         return true;
+      }
+      
+   }
+
+   @Override
+   public char getOsSeparator()
+   {
+      return '/';
+   }
+
+}
diff --git a/port/jassimp/jassimp/src/jassimp/AiIOStream.java b/port/jassimp/jassimp/src/jassimp/AiIOStream.java
new file mode 100644 (file)
index 0000000..5378da5
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Interface to allow custom resource loaders for jassimp.<p>
+ *
+ * The design is based on passing the file wholly in memory, 
+ * because Java inputstreams do not have to support seek. <p>
+ * 
+ * Writing files from Java is unsupported.
+ * 
+ * 
+ * @author Jesper Smith
+ *
+ */
+public interface AiIOStream
+{
+
+   /**
+    * Read all data into buffer. <p>
+    * 
+    * The whole stream should be read into the buffer. 
+    * No support is provided for partial reads. 
+    * 
+    * @param buffer Target buffer for the model data
+    * 
+    * @return true if successful, false if an error occurred.
+    */
+   boolean read(ByteBuffer buffer);
+
+   /**
+    * The total size of this stream. <p>
+    *  
+    * @return total size of this stream
+    */
+   int getFileSize();
+
+}
diff --git a/port/jassimp/jassimp/src/jassimp/AiIOSystem.java b/port/jassimp/jassimp/src/jassimp/AiIOSystem.java
new file mode 100644 (file)
index 0000000..d2c7415
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+public interface AiIOSystem <T extends AiIOStream>
+{
+   /**
+    * 
+    * Open a new file with a given path.
+    * When the access to the file is finished, call close() to release all associated resources
+    * 
+    * @param path Path to the file
+    * @param ioMode file I/O mode. Required are: "wb", "w", "wt", "rb", "r", "rt".
+    * 
+    * @return AiIOStream or null if an error occurred
+    */
+   public T open(String path, String ioMode);
+   
+   
+   /**
+    * Tests for the existence of a file at the given path.
+    *  
+    * @param path path to the file
+    * @return true if there is a file with this path, else false.
+    */
+   public boolean exists(String path);
+
+   /**
+    * Returns the system specific directory separator.<p>
+    * 
+    * @return System specific directory separator
+    */
+   public char getOsSeparator();
+   
+   /**
+    * Closes the given file and releases all resources associated with it.
+    * 
+    * @param file The file instance previously created by Open().
+    */
+   public void close(T file);
+}
diff --git a/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java b/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java
new file mode 100644 (file)
index 0000000..998401b
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC)
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *     
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. 
+ */
+package jassimp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.ByteBuffer;
+
+
+/**
+ * Implementation of AiIOStream reading from a InputStream
+ * 
+ * @author Jesper Smith
+ *
+ */
+public class AiInputStreamIOStream implements AiIOStream
+{
+   private final ByteArrayOutputStream os = new ByteArrayOutputStream(); 
+   
+   
+   public AiInputStreamIOStream(URI uri) throws IOException {
+      this(uri.toURL());
+   }
+   
+   public AiInputStreamIOStream(URL url) throws IOException {
+      this(url.openStream());
+   }
+   
+   public AiInputStreamIOStream(InputStream is) throws IOException {
+      int read;
+      byte[] data = new byte[1024];
+      while((read = is.read(data, 0, data.length)) != -1) {
+         os.write(data, 0, read);
+      }
+      os.flush();
+      
+      is.close();
+   }
+   
+   @Override
+   public int getFileSize() {
+      return os.size();
+   }
+   
+   @Override
+   public boolean read(ByteBuffer buffer) {
+     ByteBufferOutputStream bos = new ByteBufferOutputStream(buffer);
+     try
+     {
+        os.writeTo(bos);
+     }
+     catch (IOException e)
+     {
+        e.printStackTrace();
+        return false;
+     }
+     return true;
+   }
+   
+   /**
+    * Internal helper class to copy the contents of an OutputStream
+    * into a ByteBuffer. This avoids a copy.
+    *
+    */
+   private static class ByteBufferOutputStream extends OutputStream {
+
+      private final ByteBuffer buffer;
+      
+      public ByteBufferOutputStream(ByteBuffer buffer) {
+         this.buffer = buffer;
+      }
+      
+      @Override
+      public void write(int b) throws IOException
+      {
+         buffer.put((byte) b);
+      }
+    
+      @Override
+      public void write(byte b[], int off, int len) throws IOException {
+         buffer.put(b, off, len);
+      }
+   }
+}
+
index 92f4864..d1b4aae 100644 (file)
@@ -79,22 +79,52 @@ public final class Jassimp {
         return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class));
     }
     
+    /**
+     * Imports a file via assimp without post processing.
+     * 
+     * @param filename the file to import
+     * @param ioSystem ioSystem to load files, or null for default
+     * @return the loaded scene
+     * @throws IOException if an error occurs
+     */
+    public static AiScene importFile(String filename, AiIOSystem<?> ioSystem) 
+          throws IOException {
+       
+       return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class), ioSystem);
+    }
+    
+    
+    /**
+     * Imports a file via assimp.
+     * 
+     * @param filename the file to import
+     * @param postProcessing post processing flags
+     * @return the loaded scene, or null if an error occurred
+     * @throws IOException if an error occurs
+     */
+    public static AiScene importFile(String filename, 
+                                     Set<AiPostProcessSteps> postProcessing) 
+                                           throws IOException {
+        return importFile(filename, postProcessing, null);
+    }
     
     /**
      * Imports a file via assimp.
      * 
      * @param filename the file to import
      * @param postProcessing post processing flags
+     * @param ioSystem ioSystem to load files, or null for default
      * @return the loaded scene, or null if an error occurred
      * @throws IOException if an error occurs
      */
     public static AiScene importFile(String filename, 
-            Set<AiPostProcessSteps> postProcessing) throws IOException {
+            Set<AiPostProcessSteps> postProcessing, AiIOSystem<?> ioSystem) 
+                  throws IOException {
         
        loadLibrary();
        
         return aiImportFile(filename, AiPostProcessSteps.toRawValue(
-                postProcessing));
+                postProcessing), ioSystem);
     }
     
     
@@ -310,7 +340,7 @@ public final class Jassimp {
      * @throws IOException if an error occurs
      */
     private static native AiScene aiImportFile(String filename, 
-            long postProcessing) throws IOException;
+            long postProcessing, AiIOSystem<?> ioSystem) throws IOException;
     
     
     /**