JigsawGenerateMojo.java
/*-
* #%L
* io.earcam.maven.plugin.jigsaw
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.maven.plugin.jigsaw;
import static io.earcam.instrumental.archive.Archive.archive;
import static io.earcam.instrumental.archive.ArchiveConstruction.contentFrom;
import static io.earcam.instrumental.archive.jpms.AsJpmsModule.asJpmsModule;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.maven.plugins.annotations.LifecyclePhase.PREPARE_PACKAGE;
import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import io.earcam.instrumental.archive.Archive;
import io.earcam.instrumental.archive.ArchiveResource;
import io.earcam.instrumental.archive.jpms.AsJpmsModule;
import io.earcam.instrumental.module.jpms.ModuleInfo;
import io.earcam.unexceptional.Exceptional;
/**
* Generate <b>module-info.class</b> and/or <b>module-info.java</b>
*/
@Mojo(name = JigsawGenerateMojo.NAME, requiresProject = true, threadSafe = true, inheritByDefault = true, defaultPhase = PREPARE_PACKAGE, requiresDependencyResolution = COMPILE)
public class JigsawGenerateMojo extends AbstractJigsawMojo {
static final String NAME = "generate";
/**
* Defaults to the property <b>${project.artifactId}</b>.
*/
@Parameter(name = "moduleName", required = true, defaultValue = "${project.artifactId}")
protected String moduleName;
/**
* Defaults to the property <b>${project.version}</b>.
*/
@Parameter(name = "moduleVersion", required = true, defaultValue = "${project.version}")
protected String moduleVersion;
/**
* By default, nothing is exported. An export clause has fields <b>packageRegex</b> and <b>to</b>, where the later
* is CSV of module names.
*
* <pre>
* <exports>
* <export>
* <packageRegex>com\.acme\.api\..*</packageRegex>
* </export>
* <export>
* <packageRegex>com\.acme\.a\..*</packageRegex>
* <to>
* com.acme.b,
* com.acme.c,
* com.acme.d
* </to>
* </export>
* </exports>
* </pre>
*/
@Parameter(name = "exports", required = false)
protected Export[] exports = new Export[0];
/**
* By default, nothing is opened. An open clause has fields <b>packageRegex</b> and <b>to</b>, where the later is
* CSV of module names.
*
* <pre>
* <opens>
* <open>
* <packageRegex>com\.acme\.api\..*</packageRegex>
* </open>
* <open>
* <packageRegex>com\.acme\.a\..*</packageRegex>
* <to>
* com.acme.b,
* com.acme.c,
* com.acme.d
* </to>
* </open>
* </opens>
* </pre>
*/
@Parameter(name = "opens", required = false)
protected Export[] opens = new Export[0];
/**
* Manually specify any module <b>uses</b> clauses
*/
@Parameter(name = "uses", required = false)
protected List<String> uses = Collections.emptyList();
@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
protected File outputDirectory;
/**
* (Directory) Where to place the <b>module-info.java</b> source file.
*/
@Parameter(name = "generatedSourceDirectory", required = false)
protected File generatedSourceDirectory;
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;
public JigsawGenerateMojo()
{
super(NAME);
}
@Override
public void exec() throws MojoExecutionException, MojoFailureException
{
Archive jpmsed = build();
ArchiveResource moduleInfoBinary = extract(jpmsed);
writeBinary(moduleInfoBinary);
writeSource(moduleInfoBinary);
}
private Archive build()
{
AsJpmsModule asJpmsModule = configure();
return create(asJpmsModule);
}
private AsJpmsModule configure()
{
AsJpmsModule asJpmsModule = asJpmsModule()
.named(moduleName)
.versioned(moduleVersion)
.providingFromMetaInfServices(addMetaInfServices)
.autoRequiringJdkModules()
.autoRequiring(mavenMapper());
uses.forEach(asJpmsModule::using);
configurePorts(exports, "export", asJpmsModule::exporting);
configurePorts(opens, "open", asJpmsModule::opening);
return asJpmsModule;
}
private MavenPackageModuleMapper mavenMapper()
{
MavenPackageModuleMapper mapper = new MavenPackageModuleMapper();
project.getArtifacts()
.stream()
.map(Artifact::getFile)
.map(File::toPath)
.peek(f -> logDebug("Adding dependency module from path: {}", f))
.forEach(mapper::add);
return mapper;
}
private void configurePorts(Export[] ports, String portName, BiConsumer<Predicate<String>, String[]> porter)
{
for(Export export : ports) {
String[] to = export.trimmedTo();
porter.accept(export.packages(), to);
logDebug("Added {} with package regex: {} to: {}", portName, export.getPackageRegex(), to);
}
}
private Archive create(AsJpmsModule asJpmsModule)
{
// removes any existing module-info.class
Predicate<String> filter = n -> !"module-info.class".equals(n);
return archive()
.configured(asJpmsModule)
.sourcing(contentFrom(outputDirectory, filter))
.toObjectModel();
}
private ArchiveResource extract(Archive jpmsed)
{
return jpmsed
.content("module-info.class")
.orElseThrow(NullPointerException::new);
}
private void writeBinary(ArchiveResource moduleInfoBinary)
{
byte[] bytes = moduleInfoBinary.bytes();
Exceptional.accept(Files::write, targetFile.toPath(), bytes);
logDebug("Wrote {} bytes to binary: {}", bytes.length, targetFile);
}
private void writeSource(ArchiveResource moduleInfoBinary)
{
if(generatedSourceDirectory != null) {
generatedSourceDirectory.mkdirs();
ModuleInfo moduleInfoSource = ModuleInfo.read(moduleInfoBinary.bytes());
Path source = generatedSourceDirectory.toPath().resolve("module-info.java");
byte[] bytes = moduleInfoSource.toString().getBytes(UTF_8);
Exceptional.accept(Files::write, source, bytes);
logDebug("Wrote {} bytes to source: {}", bytes.length, source);
}
}
}