Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ It is also possible to configure the generator to use target directories and a m
<targetFolder>target/generated-sources</targetFolder>
<!-- or use NATIVE for plain Java services, SPRING_BOOT is the default value -->
<serviceType>SPRING_BOOT</serviceType>
<!-- optionally control @NullMarked generation (auto-detected if org.jspecify:jspecify dependency is present) -->
<addNullMarked>true</addNullMarked>
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.westemeyer.plugins.maven.versions;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
Expand Down Expand Up @@ -51,6 +53,16 @@ public class GenerateServiceMojo extends AbstractMojo {
*/
private static final String AUTO_CONFIGURATION_STRING = "AutoConfiguration";

/**
* Group ID for JSpecify.
*/
private static final String JSPECIFY_GROUP_ID = "org.jspecify";

/**
* Artifact ID for JSpecify.
*/
private static final String JSPECIFY_ARTIFACT_ID = "jspecify";

/**
* The project object is injected with information from a project's pom.xml.
*/
Expand Down Expand Up @@ -95,6 +107,13 @@ public class GenerateServiceMojo extends AbstractMojo {
@Parameter
boolean skipSpringBootAutoConfiguration = false;

/**
* Whether to annotate generated classes with org.jspecify.annotations.NullMarked.
* Defaults to true if org.jspecify:jspecify is present in project dependencies.
*/
@Parameter
Boolean addNullMarked;

@Override
public void execute() throws MojoFailureException {
String packaging = project.getPackaging();
Expand Down Expand Up @@ -338,9 +357,57 @@ Map<String, String> getTemplateValues(String autoconfigurationClass) {
valueMap.put("description", replaceLineFeeds(project.getDescription()));
valueMap.put("timestamp", "" + new Date().getTime());
valueMap.put("parentArtifactDefinition", getParentArtifactDefinition());
boolean useNullMarked = isNullMarked();
valueMap.put("nullMarkedImport", useNullMarked ? "import org.jspecify.annotations.NullMarked;\n" : "");
valueMap.put("nullMarkedAnnotation", useNullMarked ? "@NullMarked\n" : "");
return valueMap;
}

/**
* Determine whether generated classes should be annotated with @NullMarked.
*
* @return true if addNullMarked is explicitly true, or if addNullMarked is not set and jspecify is present
*/
boolean isNullMarked() {
if (addNullMarked != null) {
return addNullMarked;
}
return isJSpecifyPresent();
}

/**
* Check whether org.jspecify:jspecify dependency is present in the project dependencies or artifacts.
*
* @return true if org.jspecify:jspecify is found
*/
boolean isJSpecifyPresent() {
if (project != null) {
if (project.getDependencies() != null) {
for (Object depObj : project.getDependencies()) {
if (depObj instanceof Dependency) {
Dependency dependency = (Dependency) depObj;
if (JSPECIFY_GROUP_ID.equals(dependency.getGroupId())
&& JSPECIFY_ARTIFACT_ID.equals(dependency.getArtifactId())) {
return true;
}
}
}
}
if (project.getArtifacts() != null) {
for (Object artObj : project.getArtifacts()) {
if (artObj instanceof Artifact) {
Artifact artifact = (Artifact) artObj;
if (JSPECIFY_GROUP_ID.equals(artifact.getGroupId())
&& JSPECIFY_ARTIFACT_ID.equals(artifact.getArtifactId())) {
return true;
}
}
}
}
}
return false;
}

/**
* Determine the content for the creation of parent artifacts.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package ${package};

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

${nullMarkedImport}
/**
* Configuration file used to automatically add the {@link de.westemeyer.version.service.ArtifactVersionCollector} bean.
*/
@Configuration
${nullMarkedAnnotation}@Configuration
@ComponentScan
public class ${configClass} {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import de.westemeyer.version.core.api.ArtifactVersionService;
import de.westemeyer.version.core.model.Artifact;
import de.westemeyer.version.core.model.BasicArtifact;
import org.springframework.stereotype.Service;

${nullMarkedImport}
/**
* Generated version service implementation class for ${groupId}:${artifactId} artifact.
*/
@Service
${nullMarkedAnnotation}@Service
public class ${serviceClass} implements ArtifactVersionService {
@Override
public Artifact getArtifact() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package ${package};
import de.westemeyer.version.core.api.ArtifactVersionService;
import de.westemeyer.version.core.model.Artifact;
import org.springframework.stereotype.Service;

${nullMarkedImport}
/**
* Generated version service implementation class for ${groupId}:${artifactId} artifact.
*/
@Service
${nullMarkedAnnotation}@Service
public class ${serviceClass} implements ArtifactVersionService {
@Override
public Artifact getArtifact() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package ${package};
import de.westemeyer.version.core.api.ArtifactVersionService;
import de.westemeyer.version.core.model.Artifact;
import de.westemeyer.version.core.model.BasicArtifact;

${nullMarkedImport}
/**
* Generated version service implementation class for ${groupId}:${artifactId} artifact.
*/
public class ${serviceClass} implements ArtifactVersionService {
${nullMarkedAnnotation}public class ${serviceClass} implements ArtifactVersionService {
@Override
public Artifact getArtifact() {
${parentArtifactDefinition}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package ${package};

import de.westemeyer.version.core.api.ArtifactVersionService;
import de.westemeyer.version.core.model.Artifact;

${nullMarkedImport}
/**
* Generated version service implementation class for ${groupId}:${artifactId} artifact.
*/
public class ${serviceClass} implements ArtifactVersionService {
${nullMarkedAnnotation}public class ${serviceClass} implements ArtifactVersionService {
@Override
public Artifact getArtifact() {
return new Artifact("${groupId}", "${artifactId}", "${version}", ${timestamp}L, "${name}", "${description}", "${url}", null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package de.westemeyer.plugins.maven.versions;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
Expand All @@ -22,6 +24,7 @@
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
Expand Down Expand Up @@ -115,9 +118,84 @@ void getTemplateValues() {
assertEquals(
" BasicArtifact parentArtifact = new BasicArtifact(\"de.westemeyer.parent\", \"artifact-version-test-parent\", \"1.0.0\", null);\n",
templateValues.get("parentArtifactDefinition"));
assertEquals("", templateValues.get("nullMarkedImport"));
assertEquals("", templateValues.get("nullMarkedAnnotation"));
assertDoesNotThrow(() -> Long.valueOf(templateValues.get("timestamp")));
}

@Test
void getTemplateValuesWithNullMarked() {
// given
GenerateServiceMojo mojo = getServiceMojoMock();
mojo.serviceClass = "MyServiceClass";
mojo.packageName = "de.westemeyer.service.version";
mojo.addNullMarked = true;
mojo.project = getMavenProject("de.westemeyer", "artifact-version-test", "1.0.0-SNAPSHOT");
when(mojo.getTemplateValues(anyString())).thenCallRealMethod();
when(mojo.getParentArtifactDefinition()).thenCallRealMethod();
when(mojo.isNullMarked()).thenCallRealMethod();

// when
Map<String, String> templateValues = mojo.getTemplateValues("ConfigClass");

// then
assertEquals("import org.jspecify.annotations.NullMarked;\n", templateValues.get("nullMarkedImport"));
assertEquals("@NullMarked\n", templateValues.get("nullMarkedAnnotation"));
}

@Test
void isNullMarkedExplicit() {
// given
GenerateServiceMojo mojo = getServiceMojoMock();
when(mojo.isNullMarked()).thenCallRealMethod();

// explicit true
mojo.addNullMarked = true;
assertTrue(mojo.isNullMarked());

// explicit false
mojo.addNullMarked = false;
assertFalse(mojo.isNullMarked());
}

@Test
void isJSpecifyPresentDirectDependency() {
// given
GenerateServiceMojo mojo = getServiceMojoMock();
when(mojo.isJSpecifyPresent()).thenCallRealMethod();
Dependency dependency = new Dependency();
dependency.setGroupId("org.jspecify");
dependency.setArtifactId("jspecify");
when(mojo.project.getDependencies()).thenReturn(Collections.singletonList(dependency));

// when/then
assertTrue(mojo.isJSpecifyPresent());
}

@Test
void isJSpecifyPresentArtifact() {
// given
GenerateServiceMojo mojo = getServiceMojoMock();
when(mojo.isJSpecifyPresent()).thenCallRealMethod();
Artifact artifact = mock(Artifact.class);
when(artifact.getGroupId()).thenReturn("org.jspecify");
when(artifact.getArtifactId()).thenReturn("jspecify");
when(mojo.project.getArtifacts()).thenReturn(Collections.singleton(artifact));

// when/then
assertTrue(mojo.isJSpecifyPresent());
}

@Test
void isJSpecifyPresentNotFound() {
// given
GenerateServiceMojo mojo = getServiceMojoMock();
when(mojo.isJSpecifyPresent()).thenCallRealMethod();

// when/then
assertFalse(mojo.isJSpecifyPresent());
}

private static MavenProject getMavenProject(String groupId, String artifactId, String version) {
MavenProject project = mock(MavenProject.class);
when(project.getGroupId()).thenReturn(groupId);
Expand Down