imagen icon indicating copy to clipboard operation
imagen copied to clipboard

Remove reflection and core java class direct access

Open aaime opened this issue 7 months ago • 3 comments

Find all places that would throw an exception due to reflection or direct access to internal JVM classes and remove it, eventually replacing it with a less efficient code (no direct access will mean using the public API, which can be less efficient).

Example from running a JAI-EXT test against Java 17:

Error: Could not find mediaLib accelerator wrapper classes. Continuing in pure Java mode.
Occurs in: com.sun.media.jai.mlib.MediaLibAccessor
java.lang.NoClassDefFoundError: com/sun/medialib/mlib/Image
	at com.sun.media.jai.mlib.MediaLibAccessor$1.run(MediaLibAccessor.java:248)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
	at com.sun.media.jai.mlib.MediaLibAccessor.setUseMlib(MediaLibAccessor.java:245)
	at com.sun.media.jai.mlib.MediaLibAccessor.useMlib(MediaLibAccessor.java:177)
	at com.sun.media.jai.mlib.MediaLibAccessor.isMediaLibCompatible(MediaLibAccessor.java:357)
	at com.sun.media.jai.mlib.MediaLibAccessor.isMediaLibCompatible(MediaLibAccessor.java:315)
	at com.sun.media.jai.mlib.MlibScaleRIF.create(MlibScaleRIF.java:67)
	at it.geosolutions.jaiext.scale.ScaleCRIF.create(ScaleCRIF.java:135)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at javax.media.jai.FactoryCache.invoke(FactoryCache.java:122)
	at javax.media.jai.OperationRegistry.invokeFactory(OperationRegistry.java:1674)
	at it.geosolutions.jaiext.ConcurrentOperationRegistry.invokeFactory(ConcurrentOperationRegistry.java:576)
	at javax.media.jai.registry.RIFRegistry.create(RIFRegistry.java:332)
	at javax.media.jai.RenderedOp.createInstance(RenderedOp.java:819)
	at javax.media.jai.RenderedOp.createRendering(RenderedOp.java:867)
	at javax.media.jai.RenderedOp.getMinX(RenderedOp.java:2161)
	at javax.media.jai.PlanarImage.getBounds(PlanarImage.java:702)
	at javax.media.jai.PlanarImage.getTiles(PlanarImage.java:2617)
	at it.geosolutions.jaiext.scale.TestScale.testAndReturnImage(TestScale.java:285)
	at it.geosolutions.jaiext.scale.TestScale.testImage(TestScale.java:157)
	at it.geosolutions.jaiext.scale.TestScale.testImage(TestScale.java:120)
	at it.geosolutions.jaiext.scale.TestScale.testImage(TestScale.java:108)
	at it.geosolutions.jaiext.scale.TestScale.testGlobal(TestScale.java:74)
	at it.geosolutions.jaiext.scale.BicubicScaleTest.testImageScaling(BicubicScaleTest.java:56)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.lang.ClassNotFoundException: com.sun.medialib.mlib.Image
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	... 55 more
Exception in thread "SunTileScheduler0Standard1" java.lang.IllegalAccessError: class javax.media.jai.RasterAccessor (in unnamed module @0x76ed5528) cannot access class sun.awt.image.BytePackedRaster (in module java.desktop) because module java.desktop does not export sun.awt.image to unnamed module @0x76ed5528
	at javax.media.jai.RasterAccessor.<init>(RasterAccessor.java:802)
	at it.geosolutions.jaiext.scale.ScaleBicubicOpImage.computeRect(ScaleBicubicOpImage.java:257)
	at it.geosolutions.jaiext.scale.ScaleOpImage.computeTile(ScaleOpImage.java:1808)
	at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
	at javax.media.jai.OpImage.getTile(OpImage.java:1129)
	at com.sun.media.jai.util.SunTileScheduler.compute(SunTileScheduler.java:717)
	at com.sun.media.jai.util.TileJob.compute(SunTileScheduler.java:373)
	at com.sun.media.jai.util.WorkerThread.run(SunTileScheduler.java:468)
