Fixes generics, only creates one constructor, adds some tests, adds java compatibility options, rework some classes, fixes some problems

This commit is contained in:
Guillaume Tâche
2024-12-13 21:20:49 +01:00
parent 7ec52cd175
commit d63188e8ee
172 changed files with 3955 additions and 17340 deletions

View File

@@ -1,32 +1,24 @@
package com.github.gtache.fxml.compiler.maven;
import com.github.gtache.fxml.compiler.ControllerInjection;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.compatibility.impl.GenerationCompatibilityImpl;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerInjectionImpl;
import com.github.gtache.fxml.compiler.impl.ControllerInjectionTypes;
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
import com.github.gtache.fxml.compiler.impl.GenerationParametersImpl;
import com.github.gtache.fxml.compiler.impl.GenerationRequestImpl;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionImpl;
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
import com.github.gtache.fxml.compiler.parsing.ParseException;
import com.github.gtache.fxml.compiler.parsing.xml.DOMFXMLParser;
import com.github.gtache.fxml.compiler.maven.internal.CompilationInfo;
import com.github.gtache.fxml.compiler.maven.internal.CompilationInfoProvider;
import com.github.gtache.fxml.compiler.maven.internal.Compiler;
import com.github.gtache.fxml.compiler.maven.internal.ControllerProvider;
import com.github.gtache.fxml.compiler.maven.internal.FXMLProvider;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
@@ -35,7 +27,6 @@ import java.util.Map;
*/
@Mojo(name = "compile", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class FXMLCompilerMojo extends AbstractMojo {
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
@@ -43,146 +34,61 @@ public class FXMLCompilerMojo extends AbstractMojo {
@Parameter(property = "output-directory", defaultValue = "${project.build.directory}/generated-sources/java", required = true)
private Path outputDirectory;
@Parameter(property = "target-version", defaultValue = "21", required = true)
private int targetVersion;
@Parameter(property = "use-image-inputstream-constructor", defaultValue = "true", required = true)
private boolean useImageInputStreamConstructor;
@Parameter(property = "controller-injection", defaultValue = "INSTANCE", required = true)
private ControllerInjectionTypes controllerInjectionType;
@Parameter(property = "field-injection", defaultValue = "REFLECTION", required = true)
private ControllerFieldInjectionTypes fieldInjectionTypes;
private ControllerFieldInjectionTypes fieldInjectionType;
@Parameter(property = "method-injection", defaultValue = "REFLECTION", required = true)
private ControllerMethodsInjectionType methodsInjectionType;
private ControllerMethodsInjectionType methodInjectionType;
@Parameter(property = "bundle-injection", defaultValue = "CONSTRUCTOR", required = true)
private ResourceBundleInjectionTypes bundleInjectionType;
@Parameter(property = "resource-injection", defaultValue = "CONSTRUCTOR", required = true)
private ResourceBundleInjectionTypes resourceInjectionType;
@Parameter(property = "resource-map")
private Map<String, String> resourceMap;
@Parameter(property = "bundle-map")
private Map<String, String> bundleMap;
@Override
public void execute() throws MojoExecutionException {
final var fxmls = getAllFXMLs();
final var controllerMapping = createControllerMapping(fxmls);
final var mapping = createMapping(fxmls, controllerMapping);
compile(mapping);
}
private Map<Path, Path> getAllFXMLs() throws MojoExecutionException {
final var map = new HashMap<Path, Path>();
for (final var resource : project.getResources()) {
final var path = Paths.get(resource.getDirectory());
if (Files.isDirectory(path)) {
try (final var stream = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.toString().endsWith(".fxml"), FileVisitOption.FOLLOW_LINKS)) {
final var curList = stream.toList();
getLog().info("Found " + curList);
for (final var p : curList) {
map.put(p, path);
}
} catch (final IOException e) {
throw new MojoExecutionException("Error reading resources", e);
}
} else {
getLog().info("Directory " + path + " does not exist");
}
if (fieldInjectionType == ControllerFieldInjectionTypes.FACTORY && controllerInjectionType != ControllerInjectionTypes.FACTORY) {
getLog().warn("Field injection is set to FACTORY : Forcing controller injection to FACTORY");
controllerInjectionType = ControllerInjectionTypes.FACTORY;
}
return map;
final var fxmls = FXMLProvider.getFXMLs(project);
final var controllerMapping = createControllerMapping(fxmls);
final var compilationInfoMapping = createCompilationInfoMapping(fxmls, controllerMapping);
compile(compilationInfoMapping);
}
private static Map<Path, String> createControllerMapping(final Map<? extends Path, ? extends Path> fxmls) throws MojoExecutionException {
final var mapping = new HashMap<Path, String>();
for (final var fxml : fxmls.keySet()) {
mapping.put(fxml, getControllerClass(fxml));
mapping.put(fxml, ControllerProvider.getController(fxml));
}
return mapping;
}
private static String getControllerClass(final Path fxml) throws MojoExecutionException {
try {
final var documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final var document = documentBuilder.parse(fxml.toFile());
document.getDocumentElement().normalize();
final var controller = document.getDocumentElement().getAttribute("fx:controller");
if (controller.isBlank()) {
throw new MojoExecutionException("Missing controller attribute for " + fxml);
} else {
return controller;
}
} catch (final SAXException | IOException | ParserConfigurationException e) {
throw new MojoExecutionException("Error parsing fxml at " + fxml, e);
}
}
private Map<Path, CompilationInfo> createMapping(final Map<? extends Path, ? extends Path> fxmls, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
final var compilationInfoProvider = new CompilationInfoProvider(project, outputDirectory, getLog());
private Map<Path, CompilationInfo> createCompilationInfoMapping(final Map<? extends Path, ? extends Path> fxmls, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
final var mapping = new HashMap<Path, CompilationInfo>();
for (final var entry : fxmls.entrySet()) {
final var info = compilationInfoProvider.getCompilationInfo(entry.getValue(), entry.getKey(), controllerMapping);
final var info = CompilationInfoProvider.getCompilationInfo(entry.getValue(), entry.getKey(), controllerMapping, outputDirectory, project);
mapping.put(entry.getKey(), info);
}
return mapping;
}
private void compile(final Map<Path, CompilationInfo> mapping) throws MojoExecutionException {
final var generator = new GeneratorImpl();
final var parser = new DOMFXMLParser();
final var controllerInfoProvider = new ControllerInfoProvider(getLog());
try {
for (final var entry : mapping.entrySet()) {
final var inputPath = entry.getKey();
final var info = entry.getValue();
getLog().info("Parsing " + inputPath + " with " + parser.getClass().getSimpleName());
final var root = parser.parse(inputPath);
final var controllerInjection = getControllerInjection(mapping, info);
final var sourceToGeneratedClassName = getSourceToGeneratedClassName(mapping, info);
final var sourceToControllerName = getSourceToControllerName(mapping, info);
final var resourceBundleInjection = new ResourceBundleInjectionImpl(bundleInjectionType, getBundleName(info));
final var parameters = new GenerationParametersImpl(controllerInjection, sourceToGeneratedClassName, sourceToControllerName, resourceBundleInjection);
final var controllerInfo = controllerInfoProvider.getControllerInfo(info);
final var output = info.outputFile();
final var request = new GenerationRequestImpl(parameters, controllerInfo, root, info.outputClass());
getLog().info("Compiling " + inputPath);
final var content = generator.generate(request);
final var outputDir = output.getParent();
Files.createDirectories(outputDir);
Files.writeString(output, content);
getLog().info("Compiled " + inputPath + " to " + output);
}
} catch (final IOException | RuntimeException | ParseException | GenerationException e) {
throw new MojoExecutionException("Error compiling fxml", e);
}
final var parameters = new GenerationParametersImpl(new GenerationCompatibilityImpl(targetVersion), useImageInputStreamConstructor, resourceMap,
controllerInjectionType, fieldInjectionType, methodInjectionType, resourceInjectionType);
Compiler.compile(mapping, parameters);
project.addCompileSourceRoot(outputDirectory.toAbsolutePath().toString());
}
private String getBundleName(final CompilationInfo info) {
return bundleMap == null ? "" : bundleMap.getOrDefault(info.inputFile().toString(), "");
}
private static Map<String, String> getSourceToControllerName(final Map<Path, CompilationInfo> mapping, final CompilationInfo info) {
final var ret = new HashMap<String, String>();
for (final var entry : info.includes().entrySet()) {
ret.put(entry.getKey(), mapping.get(entry.getValue()).controllerClass());
}
return ret;
}
private static Map<String, String> getSourceToGeneratedClassName(final Map<Path, CompilationInfo> mapping, final CompilationInfo info) {
final var ret = new HashMap<String, String>();
for (final var entry : info.includes().entrySet()) {
ret.put(entry.getKey(), mapping.get(entry.getValue()).outputClass());
}
return ret;
}
private Map<String, ControllerInjection> getControllerInjection(final Map<Path, CompilationInfo> compilationInfoMapping, final CompilationInfo info) {
final var ret = new HashMap<String, ControllerInjection>();
ret.put(info.controllerClass(), getControllerInjection(info));
for (final var entry : info.includes().entrySet()) {
final var key = entry.getKey();
final var value = entry.getValue();
final var subInfo = compilationInfoMapping.get(value);
ret.put(key, getControllerInjection(subInfo));
}
return ret;
}
private ControllerInjection getControllerInjection(final CompilationInfo info) {
return new ControllerInjectionImpl(fieldInjectionTypes, methodsInjectionType, info.controllerClass());
}
}

View File

@@ -1,4 +1,4 @@
package com.github.gtache.fxml.compiler.maven;
package com.github.gtache.fxml.compiler.maven.internal;
import java.nio.file.Path;
import java.util.HashMap;
@@ -10,21 +10,21 @@ import java.util.Set;
/**
* Info about FXML file compilation
*
* @param inputFile The input file
* @param outputFile The output file
* @param outputClass The output class name
* @param controllerFile The controller file
* @param controllerClass The controller class name
* @param injectedFields The injected fields
* @param injectedMethods The injected methods
* @param includes The FXML inclusions
* @param inputFile The input file
* @param outputFile The output file
* @param outputClass The output class name
* @param controllerFile The controller file
* @param controllerClass The controller class name
* @param injectedFields The injected fields
* @param injectedMethods The injected methods
* @param includes The FXML inclusions
* @param requiresResourceBundle True if the file requires a resource bundle
*/
record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile,
String controllerClass,
Set<FieldInfo> injectedFields,
Set<String> injectedMethods, Map<String, Path> includes) {
public record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path controllerFile,
String controllerClass, Set<FieldInfo> injectedFields, Set<String> injectedMethods,
Map<String, Path> includes, boolean requiresResourceBundle) {
CompilationInfo {
public CompilationInfo {
Objects.requireNonNull(inputFile);
Objects.requireNonNull(outputFile);
Objects.requireNonNull(outputClass);
@@ -44,6 +44,7 @@ record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path
private String outputClass;
private Path controllerFile;
private String controllerClass;
private boolean requiresResourceBundle;
private final Set<FieldInfo> injectedFields;
private final Set<String> injectedMethods;
private final Map<String, Path> includes;
@@ -98,8 +99,13 @@ record CompilationInfo(Path inputFile, Path outputFile, String outputClass, Path
return this;
}
Builder requiresResourceBundle() {
this.requiresResourceBundle = true;
return this;
}
CompilationInfo build() {
return new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes);
return new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
}
}
}

View File

@@ -1,8 +1,10 @@
package com.github.gtache.fxml.compiler.maven;
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
import javafx.event.EventHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
@@ -17,28 +19,32 @@ import java.nio.file.Paths;
import java.util.Map;
import java.util.regex.Pattern;
import static java.util.Objects.requireNonNull;
/**
* Helper class for {@link FXMLCompilerMojo} to provides {@link CompilationInfo}
*/
class CompilationInfoProvider {
public final class CompilationInfoProvider {
private static final Logger logger = LogManager.getLogger(CompilationInfoProvider.class);
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final Pattern START_DOT_PATTERN = Pattern.compile("^\\.");
private final MavenProject project;
private final Path outputDirectory;
private final Log logger;
CompilationInfoProvider(final MavenProject project, final Path outputDirectory, final Log logger) {
this.project = requireNonNull(project);
this.outputDirectory = requireNonNull(outputDirectory);
this.logger = requireNonNull(logger);
private CompilationInfoProvider() {
}
CompilationInfo getCompilationInfo(final Path root, final Path inputPath, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
logger.info("Parsing " + inputPath);
/**
* Gets the compilation info for the given input
*
* @param root The root path
* @param inputPath The input path
* @param controllerMapping The controller mapping
* @param outputDirectory The output directory
* @param project The Maven project
* @return The compilation info
* @throws MojoExecutionException If an error occurs
*/
public static CompilationInfo getCompilationInfo(final Path root, final Path inputPath, final Map<? extends Path, String> controllerMapping,
final Path outputDirectory, final MavenProject project) throws MojoExecutionException {
logger.info("Parsing {}", inputPath);
try {
final var documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final var document = documentBuilder.parse(inputPath.toFile());
@@ -52,8 +58,8 @@ class CompilationInfoProvider {
final var targetPath = Paths.get(replacedPrefixPath.replace(inputFilename, outputFilename));
builder.outputFile(targetPath);
builder.outputClass(outputClass);
handleNode(document.getDocumentElement(), builder, controllerMapping);
logger.info(inputPath + " will be compiled to " + targetPath);
handleNode(document.getDocumentElement(), builder, controllerMapping, project);
logger.info("{} will be compiled to {}", inputPath, targetPath);
return builder.build();
} catch (final SAXException | IOException | ParserConfigurationException e) {
throw new MojoExecutionException("Error parsing fxml at " + inputPath, e);
@@ -84,15 +90,15 @@ class CompilationInfoProvider {
return builder.toString().replace(".fxml", ".java");
}
private void handleNode(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
private static void handleNode(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
if (node.getNodeName().equals("fx:include")) {
handleInclude(node, builder);
}
handleAttributes(node, builder, controllerMapping);
handleChildren(node, builder, controllerMapping);
handleAttributes(node, builder, controllerMapping, project);
handleChildren(node, builder, controllerMapping, project);
}
private void handleInclude(final Node node, final CompilationInfo.Builder builder) throws MojoExecutionException {
private static void handleInclude(final Node node, final CompilationInfo.Builder builder) throws MojoExecutionException {
final var map = node.getAttributes();
if (map == null) {
throw new MojoExecutionException("Missing attributes for include");
@@ -103,7 +109,7 @@ class CompilationInfoProvider {
} else {
final var source = sourceAttr.getNodeValue();
final var path = getRelativePath(builder.inputFile(), source);
logger.info("Found include " + source);
logger.info("Found include {}", source);
builder.addInclude(source, path);
}
}
@@ -113,14 +119,14 @@ class CompilationInfoProvider {
return base.getParent().resolve(relative).normalize();
}
private void handleChildren(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
private static void handleChildren(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
final var nl = node.getChildNodes();
for (var i = 0; i < nl.getLength(); i++) {
handleNode(nl.item(i), builder, controllerMapping);
handleNode(nl.item(i), builder, controllerMapping, project);
}
}
private void handleAttributes(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping) throws MojoExecutionException {
private static void handleAttributes(final Node node, final CompilationInfo.Builder builder, final Map<? extends Path, String> controllerMapping, final MavenProject project) throws MojoExecutionException {
final var map = node.getAttributes();
if (map != null) {
for (var i = 0; i < map.getLength(); i++) {
@@ -130,20 +136,20 @@ class CompilationInfoProvider {
if (name.startsWith("on")) {
if (value.startsWith("#")) {
final var methodName = value.replace("#", "");
logger.debug("Found injected method " + methodName);
logger.debug("Found injected method {}", methodName);
builder.addInjectedMethod(methodName);
} else if (value.startsWith("$controller.")) {
final var fieldName = value.replace("$controller.", "");
logger.debug("Found injected field " + fieldName);
logger.debug("Found injected field {}", fieldName);
builder.addInjectedField(fieldName, EventHandler.class.getName());
} else {
throw new MojoExecutionException("Unexpected attribute " + name + " with value " + value);
}
} else if (name.equals("fx:controller")) {
handleController(value, builder);
handleController(value, builder, project);
} else if (name.equals("fx:id")) {
final var type = node.getNodeName();
logger.debug("Found injected field " + value + " of type " + type);
logger.debug("Found injected field {} of type {}", value, type);
if (type.equals("fx:include")) {
final var path = getRelativePath(builder.inputFile(), map.getNamedItem("source").getNodeValue()).normalize();
final var controllerClass = controllerMapping.get(path);
@@ -154,19 +160,21 @@ class CompilationInfoProvider {
} else {
builder.addInjectedField(value, type);
}
} else if (value.startsWith("%")) {
builder.requiresResourceBundle();
}
}
}
}
private void handleController(final String controllerClass, final CompilationInfo.Builder builder) throws MojoExecutionException {
private static void handleController(final String controllerClass, final CompilationInfo.Builder builder, final MavenProject project) throws MojoExecutionException {
final var subPath = controllerClass.replace(".", "/") + ".java";
final var path = project.getCompileSourceRoots().stream()
.map(s -> Paths.get(s).resolve(subPath))
.filter(Files::exists)
.findFirst()
.orElseThrow(() -> new MojoExecutionException("Cannot find controller " + controllerClass));
logger.info("Found controller " + controllerClass);
logger.info("Found controller {}", controllerClass);
builder.controllerFile(path);
builder.controllerClass(controllerClass);
}

View File

@@ -0,0 +1,66 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.Generator;
import com.github.gtache.fxml.compiler.impl.GenerationRequestImpl;
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
import com.github.gtache.fxml.compiler.parsing.FXMLParser;
import com.github.gtache.fxml.compiler.parsing.ParseException;
import com.github.gtache.fxml.compiler.parsing.xml.DOMFXMLParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import javax.inject.Named;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
/**
* Creates compiled Java code
*/
@Named
public final class Compiler {
private static final Logger logger = LogManager.getLogger(Compiler.class);
private static final FXMLParser PARSER = new DOMFXMLParser();
private static final Generator GENERATOR = new GeneratorImpl();
private Compiler() {
}
/**
* Compiles the given files
*
* @param mapping The mapping of file to compile to compilation info
* @param parameters The generation parameters
* @throws MojoExecutionException If an error occurs
*/
public static void compile(final Map<Path, CompilationInfo> mapping, final GenerationParameters parameters) throws MojoExecutionException {
for (final var entry : mapping.entrySet()) {
compile(entry.getKey(), entry.getValue(), mapping, parameters);
}
}
private static void compile(final Path inputPath, final CompilationInfo info, final Map<Path, CompilationInfo> mapping, final GenerationParameters parameters) throws MojoExecutionException {
try {
logger.info("Parsing {} with {}", inputPath, PARSER.getClass().getSimpleName());
final var root = PARSER.parse(inputPath);
final var controllerInfo = ControllerInfoProvider.getControllerInfo(info);
final var output = info.outputFile();
final var sourceInfo = SourceInfoProvider.getSourceInfo(info, mapping);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, root, info.outputClass());
logger.info("Compiling {}", inputPath);
final var content = GENERATOR.generate(request);
final var outputDir = output.getParent();
Files.createDirectories(outputDir);
Files.writeString(output, content);
logger.info("Compiled {} to {}", inputPath, output);
} catch (final IOException | RuntimeException | ParseException | GenerationException e) {
throw new MojoExecutionException("Error compiling fxml", e);
}
}
}

View File

@@ -1,55 +1,45 @@
package com.github.gtache.fxml.compiler.maven;
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.ControllerFieldInfo;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.impl.ClassesFinder;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInfoImpl;
import com.github.gtache.fxml.compiler.impl.ControllerInfoImpl;
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Helper class for {@link FXMLCompilerMojo} to provides {@link ControllerInfo}
*/
class ControllerInfoProvider {
final class ControllerInfoProvider {
private static final Logger logger = LogManager.getLogger(ControllerInfoProvider.class);
private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+(?:static\\s+)?(?<import>[^;]+);");
private static final Pattern INITIALIZE_PATTERN = Pattern.compile("void\\s+initialize\\s*\\(\\s*\\)\\s*");
private static final Set<String> JAVA_LANG_CLASSES;
static {
final var set = new HashSet<String>();
set.add("Object");
set.add("String");
set.add("Boolean");
set.add("Character");
set.add("Byte");
set.add("Short");
set.add("Integer");
set.add("Long");
set.add("Float");
set.add("Double");
JAVA_LANG_CLASSES = Set.copyOf(set);
private ControllerInfoProvider() {
}
private final Log logger;
ControllerInfoProvider(final Log logger) {
this.logger = Objects.requireNonNull(logger);
}
ControllerInfo getControllerInfo(final CompilationInfo info) throws MojoExecutionException {
/**
* Gets the controller info for the given compilation info
*
* @param info The compilation info
* @return The controller info
* @throws MojoExecutionException If an error occurs
*/
static ControllerInfo getControllerInfo(final CompilationInfo info) throws MojoExecutionException {
try {
final var content = Files.readString(info.controllerFile());
final var imports = getImports(content);
@@ -58,16 +48,14 @@ class ControllerInfoProvider {
final var name = fieldInfo.name();
final var type = fieldInfo.type();
if (fillFieldInfo(type, name, content, imports, propertyGenericTypes)) {
logger.debug("Found injected field " + name + " of type " + type + " with generic types "
+ propertyGenericTypes.get(name) + " in controller " + info.controllerFile());
logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, type, propertyGenericTypes.get(name), info.controllerFile());
} else if (type.contains(".")) {
final var simpleName = type.substring(type.lastIndexOf('.') + 1);
if (fillFieldInfo(simpleName, name, content, imports, propertyGenericTypes)) {
logger.debug("Found injected field " + name + " of type " + simpleName + " with generic types "
+ propertyGenericTypes.get(name) + " in controller " + info.controllerFile());
logger.debug("Found injected field {} of type {} with generic types {} in controller {}", name, simpleName, propertyGenericTypes.get(name), info.controllerFile());
}
} else {
logger.info("Field " + name + "(" + type + ")" + " not found in controller " + info.controllerFile());
logger.info("Field {}({}) not found in controller {}", name, type, info.controllerFile());
}
}
final var handlerHasArgument = new HashMap<String, Boolean>();
@@ -77,12 +65,13 @@ class ControllerInfoProvider {
if (matcher.find()) {
final var arg = matcher.group("arg");
handlerHasArgument.put(name, arg != null && !arg.isBlank());
logger.debug("Found injected method " + name + " with argument " + arg + " in controller " + info.controllerFile());
logger.debug("Found injected method {} with argument {} in controller {}", name, arg, info.controllerFile());
} else {
throw new MojoExecutionException("Cannot find method " + name + " in controller " + info.controllerFile());
}
}
return new ControllerInfoImpl(handlerHasArgument, propertyGenericTypes);
final var hasInitialize = INITIALIZE_PATTERN.matcher(content).find();
return new ControllerInfoImpl(info.controllerClass(), handlerHasArgument, propertyGenericTypes, hasInitialize);
} catch (final IOException e) {
throw new MojoExecutionException("Error reading controller " + info.controllerFile(), e);
}
@@ -115,28 +104,13 @@ class ControllerInfoProvider {
}
private static boolean fillFieldInfo(final String type, final String name, final CharSequence content, final Imports imports, final Map<? super String, ? super ControllerFieldInfo> fieldInfos) throws MojoExecutionException {
final var pattern = Pattern.compile(Pattern.quote(type) + "(?<type><[^>]+>)?\\s+" + Pattern.quote(name) + "\\s*;");
final var pattern = Pattern.compile(Pattern.quote(type) + "\\s*(?<type><.+>)?\\s*" + Pattern.quote(name) + "\\s*;");
final var matcher = pattern.matcher(content);
if (matcher.find()) {
final var genericTypes = matcher.group("type");
if (genericTypes != null && !genericTypes.isBlank()) {
final var split = genericTypes.replace("<", "").replace(">", "").split(",");
final var resolved = new ArrayList<String>();
for (final var s : split) {
final var trimmed = s.trim();
if (trimmed.contains(".") || JAVA_LANG_CLASSES.contains(trimmed)) {
resolved.add(trimmed);
} else {
final var imported = imports.imports().get(trimmed);
if (imported == null) {
throw new MojoExecutionException("Cannot find class " + trimmed + " probably in one of " + imports.packages() + " ; " +
"Use non-wildcard imports, use fully qualified name or put the classes in a dependency.");
} else {
resolved.add(imported);
}
}
}
fieldInfos.put(name, new ControllerFieldInfoImpl(name, resolved));
final var parsed = new GenericParser(genericTypes, imports.imports()).parse();
fieldInfos.put(name, new ControllerFieldInfoImpl(name, parsed));
} else {
fieldInfos.put(name, new ControllerFieldInfoImpl(name, List.of()));
}

View File

@@ -0,0 +1,53 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.maven.plugin.MojoExecutionException;
import org.xml.sax.SAXException;
import javax.inject.Named;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.nio.file.Path;
/**
* Extracts controller class from FXMLs
*/
@Named
public final class ControllerProvider {
private static final DocumentBuilder DOCUMENT_BUILDER;
static {
try {
DOCUMENT_BUILDER = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (final ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
private ControllerProvider() {
}
/**
* Gets the controller class for the given FXML
*
* @param fxml The FXML
* @return The controller class
* @throws MojoExecutionException If an error occurs
*/
public static String getController(final Path fxml) throws MojoExecutionException {
try {
final var document = DOCUMENT_BUILDER.parse(fxml.toFile());
document.getDocumentElement().normalize();
final var controller = document.getDocumentElement().getAttribute("fx:controller");
if (controller.isBlank()) {
throw new MojoExecutionException("Missing controller attribute for " + fxml);
} else {
return controller;
}
} catch (final SAXException | IOException e) {
throw new MojoExecutionException("Error parsing fxml at " + fxml, e);
}
}
}

View File

@@ -0,0 +1,54 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import javax.inject.Named;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* Extracts FXML paths from Maven project
*/
@Named
public final class FXMLProvider {
private static final Logger logger = LogManager.getLogger(FXMLProvider.class);
private FXMLProvider() {
}
/**
* Returns all the FXML files in the project's resources
*
* @param project The Maven project
* @return A mapping of file to resource directory
* @throws MojoExecutionException If an error occurs
*/
public static Map<Path, Path> getFXMLs(final MavenProject project) throws MojoExecutionException {
final var map = new HashMap<Path, Path>();
for (final var resource : project.getResources()) {
final var path = Paths.get(resource.getDirectory());
if (Files.isDirectory(path)) {
try (final var stream = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.toString().endsWith(".fxml"), FileVisitOption.FOLLOW_LINKS)) {
final var curList = stream.toList();
logger.info("Found {}", curList);
for (final var p : curList) {
map.put(p, path);
}
} catch (final IOException e) {
throw new MojoExecutionException("Error reading resources", e);
}
} else {
logger.info("Directory {} does not exist", path);
}
}
return map;
}
}

View File

@@ -1,4 +1,4 @@
package com.github.gtache.fxml.compiler.maven;
package com.github.gtache.fxml.compiler.maven.internal;
import java.util.Objects;
@@ -9,6 +9,14 @@ import java.util.Objects;
* @param name The field name
*/
record FieldInfo(String type, String name) {
/**
* Instantiates a new info
*
* @param type The field type
* @param name The field name
* @throws NullPointerException if any parameter is null
*/
FieldInfo {
Objects.requireNonNull(type);
Objects.requireNonNull(name);

View File

@@ -0,0 +1,102 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.GenericTypes;
import com.github.gtache.fxml.compiler.impl.GenericTypesImpl;
import org.apache.maven.plugin.MojoExecutionException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Parser of generic types
*/
final class GenericParser {
private static final Set<String> JAVA_LANG_CLASSES = Set.of(
"Boolean",
"Byte",
"Character",
"Double",
"Float",
"Integer",
"Long",
"Object",
"Short",
"String"
);
private final String content;
private final Map<String, String> imports;
private int index;
GenericParser(final String content, final Map<String, String> imports) {
this.content = Objects.requireNonNull(content);
this.imports = Map.copyOf(imports);
}
List<GenericTypes> parse() throws MojoExecutionException {
return parseGenericTypes();
}
private List<GenericTypes> parseGenericTypes() throws MojoExecutionException {
final var ret = new ArrayList<GenericTypes>();
eatSpaces();
eat('<');
do {
eatSpaces();
final var type = parseType();
eatSpaces();
if (peek() == '<') {
final var genericTypes = parseGenericTypes();
ret.add(new GenericTypesImpl(type, genericTypes));
} else if (peek() == '>') {
eat('>');
return ret;
} else if (peek() == ',') {
eat(',');
ret.add(new GenericTypesImpl(type, List.of()));
}
} while (index < content.length());
return ret;
}
private void eat(final char c) {
if (peek() == c) {
read();
} else {
throw new IllegalArgumentException("Expected " + c + " at " + index + " in " + content);
}
}
private void eatSpaces() {
while (peek() == ' ') {
read();
}
}
private String parseType() throws MojoExecutionException {
final var sb = new StringBuilder();
while (peek() != '<' && index < content.length()) {
sb.append(read());
}
final var type = sb.toString();
if (type.contains(".") || JAVA_LANG_CLASSES.contains(type)) {
return type;
} else if (imports.containsKey(type)) {
return imports.get(type);
} else {
throw new MojoExecutionException("Cannot find class " + type + " ; Use fully qualified name or put the classes in a dependency.");
}
}
private char peek() {
return content.charAt(index);
}
private char read() {
return content.charAt(index++);
}
}

View File

@@ -0,0 +1,38 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.impl.SourceInfoImpl;
import com.github.gtache.fxml.compiler.maven.FXMLCompilerMojo;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class for {@link FXMLCompilerMojo} to provides {@link SourceInfo}
*/
final class SourceInfoProvider {
private SourceInfoProvider() {
}
/**
* Provides the {@link SourceInfo} for the given compilation info
*
* @param info The compilation info
* @param mapping The mapping of file to compilation info
* @return The source info
*/
static SourceInfo getSourceInfo(final CompilationInfo info, final Map<Path, CompilationInfo> mapping) {
final var outputClass = info.outputClass();
final var controllerClass = info.controllerClass();
final var inputFile = info.inputFile();
final var includes = info.includes();
final var requiresResourceBundle = info.requiresResourceBundle();
final var includesMapping = new HashMap<String, SourceInfo>();
includes.forEach((k, v) -> includesMapping.put(k, getSourceInfo(mapping.get(v), mapping)));
//FIXME mutliple includes
return new SourceInfoImpl(outputClass, controllerClass, inputFile, List.copyOf(includesMapping.values()), includesMapping, requiresResourceBundle);
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.maven;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestFXMLCompilerMojo {
}

View File

@@ -0,0 +1,107 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class TestCompilationInfo {
private final Path inputFile;
private final Path outputFile;
private final String outputClass;
private final Path controllerFile;
private final String controllerClass;
private final Set<FieldInfo> injectedFields;
private final Set<String> injectedMethods;
private final Map<String, Path> includes;
private final boolean requiresResourceBundle;
private final CompilationInfo info;
TestCompilationInfo(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile, @Mock final FieldInfo fieldInfo) {
this.inputFile = Objects.requireNonNull(inputFile);
this.outputFile = Objects.requireNonNull(outputFile);
this.outputClass = "outputClass";
this.controllerFile = Objects.requireNonNull(controllerFile);
this.controllerClass = "controllerClass";
this.injectedFields = new HashSet<>(Set.of(fieldInfo));
this.injectedMethods = new HashSet<>(Set.of("one", "two"));
this.includes = new HashMap<>(Map.of("one", Objects.requireNonNull(inputFile)));
this.requiresResourceBundle = true;
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
}
@Test
void testGetters() {
assertEquals(inputFile, info.inputFile());
assertEquals(outputFile, info.outputFile());
assertEquals(outputClass, info.outputClass());
assertEquals(controllerFile, info.controllerFile());
assertEquals(controllerClass, info.controllerClass());
assertEquals(injectedFields, info.injectedFields());
assertEquals(injectedMethods, info.injectedMethods());
assertEquals(includes, info.includes());
assertEquals(requiresResourceBundle, info.requiresResourceBundle());
}
@Test
void testCopyInjectedFields() {
final var originalInjectedFields = info.injectedFields();
injectedFields.clear();
assertEquals(originalInjectedFields, info.injectedFields());
}
@Test
void testCopyInjectedMethods() {
final var originalInjectedMethods = info.injectedMethods();
injectedMethods.clear();
assertEquals(originalInjectedMethods, info.injectedMethods());
}
@Test
void testCopyIncludes() {
final var originalIncludes = Map.copyOf(includes);
includes.clear();
assertEquals(originalIncludes, info.includes());
}
@Test
void testUnmodifiableInjectedFields() {
final var originalInjectedFields = info.injectedFields();
assertThrows(UnsupportedOperationException.class, originalInjectedFields::clear);
}
@Test
void testUnmodifiableInjectedMethods() {
final var originalInjectedMethods = info.injectedMethods();
assertThrows(UnsupportedOperationException.class, originalInjectedMethods::clear);
}
@Test
void testUnmodifiableIncludes() {
final var originalIncludes = Map.copyOf(includes);
assertThrows(UnsupportedOperationException.class, originalIncludes::clear);
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new CompilationInfo(null, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, null, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, null, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, outputClass, null, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, null, injectedFields, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, null, injectedMethods, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, null, includes, requiresResourceBundle));
assertThrows(NullPointerException.class, () -> new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, null, requiresResourceBundle));
}
}

View File

@@ -0,0 +1,59 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(MockitoExtension.class)
class TestCompilationInfoBuilder {
private final Path inputFile;
private final Path outputFile;
private final String outputClass;
private final Path controllerFile;
private final String controllerClass;
private final Set<FieldInfo> injectedFields;
private final Set<String> injectedMethods;
private final Map<String, Path> includes;
private final boolean requiresResourceBundle;
private final CompilationInfo info;
TestCompilationInfoBuilder(@Mock final Path inputFile, @Mock final Path outputFile, @Mock final Path controllerFile, @Mock final FieldInfo fieldInfo) {
this.inputFile = Objects.requireNonNull(inputFile);
this.outputFile = Objects.requireNonNull(outputFile);
this.outputClass = "outputClass";
this.controllerFile = Objects.requireNonNull(controllerFile);
this.controllerClass = "controllerClass";
this.injectedFields = new HashSet<>(Set.of(fieldInfo));
this.injectedMethods = new HashSet<>(Set.of("one", "two"));
this.includes = new HashMap<>(Map.of("one", Objects.requireNonNull(inputFile)));
this.requiresResourceBundle = true;
this.info = new CompilationInfo(inputFile, outputFile, outputClass, controllerFile, controllerClass, injectedFields, injectedMethods, includes, requiresResourceBundle);
}
@Test
void testBuilder() {
final var builder = new CompilationInfo.Builder();
builder.inputFile(inputFile);
assertEquals(inputFile, builder.inputFile());
builder.outputFile(outputFile);
builder.outputClass(outputClass);
builder.controllerFile(controllerFile);
builder.controllerClass(controllerClass);
injectedFields.forEach(f -> builder.addInjectedField(f.name(), f.type()));
injectedMethods.forEach(builder::addInjectedMethod);
includes.forEach(builder::addInclude);
builder.requiresResourceBundle();
final var actual = builder.build();
assertEquals(info, actual);
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestCompilationInfoProvider {
}

View File

@@ -0,0 +1,155 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.ControllerInfo;
import com.github.gtache.fxml.compiler.GenerationException;
import com.github.gtache.fxml.compiler.GenerationParameters;
import com.github.gtache.fxml.compiler.Generator;
import com.github.gtache.fxml.compiler.SourceInfo;
import com.github.gtache.fxml.compiler.impl.GenerationRequestImpl;
import com.github.gtache.fxml.compiler.parsing.FXMLParser;
import com.github.gtache.fxml.compiler.parsing.ParseException;
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
import org.apache.maven.plugin.MojoExecutionException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class TestCompiler {
private final ControllerInfoProvider controllerInfoProvider;
private final SourceInfoProvider sourceInfoProvider;
private final FXMLParser fxmlParser;
private final Generator generator;
private final CompilationInfo compilationInfo;
private final ParsedObject object;
private final ControllerInfo controllerInfo;
private final SourceInfo sourceInfo;
private final String content;
private final GenerationParameters parameters;
private final Compiler compiler;
TestCompiler(@Mock final ControllerInfoProvider controllerInfoProvider, @Mock final SourceInfoProvider sourceInfoProvider,
@Mock final FXMLParser fxmlParser, @Mock final CompilationInfo compilationInfo, @Mock final ParsedObject object,
@Mock final ControllerInfo controllerInfo, @Mock final SourceInfo sourceInfo,
@Mock final GenerationParameters parameters, @Mock final Generator generator) {
this.controllerInfoProvider = Objects.requireNonNull(controllerInfoProvider);
this.sourceInfoProvider = Objects.requireNonNull(sourceInfoProvider);
this.fxmlParser = Objects.requireNonNull(fxmlParser);
this.compilationInfo = Objects.requireNonNull(compilationInfo);
this.object = Objects.requireNonNull(object);
this.controllerInfo = Objects.requireNonNull(controllerInfo);
this.sourceInfo = Objects.requireNonNull(sourceInfo);
this.content = "content";
this.parameters = Objects.requireNonNull(parameters);
this.generator = Objects.requireNonNull(generator);
this.compiler = new Compiler(controllerInfoProvider, sourceInfoProvider, fxmlParser, generator);
}
@BeforeEach
void beforeEach() throws MojoExecutionException, GenerationException, ParseException {
when(fxmlParser.parse((Path) any())).thenReturn(object);
when(ControllerInfoProvider.getControllerInfo(compilationInfo)).thenReturn(controllerInfo);
when(SourceInfoProvider.getSourceInfo(eq(compilationInfo), anyMap())).thenReturn(sourceInfo);
when(generator.generate(any())).thenReturn(content);
}
@Test
void testCompile(@TempDir final Path tempDir) throws Exception {
final var path = tempDir.resolve("fxml1.fxml");
final var outputPath = tempDir.resolve("subFolder").resolve("fxml1.java");
final var outputClass = "outputClass";
when(compilationInfo.outputFile()).thenReturn(outputPath);
when(compilationInfo.outputClass()).thenReturn(outputClass);
final var mapping = Map.of(path, compilationInfo);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, object, outputClass);
compiler.compile(mapping, parameters);
verify(fxmlParser).parse(path);
ControllerInfoProvider.getControllerInfo(compilationInfo);
SourceInfoProvider.getSourceInfo(compilationInfo, mapping);
verify(generator).generate(request);
assertEquals(content, Files.readString(outputPath));
}
@Test
void testCompileIOException(@TempDir final Path tempDir) throws Exception {
final var path = tempDir.resolve("fxml1.fxml");
final var outputPath = Paths.get("/whatever");
final var outputClass = "outputClass";
when(compilationInfo.outputFile()).thenReturn(outputPath);
when(compilationInfo.outputClass()).thenReturn(outputClass);
final var mapping = Map.of(path, compilationInfo);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, object, outputClass);
assertThrows(MojoExecutionException.class, () -> compiler.compile(mapping, parameters));
verify(fxmlParser).parse(path);
ControllerInfoProvider.getControllerInfo(compilationInfo);
SourceInfoProvider.getSourceInfo(compilationInfo, mapping);
verify(generator).generate(request);
}
@Test
void testCompileRuntimeException(@TempDir final Path tempDir) throws Exception {
final var path = tempDir.resolve("fxml1.fxml");
final var outputPath = tempDir.resolve("subFolder").resolve("fxml1.java");
final var outputClass = "outputClass";
when(compilationInfo.outputFile()).thenReturn(outputPath);
when(compilationInfo.outputClass()).thenReturn(outputClass);
final var mapping = Map.of(path, compilationInfo);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, object, outputClass);
when(generator.generate(request)).thenThrow(RuntimeException.class);
assertThrows(MojoExecutionException.class, () -> compiler.compile(mapping, parameters));
verify(fxmlParser).parse(path);
ControllerInfoProvider.getControllerInfo(compilationInfo);
SourceInfoProvider.getSourceInfo(compilationInfo, mapping);
verify(generator).generate(request);
}
@Test
void testCompileParseException(@TempDir final Path tempDir) throws Exception {
when(fxmlParser.parse((Path) any())).thenThrow(ParseException.class);
final var path = tempDir.resolve("fxml1.fxml");
final var mapping = Map.of(path, compilationInfo);
assertThrows(MojoExecutionException.class, () -> compiler.compile(mapping, parameters));
verify(fxmlParser).parse(path);
verifyNoInteractions(controllerInfoProvider, sourceInfoProvider, generator);
}
@Test
void testCompileGenerationException(@TempDir final Path tempDir) throws Exception {
final var path = tempDir.resolve("fxml1.fxml");
final var outputPath = tempDir.resolve("subFolder").resolve("fxml1.java");
final var outputClass = "outputClass";
when(compilationInfo.outputFile()).thenReturn(outputPath);
when(compilationInfo.outputClass()).thenReturn(outputClass);
final var mapping = Map.of(path, compilationInfo);
final var request = new GenerationRequestImpl(parameters, controllerInfo, sourceInfo, object, outputClass);
when(generator.generate(request)).thenThrow(GenerationException.class);
assertThrows(MojoExecutionException.class, () -> compiler.compile(mapping, parameters));
verify(fxmlParser).parse(path);
ControllerInfoProvider.getControllerInfo(compilationInfo);
SourceInfoProvider.getSourceInfo(compilationInfo, mapping);
verify(generator).generate(request);
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new Compiler(null, sourceInfoProvider, fxmlParser, generator));
assertThrows(NullPointerException.class, () -> new Compiler(controllerInfoProvider, null, fxmlParser, generator));
assertThrows(NullPointerException.class, () -> new Compiler(controllerInfoProvider, sourceInfoProvider, null, generator));
assertThrows(NullPointerException.class, () -> new Compiler(controllerInfoProvider, sourceInfoProvider, fxmlParser, null));
}
}

View File

@@ -0,0 +1,81 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.impl.ControllerFieldInfoImpl;
import com.github.gtache.fxml.compiler.impl.ControllerInfoImpl;
import org.apache.maven.plugin.MojoExecutionException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestControllerInfoProvider {
private final CompilationInfo compilationInfo;
private final ControllerInfoProvider controllerInfoProvider;
TestControllerInfoProvider(@Mock final CompilationInfo compilationInfo) {
this.compilationInfo = Objects.requireNonNull(compilationInfo);
this.controllerInfoProvider = new ControllerInfoProvider();
}
@Test
void testGetControllerInfo(@TempDir final Path tempDir) throws Exception {
final var fxml = tempDir.resolve("fxml.fxml");
Files.writeString(fxml, """
package com.github.gtache.fxml.compiler.maven.internal;
import javafx.event.EventHandler;
import javafx.event.KeyEvent;
import javafx.scene.control.*;
public class LoadController {
private EventHandler<KeyEvent> keyEventHandler;
private ComboBox<String> comboBox;
private Button button;
private ComboBox rawBox;
private TableColumn<Integer, ComboBox<String>> tableColumn;
@FXML
void initialize() {
}
@FXML
private void onClick(){
}
@FXML
private void onOtherClick(final KeyEvent event){
}
}
""");
when(compilationInfo.controllerFile()).thenReturn(fxml);
final var expectedInfo = new ControllerInfoImpl("com.github.gtache.fxml.compiler.maven.internal.LoadController",
Map.of("onClick", false, "onOtherClick", true), Map.of("keyEventHandler", new ControllerFieldInfoImpl("keyEventHandler", List.of()),
"comboBox", new ControllerFieldInfoImpl("comboBox", List.of("String")), "button", new ControllerFieldInfoImpl("button", List.of()),
"rawBox", new ControllerFieldInfoImpl("rawBox", List.of()),
"tableColumn", new ControllerFieldInfoImpl("tableColumn", List.of("Integer", "javafx.scene.control.ComboBox<String>"))), true);
final var actual = ControllerInfoProvider.getControllerInfo(compilationInfo);
assertEquals(expectedInfo, actual);
}
@Test
void testGetControllerInfoException() {
when(compilationInfo.controllerFile()).thenReturn(Paths.get("/whatever"));
assertThrows(MojoExecutionException.class, () -> ControllerInfoProvider.getControllerInfo(compilationInfo));
}
}

View File

@@ -0,0 +1,48 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.maven.plugin.MojoExecutionException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(MockitoExtension.class)
class TestControllerProvider {
private final ControllerProvider provider;
TestControllerProvider() {
this.provider = new ControllerProvider();
}
@Test
void testGetController(@TempDir final Path tempDir) throws Exception {
final var fxml = tempDir.resolve("fxml.fxml");
Files.writeString(fxml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<BorderPane xmlns=\"http://javafx.com/javafx/22\" xmlns:fx=\"http://javafx.com/fxml/1\"" +
" fx:controller=\"LoadController\">" +
"</BorderPane>\n");
assertEquals("LoadController", ControllerProvider.getController(fxml));
}
@Test
void testGetControllerBlank(@TempDir final Path tempDir) throws Exception {
final var fxml = tempDir.resolve("fxml.fxml");
Files.writeString(fxml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<BorderPane xmlns=\"http://javafx.com/javafx/22\" xmlns:fx=\"http://javafx.com/fxml/1\">" +
"</BorderPane>\n");
assertThrows(MojoExecutionException.class, () -> ControllerProvider.getController(fxml));
}
@Test
void testGetControllerError() {
assertThrows(MojoExecutionException.class, () -> ControllerProvider.getController(Paths.get("fxml.fxml")));
}
}

View File

@@ -0,0 +1,65 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.apache.maven.model.Resource;
import org.apache.maven.project.MavenProject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class TestFXMLProvider {
private final MavenProject project;
private final FXMLProvider provider;
TestFXMLProvider(@Mock final MavenProject project) {
this.project = Objects.requireNonNull(project);
this.provider = new FXMLProvider();
}
@Test
void testGetFXMLs(@TempDir final Path tempDir, @TempDir final Path otherTempDir) throws Exception {
final var subFolder = tempDir.resolve("subFolder");
Files.createFile(subFolder.resolve("subfxml1.fxml"));
Files.createFile(subFolder.resolve("subfxml2.fxml"));
Files.createFile(tempDir.resolve("fxml1.fxml"));
Files.createFile(tempDir.resolve("fxml2.fxml"));
final var otherSubFolder = otherTempDir.resolve("subFolder");
Files.createFile(otherSubFolder.resolve("subfxml1.fxml"));
Files.createFile(otherSubFolder.resolve("subfxml2.fxml"));
Files.createFile(otherTempDir.resolve("fxml1.fxml"));
Files.createFile(otherTempDir.resolve("fxml2.fxml"));
final var resource1 = mock(Resource.class);
final var resource2 = mock(Resource.class);
when(resource1.getDirectory()).thenReturn(tempDir.toString());
when(resource2.getDirectory()).thenReturn(otherTempDir.toString());
when(project.getResources()).thenReturn(List.of(resource1, resource2));
final var expected = Map.of(
subFolder.resolve("subfxml1.fxml"), tempDir,
subFolder.resolve("subfxml2.fxml"), tempDir,
tempDir.resolve("fxml1.fxml"), tempDir,
tempDir.resolve("fxml2.fxml"), tempDir,
otherSubFolder.resolve("subfxml1.fxml"), otherTempDir,
otherSubFolder.resolve("subfxml2.fxml"), otherTempDir,
otherTempDir.resolve("fxml1.fxml"), otherTempDir,
otherTempDir.resolve("fxml2.fxml"), otherTempDir
);
final var map = FXMLProvider.getFXMLs(project);
assertEquals(expected, map);
}
}

View File

@@ -0,0 +1,31 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class TestFieldInfo {
private final String type;
private final String name;
private final FieldInfo fieldInfo;
TestFieldInfo() {
this.type = "type";
this.name = "name";
this.fieldInfo = new FieldInfo(type, name);
}
@Test
void testGetters() {
assertEquals(type, fieldInfo.type());
assertEquals(name, fieldInfo.name());
}
@Test
void testIllegal() {
assertThrows(NullPointerException.class, () -> new FieldInfo(null, name));
assertThrows(NullPointerException.class, () -> new FieldInfo(type, null));
}
}

View File

@@ -0,0 +1,49 @@
package com.github.gtache.fxml.compiler.maven.internal;
import com.github.gtache.fxml.compiler.impl.GenericTypesImpl;
import org.apache.maven.plugin.MojoExecutionException;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TestGenericParser {
@ParameterizedTest
@MethodSource("providesParseTests")
void testParse(final String genericTypes, final Map<String, String> imports, final List<GenericTypesImpl> expectedGenericTypes) throws MojoExecutionException {
final var parser = new GenericParser(genericTypes, imports);
final var parsed = parser.parse();
assertEquals(expectedGenericTypes, parsed);
}
private static Stream<Arguments> providesParseTests() {
return Stream.of(
Arguments.of("<Map<String, TableView<Node, Node>>>",
Map.of("Map", "java.util.Map", "String", "java.lang.String", "TableView", "javafx.scene.control.TableView", "Node", "javafx.scene.Node"),
List.of(new GenericTypesImpl("java.util.Map",
List.of(new GenericTypesImpl("java.lang.String", List.of()), new GenericTypesImpl("javafx.scene.control.TableView",
List.of(new GenericTypesImpl("javafx.scene.Node", List.of()), new GenericTypesImpl("javafx.scene.Node", List.of()))))))),
Arguments.of("<Map<String, String>>",
Map.of("Map", "java.util.Map"),
List.of(new GenericTypesImpl("java.util.Map",
List.of(new GenericTypesImpl("String", List.of()), new GenericTypesImpl("String", List.of()))))),
Arguments.of("<String>",
Map.of(),
List.of(new GenericTypesImpl("String", List.of()))),
Arguments.of("<Collection<String>>",
Map.of("Collection", "java.util.Collection", "String", "java.lang.String"),
List.of(new GenericTypesImpl("java.util.Collection",
List.of(new GenericTypesImpl("java.lang.String", List.of()))))),
Arguments.of("<Collection<String>>",
Map.of(),
List.of(new GenericTypesImpl("Collection",
List.of(new GenericTypesImpl("String", List.of())))))
);
}
}

View File

@@ -0,0 +1,8 @@
package com.github.gtache.fxml.compiler.maven.internal;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TestSourceInfoProvider {
}