mthmulders commented on code in PR #12633:
URL: https://github.com/apache/maven/pull/12633#discussion_r3878476405


##########
impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java:
##########
@@ -0,0 +1,355 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.maven.cling.invoker.mvnup.goals;
+
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import eu.maveniverse.domtrip.Document;
+import eu.maveniverse.domtrip.Element;
+import org.apache.maven.api.cli.mvnup.UpgradeOptions;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Priority;
+import org.apache.maven.api.di.Singleton;
+import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
+import org.apache.maven.impl.JdkSourceLevelSupport;
+
+import static 
eu.maveniverse.domtrip.maven.MavenPomElements.Elements.ARTIFACT_ID;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.BUILD;
+import static 
eu.maveniverse.domtrip.maven.MavenPomElements.Elements.CONFIGURATION;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.GROUP_ID;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN;
+import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGINS;
+import static 
eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_MANAGEMENT;
+import static 
eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROPERTIES;
+
+/**
+ * Strategy for adding the {@code maven-toolchains-plugin} with the {@code 
select-jdk-toolchain}
+ * goal when the project's required {@code --source}/{@code --release} level 
is no longer supported
+ * by the running JDK.
+ *
+ * <p>This strategy detects the project's source level from:
+ * <ol>
+ *   <li>{@code maven.compiler.release} property</li>
+ *   <li>{@code maven.compiler.source} property</li>
+ *   <li>Compiler plugin {@code <configuration><release>} or {@code 
<source>}</li>
+ * </ol>
+ *
+ * <p>If the running JDK does not support the detected source level (per JEP 
182 retirement
+ * schedule), and the {@code maven-toolchains-plugin} is not already 
configured with the
+ * {@code select-jdk-toolchain} goal, this strategy adds it so that the 
plugin's built-in
+ * JDK discovery mechanism can find a compatible JDK at build time.
+ *
+ * @see <a href="https://openjdk.org/jeps/182";>JEP 182: Policy for Retiring 
javac -source and -target Options</a>
+ */
+@Named
+@Singleton
+@Priority(15)
+public class ToolchainPluginStrategy extends AbstractUpgradeStrategy {
+
+    static final String MAVEN_TOOLCHAINS_PLUGIN = "maven-toolchains-plugin";
+    static final String TOOLCHAINS_PLUGIN_GROUP_ID = 
"org.apache.maven.plugins";
+    static final String SELECT_JDK_TOOLCHAIN_GOAL = "select-jdk-toolchain";
+
+    private static final String MAVEN_COMPILER_RELEASE = 
"maven.compiler.release";
+    private static final String MAVEN_COMPILER_SOURCE = 
"maven.compiler.source";
+    private static final String MAVEN_COMPILER_PLUGIN = 
"maven-compiler-plugin";
+
+    @Override
+    public boolean isApplicable(UpgradeContext context) {
+        UpgradeOptions options = getOptions(context);
+
+        if (options.all().orElse(false)) {
+            return true;
+        }
+
+        // Default behavior: run when no specific options are provided,
+        // or when all options are explicitly disabled (safety net — toolchain
+        // setup should always run as it prevents build failures)
+        boolean noOptionsSpecified = options.all().isEmpty()
+                && options.infer().isEmpty()
+                && options.model().isEmpty()
+                && options.plugins().isEmpty()
+                && options.modelVersion().isEmpty();
+
+        boolean allOptionsDisabled = options.all().map(v -> !v).orElse(false)
+                && options.infer().map(v -> !v).orElse(false)
+                && options.model().map(v -> !v).orElse(false)
+                && options.plugins().map(v -> !v).orElse(false)
+                && options.modelVersion().isEmpty();
+
+        if (noOptionsSpecified || allOptionsDisabled) {
+            return true;
+        }
+
+        // Run when --model is explicitly set
+        if (options.model().isPresent()) {
+            return options.model().get();
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getDescription() {
+        return "Adding maven-toolchains-plugin for JDK source level 
compatibility";
+    }
+
+    @Override
+    protected UpgradeResult doApply(UpgradeContext context, Map<Path, 
Document> pomMap) {
+        Set<Path> processedPoms = new HashSet<>();
+        Set<Path> modifiedPoms = new HashSet<>();
+        Set<Path> errorPoms = new HashSet<>();
+
+        int runningJdkMajor = getRunningJdkMajor();
+
+        for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
+            Path pomPath = entry.getKey();
+            Document pomDocument = entry.getValue();
+            processedPoms.add(pomPath);
+
+            context.info(pomPath + " (checking JDK source level 
compatibility)");
+            context.indent();
+
+            try {
+                int sourceLevel = detectSourceLevel(pomDocument);
+                if (sourceLevel <= 0) {
+                    context.success("No source level configured");
+                    continue;
+                }
+
+                if (JdkSourceLevelSupport.supportsSourceLevel(runningJdkMajor, 
sourceLevel)) {
+                    context.success("Running JDK " + runningJdkMajor + " 
supports --source " + sourceLevel);
+                    continue;
+                }
+
+                if (hasToolchainsPluginWithSelectGoal(pomDocument)) {
+                    context.success(
+                            "maven-toolchains-plugin with " + 
SELECT_JDK_TOOLCHAIN_GOAL + " goal already configured");
+                    continue;
+                }
+
+                int latestJdk = 
JdkSourceLevelSupport.latestJdkForSourceLevel(sourceLevel);
+                addToolchainsPlugin(pomDocument, latestJdk);
+                modifiedPoms.add(pomPath);
+                context.success("Added maven-toolchains-plugin with " + 
SELECT_JDK_TOOLCHAIN_GOAL + " goal (--source "
+                        + sourceLevel + " requires JDK <= " + latestJdk + ")");
+            } catch (Exception e) {
+                context.failure("Failed to add toolchains plugin: " + 
e.getMessage());
+                errorPoms.add(pomPath);
+            } finally {
+                context.unindent();
+            }
+        }
+
+        return new UpgradeResult(processedPoms, modifiedPoms, errorPoms);
+    }
+
+    /**
+     * Detects the project's required source level from properties or compiler 
plugin configuration.
+     *
+     * @return the source level as a major version, or {@code -1} if none is 
configured
+     */
+    int detectSourceLevel(Document pomDocument) {
+        Element root = pomDocument.root();
+
+        // Check properties: maven.compiler.release takes precedence
+        Element properties = root.childElement(PROPERTIES).orElse(null);
+        if (properties != null) {
+            Element releaseElement =
+                    
properties.childElement(MAVEN_COMPILER_RELEASE).orElse(null);
+            if (releaseElement != null) {
+                int level = JdkSourceLevelSupport.normalizeSourceLevel(
+                        releaseElement.textContent().trim());
+                if (level > 0) {
+                    return level;
+                }
+            }
+
+            Element sourceElement =
+                    
properties.childElement(MAVEN_COMPILER_SOURCE).orElse(null);
+            if (sourceElement != null) {
+                int level = JdkSourceLevelSupport.normalizeSourceLevel(
+                        sourceElement.textContent().trim());
+                if (level > 0) {
+                    return level;
+                }
+            }
+        }
+
+        // Check compiler plugin configuration
+        return detectSourceLevelFromCompilerPlugin(root);
+    }
+
+    private int detectSourceLevelFromCompilerPlugin(Element root) {
+        Element build = root.childElement(BUILD).orElse(null);
+        if (build == null) {
+            return -1;
+        }
+
+        // Check <build><plugins>
+        int level = 
detectFromPluginSection(build.childElement(PLUGINS).orElse(null));
+        if (level > 0) {
+            return level;
+        }
+
+        // Check <build><pluginManagement><plugins>
+        Element pluginManagement = 
build.childElement(PLUGIN_MANAGEMENT).orElse(null);
+        if (pluginManagement != null) {
+            level = detectFromPluginSection(
+                    pluginManagement.childElement(PLUGINS).orElse(null));
+        }
+
+        return level;
+    }
+
+    private int detectFromPluginSection(Element pluginsElement) {
+        if (pluginsElement == null) {
+            return -1;
+        }
+
+        for (Element plugin : pluginsElement.childElements(PLUGIN).toList()) {
+            String artifactId = plugin.childTextTrimmed(ARTIFACT_ID);
+            String groupId = plugin.childTextTrimmed(GROUP_ID);
+            if (MAVEN_COMPILER_PLUGIN.equals(artifactId)
+                    && (groupId == null || groupId.isEmpty() || 
TOOLCHAINS_PLUGIN_GROUP_ID.equals(groupId))) {
+                Element config = 
plugin.childElement(CONFIGURATION).orElse(null);
+                if (config != null) {
+                    // <release> takes precedence
+                    Element releaseNode = 
config.childElement("release").orElse(null);
+                    if (releaseNode != null
+                            && releaseNode.textContent() != null
+                            && !releaseNode.textContent().isBlank()) {
+                        int level = JdkSourceLevelSupport.normalizeSourceLevel(
+                                releaseNode.textContent().trim());
+                        if (level > 0) {
+                            return level;
+                        }
+                    }
+                    Element sourceNode = 
config.childElement("source").orElse(null);
+                    if (sourceNode != null
+                            && sourceNode.textContent() != null
+                            && !sourceNode.textContent().isBlank()) {
+                        int level = JdkSourceLevelSupport.normalizeSourceLevel(
+                                sourceNode.textContent().trim());
+                        if (level > 0) {
+                            return level;
+                        }
+                    }
+                }
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Checks whether the POM already has the {@code maven-toolchains-plugin}
+     * configured with the {@code select-jdk-toolchain} goal.
+     */
+    boolean hasToolchainsPluginWithSelectGoal(Document pomDocument) {
+        Element root = pomDocument.root();
+        Element build = root.childElement(BUILD).orElse(null);
+        if (build == null) {
+            return false;
+        }
+
+        // Check both <plugins> and <pluginManagement><plugins>
+        if 
(hasSelectGoalInPluginSection(build.childElement(PLUGINS).orElse(null))) {
+            return true;
+        }
+
+        Element pluginManagement = 
build.childElement(PLUGIN_MANAGEMENT).orElse(null);
+        if (pluginManagement != null) {
+            return hasSelectGoalInPluginSection(
+                    pluginManagement.childElement(PLUGINS).orElse(null));
+        }
+
+        return false;
+    }
+
+    private boolean hasSelectGoalInPluginSection(Element pluginsElement) {
+        if (pluginsElement == null) {
+            return false;
+        }
+
+        for (Element plugin : pluginsElement.childElements(PLUGIN).toList()) {
+            String artifactId = plugin.childTextTrimmed(ARTIFACT_ID);
+            String groupId = plugin.childTextTrimmed(GROUP_ID);
+            if (MAVEN_TOOLCHAINS_PLUGIN.equals(artifactId)
+                    && (groupId == null || groupId.isEmpty() || 
TOOLCHAINS_PLUGIN_GROUP_ID.equals(groupId))) {
+                // Check if it has the select-jdk-toolchain goal in any 
execution
+                Element executions = 
plugin.childElement("executions").orElse(null);
+                if (executions != null) {
+                    for (Element execution :
+                            executions.childElements("execution").toList()) {
+                        Element goals = 
execution.childElement("goals").orElse(null);
+                        if (goals != null) {
+                            for (Element goal : 
goals.childElements("goal").toList()) {
+                                if 
(SELECT_JDK_TOOLCHAIN_GOAL.equals(goal.textContentTrimmed())) {
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Adds the {@code maven-toolchains-plugin} with {@code 
select-jdk-toolchain} goal
+     * to the POM's {@code <build><plugins>} section, configured with a 
version constraint
+     * so the plugin selects a JDK that supports the project's source level.
+     *
+     * @param pomDocument the POM document to modify
+     * @param maxJdkVersion the latest JDK major version that supports the 
source level
+     */
+    void addToolchainsPlugin(Document pomDocument, int maxJdkVersion) {
+        Element root = pomDocument.root();
+        Element build = root.childElement(BUILD).orElse(null);

Review Comment:
   Why not put the `DomUtils.insertNewElement` call inside an `orElseGet`? The 
pattern repeats a few times in this method, I believe the method could become a 
bit more compact.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to