Exception in thread "SunTileScheduler0Standard0" java.lang.IllegalAccessError: class javax.media.jai.RasterAccessor (in unnamed module @0x76ed5528) cannot access class sun.awt.image.BytePackedRaster (in module java.desktop) because module java.desktop does not export sun.awt.image to unnamed module @0x76ed5528
	at javax.media.jai.RasterAccessor.<init>(RasterAccessor.java:802)
	at it.geosolutions.jaiext.scale.ScaleBicubicOpImage.computeRect(ScaleBicubicOpImage.java:257)
	at it.geosolutions.jaiext.scale.ScaleOpImage.computeTile(ScaleOpImage.java:1627)
	at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
	at javax.media.jai.OpImage.getTile(OpImage.java:1129)
	at com.sun.media.jai.util.SunTileScheduler.compute(SunTileScheduler.java:717)
	at com.sun.media.jai.util.TileJob.compute(SunTileScheduler.java:373)
	at com.sun.media.jai.util.WorkerThread.run(SunTileScheduler.java:468)

aaime avatar May 21 '25 14:05 aaime

For your consideration:

> git grep -nE '^import\s+java\.lang\.reflect\.' -- '*.java'
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codec/FileCacheSeekableStream.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/CodecUtils.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:20:import java.lang.reflect.InvocationTargetException;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:21:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codec/FileCacheSeekableStream.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/CodecUtils.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:20:import java.lang.reflect.InvocationTargetException;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:21:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codec/FileCacheSeekableStream.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/CodecUtils.java:24:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:20:import java.lang.reflect.InvocationTargetException;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/ImagingListenerProxy.java:21:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/util/DataBufferUtils.java:21:import java.lang.reflect.Constructor;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/util/DataBufferUtils.java:22:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/util/ImagingException.java:22:import java.lang.reflect.InvocationTargetException;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/codecimpl/util/ImagingException.java:23:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/opimage/FileLoadRIF.java:26:import java.lang.reflect.AccessibleObject;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/media/opimage/FileLoadRIF.java:27:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/registry/TileDecoderRegistryMode.java:20:import java.lang.reflect.Method;
legacy/codec/codec-core/src/main/java/org/eclipse/imagen/registry/TileEncoderRegistryMode.java:20:import java.lang.reflect.Method;
legacy/core/src/main/java/org/eclipse/imagen/media/test/OpImageTester.java:29:import java.lang.reflect.Method;
legacy/network/network-core/src/main/java/org/eclipse/imagen/media/rmi/InterfaceProxy.java:20:import java.lang.reflect.InvocationHandler;
legacy/network/network-core/src/main/java/org/eclipse/imagen/media/rmi/InterfaceProxy.java:21:import java.lang.reflect.Proxy;
legacy/network/network-core/src/main/java/org/eclipse/imagen/media/rmi/RenderingHintsProxy.java:23:import java.lang.reflect.Field;
legacy/network/network-core/src/main/java/org/eclipse/imagen/media/rmi/RenderingHintsProxy.java:24:import java.lang.reflect.Modifier;
legacy/network/network-core/src/main/java/org/eclipse/imagen/registry/RemoteRenderableRegistryMode.java:20:import java.lang.reflect.Method;
legacy/network/network-core/src/main/java/org/eclipse/imagen/registry/RemoteRenderedRegistryMode.java:20:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/FactoryCache.java:20:import java.lang.reflect.InvocationTargetException;
modules/core/src/main/java/org/eclipse/imagen/FactoryCache.java:21:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/JAI.java:30:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/ParameterListDescriptorImpl.java:20:import java.lang.reflect.Field;
modules/core/src/main/java/org/eclipse/imagen/ParameterListDescriptorImpl.java:21:import java.lang.reflect.Modifier;
modules/core/src/main/java/org/eclipse/imagen/PointOpImage.java:33:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/RegistryMode.java:20:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/RenderableGraphics.java:48:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/TiledImageGraphics.java:50:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/media/util/DataBufferUtils.java:21:import java.lang.reflect.Constructor;
modules/core/src/main/java/org/eclipse/imagen/media/util/DataBufferUtils.java:22:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/media/util/DisposableNullOpImage.java:21:import java.lang.reflect.AccessibleObject;
modules/core/src/main/java/org/eclipse/imagen/media/util/DisposableNullOpImage.java:22:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/media/util/ImageUtil.java:39:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/media/util/ImageUtil.java:40:import java.lang.reflect.Modifier;
modules/core/src/main/java/org/eclipse/imagen/registry/CollectionRegistryMode.java:20:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/registry/RenderableCollectionRegistryMode.java:20:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/registry/RenderableRegistryMode.java:21:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/registry/RenderedRegistryMode.java:20:import java.lang.reflect.Method;
modules/core/src/main/java/org/eclipse/imagen/util/ImagingException.java:22:import java.lang.reflect.InvocationTargetException;
modules/core/src/main/java/org/eclipse/imagen/util/ImagingException.java:23:import java.lang.reflect.Method;
modules/imageread/src/main/java/org/eclipse/imagen/media/imageread/ImageReadOpImage.java:54:import java.lang.reflect.Method;
modules/jiffle/jt-jiffle-language/src/main/java/org/eclipse/imagen/media/jiffle/runtime/AbstractJiffleRuntime.java:47:import java.lang.reflect.Field;
modules/rendered-image-browser/src/main/java/org/eclipse/imagen/media/viewer/HTMLRenderers.java:20:import java.lang.reflect.Array;
modules/rendered-image-browser/src/main/java/org/eclipse/imagen/media/viewer/RenderedImageBrowser.java:29:import java.lang.reflect.Array;
modules/rendered-image-browser/src/main/java/org/eclipse/imagen/media/viewer/RenderedImageInfoPanel.java:25:import java.lang.reflect.Field;
modules/rendered-image-browser/src/main/java/org/eclipse/imagen/media/viewer/RenderedImageInfoPanel.java:26:import java.lang.reflect.Modifier;
modules/utilities/src/main/java/org/eclipse/imagen/media/JAIExt.java:20:import java.lang.reflect.Method;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/ColorModelState.java:31:import java.lang.reflect.Method;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/ColorModelState.java:32:import java.lang.reflect.Modifier;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/InterfaceState.java:23:import java.lang.reflect.InvocationHandler;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/InterfaceState.java:24:import java.lang.reflect.InvocationTargetException;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/InterfaceState.java:25:import java.lang.reflect.Method;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/InterfaceState.java:26:import java.lang.reflect.Proxy;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/RenderingHintsState.java:27:import java.lang.reflect.Field;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/RenderingHintsState.java:28:import java.lang.reflect.Modifier;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/SerializerImpl.java:21:import java.lang.reflect.Constructor;
modules/utilities/src/main/java/org/eclipse/imagen/media/serialize/SerializerImpl.java:22:import java.lang.reflect.Method;

