Skip to content

Commit 1004cd4

Browse files
authored
Merge pull request #35 from scijava/main-class-exists-rule
Add Main class exists rule
2 parents 16bd6c6 + c616820 commit 1004cd4

File tree

3 files changed

+343
-0
lines changed

3 files changed

+343
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
if (mainClass.trim().isEmpty()) {
86+
return;
87+
}
88+
89+
// Get the build output directory (e.g., target/classes)
90+
String outputDirectory = project.getBuild().getOutputDirectory();
91+
92+
// Convert class name to file path (e.g., com.example.Main -> com/example/Main.class)
93+
String classFilePath = mainClass.replace('.', File.separatorChar) + ".class";
94+
// Check if e.g. target/classes/com/example/Main.class exists
95+
File classFile = new File(outputDirectory, classFilePath);
96+
if (!classFile.exists()) {
97+
throw new EnforcerRuleException(
98+
"Main class '" + mainClass + "' declared in POM does not exist. " +
99+
"Expected to find: " + classFile.getAbsolutePath()
100+
);
101+
}
102+
103+
getLog().info("Main class '" + mainClass + "' exists at: " + classFile.getAbsolutePath());
104+
}
105+
}
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: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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 org.apache.maven.enforcer.rule.api.EnforcerLogger;
33+
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
34+
import org.apache.maven.model.Build;
35+
import org.apache.maven.project.MavenProject;
36+
import org.junit.Assert;
37+
import org.junit.Before;
38+
import org.junit.Rule;
39+
import org.junit.Test;
40+
import org.junit.rules.TemporaryFolder;
41+
42+
import java.io.File;
43+
import java.io.IOException;
44+
45+
import static org.junit.Assert.assertTrue;
46+
47+
/**
48+
* Tests for {@link MainClassExistsRule}.
49+
*
50+
* @author Gabriel Selzer
51+
*/
52+
public class MainClassExistsRuleTest {
53+
54+
/**
55+
* Test subclass that provides a mock logger.
56+
*/
57+
private class MainClassExistsRuleMock extends MainClassExistsRule {
58+
public MainClassExistsRuleMock(MavenProject project) {
59+
super(project);
60+
}
61+
62+
@Override
63+
public EnforcerLogger getLog() {
64+
return logMock;
65+
}
66+
}
67+
68+
@Rule
69+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
70+
71+
private MavenProject project;
72+
private File outputDirectory;
73+
private EnforcerLoggerMock logMock;
74+
75+
@Before
76+
public void setUp() throws Exception {
77+
// Create a temporary output directory
78+
outputDirectory = temporaryFolder.newFolder("target", "classes");
79+
80+
// Create a mock MavenProject
81+
project = new MavenProject();
82+
Build build = new Build();
83+
build.setOutputDirectory(outputDirectory.getAbsolutePath());
84+
project.setBuild(build);
85+
86+
// Create a mock logger
87+
logMock = new EnforcerLoggerMock();
88+
}
89+
90+
@Test
91+
public void TestNoMainClassProperty() throws Exception {
92+
// No "main-class" property set
93+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
94+
// Execute the rule and make sure it passes
95+
rule.execute();
96+
}
97+
98+
@Test
99+
public void TestEmptyMainClassProperty() throws Exception {
100+
// No "main-class" property set
101+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
102+
// Execute the rule and make sure it passes
103+
rule.execute();
104+
}
105+
106+
@Test
107+
public void TestMainClassExists() throws Exception {
108+
// Set main-class property
109+
project.getProperties().setProperty("main-class", "com.example.Main");
110+
111+
// Create the corresponding .class file
112+
createClassFile("com.example.Main");
113+
114+
// Execute the rule and make sure it passes
115+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
116+
rule.execute();
117+
}
118+
119+
@Test
120+
public void TestMainClassDoesNotExist() {
121+
// Set main-class property...
122+
project.getProperties().setProperty("main-class", "com.example.NonExistent");
123+
124+
// ...but don't create the file
125+
MainClassExistsRule rule = new MainClassExistsRuleMock(project);
126+
127+
// ...and assert an Exception is thrown.
128+
Assert.assertThrows(EnforcerRuleException.class, rule::execute);
129+
}
130+
131+
/**
132+
* Helper method to create a .class file at the appropriate location
133+
* based on the fully qualified class name.
134+
*/
135+
private void createClassFile(String fullyQualifiedClassName) throws IOException {
136+
String classFilePath = fullyQualifiedClassName.replace('.', File.separatorChar) + ".class";
137+
File classFile = new File(outputDirectory, classFilePath);
138+
139+
// Create parent directories if needed
140+
classFile.getParentFile().mkdirs();
141+
142+
// Create the .class file
143+
assertTrue("Failed to create class file: " + classFile.getAbsolutePath(),
144+
classFile.createNewFile());
145+
}
146+
}

0 commit comments

Comments
 (0)