Cache projectsUsingPlugin list to avoid O(N²) reactor scan

InstallMojo.execute() computes the list of reactor projects using
the install plugin on every module invocation by streaming over all
projects and calling getPluginsAsMap() for each one. In a 4383-module
reactor, this produces ~19.2M filter evaluations (4383² calls).

JFR profiling shows this as 5.6% of total CPU time, all in
PluginContainer.getPluginsAsMap() called from usingPlugin().

Fix: cache the computed list in the first reactor project's plugin
context on first invocation. The list is invariant during a build —
which projects have the install plugin configured does not change
between module invocations. This turns O(N²) into O(N).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
diff --git a/src/main/java/org/apache/maven/plugins/install/InstallMojo.java b/src/main/java/org/apache/maven/plugins/install/InstallMojo.java
index d002520..f3183f5 100644
--- a/src/main/java/org/apache/maven/plugins/install/InstallMojo.java
+++ b/src/main/java/org/apache/maven/plugins/install/InstallMojo.java
@@ -98,6 +98,7 @@
     }
 
     private static final String INSTALL_PROCESSED_MARKER = InstallMojo.class.getName() + ".processed";
+    private static final String PROJECTS_USING_PLUGIN_KEY = InstallMojo.class.getName() + ".projectsUsingPlugin";
 
     public InstallMojo() {}
 
@@ -120,6 +121,26 @@
         return pluginContext.containsKey(INSTALL_PROCESSED_MARKER);
     }
 
+    /**
+     * Returns the list of reactor projects that have this plugin configured, cached on first call.
+     * The list is invariant during a build and is stored in the current project's plugin
+     * context to avoid recomputing it on every module invocation (O(N) total instead of O(N²)).
+     */
+    @SuppressWarnings("unchecked")
+    private List<Project> getProjectsUsingPlugin() {
+        List<Project> allProjects = session.getProjects();
+        if (allProjects.isEmpty()) {
+            return List.of();
+        }
+        Map<String, Object> ctx = session.getPluginContext(allProjects.get(0));
+        List<Project> cached = (List<Project>) ctx.get(PROJECTS_USING_PLUGIN_KEY);
+        if (cached == null) {
+            cached = allProjects.stream().filter(this::usingPlugin).collect(Collectors.toList());
+            ctx.put(PROJECTS_USING_PLUGIN_KEY, cached);
+        }
+        return cached;
+    }
+
     private boolean usingPlugin(Project project) {
         Plugin plugin = project.getBuild().getPluginsAsMap().get("org.apache.maven.plugins:maven-install-plugin");
         return plugin != null
@@ -144,8 +165,7 @@
             }
         }
 
-        List<Project> projectsUsingPlugin =
-                session.getProjects().stream().filter(this::usingPlugin).collect(Collectors.toList());
+        List<Project> projectsUsingPlugin = getProjectsUsingPlugin();
         if (allProjectsMarked(projectsUsingPlugin)) {
             for (Project reactorProject : projectsUsingPlugin) {
                 State state = getState(reactorProject);