aaime avatar Aug 13 '25 12:08 aaime

I had a go at all reflective access in the supported modules (and ignored legacy ones).

FactoryCache/RegistryMode

These both use reflection to access public API. This is not forbidden per se, and as long as ImageN does not embrace modularity, access is granted. The day we want to use a modular description, then then module-info for imagen-core will have to declare it depends on java.desktop via:

module org.imagen.core {
    requires java.desktop;
   ...

JAI, ImageUtil, CollectionRegistryMode, RenderableCollectionRegistryMode, RenderableRegistryMode and RenderedRegistryMode

JAI depends on java.lang.reflect.Method because KEY_DEFAULT_COLOR_MODEL_METHOD is of such type. There is no default value for it, whether the method usage is allowed or not, it fully depends on the caller setting that hint. The implementations in ImageN provide method references to ImageN classes themselves. So not a problem per se.

ParameterListDescriptorImpl

Reflective access is used to validate that an instance of EnumeratedParameter has a valid value. Currently the reflection is performed on public methods of ImageN own classes, so it's fine. Eventual new operator provided by downstreams may have to open access, in case of modular applications.

PointOpImage

This one uses reflection to find out if it makes sense to use "in place" operations (I believe it means changing the source tile directly). To do so, it checks whether the source operation has overridden the getTile method, using reflection. Again, this is going to operate mostly on ImageN classes, but if one provided an external operation, in the worst case access to the method is guarded with a try/catch.

RenderableGraphics and TiledImageGraphics

These classes use method reflection extensively to schedule operations against a wrapped Graphics2D. But it's doing so against public methods, so no issue.

DataBufferDouble, DataBufferFloat and DataBufferUtils

These classes are at the center of much reflective access action. They were setup to allow compatibility with JDK older than 1.4, which did not have the double/float data buffer classes, but nowadays they're no longer necessary. Moved them to legacy and switched the supported modules to use JDK classes instead.

ImagingException

It's using reflection to access Throwable.getCause, because it has been introduced in JDK 1.4 and JAI was trying to be compatible with older Java versions too.

ImageReadOpImage

This one is looking for a public "clone" method in ImageReadParam subclasses though reflection. If the method is not found, it catches the exception and moves on. No issue here.

AbstractJiffleRuntime

This one uses reflection to grab a field of a class it generated though Janino. Should not be an issue as the code is generated in the "org.eclipse.imagen.media.jiffle.runtime" package.

RenderedImageBrowser

First off, this module is just a debugging tool, I don't see it being used in a Swing application nowadays. That said:

