From 2928c9380f130f02b70b80d4828a4660c4ea0d34 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Wed, 7 Jan 2026 17:04:59 -0600 Subject: [PATCH 1/2] Add a MainClassExistsRule --- .../plugin/enforcer/MainClassExistsRule.java | 102 +++++++++++++ .../plugin/enforcer/EnforcerLoggerMock.java | 92 ++++++++++++ .../enforcer/MainClassExistsRuleTest.java | 141 ++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java create mode 100644 src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java create mode 100644 src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java diff --git a/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java new file mode 100644 index 0000000..cd82049 --- /dev/null +++ b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java @@ -0,0 +1,102 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +/* ======================================================================== + * This file was adapted from the no-package-cycles-enforcer-rule project: + * https://github.com/andrena/no-package-cycles-enforcer-rule + * + * Copyright 2013 - 2018 David Burkhart, Ben Romberg, Daniel Galan y Martins, + * Bastian Feigl, Marc Philipp, and Carsten Otto. + * + * Licensed 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.scijava.maven.plugin.enforcer; + +import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.project.MavenProject; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.Objects; +import java.util.Properties; + +/** + * Ensures a main class exists if the POM declares one. + * + * @author Gabriel Selzer + */ +@Named("mainClassExistsRule") +public class MainClassExistsRule extends AbstractEnforcerRule { + + // Inject needed Maven components + private final MavenProject project; + + @Inject + public MainClassExistsRule(MavenProject project) { + this.project = Objects.requireNonNull(project); + } + + @Override + public void execute() throws EnforcerRuleException { + Properties properties = project.getProperties(); + if (!properties.containsKey("main-class")) { + return; + } + String mainClass = properties.getProperty("main-class"); + + // Get the build output directory (e.g., target/classes) + String outputDirectory = project.getBuild().getOutputDirectory(); + + // Convert class name to file path (e.g., com.example.Main -> com/example/Main.class) + String classFilePath = mainClass.replace('.', File.separatorChar) + ".class"; + // Check if e.g. target/classes/com/example/Main.class exists + File classFile = new File(outputDirectory, classFilePath); + if (!classFile.exists()) { + throw new EnforcerRuleException( + "Main class '" + mainClass + "' declared in POM does not exist. " + + "Expected to find: " + classFile.getAbsolutePath() + ); + } + + getLog().info("Main class '" + mainClass + "' exists at: " + classFile.getAbsolutePath()); + } +} diff --git a/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java b/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java new file mode 100644 index 0000000..a62f04a --- /dev/null +++ b/src/test/java/org/scijava/maven/plugin/enforcer/EnforcerLoggerMock.java @@ -0,0 +1,92 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.maven.plugin.enforcer; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.maven.enforcer.rule.api.EnforcerLogger; + +public class EnforcerLoggerMock implements EnforcerLogger { + private final List info = new ArrayList(); + + public List getInfo() { + return info; + } + + public void debug(CharSequence message) { + } + + public void debug(Supplier messageSupplier) { + } + + public void info(CharSequence message) { + info.add(message.toString()); + } + + public void info(Supplier messageSupplier) { + info.add(messageSupplier.get().toString()); + } + + public void warn(CharSequence message) { + } + + public void warn(Supplier messageSupplier) { + } + + public void error(CharSequence message) { + } + + public void error(Supplier messageSupplier) { + } + + public void warnOrError(CharSequence message) { + } + + public void warnOrError(Supplier messageSupplier) { + } + + public boolean isDebugEnabled() { + return false; + } + + public boolean isInfoEnabled() { + return true; + } + + public boolean isWarnEnabled() { + return false; + } + + public boolean isErrorEnabled() { + return false; + } +} diff --git a/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java new file mode 100644 index 0000000..bb8293f --- /dev/null +++ b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java @@ -0,0 +1,141 @@ +/*- + * #%L + * A plugin for managing SciJava-based projects. + * %% + * Copyright (C) 2014 - 2025 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.maven.plugin.enforcer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; + +import org.apache.maven.enforcer.rule.api.EnforcerLogger; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +/** + * Tests for {@link MainClassExistsRule}. + * + * @author Gabriel Selzer + */ +public class MainClassExistsRuleTest { + + /** + * Test subclass that provides a mock logger. + */ + private class MainClassExistsRuleMock extends MainClassExistsRule { + public MainClassExistsRuleMock(MavenProject project) { + super(project); + } + + @Override + public EnforcerLogger getLog() { + return logMock; + } + } + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private MavenProject project; + private File outputDirectory; + private EnforcerLoggerMock logMock; + + @Before + public void setUp() throws Exception { + // Create a temporary output directory + outputDirectory = temporaryFolder.newFolder("target", "classes"); + + // Create a mock MavenProject + project = new MavenProject(); + Build build = new Build(); + build.setOutputDirectory(outputDirectory.getAbsolutePath()); + project.setBuild(build); + + // Create a mock logger + logMock = new EnforcerLoggerMock(); + } + + @Test + public void execute_NoMainClassProperty_Passes() throws Exception { + // No "main-class" property set + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + // Execute the rule and make sure it passes + rule.execute(); + } + + @Test + public void execute_MainClassExists_Passes() throws Exception { + // Set main-class property + project.getProperties().setProperty("main-class", "com.example.Main"); + + // Create the corresponding .class file + createClassFile("com.example.Main"); + + // Execute the rule and make sure it passes + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + rule.execute(); + } + + @Test + public void execute_MainClassDoesNotExist_ThrowsException() throws Exception { + // Set main-class property... + project.getProperties().setProperty("main-class", "com.example.NonExistent"); + + // ...but don't create the file + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + + // ...and assert an Exception is thrown. + Assert.assertThrows(EnforcerRuleException.class, rule::execute); + } + + /** + * Helper method to create a .class file at the appropriate location + * based on the fully qualified class name. + */ + private void createClassFile(String fullyQualifiedClassName) throws IOException { + String classFilePath = fullyQualifiedClassName.replace('.', File.separatorChar) + ".class"; + File classFile = new File(outputDirectory, classFilePath); + + // Create parent directories if needed + classFile.getParentFile().mkdirs(); + + // Create the .class file + assertTrue("Failed to create class file: " + classFile.getAbsolutePath(), + classFile.createNewFile()); + } +} From c616820dd0347c68d75af81e0bca77f2fc6b22f4 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Wed, 7 Jan 2026 17:23:48 -0600 Subject: [PATCH 2/2] Check for empty main class This is actually what happens when the pom doesn't have this property --- .../plugin/enforcer/MainClassExistsRule.java | 3 +++ .../enforcer/MainClassExistsRuleTest.java | 27 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java index cd82049..5b3607a 100644 --- a/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java +++ b/src/main/java/org/scijava/maven/plugin/enforcer/MainClassExistsRule.java @@ -82,6 +82,9 @@ public void execute() throws EnforcerRuleException { return; } String mainClass = properties.getProperty("main-class"); + if (mainClass.trim().isEmpty()) { + return; + } // Get the build output directory (e.g., target/classes) String outputDirectory = project.getBuild().getOutputDirectory(); diff --git a/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java index bb8293f..0a1dd31 100644 --- a/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java +++ b/src/test/java/org/scijava/maven/plugin/enforcer/MainClassExistsRuleTest.java @@ -29,13 +29,6 @@ package org.scijava.maven.plugin.enforcer; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; - import org.apache.maven.enforcer.rule.api.EnforcerLogger; import org.apache.maven.enforcer.rule.api.EnforcerRuleException; import org.apache.maven.model.Build; @@ -44,9 +37,13 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertTrue; + /** * Tests for {@link MainClassExistsRule}. * @@ -91,15 +88,23 @@ public void setUp() throws Exception { } @Test - public void execute_NoMainClassProperty_Passes() throws Exception { + public void TestNoMainClassProperty() throws Exception { // No "main-class" property set MainClassExistsRule rule = new MainClassExistsRuleMock(project); // Execute the rule and make sure it passes rule.execute(); } + @Test + public void TestEmptyMainClassProperty() throws Exception { + // No "main-class" property set + MainClassExistsRule rule = new MainClassExistsRuleMock(project); + // Execute the rule and make sure it passes + rule.execute(); + } + @Test - public void execute_MainClassExists_Passes() throws Exception { + public void TestMainClassExists() throws Exception { // Set main-class property project.getProperties().setProperty("main-class", "com.example.Main"); @@ -112,7 +117,7 @@ public void execute_MainClassExists_Passes() throws Exception { } @Test - public void execute_MainClassDoesNotExist_ThrowsException() throws Exception { + public void TestMainClassDoesNotExist() { // Set main-class property... project.getProperties().setProperty("main-class", "com.example.NonExistent");