Added supported for custom IO Systems in Java. Implemented ClassLoader IO System

pull/1489/head
Jesper Smith 2017-07-27 17:42:01 -05:00
parent 114c48bbcf
commit 0229a3acf3
7 changed files with 619 additions and 18 deletions

View File

@ -1,7 +1,9 @@
#include "jassimp.h" #include "jassimp.h"
#include <assimp/cimport.h> #include <assimp/Importer.hpp>
#include <assimp/scene.h> #include <assimp/scene.h>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
#ifdef JNI_LOG #ifdef JNI_LOG
@ -15,6 +17,8 @@
#define lprintf #define lprintf
#endif #endif
static std::string gLastErrorString;
// Automatically deletes a local ref when it goes out of scope // Automatically deletes a local ref when it goes out of scope
class SmartLocalRef { class SmartLocalRef {
private: private:
@ -270,6 +274,81 @@ static bool callv(JNIEnv *env, jobject object, const char* typeName,
return true; 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, static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName,
const char* signature,/* const*/ jvalue* params, jobject& returnValue) const char* signature,/* const*/ jvalue* params, jobject& returnValue)
@ -359,6 +438,155 @@ static bool copyBufferArray(JNIEnv *env, jobject jMesh, const char* jBufferName,
return true; 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) 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 JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
(JNIEnv *env, jclass jClazz) (JNIEnv *env, jclass jClazz)
{ {
const char *err = aiGetErrorString(); const char *err = gLastErrorString.c_str();
if (NULL == err) if (NULL == err)
{ {
@ -1486,18 +1714,26 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile 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; jobject jScene = NULL;
/* convert params */ /* convert params */
const char* cFilename = env->GetStringUTFChars(jFilename, NULL); 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); lprintf("opening file: %s\n", cFilename);
/* do import */ /* do import */
const aiScene *cScene = aiImportFile(cFilename, (unsigned int) postProcess); const aiScene *cScene = imp.ReadFile(cFilename, (unsigned int) postProcess);
if (!cScene) if (!cScene)
{ {
@ -1552,19 +1788,13 @@ error:
/* thats really a problem because we cannot throw in this case */ /* thats really a problem because we cannot throw in this case */
env->FatalError("could not throw java.io.IOException"); env->FatalError("could not throw java.io.IOException");
} }
gLastErrorString = imp.GetErrorString();
env->ThrowNew(exception, aiGetErrorString()); env->ThrowNew(exception, gLastErrorString.c_str());
lprintf("problem detected\n"); lprintf("problem detected\n");
} }
end: end:
/*
* NOTE: this releases all memory used in the native domain.
* Ensure all data has been passed to java before!
*/
aiReleaseImport(cScene);
/* free params */ /* free params */
env->ReleaseStringUTFChars(jFilename, cFilename); env->ReleaseStringUTFChars(jFilename, cFilename);

View File

@ -39,7 +39,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
* Signature: (Ljava/lang/String;J)Ljassimp/AiScene; * Signature: (Ljava/lang/String;J)Ljassimp/AiScene;
*/ */
JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile
(JNIEnv *, jclass, jstring, jlong); (JNIEnv *, jclass, jstring, jlong, jobject);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -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 '/';
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -79,6 +79,20 @@ public final class Jassimp {
return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class)); 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. * Imports a file via assimp.
@ -89,12 +103,28 @@ public final class Jassimp {
* @throws IOException if an error occurs * @throws IOException if an error occurs
*/ */
public static AiScene importFile(String filename, public static AiScene importFile(String filename,
Set<AiPostProcessSteps> postProcessing) throws IOException { 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, AiIOSystem<?> ioSystem)
throws IOException {
loadLibrary(); loadLibrary();
return aiImportFile(filename, AiPostProcessSteps.toRawValue( return aiImportFile(filename, AiPostProcessSteps.toRawValue(
postProcessing)); postProcessing), ioSystem);
} }
@ -310,7 +340,7 @@ public final class Jassimp {
* @throws IOException if an error occurs * @throws IOException if an error occurs
*/ */
private static native AiScene aiImportFile(String filename, private static native AiScene aiImportFile(String filename,
long postProcessing) throws IOException; long postProcessing, AiIOSystem<?> ioSystem) throws IOException;
/** /**