  • RenderedImageBrowser and HTMLRenderers are using java.lang.reflect.Arrays to get array length and item contents. This should be fine.
  • RenderedImageInfoPanel is using Field/Modifier to enumerate the hints in the JAI class (all KEY_.... public static fields). Again, going after public information in the ImageN project, not an issue.

ColorModel/SampleModer serialization support

The classes in the org.eclipse.imagen.media.serialize are used to serialize on disk the GeoTools image mosaic sample image, as the SampleModel and ColorModel classes are not serializable (if it wasn't for that use case, the classes in this package would have been moved to legacy modules).

Going class by class:

  • ColorModelState is using Method and Modified to handle unforeseen ColorSpace subclasses. If they have a getInstance method with no parameter, they can be serialized even if unkonwn. Not something we'd be hitting normally, and the code is handling exceptions, so it should be safe for normal usage (actually for normal color spaces, the code is not run at all).
  • InterfaceState is weird... it's used to serialize/deserialize a method implementing multiple interfaces, when a straight serializer cannot be found (e.g., one for the well known classes). It seems to be working again on public API anyways, and I don't see how we're going to hit it in GeoTools/GeoServer use cases. Does not seem to be a problem, if it becomes one, it's a matter of opening the modules it's trying to read from (custom ones, as we have serializers for the common classes).
  • RenderingHintsState can use reflection to find a Hint that is declared as a public constant in a class. Since the hints we use are in the JAI class, that does not seem to be a problem. Again, if a third party uses a custom hint is used for a custom operation, then it will be a matter of opening that module. Not used anyways by GeoTools image mosaic use case.
  • SerializerImpl uses constructor to create SerializableStateImpl subclasses (found in ImageN) and to find the getSupportedClasses, permitsSubclasses static methods found in them. Again, using publicly avaiable API in the same package, no problem.

Conclusion

The remaining usage of reflection in ImageN appears to be non problematic. The usage of reflection that was there to handle pre/post Java 1.4 differences is going to be removed, I have a PR in the making.

aaime avatar Sep 18 '25 17:09 aaime

@aaime checking in to see if this was resolved.

jodygarnett avatar Oct 07 '25 00:10 jodygarnett