Finishes implementing, seems to work ; needs to manage define, copy, reference, root
This commit is contained in:
@@ -16,11 +16,14 @@
|
||||
<groupId>com.github.gtache</groupId>
|
||||
<artifactId>fxml-compiler-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Helper class to find classes in a package
|
||||
*/
|
||||
public final class ClassesFinder {
|
||||
private static final Pattern START_FILE_PATTERN = Pattern.compile("^(?:file:/)?/");
|
||||
|
||||
private ClassesFinder() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all classes in the given package
|
||||
*
|
||||
* @param packageName The package
|
||||
* @return The classes
|
||||
* @throws IOException If an error occurs
|
||||
*/
|
||||
public static Set<String> getClasses(final String packageName) throws IOException {
|
||||
return doGetClasses(packageName);
|
||||
}
|
||||
|
||||
private static Set<String> doGetClasses(final String packageName) throws IOException {
|
||||
final var classLoader = Thread.currentThread().getContextClassLoader();
|
||||
final var path = packageName.replace('.', '/');
|
||||
final var resources = classLoader.getResources(path);
|
||||
final var classes = new HashSet<String>();
|
||||
while (resources.hasMoreElements()) {
|
||||
final var resource = resources.nextElement();
|
||||
final var file = resource.getFile();
|
||||
if (file.contains(".jar!")) {
|
||||
final var jarFile = file.substring(0, file.indexOf(".jar!") + 4);
|
||||
try (final var fs = FileSystems.newFileSystem(Paths.get(URI.create(jarFile)), classLoader)) {
|
||||
classes.addAll(findClasses(fs.getPath(path), packageName));
|
||||
}
|
||||
} else {
|
||||
final var filepath = START_FILE_PATTERN.matcher(file).replaceAll("");
|
||||
classes.addAll(findClasses(Paths.get(filepath), packageName));
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static List<String> findClasses(final Path directory, final String packageName) throws IOException {
|
||||
if (Files.isDirectory(directory)) {
|
||||
final var classes = new ArrayList<String>();
|
||||
try (final var stream = Files.list(directory)) {
|
||||
final var files = stream.toList();
|
||||
for (final var file : files) {
|
||||
final var filename = file.getFileName().toString();
|
||||
if (filename.endsWith(".class")) {
|
||||
final var className = packageName + '.' + filename.substring(0, filename.length() - 6);
|
||||
classes.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.SequencedMap;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Used by {@link ReflectionHelper} to store the constructor arguments
|
||||
*
|
||||
* @param constructor The constructor
|
||||
* @param namedArgs The named arguments
|
||||
*/
|
||||
record ConstructorArgs(Constructor<?> constructor,
|
||||
SequencedMap<String, Parameter> namedArgs) {
|
||||
ConstructorArgs {
|
||||
requireNonNull(constructor);
|
||||
namedArgs = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(namedArgs));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import java.util.Objects;
|
||||
* Implementation of {@link GenerationRequest}
|
||||
*
|
||||
* @param parameters The generation parameters
|
||||
* @param controllerInfo The controller info
|
||||
* @param rootObject The root object
|
||||
* @param outputClassName The output class name
|
||||
*/
|
||||
|
||||
@@ -1,42 +1,31 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.ControllerInjection;
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.GenerationRequest;
|
||||
import com.github.gtache.fxml.compiler.Generator;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedConstant;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedInclude;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static com.github.gtache.fxml.compiler.impl.ReflectionHelper.*;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Generator}
|
||||
*/
|
||||
public class GeneratorImpl implements Generator {
|
||||
|
||||
private static final Map<Class<?>, Boolean> HAS_VALUE_OF = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Boolean> IS_GENERIC = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Map<String, Method>> METHODS = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Map<String, Method>> STATIC_METHODS = new ConcurrentHashMap<>();
|
||||
private static final Pattern INT_PATTERN = Pattern.compile("\\d+");
|
||||
private static final Pattern DECIMAL_PATTERN = Pattern.compile("\\d+(?:\\.\\d+)?");
|
||||
|
||||
private final Collection<String> controllerFactoryPostAction;
|
||||
private final Map<String, AtomicInteger> variableNameCounters;
|
||||
@@ -50,7 +39,7 @@ public class GeneratorImpl implements Generator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate(final GenerationRequest request) {
|
||||
public String generate(final GenerationRequest request) throws GenerationException {
|
||||
controllerFactoryPostAction.clear();
|
||||
variableNameCounters.clear();
|
||||
final var className = request.outputClassName();
|
||||
@@ -119,6 +108,11 @@ public class GeneratorImpl implements Generator {
|
||||
this.resourceBundlesMap = Map.copyOf(resourceBundlesMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the view. Can only be called once.
|
||||
*
|
||||
* @return The view parent
|
||||
*/
|
||||
%6$s
|
||||
|
||||
%10$s
|
||||
@@ -144,7 +138,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param request The generation request
|
||||
* @return The helper methods
|
||||
*/
|
||||
private static String getHelperMethods(final GenerationRequest request) {
|
||||
private static String getHelperMethods(final GenerationRequest request) throws GenerationException {
|
||||
final var injection = getControllerInjection(request);
|
||||
final var methodInjectionType = injection.methodInjectionType();
|
||||
final var sb = new StringBuilder();
|
||||
@@ -207,7 +201,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param request The generation request
|
||||
* @return The imports
|
||||
*/
|
||||
private static String getImports(final GenerationRequest request) {
|
||||
private static String getImports(final GenerationRequest request) throws GenerationException {
|
||||
final var injection = getControllerInjection(request);
|
||||
final var fieldInjectionType = injection.fieldInjectionType();
|
||||
final var sb = new StringBuilder("import java.util.Map;\nimport java.util.ResourceBundle;\nimport java.util.HashMap;\n");
|
||||
@@ -230,12 +224,12 @@ public class GeneratorImpl implements Generator {
|
||||
* @param request The generation request
|
||||
* @return The load method
|
||||
*/
|
||||
private String getLoadMethod(final GenerationRequest request) {
|
||||
private String getLoadMethod(final GenerationRequest request) throws GenerationException {
|
||||
final var rootObject = request.rootObject();
|
||||
final var controllerInjection = getControllerInjection(request);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
final var controllerClass = controllerInjection.injectionClass();
|
||||
final var sb = new StringBuilder("public javafx.scene.Parent load() {\n");
|
||||
final var sb = new StringBuilder("public <T> T load() {\n");
|
||||
sb.append(" if (loaded) {\n");
|
||||
sb.append(" throw new IllegalStateException(\"Already loaded\");\n");
|
||||
sb.append(" }\n");
|
||||
@@ -283,44 +277,52 @@ public class GeneratorImpl implements Generator {
|
||||
* @param variableName The variable name for the object
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private void format(final GenerationRequest request, final ParsedObject parsedObject, final String variableName, final StringBuilder sb) {
|
||||
if (parsedObject instanceof final ParsedInclude include) {
|
||||
formatInclude(request, include, variableName, sb);
|
||||
} else {
|
||||
final var clazz = parsedObject.clazz();
|
||||
final var constructors = clazz.getConstructors();
|
||||
final var allPropertyNames = new HashSet<>(parsedObject.properties().keySet());
|
||||
allPropertyNames.addAll(parsedObject.children().keySet().stream().map(ParsedProperty::name).collect(Collectors.toSet()));
|
||||
final var constructorArgs = getMatchingConstructorArgs(constructors, allPropertyNames);
|
||||
if (constructorArgs == null) {
|
||||
if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) {
|
||||
final var property = parsedObject.properties().get("fx:constant");
|
||||
sb.append(" final var ").append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n");
|
||||
private void format(final GenerationRequest request, final ParsedObject parsedObject, final String variableName, final StringBuilder sb) throws GenerationException {
|
||||
switch (parsedObject) {
|
||||
case final ParsedInclude include -> formatInclude(request, include, variableName, sb);
|
||||
case final ParsedConstant constant -> formatConstant(constant, variableName, sb);
|
||||
default -> {
|
||||
final var clazz = getClass(parsedObject.className());
|
||||
final var constructors = clazz.getConstructors();
|
||||
final var allPropertyNames = new HashSet<>(parsedObject.attributes().keySet());
|
||||
allPropertyNames.addAll(parsedObject.properties().keySet().stream().map(ParsedProperty::name).collect(Collectors.toSet()));
|
||||
final var constructorArgs = getMatchingConstructorArgs(constructors, allPropertyNames);
|
||||
if (constructorArgs == null) {
|
||||
if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) {
|
||||
final var property = parsedObject.attributes().get("fx:constant");
|
||||
sb.append(" final var ").append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n");
|
||||
} else {
|
||||
throw new GenerationException("Cannot find constructor for " + clazz.getCanonicalName());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot find constructor for " + clazz.getCanonicalName());
|
||||
final var args = getListConstructorArgs(constructorArgs, parsedObject);
|
||||
final var genericTypes = getGenericTypes(request, parsedObject);
|
||||
sb.append(" final var ").append(variableName).append(" = new ").append(clazz.getCanonicalName())
|
||||
.append(genericTypes).append("(").append(String.join(", ", args)).append(");\n");
|
||||
final var sortedAttributes = parsedObject.attributes().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList();
|
||||
for (final var e : sortedAttributes) {
|
||||
if (!constructorArgs.namedArgs().containsKey(e.getKey())) {
|
||||
final var p = e.getValue();
|
||||
formatProperty(request, p, parsedObject, variableName, sb);
|
||||
}
|
||||
}
|
||||
final var sortedProperties = parsedObject.properties().entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().name())).toList();
|
||||
for (final var e : sortedProperties) {
|
||||
if (!constructorArgs.namedArgs().containsKey(e.getKey().name())) {
|
||||
final var p = e.getKey();
|
||||
final var o = e.getValue();
|
||||
formatChild(request, parsedObject, p, o, variableName, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final var args = getListConstructorArgs(constructorArgs, parsedObject);
|
||||
final var genericTypes = getGenericTypes(request, parsedObject);
|
||||
sb.append(" final var ").append(variableName).append(" = new ").append(clazz.getCanonicalName())
|
||||
.append(genericTypes).append("(").append(String.join(", ", args)).append(");\n");
|
||||
parsedObject.properties().entrySet().stream().filter(e -> !constructorArgs.namedArgs().containsKey(e.getKey())).forEach(e -> {
|
||||
final var p = e.getValue();
|
||||
formatProperty(request, p, parsedObject, variableName, sb);
|
||||
});
|
||||
parsedObject.children().entrySet().stream().filter(e -> !constructorArgs.namedArgs().containsKey(e.getKey().name())).forEach(e -> {
|
||||
final var p = e.getKey();
|
||||
final var o = e.getValue();
|
||||
formatChild(request, parsedObject, p, o, variableName, sb);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getGenericTypes(final GenerationRequest request, final ParsedObject parsedObject) {
|
||||
final var clazz = parsedObject.clazz();
|
||||
private static String getGenericTypes(final GenerationRequest request, final ParsedObject parsedObject) throws GenerationException {
|
||||
final var clazz = getClass(parsedObject.className());
|
||||
if (isGeneric(clazz)) {
|
||||
final var idProperty = parsedObject.properties().get("fx:id");
|
||||
final var idProperty = parsedObject.attributes().get("fx:id");
|
||||
if (idProperty == null) {
|
||||
return "<>";
|
||||
} else {
|
||||
@@ -336,16 +338,6 @@ public class GeneratorImpl implements Generator {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class is generic
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class is generic
|
||||
*/
|
||||
private static boolean isGeneric(final Class<?> clazz) {
|
||||
return IS_GENERIC.computeIfAbsent(clazz, c -> c.getTypeParameters().length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an include object
|
||||
@@ -355,14 +347,14 @@ public class GeneratorImpl implements Generator {
|
||||
* @param subNodeName The sub node name
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private void formatInclude(final GenerationRequest request, final ParsedInclude include, final String subNodeName, final StringBuilder sb) {
|
||||
private void formatInclude(final GenerationRequest request, final ParsedInclude include, final String subNodeName, final StringBuilder sb) throws GenerationException {
|
||||
final var subViewVariable = getNextVariableName("view");
|
||||
final var source = include.source();
|
||||
final var resources = include.resources();
|
||||
final var subControllerClass = request.parameters().sourceToControllerName().get(source);
|
||||
final var subClassName = request.parameters().sourceToGeneratedClassName().get(source);
|
||||
if (subClassName == null) {
|
||||
throw new IllegalArgumentException("Unknown include source : " + source);
|
||||
throw new GenerationException("Unknown include source : " + source);
|
||||
}
|
||||
if (resources == null) {
|
||||
sb.append(" final var ").append(subViewVariable).append(" = new ").append(subClassName).append("(controllersMap, resourceBundlesMap);\n");
|
||||
@@ -383,6 +375,17 @@ public class GeneratorImpl implements Generator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a constant object
|
||||
*
|
||||
* @param constant The constant
|
||||
* @param variableName The variable name
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatConstant(final ParsedConstant constant, final String variableName, final StringBuilder sb) {
|
||||
sb.append(" final var ").append(variableName).append(" = ").append(constant.className()).append(".").append(constant.constant()).append(";\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a property
|
||||
*
|
||||
@@ -392,7 +395,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param parentVariable The parent variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private void formatProperty(final GenerationRequest request, final ParsedProperty property, final ParsedObject parent, final String parentVariable, final StringBuilder sb) {
|
||||
private void formatProperty(final GenerationRequest request, final ParsedProperty property, final ParsedObject parent, final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var setMethod = getSetMethod(propertyName);
|
||||
if (propertyName.equals("fx:id")) {
|
||||
@@ -400,40 +403,42 @@ public class GeneratorImpl implements Generator {
|
||||
injectControllerField(request, id, parentVariable, sb);
|
||||
} else if (propertyName.equals("fx:controller")) {
|
||||
if (parent != request.rootObject()) {
|
||||
throw new IllegalStateException("Invalid nested controller");
|
||||
throw new GenerationException("Invalid nested controller");
|
||||
}
|
||||
} else if (property.sourceType() == EventHandler.class) {
|
||||
} else if (Objects.equals(property.sourceType(), EventHandler.class.getName())) {
|
||||
injectControllerMethod(request, property, parentVariable, sb);
|
||||
} else if (property.sourceType() != null) {
|
||||
if (hasStaticMethod(property.sourceType(), setMethod)) {
|
||||
final var method = getStaticMethod(property.sourceType(), setMethod);
|
||||
final var propertySourceTypeClass = getClass(property.sourceType());
|
||||
if (hasStaticMethod(propertySourceTypeClass, setMethod)) {
|
||||
final var method = getStaticMethod(propertySourceTypeClass, setMethod);
|
||||
final var parameterType = method.getParameterTypes()[1];
|
||||
final var arg = getArg(request, property.value(), parameterType);
|
||||
setLaterIfNeeded(request, property, parameterType, " " + property.sourceType().getName() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n", sb);
|
||||
setLaterIfNeeded(request, property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n", sb);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot set " + propertyName + " on " + property.sourceType().getCanonicalName());
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + property.sourceType());
|
||||
}
|
||||
} else {
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
if (hasMethod(parent.clazz(), setMethod)) {
|
||||
final var method = getMethod(parent.clazz(), setMethod);
|
||||
final var parentClass = getClass(parent.className());
|
||||
if (hasMethod(parentClass, setMethod)) {
|
||||
final var method = getMethod(parentClass, setMethod);
|
||||
final var parameterType = method.getParameterTypes()[0];
|
||||
final var arg = getArg(request, property.value(), parameterType);
|
||||
setLaterIfNeeded(request, property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n", sb);
|
||||
} else if (hasMethod(parent.clazz(), getMethod)) {
|
||||
final var method = getMethod(parent.clazz(), getMethod);
|
||||
} else if (hasMethod(parentClass, getMethod)) {
|
||||
final var method = getMethod(parentClass, getMethod);
|
||||
final var returnType = method.getReturnType();
|
||||
if (hasMethod(returnType, "addAll")) {
|
||||
final var arg = getArg(request, property.value(), String.class);
|
||||
setLaterIfNeeded(request, property, String.class, " " + parentVariable + "." + getMethod + "().addAll(" + arg + ");\n", sb);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot set " + propertyName + " on " + parent.clazz().getCanonicalName());
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + parent.className());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setLaterIfNeeded(final GenerationRequest request, final ParsedProperty property, final Class<?> type, final String arg, final StringBuilder sb) {
|
||||
private void setLaterIfNeeded(final GenerationRequest request, final ParsedProperty property, final Class<?> type, final String arg, final StringBuilder sb) throws GenerationException {
|
||||
if (type == String.class && property.value().startsWith("%") && request.parameters().resourceBundleInjection().injectionType() == ResourceBundleInjectionTypes.GETTER
|
||||
&& getControllerInjection(request).fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) {
|
||||
controllerFactoryPostAction.add(arg);
|
||||
@@ -450,7 +455,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param parentVariable The parent variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private void injectControllerMethod(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) {
|
||||
private void injectControllerMethod(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var injection = getControllerInjection(request);
|
||||
final var methodInjection = getMethodInjection(request, property, parentVariable, sb);
|
||||
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) {
|
||||
@@ -459,7 +464,7 @@ public class GeneratorImpl implements Generator {
|
||||
case ASSIGN, SETTERS, REFLECTION -> sb.append(methodInjection);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown injection type : " + injection.fieldInjectionType());
|
||||
throw new GenerationException("Unknown injection type : " + injection.fieldInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +477,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param sb The string builder
|
||||
* @return The method injection
|
||||
*/
|
||||
private static String getMethodInjection(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) {
|
||||
private static String getMethodInjection(final GenerationRequest request, final ParsedProperty property, final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property.name());
|
||||
final var injection = getControllerInjection(request);
|
||||
final var controllerMethod = property.value().replace("#", "");
|
||||
@@ -490,7 +495,7 @@ public class GeneratorImpl implements Generator {
|
||||
" " + parentVariable + "." + setMethod + "(e -> callMethod(\"" + controllerMethod + "\", e));\n";
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown injection type : " + injection.methodInjectionType());
|
||||
throw new GenerationException("Unknown injection type : " + injection.methodInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +507,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param parameterType The parameter type
|
||||
* @return The formatted value
|
||||
*/
|
||||
private static String getArg(final GenerationRequest request, final String value, final Class<?> parameterType) {
|
||||
private static String getArg(final GenerationRequest request, final String value, final Class<?> parameterType) throws GenerationException {
|
||||
if (parameterType == String.class && value.startsWith("%")) {
|
||||
return getBundleValue(request, value.substring(1));
|
||||
} else {
|
||||
@@ -518,7 +523,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param variable The object variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void injectControllerField(final GenerationRequest request, final String id, final String variable, final StringBuilder sb) {
|
||||
private static void injectControllerField(final GenerationRequest request, final String id, final String variable, final StringBuilder sb) throws GenerationException {
|
||||
final var controllerInjection = getControllerInjection(request);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
if (controllerInjectionType instanceof final ControllerFieldInjectionTypes types) {
|
||||
@@ -530,12 +535,11 @@ public class GeneratorImpl implements Generator {
|
||||
final var setMethod = getSetMethod(id);
|
||||
sb.append(" controller.").append(setMethod).append("(").append(variable).append(");\n");
|
||||
}
|
||||
case REFLECTION -> {
|
||||
sb.append(" injectField(\"").append(id).append("\", ").append(variable).append(");\n");
|
||||
}
|
||||
case REFLECTION ->
|
||||
sb.append(" injectField(\"").append(id).append("\", ").append(variable).append(");\n");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown controller injection type : " + controllerInjectionType);
|
||||
throw new GenerationException("Unknown controller injection type : " + controllerInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,10 +549,10 @@ public class GeneratorImpl implements Generator {
|
||||
* @param request The generation request
|
||||
* @return The controller injection
|
||||
*/
|
||||
private static ControllerInjection getControllerInjection(final GenerationRequest request) {
|
||||
final var property = request.rootObject().properties().get("fx:controller");
|
||||
private static ControllerInjection getControllerInjection(final GenerationRequest request) throws GenerationException {
|
||||
final var property = request.rootObject().attributes().get("fx:controller");
|
||||
if (property == null) {
|
||||
throw new IllegalArgumentException("Root object must have a controller property");
|
||||
throw new GenerationException("Root object must have a controller property");
|
||||
} else {
|
||||
final var id = property.value();
|
||||
return request.parameters().controllerInjections().get(id);
|
||||
@@ -566,13 +570,14 @@ public class GeneratorImpl implements Generator {
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private void formatChild(final GenerationRequest request, final ParsedObject parent, final ParsedProperty property,
|
||||
final Collection<? extends ParsedObject> objects, final String parentVariable, final StringBuilder sb) {
|
||||
final Collection<? extends ParsedObject> objects, final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var variables = objects.stream().map(go -> {
|
||||
final var variables = new ArrayList<String>();
|
||||
for (final var object : objects) {
|
||||
final var vn = getNextVariableName("object");
|
||||
format(request, go, vn, sb);
|
||||
return vn;
|
||||
}).toList();
|
||||
format(request, object, vn, sb);
|
||||
variables.add(vn);
|
||||
}
|
||||
if (variables.size() > 1) {
|
||||
formatMultipleChildren(variables, propertyName, parent, parentVariable, sb);
|
||||
} else if (variables.size() == 1) {
|
||||
@@ -591,10 +596,12 @@ public class GeneratorImpl implements Generator {
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatMultipleChildren(final Iterable<String> variables, final String propertyName, final ParsedObject parent,
|
||||
final String parentVariable, final StringBuilder sb) {
|
||||
final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
if (hasMethod(parent.clazz(), getMethod)) {
|
||||
if (hasMethod(getClass(parent.className()), getMethod)) {
|
||||
sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(String.join(", ", variables)).append(");\n");
|
||||
} else {
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,7 +615,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatSingleChild(final String variableName, final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable, final StringBuilder sb) {
|
||||
final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
if (property.sourceType() == null) {
|
||||
formatSingleChildInstance(variableName, property, parent, parentVariable, sb);
|
||||
} else {
|
||||
@@ -626,16 +633,17 @@ public class GeneratorImpl implements Generator {
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatSingleChildInstance(final String variableName, final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable, final StringBuilder sb) {
|
||||
final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
final var getMethod = getGetMethod(property);
|
||||
if (hasMethod(parent.clazz(), setMethod)) {
|
||||
final var parentClass = getClass(parent.className());
|
||||
if (hasMethod(parentClass, setMethod)) {
|
||||
sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(variableName).append(");\n");
|
||||
} else if (hasMethod(parent.clazz(), getMethod)) {
|
||||
} else if (hasMethod(parentClass, getMethod)) {
|
||||
//Probably a list method that has only one element
|
||||
sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(").append(variableName).append(");\n");
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot set " + property.name() + " on " + parent.clazz().getCanonicalName());
|
||||
throw new GenerationException("Cannot set " + property.name() + " on " + parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,12 +655,12 @@ public class GeneratorImpl implements Generator {
|
||||
* @param parentVariable The parent variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatSingleChildStatic(final String variableName, final ParsedProperty property, final String parentVariable, final StringBuilder sb) {
|
||||
private static void formatSingleChildStatic(final String variableName, final ParsedProperty property, final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
if (hasStaticMethod(property.sourceType(), setMethod)) {
|
||||
sb.append(" ").append(property.sourceType().getName()).append(".").append(setMethod).append("(").append(parentVariable).append(", ").append(variableName).append(");\n");
|
||||
if (hasStaticMethod(getClass(property.sourceType()), setMethod)) {
|
||||
sb.append(" ").append(property.sourceType()).append(".").append(setMethod).append("(").append(parentVariable).append(", ").append(variableName).append(");\n");
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot set " + property.name() + " on " + property.sourceType().getCanonicalName());
|
||||
throw new GenerationException("Cannot set " + property.name() + " on " + property.sourceType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,116 +704,8 @@ public class GeneratorImpl implements Generator {
|
||||
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a method with the given name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a method with the given name
|
||||
*/
|
||||
private static boolean hasMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
final var method = methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
|
||||
return method != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the method corresponding to the given class and name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return The method
|
||||
*/
|
||||
private static Method getMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
return methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a method with the given name
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a method with the given name
|
||||
*/
|
||||
private static Method computeMethod(final Class<?> clazz, final String methodName) {
|
||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
||||
if (m.getName().equals(methodName) && !Modifier.isStatic(m.getModifiers())) {
|
||||
final var parameterTypes = m.getParameterTypes();
|
||||
return methodName.startsWith("get") ? parameterTypes.length == 0 : parameterTypes.length >= 1; //TODO not very clean
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).toList();
|
||||
if (matching.size() > 1) {
|
||||
final var varargsFilter = matching.stream().filter(Method::isVarArgs).toList();
|
||||
if (varargsFilter.size() == 1) {
|
||||
return varargsFilter.getFirst();
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
||||
}
|
||||
} else if (matching.size() == 1) {
|
||||
return matching.getFirst();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a static method with the given name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a static method with the given name
|
||||
*/
|
||||
private static boolean hasStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
final var method = methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
|
||||
return method != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static method corresponding to the given class and name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return The method
|
||||
*/
|
||||
private static Method getStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static method corresponding to the given class and name
|
||||
*
|
||||
* @param clazz The class name
|
||||
* @param methodName The method name
|
||||
* @return The method, or null if not found
|
||||
*/
|
||||
private static Method computeStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
||||
if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) {
|
||||
final var parameterTypes = m.getParameterTypes();
|
||||
return parameterTypes.length > 1 && parameterTypes[0] == Node.class;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).toList();
|
||||
if (matching.size() > 1) {
|
||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
||||
} else if (matching.size() == 1) {
|
||||
return matching.getFirst();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBundleValue(final GenerationRequest request, final String value) {
|
||||
private static String getBundleValue(final GenerationRequest request, final String value) throws GenerationException {
|
||||
final var resourceBundleInjectionType = request.parameters().resourceBundleInjection().injectionType();
|
||||
if (resourceBundleInjectionType instanceof final ResourceBundleInjectionTypes types) {
|
||||
return switch (types) {
|
||||
@@ -813,7 +713,7 @@ public class GeneratorImpl implements Generator {
|
||||
case GETTER -> "controller.resources().getString(\"" + value + "\")";
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown resource bundle injection type : " + resourceBundleInjectionType);
|
||||
throw new GenerationException("Unknown resource bundle injection type : " + resourceBundleInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,18 +724,18 @@ public class GeneratorImpl implements Generator {
|
||||
* @param parsedObject The parsed object
|
||||
* @return The list of constructor arguments
|
||||
*/
|
||||
private static List<String> getListConstructorArgs(final ConstructorArgs constructorArgs, final ParsedObject parsedObject) {
|
||||
private static List<String> getListConstructorArgs(final ConstructorArgs constructorArgs, final ParsedObject parsedObject) throws GenerationException {
|
||||
final var args = new ArrayList<String>(constructorArgs.namedArgs().size());
|
||||
for (final var entry : constructorArgs.namedArgs().entrySet()) {
|
||||
final var type = entry.getValue().type();
|
||||
final var p = parsedObject.properties().get(entry.getKey());
|
||||
final var p = parsedObject.attributes().get(entry.getKey());
|
||||
if (p == null) {
|
||||
final var c = parsedObject.children().entrySet().stream().filter(e ->
|
||||
final var c = parsedObject.properties().entrySet().stream().filter(e ->
|
||||
e.getKey().name().equals(entry.getKey())).findFirst().orElse(null);
|
||||
if (c == null) {
|
||||
args.add(toString(entry.getValue().defaultValue(), type));
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Constructor using complex property not supported yet");
|
||||
throw new GenerationException("Constructor using complex property not supported yet");
|
||||
}
|
||||
} else {
|
||||
args.add(toString(p.value(), type));
|
||||
@@ -878,47 +778,6 @@ public class GeneratorImpl implements Generator {
|
||||
return constructorArgs.namedArgs().keySet().stream().filter(allPropertyNames::contains).count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the constructor arguments for the given constructor
|
||||
*
|
||||
* @param constructor The constructor
|
||||
* @return The constructor arguments
|
||||
*/
|
||||
private static ConstructorArgs getConstructorArgs(final Constructor<?> constructor) {
|
||||
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||
final var annotationsArray = constructor.getParameterAnnotations();
|
||||
for (var i = 0; i < annotationsArray.length; i++) {
|
||||
final var annotations = annotationsArray[i];
|
||||
final var getNamedArg = Arrays.stream(annotations).filter(NamedArg.class::isInstance).findFirst().orElse(null);
|
||||
if (getNamedArg != null) {
|
||||
final var namedArg = (NamedArg) getNamedArg;
|
||||
final var name = namedArg.value();
|
||||
final var clazz = constructor.getParameterTypes()[i];
|
||||
namedArgs.put(name, new Parameter(name, constructor.getParameterTypes()[i], namedArg.defaultValue().isEmpty() ?
|
||||
getDefaultValue(clazz) : namedArg.defaultValue()));
|
||||
}
|
||||
}
|
||||
return new ConstructorArgs(constructor, namedArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the default value for the given class
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return The value
|
||||
*/
|
||||
private static String getDefaultValue(final Class<?> clazz) {
|
||||
final var primitiveWrappers = Set.of(Integer.class, Byte.class, Short.class, Long.class, Float.class, Double.class);
|
||||
if (clazz == char.class || clazz == Character.class) {
|
||||
return "\u0000";
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
return "false";
|
||||
} else if (clazz.isPrimitive() || primitiveWrappers.contains(clazz)) {
|
||||
return "0";
|
||||
} else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the string value to use in the generated code
|
||||
@@ -928,13 +787,25 @@ public class GeneratorImpl implements Generator {
|
||||
* @return The computed string value
|
||||
*/
|
||||
private static String toString(final String value, final Class<?> clazz) {
|
||||
final var primitiveWrappers = Set.of(Integer.class, Byte.class, Short.class, Long.class, Float.class, Double.class, Boolean.class);
|
||||
if (clazz == String.class) {
|
||||
return "\"" + value.replace("\"", "\\\"") + "\"";
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
return "'" + value + "'";
|
||||
} else if (clazz.isPrimitive() || primitiveWrappers.contains(clazz)) {
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
return value;
|
||||
} else if (clazz == byte.class || clazz == Byte.class || clazz == short.class || clazz == Short.class ||
|
||||
clazz == int.class || clazz == Integer.class || clazz == long.class || clazz == Long.class) {
|
||||
if (INT_PATTERN.matcher(value).matches()) {
|
||||
return value;
|
||||
} else {
|
||||
return getWrapperClass(clazz) + ".valueOf(\"" + value + "\")";
|
||||
}
|
||||
} else if (clazz == float.class || clazz == Float.class || clazz == double.class || clazz == Double.class) {
|
||||
if (DECIMAL_PATTERN.matcher(value).matches()) {
|
||||
return value;
|
||||
} else {
|
||||
return getWrapperClass(clazz) + ".valueOf(\"" + value + "\")";
|
||||
}
|
||||
} else if (hasValueOf(clazz)) {
|
||||
if (clazz.isEnum()) {
|
||||
return clazz.getCanonicalName() + "." + value;
|
||||
@@ -946,29 +817,12 @@ public class GeneratorImpl implements Generator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a valueOf(String) method
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class has a valueOf(String)
|
||||
*/
|
||||
private static boolean hasValueOf(final Class<?> clazz) {
|
||||
return HAS_VALUE_OF.computeIfAbsent(clazz, GeneratorImpl::computeHasValueOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes if the given class has a valueOf(String) method
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class has a valueOf(String)
|
||||
*/
|
||||
private static boolean computeHasValueOf(final Class<?> clazz) {
|
||||
try {
|
||||
clazz.getMethod("valueOf", String.class);
|
||||
return true;
|
||||
} catch (final NoSuchMethodException ignored) {
|
||||
return false;
|
||||
private static String getWrapperClass(final Class<?> clazz) {
|
||||
final var name = clazz.getName();
|
||||
if (name.contains(".") || Character.isUpperCase(name.charAt(0))) {
|
||||
return name;
|
||||
} else {
|
||||
return name.substring(0, 1).toUpperCase() + name.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,20 +837,11 @@ public class GeneratorImpl implements Generator {
|
||||
return prefix + counter.getAndIncrement();
|
||||
}
|
||||
|
||||
private record ConstructorArgs(Constructor<?> constructor,
|
||||
SequencedMap<String, GeneratorImpl.Parameter> namedArgs) {
|
||||
private ConstructorArgs {
|
||||
requireNonNull(constructor);
|
||||
namedArgs = new LinkedHashMap<>(namedArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private record Parameter(String name, Class<?> type, String defaultValue) {
|
||||
|
||||
private Parameter {
|
||||
requireNonNull(name);
|
||||
requireNonNull(type);
|
||||
requireNonNull(defaultValue);
|
||||
private static Class<?> getClass(final String className) throws GenerationException {
|
||||
try {
|
||||
return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
|
||||
} catch (final ClassNotFoundException e) {
|
||||
throw new GenerationException("Cannot find class " + className + " ; Is a dependency missing for the plugin?", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Used by {@link ConstructorArgs} to store the constructor arguments
|
||||
*
|
||||
* @param name The parameter name
|
||||
* @param type The parameter type
|
||||
* @param defaultValue The parameter default value
|
||||
*/
|
||||
record Parameter(String name, Class<?> type, String defaultValue) {
|
||||
|
||||
Parameter {
|
||||
requireNonNull(name);
|
||||
requireNonNull(type);
|
||||
requireNonNull(defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.Node;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Helper methods for reflection
|
||||
*/
|
||||
final class ReflectionHelper {
|
||||
private static final Map<Class<?>, Boolean> HAS_VALUE_OF = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Boolean> IS_GENERIC = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Map<String, Method>> METHODS = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Map<String, Method>> STATIC_METHODS = new ConcurrentHashMap<>();
|
||||
|
||||
private ReflectionHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class is generic
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class is generic
|
||||
*/
|
||||
static boolean isGeneric(final Class<?> clazz) {
|
||||
return IS_GENERIC.computeIfAbsent(clazz, c -> c.getTypeParameters().length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a method with the given name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a method with the given name
|
||||
*/
|
||||
static boolean hasMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
final var method = methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
|
||||
return method != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the method corresponding to the given class and name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return The method
|
||||
*/
|
||||
static Method getMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
return methodMap.computeIfAbsent(methodName, m -> computeMethod(clazz, m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a method with the given name
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a method with the given name
|
||||
*/
|
||||
private static Method computeMethod(final Class<?> clazz, final String methodName) {
|
||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
||||
if (m.getName().equals(methodName) && !Modifier.isStatic(m.getModifiers())) {
|
||||
final var parameterTypes = m.getParameterTypes();
|
||||
return methodName.startsWith("get") ? parameterTypes.length == 0 : parameterTypes.length >= 1; //TODO not very clean
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).toList();
|
||||
if (matching.size() > 1) {
|
||||
final var varargsFilter = matching.stream().filter(Method::isVarArgs).toList();
|
||||
if (varargsFilter.size() == 1) {
|
||||
return varargsFilter.getFirst();
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
||||
}
|
||||
} else if (matching.size() == 1) {
|
||||
return matching.getFirst();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a static method with the given name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return True if the class has a static method with the given name
|
||||
*/
|
||||
static boolean hasStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
final var method = methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
|
||||
return method != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static method corresponding to the given class and name
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param methodName The method name
|
||||
* @return The method
|
||||
*/
|
||||
static Method getStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var methodMap = STATIC_METHODS.computeIfAbsent(clazz, c -> new ConcurrentHashMap<>());
|
||||
return methodMap.computeIfAbsent(methodName, m -> computeStaticMethod(clazz, m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the static method corresponding to the given class and name
|
||||
*
|
||||
* @param clazz The class name
|
||||
* @param methodName The method name
|
||||
* @return The method, or null if not found
|
||||
*/
|
||||
private static Method computeStaticMethod(final Class<?> clazz, final String methodName) {
|
||||
final var matching = Arrays.stream(clazz.getMethods()).filter(m -> {
|
||||
if (m.getName().equals(methodName) && Modifier.isStatic(m.getModifiers())) {
|
||||
final var parameterTypes = m.getParameterTypes();
|
||||
return parameterTypes.length > 1 && parameterTypes[0] == Node.class;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).toList();
|
||||
if (matching.size() > 1) {
|
||||
throw new UnsupportedOperationException("Multiple matching methods not supported yet : " + clazz + " - " + methodName);
|
||||
} else if (matching.size() == 1) {
|
||||
return matching.getFirst();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given class has a valueOf(String) method
|
||||
* The result is cached
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class has a valueOf(String)
|
||||
*/
|
||||
static boolean hasValueOf(final Class<?> clazz) {
|
||||
return HAS_VALUE_OF.computeIfAbsent(clazz, ReflectionHelper::computeHasValueOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes if the given class has a valueOf(String) method
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return True if the class has a valueOf(String)
|
||||
*/
|
||||
private static boolean computeHasValueOf(final Class<?> clazz) {
|
||||
try {
|
||||
clazz.getMethod("valueOf", String.class);
|
||||
return true;
|
||||
} catch (final NoSuchMethodException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the constructor arguments for the given constructor
|
||||
*
|
||||
* @param constructor The constructor
|
||||
* @return The constructor arguments
|
||||
*/
|
||||
static ConstructorArgs getConstructorArgs(final Constructor<?> constructor) {
|
||||
final var namedArgs = new LinkedHashMap<String, Parameter>();
|
||||
final var annotationsArray = constructor.getParameterAnnotations();
|
||||
var hasNamedArgs = 0;
|
||||
for (var i = 0; i < annotationsArray.length; i++) {
|
||||
final var annotations = annotationsArray[i];
|
||||
final var getNamedArg = Arrays.stream(annotations).filter(NamedArg.class::isInstance).findFirst().orElse(null);
|
||||
if (getNamedArg != null) {
|
||||
hasNamedArgs++;
|
||||
final var namedArg = (NamedArg) getNamedArg;
|
||||
final var name = namedArg.value();
|
||||
final var clazz = constructor.getParameterTypes()[i];
|
||||
namedArgs.put(name, new Parameter(name, constructor.getParameterTypes()[i], namedArg.defaultValue().isEmpty() ?
|
||||
getDefaultValue(clazz) : namedArg.defaultValue()));
|
||||
}
|
||||
}
|
||||
if (hasNamedArgs != 0 && hasNamedArgs != annotationsArray.length) {
|
||||
throw new IllegalStateException("Constructor " + constructor + " has both named and unnamed arguments");
|
||||
} else {
|
||||
return new ConstructorArgs(constructor, namedArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the default value for the given class
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return The value
|
||||
*/
|
||||
private static String getDefaultValue(final Class<?> clazz) {
|
||||
final var primitiveWrappers = Set.of(Integer.class, Byte.class, Short.class, Long.class, Float.class, Double.class);
|
||||
if (clazz == char.class || clazz == Character.class) {
|
||||
return "\u0000";
|
||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||
return "false";
|
||||
} else if (clazz.isPrimitive() || primitiveWrappers.contains(clazz)) {
|
||||
return "0";
|
||||
} else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedConstant;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedConstant}
|
||||
*
|
||||
* @param className The constant class
|
||||
* @param attributes The constant properties
|
||||
*/
|
||||
public record ParsedConstantImpl(String className, Map<String, ParsedProperty> attributes) implements ParsedConstant {
|
||||
|
||||
public ParsedConstantImpl {
|
||||
Objects.requireNonNull(className);
|
||||
attributes = Map.copyOf(attributes);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,16 @@ package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedInclude;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedInclude}
|
||||
*
|
||||
* @param properties The object properties
|
||||
* @param attributes The object properties
|
||||
*/
|
||||
public record ParsedIncludeImpl(SequencedMap<String, ParsedProperty> properties) implements ParsedInclude {
|
||||
public record ParsedIncludeImpl(Map<String, ParsedProperty> attributes) implements ParsedInclude {
|
||||
|
||||
public ParsedIncludeImpl {
|
||||
properties = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(properties));
|
||||
attributes = Map.copyOf(attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedCollection;
|
||||
import java.util.SequencedMap;
|
||||
@@ -13,17 +15,17 @@ import java.util.SequencedMap;
|
||||
/**
|
||||
* Implementation of {@link ParsedObject}
|
||||
*
|
||||
* @param clazz The object class
|
||||
* @param properties The object properties
|
||||
* @param children The object children (complex properties)
|
||||
* @param className The object class
|
||||
* @param attributes The object properties
|
||||
* @param properties The object children (complex properties)
|
||||
*/
|
||||
public record ParsedObjectImpl(Class<?> clazz, SequencedMap<String, ParsedProperty> properties,
|
||||
SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children) implements ParsedObject {
|
||||
public record ParsedObjectImpl(String className, Map<String, ParsedProperty> attributes,
|
||||
SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties) implements ParsedObject {
|
||||
|
||||
public ParsedObjectImpl {
|
||||
Objects.requireNonNull(clazz);
|
||||
Objects.requireNonNull(className);
|
||||
attributes = Map.copyOf(attributes);
|
||||
properties = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(properties));
|
||||
children = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(children));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,26 +33,37 @@ public record ParsedObjectImpl(Class<?> clazz, SequencedMap<String, ParsedProper
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private Class<?> clazz;
|
||||
private final SequencedMap<String, ParsedProperty> properties;
|
||||
private final SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children;
|
||||
private String className;
|
||||
private final Map<String, ParsedProperty> attributes;
|
||||
private final SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties;
|
||||
|
||||
/**
|
||||
* Creates a new builder
|
||||
*/
|
||||
public Builder() {
|
||||
this.attributes = new HashMap<>();
|
||||
this.properties = new LinkedHashMap<>();
|
||||
this.children = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object class
|
||||
*
|
||||
* @param clazz The object class
|
||||
* @param className The object class
|
||||
* @return The builder
|
||||
*/
|
||||
public Builder clazz(final Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
public Builder className(final String className) {
|
||||
this.className = className;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute
|
||||
*
|
||||
* @param attribute The attribute
|
||||
* @return The builder
|
||||
*/
|
||||
public Builder addAttribute(final ParsedProperty attribute) {
|
||||
attributes.put(attribute.name(), attribute);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -58,24 +71,13 @@ public record ParsedObjectImpl(Class<?> clazz, SequencedMap<String, ParsedProper
|
||||
* Adds a property
|
||||
*
|
||||
* @param property The property
|
||||
* @param child The property element
|
||||
* @return The builder
|
||||
*/
|
||||
public Builder addProperty(final ParsedProperty property) {
|
||||
properties.put(property.name(), property);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child
|
||||
*
|
||||
* @param property The property
|
||||
* @param child The child
|
||||
* @return The builder
|
||||
*/
|
||||
public Builder addChild(final ParsedProperty property, final ParsedObject child) {
|
||||
final var sequence = children.computeIfAbsent(property, k -> new ArrayList<>());
|
||||
public Builder addProperty(final ParsedProperty property, final ParsedObject child) {
|
||||
final var sequence = properties.computeIfAbsent(property, k -> new ArrayList<>());
|
||||
sequence.add(child);
|
||||
children.put(property, sequence);
|
||||
properties.put(property, sequence);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ public record ParsedObjectImpl(Class<?> clazz, SequencedMap<String, ParsedProper
|
||||
* @return The object
|
||||
*/
|
||||
public ParsedObjectImpl build() {
|
||||
return new ParsedObjectImpl(clazz, properties, children);
|
||||
return new ParsedObjectImpl(className, attributes, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.Objects;
|
||||
* @param sourceType The property source type
|
||||
* @param value The property value
|
||||
*/
|
||||
public record ParsedPropertyImpl(String name, Class<?> sourceType, String value) implements ParsedProperty {
|
||||
public record ParsedPropertyImpl(String name, String sourceType, String value) implements ParsedProperty {
|
||||
|
||||
public ParsedPropertyImpl {
|
||||
Objects.requireNonNull(name);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
module com.github.gtache.fxml.compiler.core {
|
||||
requires transitive com.github.gtache.fxml.compiler.api;
|
||||
requires transitive javafx.graphics;
|
||||
requires org.apache.logging.log4j;
|
||||
|
||||
exports com.github.gtache.fxml.compiler.impl;
|
||||
exports com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TestClassesFinder {
|
||||
|
||||
@Test
|
||||
void testGetClassesCurrent() throws IOException {
|
||||
final var expected = Set.of(
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedConstantImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedIncludeImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedObjectImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedObjectImplBuilder",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedPropertyImpl");
|
||||
final var actual = ClassesFinder.getClasses("com.github.gtache.fxml.compiler.parsing.impl");
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetClassesJar() throws IOException {
|
||||
final var expected = Set.of("javafx.beans.DefaultProperty",
|
||||
"javafx.beans.InvalidationListener",
|
||||
"javafx.beans.NamedArg",
|
||||
"javafx.beans.Observable",
|
||||
"javafx.beans.WeakInvalidationListener",
|
||||
"javafx.beans.WeakListener");
|
||||
final var actual = ClassesFinder.getClasses("javafx.beans");
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
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.lang.reflect.Constructor;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestConstructorArgs {
|
||||
|
||||
private final Constructor<?> constructor;
|
||||
private final SequencedMap<String, Parameter> namedArgs;
|
||||
private final ConstructorArgs constructorArgs;
|
||||
|
||||
TestConstructorArgs(@Mock final Constructor<?> constructor, @Mock final Parameter parameter1, @Mock final Parameter parameter2) {
|
||||
this.constructor = Objects.requireNonNull(constructor);
|
||||
this.namedArgs = new LinkedHashMap<>();
|
||||
namedArgs.put("p1", parameter1);
|
||||
namedArgs.put("p2", parameter2);
|
||||
this.constructorArgs = new ConstructorArgs(constructor, namedArgs);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(constructor, constructorArgs.constructor());
|
||||
assertEquals(namedArgs, constructorArgs.namedArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopy() {
|
||||
final var original = constructorArgs.namedArgs();
|
||||
namedArgs.put("p3", null);
|
||||
assertEquals(original, constructorArgs.namedArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var map = constructorArgs.namedArgs();
|
||||
assertThrows(UnsupportedOperationException.class, map::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ConstructorArgs(null, namedArgs));
|
||||
assertThrows(NullPointerException.class, () -> new ConstructorArgs(constructor, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class TestParameter {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
private final String defaultValue;
|
||||
private final Parameter parameter;
|
||||
|
||||
TestParameter() {
|
||||
this.name = "name";
|
||||
this.type = Object.class;
|
||||
this.defaultValue = "default";
|
||||
this.parameter = new Parameter(name, type, defaultValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(name, parameter.name());
|
||||
assertEquals(type, parameter.type());
|
||||
assertEquals(defaultValue, parameter.defaultValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new Parameter(null, type, defaultValue));
|
||||
assertThrows(NullPointerException.class, () -> new Parameter(name, null, defaultValue));
|
||||
assertThrows(NullPointerException.class, () -> new Parameter(name, type, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TestReflectionHelper {
|
||||
|
||||
@Test
|
||||
void testIsGeneric() {
|
||||
assertFalse(ReflectionHelper.isGeneric(String.class));
|
||||
assertTrue(ReflectionHelper.isGeneric(ComboBox.class));
|
||||
assertTrue(ReflectionHelper.isGeneric(TableCell.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasMethod() {
|
||||
assertFalse(ReflectionHelper.hasMethod(String.class, "bla"));
|
||||
assertTrue(ReflectionHelper.hasMethod(String.class, "charAt"));
|
||||
assertTrue(ReflectionHelper.hasMethod(StackPane.class, "getChildren"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetMethod() throws NoSuchMethodException {
|
||||
assertEquals(String.class.getMethod("charAt", int.class), ReflectionHelper.getMethod(String.class, "charAt"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasStaticMethod() {
|
||||
assertTrue(ReflectionHelper.hasStaticMethod(HBox.class, "setHgrow"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStaticMethod() throws NoSuchMethodException {
|
||||
assertEquals(HBox.class.getMethod("setHgrow", Node.class, Priority.class), ReflectionHelper.getStaticMethod(HBox.class, "setHgrow"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasValueOf() {
|
||||
assertTrue(ReflectionHelper.hasValueOf(Integer.class));
|
||||
assertTrue(ReflectionHelper.hasValueOf(Pos.class));
|
||||
assertFalse(ReflectionHelper.hasValueOf(HBox.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConstructorArgsNamedArgsDefault() {
|
||||
final var parameters = new LinkedHashMap<String, Parameter>();
|
||||
parameters.put("p1", new Parameter("p1", int.class, "0"));
|
||||
parameters.put("p2", new Parameter("p2", Integer.class, "0"));
|
||||
parameters.put("p3", new Parameter("p3", char.class, "\u0000"));
|
||||
parameters.put("p4", new Parameter("p4", Character.class, "\u0000"));
|
||||
parameters.put("p5", new Parameter("p5", boolean.class, "false"));
|
||||
parameters.put("p6", new Parameter("p6", Boolean.class, "false"));
|
||||
parameters.put("p7", new Parameter("p7", byte.class, "0"));
|
||||
parameters.put("p8", new Parameter("p8", Byte.class, "0"));
|
||||
parameters.put("p9", new Parameter("p9", short.class, "0"));
|
||||
parameters.put("p10", new Parameter("p10", Short.class, "0"));
|
||||
parameters.put("p11", new Parameter("p11", long.class, "0"));
|
||||
parameters.put("p12", new Parameter("p12", Long.class, "0"));
|
||||
parameters.put("p13", new Parameter("p13", float.class, "0"));
|
||||
parameters.put("p14", new Parameter("p14", Float.class, "0"));
|
||||
parameters.put("p15", new Parameter("p15", double.class, "0"));
|
||||
parameters.put("p16", new Parameter("p16", Double.class, "0"));
|
||||
parameters.put("p17", new Parameter("p17", String.class, "null"));
|
||||
parameters.put("p18", new Parameter("p18", Object.class, "null"));
|
||||
final var defaultConstructor = Arrays.stream(WholeConstructorArgs.class.getConstructors()).filter(c -> c.getParameterCount() == 18).findFirst().orElseThrow();
|
||||
final var expected = new ConstructorArgs(defaultConstructor, parameters);
|
||||
final var actual = ReflectionHelper.getConstructorArgs(defaultConstructor);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConstructorArgsNamedArgs() {
|
||||
final var parameters = new LinkedHashMap<String, Parameter>();
|
||||
parameters.put("p1", new Parameter("p1", int.class, "1"));
|
||||
parameters.put("p3", new Parameter("p3", char.class, "a"));
|
||||
parameters.put("p5", new Parameter("p5", boolean.class, "true"));
|
||||
parameters.put("p7", new Parameter("p7", byte.class, "2"));
|
||||
parameters.put("p9", new Parameter("p9", short.class, "3"));
|
||||
parameters.put("p11", new Parameter("p11", long.class, "4"));
|
||||
parameters.put("p13", new Parameter("p13", float.class, "5.5"));
|
||||
parameters.put("p15", new Parameter("p15", double.class, "6.6"));
|
||||
parameters.put("p17", new Parameter("p17", String.class, "str"));
|
||||
final var constructor = Arrays.stream(WholeConstructorArgs.class.getConstructors()).filter(c -> c.getParameterCount() == 9).findFirst().orElseThrow();
|
||||
final var expected = new ConstructorArgs(constructor, parameters);
|
||||
final var actual = ReflectionHelper.getConstructorArgs(constructor);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConstructorArgsNoNamedArgs() {
|
||||
final var constructor = Arrays.stream(WholeConstructorArgs.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findFirst().orElseThrow();
|
||||
final var expected = new ConstructorArgs(constructor, new LinkedHashMap<>());
|
||||
final var actual = ReflectionHelper.getConstructorArgs(constructor);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetConstructorArgsMixed() {
|
||||
final var constructor = Arrays.stream(WholeConstructorArgs.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().orElseThrow();
|
||||
assertThrows(IllegalStateException.class, () -> ReflectionHelper.getConstructorArgs(constructor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
|
||||
public class WholeConstructorArgs {
|
||||
public WholeConstructorArgs(@NamedArg("p1") final int p1, @NamedArg("p2") final Integer p2,
|
||||
@NamedArg("p3") final char p3, @NamedArg("p4") final Character p4,
|
||||
@NamedArg("p5") final boolean p5, @NamedArg("p6") final Boolean p6,
|
||||
@NamedArg("p7") final byte p7, @NamedArg("p8") final Byte p8,
|
||||
@NamedArg("p9") final short p9, @NamedArg("p10") final Short p10,
|
||||
@NamedArg("p11") final long p11, @NamedArg("p12") final Long p12,
|
||||
@NamedArg("p13") final float p13, @NamedArg("p14") final Float p14,
|
||||
@NamedArg("p15") final double p15, @NamedArg("p16") final Double p16,
|
||||
@NamedArg("p17") final String p17, @NamedArg("p18") final Object p18) {
|
||||
}
|
||||
|
||||
public WholeConstructorArgs(@NamedArg(value = "p1", defaultValue = "1") final int p1,
|
||||
@NamedArg(value = "p3", defaultValue = "a") final char p3,
|
||||
@NamedArg(value = "p5", defaultValue = "true") final boolean p5,
|
||||
@NamedArg(value = "p7", defaultValue = "2") final byte p7,
|
||||
@NamedArg(value = "p9", defaultValue = "3") final short p9,
|
||||
@NamedArg(value = "p11", defaultValue = "4") final long p11,
|
||||
@NamedArg(value = "p13", defaultValue = "5.5") final float p13,
|
||||
@NamedArg(value = "p15", defaultValue = "6.6") final double p15,
|
||||
@NamedArg(value = "p17", defaultValue = "str") final String p17) {
|
||||
}
|
||||
|
||||
public WholeConstructorArgs(final int p1, final char p3) {
|
||||
|
||||
}
|
||||
|
||||
public WholeConstructorArgs(final int p1, @NamedArg("p3") final char p3, @NamedArg("p5") final boolean p5) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedConstant;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TestParsedConstantImpl {
|
||||
|
||||
private final String className;
|
||||
private final Map<String, ParsedProperty> attributes;
|
||||
private final ParsedConstant constant;
|
||||
|
||||
TestParsedConstantImpl() {
|
||||
this.className = "test";
|
||||
this.attributes = new HashMap<>(Map.of("fx:constant", new ParsedPropertyImpl("fx:constant", String.class.getName(), "value")));
|
||||
this.constant = new ParsedConstantImpl(className, attributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(className, constant.className());
|
||||
assertEquals(attributes, constant.attributes());
|
||||
assertEquals(attributes.get("fx:constant").value(), constant.constant());
|
||||
assertEquals(Map.of(), constant.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalAttributes = constant.attributes();
|
||||
attributes.clear();
|
||||
assertEquals(originalAttributes, constant.attributes());
|
||||
assertNotEquals(attributes, constant.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = constant.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedConstantImpl(null, attributes));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedConstantImpl(className, null));
|
||||
}
|
||||
}
|
||||
@@ -26,22 +26,22 @@ class TestParsedIncludeImpl {
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(properties, include.properties());
|
||||
assertEquals(ParsedInclude.class, include.clazz());
|
||||
assertEquals(new LinkedHashMap<>(), include.children());
|
||||
assertEquals(properties, include.attributes());
|
||||
assertEquals(ParsedInclude.class.getName(), include.className());
|
||||
assertEquals(new LinkedHashMap<>(), include.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalProperties = include.properties();
|
||||
final var originalProperties = include.attributes();
|
||||
properties.clear();
|
||||
assertEquals(originalProperties, include.properties());
|
||||
assertNotEquals(properties, include.properties());
|
||||
assertEquals(originalProperties, include.attributes());
|
||||
assertNotEquals(properties, include.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = include.properties();
|
||||
final var objectProperties = include.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestParsedObjectImpl {
|
||||
|
||||
private final Class<?> clazz;
|
||||
private final String clazz;
|
||||
private final SequencedMap<String, ParsedProperty> properties;
|
||||
private final SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children;
|
||||
private final ParsedObject parsedObject;
|
||||
|
||||
TestParsedObjectImpl(@Mock final ParsedProperty property, @Mock final ParsedObject object) {
|
||||
this.clazz = Object.class;
|
||||
this.clazz = Object.class.getName();
|
||||
this.properties = new LinkedHashMap<>();
|
||||
this.properties.put("name", property);
|
||||
this.children = new LinkedHashMap<>();
|
||||
@@ -34,25 +34,25 @@ class TestParsedObjectImpl {
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(clazz, parsedObject.clazz());
|
||||
assertEquals(properties, parsedObject.properties());
|
||||
assertEquals(children, parsedObject.children());
|
||||
assertEquals(clazz, parsedObject.className());
|
||||
assertEquals(properties, parsedObject.attributes());
|
||||
assertEquals(children, parsedObject.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalProperties = parsedObject.properties();
|
||||
final var originalChildren = parsedObject.children();
|
||||
final var originalProperties = parsedObject.attributes();
|
||||
final var originalChildren = parsedObject.properties();
|
||||
properties.clear();
|
||||
children.clear();
|
||||
assertEquals(originalProperties, parsedObject.properties());
|
||||
assertEquals(originalChildren, parsedObject.children());
|
||||
assertEquals(originalProperties, parsedObject.attributes());
|
||||
assertEquals(originalChildren, parsedObject.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = parsedObject.properties();
|
||||
final var objectChildren = parsedObject.children();
|
||||
final var objectProperties = parsedObject.attributes();
|
||||
final var objectChildren = parsedObject.properties();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
assertThrows(UnsupportedOperationException.class, objectChildren::clear);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import static org.mockito.Mockito.when;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestParsedObjectImplBuilder {
|
||||
|
||||
private final Class<?> clazz1;
|
||||
private final Class<?> clazz2;
|
||||
private final String clazz1;
|
||||
private final String clazz2;
|
||||
private final ParsedProperty property1;
|
||||
private final ParsedProperty property2;
|
||||
private final ParsedObject object1;
|
||||
@@ -29,8 +29,8 @@ class TestParsedObjectImplBuilder {
|
||||
|
||||
TestParsedObjectImplBuilder(@Mock final ParsedProperty property1, @Mock final ParsedProperty property2,
|
||||
@Mock final ParsedObject object1, @Mock final ParsedObject object2) {
|
||||
this.clazz1 = Object.class;
|
||||
this.clazz2 = String.class;
|
||||
this.clazz1 = Object.class.getName();
|
||||
this.clazz2 = String.class.getName();
|
||||
this.property1 = Objects.requireNonNull(property1);
|
||||
this.property2 = Objects.requireNonNull(property2);
|
||||
this.object1 = Objects.requireNonNull(object1);
|
||||
@@ -50,59 +50,59 @@ class TestParsedObjectImplBuilder {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClazz() {
|
||||
builder.clazz(clazz1);
|
||||
void testClassName() {
|
||||
builder.className(clazz1);
|
||||
final var built = builder.build();
|
||||
assertEquals(clazz1, built.clazz());
|
||||
assertEquals(clazz1, built.className());
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(), built.properties());
|
||||
assertEquals(Map.of(), built.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOverwriteClazz() {
|
||||
builder.clazz(clazz1);
|
||||
builder.clazz(clazz2);
|
||||
void testOverwriteClassName() {
|
||||
builder.className(clazz1);
|
||||
builder.className(clazz2);
|
||||
final var built = builder.build();
|
||||
assertEquals(clazz2, built.clazz());
|
||||
assertEquals(clazz2, built.className());
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(), built.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddAttribute() {
|
||||
builder.className(clazz1);
|
||||
builder.addAttribute(property1);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(property1.name(), property1), built.attributes());
|
||||
assertEquals(Map.of(), built.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMultipleAttributes() {
|
||||
builder.className(clazz1);
|
||||
builder.addAttribute(property1);
|
||||
builder.addAttribute(property2);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(property1.name(), property1, property2.name(), property2), built.attributes());
|
||||
assertEquals(Map.of(), built.properties());
|
||||
assertEquals(Map.of(), built.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddProperty() {
|
||||
builder.clazz(clazz1);
|
||||
builder.addProperty(property1);
|
||||
builder.className(clazz1);
|
||||
builder.addProperty(property1, object1);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(property1.name(), property1), built.properties());
|
||||
assertEquals(Map.of(), built.children());
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(property1, List.of(object1)), built.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMultipleProperties() {
|
||||
builder.clazz(clazz1);
|
||||
builder.addProperty(property1);
|
||||
builder.addProperty(property2);
|
||||
builder.className(clazz1);
|
||||
builder.addProperty(property1, object1);
|
||||
builder.addProperty(property2, object2);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(property1.name(), property1, property2.name(), property2), built.properties());
|
||||
assertEquals(Map.of(), built.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddChild() {
|
||||
builder.clazz(clazz1);
|
||||
builder.addChild(property1, object1);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(), built.properties());
|
||||
assertEquals(Map.of(property1, List.of(object1)), built.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMultipleChildren() {
|
||||
builder.clazz(clazz1);
|
||||
builder.addChild(property1, object1);
|
||||
builder.addChild(property2, object2);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(), built.properties());
|
||||
assertEquals(Map.of(property1, List.of(object1), property2, List.of(object2)), built.children());
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(property1, List.of(object1), property2, List.of(object2)), built.properties());
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
class TestParsedPropertyImpl {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> sourceType;
|
||||
private final String sourceType;
|
||||
private final String value;
|
||||
private final ParsedProperty property;
|
||||
|
||||
TestParsedPropertyImpl() {
|
||||
this.name = "name";
|
||||
this.sourceType = Object.class;
|
||||
this.sourceType = Object.class.getName();
|
||||
this.value = "value";
|
||||
this.property = new ParsedPropertyImpl(name, sourceType, value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user