The current repository code will not generate the correct source files as they are in the process of being changed from using static and singletons to dependency injection that "looks behind" to check for any breaking api changes.
-
Handle unlisted enums in foreign.h
-
Handle Unlisted params in Heif
-
Handle options in init.c
-
When the JDK 20 reaches GA, update Panama for version 20
public class SharedReference<T, R extends Addressable<T>> {
private static final AtomicIntegerFieldUpdater<SharedReference> updater = newUpdater(SharedReference.class, "threadCount");
private final long id;
private volatile int threadCount = 1; (1)
private volatile R referent; (2)
private final StampedLock lock = new StampedLock(); (3)
private final T address; (4)
}
-
The number of threads holding onto this shared image, this field is updated atomicly
-
The underlying image, this will be nulled if the thread count goes to zero
-
The stamped lock shared among threads
-
Either the
MemorySegment
,ByteBuffer
orLong
address depending on the implementation
SharedResource
, this is to prevent end-users from holding on to references that have been free’dclass VipsImageDelegate<T> implements JVipsImage<T> {
protected final long id;
protected final SharedReference<T, JVipsImage<T>> sharedImage; // (1)
protected final Thread thread; // (2)
@Override
public int getXSize() {
ensureOperation(); (3)
StampedLock lock = sharedImage.getLock(); (4)
long id = lock.tryReadLock();
try {
if(id != 0) {
return getSharedImage().getXSize(); (5)
} else {
throw new RuntimeException("Failed to get read lock for image");
}
} finally {
lock.unlockRead(id);
}
}
void ensureOperation(){
if(this.sharedImage.getImage() == null || this.sharedImage.getThreadCount() == 0){
throw new RuntimeException("Image was GC'ed");
}
if(this.thread != Thread.currentThread()){
throw new RuntimeException("Thread does not hold image");
}
}
}
-
Globally shared image
-
The owning thread
-
Ensure the calling thread is the owning thread, also make sure the unerlying image is not null or the thread count is 0
-
Get the shared StampedLock
-
This could throw a null pointer exception if a something wierd happens
public class MyImageManipulator {
void doSomethingWithAnImage(String file){
try(VipsContext context = JniContext.create()){ (1)
JVipsImage<Long> img = context.loadImage(file);
Executors.newSingleThreadPool().submit(()-> {
context.saveAvif(img); (2)
});
} catch(VipsException e){
throw new RuntimeException(e);
}
}
}
-
Open an image context
-
This will throw an error
public class MyImageManipulator {
JVipsExecutorService executor = JVipsExecutorService.ofDefault(); (1)
void doSomethingWithAnImage(String file){
try(VipsContext context = JniContext.create()){ (2)
JVipsImage<Long> img = context.loadImage(file);
executor.submit(()-> {
JVipsImage<Long> imgref = context.transfer(Thread.currentThread(), img); (3)
context.saveAvif(imgref);
}); (4)
} catch(VipsException e){
throw new RuntimeException(e);
}
}
}
-
All sub operations that happen in another thread must use the
JVipsExecutorService
the overridden life cycle methods will properly unref the image -
Create the context
-
Create a new refrence in the underlying
SharedImage
-
After it has run the
ThreadPoolExecutor#afterExecute(…)
is invoked and the ref counts are de-incremented
A lot of code is generated, the jvips-plugin
directory which is a seperate project under a Gradle composite build
-
Read the GObject Introspection
-
Read the associated C source files
-
Register All Types
-
Register all Executable’s with the associated type
-
Parse the Documentation
-
Early Stage Parsing handles Optional Params (more on this)
-
Parse the documentation update to JavaDoc format with AsciiDoc syntax
-
-
Method names are:
-
Transformed to camelCase, some are elongated ie:
getXres
becomesgetXResolution
-
Splitting on certain keywords so
jpegsave_buffer
andjpegsave_file
becomejpegSaveBuffer
andjpegSaveFile
-
Take advantage of method overloading, so
xyz
andxyz1
both becomexyz
-
-
Each method that has optional parameters has it add to it’s method signature as an
@OptionalParam
with an overloaded method that just passesnull
-
Each method that has 1 optional parameter has it add to it’s method signature as a boxed primitive
@OptionalParam(name = "opt")
-
Methods with more than 1 optional parameter go through a process where each one that has the same parameter names are grouped together and a method dto is created, ending with
Params
all the parameters are boxed primitives or any reference type
-
We look at each source file
-
Look for a block like this:
vobject_class->nickname = "thumbnail_base"; vobject_class->description = _( "thumbnail generation" ); vobject_class->build = vips_thumbnail_build;
-
Parse the macros that may look like this:
VIPS_ARG_IMAGE( class, "out", 2, _( "Output" ), _( "Output image" ), VIPS_ARGUMENT_REQUIRED_OUTPUT, G_STRUCT_OFFSET( VipsThumbnail, out ) );
-
The
…_base"
lets us know to add it to other blocks found in the same file, there are some corner cases
https://stackoverflow.com/questions/49959378/releasing-a-direct-buffer-in-java-and-possible-pitfalls
Strings:
http://www.club.cc.cmu.edu/~cmccabe/blog_jni_flaws.html https://stackoverflow.com/questions/16694239/java-native-code-string-ending
It looks like the JDK team is also doing similar with threads with Panama to prevent resource leaks https://minborgsjavapot.blogspot.com/2022/12/java-20-sneak-peek-on-panama-ffm-api.html
SilkDI: