Skip to content

Commit 2928c93

Browse files
committed
Add a MainClassExistsRule
1 parent 16bd6c6 commit 2928c93

File tree

3 files changed

+335
-0
lines changed

3 files changed

+335
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*-
2+
* #%L
3+
* A plugin for managing SciJava-based projects.
4+
* %%
5+
* Copyright (C) 2014 - 2025 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
/* ========================================================================
31+
* This file was adapted from the no-package-cycles-enforcer-rule project:
32+
* https://github.com/andrena/no-package-cycles-enforcer-rule
33+
*
34+
* Copyright 2013 - 2018 David Burkhart, Ben Romberg, Daniel Galan y Martins,
35+
* Bastian Feigl, Marc Philipp, and Carsten Otto.
36+
*
37+
* Licensed under the Apache License, Version 2.0 (the "License");
38+
* you may not use this file except in compliance with the License.
39+
* You may obtain a copy of the License at
40+
*
41+
* http://www.apache.org/licenses/LICENSE-2.0
42+
*
43+
* Unless required by applicable law or agreed to in writing, software
44+
* distributed under the License is distributed on an "AS IS" BASIS,
45+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
46+
* See the License for the specific language governing permissions and
47+
* limitations under the License.
48+
* ======================================================================== */
49+
50+
package org.scijava.maven.plugin.enforcer;
51+
52+
import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
53+
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
54+
import org.apache.maven.project.MavenProject;
55+
56+
import javax.inject.Inject;
57+
import javax.inject.Named;
58+
import java.io.File;
59+
import java.util.Objects;
60+
import java.util.Properties;
61+
62+
/**
63+
* Ensures a main class exists if the POM declares one.
64+
*
65+
* @author Gabriel Selzer
66+
*/
67+
@Named("mainClassExistsRule")
68+
public class MainClassExistsRule extends AbstractEnforcerRule {
69+
70+
// Inject needed Maven components
71+
private final MavenProject project;
72+
73+
@Inject
74+
public MainClassExistsRule(MavenProject project) {
75+
this.project = Objects.requireNonNull(project);
76+
}
77+
78+
@Override
79+
public void execute() throws EnforcerRuleException {
80+
Properties properties = project.getProperties();
81+
if (!properties.containsKey("main-class")) {
82+
return;
83+
}
84+
String mainClass = properties.getProperty("main-class");
85+
86+
// Get the build output directory (e.g., target/classes)
87+
String outputDirectory = project.getBuild().getOutputDirectory();
88+
89+
// Convert class name to file path (e.g., com.example.Main -> com/example/Main.class)
90+
String classFilePath = mainClass.replace('.', File.separatorChar) + ".class";
91+
// Check if e.g. target/classes/com/example/Main.class exists
92+
File classFile = new File(outputDirectory, classFilePath);
93+
if (!classFile.exists()) {
94+
throw new EnforcerRuleException(
95+
"Main class '" + mainClass + "' declared in POM does not exist. " +
96+
"Expected to find: " + classFile.getAbsolutePath()
97+
);
98+
}
99+
100+
getLog().info("Main class '" + mainClass + "' exists at: " + classFile.getAbsolutePath());
101+
}
102+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*-
2+
* #%L
3+
* A plugin for managing SciJava-based projects.
4+
* %%
5+
* Copyright (C) 2014 - 2025 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.maven.plugin.enforcer;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.function.Supplier;
35+
36+
import org.apache.maven.enforcer.rule.api.EnforcerLogger;
37+
38+
public class EnforcerLoggerMock implements EnforcerLogger {
39+
private final List<String> info = new ArrayList<String>();
40+
41+
public List<String> getInfo() {
42+
return info;
43+
}
44+
45+
public void debug(CharSequence message) {
46+
}
47+
48+
public void debug(Supplier<CharSequence> messageSupplier) {
49+
}
50+
51+
public void info(CharSequence message) {
52+
info.add(message.toString());
53+
}
54+
55+
public void info(Supplier<CharSequence> messageSupplier) {
56+
info.add(messageSupplier.get().toString());
57+
}
58+
59+
public void warn(CharSequence message) {
60+
}
61+
62+
public void warn(Supplier<CharSequence> messageSupplier) {
63+
}
64+
65+
public void error(CharSequence message) {
66+
}
67+
68+
public void error(Supplier<CharSequence> messageSupplier) {
69+
}
70+
71+
public void warnOrError(CharSequence message) {
72+
}
73+
74+
public void warnOrError(Supplier<CharSequence> messageSupplier) {
75+
}
76+
77+
public boolean isDebugEnabled() {
78+
return false;
79+
}
80+
81+
public boolean isInfoEnabled() {
82+
return true;
83+
}
84+
85+
public boolean isWarnEnabled() {
86+
return false;
87+
}
88+
89+
public boolean isErrorEnabled() {
90+
return false;
91+
}
92+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*-
2+
* #%L
3+
* A plugin for managing SciJava-based projects.
4+
* %%
5+
* Copyright (C) 2014 - 2025 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.maven.plugin.enforcer;
31+
32+
import static org.hamcrest.MatcherAssert.assertThat;
33+
import static org.hamcrest.Matchers.containsString;
34+
import static org.junit.Assert.assertTrue;
35+
36+
import java.io.File;
37+
import java.io.IOException;
38+
39+
import org.apache.maven.enforcer.rule.api.EnforcerLogger;
40+
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
41+
import org.apache.maven.model.Build;
42+
import org.apache.maven.project.MavenProject;
43+
import org.junit.Assert;
44+
import org.junit.Before;
45+
import org.junit.Rule;
46+
import org.junit.Test;
47+
import org.junit.rules.ExpectedException;
48+
import org.junit.rules.TemporaryFolder;
49+
50+
/**
51+
* Tests for {@link MainClassExistsRule}.
52+
*
53+
* @author Gabriel Selzer
54+
*/
55+
public class MainClassExistsRuleTest {
56+
57+
/**
58+
* Test subclass that provides a mock logger.
59+
*/
60+
private class MainClassExistsRuleMock extends MainClassExistsRule {
61+
public MainClassExistsRuleMock(MavenProject project) {
62+
super(project);
63+
}
64+
65+
@Override
66+
public EnforcerLogger getLog() {
67+
return logMock;
68+
}
69+
}
70+
71+
@Rule
72+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
73+
74+
private MavenProject project;
75+
private File outputDirectory;
76+
private EnforcerLoggerMock logMock;
77+
78+
@Before
79+
public void setUp() throws Exception {
80+
// Create a temporary output directory
81+
outputDirectory = temporaryFolder.newFolder("target", "classes");
82+
83+
// Create a mock MavenProject
84+
project = new MavenProject();
85+
Build build = new Build();
86+
build.setOutputDirectory(outputDirectory.getAbsolutePath());
87+
project.setBuild(build);
88+
89+
// Create a mock logger
90+
logMock = new EnforcerLoggerMock();
91+
}
92+
93+
@Test
94+
public void execute_NoMainClassProperty_Passes() throws Exception {
95+
// No "main-class" property set
96+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
97+
// Execute the rule and make sure it passes
98+
rule.execute();
99+
}
100+
101+
@Test
102+
public void execute_MainClassExists_Passes() throws Exception {
103+
// Set main-class property
104+
project.getProperties().setProperty("main-class", "com.example.Main");
105+
106+
// Create the corresponding .class file
107+
createClassFile("com.example.Main");
108+
109+
// Execute the rule and make sure it passes
110+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
111+
rule.execute();
112+
}
113+
114+
@Test
115+
public void execute_MainClassDoesNotExist_ThrowsException() throws Exception {
116+
// Set main-class property...
117+
project.getProperties().setProperty("main-class", "com.example.NonExistent");
118+
119+
// ...but don't create the file
120+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
121+
122+
// ...and assert an Exception is thrown.
123+
Assert.assertThrows(EnforcerRuleException.class, rule::execute);
124+
}
125+
126+
/**
127+
* Helper method to create a .class file at the appropriate location
128+
* based on the fully qualified class name.
129+
*/
130+
private void createClassFile(String fullyQualifiedClassName) throws IOException {
131+
String classFilePath = fullyQualifiedClassName.replace('.', File.separatorChar) + ".class";
132+
File classFile = new File(outputDirectory, classFilePath);
133+
134+
// Create parent directories if needed
135+
classFile.getParentFile().mkdirs();
136+
137+
// Create the .class file
138+
assertTrue("Failed to create class file: " + classFile.getAbsolutePath(),
139+
classFile.createNewFile());
140+
}
141+
}

0 commit comments

Comments
 (0)