[Git][java-team/libpf4j-java][upstream] New upstream version 3.13.0+dfsg

Pierre Gruet (@pgt) gitlab at salsa.debian.org
Thu Oct 2 05:58:05 BST 2025



Pierre Gruet pushed to branch upstream at Debian Java Maintainers / libpf4j-java


Commits:
cb0a4dc8 by Pierre Gruet at 2025-10-01T23:38:21+02:00
New upstream version 3.13.0+dfsg
- - - - -


24 changed files:

- CHANGELOG.md
- README.md
- demo/maven/api/pom.xml
- demo/maven/app/pom.xml
- demo/maven/plugins/plugin1/pom.xml
- demo/maven/plugins/plugin2/pom.xml
- demo/maven/plugins/pom.xml
- demo/maven/pom.xml
- maven-archetypes/quickstart/pom.xml
- pf4j/pom.xml
- pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java
- pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
- pf4j/src/main/java/org/pf4j/PluginClassLoader.java
- pf4j/src/main/java/org/pf4j/PluginState.java
- pf4j/src/main/java/org/pf4j/PluginStateEvent.java
- pf4j/src/main/java/org/pf4j/SingletonExtensionFactory.java
- pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java
- pf4j/src/test/java/org/pf4j/AbstractPluginManagerTest.java
- pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java
- pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java
- pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java
- pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java
- pf4j/src/test/java/org/pf4j/test/JavaSources.java
- pom.xml


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -4,6 +4,24 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ### [Unreleased][unreleased]
 
