Removes loader, adds some missing features, refactors for easier comprehension and maintenance
This commit is contained in:
@@ -1,83 +1,59 @@
|
||||
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.event.EventHandler;
|
||||
import com.github.gtache.fxml.compiler.impl.internal.GenerationProgress;
|
||||
import com.github.gtache.fxml.compiler.impl.internal.HelperMethodsProvider;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
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 com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getVariablePrefix;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ObjectFormatter.format;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.ReflectionHelper.*;
|
||||
//TODO handle binding (${})
|
||||
|
||||
/**
|
||||
* Implementation of {@link Generator}
|
||||
*/
|
||||
public class GeneratorImpl implements Generator {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Instantiates a new generator
|
||||
*/
|
||||
public GeneratorImpl() {
|
||||
this.controllerFactoryPostAction = new ArrayList<>();
|
||||
this.variableNameCounters = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate(final GenerationRequest request) throws GenerationException {
|
||||
controllerFactoryPostAction.clear();
|
||||
variableNameCounters.clear();
|
||||
final var progress = new GenerationProgress(request);
|
||||
final var className = request.outputClassName();
|
||||
final var pkgName = className.substring(0, className.lastIndexOf('.'));
|
||||
final var simpleClassName = className.substring(className.lastIndexOf('.') + 1);
|
||||
final var loadMethod = getLoadMethod(request);
|
||||
final var controllerInjection = getControllerInjection(request);
|
||||
final var loadMethod = getLoadMethod(progress);
|
||||
final var controllerInjection = getControllerInjection(progress);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
final var controllerInjectionClass = controllerInjection.injectionClass();
|
||||
final String constructorArgument;
|
||||
final String constructorControllerJavadoc;
|
||||
final String controllerArgumentType;
|
||||
final String controllerMapType;
|
||||
final var controllerInjectionClass = controllerInjection.injectionClass();
|
||||
final var imports = getImports(request);
|
||||
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) {
|
||||
constructorArgument = "controllerFactory";
|
||||
constructorControllerJavadoc = "controller factory";
|
||||
controllerArgumentType = "ControllerFactory<" + controllerInjectionClass + ">";
|
||||
controllerMapType = "ControllerFactory<?>";
|
||||
controllerArgumentType = "com.github.gtache.fxml.compiler.ControllerFactory<" + controllerInjectionClass + ">";
|
||||
controllerMapType = "com.github.gtache.fxml.compiler.ControllerFactory<?>";
|
||||
} else {
|
||||
constructorArgument = "controller";
|
||||
constructorControllerJavadoc = "controller";
|
||||
controllerArgumentType = controllerInjectionClass;
|
||||
controllerMapType = "Object";
|
||||
}
|
||||
final var helperMethods = getHelperMethods(request);
|
||||
final var helperMethods = HelperMethodsProvider.getHelperMethods(progress);
|
||||
return """
|
||||
package %1$s;
|
||||
|
||||
%9$s
|
||||
|
||||
/**
|
||||
* Generated code, not thread-safe
|
||||
*/
|
||||
public final class %2$s {
|
||||
|
||||
private final Map<Class<?>, %7$s> controllersMap;
|
||||
private final Map<Class<?>, ResourceBundle> resourceBundlesMap;
|
||||
private final java.util.Map<Class<?>, %7$s> controllersMap;
|
||||
private final java.util.Map<Class<?>, java.util.ResourceBundle> resourceBundlesMap;
|
||||
private boolean loaded;
|
||||
private %3$s controller;
|
||||
|
||||
@@ -86,7 +62,7 @@ public class GeneratorImpl implements Generator {
|
||||
* @param %4$s The %5$s
|
||||
*/
|
||||
public %2$s(final %8$s %4$s) {
|
||||
this(Map.of(%3$s.class, %4$s), Map.of());
|
||||
this(java.util.Map.of(%3$s.class, %4$s), java.util.Map.of());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,8 +70,8 @@ public class GeneratorImpl implements Generator {
|
||||
* @param %4$s The %5$s
|
||||
* @param resourceBundle The resource bundle
|
||||
*/
|
||||
public %2$s(final %8$s %4$s, final ResourceBundle resourceBundle) {
|
||||
this(Map.of(%3$s.class, %4$s), Map.of(%3$s.class, resourceBundle));
|
||||
public %2$s(final %8$s %4$s, final java.util.ResourceBundle resourceBundle) {
|
||||
this(java.util.Map.of(%3$s.class, %4$s), java.util.Map.of(%3$s.class, resourceBundle));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,9 +79,9 @@ public class GeneratorImpl implements Generator {
|
||||
* @param controllersMap The map of controller class to %5$s
|
||||
* @param resourceBundlesMap The map of controller class to resource bundle
|
||||
*/
|
||||
public %2$s(final Map<Class<?>, %7$s> controllersMap, final Map<Class<?>, ResourceBundle> resourceBundlesMap) {
|
||||
this.controllersMap = Map.copyOf(controllersMap);
|
||||
this.resourceBundlesMap = Map.copyOf(resourceBundlesMap);
|
||||
public %2$s(final java.util.Map<Class<?>, %7$s> controllersMap, final java.util.Map<Class<?>, java.util.ResourceBundle> resourceBundlesMap) {
|
||||
this.controllersMap = java.util.Map.copyOf(controllersMap);
|
||||
this.resourceBundlesMap = java.util.Map.copyOf(resourceBundlesMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +91,7 @@ public class GeneratorImpl implements Generator {
|
||||
*/
|
||||
%6$s
|
||||
|
||||
%10$s
|
||||
%9$s
|
||||
|
||||
/**
|
||||
* @return The controller
|
||||
@@ -129,113 +105,30 @@ public class GeneratorImpl implements Generator {
|
||||
}
|
||||
}
|
||||
""".formatted(pkgName, simpleClassName, controllerInjectionClass, constructorArgument, constructorControllerJavadoc,
|
||||
loadMethod, controllerMapType, controllerArgumentType, imports, helperMethods);
|
||||
loadMethod, controllerMapType, controllerArgumentType, helperMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets helper methods string for the given generation request
|
||||
*
|
||||
* @param request The generation request
|
||||
* @return The helper methods
|
||||
*/
|
||||
private static String getHelperMethods(final GenerationRequest request) throws GenerationException {
|
||||
final var injection = getControllerInjection(request);
|
||||
final var methodInjectionType = injection.methodInjectionType();
|
||||
final var sb = new StringBuilder();
|
||||
if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) {
|
||||
sb.append("""
|
||||
private <T extends Event> void callMethod(final String methodName, final T event) {
|
||||
try {
|
||||
final Method method;
|
||||
final var methods = Arrays.stream(controller.getClass().getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(methodName)).toList();
|
||||
if (methods.size() > 1) {
|
||||
final var eventMethods = methods.stream().filter(m ->
|
||||
m.getParameterCount() == 1 && Event.class.isAssignableFrom(m.getParameterTypes()[0])).toList();
|
||||
if (eventMethods.size() == 1) {
|
||||
method = eventMethods.getFirst();
|
||||
} else {
|
||||
final var emptyMethods = methods.stream().filter(m -> m.getParameterCount() == 0).toList();
|
||||
if (emptyMethods.size() == 1) {
|
||||
method = emptyMethods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Multiple matching methods for " + methodName);
|
||||
}
|
||||
}
|
||||
} else if (methods.size() == 1) {
|
||||
method = methods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("No matching method for " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (method.getParameterCount() == 0) {
|
||||
method.invoke(controller);
|
||||
} else {
|
||||
method.invoke(controller, event);
|
||||
}
|
||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new RuntimeException("Error using reflection on " + methodName, ex);
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
if (injection.fieldInjectionType() == ControllerFieldInjectionTypes.REFLECTION) {
|
||||
sb.append("""
|
||||
private <T> void injectField(final String fieldName, final T object) {
|
||||
try {
|
||||
final var field = controller.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(controller, object);
|
||||
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error using reflection on " + fieldName, e);
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets imports for the given generation request
|
||||
*
|
||||
* @param request The generation request
|
||||
* @return The imports
|
||||
*/
|
||||
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");
|
||||
if (fieldInjectionType == ControllerFieldInjectionTypes.FACTORY) {
|
||||
sb.append("import com.github.gtache.fxml.compiler.ControllerFactory;\n");
|
||||
}
|
||||
final var methodInjectionType = injection.methodInjectionType();
|
||||
if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) {
|
||||
sb.append("import java.lang.reflect.InvocationTargetException;\n");
|
||||
sb.append("import java.util.Arrays;\n");
|
||||
sb.append("import javafx.event.Event;\n");
|
||||
sb.append("import java.lang.reflect.Method;\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the load method
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param progress The generation progress
|
||||
* @return The load method
|
||||
*/
|
||||
private String getLoadMethod(final GenerationRequest request) throws GenerationException {
|
||||
private static String getLoadMethod(final GenerationProgress progress) throws GenerationException {
|
||||
final var request = progress.request();
|
||||
final var rootObject = request.rootObject();
|
||||
final var controllerInjection = getControllerInjection(request);
|
||||
final var controllerInjection = getControllerInjection(progress);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
final var controllerClass = controllerInjection.injectionClass();
|
||||
final var sb = new StringBuilder("public <T> T load() {\n");
|
||||
final var sb = progress.stringBuilder();
|
||||
sb.append("public <T> T load() {\n");
|
||||
sb.append(" if (loaded) {\n");
|
||||
sb.append(" throw new IllegalStateException(\"Already loaded\");\n");
|
||||
sb.append(" }\n");
|
||||
final var resourceBundleInjection = request.parameters().resourceBundleInjection();
|
||||
if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.GET_BUNDLE) {
|
||||
sb.append(" final var bundle = ResourceBundle.getBundle(\"").append(resourceBundleInjection.bundleName()).append("\");\n");
|
||||
sb.append(" final var bundle = java.util.ResourceBundle.getBundle(\"").append(resourceBundleInjection.bundleName()).append("\");\n");
|
||||
} else if (resourceBundleInjection.injectionType() == ResourceBundleInjectionTypes.CONSTRUCTOR) {
|
||||
sb.append(" final var bundle = resourceBundlesMap.get(").append(controllerClass).append(".class);\n");
|
||||
}
|
||||
@@ -244,19 +137,19 @@ public class GeneratorImpl implements Generator {
|
||||
} else {
|
||||
sb.append(" controller = (").append(controllerClass).append(") controllersMap.get(").append(controllerClass).append(".class);\n");
|
||||
}
|
||||
final var variableName = getNextVariableName("object");
|
||||
format(request, rootObject, variableName, sb);
|
||||
final var variableName = progress.getNextVariableName(getVariablePrefix(rootObject));
|
||||
format(progress, rootObject, variableName);
|
||||
if (controllerInjectionType == ControllerFieldInjectionTypes.FACTORY) {
|
||||
sb.append(" final var controllerFactory = controllersMap.get(").append(controllerClass).append(".class);\n");
|
||||
sb.append(" controller = (").append(controllerClass).append(") controllerFactory.create(fieldMap);\n");
|
||||
controllerFactoryPostAction.forEach(sb::append);
|
||||
progress.controllerFactoryPostAction().forEach(sb::append);
|
||||
}
|
||||
if (controllerInjection.methodInjectionType() == ControllerMethodsInjectionType.REFLECTION) {
|
||||
sb.append(" try {\n");
|
||||
sb.append(" final var initialize = controller.getClass().getDeclaredMethod(\"initialize\");\n");
|
||||
sb.append(" initialize.setAccessible(true);\n");
|
||||
sb.append(" initialize.invoke(controller);\n");
|
||||
sb.append(" } catch (final InvocationTargetException | IllegalAccessException e) {\n");
|
||||
sb.append(" } catch (final java.lang.reflect.InvocationTargetException | IllegalAccessException e) {\n");
|
||||
sb.append(" throw new RuntimeException(\"Error using reflection\", e);\n");
|
||||
sb.append(" } catch (final NoSuchMethodException ignored) {\n");
|
||||
sb.append(" }\n");
|
||||
@@ -264,584 +157,10 @@ public class GeneratorImpl implements Generator {
|
||||
sb.append(" controller.initialize();\n");
|
||||
}
|
||||
sb.append(" loaded = true;\n");
|
||||
sb.append(" return ").append(variableName).append(";\n");
|
||||
sb.append(" return (T) ").append(variableName).append(";\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an object
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param parsedObject The parsed object to format
|
||||
* @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) 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.attributes().get("fx:id");
|
||||
if (idProperty == null) {
|
||||
return "<>";
|
||||
} else {
|
||||
final var id = idProperty.value();
|
||||
final var genericTypes = request.controllerInfo().propertyGenericTypes(id);
|
||||
if (genericTypes == null) { //Raw
|
||||
return "";
|
||||
} else {
|
||||
return "<" + String.join(", ", genericTypes) + ">";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats an include object
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param include The include object
|
||||
* @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) 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 GenerationException("Unknown include source : " + source);
|
||||
}
|
||||
if (resources == null) {
|
||||
sb.append(" final var ").append(subViewVariable).append(" = new ").append(subClassName).append("(controllersMap, resourceBundlesMap);\n");
|
||||
} else {
|
||||
final var subResourceBundlesMapVariable = getNextVariableName("map");
|
||||
final var subBundleVariable = getNextVariableName("bundle");
|
||||
sb.append(" final var ").append(subResourceBundlesMapVariable).append(" = new HashMap<>(resourceBundlesMap);\n");
|
||||
sb.append(" final var ").append(subBundleVariable).append(" = ResourceBundle.getBundle(\"").append(resources).append("\");\n");
|
||||
sb.append(" ").append(subResourceBundlesMapVariable).append(".put(").append(subControllerClass).append(", ").append(subBundleVariable).append(");\n");
|
||||
sb.append(" final var ").append(subViewVariable).append(" = new ").append(subClassName).append("(controllersMap, ").append(subResourceBundlesMapVariable).append(");\n");
|
||||
}
|
||||
sb.append(" final var ").append(subNodeName).append(" = ").append(subViewVariable).append(".load();\n");
|
||||
final var id = include.controllerId();
|
||||
if (id != null) {
|
||||
final var subControllerVariable = getNextVariableName("controller");
|
||||
sb.append(" final var ").append(subControllerVariable).append(" = ").append(subViewVariable).append(".controller();\n");
|
||||
injectControllerField(request, id, subControllerVariable, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param property The property to format
|
||||
* @param parent The property's parent object
|
||||
* @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) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var setMethod = getSetMethod(propertyName);
|
||||
if (propertyName.equals("fx:id")) {
|
||||
final var id = property.value();
|
||||
injectControllerField(request, id, parentVariable, sb);
|
||||
} else if (propertyName.equals("fx:controller")) {
|
||||
if (parent != request.rootObject()) {
|
||||
throw new GenerationException("Invalid nested controller");
|
||||
}
|
||||
} else if (Objects.equals(property.sourceType(), EventHandler.class.getName())) {
|
||||
injectControllerMethod(request, property, parentVariable, sb);
|
||||
} else if (property.sourceType() != null) {
|
||||
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() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n", sb);
|
||||
} else {
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + property.sourceType());
|
||||
}
|
||||
} else {
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
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(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 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) throws GenerationException {
|
||||
if (type == String.class && property.value().startsWith("%") && request.parameters().resourceBundleInjection().injectionType() == ResourceBundleInjectionTypes.GETTER
|
||||
&& getControllerInjection(request).fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) {
|
||||
controllerFactoryPostAction.add(arg);
|
||||
} else {
|
||||
sb.append(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a controller method
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param property The property to inject
|
||||
* @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) throws GenerationException {
|
||||
final var injection = getControllerInjection(request);
|
||||
final var methodInjection = getMethodInjection(request, property, parentVariable, sb);
|
||||
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) {
|
||||
switch (fieldTypes) {
|
||||
case FACTORY -> controllerFactoryPostAction.add(methodInjection);
|
||||
case ASSIGN, SETTERS, REFLECTION -> sb.append(methodInjection);
|
||||
}
|
||||
} else {
|
||||
throw new GenerationException("Unknown injection type : " + injection.fieldInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the method injection
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param property The property
|
||||
* @param parentVariable The parent variable
|
||||
* @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) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property.name());
|
||||
final var injection = getControllerInjection(request);
|
||||
final var controllerMethod = property.value().replace("#", "");
|
||||
if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) {
|
||||
return switch (methodTypes) {
|
||||
case REFERENCE -> {
|
||||
final var hasArgument = request.controllerInfo().handlerHasArgument(controllerMethod);
|
||||
if (hasArgument) {
|
||||
yield " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n";
|
||||
} else {
|
||||
yield " " + parentVariable + "." + setMethod + "(e -> controller." + controllerMethod + "());\n";
|
||||
}
|
||||
}
|
||||
case REFLECTION ->
|
||||
" " + parentVariable + "." + setMethod + "(e -> callMethod(\"" + controllerMethod + "\", e));\n";
|
||||
};
|
||||
} else {
|
||||
throw new GenerationException("Unknown injection type : " + injection.methodInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an argument to a method
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param value The value
|
||||
* @param parameterType The parameter type
|
||||
* @return The formatted value
|
||||
*/
|
||||
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 {
|
||||
return toString(value, parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the given variable into the controller
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param id The object id
|
||||
* @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) throws GenerationException {
|
||||
final var controllerInjection = getControllerInjection(request);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
if (controllerInjectionType instanceof final ControllerFieldInjectionTypes types) {
|
||||
switch (types) {
|
||||
case FACTORY ->
|
||||
sb.append(" fieldMap.put(\"").append(id).append("\", ").append(variable).append(");\n");
|
||||
case ASSIGN -> sb.append(" controller.").append(id).append(" = ").append(variable).append(";\n");
|
||||
case SETTERS -> {
|
||||
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");
|
||||
}
|
||||
} else {
|
||||
throw new GenerationException("Unknown controller injection type : " + controllerInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller injection object from the generation request
|
||||
*
|
||||
* @param request The generation request
|
||||
* @return The controller injection
|
||||
*/
|
||||
private static ControllerInjection getControllerInjection(final GenerationRequest request) throws GenerationException {
|
||||
final var property = request.rootObject().attributes().get("fx:controller");
|
||||
if (property == null) {
|
||||
throw new GenerationException("Root object must have a controller property");
|
||||
} else {
|
||||
final var id = property.value();
|
||||
return request.parameters().controllerInjections().get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the children objects of a property
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param parent The parent object
|
||||
* @param property The parent property
|
||||
* @param objects The child objects
|
||||
* @param parentVariable The parent object variable
|
||||
* @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) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var variables = new ArrayList<String>();
|
||||
for (final var object : objects) {
|
||||
final var vn = getNextVariableName("object");
|
||||
format(request, object, vn, sb);
|
||||
variables.add(vn);
|
||||
}
|
||||
if (variables.size() > 1) {
|
||||
formatMultipleChildren(variables, propertyName, parent, parentVariable, sb);
|
||||
} else if (variables.size() == 1) {
|
||||
final var vn = variables.getFirst();
|
||||
formatSingleChild(vn, property, parent, parentVariable, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats children objects given that they are more than one
|
||||
*
|
||||
* @param variables The children variables
|
||||
* @param propertyName The property name
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
* @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) throws GenerationException {
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a single child object
|
||||
*
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatSingleChild(final String variableName, final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
if (property.sourceType() == null) {
|
||||
formatSingleChildInstance(variableName, property, parent, parentVariable, sb);
|
||||
} else {
|
||||
formatSingleChildStatic(variableName, property, parentVariable, sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a single child object using an instance method on the parent object
|
||||
*
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
* @param sb The string builder
|
||||
*/
|
||||
private static void formatSingleChildInstance(final String variableName, final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable, final StringBuilder sb) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
final var getMethod = getGetMethod(property);
|
||||
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(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 GenerationException("Cannot set " + property.name() + " on " + parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a child object using a static method
|
||||
*
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @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) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
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 GenerationException("Cannot set " + property.name() + " on " + property.sourceType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter method name for the given property
|
||||
*
|
||||
* @param property The property
|
||||
* @return The getter method name
|
||||
*/
|
||||
private static String getGetMethod(final ParsedProperty property) {
|
||||
return getGetMethod(property.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter method name for the given property name
|
||||
*
|
||||
* @param propertyName The property name
|
||||
* @return The getter method name
|
||||
*/
|
||||
private static String getGetMethod(final String propertyName) {
|
||||
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter method name for the given property
|
||||
*
|
||||
* @param property The property
|
||||
* @return The setter method name
|
||||
*/
|
||||
private static String getSetMethod(final ParsedProperty property) {
|
||||
return getSetMethod(property.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter method name for the given property name
|
||||
*
|
||||
* @param propertyName The property name
|
||||
* @return The setter method name
|
||||
*/
|
||||
private static String getSetMethod(final String propertyName) {
|
||||
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
case CONSTRUCTOR, GET_BUNDLE -> "bundle.getString(\"" + value + "\")";
|
||||
case GETTER -> "controller.resources().getString(\"" + value + "\")";
|
||||
};
|
||||
} else {
|
||||
throw new GenerationException("Unknown resource bundle injection type : " + resourceBundleInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constructor arguments as a list of strings
|
||||
*
|
||||
* @param constructorArgs The constructor arguments
|
||||
* @param parsedObject The parsed object
|
||||
* @return The list of constructor arguments
|
||||
*/
|
||||
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.attributes().get(entry.getKey());
|
||||
if (p == null) {
|
||||
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 GenerationException("Constructor using complex property not supported yet");
|
||||
}
|
||||
} else {
|
||||
args.add(toString(p.value(), type));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constructor arguments that best match the given property names
|
||||
*
|
||||
* @param constructors The constructors
|
||||
* @param allPropertyNames The property names
|
||||
* @return The matching constructor arguments, or null if no constructor matches and no default constructor exists
|
||||
*/
|
||||
private static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Set<String> allPropertyNames) {
|
||||
ConstructorArgs matchingConstructorArgs = null;
|
||||
for (final var constructor : constructors) {
|
||||
final var constructorArgs = getConstructorArgs(constructor);
|
||||
final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames);
|
||||
if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) {
|
||||
matchingConstructorArgs = constructorArgs;
|
||||
}
|
||||
}
|
||||
if (matchingConstructorArgs == null) {
|
||||
return Arrays.stream(constructors).filter(c -> c.getParameterCount() == 0).findFirst().map(c -> new ConstructorArgs(c, new LinkedHashMap<>())).orElse(null);
|
||||
} else {
|
||||
return matchingConstructorArgs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks how many arguments of the given constructor match the given property names
|
||||
*
|
||||
* @param constructorArgs The constructor arguments
|
||||
* @param allPropertyNames The property names
|
||||
* @return The number of matching arguments
|
||||
*/
|
||||
private static long getMatchingArgsCount(final ConstructorArgs constructorArgs, final Set<String> allPropertyNames) {
|
||||
return constructorArgs.namedArgs().keySet().stream().filter(allPropertyNames::contains).count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the string value to use in the generated code
|
||||
*
|
||||
* @param value The value
|
||||
* @param clazz The value class
|
||||
* @return The computed string value
|
||||
*/
|
||||
private static String toString(final String value, final Class<?> clazz) {
|
||||
if (clazz == String.class) {
|
||||
return "\"" + value.replace("\"", "\\\"") + "\"";
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
return "'" + value + "'";
|
||||
} 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;
|
||||
} else {
|
||||
return clazz.getCanonicalName() + ".valueOf(\"" + value + "\")";
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the next available variable name for the given prefix
|
||||
*
|
||||
* @param prefix The variable name prefix
|
||||
* @return The next available variable name
|
||||
*/
|
||||
private String getNextVariableName(final String prefix) {
|
||||
final var counter = variableNameCounters.computeIfAbsent(prefix, k -> new AtomicInteger(0));
|
||||
return prefix + counter.getAndIncrement();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collections;
|
||||
@@ -15,6 +15,14 @@ import static java.util.Objects.requireNonNull;
|
||||
*/
|
||||
record ConstructorArgs(Constructor<?> constructor,
|
||||
SequencedMap<String, Parameter> namedArgs) {
|
||||
|
||||
/**
|
||||
* Instantiates new args
|
||||
*
|
||||
* @param constructor The constructor
|
||||
* @param namedArgs The named args
|
||||
* @throws NullPointerException if any argument is null
|
||||
*/
|
||||
ConstructorArgs {
|
||||
requireNonNull(constructor);
|
||||
namedArgs = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(namedArgs));
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.getConstructorArgs;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to handle constructors
|
||||
*/
|
||||
final class ConstructorHelper {
|
||||
private ConstructorHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constructor arguments as a list of strings
|
||||
*
|
||||
* @param constructorArgs The constructor arguments
|
||||
* @param parsedObject The parsed object
|
||||
* @return The list of constructor arguments
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
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.attributes().get(entry.getKey());
|
||||
if (p == null) {
|
||||
final var c = parsedObject.properties().entrySet().stream().filter(e ->
|
||||
e.getKey().name().equals(entry.getKey())).findFirst().orElse(null);
|
||||
if (c == null) {
|
||||
args.add(ValueFormatter.toString(entry.getValue().defaultValue(), type));
|
||||
} else {
|
||||
throw new GenerationException("Constructor using complex property not supported yet");
|
||||
}
|
||||
} else {
|
||||
args.add(ValueFormatter.toString(p.value(), type));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constructor arguments that best match the given property names
|
||||
*
|
||||
* @param constructors The constructors
|
||||
* @param allPropertyNames The property names
|
||||
* @return The matching constructor arguments, or null if no constructor matches and no default constructor exists
|
||||
*/
|
||||
static ConstructorArgs getMatchingConstructorArgs(final Constructor<?>[] constructors, final Set<String> allPropertyNames) {
|
||||
ConstructorArgs matchingConstructorArgs = null;
|
||||
for (final var constructor : constructors) {
|
||||
final var constructorArgs = getConstructorArgs(constructor);
|
||||
final var matchingArgsCount = getMatchingArgsCount(constructorArgs, allPropertyNames);
|
||||
if (matchingConstructorArgs == null ? matchingArgsCount > 0 : matchingArgsCount > getMatchingArgsCount(matchingConstructorArgs, allPropertyNames)) {
|
||||
matchingConstructorArgs = constructorArgs;
|
||||
}
|
||||
}
|
||||
if (matchingConstructorArgs == null) {
|
||||
return Arrays.stream(constructors).filter(c -> c.getParameterCount() == 0).findFirst().map(c -> new ConstructorArgs(c, new LinkedHashMap<>())).orElse(null);
|
||||
} else {
|
||||
return matchingConstructorArgs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks how many arguments of the given constructor match the given property names
|
||||
*
|
||||
* @param constructorArgs The constructor arguments
|
||||
* @param allPropertyNames The property names
|
||||
* @return The number of matching arguments
|
||||
*/
|
||||
private static long getMatchingArgsCount(final ConstructorArgs constructorArgs, final Set<String> allPropertyNames) {
|
||||
return constructorArgs.namedArgs().keySet().stream().filter(allPropertyNames::contains).count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.InjectionType;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getSetMethod;
|
||||
|
||||
/**
|
||||
* Various methods to help {@link GeneratorImpl} for injecting controllers
|
||||
*/
|
||||
final class ControllerInjector {
|
||||
private ControllerInjector() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the given variable into the controller
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param id The object id
|
||||
* @param variable The object variable
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void injectControllerField(final GenerationProgress progress, final String id, final String variable) throws GenerationException {
|
||||
final var controllerInjection = getControllerInjection(progress);
|
||||
final var controllerInjectionType = controllerInjection.fieldInjectionType();
|
||||
if (controllerInjectionType instanceof final ControllerFieldInjectionTypes types) {
|
||||
final var sb = progress.stringBuilder();
|
||||
switch (types) {
|
||||
case FACTORY ->
|
||||
sb.append(" fieldMap.put(\"").append(id).append("\", ").append(variable).append(");\n");
|
||||
case ASSIGN -> sb.append(" controller.").append(id).append(" = ").append(variable).append(";\n");
|
||||
case SETTERS -> {
|
||||
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");
|
||||
}
|
||||
} else {
|
||||
throw new GenerationException("Unknown controller injection type : " + controllerInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects an event handler controller method
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to inject
|
||||
* @param parentVariable The parent variable
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void injectEventHandlerControllerMethod(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
injectControllerMethod(progress, getEventHandlerMethodInjection(progress, property, parentVariable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a callback controller method
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to inject
|
||||
* @param parentVariable The parent variable
|
||||
* @param argumentClazz The argument class
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void injectCallbackControllerMethod(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String argumentClazz) throws GenerationException {
|
||||
injectControllerMethod(progress, getCallbackMethodInjection(progress, property, parentVariable, argumentClazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a controller method
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param methodInjection The method injection
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static void injectControllerMethod(final GenerationProgress progress, final String methodInjection) throws GenerationException {
|
||||
final var injection = getControllerInjection(progress);
|
||||
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) {
|
||||
switch (fieldTypes) {
|
||||
case FACTORY -> progress.controllerFactoryPostAction().add(methodInjection);
|
||||
case ASSIGN, SETTERS, REFLECTION -> progress.stringBuilder().append(methodInjection);
|
||||
}
|
||||
} else {
|
||||
throw getUnknownInjectionException(injection.fieldInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the method injection for event handler
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property
|
||||
* @param parentVariable The parent variable
|
||||
* @return The method injection
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static String getEventHandlerMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property.name());
|
||||
final var injection = getControllerInjection(progress);
|
||||
final var controllerMethod = property.value().replace("#", "");
|
||||
if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) {
|
||||
return switch (methodTypes) {
|
||||
case REFERENCE -> {
|
||||
final var hasArgument = progress.request().controllerInfo().handlerHasArgument(controllerMethod);
|
||||
if (hasArgument) {
|
||||
yield " " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n";
|
||||
} else {
|
||||
yield " " + parentVariable + "." + setMethod + "(e -> controller." + controllerMethod + "());\n";
|
||||
}
|
||||
}
|
||||
case REFLECTION ->
|
||||
" " + parentVariable + "." + setMethod + "(e -> callEventHandlerMethod(\"" + controllerMethod + "\", e));\n";
|
||||
};
|
||||
} else {
|
||||
throw getUnknownInjectionException(injection.methodInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the method injection for callback
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property
|
||||
* @param parentVariable The parent variable
|
||||
* @param argumentClazz The argument class
|
||||
* @return The method injection
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static String getCallbackMethodInjection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String argumentClazz) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property.name());
|
||||
final var injection = getControllerInjection(progress);
|
||||
final var controllerMethod = property.value().replace("#", "");
|
||||
if (injection.methodInjectionType() instanceof final ControllerMethodsInjectionType methodTypes) {
|
||||
return switch (methodTypes) {
|
||||
case REFERENCE ->
|
||||
" " + parentVariable + "." + setMethod + "(controller::" + controllerMethod + ");\n";
|
||||
case REFLECTION ->
|
||||
" " + parentVariable + "." + setMethod + "(e -> callCallbackMethod(\"" + controllerMethod + "\", e, " + argumentClazz + "));\n";
|
||||
};
|
||||
} else {
|
||||
throw getUnknownInjectionException(injection.methodInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerationException getUnknownInjectionException(final InjectionType type) {
|
||||
return new GenerationException("Unknown injection type : " + type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to set fields
|
||||
*/
|
||||
final class FieldSetter {
|
||||
|
||||
private FieldSetter() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an event handler field
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to inject
|
||||
* @param parentVariable The parent variable
|
||||
* @throws GenerationException if an error occurs@
|
||||
*/
|
||||
static void setEventHandler(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
setField(progress, property, parentVariable, "javafx.event.EventHandler");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a field
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to inject
|
||||
* @param parentVariable The parent variable
|
||||
* @param fieldType The field type
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void setField(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) throws GenerationException {
|
||||
final var injection = getControllerInjection(progress);
|
||||
if (injection.fieldInjectionType() instanceof final ControllerFieldInjectionTypes fieldTypes) {
|
||||
switch (fieldTypes) {
|
||||
case ASSIGN -> setAssign(progress, property, parentVariable);
|
||||
case FACTORY -> setFactory(progress, property, parentVariable);
|
||||
case SETTERS -> setSetter(progress, property, parentVariable);
|
||||
case REFLECTION -> setReflection(progress, property, parentVariable, fieldType);
|
||||
}
|
||||
} else {
|
||||
throw new GenerationException("Unknown injection type : " + injection.fieldInjectionType());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setAssign(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) {
|
||||
final var methodName = getSetMethod(property);
|
||||
final var value = property.value().replace("$", "");
|
||||
progress.stringBuilder().append(" ").append(parentVariable).append(".").append(methodName).append("(").append(value).append(");\n");
|
||||
}
|
||||
|
||||
private static void setFactory(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) {
|
||||
progress.controllerFactoryPostAction().add(getSetString(property, parentVariable));
|
||||
}
|
||||
|
||||
private static void setSetter(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) {
|
||||
progress.stringBuilder().append(getSetString(property, parentVariable));
|
||||
}
|
||||
|
||||
private static String getSetString(final ParsedProperty property, final String parentVariable) {
|
||||
final var methodName = getSetMethod(property);
|
||||
final var value = property.value().replace("$", "");
|
||||
final var split = value.split("\\.");
|
||||
final var getterName = getGetMethod(split[1]);
|
||||
return " " + parentVariable + "." + methodName + "(" + split[0] + "." + getterName + ");\n";
|
||||
}
|
||||
|
||||
private static void setReflection(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String fieldType) {
|
||||
final var methodName = getSetMethod(property);
|
||||
final var value = property.value().replace("$", "");
|
||||
final var split = value.split("\\.");
|
||||
final var fieldName = split[1];
|
||||
progress.stringBuilder().append("""
|
||||
try {
|
||||
final var field = controller.getClass().getDeclaredField("%s");
|
||||
field.setAccessible(true);
|
||||
final var value = (%s) field.get(controller);
|
||||
%s.%s(value);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
""".formatted(fieldName, fieldType, parentVariable, methodName));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import javafx.scene.text.FontPosture;
|
||||
import javafx.scene.text.FontWeight;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format fonts
|
||||
*/
|
||||
final class FontFormatter {
|
||||
|
||||
private FontFormatter() {
|
||||
|
||||
}
|
||||
|
||||
static void formatFont(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
|
||||
final var value = parseFontValue(parsedObject);
|
||||
final var url = value.url();
|
||||
final var fw = value.fontWeight();
|
||||
final var fp = value.fontPosture();
|
||||
final var size = value.size();
|
||||
final var name = value.name();
|
||||
if (url != null) {
|
||||
formatURL(progress, url, size, variableName);
|
||||
} else if (fw == null && fp == null) {
|
||||
formatNoStyle(progress, name, size, variableName);
|
||||
} else {
|
||||
formatStyle(progress, fw, fp, size, name, variableName);
|
||||
}
|
||||
handleId(progress, parsedObject, variableName);
|
||||
} else {
|
||||
throw new GenerationException("Font cannot have children or properties : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatURL(final GenerationProgress progress, final URL url, final double size, final String variableName) {
|
||||
final var urlVariableName = URLBuilder.formatURL(progress, url.toString());
|
||||
progress.stringBuilder().append("""
|
||||
Font %1$s;
|
||||
try (final var in = %2$s.openStream()) {
|
||||
%1$s = Font.loadFont(in, %3$s);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
""".formatted(variableName, urlVariableName, size));
|
||||
}
|
||||
|
||||
private static void formatNoStyle(final GenerationProgress progress, final String name, final double size, final String variableName) {
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name).append("\", ").append(size).append(");\n");
|
||||
}
|
||||
|
||||
private static void formatStyle(final GenerationProgress progress, final FontWeight fw, final FontPosture fp, final double size, final String name, final String variableName) {
|
||||
final var finalFW = fw == null ? FontWeight.NORMAL : fw;
|
||||
final var finalFP = fp == null ? FontPosture.REGULAR : fp;
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.text.Font(\"").append(name)
|
||||
.append("\", javafx.scene.text.FontWeight.").append(finalFW.name()).append(", javafx.scene.text.FontPosture.")
|
||||
.append(finalFP.name()).append(", ").append(size).append(");\n");
|
||||
}
|
||||
|
||||
private static FontValue parseFontValue(final ParsedObject parsedObject) throws GenerationException {
|
||||
URL url = null;
|
||||
String name = null;
|
||||
double size = 12;
|
||||
FontWeight fw = null;
|
||||
FontPosture fp = null;
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
for (final var property : sortedAttributes) {
|
||||
switch (property.name()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "name" -> {
|
||||
try {
|
||||
url = new URI(property.value()).toURL();
|
||||
} catch (final MalformedURLException | URISyntaxException | IllegalArgumentException ignored) {
|
||||
name = property.value();
|
||||
}
|
||||
}
|
||||
case "size" -> size = Double.parseDouble(property.value());
|
||||
case "style" -> {
|
||||
final var style = getFontStyle(property);
|
||||
if (style.fontWeight() != null) {
|
||||
fw = style.fontWeight();
|
||||
}
|
||||
if (style.fontPosture() != null) {
|
||||
fp = style.fontPosture();
|
||||
}
|
||||
}
|
||||
case "url" -> url = getURL(property);
|
||||
default -> throw new GenerationException("Unknown font attribute : " + property.name());
|
||||
}
|
||||
}
|
||||
return new FontValue(url, name, size, fw, fp);
|
||||
}
|
||||
|
||||
private static URL getURL(final ParsedProperty property) throws GenerationException {
|
||||
try {
|
||||
return new URI(property.value()).toURL();
|
||||
} catch (final MalformedURLException | URISyntaxException e) {
|
||||
throw new GenerationException("Couldn't parse url : " + property.value(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static FontStyle getFontStyle(final ParsedProperty property) {
|
||||
final var split = property.value().split(" ");
|
||||
FontWeight fw = null;
|
||||
FontPosture fp = null;
|
||||
for (final var s : split) {
|
||||
final var fontWeight = FontWeight.findByName(s);
|
||||
final var fontPosture = FontPosture.findByName(s);
|
||||
if (fontWeight != null) {
|
||||
fw = fontWeight;
|
||||
} else if (fontPosture != null) {
|
||||
fp = fontPosture;
|
||||
}
|
||||
}
|
||||
return new FontStyle(fw, fp);
|
||||
}
|
||||
|
||||
private record FontValue(URL url, String name, double size, FontWeight fontWeight,
|
||||
FontPosture fontPosture) {
|
||||
}
|
||||
|
||||
private record FontStyle(FontWeight fontWeight, FontPosture fontPosture) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.ControllerInjection;
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectControllerField;
|
||||
|
||||
/**
|
||||
* Various helper methods for {@link GeneratorImpl}
|
||||
*/
|
||||
public final class GenerationHelper {
|
||||
|
||||
static final String FX_ID = "fx:id";
|
||||
static final String FX_VALUE = "fx:value";
|
||||
static final String VALUE = "value";
|
||||
static final String START_VAR = " final var ";
|
||||
|
||||
private GenerationHelper() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the variable prefix for the given object
|
||||
*
|
||||
* @param object The object
|
||||
* @return The variable prefix
|
||||
*/
|
||||
public static String getVariablePrefix(final ParsedObject object) {
|
||||
final var className = object.className();
|
||||
return className.substring(className.lastIndexOf('.') + 1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the controller injection object from the generation request
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @return The controller injection
|
||||
* @throws GenerationException If the controller is not found
|
||||
*/
|
||||
public static ControllerInjection getControllerInjection(final GenerationProgress progress) throws GenerationException {
|
||||
final var request = progress.request();
|
||||
final var property = request.rootObject().attributes().get("fx:controller");
|
||||
if (property == null) {
|
||||
throw new GenerationException("Root object must have a controller property");
|
||||
} else {
|
||||
final var id = property.value();
|
||||
return request.parameters().controllerInjections().get(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter method name for the given property
|
||||
*
|
||||
* @param property The property
|
||||
* @return The getter method name
|
||||
*/
|
||||
static String getGetMethod(final ParsedProperty property) {
|
||||
return getGetMethod(property.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the getter method name for the given property name
|
||||
*
|
||||
* @param propertyName The property name
|
||||
* @return The getter method name
|
||||
*/
|
||||
static String getGetMethod(final String propertyName) {
|
||||
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter method name for the given property
|
||||
*
|
||||
* @param property The property
|
||||
* @return The setter method name
|
||||
*/
|
||||
static String getSetMethod(final ParsedProperty property) {
|
||||
return getSetMethod(property.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter method name for the given property name
|
||||
*
|
||||
* @param propertyName The property name
|
||||
* @return The setter method name
|
||||
*/
|
||||
static String getSetMethod(final String propertyName) {
|
||||
return "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the fx:id attribute of an object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object
|
||||
* @param variableName The variable name
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void handleId(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
final var id = parsedObject.attributes().get(FX_ID);
|
||||
if (id != null) {
|
||||
progress.idToVariableName().put(id.value(), variableName);
|
||||
progress.idToObject().put(id.value(), parsedObject);
|
||||
//TODO Don't inject if variable doesn't exist
|
||||
injectControllerField(progress, id.value(), variableName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sorted attributes of the given object
|
||||
*
|
||||
* @param parsedObject The parsed object
|
||||
* @return The sorted attributes
|
||||
*/
|
||||
static List<ParsedProperty> getSortedAttributes(final ParsedObject parsedObject) {
|
||||
return parsedObject.attributes().entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationRequest;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedCollection;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Used by {@link GeneratorImpl} to track the generation progress
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param idToVariableName The id to variable name mapping
|
||||
* @param idToObject The id to parsed object mapping
|
||||
* @param variableNameCounters The variable name counters for variable name generation
|
||||
* @param controllerFactoryPostAction The controller factory post action for factory injection
|
||||
* @param stringBuilder The string builder
|
||||
*/
|
||||
public record GenerationProgress(GenerationRequest request, Map<String, String> idToVariableName,
|
||||
Map<String, ParsedObject> idToObject,
|
||||
Map<String, AtomicInteger> variableNameCounters,
|
||||
SequencedCollection<String> controllerFactoryPostAction,
|
||||
StringBuilder stringBuilder) {
|
||||
|
||||
/**
|
||||
* Instantiates a new GenerationProgress
|
||||
*
|
||||
* @param request The generation request
|
||||
* @param idToVariableName The id to variable name mapping
|
||||
* @param idToObject The id to parsed object mapping
|
||||
* @param variableNameCounters The variable name counters
|
||||
* @param controllerFactoryPostAction The controller factory post action
|
||||
* @param stringBuilder The string builder
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public GenerationProgress {
|
||||
Objects.requireNonNull(request);
|
||||
Objects.requireNonNull(idToVariableName);
|
||||
Objects.requireNonNull(idToObject);
|
||||
Objects.requireNonNull(variableNameCounters);
|
||||
Objects.requireNonNull(controllerFactoryPostAction);
|
||||
Objects.requireNonNull(stringBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new GenerationProgress
|
||||
*
|
||||
* @param request The generation request
|
||||
* @throws NullPointerException if request is null
|
||||
*/
|
||||
public GenerationProgress(final GenerationRequest request) {
|
||||
this(request, new HashMap<>(), new HashMap<>(), new HashMap<>(), new ArrayList<>(), new StringBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next available variable name for the given prefix
|
||||
*
|
||||
* @param prefix The variable name prefix
|
||||
* @return The next available variable name
|
||||
*/
|
||||
public String getNextVariableName(final String prefix) {
|
||||
final var counter = variableNameCounters.computeIfAbsent(prefix, k -> new AtomicInteger(0));
|
||||
return prefix + counter.getAndIncrement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerMethodsInjectionType;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.getControllerInjection;
|
||||
|
||||
/**
|
||||
* Provides the helper methods for the generated code
|
||||
*/
|
||||
public final class HelperMethodsProvider {
|
||||
|
||||
private HelperMethodsProvider() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets helper methods string for the given generation progress
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @return The helper methods
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
public static String getHelperMethods(final GenerationProgress progress) throws GenerationException {
|
||||
final var injection = getControllerInjection(progress);
|
||||
final var methodInjectionType = injection.methodInjectionType();
|
||||
final var sb = new StringBuilder();
|
||||
if (methodInjectionType == ControllerMethodsInjectionType.REFLECTION) {
|
||||
sb.append("""
|
||||
private <T extends javafx.event.Event> void callEventHandlerMethod(final String methodName, final T event) {
|
||||
try {
|
||||
final java.lang.reflect.Method method;
|
||||
final var methods = java.util.Arrays.stream(controller.getClass().getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(methodName)).toList();
|
||||
if (methods.size() > 1) {
|
||||
final var eventMethods = methods.stream().filter(m ->
|
||||
m.getParameterCount() == 1 && javafx.event.Event.class.isAssignableFrom(m.getParameterTypes()[0])).toList();
|
||||
if (eventMethods.size() == 1) {
|
||||
method = eventMethods.getFirst();
|
||||
} else {
|
||||
final var emptyMethods = methods.stream().filter(m -> m.getParameterCount() == 0).toList();
|
||||
if (emptyMethods.size() == 1) {
|
||||
method = emptyMethods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Multiple matching methods for " + methodName);
|
||||
}
|
||||
}
|
||||
} else if (methods.size() == 1) {
|
||||
method = methods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("No matching method for " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
if (method.getParameterCount() == 0) {
|
||||
method.invoke(controller);
|
||||
} else {
|
||||
method.invoke(controller, event);
|
||||
}
|
||||
} catch (final IllegalAccessException | java.lang.reflect.InvocationTargetException ex) {
|
||||
throw new RuntimeException("Error using reflection on " + methodName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private <T, U> U callCallbackMethod(final String methodName, final T value, final Class<T> clazz) {
|
||||
try {
|
||||
final java.lang.reflect.Method method;
|
||||
final var methods = java.util.Arrays.stream(controller.getClass().getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals(methodName)).toList();
|
||||
if (methods.size() > 1) {
|
||||
final var eventMethods = methods.stream().filter(m ->
|
||||
m.getParameterCount() == 1 && clazz.isAssignableFrom(m.getParameterTypes()[0])).toList();
|
||||
if (eventMethods.size() == 1) {
|
||||
method = eventMethods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Multiple matching methods for " + methodName);
|
||||
}
|
||||
} else if (methods.size() == 1) {
|
||||
method = methods.getFirst();
|
||||
} else {
|
||||
throw new IllegalArgumentException("No matching method for " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return (U) method.invoke(controller, value);
|
||||
} catch (final IllegalAccessException | java.lang.reflect.InvocationTargetException ex) {
|
||||
throw new RuntimeException("Error using reflection on " + methodName, ex);
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
if (injection.fieldInjectionType() == ControllerFieldInjectionTypes.REFLECTION) {
|
||||
sb.append("""
|
||||
private <T> void injectField(final String fieldName, final T object) {
|
||||
try {
|
||||
final var field = controller.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(controller, object);
|
||||
} catch (final NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error using reflection on " + fieldName, e);
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format Images
|
||||
*/
|
||||
final class ImageBuilder {
|
||||
|
||||
private ImageBuilder() {
|
||||
|
||||
}
|
||||
|
||||
static void formatImage(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
String url = null;
|
||||
var requestedWidth = 0.0;
|
||||
var requestedHeight = 0.0;
|
||||
var preserveRatio = false;
|
||||
var smooth = false;
|
||||
var backgroundLoading = false;
|
||||
for (final var property : sortedAttributes) {
|
||||
switch (property.name()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "url" -> url = formatURL(progress, property.value());
|
||||
case "requestedWidth" -> requestedWidth = Double.parseDouble(property.value());
|
||||
case "requestedHeight" -> requestedHeight = Double.parseDouble(property.value());
|
||||
case "preserveRatio" -> preserveRatio = Boolean.parseBoolean(property.value());
|
||||
case "smooth" -> smooth = Boolean.parseBoolean(property.value());
|
||||
case "backgroundLoading" -> backgroundLoading = Boolean.parseBoolean(property.value());
|
||||
default -> throw new GenerationException("Unknown image attribute : " + property.name());
|
||||
}
|
||||
}
|
||||
final var urlString = progress.getNextVariableName("urlStr");
|
||||
progress.stringBuilder().append(START_VAR).append(urlString).append(" = ").append(url).append(".toString();\n");
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = new javafx.scene.image.Image(").append(urlString)
|
||||
.append(", ").append(requestedWidth).append(", ").append(requestedHeight).append(", ")
|
||||
.append(preserveRatio).append(", ").append(smooth).append(", ").append(backgroundLoading).append(");\n");
|
||||
handleId(progress, parsedObject, variableName);
|
||||
} else {
|
||||
throw new GenerationException("Image cannot have children or properties : " + parsedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.*;
|
||||
import com.github.gtache.fxml.compiler.parsing.impl.ParsedPropertyImpl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.SequencedCollection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ConstructorHelper.getListConstructorArgs;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ConstructorHelper.getMatchingConstructorArgs;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectControllerField;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.FontFormatter.formatFont;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ImageBuilder.formatImage;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.PropertyFormatter.formatProperty;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.SceneBuilder.formatScene;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.TriangleMeshBuilder.formatTriangleMesh;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.WebViewBuilder.formatWebView;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format properties
|
||||
*/
|
||||
public final class ObjectFormatter {
|
||||
|
||||
private static final String NEW_ASSIGN = " = new ";
|
||||
|
||||
private static final Set<String> BUILDER_CLASSES = Set.of(
|
||||
"javafx.scene.Scene",
|
||||
"javafx.scene.text.Font",
|
||||
"javafx.scene.image.Image",
|
||||
"java.net.URL",
|
||||
"javafx.scene.shape.TriangleMesh",
|
||||
"javafx.scene.web.WebView"
|
||||
);
|
||||
|
||||
private static final Set<String> SIMPLE_CLASSES = Set.of(
|
||||
"java.lang.String",
|
||||
"java.lang.Integer",
|
||||
"java.lang.Byte",
|
||||
"java.lang.Short",
|
||||
"java.lang.Long",
|
||||
"java.lang.Float",
|
||||
"java.lang.Double"
|
||||
);
|
||||
|
||||
private ObjectFormatter() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object to format
|
||||
* @param variableName The variable name for the object
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
public static void format(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
switch (parsedObject) {
|
||||
case final ParsedConstant constant -> formatConstant(progress, constant, variableName);
|
||||
case final ParsedCopy copy -> formatCopy(progress, copy, variableName);
|
||||
case final ParsedDefine define -> formatDefine(progress, define, variableName);
|
||||
case final ParsedFactory factory -> formatFactory(progress, factory, variableName);
|
||||
case final ParsedInclude include -> formatInclude(progress, include, variableName);
|
||||
case final ParsedReference reference -> formatReference(progress, reference, variableName);
|
||||
case final ParsedValue value -> formatValue(progress, value, variableName);
|
||||
case final ParsedText text -> formatText(progress, text, variableName);
|
||||
default -> formatObject(progress, parsedObject, variableName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a simple text
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param text The parsed text
|
||||
* @param variableName The variable name
|
||||
*/
|
||||
private static void formatText(final GenerationProgress progress, final ParsedText text, final String variableName) {
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = \"").append(text.text()).append("\";\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a basic object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object to format
|
||||
* @param variableName The variable name for the object
|
||||
*/
|
||||
private static void formatObject(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (BUILDER_CLASSES.contains(parsedObject.className())) {
|
||||
formatBuilderObject(progress, parsedObject, variableName);
|
||||
} else {
|
||||
formatNotBuilder(progress, parsedObject, variableName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a builder object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object
|
||||
* @param variableName The variable name
|
||||
*/
|
||||
private static void formatBuilderObject(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
final var className = parsedObject.className();
|
||||
switch (className) {
|
||||
case "javafx.scene.Scene" -> formatScene(progress, parsedObject, variableName);
|
||||
case "javafx.scene.text.Font" -> formatFont(progress, parsedObject, variableName);
|
||||
case "javafx.scene.image.Image" -> formatImage(progress, parsedObject, variableName);
|
||||
case "java.net.URL" -> formatURL(progress, parsedObject, variableName);
|
||||
case "javafx.scene.shape.TriangleMesh" -> formatTriangleMesh(progress, parsedObject, variableName);
|
||||
case "javafx.scene.web.WebView" -> formatWebView(progress, parsedObject, variableName);
|
||||
default -> throw new IllegalArgumentException("Unknown builder class : " + className);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatNotBuilder(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (isSimpleClass(parsedObject)) {
|
||||
formatSimpleClass(progress, parsedObject, variableName);
|
||||
} else {
|
||||
formatComplexClass(progress, parsedObject, variableName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatSimpleClass(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (!parsedObject.properties().isEmpty()) {
|
||||
throw new GenerationException("Simple class cannot have properties : " + parsedObject);
|
||||
}
|
||||
if (parsedObject.attributes().keySet().stream().anyMatch(k -> !k.equals(FX_ID) && !k.equals(VALUE) && !k.equals(FX_VALUE))) {
|
||||
throw new GenerationException("Invalid attributes for simple class : " + parsedObject);
|
||||
}
|
||||
final var value = getSimpleValue(progress, parsedObject);
|
||||
final var valueStr = ValueFormatter.toString(value, ReflectionHelper.getClass(parsedObject.className()));
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(valueStr).append(";\n");
|
||||
handleId(progress, parsedObject, variableName);
|
||||
}
|
||||
|
||||
private static String getSimpleValue(final GenerationProgress progress, final ParsedObject parsedObject) throws GenerationException {
|
||||
final var definedChildren = parsedObject.children().stream().filter(ParsedDefine.class::isInstance).toList();
|
||||
for (final var definedChild : definedChildren) {
|
||||
formatObject(progress, definedChild, progress.getNextVariableName("define"));
|
||||
}
|
||||
final var notDefinedChildren = parsedObject.children().stream().filter(c -> !(c instanceof ParsedDefine)).toList();
|
||||
if (parsedObject.attributes().containsKey(FX_VALUE)) {
|
||||
return getSimpleFXValue(parsedObject, notDefinedChildren);
|
||||
} else if (parsedObject.attributes().containsKey(VALUE)) {
|
||||
return getSimpleValue(parsedObject, notDefinedChildren);
|
||||
} else {
|
||||
return getSimpleChild(parsedObject, notDefinedChildren);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSimpleFXValue(final ParsedObject parsedObject, final Collection<ParsedObject> notDefinedChildren) throws GenerationException {
|
||||
if (notDefinedChildren.isEmpty() && !parsedObject.attributes().containsKey(VALUE)) {
|
||||
return parsedObject.attributes().get(FX_VALUE).value();
|
||||
} else {
|
||||
throw new GenerationException("Malformed simple class : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSimpleValue(final ParsedObject parsedObject, final Collection<ParsedObject> notDefinedChildren) throws GenerationException {
|
||||
if (notDefinedChildren.isEmpty()) {
|
||||
return parsedObject.attributes().get(VALUE).value();
|
||||
} else {
|
||||
throw new GenerationException("Malformed simple class : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSimpleChild(final ParsedObject parsedObject, final SequencedCollection<ParsedObject> notDefinedChildren) throws GenerationException {
|
||||
if (notDefinedChildren.size() == 1) {
|
||||
final var child = notDefinedChildren.getFirst();
|
||||
if (child instanceof final ParsedText text) {
|
||||
return text.text();
|
||||
} else {
|
||||
throw new GenerationException("Invalid value for : " + parsedObject);
|
||||
}
|
||||
} else {
|
||||
throw new GenerationException("Value not found for : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSimpleClass(final ParsedObject object) throws GenerationException {
|
||||
final var className = object.className();
|
||||
if (SIMPLE_CLASSES.contains(className)) {
|
||||
return true;
|
||||
} else {
|
||||
final var clazz = ReflectionHelper.getClass(className);
|
||||
return clazz.isEnum();
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatComplexClass(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
final var clazz = ReflectionHelper.getClass(parsedObject.className());
|
||||
final var children = parsedObject.children();
|
||||
final var definedChildren = children.stream().filter(ParsedDefine.class::isInstance).toList();
|
||||
final var notDefinedChildren = children.stream().filter(c -> !(c instanceof ParsedDefine)).toList();
|
||||
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()));
|
||||
if (!definedChildren.isEmpty()) {
|
||||
for (final var definedChild : definedChildren) {
|
||||
format(progress, definedChild, progress.getNextVariableName("define"));
|
||||
}
|
||||
}
|
||||
if (!notDefinedChildren.isEmpty()) {
|
||||
final var defaultProperty = getDefaultProperty(parsedObject.className());
|
||||
if (defaultProperty != null) {
|
||||
allPropertyNames.add(defaultProperty);
|
||||
}
|
||||
}
|
||||
final var constructorArgs = getMatchingConstructorArgs(constructors, allPropertyNames);
|
||||
if (constructorArgs == null) {
|
||||
formatNoConstructor(progress, parsedObject, variableName, allPropertyNames);
|
||||
} else {
|
||||
formatConstructor(progress, parsedObject, variableName, constructorArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatNoConstructor(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName, final Collection<String> allPropertyNames) throws GenerationException {
|
||||
final var clazz = ReflectionHelper.getClass(parsedObject.className());
|
||||
if (allPropertyNames.size() == 1 && allPropertyNames.iterator().next().equals("fx:constant")) {
|
||||
final var property = parsedObject.attributes().get("fx:constant");
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(clazz.getCanonicalName()).append(".").append(property.value()).append(";\n");
|
||||
} else {
|
||||
throw new GenerationException("Cannot find constructor for " + clazz.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatConstructor(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName, final ConstructorArgs constructorArgs) throws GenerationException {
|
||||
final var args = getListConstructorArgs(constructorArgs, parsedObject);
|
||||
final var genericTypes = getGenericTypes(progress, parsedObject);
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(NEW_ASSIGN).append(parsedObject.className())
|
||||
.append(genericTypes).append("(").append(String.join(", ", args)).append(");\n");
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
for (final var value : sortedAttributes) {
|
||||
if (!constructorArgs.namedArgs().containsKey(value.name())) {
|
||||
formatProperty(progress, value, parsedObject, variableName);
|
||||
}
|
||||
}
|
||||
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(progress, parsedObject, p, o, variableName);
|
||||
}
|
||||
}
|
||||
final var notDefinedChildren = parsedObject.children().stream().filter(c -> !(c instanceof ParsedDefine)).toList();
|
||||
if (!notDefinedChildren.isEmpty()) {
|
||||
final var defaultProperty = getDefaultProperty(parsedObject.className());
|
||||
if (!constructorArgs.namedArgs().containsKey(defaultProperty)) {
|
||||
final var property = new ParsedPropertyImpl(defaultProperty, null, null);
|
||||
formatChild(progress, parsedObject, property, notDefinedChildren, variableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an include object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param include The include object
|
||||
* @param subNodeName The sub node name
|
||||
*/
|
||||
private static void formatInclude(final GenerationProgress progress, final ParsedInclude include, final String subNodeName) throws GenerationException {
|
||||
final var subViewVariable = progress.getNextVariableName("view");
|
||||
final var source = include.source();
|
||||
final var resources = include.resources();
|
||||
final var request = progress.request();
|
||||
final var subControllerClass = request.parameters().sourceToControllerName().get(source);
|
||||
final var subClassName = request.parameters().sourceToGeneratedClassName().get(source);
|
||||
if (subClassName == null) {
|
||||
throw new GenerationException("Unknown include source : " + source);
|
||||
}
|
||||
final var sb = progress.stringBuilder();
|
||||
if (resources == null) {
|
||||
sb.append(START_VAR).append(subViewVariable).append(NEW_ASSIGN).append(subClassName).append("(controllersMap, resourceBundlesMap);\n");
|
||||
} else {
|
||||
final var subResourceBundlesMapVariable = progress.getNextVariableName("map");
|
||||
final var subBundleVariable = progress.getNextVariableName("bundle");
|
||||
sb.append(START_VAR).append(subResourceBundlesMapVariable).append(" = new HashMap<>(resourceBundlesMap);\n");
|
||||
sb.append(START_VAR).append(subBundleVariable).append(" = java.util.ResourceBundle.getBundle(\"").append(resources).append("\");\n");
|
||||
sb.append(" ").append(subResourceBundlesMapVariable).append(".put(").append(subControllerClass).append(", ").append(subBundleVariable).append(");\n");
|
||||
sb.append(START_VAR).append(subViewVariable).append(NEW_ASSIGN).append(subClassName).append("(controllersMap, ").append(subResourceBundlesMapVariable).append(");\n");
|
||||
}
|
||||
sb.append(" final javafx.scene.Parent ").append(subNodeName).append(" = ").append(subViewVariable).append(".load();\n");
|
||||
injectSubController(progress, include, subViewVariable);
|
||||
}
|
||||
|
||||
private static void injectSubController(final GenerationProgress progress, final ParsedInclude include, final String subViewVariable) throws GenerationException {
|
||||
final var id = include.controllerId();
|
||||
if (id != null) {
|
||||
final var subControllerVariable = progress.getNextVariableName("controller");
|
||||
progress.stringBuilder().append(START_VAR).append(subControllerVariable).append(" = ").append(subViewVariable).append(".controller();\n");
|
||||
progress.idToVariableName().put(id, subControllerVariable);
|
||||
progress.idToObject().put(id, include);
|
||||
//TODO Don't inject if variable doesn't exist
|
||||
injectControllerField(progress, id, subControllerVariable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a fx:define
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param define The parsed define
|
||||
* @param variableName The variable name
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static void formatDefine(final GenerationProgress progress, final ParsedDefine define, final String variableName) throws GenerationException {
|
||||
formatObject(progress, define.object(), variableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a fx:reference
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param reference The parsed reference
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static void formatReference(final GenerationProgress progress, final ParsedReference reference, final String variableName) throws GenerationException {
|
||||
final var id = reference.source();
|
||||
final var variable = progress.idToVariableName().get(id);
|
||||
if (variable == null) {
|
||||
throw new GenerationException("Unknown id : " + id);
|
||||
}
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(variable).append(";\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a fx:copy
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param copy The parsed copy
|
||||
* @param variableName The variable name
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static void formatCopy(final GenerationProgress progress, final ParsedCopy copy, final String variableName) throws GenerationException {
|
||||
final var id = copy.source();
|
||||
final var variable = progress.idToVariableName().get(id);
|
||||
final var object = progress.idToObject().get(id);
|
||||
if (variable == null || object == null) {
|
||||
throw new GenerationException("Unknown id : " + id);
|
||||
}
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(NEW_ASSIGN).append(object.className()).append("(").append(variable).append(");\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a constant object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param constant The constant
|
||||
* @param variableName The variable name
|
||||
*/
|
||||
private static void formatConstant(final GenerationProgress progress, final ParsedConstant constant, final String variableName) {
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(constant.className()).append(".").append(constant.constant()).append(";\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a value object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param value The value
|
||||
* @param variableName The variable name
|
||||
*/
|
||||
private static void formatValue(final GenerationProgress progress, final ParsedValue value, final String variableName) {
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(value.className()).append(".valueOf(\"").append(value.value()).append("\");\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a factory object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param factory The factory
|
||||
* @param variableName The variable name
|
||||
*/
|
||||
private static void formatFactory(final GenerationProgress progress, final ParsedFactory factory, final String variableName) throws GenerationException {
|
||||
final var variables = new ArrayList<String>();
|
||||
for (final var argument : factory.arguments()) {
|
||||
final var argumentVariable = progress.getNextVariableName("arg");
|
||||
variables.add(argumentVariable);
|
||||
format(progress, argument, argumentVariable);
|
||||
}
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = ").append(factory.className())
|
||||
.append(".").append(factory.factory()).append("(").append(String.join(", ", variables)).append(");\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the children objects of a property
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parent The parent object
|
||||
* @param property The parent property
|
||||
* @param objects The child objects
|
||||
* @param parentVariable The parent object variable
|
||||
*/
|
||||
private static void formatChild(final GenerationProgress progress, final ParsedObject parent, final ParsedProperty property,
|
||||
final Iterable<? extends ParsedObject> objects, final String parentVariable) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var variables = new ArrayList<String>();
|
||||
for (final var object : objects) {
|
||||
final var vn = progress.getNextVariableName(getVariablePrefix(object));
|
||||
format(progress, object, vn);
|
||||
if (!(object instanceof ParsedDefine)) {
|
||||
variables.add(vn);
|
||||
}
|
||||
}
|
||||
if (variables.size() > 1) {
|
||||
formatMultipleChildren(progress, variables, propertyName, parent, parentVariable);
|
||||
} else if (variables.size() == 1) {
|
||||
final var vn = variables.getFirst();
|
||||
formatSingleChild(progress, vn, property, parent, parentVariable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats children objects given that they are more than one
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param variables The children variables
|
||||
* @param propertyName The property name
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
*/
|
||||
private static void formatMultipleChildren(final GenerationProgress progress, final Iterable<String> variables, final String propertyName, final ParsedObject parent,
|
||||
final String parentVariable) throws GenerationException {
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
if (hasMethod(ReflectionHelper.getClass(parent.className()), getMethod)) {
|
||||
progress.stringBuilder().append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(java.util.List.of(").append(String.join(", ", variables)).append("));\n");
|
||||
} else {
|
||||
throw getCannotSetException(propertyName, parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a single child object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
*/
|
||||
private static void formatSingleChild(final GenerationProgress progress, final String variableName, final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable) throws GenerationException {
|
||||
if (property.sourceType() == null) {
|
||||
formatSingleChildInstance(progress, variableName, property, parent, parentVariable);
|
||||
} else {
|
||||
formatSingleChildStatic(progress, variableName, property, parentVariable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a single child object using an instance method on the parent object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @param parent The parent object
|
||||
* @param parentVariable The parent object variable
|
||||
*/
|
||||
private static void formatSingleChildInstance(final GenerationProgress progress, final String variableName,
|
||||
final ParsedProperty property, final ParsedObject parent,
|
||||
final String parentVariable) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
final var getMethod = getGetMethod(property);
|
||||
final var parentClass = ReflectionHelper.getClass(parent.className());
|
||||
final var sb = progress.stringBuilder();
|
||||
if (hasMethod(parentClass, setMethod)) {
|
||||
sb.append(" ").append(parentVariable).append(".").append(setMethod).append("(").append(variableName).append(");\n");
|
||||
} else if (hasMethod(parentClass, getMethod)) {
|
||||
//Probably a list method that has only one element
|
||||
sb.append(" ").append(parentVariable).append(".").append(getMethod).append("().addAll(java.util.List.of(").append(variableName).append("));\n");
|
||||
} else {
|
||||
throw getCannotSetException(property.name(), parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a child object using a static method
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param variableName The child's variable name
|
||||
* @param property The parent property
|
||||
* @param parentVariable The parent variable
|
||||
*/
|
||||
private static void formatSingleChildStatic(final GenerationProgress progress, final String variableName,
|
||||
final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property);
|
||||
if (hasStaticMethod(ReflectionHelper.getClass(property.sourceType()), setMethod)) {
|
||||
progress.stringBuilder().append(" ").append(property.sourceType()).append(".").append(setMethod)
|
||||
.append("(").append(parentVariable).append(", ").append(variableName).append(");\n");
|
||||
} else {
|
||||
throw getCannotSetException(property.name(), property.sourceType());
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerationException getCannotSetException(final String propertyName, final String className) {
|
||||
return new GenerationException("Cannot set " + propertyName + " on " + className);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@@ -11,7 +11,15 @@ import static java.util.Objects.requireNonNull;
|
||||
*/
|
||||
record Parameter(String name, Class<?> type, String defaultValue) {
|
||||
|
||||
Parameter {
|
||||
/**
|
||||
* Instantiates a new Parameter
|
||||
*
|
||||
* @param name The parameter name
|
||||
* @param type The parameter type
|
||||
* @param defaultValue The parameter default value
|
||||
* @throws NullPointerException if any parameter is null
|
||||
*/
|
||||
public Parameter {
|
||||
requireNonNull(name);
|
||||
requireNonNull(type);
|
||||
requireNonNull(defaultValue);
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.ControllerFieldInjectionTypes;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import javafx.event.EventHandler;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectEventHandlerControllerMethod;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setEventHandler;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ValueFormatter.getArg;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format properties
|
||||
*/
|
||||
final class PropertyFormatter {
|
||||
private PropertyFormatter() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a property
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to format
|
||||
* @param parent The property's parent object
|
||||
* @param parentVariable The parent variable
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void formatProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
if (propertyName.equals(FX_ID)) {
|
||||
handleId(progress, parent, parentVariable);
|
||||
} else if (propertyName.equals("fx:controller")) {
|
||||
checkDuplicateController(progress, parent);
|
||||
} else if (Objects.equals(property.sourceType(), EventHandler.class.getName())) {
|
||||
handleEventHandler(progress, property, parentVariable);
|
||||
} else if (property.sourceType() != null) {
|
||||
handleStaticProperty(progress, property, parentVariable, propertyName);
|
||||
} else {
|
||||
handleProperty(progress, property, parent, parentVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDuplicateController(final GenerationProgress progress, final ParsedObject parent) throws GenerationException {
|
||||
if (parent != progress.request().rootObject()) {
|
||||
throw new GenerationException("Invalid nested controller");
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleEventHandler(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
if (property.value().startsWith("#")) {
|
||||
injectEventHandlerControllerMethod(progress, property, parentVariable);
|
||||
} else {
|
||||
setEventHandler(progress, property, parentVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleStaticProperty(final GenerationProgress progress, final ParsedProperty property, final String parentVariable, final String propertyName) throws GenerationException {
|
||||
final var setMethod = getSetMethod(propertyName);
|
||||
final var propertySourceTypeClass = ReflectionHelper.getClass(property.sourceType());
|
||||
if (hasStaticMethod(propertySourceTypeClass, setMethod)) {
|
||||
final var method = getStaticMethod(propertySourceTypeClass, setMethod);
|
||||
final var parameterType = method.getParameterTypes()[1];
|
||||
final var arg = getArg(progress, property.value(), parameterType);
|
||||
setLaterIfNeeded(progress, property, parameterType, " " + property.sourceType() + "." + setMethod + "(" + parentVariable + ", " + arg + ");\n");
|
||||
} else {
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + property.sourceType());
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleProperty(final GenerationProgress progress, final ParsedProperty property, final ParsedObject parent, final String parentVariable) throws GenerationException {
|
||||
final var propertyName = property.name();
|
||||
final var setMethod = getSetMethod(propertyName);
|
||||
final var getMethod = getGetMethod(propertyName);
|
||||
final var parentClass = ReflectionHelper.getClass(parent.className());
|
||||
if (hasMethod(parentClass, setMethod)) {
|
||||
handleSetProperty(progress, property, parentClass, parentVariable);
|
||||
} else if (hasMethod(parentClass, getMethod)) {
|
||||
handleGetProperty(progress, property, parentClass, parentVariable);
|
||||
} else {
|
||||
throw new GenerationException("Cannot set " + propertyName + " on " + parent.className());
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
|
||||
final var setMethod = getSetMethod(property.name());
|
||||
final var method = getMethod(parentClass, setMethod);
|
||||
final var parameterType = method.getParameterTypes()[0];
|
||||
final var arg = getArg(progress, property.value(), parameterType);
|
||||
setLaterIfNeeded(progress, property, parameterType, " " + parentVariable + "." + setMethod + "(" + arg + ");\n");
|
||||
}
|
||||
|
||||
private static void handleGetProperty(final GenerationProgress progress, final ParsedProperty property, final Class<?> parentClass, final String parentVariable) throws GenerationException {
|
||||
final var getMethod = getGetMethod(property.name());
|
||||
final var method = getMethod(parentClass, getMethod);
|
||||
final var returnType = method.getReturnType();
|
||||
if (hasMethod(returnType, "addAll")) {
|
||||
final var arg = getArg(progress, property.value(), String.class);
|
||||
setLaterIfNeeded(progress, property, String.class, " " + parentVariable + "." + getMethod + "().addAll(java.util.List.of(" + arg + "));\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the text to set after constructor creation if factory injection is used
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property
|
||||
* @param type The type
|
||||
* @param arg The argument
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static void setLaterIfNeeded(final GenerationProgress progress, final ParsedProperty property, final Class<?> type, final String arg) throws GenerationException {
|
||||
if (type == String.class && property.value().startsWith("%") && progress.request().parameters().resourceBundleInjection().injectionType() == ResourceBundleInjectionTypes.GETTER
|
||||
&& getControllerInjection(progress).fieldInjectionType() == ControllerFieldInjectionTypes.FACTORY) {
|
||||
progress.controllerFactoryPostAction().add(arg);
|
||||
} else {
|
||||
progress.stringBuilder().append(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.Node;
|
||||
|
||||
@@ -12,12 +15,15 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.FX_ID;
|
||||
|
||||
/**
|
||||
* 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<String, String> DEFAULT_PROPERTY = 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<>();
|
||||
|
||||
@@ -199,6 +205,69 @@ final class ReflectionHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the default property for the given class
|
||||
*
|
||||
* @param className The class name
|
||||
* @return The default property
|
||||
* @throws GenerationException If the class is not found or no default property is found
|
||||
*/
|
||||
static String getDefaultProperty(final String className) throws GenerationException {
|
||||
if (DEFAULT_PROPERTY.containsKey(className)) {
|
||||
return DEFAULT_PROPERTY.get(className);
|
||||
} else {
|
||||
final var defaultProperty = computeDefaultProperty(className);
|
||||
if (defaultProperty != null) {
|
||||
DEFAULT_PROPERTY.put(className, defaultProperty);
|
||||
}
|
||||
return defaultProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapper class for the given class
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return The wrapper class (e.g. int.class -> Integer.class) or the original class if it is not a primitive
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class for the given class name
|
||||
*
|
||||
* @param className The class name
|
||||
* @return The class
|
||||
* @throws GenerationException If the class is not found
|
||||
*/
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static String computeDefaultProperty(final String className) throws GenerationException {
|
||||
try {
|
||||
final var clazz = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
|
||||
final var annotation = clazz.getAnnotation(DefaultProperty.class);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
} else {
|
||||
return annotation.value();
|
||||
}
|
||||
} catch (final ClassNotFoundException e) {
|
||||
throw new GenerationException("Class " + className + " not found ; Either specify the property explicitly or put the class in a dependency", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the default value for the given class
|
||||
*
|
||||
@@ -217,4 +286,31 @@ final class ReflectionHelper {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generic types for the given object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object
|
||||
* @return The generic types
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static String getGenericTypes(final GenerationProgress progress, final ParsedObject parsedObject) throws GenerationException {
|
||||
final var clazz = getClass(parsedObject.className());
|
||||
if (isGeneric(clazz)) {
|
||||
final var idProperty = parsedObject.attributes().get(FX_ID);
|
||||
if (idProperty == null) {
|
||||
return "<>";
|
||||
} else {
|
||||
final var id = idProperty.value();
|
||||
final var genericTypes = progress.request().controllerInfo().propertyGenericTypes(id);
|
||||
if (genericTypes == null) { //Raw
|
||||
return "";
|
||||
} else {
|
||||
return "<" + String.join(", ", genericTypes) + ">";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ObjectFormatter.format;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.URLBuilder.formatURL;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format Scenes
|
||||
*/
|
||||
final class SceneBuilder {
|
||||
|
||||
private SceneBuilder() {
|
||||
|
||||
}
|
||||
|
||||
static void formatScene(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
final var root = findRoot(parsedObject);
|
||||
final var rootVariableName = progress.getNextVariableName("root");
|
||||
format(progress, root, rootVariableName);
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
double width = -1;
|
||||
double height = -1;
|
||||
var paint = Color.WHITE.toString();
|
||||
final var stylesheets = new ArrayList<String>();
|
||||
for (final var property : sortedAttributes) {
|
||||
switch (property.name()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "width" -> width = Double.parseDouble(property.value());
|
||||
case "height" -> height = Double.parseDouble(property.value());
|
||||
case "fill" -> paint = property.value();
|
||||
case "stylesheets" -> stylesheets.add(property.value());
|
||||
default -> throw new GenerationException("Unknown font attribute : " + property.name());
|
||||
}
|
||||
}
|
||||
final var sb = progress.stringBuilder();
|
||||
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.Scene(").append(rootVariableName).append(", ")
|
||||
.append(width).append(", ").append(height).append(", javafx.scene.paint.Color.valueOf(\"").append(paint).append("\"));\n");
|
||||
addStylesheets(progress, variableName, stylesheets);
|
||||
handleId(progress, parsedObject, variableName);
|
||||
}
|
||||
|
||||
private static ParsedObject findRoot(final ParsedObject parsedObject) throws GenerationException {
|
||||
final var rootProperty = parsedObject.properties().entrySet().stream().filter(e -> e.getKey().name().equals("root"))
|
||||
.filter(e -> e.getValue().size() == 1)
|
||||
.map(e -> e.getValue().getFirst()).findFirst().orElse(null);
|
||||
if (rootProperty != null) {
|
||||
return rootProperty;
|
||||
} else if (parsedObject.children().size() == 1) {
|
||||
return parsedObject.children().getFirst();
|
||||
} else {
|
||||
throw new GenerationException("Scene must have a root");
|
||||
}
|
||||
}
|
||||
|
||||
private static void addStylesheets(final GenerationProgress progress, final String variableName, final Collection<String> stylesheets) {
|
||||
if (!stylesheets.isEmpty()) {
|
||||
final var urlVariables = formatURL(progress, stylesheets);
|
||||
final var tmpVariable = progress.getNextVariableName("stylesheets");
|
||||
final var sb = progress.stringBuilder();
|
||||
sb.append("""
|
||||
final var %1$s = %2$s.getStylesheets();
|
||||
%1$s.addAll(java.util.List.of(%3$s));
|
||||
""".formatted(tmpVariable, variableName, String.join(", ", urlVariables)));
|
||||
stylesheets.forEach(s -> sb.append(" ").append(variableName).append(".getStyleSheets().add(\"").append(s).append("\");\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import javafx.scene.shape.VertexFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format TriangleMeshes
|
||||
*/
|
||||
final class TriangleMeshBuilder {
|
||||
|
||||
private TriangleMeshBuilder() {
|
||||
|
||||
}
|
||||
|
||||
static void formatTriangleMesh(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
final var points = new ArrayList<Float>();
|
||||
final var texCoords = new ArrayList<Float>();
|
||||
final var normals = new ArrayList<Float>();
|
||||
final var faces = new ArrayList<Integer>();
|
||||
final var faceSmoothingGroups = new ArrayList<Integer>();
|
||||
VertexFormat vertexFormat = null;
|
||||
for (final var property : sortedAttributes) {
|
||||
switch (property.name().toLowerCase()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "points" -> {
|
||||
points.clear();
|
||||
points.addAll(parseList(property.value(), Float::parseFloat));
|
||||
}
|
||||
case "texcoords" -> {
|
||||
texCoords.clear();
|
||||
texCoords.addAll(parseList(property.value(), Float::parseFloat));
|
||||
}
|
||||
case "normals" -> {
|
||||
normals.clear();
|
||||
normals.addAll(parseList(property.value(), Float::parseFloat));
|
||||
}
|
||||
case "faces" -> {
|
||||
faces.clear();
|
||||
faces.addAll(parseList(property.value(), Integer::parseInt));
|
||||
}
|
||||
case "facesmoothinggroups" -> {
|
||||
faceSmoothingGroups.clear();
|
||||
faceSmoothingGroups.addAll(parseList(property.value(), Integer::parseInt));
|
||||
}
|
||||
case "vertexformat" -> vertexFormat = parseVertexFormat(property);
|
||||
default -> throw new GenerationException("Unknown TriangleMesh attribute : " + property.name());
|
||||
}
|
||||
}
|
||||
final var sb = progress.stringBuilder();
|
||||
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.shape.TriangleMesh();\n");
|
||||
setPoints(progress, variableName, points);
|
||||
setTexCoords(progress, variableName, texCoords);
|
||||
setNormals(progress, variableName, normals);
|
||||
setFaces(progress, variableName, faces);
|
||||
setFaceSmoothingGroups(progress, variableName, faceSmoothingGroups);
|
||||
setVertexFormat(progress, variableName, vertexFormat);
|
||||
handleId(progress, parsedObject, variableName);
|
||||
} else {
|
||||
throw new GenerationException("Image cannot have children or properties : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static VertexFormat parseVertexFormat(final ParsedProperty property) throws GenerationException {
|
||||
if (property.value().equalsIgnoreCase("point_texcoord")) {
|
||||
return VertexFormat.POINT_TEXCOORD;
|
||||
} else if (property.value().equalsIgnoreCase("point_normal_texcoord")) {
|
||||
return VertexFormat.POINT_NORMAL_TEXCOORD;
|
||||
} else {
|
||||
throw new GenerationException("Unknown vertex format : " + property.value());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setPoints(final GenerationProgress progress, final String variableName, final Collection<Float> points) {
|
||||
if (!points.isEmpty()) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".getPoints().setAll(new float[]{").append(formatList(points)).append("});\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setTexCoords(final GenerationProgress progress, final String variableName, final Collection<Float> texCoords) {
|
||||
if (!texCoords.isEmpty()) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".getTexCoords().setAll(new float[]{").append(formatList(texCoords)).append("});\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNormals(final GenerationProgress progress, final String variableName, final Collection<Float> normals) {
|
||||
if (!normals.isEmpty()) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".getNormals().setAll(new float[]{").append(formatList(normals)).append("});\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFaces(final GenerationProgress progress, final String variableName, final Collection<Integer> faces) {
|
||||
if (!faces.isEmpty()) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".getFaces().setAll(new int[]{").append(formatList(faces)).append("});\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFaceSmoothingGroups(final GenerationProgress progress, final String variableName, final Collection<Integer> faceSmoothingGroups) {
|
||||
if (!faceSmoothingGroups.isEmpty()) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".getFaceSmoothingGroups().setAll(new int[]{").append(formatList(faceSmoothingGroups)).append("});\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void setVertexFormat(final GenerationProgress progress, final String variableName, final VertexFormat vertexFormat) {
|
||||
if (vertexFormat != null) {
|
||||
progress.stringBuilder().append(" ").append(variableName).append(".setVertexFormat(javafx.scene.shape.VertexFormat.").append(vertexFormat).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> String formatList(final Collection<T> list) {
|
||||
return list.stream().map(String::valueOf).collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
private static <T> List<T> parseList(final CharSequence value, final Function<? super String, ? extends T> parser) {
|
||||
final var splitPattern = Pattern.compile("[\\s+,]");
|
||||
final var split = splitPattern.split(value);
|
||||
return Arrays.stream(split).map(parser).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format URLs
|
||||
*/
|
||||
final class URLBuilder {
|
||||
|
||||
private URLBuilder() {
|
||||
|
||||
}
|
||||
|
||||
static List<String> formatURL(final GenerationProgress progress, final Collection<String> stylesheets) {
|
||||
return stylesheets.stream().map(s -> formatURL(progress, s)).toList();
|
||||
}
|
||||
|
||||
static String formatURL(final GenerationProgress progress, final String url) {
|
||||
final var variableName = progress.getNextVariableName("url");
|
||||
final var sb = progress.stringBuilder();
|
||||
if (url.startsWith("@")) {
|
||||
sb.append(START_VAR).append(variableName).append(" = getClass().getResource(\"").append(url.substring(1)).append("\");\n");
|
||||
} else {
|
||||
sb.append("""
|
||||
final java.net.URL %1$s;
|
||||
try {
|
||||
%1$s = new java.net.URI("%2$s").toURL();
|
||||
} catch (final java.net.MalformedURLException | java.net.URISyntaxException e) {
|
||||
throw new RuntimeException("Couldn't parse url : %2$s", e);
|
||||
}
|
||||
""".formatted(variableName, url));
|
||||
}
|
||||
return variableName;
|
||||
}
|
||||
|
||||
static void formatURL(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
String value = null;
|
||||
for (final var property : sortedAttributes) {
|
||||
switch (property.name()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "value" -> value = property.value();
|
||||
default -> throw new GenerationException("Unknown URL attribute : " + property.name());
|
||||
}
|
||||
}
|
||||
progress.stringBuilder().append(START_VAR).append(variableName).append(" = getClass().getResource(\"").append(value).append("\");\n");
|
||||
handleId(progress, parsedObject, variableName);
|
||||
} else {
|
||||
throw new GenerationException("URL cannot have children or properties : " + parsedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.impl.ResourceBundleInjectionTypes;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.getWrapperClass;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ReflectionHelper.hasValueOf;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format values
|
||||
*/
|
||||
final class ValueFormatter {
|
||||
|
||||
private static final Pattern INT_PATTERN = Pattern.compile("\\d+");
|
||||
private static final Pattern DECIMAL_PATTERN = Pattern.compile("\\d+(?:\\.\\d+)?");
|
||||
private static final Pattern START_BACKSLASH_PATTERN = Pattern.compile("^\\\\");
|
||||
|
||||
private ValueFormatter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an argument to a method
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param value The value
|
||||
* @param parameterType The parameter type
|
||||
* @return The formatted value
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static String getArg(final GenerationProgress progress, final String value, final Class<?> parameterType) throws GenerationException {
|
||||
if (parameterType == String.class && value.startsWith("%")) {
|
||||
return getBundleValue(progress, value.substring(1));
|
||||
} else if (value.startsWith("@")) {
|
||||
final var subpath = value.substring(1);
|
||||
return getResourceValue(subpath);
|
||||
} else if (value.startsWith("${")) {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
} else if (value.startsWith("$")) {
|
||||
final var variable = progress.idToVariableName().get(value.substring(1));
|
||||
if (variable == null) {
|
||||
throw new GenerationException("Unknown variable : " + value.substring(1));
|
||||
}
|
||||
return variable;
|
||||
} else {
|
||||
return toString(value, parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getResourceValue(final String subpath) {
|
||||
return "getClass().getResource(\"" + subpath + "\").toString()";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resource bundle value for the given value
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param value The value
|
||||
* @return The resource bundle value
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
private static String getBundleValue(final GenerationProgress progress, final String value) throws GenerationException {
|
||||
final var resourceBundleInjectionType = progress.request().parameters().resourceBundleInjection().injectionType();
|
||||
if (resourceBundleInjectionType instanceof final ResourceBundleInjectionTypes types) {
|
||||
return switch (types) {
|
||||
case CONSTRUCTOR, GET_BUNDLE -> "bundle.getString(\"" + value + "\")";
|
||||
case GETTER -> "controller.resources().getString(\"" + value + "\")";
|
||||
};
|
||||
} else {
|
||||
throw new GenerationException("Unknown resource bundle injection type : " + resourceBundleInjectionType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the string value to use in the generated code
|
||||
*
|
||||
* @param value The value
|
||||
* @param clazz The value class
|
||||
* @return The computed string value
|
||||
*/
|
||||
static String toString(final String value, final Class<?> clazz) {
|
||||
if (clazz == String.class) {
|
||||
return "\"" + START_BACKSLASH_PATTERN.matcher(value).replaceAll("").replace("\"", "\\\"") + "\"";
|
||||
} else if (clazz == char.class || clazz == Character.class) {
|
||||
return "'" + value + "'";
|
||||
} 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 getValueOf(getWrapperClass(clazz), 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 getValueOf(getWrapperClass(clazz), value);
|
||||
}
|
||||
} else if (hasValueOf(clazz)) {
|
||||
if (clazz.isEnum()) {
|
||||
return clazz.getCanonicalName() + "." + value;
|
||||
} else {
|
||||
return getValueOf(clazz.getCanonicalName(), value);
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValueOf(final String clazz, final String value) {
|
||||
return clazz + ".valueOf(\"" + value + "\")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.GeneratorImpl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectCallbackControllerMethod;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.ControllerInjector.injectEventHandlerControllerMethod;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setEventHandler;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.FieldSetter.setField;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.GenerationHelper.*;
|
||||
import static com.github.gtache.fxml.compiler.impl.internal.PropertyFormatter.formatProperty;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link GeneratorImpl} to format WebViews
|
||||
*/
|
||||
final class WebViewBuilder {
|
||||
|
||||
private WebViewBuilder() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a WebView object
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param parsedObject The parsed object
|
||||
* @param variableName The variable name
|
||||
* @throws GenerationException if an error occurs
|
||||
*/
|
||||
static void formatWebView(final GenerationProgress progress, final ParsedObject parsedObject, final String variableName) throws GenerationException {
|
||||
if (parsedObject.children().isEmpty() && parsedObject.properties().isEmpty()) {
|
||||
final var sortedAttributes = getSortedAttributes(parsedObject);
|
||||
final var sb = progress.stringBuilder();
|
||||
sb.append(START_VAR).append(variableName).append(" = new javafx.scene.web.WebView();\n");
|
||||
final var engineVariable = progress.getNextVariableName("engine");
|
||||
sb.append(START_VAR).append(engineVariable).append(" = ").append(variableName).append(".getEngine();\n");
|
||||
for (final var value : sortedAttributes) {
|
||||
formatAttribute(progress, value, parsedObject, variableName, engineVariable);
|
||||
}
|
||||
handleId(progress, parsedObject, variableName);
|
||||
} else {
|
||||
throw new GenerationException("WebView cannot have children or properties : " + parsedObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static void formatAttribute(final GenerationProgress progress, final ParsedProperty value, final ParsedObject parsedObject,
|
||||
final String variableName, final String engineVariable) throws GenerationException {
|
||||
switch (value.name()) {
|
||||
case FX_ID -> {
|
||||
//Do nothing
|
||||
}
|
||||
case "confirmHandler" -> injectConfirmHandler(progress, value, engineVariable);
|
||||
case "createPopupHandler" -> injectCreatePopupHandler(progress, value, engineVariable);
|
||||
case "onAlert", "onResized", "onStatusChanged", "onVisibilityChanged" ->
|
||||
injectEventHandler(progress, value, engineVariable);
|
||||
case "promptHandler" -> injectPromptHandler(progress, value, engineVariable);
|
||||
case "location" -> injectLocation(progress, value, engineVariable);
|
||||
default -> formatProperty(progress, value, parsedObject, variableName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectConfirmHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
|
||||
if (value.value().startsWith("#")) {
|
||||
injectCallbackControllerMethod(progress, value, engineVariable, "String.class");
|
||||
} else {
|
||||
setCallback(progress, value, engineVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectCreatePopupHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
|
||||
if (value.value().startsWith("#")) {
|
||||
injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PopupFeatures.class");
|
||||
} else {
|
||||
setCallback(progress, value, engineVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectEventHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
|
||||
if (value.value().startsWith("#")) {
|
||||
injectEventHandlerControllerMethod(progress, value, engineVariable);
|
||||
} else {
|
||||
setEventHandler(progress, value, engineVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectPromptHandler(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) throws GenerationException {
|
||||
if (value.value().startsWith("#")) {
|
||||
injectCallbackControllerMethod(progress, value, engineVariable, "javafx.scene.web.PromptData.class");
|
||||
} else {
|
||||
setCallback(progress, value, engineVariable);
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectLocation(final GenerationProgress progress, final ParsedProperty value, final String engineVariable) {
|
||||
progress.stringBuilder().append(" ").append(engineVariable).append(".load(\"").append(value.value()).append("\");\n");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback field
|
||||
*
|
||||
* @param progress The generation progress
|
||||
* @param property The property to inject
|
||||
* @param parentVariable The parent variable
|
||||
*/
|
||||
private static void setCallback(final GenerationProgress progress, final ParsedProperty property, final String parentVariable) throws GenerationException {
|
||||
setField(progress, property, parentVariable, "javafx.util.Callback");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedCopy;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedCopy}
|
||||
*
|
||||
* @param attributes The reference properties
|
||||
*/
|
||||
public record ParsedCopyImpl(Map<String, ParsedProperty> attributes) implements ParsedCopy {
|
||||
|
||||
public ParsedCopyImpl {
|
||||
attributes = Map.copyOf(attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedObject}
|
||||
*
|
||||
* @param className The object class
|
||||
* @param attributes The object properties
|
||||
* @param properties The object children (complex properties)
|
||||
*/
|
||||
public record ParsedDefineImpl(ParsedObject object) implements ParsedDefine {
|
||||
|
||||
public ParsedDefineImpl {
|
||||
Objects.requireNonNull(object);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedFactory;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedCollection;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedFactory}
|
||||
*
|
||||
* @param className The factory class
|
||||
* @param attributes The factory properties
|
||||
* @param arguments The factory arguments
|
||||
*/
|
||||
public record ParsedFactoryImpl(String className, Map<String, ParsedProperty> attributes,
|
||||
SequencedCollection<ParsedObject> arguments,
|
||||
SequencedCollection<ParsedObject> children) implements ParsedFactory {
|
||||
|
||||
public ParsedFactoryImpl {
|
||||
Objects.requireNonNull(className);
|
||||
attributes = Map.copyOf(attributes);
|
||||
arguments = List.copyOf(arguments);
|
||||
children = List.copyOf(children);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedCollection;
|
||||
@@ -20,74 +19,13 @@ import java.util.SequencedMap;
|
||||
* @param properties The object children (complex properties)
|
||||
*/
|
||||
public record ParsedObjectImpl(String className, Map<String, ParsedProperty> attributes,
|
||||
SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties) implements ParsedObject {
|
||||
SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties,
|
||||
SequencedCollection<ParsedObject> children) implements ParsedObject {
|
||||
|
||||
public ParsedObjectImpl {
|
||||
Objects.requireNonNull(className);
|
||||
attributes = Map.copyOf(attributes);
|
||||
properties = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link ParsedObjectImpl}
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
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<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object class
|
||||
*
|
||||
* @param className The object class
|
||||
* @return The builder
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property
|
||||
*
|
||||
* @param property The property
|
||||
* @param child The property element
|
||||
* @return The builder
|
||||
*/
|
||||
public Builder addProperty(final ParsedProperty property, final ParsedObject child) {
|
||||
final var sequence = properties.computeIfAbsent(property, k -> new ArrayList<>());
|
||||
sequence.add(child);
|
||||
properties.put(property, sequence);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the object
|
||||
*
|
||||
* @return The object
|
||||
*/
|
||||
public ParsedObjectImpl build() {
|
||||
return new ParsedObjectImpl(className, attributes, properties);
|
||||
}
|
||||
children = List.copyOf(children);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Objects;
|
||||
* @param name The property name
|
||||
* @param sourceType The property source type
|
||||
* @param value The property value
|
||||
* @param defines The property defines
|
||||
*/
|
||||
public record ParsedPropertyImpl(String name, String sourceType, String value) implements ParsedProperty {
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedReference;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedReference}
|
||||
*
|
||||
* @param attributes The reference properties
|
||||
*/
|
||||
public record ParsedReferenceImpl(Map<String, ParsedProperty> attributes) implements ParsedReference {
|
||||
|
||||
public ParsedReferenceImpl {
|
||||
attributes = Map.copyOf(attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedText;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedText}
|
||||
*
|
||||
* @param text The text
|
||||
*/
|
||||
public record ParsedTextImpl(String text) implements ParsedText {
|
||||
|
||||
/**
|
||||
* Instantiates a new parsed text
|
||||
*
|
||||
* @param text The text
|
||||
* @throws NullPointerException if the text is null
|
||||
*/
|
||||
public ParsedTextImpl {
|
||||
Objects.requireNonNull(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedValue;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ParsedValue}
|
||||
*
|
||||
* @param className The value class
|
||||
* @param attributes The value properties
|
||||
*/
|
||||
public record ParsedValueImpl(String className, Map<String, ParsedProperty> attributes) implements ParsedValue {
|
||||
|
||||
public ParsedValueImpl {
|
||||
Objects.requireNonNull(className);
|
||||
attributes = Map.copyOf(attributes);
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,14 @@ class TestClassesFinder {
|
||||
void testGetClassesCurrent() throws IOException {
|
||||
final var expected = Set.of(
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedConstantImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedCopyImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedFactoryImpl",
|
||||
"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");
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedPropertyImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedReferenceImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedTextImpl",
|
||||
"com.github.gtache.fxml.compiler.parsing.impl.TestParsedValueImpl");
|
||||
final var actual = ClassesFinder.getClasses("com.github.gtache.fxml.compiler.parsing.impl");
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationRequest;
|
||||
import com.github.gtache.fxml.compiler.impl.internal.GenerationProgress;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.SequencedCollection;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestGenerationProgress {
|
||||
|
||||
private final GenerationRequest request;
|
||||
private final Map<String, String> idToVariableName;
|
||||
private final Map<String, ParsedObject> idToObject;
|
||||
private final Map<String, AtomicInteger> variableNameCounters;
|
||||
private final SequencedCollection<String> controllerFactoryPostAction;
|
||||
private final StringBuilder sb;
|
||||
private final GenerationProgress progress;
|
||||
|
||||
TestGenerationProgress(@Mock final GenerationRequest request, @Mock final ParsedObject object) {
|
||||
this.request = Objects.requireNonNull(request);
|
||||
this.idToVariableName = new HashMap<>();
|
||||
idToVariableName.put("var1", "var2");
|
||||
this.idToObject = new HashMap<>();
|
||||
idToObject.put("var1", object);
|
||||
this.variableNameCounters = new HashMap<>();
|
||||
variableNameCounters.put("var", new AtomicInteger(0));
|
||||
this.controllerFactoryPostAction = new ArrayList<>();
|
||||
controllerFactoryPostAction.add("bla");
|
||||
this.sb = new StringBuilder("test");
|
||||
this.progress = new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, sb);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(request, progress.request());
|
||||
assertEquals(idToVariableName, progress.idToVariableName());
|
||||
assertEquals(variableNameCounters, progress.variableNameCounters());
|
||||
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
|
||||
assertEquals(sb, progress.stringBuilder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructorDoesntCopy() {
|
||||
idToVariableName.clear();
|
||||
assertEquals(idToVariableName, progress.idToVariableName());
|
||||
|
||||
idToObject.clear();
|
||||
assertEquals(idToObject, progress.idToObject());
|
||||
|
||||
variableNameCounters.clear();
|
||||
assertEquals(variableNameCounters, progress.variableNameCounters());
|
||||
|
||||
controllerFactoryPostAction.clear();
|
||||
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
|
||||
|
||||
sb.setLength(0);
|
||||
assertEquals(sb, progress.stringBuilder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanModify() {
|
||||
progress.idToVariableName().put("var3", "var4");
|
||||
assertEquals(idToVariableName, progress.idToVariableName());
|
||||
|
||||
progress.idToObject().put("var3", mock(ParsedObject.class));
|
||||
assertEquals(idToObject, progress.idToObject());
|
||||
|
||||
progress.variableNameCounters().put("var5", new AtomicInteger(0));
|
||||
assertEquals(variableNameCounters, progress.variableNameCounters());
|
||||
|
||||
progress.controllerFactoryPostAction().add("bla2");
|
||||
assertEquals(controllerFactoryPostAction, progress.controllerFactoryPostAction());
|
||||
|
||||
progress.stringBuilder().append("test2");
|
||||
assertEquals(sb, progress.stringBuilder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOtherConstructor() {
|
||||
final var progress2 = new GenerationProgress(request);
|
||||
assertEquals(request, progress2.request());
|
||||
assertEquals(Map.of(), progress2.idToVariableName());
|
||||
assertEquals(Map.of(), progress2.variableNameCounters());
|
||||
assertEquals(List.of(), progress2.controllerFactoryPostAction());
|
||||
assertEquals("", progress2.stringBuilder().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNextVariableName() {
|
||||
assertEquals("var0", progress.getNextVariableName("var"));
|
||||
assertEquals("var1", progress.getNextVariableName("var"));
|
||||
assertEquals("var2", progress.getNextVariableName("var"));
|
||||
assertEquals("bla0", progress.getNextVariableName("bla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(null, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, sb));
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, null, idToObject, variableNameCounters, controllerFactoryPostAction, sb));
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, null, variableNameCounters, controllerFactoryPostAction, sb));
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, null, controllerFactoryPostAction, sb));
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, null, sb));
|
||||
assertThrows(NullPointerException.class, () -> new GenerationProgress(request, idToVariableName, idToObject, variableNameCounters, controllerFactoryPostAction, null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.gtache.fxml.compiler.impl;
|
||||
package com.github.gtache.fxml.compiler.impl.internal;
|
||||
|
||||
import com.github.gtache.fxml.compiler.GenerationException;
|
||||
import com.github.gtache.fxml.compiler.impl.WholeConstructorArgs;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBox;
|
||||
@@ -110,4 +112,19 @@ class TestReflectionHelper {
|
||||
final var constructor = Arrays.stream(WholeConstructorArgs.class.getConstructors()).filter(c -> c.getParameterCount() == 3).findFirst().orElseThrow();
|
||||
assertThrows(IllegalStateException.class, () -> ReflectionHelper.getConstructorArgs(constructor));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDefaultPropertyClassNotFound() {
|
||||
assertThrows(GenerationException.class, () -> ReflectionHelper.getDefaultProperty("bla.bla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDefaultPropertyNoDefaultProperty() throws GenerationException {
|
||||
assertNull(ReflectionHelper.getDefaultProperty("java.lang.String"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDefaultProperty() throws GenerationException {
|
||||
assertEquals("items", ReflectionHelper.getDefaultProperty("javafx.scene.control.ListView"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedCopy;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
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.util.LinkedHashMap;
|
||||
import java.util.SequencedMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestParsedCopyImpl {
|
||||
|
||||
private final SequencedMap<String, ParsedProperty> properties;
|
||||
private final ParsedCopy copy;
|
||||
|
||||
TestParsedCopyImpl(@Mock final ParsedProperty property) {
|
||||
this.properties = new LinkedHashMap<>();
|
||||
this.properties.put("name", property);
|
||||
this.copy = new ParsedCopyImpl(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(properties, copy.attributes());
|
||||
assertEquals(ParsedCopy.class.getName(), copy.className());
|
||||
assertEquals(new LinkedHashMap<>(), copy.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalProperties = copy.attributes();
|
||||
properties.clear();
|
||||
assertEquals(originalProperties, copy.attributes());
|
||||
assertNotEquals(properties, copy.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = copy.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new TestParsedCopyImpl(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedFactory;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SequencedCollection;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestParsedFactoryImpl {
|
||||
|
||||
private final String className;
|
||||
private final Map<String, ParsedProperty> attributes;
|
||||
private final SequencedCollection<ParsedObject> arguments;
|
||||
private final SequencedCollection<ParsedObject> children;
|
||||
private final ParsedFactory factory;
|
||||
|
||||
TestParsedFactoryImpl(@Mock final ParsedObject object1, @Mock final ParsedObject object2, @Mock final ParsedDefine define) {
|
||||
this.className = "test";
|
||||
this.attributes = new HashMap<>(Map.of("fx:factory", new ParsedPropertyImpl("fx:factory", String.class.getName(), "value")));
|
||||
this.arguments = new ArrayList<>(List.of(object1, object2));
|
||||
this.children = new ArrayList<>(List.of(define));
|
||||
this.factory = new ParsedFactoryImpl(className, attributes, arguments, children);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(className, factory.className());
|
||||
assertEquals(attributes, factory.attributes());
|
||||
assertEquals(attributes.get("fx:factory").value(), factory.factory());
|
||||
assertEquals(arguments, factory.arguments());
|
||||
assertEquals(children, factory.children());
|
||||
assertEquals(Map.of(), factory.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalAttributes = factory.attributes();
|
||||
attributes.clear();
|
||||
assertEquals(originalAttributes, factory.attributes());
|
||||
assertNotEquals(attributes, factory.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyArguments() {
|
||||
final var originalArguments = factory.arguments();
|
||||
arguments.clear();
|
||||
assertEquals(originalArguments, factory.arguments());
|
||||
assertNotEquals(arguments, factory.arguments());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyDefines() {
|
||||
final var originalDefines = factory.children();
|
||||
children.clear();
|
||||
assertEquals(originalDefines, factory.children());
|
||||
assertNotEquals(children, factory.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = factory.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
|
||||
final var objectArguments = factory.arguments();
|
||||
assertThrows(UnsupportedOperationException.class, objectArguments::clear);
|
||||
|
||||
final var objectDefines = factory.children();
|
||||
assertThrows(UnsupportedOperationException.class, objectDefines::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedFactoryImpl(null, attributes, arguments, children));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedFactoryImpl(className, null, arguments, children));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedFactoryImpl(className, attributes, null, children));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedFactoryImpl(className, attributes, arguments, null));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedDefine;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -7,6 +8,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.SequencedCollection;
|
||||
@@ -19,48 +21,57 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
class TestParsedObjectImpl {
|
||||
|
||||
private final String clazz;
|
||||
private final SequencedMap<String, ParsedProperty> properties;
|
||||
private final SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> children;
|
||||
private final SequencedMap<String, ParsedProperty> attributes;
|
||||
private final SequencedMap<ParsedProperty, SequencedCollection<ParsedObject>> properties;
|
||||
private final SequencedCollection<ParsedObject> objects;
|
||||
private final ParsedObject parsedObject;
|
||||
|
||||
TestParsedObjectImpl(@Mock final ParsedProperty property, @Mock final ParsedObject object) {
|
||||
TestParsedObjectImpl(@Mock final ParsedProperty property, @Mock final ParsedObject object, @Mock final ParsedDefine define) {
|
||||
this.clazz = Object.class.getName();
|
||||
this.attributes = new LinkedHashMap<>();
|
||||
this.attributes.put("name", property);
|
||||
this.properties = new LinkedHashMap<>();
|
||||
this.properties.put("name", property);
|
||||
this.children = new LinkedHashMap<>();
|
||||
this.children.put(property, List.of(object));
|
||||
this.parsedObject = new ParsedObjectImpl(clazz, properties, children);
|
||||
this.properties.put(property, List.of(object));
|
||||
this.objects = new ArrayList<>(List.of(define));
|
||||
this.parsedObject = new ParsedObjectImpl(clazz, attributes, properties, objects);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(clazz, parsedObject.className());
|
||||
assertEquals(properties, parsedObject.attributes());
|
||||
assertEquals(children, parsedObject.properties());
|
||||
assertEquals(attributes, parsedObject.attributes());
|
||||
assertEquals(properties, parsedObject.properties());
|
||||
assertEquals(objects, parsedObject.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalProperties = parsedObject.attributes();
|
||||
final var originalChildren = parsedObject.properties();
|
||||
final var originalAttributes = parsedObject.attributes();
|
||||
final var originalProperties = parsedObject.properties();
|
||||
final var originalChildren = parsedObject.children();
|
||||
attributes.clear();
|
||||
properties.clear();
|
||||
children.clear();
|
||||
assertEquals(originalProperties, parsedObject.attributes());
|
||||
assertEquals(originalChildren, parsedObject.properties());
|
||||
objects.clear();
|
||||
assertEquals(originalAttributes, parsedObject.attributes());
|
||||
assertEquals(originalProperties, parsedObject.properties());
|
||||
assertEquals(originalChildren, parsedObject.children());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = parsedObject.attributes();
|
||||
final var objectChildren = parsedObject.properties();
|
||||
final var objectAttributes = parsedObject.attributes();
|
||||
final var objectProperties = parsedObject.properties();
|
||||
final var objectChildren = parsedObject.children();
|
||||
assertThrows(UnsupportedOperationException.class, objectAttributes::clear);
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
assertThrows(UnsupportedOperationException.class, objectChildren::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(null, properties, children));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, null, children));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, properties, null));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(null, attributes, properties, objects));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, null, properties, objects));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, attributes, null, objects));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedObjectImpl(clazz, attributes, properties, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedObject;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.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 TestParsedObjectImplBuilder {
|
||||
|
||||
private final String clazz1;
|
||||
private final String clazz2;
|
||||
private final ParsedProperty property1;
|
||||
private final ParsedProperty property2;
|
||||
private final ParsedObject object1;
|
||||
private final ParsedObject object2;
|
||||
private final ParsedObjectImpl.Builder builder;
|
||||
|
||||
TestParsedObjectImplBuilder(@Mock final ParsedProperty property1, @Mock final ParsedProperty property2,
|
||||
@Mock final ParsedObject object1, @Mock final ParsedObject object2) {
|
||||
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);
|
||||
this.object2 = Objects.requireNonNull(object2);
|
||||
this.builder = new ParsedObjectImpl.Builder();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
when(property1.name()).thenReturn("property1");
|
||||
when(property2.name()).thenReturn("property2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildNullClass() {
|
||||
assertThrows(NullPointerException.class, builder::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClassName() {
|
||||
builder.className(clazz1);
|
||||
final var built = builder.build();
|
||||
assertEquals(clazz1, built.className());
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(), built.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOverwriteClassName() {
|
||||
builder.className(clazz1);
|
||||
builder.className(clazz2);
|
||||
final var built = builder.build();
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddProperty() {
|
||||
builder.className(clazz1);
|
||||
builder.addProperty(property1, object1);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(property1, List.of(object1)), built.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMultipleProperties() {
|
||||
builder.className(clazz1);
|
||||
builder.addProperty(property1, object1);
|
||||
builder.addProperty(property2, object2);
|
||||
final var built = builder.build();
|
||||
assertEquals(Map.of(), built.attributes());
|
||||
assertEquals(Map.of(property1, List.of(object1), property2, List.of(object2)), built.properties());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedReference;
|
||||
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.util.LinkedHashMap;
|
||||
import java.util.SequencedMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TestParsedReferenceImpl {
|
||||
|
||||
private final SequencedMap<String, ParsedProperty> properties;
|
||||
private final ParsedReference reference;
|
||||
|
||||
TestParsedReferenceImpl(@Mock final ParsedProperty property) {
|
||||
this.properties = new LinkedHashMap<>();
|
||||
this.properties.put("name", property);
|
||||
this.reference = new ParsedReferenceImpl(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(properties, reference.attributes());
|
||||
assertEquals(ParsedReference.class.getName(), reference.className());
|
||||
assertEquals(new LinkedHashMap<>(), reference.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalProperties = reference.attributes();
|
||||
properties.clear();
|
||||
assertEquals(originalProperties, reference.attributes());
|
||||
assertNotEquals(properties, reference.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = reference.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedReferenceImpl(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedText;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class TestParsedTextImpl {
|
||||
|
||||
private final String text;
|
||||
private final ParsedText parsedText;
|
||||
|
||||
TestParsedTextImpl() {
|
||||
this.text = "text";
|
||||
this.parsedText = new ParsedTextImpl(text);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testText() {
|
||||
assertEquals(text, parsedText.text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedTextImpl(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.gtache.fxml.compiler.parsing.impl;
|
||||
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedProperty;
|
||||
import com.github.gtache.fxml.compiler.parsing.ParsedValue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TestParsedValueImpl {
|
||||
|
||||
private final String className;
|
||||
private final Map<String, ParsedProperty> attributes;
|
||||
private final ParsedValue value;
|
||||
|
||||
TestParsedValueImpl() {
|
||||
this.className = "test";
|
||||
this.attributes = new HashMap<>(Map.of("fx:value", new ParsedPropertyImpl("fx:value", String.class.getName(), "value")));
|
||||
this.value = new ParsedValueImpl(className, attributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetters() {
|
||||
assertEquals(className, value.className());
|
||||
assertEquals(attributes, value.attributes());
|
||||
assertEquals(attributes.get("fx:value").value(), value.value());
|
||||
assertEquals(Map.of(), value.properties());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyMap() {
|
||||
final var originalAttributes = value.attributes();
|
||||
attributes.clear();
|
||||
assertEquals(originalAttributes, value.attributes());
|
||||
assertNotEquals(attributes, value.attributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmodifiable() {
|
||||
final var objectProperties = value.attributes();
|
||||
assertThrows(UnsupportedOperationException.class, objectProperties::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIllegal() {
|
||||
assertThrows(NullPointerException.class, () -> new ParsedValueImpl(null, attributes));
|
||||
assertThrows(NullPointerException.class, () -> new ParsedValueImpl(className, null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user