+#### Fixed
+- [#595]: Fix StackOverFlow error on Kotlin classes without @Extension
+
+#### Changed
+- [#594]: Always check for ClassLoaders differences in AbstractExtensionFinder
+
+#### Added
+
+#### Removed
+
+### [3.12.1] - 2024-10-12
+
+#### Fixed
+- [#586]: Plugin Extensions should be detected with any ClassLoadingStrategy
+- [#590]: Fix fire PluginStateEvent for STOPPED state
+
+### [3.12.0] - 2024-06-25
+
 #### Fixed
 - [#576]: Error due to incorrect dependency update after plugin deletion
 
@@ -14,8 +32,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 #### Added
 - Add PluginClassLoader#isClosed method
 
-#### Removed
-
 ### [3.11.1] - 2024-05-19
 
 #### Fixed
@@ -448,7 +464,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - [#41]: Added plugin archive source abstraction
 - Added test for DefaultPluginRepository
 
-[unreleased]: https://github.com/decebals/pf4j/compare/release-3.11.1...HEAD
+[unreleased]: https://github.com/decebals/pf4j/compare/release-3.12.1...HEAD
+[3.12.1]: https://github.com/decebals/pf4j/compare/release-3.12.0...release-3.12.1
+[3.12.0]: https://github.com/decebals/pf4j/compare/release-3.11.1...release-3.12.0
 [3.11.1]: https://github.com/decebals/pf4j/compare/release-3.11.0...release-3.11.1
 [3.11.0]: https://github.com/decebals/pf4j/compare/release-3.10.0...release-3.11.0
 [3.10.0]: https://github.com/decebals/pf4j/compare/release-3.9.0...release-3.10.0
@@ -481,6 +499,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 [0.11.0]: https://github.com/decebals/pf4j/compare/release-0.10.0...release-0.11.0
 [0.10.0]: https://github.com/decebals/pf4j/compare/release-0.9.0...release-0.10.0
 
+[#595]: https://github.com/pf4j/pf4j/pull/595
+[#594]: https://github.com/pf4j/pf4j/issues/594
+[#590]: https://github.com/pf4j/pf4j/issues/590
+[#586]: https://github.com/pf4j/pf4j/pull/586
 [#584]: https://github.com/pf4j/pf4j/pull/584
 [#582]: https://github.com/pf4j/pf4j/issues/582
 [#576]: https://github.com/pf4j/pf4j/issues/576


=====================================
README.md
=====================================
@@ -10,7 +10,7 @@ Plugin Framework for Java (PF4J)
 A plugin is a way for a third party to extend the functionality of an application. A plugin implements extension points
 declared by application or other plugins. Also, a plugin can define extension points.  
 
-**NOTE:** Starting with version 0.9 you can define an extension directly in the application jar (you're not obligated to put the extension in a plugin - you can see this extension as a default/system extension). See [WhazzupGreeting](https://github.com/pf4j/pf4j/blob/master/demo/app/src/main/java/org/pf4j/demo/WhazzupGreeting.java) for a real example.  
+**NOTE:** Starting with version 0.9 you can define an extension directly in the application jar (you're not obligated to put the extension in a plugin - you can see this extension as a default/system extension). See [WhazzupGreeting](https://github.com/pf4j/pf4j/blob/master/demo/gradle/app/src/main/java/org/pf4j/demo/WhazzupGreeting.java) for a real example.  
 
 Features/Benefits
 -------------------


=====================================
demo/maven/api/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j.demo</groupId>
         <artifactId>pf4j-demo-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j-demo-api</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>jar</packaging>
     <name>Demo Api</name>
 


=====================================
demo/maven/app/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j.demo</groupId>
         <artifactId>pf4j-demo-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j-demo-app</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>jar</packaging>
     <name>Demo App</name>
 


=====================================
demo/maven/plugins/plugin1/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j.demo</groupId>
         <artifactId>pf4j-demo-plugins</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j-demo-plugin1</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>jar</packaging>
     <name>Demo Plugin #1</name>
 


=====================================
demo/maven/plugins/plugin2/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j.demo</groupId>
         <artifactId>pf4j-demo-plugins</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j-demo-plugin2</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>jar</packaging>
     <name>Demo Plugin #2</name>
 


=====================================
demo/maven/plugins/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j.demo</groupId>
         <artifactId>pf4j-demo-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j-demo-plugins</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>pom</packaging>
     <name>Demo Plugins Parent</name>
 


=====================================
demo/maven/pom.xml
=====================================
@@ -4,14 +4,14 @@
     <parent>
         <groupId>org.pf4j</groupId>
         <artifactId>pf4j-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
         <relativePath>../..</relativePath>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.pf4j.demo</groupId>
     <artifactId>pf4j-demo-parent</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>pom</packaging>
     <name>Demo Parent</name>
 


=====================================
maven-archetypes/quickstart/pom.xml
=====================================
@@ -4,7 +4,7 @@
     <parent>
         <groupId>org.pf4j</groupId>
         <artifactId>pf4j-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 


=====================================
pf4j/pom.xml
=====================================
@@ -4,12 +4,12 @@
     <parent>
         <groupId>org.pf4j</groupId>
         <artifactId>pf4j-parent</artifactId>
-        <version>3.12.0</version>
+        <version>3.13.0</version>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
     <artifactId>pf4j</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>jar</packaging>
     <name>PF4J</name>
     <description>Plugin Framework for Java</description>
@@ -192,6 +192,18 @@
             <artifactId>compile-testing</artifactId>
             <version>0.21.0</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib</artifactId>
+            <version>2.0.21</version>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 


=====================================
pf4j/src/main/java/org/pf4j/AbstractExtensionFinder.java
=====================================
@@ -88,7 +88,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
 
         if (pluginId != null) {
             PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
-            if (PluginState.STARTED != pluginWrapper.getPluginState()) {
+            if (!pluginWrapper.getPluginState().isStarted()) {
                 return result;
             }
 
@@ -107,7 +107,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
                     // If optional dependencies are used, the class loader might not be able
                     // to load the extension class because of missing optional dependencies.
                     //
-                    // Therefore we're extracting the extension annotation via asm, in order
+                    // Therefore, we're extracting the extension annotation via asm, in order
                     // to extract the required plugins for an extension. Only if all required
                     // plugins are currently available and started, the corresponding
                     // extension is loaded through the class loader.
@@ -121,7 +121,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
                     List<String> missingPluginIds = new ArrayList<>();
                     for (String requiredPluginId : extensionInfo.getPlugins()) {
                         PluginWrapper requiredPlugin = pluginManager.getPlugin(requiredPluginId);
-                        if (requiredPlugin == null || !PluginState.STARTED.equals(requiredPlugin.getPluginState())) {
+                        if (requiredPlugin == null || !requiredPlugin.getPluginState().isStarted()) {
                             missingPluginIds.add(requiredPluginId);
                         }
                     }
@@ -146,8 +146,8 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
                     log.debug("Added extension '{}' with ordinal {}", className, extensionWrapper.getOrdinal());
                 } else {
                     log.trace("'{}' is not an extension for extension point '{}'", className, type.getName());
-                    if (RuntimeMode.DEVELOPMENT.equals(pluginManager.getRuntimeMode())) {
-                        checkDifferentClassLoaders(type, extensionClass);
+                    if (checkDifferentClassLoaders(type, extensionClass)) {
+                        log.error("Different class loaders: '{}' (E) and '{}' (EP)", extensionClass.getClassLoader(), type.getClassLoader());
                     }
                 }
             } catch (ClassNotFoundException | NoClassDefFoundError e) {
@@ -179,7 +179,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
 
         if (pluginId != null) {
             PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId);
-            if (PluginState.STARTED != pluginWrapper.getPluginState()) {
+            if (!pluginWrapper.getPluginState().isStarted()) {
                 return result;
             }
 
@@ -231,13 +231,13 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
         // clear cache
         entries = null;
 
-        // By default we're assuming, that no checks for extension dependencies are necessary.
+        // By default, we're assuming, that no checks for extension dependencies are necessary.
         //
         // A plugin, that has an optional dependency to other plugins, might lead to unloadable
         // Java classes (NoClassDefFoundError) at application runtime due to possibly missing
-        // dependencies. Therefore we're enabling the check for optional extensions, if the
+        // dependencies. Therefore, we're enabling the check for optional extensions, if the
         // started plugin contains at least one optional plugin dependency.
-        if (checkForExtensionDependencies == null && PluginState.STARTED.equals(event.getPluginState())) {
+        if (checkForExtensionDependencies == null && event.getPluginState().isStarted()) {
             for (PluginDependency dependency : event.getPlugin().getDescriptor().getDependencies()) {
                 if (dependency.isOptional()) {
                     log.debug("Enable check for extension dependencies via ASM.");
@@ -255,7 +255,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
      * <p>
      * This feature is enabled by default, if at least one available plugin makes use of
      * optional plugin dependencies. Those optional plugins might not be available at runtime.
-     * Therefore any extension is checked by default against available plugins before its
+     * Therefore, any extension is checked by default against available plugins before its
      * instantiation.
      * <p>
      * Notice: This feature requires the optional <a href="https://asm.ow2.io/">ASM library</a>
@@ -274,7 +274,7 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
      * <p>
      * This feature is enabled by default, if at least one available plugin makes use of
      * optional plugin dependencies. Those optional plugins might not be available at runtime.
-     * Therefore any extension is checked by default against available plugins before its
+     * Therefore, any extension is checked by default against available plugins before its
      * instantiation.
      * <p>
      * Notice: This feature requires the optional <a href="https://asm.ow2.io/">ASM library</a>
@@ -360,7 +360,12 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
         // search recursively through all annotations
         for (Annotation annotation : clazz.getAnnotations()) {
             Class<? extends Annotation> annotationClass = annotation.annotationType();
-            if (!annotationClass.getName().startsWith("java.lang.annotation")) {
+            if (!annotationClass.getName().startsWith("java.lang.annotation") && !annotationClass.getName().startsWith("kotlin")) {
+                // In case an annotation is annotated with itself,
+                // an example would be @Target, which is ignored by the check above
+                // however, other libraries might do similar things
+                if (annotationClass.equals(clazz)) continue;
+
                 Extension extensionAnnotation = findExtensionAnnotation(annotationClass);
                 if (extensionAnnotation != null) {
                     return extensionAnnotation;
@@ -371,15 +376,13 @@ public abstract class AbstractExtensionFinder implements ExtensionFinder, Plugin
         return null;
     }
 
-    private void checkDifferentClassLoaders(Class<?> type, Class<?> extensionClass) {
+    boolean checkDifferentClassLoaders(Class<?> type, Class<?> extensionClass) {
         ClassLoader typeClassLoader = type.getClassLoader(); // class loader of extension point
         ClassLoader extensionClassLoader = extensionClass.getClassLoader();
         boolean match = ClassUtils.getAllInterfacesNames(extensionClass).contains(type.getSimpleName());
-        if (match && !extensionClassLoader.equals(typeClassLoader)) {
-            // in this scenario the method 'isAssignableFrom' returns only FALSE
-            // see http://www.coderanch.com/t/557846/java/java/FWIW-FYI-isAssignableFrom-isInstance-differing
-            log.error("Different class loaders: '{}' (E) and '{}' (EP)", extensionClassLoader, typeClassLoader);
-        }
+        // in this scenario the method 'isAssignableFrom' returns only FALSE
+        // see http://www.coderanch.com/t/557846/java/java/FWIW-FYI-isAssignableFrom-isInstance-differing
+        return match && extensionClassLoader != typeClassLoader;
     }
 
 }


=====================================
pf4j/src/main/java/org/pf4j/AbstractPluginManager.java
=====================================
@@ -312,7 +312,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         PluginState pluginState;
         try {
             pluginState = stopPlugin(pluginId, false);
-            if (PluginState.STARTED == pluginState) {
+            if (pluginState.isStarted()) {
                 return false;
             }
 
@@ -358,7 +358,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         PluginWrapper pluginWrapper = getPlugin(pluginId);
         // stop the plugin if it's started
         PluginState pluginState = stopPlugin(pluginId);
-        if (PluginState.STARTED == pluginState) {
+        if (pluginState.isStarted()) {
             log.error("Failed to stop plugin '{}' on delete", pluginId);
             return false;
         }
@@ -385,7 +385,7 @@ public abstract class AbstractPluginManager implements PluginManager {
     public void startPlugins() {
         for (PluginWrapper pluginWrapper : resolvedPlugins) {
             PluginState pluginState = pluginWrapper.getPluginState();
-            if ((PluginState.DISABLED != pluginState) && (PluginState.STARTED != pluginState)) {
+            if (!pluginState.isDisabled() && !pluginState.isStarted()) {
                 try {
                     log.info("Start plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()));
                     pluginWrapper.getPlugin().start();
@@ -413,7 +413,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         PluginWrapper pluginWrapper = getPlugin(pluginId);
         PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
         PluginState pluginState = pluginWrapper.getPluginState();
-        if (PluginState.STARTED == pluginState) {
+        if (pluginState.isStarted()) {
             log.debug("Already started plugin '{}'", getPluginLabel(pluginDescriptor));
             return PluginState.STARTED;
         }
@@ -423,7 +423,7 @@ public abstract class AbstractPluginManager implements PluginManager {
             return pluginState;
         }
 
-        if (PluginState.DISABLED == pluginState) {
+        if (pluginState.isDisabled()) {
             // automatically enable plugin on manual plugin start
             if (!enablePlugin(pluginId)) {
                 return pluginState;
@@ -458,7 +458,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         while (itr.hasNext()) {
             PluginWrapper pluginWrapper = itr.next();
             PluginState pluginState = pluginWrapper.getPluginState();
-            if (PluginState.STARTED == pluginState) {
+            if (pluginState.isStarted()) {
                 try {
                     log.info("Stop plugin '{}'", getPluginLabel(pluginWrapper.getDescriptor()));
                     pluginWrapper.getPlugin().stop();
@@ -516,7 +516,7 @@ public abstract class AbstractPluginManager implements PluginManager {
         pluginWrapper.setPluginState(PluginState.STOPPED);
         getStartedPlugins().remove(pluginWrapper);
 
-        firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STOPPED));
+        firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, PluginState.STARTED));
 
         return PluginState.STOPPED;
     }
@@ -550,11 +550,11 @@ public abstract class AbstractPluginManager implements PluginManager {
         PluginWrapper pluginWrapper = getPlugin(pluginId);
         PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
         PluginState pluginState = pluginWrapper.getPluginState();
-        if (PluginState.DISABLED == pluginState) {
+        if (pluginState.isDisabled()) {
             log.debug("Already disabled plugin '{}'", getPluginLabel(pluginDescriptor));
             return true;
-        } else if (PluginState.STARTED == pluginState) {
-            if (PluginState.STOPPED != stopPlugin(pluginId)) {
+        } else if (pluginState.isStarted()) {
+            if (!stopPlugin(pluginId).isStopped()) {
                 log.error("Failed to stop plugin '{}' on disable", getPluginLabel(pluginDescriptor));
                 return false;
             }
@@ -582,7 +582,7 @@ public abstract class AbstractPluginManager implements PluginManager {
 
         PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
         PluginState pluginState = pluginWrapper.getPluginState();
-        if (PluginState.DISABLED != pluginState) {
+        if (!pluginState.isDisabled()) {
             log.debug("Plugin '{}' is not disabled", getPluginLabel(pluginDescriptor));
             return true;
         }
@@ -842,7 +842,7 @@ public abstract class AbstractPluginManager implements PluginManager {
             PluginWrapper pluginWrapper = plugins.get(pluginId);
             if (unresolvedPlugins.remove(pluginWrapper)) {
                 PluginState pluginState = pluginWrapper.getPluginState();
-                if (pluginState != PluginState.DISABLED) {
+                if (!pluginState.isDisabled()) {
                     pluginWrapper.setPluginState(PluginState.RESOLVED);
                 }
 


=====================================
pf4j/src/main/java/org/pf4j/PluginClassLoader.java
=====================================
@@ -181,8 +181,9 @@ public class PluginClassLoader extends URLClassLoader {
      */
     @Override
     public URL getResource(String name) {
+        ClassLoadingStrategy loadingStrategy = getClassLoadingStrategy(name);
         log.trace("Received request to load resource '{}'", name);
-        for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) {
+        for (ClassLoadingStrategy.Source classLoadingSource : loadingStrategy.getSources()) {
             URL url = null;
             switch (classLoadingSource) {
                 case APPLICATION:
@@ -210,9 +211,9 @@ public class PluginClassLoader extends URLClassLoader {
     @Override
     public Enumeration<URL> getResources(String name) throws IOException {
         List<URL> resources = new ArrayList<>();
-
+        ClassLoadingStrategy loadingStrategy = getClassLoadingStrategy(name);
         log.trace("Received request to load resources '{}'", name);
-        for (ClassLoadingStrategy.Source classLoadingSource : classLoadingStrategy.getSources()) {
+        for (ClassLoadingStrategy.Source classLoadingSource : loadingStrategy.getSources()) {
             switch (classLoadingSource) {
                 case APPLICATION:
                     if (getParent() != null) {
@@ -231,6 +232,14 @@ public class PluginClassLoader extends URLClassLoader {
         return Collections.enumeration(resources);
     }
 
+    private ClassLoadingStrategy getClassLoadingStrategy(String name) {
+        ClassLoadingStrategy loadingStrategy = classLoadingStrategy;
+        if (LegacyExtensionFinder.EXTENSIONS_RESOURCE.equals(name)) {
+            loadingStrategy = ClassLoadingStrategy.PAD;
+        }
+        return loadingStrategy;
+    }
+
     /**
      * Closes this class loader.
      * <p>


=====================================
pf4j/src/main/java/org/pf4j/PluginState.java
=====================================
@@ -72,8 +72,57 @@ public enum PluginState {
         this.status = status;
     }
 
+    /**
+     * Returns {@code true} if the value is {@link #CREATED}.
+     */
+    public boolean isCreated() {
+        return this == CREATED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #DISABLED}.
+     */
+    public boolean isDisabled() {
+        return this == DISABLED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #RESOLVED}.
+     */
+    public boolean isResolved() {
+        return this == RESOLVED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #STARTED}.
+     */
+    public boolean isStarted() {
+        return this == STARTED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #STOPPED}.
+     */
+    public boolean isStopped() {
+        return this == STOPPED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #FAILED}.
+     */
+    public boolean isFailed() {
+        return this == FAILED;
+    }
+
+    /**
+     * Returns {@code true} if the value is {@link #UNLOADED}.
+     */
+    public boolean isUnloaded() {
+        return this == UNLOADED;
+    }
+
     public boolean equals(String status) {
-        return (this.status.equalsIgnoreCase(status));
+        return this.status.equalsIgnoreCase(status);
     }
 
     @Override


=====================================
pf4j/src/main/java/org/pf4j/PluginStateEvent.java
=====================================
@@ -16,6 +16,7 @@
 package org.pf4j;
 
 import java.util.EventObject;
+import java.util.Objects;
 
 /**
  * Event object that indicates a change in the state of a plugin.
@@ -83,4 +84,17 @@ public class PluginStateEvent extends EventObject {
                 ']';
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PluginStateEvent that = (PluginStateEvent) o;
+        return Objects.equals(plugin, that.plugin) && oldState == that.oldState;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(plugin, oldState);
+    }
+
 }


=====================================
pf4j/src/main/java/org/pf4j/SingletonExtensionFactory.java
=====================================
@@ -39,7 +39,7 @@ public class SingletonExtensionFactory extends DefaultExtensionFactory {
         cache = new HashMap<>();
 
         pluginManager.addPluginStateListener(event -> {
-            if (event.getPluginState() != PluginState.STARTED) {
+            if (!event.getPluginState().isStarted()) {
                 cache.remove(event.getPlugin().getPluginClassLoader());
             }
         });


=====================================
pf4j/src/test/java/org/pf4j/AbstractExtensionFinderTest.java
=====================================
@@ -15,6 +15,7 @@
  */
 package org.pf4j;
 
+import kotlin.sequences.Sequence;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -35,13 +36,14 @@ import java.util.Set;
 import java.util.UUID;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 /**
  * @author Mario Franco
  */
-public class AbstractExtensionFinderTest {
+class AbstractExtensionFinderTest {
 
     private PluginManager pluginManager;
 
@@ -71,7 +73,7 @@ public class AbstractExtensionFinderTest {
      * Test of {@link AbstractExtensionFinder#find(Class)}.
      */
     @Test
-    public void testFindFailType() {
+    void testFindFailType() {
         ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {
 
             @Override
@@ -93,7 +95,7 @@ public class AbstractExtensionFinderTest {
      * Test of {@link AbstractExtensionFinder#find(Class)}.
      */
     @Test
-    public void testFindFromClasspath() {
+    void testFindFromClasspath() {
         ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {
 
             @Override
@@ -122,7 +124,7 @@ public class AbstractExtensionFinderTest {
      * Test of {@link AbstractExtensionFinder#find(Class, String)}.
      */
     @Test
-    public void testFindFromPlugin() {
+    void testFindFromPlugin() {
         ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {
 
             @Override
@@ -161,7 +163,7 @@ public class AbstractExtensionFinderTest {
      * Test of {@link AbstractExtensionFinder#findClassNames(String)}.
      */
     @Test
-    public void testFindClassNames() {
+    void testFindClassNames() {
         ExtensionFinder instance = new AbstractExtensionFinder(pluginManager) {
 
             @Override
@@ -200,7 +202,7 @@ public class AbstractExtensionFinderTest {
      * Test of {@link org.pf4j.AbstractExtensionFinder#find(java.lang.String)}.
      */
     @Test
-    public void testFindExtensionWrappersFromPluginId() {
+    void testFindExtensionWrappersFromPluginId() {
         // complicate the test to show hot to deal with dynamic Java classes (generated at runtime from sources)
         PluginWrapper plugin3 = mock(PluginWrapper.class);
         JavaFileObject object = JavaSources.compile(DefaultExtensionFactoryTest.FailTestExtension);
@@ -251,27 +253,59 @@ public class AbstractExtensionFinderTest {
     }
 
     @Test
-    public void findExtensionAnnotation() {
-        List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting, JavaSources.WhazzupGreeting);
+    void findExtensionAnnotation() {
+        List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
         assertEquals(2, generatedFiles.size());
 
         Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
-        Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
+        Class<?> clazz = loadedClasses.get(JavaSources.WHAZZUP_GREETING_CLASS_NAME);
         Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
         Assertions.assertNotNull(extension);
     }
 
     @Test
-    public void findExtensionAnnotationThatMissing() {
-        List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.Greeting,
+    void findExtensionAnnotationThatMissing() {
+        List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.GREETING,
             ExtensionAnnotationProcessorTest.SpinnakerExtension_NoExtension,
             ExtensionAnnotationProcessorTest.WhazzupGreeting_SpinnakerExtension);
         assertEquals(3, generatedFiles.size());
 
         Map<String, Class<?>> loadedClasses = new JavaFileObjectClassLoader().load(generatedFiles);
-        Class<?> clazz = loadedClasses.get("test.WhazzupGreeting");
+        Class<?> clazz = loadedClasses.get(JavaSources.WHAZZUP_GREETING_CLASS_NAME);
         Extension extension = AbstractExtensionFinder.findExtensionAnnotation(clazz);
         Assertions.assertNull(extension);
     }
 
+    // This is a regression test, as this caused an StackOverflowError with the previous implementation
+    @Test
+    public void runningOnNonExtensionKotlinClassDoesNotThrowException() {
+        Extension result = AbstractExtensionFinder.findExtensionAnnotation(Sequence.class);
+
+        Assertions.assertNull(result);
+    }
+
+    @Test
+    void checkDifferentClassLoaders() {
+        AbstractExtensionFinder extensionFinder = new AbstractExtensionFinder(pluginManager) {
+
+            @Override
+            public Map<String, Set<String>> readPluginsStorages() {
+                return Collections.emptyMap();
+            }
+
+            @Override
+            public Map<String, Set<String>> readClasspathStorages() {
+                return Collections.emptyMap();
+            }
+
+        };
+
+        List<JavaFileObject> generatedFiles = JavaSources.compileAll(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
+        assertEquals(2, generatedFiles.size());
+        Class<?> extensionPointClass = new JavaFileObjectClassLoader().load(generatedFiles).get(JavaSources.GREETING_CLASS_NAME);
+        Class<?> extensionClass = new JavaFileObjectClassLoader().load(generatedFiles).get(JavaSources.WHAZZUP_GREETING_CLASS_NAME);
+
+        assertTrue(extensionFinder.checkDifferentClassLoaders(extensionPointClass, extensionClass));
+    }
+
 }


=====================================
pf4j/src/test/java/org/pf4j/AbstractPluginManagerTest.java
=====================================
@@ -138,6 +138,25 @@ public class AbstractPluginManagerTest {
         verify(pluginManager, times(1)).resolveDependencies();
     }
 
+    @Test
+    void stopPluginFirePluginStateListeners() {
+        PluginStateListener pluginStateListener = mock(PluginStateListener.class);
+        pluginManager.addPluginStateListener(pluginStateListener);
+
+        String pluginId = "plugin1";
+        PluginWrapper pluginWrapper = createPluginWrapper(pluginId);
+        pluginWrapper.setPluginState(PluginState.STARTED);
+
+        doReturn(pluginWrapper).when(pluginManager).getPlugin(pluginId);
+        doNothing().when(pluginManager).checkPluginId(pluginId);
+        doReturn(new ArrayList<>(Arrays.asList(pluginWrapper))).when(pluginManager).getStartedPlugins();
+
+        pluginManager.stopPlugin(pluginId, false);
+
+        PluginStateEvent event = new PluginStateEvent(pluginManager, pluginWrapper, PluginState.STARTED);
+        verify(pluginStateListener).pluginStateChanged(event);
+    }
+
     private PluginWrapper createPluginWrapper(String pluginId, String... dependencies) {
         PluginDescriptor pluginDescriptor = new DefaultPluginDescriptor()
             .setPluginId(pluginId)


=====================================
pf4j/src/test/java/org/pf4j/DefaultPluginManagerTest.java
=====================================
@@ -42,7 +42,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
-import static org.pf4j.test.JavaSources.Greeting;
 
 class DefaultPluginManagerTest {
 
@@ -203,7 +202,7 @@ class DefaultPluginManagerTest {
         assertFalse(pluginZip.file().exists());
 
         Optional<PluginStateEvent> unloadedEvent = receivedEvents.stream()
-            .filter(event -> event.getPluginState() == PluginState.UNLOADED)
+            .filter(event -> event.getPluginState().isUnloaded())
             .findFirst();
 
         assertTrue(unloadedEvent.isPresent());
@@ -226,7 +225,7 @@ class DefaultPluginManagerTest {
         assertFalse(pluginJar.file().exists());
 
         Optional<PluginStateEvent> unloadedEvent = receivedEvents.stream()
-            .filter(event -> event.getPluginState() == PluginState.UNLOADED)
+            .filter(event -> event.getPluginState().isUnloaded())
             .findFirst();
 
         assertTrue(unloadedEvent.isPresent());
@@ -368,7 +367,7 @@ class DefaultPluginManagerTest {
         assertFalse(pluginZip.file().exists());
 
         Optional<PluginStateEvent> unloadedEvent = receivedEvents.stream()
-            .filter(event -> event.getPluginState() == PluginState.UNLOADED)
+            .filter(event -> event.getPluginState().isUnloaded())
             .findFirst();
 
         assertTrue(unloadedEvent.isPresent());
@@ -441,7 +440,7 @@ class DefaultPluginManagerTest {
 
     @Test
     void unloadPlugin() throws IOException, ClassNotFoundException {
-        JavaFileObject object = JavaSources.compile(Greeting);
+        JavaFileObject object = JavaSources.compile(JavaSources.GREETING);
         String pluginClassName = JavaFileObjectUtils.getClassName(object);
         byte[] pluginBytes = JavaFileObjectUtils.getAllBytes(object);
 


=====================================
pf4j/src/test/java/org/pf4j/ExtensionAnnotationProcessorTest.java
=====================================
@@ -125,7 +125,7 @@ public class ExtensionAnnotationProcessorTest {
     @Test
     public void options() {
         Compilation compilation = compiler().withOptions("-Ab=2", "-Ac=3")
-            .compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
+            .compile(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
         assertEquals(Compilation.Status.SUCCESS, compilation.status());
         Map<String, String> options = new HashMap<>();
         options.put("b", "2");
@@ -135,20 +135,20 @@ public class ExtensionAnnotationProcessorTest {
 
     @Test
     public void storage() {
-        Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
+        Compilation compilation = compile(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
         assertEquals(Compilation.Status.SUCCESS, compilation.status());
         assertEquals(annotationProcessor.getStorage().getClass(), LegacyExtensionStorage.class);
     }
 
     @Test
     public void compileWithoutError() {
-         Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
+         Compilation compilation = compile(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
          assertThat(compilation).succeededWithoutWarnings();
     }
 
     @Test
     public void compileWithError() {
-        Compilation compilation = compile(JavaSources.Greeting, WhazzupGreeting_NoExtensionPoint);
+        Compilation compilation = compile(JavaSources.GREETING, WhazzupGreeting_NoExtensionPoint);
         assertThat(compilation).failed();
         assertThat(compilation).hadErrorContaining("it doesn't implement ExtensionPoint")
             .inFile(WhazzupGreeting_NoExtensionPoint)
@@ -158,19 +158,19 @@ public class ExtensionAnnotationProcessorTest {
 
     @Test
     public void getExtensions() {
-        Compilation compilation = compile(JavaSources.Greeting, JavaSources.WhazzupGreeting);
+        Compilation compilation = compile(JavaSources.GREETING, JavaSources.WHAZZUP_GREETING);
         assertThat(compilation).succeededWithoutWarnings();
         Map<String, Set<String>> extensions = new HashMap<>();
-        extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting")));
+        extensions.put(JavaSources.GREETING_CLASS_NAME, new HashSet<>(Collections.singletonList(JavaSources.WHAZZUP_GREETING_CLASS_NAME)));
         assertEquals(extensions, annotationProcessor.getExtensions());
     }
 
     @Test
     public void compileNestedExtensionAnnotation() {
-        Compilation compilation = compile(JavaSources.Greeting, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension);
+        Compilation compilation = compile(JavaSources.GREETING, SpinnakerExtension, WhazzupGreeting_SpinnakerExtension);
         assertThat(compilation).succeededWithoutWarnings();
         Map<String, Set<String>> extensions = new HashMap<>();
-        extensions.put("test.Greeting", new HashSet<>(Collections.singletonList("test.WhazzupGreeting")));
+        extensions.put(JavaSources.GREETING_CLASS_NAME, new HashSet<>(Collections.singletonList(JavaSources.WHAZZUP_GREETING_CLASS_NAME)));
         assertEquals(extensions, annotationProcessor.getExtensions());
     }
 


=====================================
pf4j/src/test/java/org/pf4j/PluginClassLoaderTest.java
=====================================
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.pf4j.processor.LegacyExtensionStorage;
 import org.pf4j.test.PluginZip;
 import org.pf4j.util.FileUtils;
 
@@ -74,6 +75,7 @@ class PluginClassLoaderTest {
         createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-dependency-and-plugin"));
         createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-dependency"));
         createFile(parentClassPathBase.resolve("META-INF").resolve("file-in-both-parent-and-plugin"));
+        createFile(parentClassPathBase.resolve(LegacyExtensionStorage.EXTENSIONS_RESOURCE));
     }
 
     private static void createFile(Path pathToFile) throws IOException {
@@ -106,6 +108,7 @@ class PluginClassLoaderTest {
                 .addFile(Paths.get("classes/META-INF/dependency-file"), "dependency")
                 .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency-and-plugin"), "dependency")
                 .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency"), "dependency")
+                .addFile(Paths.get("classes/" + LegacyExtensionStorage.EXTENSIONS_RESOURCE), "dependency")
                 .build();
 
         FileUtils.expandIfZip(pluginDependencyZip.path());
@@ -148,6 +151,7 @@ class PluginClassLoaderTest {
                 .addFile(Paths.get("classes/META-INF/plugin-file"), "plugin")
                 .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-dependency-and-plugin"), "plugin")
                 .addFile(Paths.get("classes/META-INF/file-in-both-parent-and-plugin"), "plugin")
+                .addFile(Paths.get("classes/" + LegacyExtensionStorage.EXTENSIONS_RESOURCE), "plugin")
                 .build();
 
         FileUtils.expandIfZip(pluginZip.path());
@@ -325,6 +329,18 @@ class PluginClassLoaderTest {
         assertNumberOfResourcesAndFirstLineOfFirstElement(3, "parent", resources);
     }
 
+    @Test
+    void parentFirstGetExtensionsIndexExistsInParentAndDependencyAndPlugin() throws URISyntaxException, IOException {
+        URL resource = parentLastPluginClassLoader.getResource(LegacyExtensionFinder.EXTENSIONS_RESOURCE);
+        assertFirstLine("plugin", resource);
+    }
+
+    @Test
+    void parentLastGetExtensionsIndexExistsInParentAndDependencyAndPlugin() throws URISyntaxException, IOException {
+        URL resource = parentLastPluginClassLoader.getResource(LegacyExtensionFinder.EXTENSIONS_RESOURCE);
+        assertFirstLine("plugin", resource);
+    }
+
     @Test
     void isClosed() throws IOException {
         parentLastPluginClassLoader.close();


=====================================
pf4j/src/test/java/org/pf4j/SecurePluginManagerWrapperTest.java
=====================================
@@ -15,15 +15,6 @@
  */
 package org.pf4j;
 
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.List;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -33,6 +24,14 @@ import org.pf4j.test.TestExtension;
 import org.pf4j.test.TestExtensionPoint;
 import org.pf4j.test.TestPlugin;
 
+import java.io.IOException;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 public class SecurePluginManagerWrapperTest {
 
     private static final String OTHER_PLUGIN_ID = "test-plugin-2";


=====================================
pf4j/src/test/java/org/pf4j/test/JavaSources.java
=====================================
@@ -30,7 +30,8 @@ import static com.google.testing.compile.Compiler.javac;
  */
 public class JavaSources {
 
-    public static final JavaFileObject Greeting = JavaFileObjects.forSourceLines("Greeting",
+    public static final String GREETING_CLASS_NAME = "test.Greeting";
+    public static final JavaFileObject GREETING = JavaFileObjects.forSourceLines("Greeting",
         "package test;",
         "import org.pf4j.ExtensionPoint;",
         "",
@@ -38,7 +39,8 @@ public class JavaSources {
         "   String getGreeting();",
         "}");
 
-    public static final JavaFileObject WhazzupGreeting = JavaFileObjects.forSourceLines("WhazzupGreeting",
+    public static final String WHAZZUP_GREETING_CLASS_NAME = "test.WhazzupGreeting";
+    public static final JavaFileObject WHAZZUP_GREETING = JavaFileObjects.forSourceLines("WhazzupGreeting",
         "package test;",
         "import org.pf4j.Extension;",
         "",


=====================================
pom.xml
=====================================
@@ -10,7 +10,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.pf4j</groupId>
     <artifactId>pf4j-parent</artifactId>
-    <version>3.12.0</version>
+    <version>3.13.0</version>
     <packaging>pom</packaging>
     <name>PF4J Parent</name>
     <description>Plugin Framework for Java</description>
@@ -28,7 +28,7 @@
         <connection>scm:git:https://github.com/pf4j/pf4j.git</connection>
         <developerConnection>scm:git:git at github.com:pf4j/pf4j.git</developerConnection>
         <url>git at github.com/pf4j/pf4j.git</url>
-        <tag>release-3.12.0</tag>
+        <tag>release-3.13.0</tag>
     </scm>
 
     <distributionManagement>



View it on GitLab: https://salsa.debian.org/java-team/libpf4j-java/-/commit/cb0a4dc8fc2bfcb9f0c390e57329a8d63be0ca1f

-- 
View it on GitLab: https://salsa.debian.org/java-team/libpf4j-java/-/commit/cb0a4dc8fc2bfcb9f0c390e57329a8d63be0ca1f
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20251002/cf814fe1/attachment.htm>


More information about the pkg-java-commits mailing list