Refactoring for more flexible class hierarchy.
This commit is contained in:
parent
6af7e4fd82
commit
6788b10068
@ -0,0 +1,49 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import groowt.view.component.TemplateSource.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public abstract class AbstractComponentTemplateCompiler implements ComponentTemplateCompiler {
|
||||||
|
|
||||||
|
protected abstract ComponentTemplate compile(
|
||||||
|
TemplateSource templateSource,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader actualSource
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, TemplateSource source) {
|
||||||
|
return switch (source) {
|
||||||
|
case FileSource(File file) -> {
|
||||||
|
try {
|
||||||
|
yield this.compile(source, forClass, new FileReader(file));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case StringSource(String rawSource) ->
|
||||||
|
this.compile(source, forClass, new StringReader(rawSource));
|
||||||
|
case InputStreamSource(InputStream inputStream) ->
|
||||||
|
this.compile(source, forClass, new InputStreamReader(inputStream));
|
||||||
|
case URISource(URI uri) -> {
|
||||||
|
try {
|
||||||
|
yield this.compile(source, forClass, new InputStreamReader(uri.toURL().openStream()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case URLSource(URL url) -> {
|
||||||
|
try {
|
||||||
|
yield this.compile(source, forClass, new InputStreamReader(url.openStream()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ReaderSource(Reader reader) -> this.compile(source, forClass, reader);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,9 +5,11 @@ import groovy.lang.Closure;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class AbstractViewComponent implements ViewComponent {
|
public abstract class AbstractViewComponent implements ViewComponent {
|
||||||
|
|
||||||
|
private ComponentContext context;
|
||||||
private ComponentTemplate template;
|
private ComponentTemplate template;
|
||||||
|
|
||||||
public AbstractViewComponent() {}
|
public AbstractViewComponent() {}
|
||||||
@ -24,6 +26,27 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected AbstractViewComponent(TemplateSource source, Function<String, ComponentTemplateCompiler> getCompiler) {
|
||||||
|
final Class<? extends AbstractViewComponent> selfClass = this.getSelfClass();
|
||||||
|
this.template = getCompiler.apply(selfClass.getPackageName()).compile(selfClass, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractViewComponent(TemplateSource source, ComponentTemplateCompiler compiler) {
|
||||||
|
this.template = compiler.compile(this.getSelfClass(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Class<? extends AbstractViewComponent> getSelfClass();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(ComponentContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentContext getContext() {
|
||||||
|
return Objects.requireNonNull(this.context);
|
||||||
|
}
|
||||||
|
|
||||||
protected ComponentTemplate getTemplate() {
|
protected ComponentTemplate getTemplate() {
|
||||||
return Objects.requireNonNull(template);
|
return Objects.requireNonNull(template);
|
||||||
}
|
}
|
||||||
@ -40,6 +63,11 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
|||||||
this.getContext().afterComponentRender(this);
|
this.getContext().afterComponentRender(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @implSpec If overriding, <strong>please</strong> call
|
||||||
|
* {@link #beforeRender()}and {@link #afterRender()} before
|
||||||
|
* and after the actual rendering is done, respectively.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void renderTo(Writer out) throws IOException {
|
public void renderTo(Writer out) throws IOException {
|
||||||
final Closure<?> closure = this.template.getRenderer();
|
final Closure<?> closure = this.template.getRenderer();
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public abstract class CachingComponentTemplateCompiler implements ComponentTemplateCompiler {
|
public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
|
||||||
|
|
||||||
private final Map<Class<? extends ViewComponent>, ComponentTemplate> cache = new HashMap<>();
|
private final Map<Class<? extends ViewComponent>, ComponentTemplate> cache = new HashMap<>();
|
||||||
|
|
||||||
protected final void putInCache(Class<? extends ViewComponent> forClass, ComponentTemplate template) {
|
@Override
|
||||||
this.cache.put(forClass, template);
|
protected final ComponentTemplate compile(
|
||||||
}
|
TemplateSource source,
|
||||||
|
|
||||||
protected final ComponentTemplate getFromCache(Class<? extends ViewComponent> forClass) {
|
|
||||||
return Objects.requireNonNull(this.cache.get(forClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final ComponentTemplate getFromCacheOrElse(
|
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Supplier<? extends ComponentTemplate> onEmpty
|
Reader sourceReader
|
||||||
) {
|
) {
|
||||||
return this.cache.computeIfAbsent(forClass, ignored -> onEmpty.get());
|
return this.cache.computeIfAbsent(forClass, ignored -> this.doCompile(source, forClass, sourceReader));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract ComponentTemplate doCompile(
|
||||||
|
TemplateSource source,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader sourceReader
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,50 +4,79 @@ import groovy.lang.Closure;
|
|||||||
|
|
||||||
import static groowt.view.component.ComponentFactoryUtil.flatten;
|
import static groowt.view.component.ComponentFactoryUtil.flatten;
|
||||||
|
|
||||||
final class ClosureComponentFactory<T extends ViewComponent> extends ComponentFactoryBase<T> {
|
final class ClosureComponentFactory<T extends ViewComponent> implements ComponentFactory<T> {
|
||||||
|
|
||||||
|
private enum Type {
|
||||||
|
ALL,
|
||||||
|
NAME_AND_CONTEXT, NAME_AND_ARGS, CONTEXT_AND_ARGS,
|
||||||
|
NAME_ONLY, CONTEXT_ONLY, ARGS_ONLY,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
||||||
private final Closure<T> closure;
|
private final Closure<T> closure;
|
||||||
private final Class<?> firstParamType;
|
private final Type type;
|
||||||
|
|
||||||
public ClosureComponentFactory(Closure<T> closure) {
|
@SuppressWarnings("unchecked")
|
||||||
this.closure = closure;
|
public ClosureComponentFactory(Closure<? extends T> closure) {
|
||||||
if (this.closure.getParameterTypes().length < 2) {
|
this.closure = (Closure<T>) closure;
|
||||||
throw new IllegalArgumentException(
|
final var paramTypes = this.closure.getParameterTypes();
|
||||||
"Closures for " + getClass().getName() + " require at least two parameters"
|
if (paramTypes.length == 0) {
|
||||||
);
|
this.type = Type.NONE;
|
||||||
}
|
} else if (paramTypes.length == 1) {
|
||||||
this.firstParamType = this.closure.getParameterTypes()[0];
|
final var paramType = paramTypes[0];
|
||||||
if (this.firstParamType != Object.class
|
if (paramType == String.class || paramType == Class.class) {
|
||||||
&& !(this.firstParamType == String.class || this.firstParamType == Class.class)) {
|
this.type = Type.NAME_ONLY;
|
||||||
throw new IllegalArgumentException(
|
} else if (ComponentContext.class.isAssignableFrom(paramType)) {
|
||||||
"The first closure parameter must be any of type Object (i.e, dynamic), String, or Class"
|
this.type = Type.CONTEXT_ONLY;
|
||||||
);
|
} else {
|
||||||
}
|
this.type = Type.ARGS_ONLY;
|
||||||
final var secondParamType = this.closure.getParameterTypes()[1];
|
}
|
||||||
if (secondParamType != Object.class && !ComponentContext.class.isAssignableFrom(secondParamType)) {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
final var firstParamType = paramTypes[0];
|
||||||
"The second closure parameter must be of type Object (i.e., dynamic) or " +
|
final var secondParamType = paramTypes[1];
|
||||||
"ComponentContext or a subclass thereof."
|
if (firstParamType == String.class || firstParamType == Class.class) {
|
||||||
);
|
if (ComponentContext.class.isAssignableFrom(secondParamType)) {
|
||||||
|
if (paramTypes.length > 2) {
|
||||||
|
this.type = Type.ALL;
|
||||||
|
} else {
|
||||||
|
this.type = Type.NAME_AND_CONTEXT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.type = Type.NAME_AND_ARGS;
|
||||||
|
}
|
||||||
|
} else if (ComponentContext.class.isAssignableFrom(firstParamType)) {
|
||||||
|
this.type = Type.CONTEXT_AND_ARGS;
|
||||||
|
} else {
|
||||||
|
this.type = Type.ARGS_ONLY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private T flatCall(Object... args) {
|
||||||
|
return this.closure.call(flatten(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private T objTypeCreate(Object type, ComponentContext componentContext, Object... args) {
|
||||||
|
return switch (this.type) {
|
||||||
|
case ALL -> this.flatCall(type, componentContext, args);
|
||||||
|
case NAME_AND_CONTEXT -> this.closure.call(type, componentContext);
|
||||||
|
case NAME_AND_ARGS -> this.flatCall(type, args);
|
||||||
|
case CONTEXT_AND_ARGS -> this.flatCall(componentContext, args);
|
||||||
|
case NAME_ONLY -> this.closure.call(type);
|
||||||
|
case CONTEXT_ONLY -> this.closure.call(componentContext);
|
||||||
|
case ARGS_ONLY -> this.closure.call(args);
|
||||||
|
case NONE -> this.closure.call();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T create(String type, ComponentContext componentContext, Object... args) {
|
public T create(String type, ComponentContext componentContext, Object... args) {
|
||||||
if (this.firstParamType != Object.class && this.firstParamType != String.class) {
|
return this.objTypeCreate(type, componentContext, args);
|
||||||
throw new IllegalArgumentException("Cannot call this ClosureComponentFactory " +
|
|
||||||
"with a String component type argument.");
|
|
||||||
}
|
|
||||||
return this.closure.call(flatten(type, componentContext, args));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
public T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
if (this.firstParamType != Object.class && this.firstParamType != Class.class) {
|
return this.objTypeCreate(type, componentContext, args);
|
||||||
throw new IllegalArgumentException("Cannot call this ClosureComponentFactory " +
|
|
||||||
"with a Class component type argument.");
|
|
||||||
}
|
|
||||||
return this.closure.call(flatten(type, componentContext, args));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,9 @@ public class ComponentCreateException extends RuntimeException {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return "Exception in " + this.template.getClass() + " while creating " + this.componentType.getClass() +
|
return "Exception in " + this.template.getClass().getName() + " while creating "
|
||||||
" at line " + this.line + ", column " + this.column + ".";
|
+ this.componentType.getClass().getName() + " at line " + this.line
|
||||||
|
+ ", column " + this.column + ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,13 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groovy.lang.GroovyObject;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ComponentFactory<T extends ViewComponent> {
|
public interface ComponentFactory<T extends ViewComponent> {
|
||||||
|
|
||||||
/**
|
static <T extends ViewComponent> ComponentFactory<T> ofClosure(Closure<? extends T> closure) {
|
||||||
* @param closure A closure with the following signature:
|
|
||||||
* <p>
|
|
||||||
* {@code Object componentType, ComponentContext context, ... -> T }
|
|
||||||
* <p>
|
|
||||||
* where '{@code ...}' represents any additional parameters (or none).
|
|
||||||
* <p>
|
|
||||||
* The first two parameters are not optional and must be present
|
|
||||||
* or else this method will not work. The first parameter may be either
|
|
||||||
* a {@link String} or a {@link Class}.
|
|
||||||
*
|
|
||||||
* @return A factory which will create type {@code T}.
|
|
||||||
* @param <T> The desired {@link ViewComponent} type.
|
|
||||||
*/
|
|
||||||
static <T extends ViewComponent> ComponentFactory<T> ofClosure(Closure<T> closure) {
|
|
||||||
return new ClosureComponentFactory<>(closure);
|
return new ClosureComponentFactory<>(closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
|
||||||
public interface ComponentScope {
|
public interface ComponentScope {
|
||||||
|
|
||||||
void add(String name, ComponentFactory<?> factory);
|
void add(String name, ComponentFactory<?> factory);
|
||||||
@ -24,11 +26,6 @@ public interface ComponentScope {
|
|||||||
return (ComponentFactory<T>) this.get(clazz.getName());
|
return (ComponentFactory<T>) this.get(clazz.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
default <T extends ViewComponent> ComponentFactory<T> getAs(String name, Class<T> viewComponentType) {
|
|
||||||
return (ComponentFactory<T>) this.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
default void remove(Class<? extends ViewComponent> clazz) {
|
default void remove(Class<? extends ViewComponent> clazz) {
|
||||||
this.remove(clazz.getName());
|
this.remove(clazz.getName());
|
||||||
}
|
}
|
||||||
@ -38,4 +35,12 @@ public interface ComponentScope {
|
|||||||
return (ComponentFactory<T>) this.factoryMissing(clazz.getName());
|
return (ComponentFactory<T>) this.factoryMissing(clazz.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void add(String name, Closure<? extends ViewComponent> closure) {
|
||||||
|
this.add(name, ComponentFactory.ofClosure(closure));
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T extends ViewComponent> void add(Class<T> type, Closure<? extends T> closure) {
|
||||||
|
this.add(type, ComponentFactory.ofClosure(closure));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public interface ComponentTemplateCompiler {
|
public interface ComponentTemplateCompiler {
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, File templateFile);
|
ComponentTemplate compile(Class<? extends ViewComponent> forClass, TemplateSource source);
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, String template);
|
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, URI templateURI);
|
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, URL templateURL);
|
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, InputStream inputStream);
|
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, Reader reader);
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public sealed interface TemplateSource {
|
||||||
|
|
||||||
|
static TemplateSource of(String template) {
|
||||||
|
return new StringSource(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TemplateSource of(File templateFile) {
|
||||||
|
return new FileSource(templateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TemplateSource of(URI templateURI) {
|
||||||
|
return new URISource(templateURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TemplateSource of(URL url) {
|
||||||
|
return new URLSource(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TemplateSource of(InputStream templateInputStream) {
|
||||||
|
return new InputStreamSource(templateInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TemplateSource of(Reader templateReader) {
|
||||||
|
return new ReaderSource(templateReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resourceName An <strong>absolute</strong> path resource name.
|
||||||
|
* @return A template source
|
||||||
|
*/
|
||||||
|
static TemplateSource fromResource(String resourceName) {
|
||||||
|
return of(TemplateSource.class.getClassLoader().getResource(resourceName));
|
||||||
|
}
|
||||||
|
|
||||||
|
record StringSource(String template) implements TemplateSource {}
|
||||||
|
|
||||||
|
record FileSource(File templateFile) implements TemplateSource {}
|
||||||
|
|
||||||
|
record URISource(URI templateURI) implements TemplateSource {}
|
||||||
|
|
||||||
|
record URLSource(URL templateURL) implements TemplateSource {}
|
||||||
|
|
||||||
|
record InputStreamSource(InputStream templateInputStream) implements TemplateSource {}
|
||||||
|
|
||||||
|
record ReaderSource(Reader templateReader) implements TemplateSource {}
|
||||||
|
|
||||||
|
}
|
@ -7,6 +7,7 @@ import java.io.IOException;
|
|||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
public interface View {
|
public interface View {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.AbstractViewComponent
|
||||||
|
import groowt.view.component.ComponentTemplate
|
||||||
|
import groowt.view.component.TemplateSource
|
||||||
|
|
||||||
|
class DefaultWebViewComponent extends AbstractWebViewComponent {
|
||||||
|
|
||||||
|
DefaultWebViewComponent() {}
|
||||||
|
|
||||||
|
DefaultWebViewComponent(ComponentTemplate template) {
|
||||||
|
super(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultWebViewComponent(Class<? extends ComponentTemplate> templateType) {
|
||||||
|
super(templateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultWebViewComponent(TemplateSource source) {
|
||||||
|
super(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultWebViewComponent(TemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
||||||
|
super(source, compiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience constructor which creates a {@link TemplateSource}
|
||||||
|
* from the given {@code source} parameter and passes it to super. See
|
||||||
|
* {@link TemplateSource} for possible types.
|
||||||
|
*
|
||||||
|
* @param source the object passed to {@link TemplateSource#of}
|
||||||
|
*
|
||||||
|
* @see TemplateSource
|
||||||
|
*/
|
||||||
|
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||||
|
DefaultWebViewComponent(Object source) {
|
||||||
|
super(TemplateSource.of(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final Class<? extends AbstractViewComponent> getSelfClass() {
|
||||||
|
this.class
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,122 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
|
||||||
import groowt.view.component.AbstractViewComponent;
|
|
||||||
import groowt.view.component.ComponentContext;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.web.WebViewTemplateComponentSource.*;
|
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class DefaultWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
|
||||||
|
|
||||||
private static ComponentTemplate getComponentTemplate(
|
|
||||||
Class<? extends DefaultWebViewComponent> selfType,
|
|
||||||
WebViewTemplateComponentSource source,
|
|
||||||
@Nullable GroovyClassLoader groovyClassLoader
|
|
||||||
) {
|
|
||||||
final var compiler = new DefaultWebComponentTemplateCompiler(
|
|
||||||
CompilerConfiguration.DEFAULT,
|
|
||||||
selfType.getPackageName()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (groovyClassLoader != null) {
|
|
||||||
compiler.setGroovyClassLoader(groovyClassLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (source) {
|
|
||||||
case FileSource(File f) -> compiler.compile(selfType, f);
|
|
||||||
case InputStreamSource(InputStream inputStream) -> compiler.compile(selfType, inputStream);
|
|
||||||
case ReaderSource(Reader r) -> compiler.compile(selfType, r);
|
|
||||||
case StringSource(String s) -> compiler.compile(selfType, s);
|
|
||||||
case URISource(URI uri) -> compiler.compile(selfType, uri);
|
|
||||||
case URLSource(URL url) -> compiler.compile(selfType, url);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private ComponentContext context;
|
|
||||||
private List<WebViewChildRenderer> children;
|
|
||||||
|
|
||||||
public DefaultWebViewComponent() {}
|
|
||||||
|
|
||||||
public DefaultWebViewComponent(ComponentTemplate template) {
|
|
||||||
super(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultWebViewComponent(Class<? extends ComponentTemplate> templateType) {
|
|
||||||
super(templateType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultWebViewComponent(WebViewTemplateComponentSource source) {
|
|
||||||
this.setTemplate(getComponentTemplate(this.getClass(), source, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultWebViewComponent(WebViewTemplateComponentSource source, GroovyClassLoader groovyClassLoader) {
|
|
||||||
this.setTemplate(getComponentTemplate(this.getClass(), source, groovyClassLoader));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(ComponentContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentContext getContext() {
|
|
||||||
return Objects.requireNonNull(this.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WebViewChildRenderer> getChildRenderers() {
|
|
||||||
if (this.children == null) {
|
|
||||||
this.children = new ArrayList<>();
|
|
||||||
}
|
|
||||||
return this.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChildren() {
|
|
||||||
return !this.getChildRenderers().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
|
||||||
this.children = children;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderChildren() {
|
|
||||||
for (final var childRenderer : this.getChildRenderers()) {
|
|
||||||
try {
|
|
||||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
|
||||||
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
|
||||||
}
|
|
||||||
childRenderer.render(this);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ChildRenderException(e);
|
|
||||||
} finally {
|
|
||||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
|
||||||
this.getContext().afterComponentRender(childComponentRenderer.getComponent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderTo(Writer out) throws IOException {
|
|
||||||
final var webWriter = new WebViewComponentWriter(out);
|
|
||||||
final Closure<?> renderer = this.getTemplate().getRenderer();
|
|
||||||
renderer.setDelegate(this);
|
|
||||||
renderer.setResolveStrategy(Closure.DELEGATE_FIRST);
|
|
||||||
renderer.call(this.getContext(), webWriter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,22 +4,23 @@ import groowt.view.component.ComponentScope
|
|||||||
import groowt.view.component.DefaultComponentContext
|
import groowt.view.component.DefaultComponentContext
|
||||||
import groowt.view.component.ViewComponent
|
import groowt.view.component.ViewComponent
|
||||||
import groowt.view.web.lib.Fragment
|
import groowt.view.web.lib.Fragment
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollector
|
import groowt.view.web.runtime.DefaultWebViewComponentChildCollection
|
||||||
import org.jetbrains.annotations.ApiStatus
|
import org.jetbrains.annotations.ApiStatus
|
||||||
|
|
||||||
class DefaultWebViewComponentContext extends DefaultComponentContext {
|
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ComponentScope getNewDefaultScope() {
|
protected ComponentScope getNewDefaultScope() {
|
||||||
new WebViewScope()
|
new WebViewScope()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
ViewComponent createFragment(Closure<?> childCollector) {
|
ViewComponent createFragment(Closure<?> childCollector) {
|
||||||
def collector = new WebViewComponentChildCollector()
|
def childCollection = new DefaultWebViewComponentChildCollection()
|
||||||
childCollector.call(collector)
|
childCollector.call(childCollection)
|
||||||
def fragment = new Fragment()
|
def fragment = new Fragment()
|
||||||
fragment.childRenderers = collector.children
|
fragment.childRenderers = childCollection.children
|
||||||
fragment
|
fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
import groowt.view.component.ComponentFactory
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
final class WebViewComponentFactories {
|
||||||
|
|
||||||
|
static <T extends WebViewComponent> ComponentFactory<T> withAttr(
|
||||||
|
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
|
||||||
|
Closure<T> closure
|
||||||
|
) {
|
||||||
|
ComponentFactory.ofClosure { Map<String, Object> attr -> closure(attr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends WebViewComponent> ComponentFactory<T> withAttr(Function<Map<String, Object>, T> tFunction) {
|
||||||
|
ComponentFactory.ofClosure { Map<String, Object> attr -> tFunction.apply(attr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebViewComponentFactories() {}
|
||||||
|
|
||||||
|
}
|
@ -2,15 +2,14 @@ package groowt.view.web
|
|||||||
|
|
||||||
import groowt.view.component.ComponentFactory
|
import groowt.view.component.ComponentFactory
|
||||||
import groowt.view.component.DefaultComponentScope
|
import groowt.view.component.DefaultComponentScope
|
||||||
|
import groowt.view.web.lib.Echo
|
||||||
import groowt.view.web.lib.Echo.EchoFactory
|
import groowt.view.web.lib.Echo.EchoFactory
|
||||||
|
|
||||||
class WebViewScope extends DefaultComponentScope {
|
class WebViewScope extends DefaultComponentScope {
|
||||||
|
|
||||||
private final EchoFactory echoFactory = new EchoFactory()
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ComponentFactory factoryMissing(String typeName) {
|
ComponentFactory factoryMissing(String typeName) {
|
||||||
echoFactory
|
Echo.FACTORY
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,10 @@ public abstract class DelegatingWebViewComponent extends DefaultWebViewComponent
|
|||||||
protected abstract View getDelegate();
|
protected abstract View getDelegate();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderTo(Writer out) throws IOException {
|
public final void renderTo(Writer out) throws IOException {
|
||||||
|
this.beforeRender();
|
||||||
this.getDelegate().renderTo(out);
|
this.getDelegate().renderTo(out);
|
||||||
|
this.afterRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import groowt.view.StandardGStringTemplateView
|
|
||||||
import groowt.view.View
|
import groowt.view.View
|
||||||
import groowt.view.component.ComponentContext
|
import groowt.view.component.ComponentContext
|
||||||
import groowt.view.component.ComponentFactory
|
import groowt.view.component.ComponentFactory
|
||||||
|
import groowt.view.component.ComponentRenderException
|
||||||
import groowt.view.web.WebViewChildComponentRenderer
|
import groowt.view.web.WebViewChildComponentRenderer
|
||||||
|
|
||||||
class Echo extends DelegatingWebViewComponent {
|
class Echo extends DelegatingWebViewComponent {
|
||||||
|
|
||||||
static final class EchoFactory implements ComponentFactory<Echo> {
|
static final ComponentFactory<Echo> FACTORY = new EchoFactory()
|
||||||
|
|
||||||
|
private static final class EchoFactory implements ComponentFactory<Echo> {
|
||||||
|
|
||||||
|
Echo doCreate(String typeName) {
|
||||||
|
doCreate(typeName, [:], true)
|
||||||
|
}
|
||||||
|
|
||||||
Echo doCreate(String typeName, boolean selfClose) {
|
Echo doCreate(String typeName, boolean selfClose) {
|
||||||
doCreate(typeName, [:], selfClose)
|
doCreate(typeName, [:], selfClose)
|
||||||
@ -19,8 +25,7 @@ class Echo extends DelegatingWebViewComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Echo doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
Echo doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
||||||
def echo = new Echo(attr, typeName, selfClose)
|
new Echo(attr, typeName, selfClose)
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Echo doCreate(
|
Echo doCreate(
|
||||||
@ -56,26 +61,43 @@ class Echo extends DelegatingWebViewComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getDelegate() {
|
protected View getDelegate() {
|
||||||
return new StandardGStringTemplateView(
|
if (this.selfClose && this.hasChildren()) {
|
||||||
src: Echo.getResource('EchoTemplate.gst'),
|
throw new ComponentRenderException('Cannot have selfClose set to true and have children.')
|
||||||
parent: this
|
}
|
||||||
)
|
return {
|
||||||
|
it << '<'
|
||||||
|
it << this.name
|
||||||
|
if (!this.attr.isEmpty()) {
|
||||||
|
it << ' '
|
||||||
|
formatAttr(it)
|
||||||
|
}
|
||||||
|
if (this.selfClose) {
|
||||||
|
it << ' /'
|
||||||
|
}
|
||||||
|
it << '>'
|
||||||
|
if (this.hasChildren()) {
|
||||||
|
this.renderChildren() // TODO: fix this
|
||||||
|
}
|
||||||
|
if (this.hasChildren() || !this.selfClose) {
|
||||||
|
it << '</'
|
||||||
|
it << this.name
|
||||||
|
it << '>'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatAttr() {
|
protected void formatAttr(Writer writer) {
|
||||||
def sb = new StringBuilder()
|
|
||||||
def iter = this.attr.iterator()
|
def iter = this.attr.iterator()
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
def entry = iter.next()
|
def entry = iter.next()
|
||||||
sb << entry.key
|
writer << entry.key
|
||||||
sb << '="'
|
writer << '="'
|
||||||
sb << entry.value
|
writer << entry.value
|
||||||
sb << '"'
|
writer << '"'
|
||||||
if (iter.hasNext()) {
|
if (iter.hasNext()) {
|
||||||
sb << ' '
|
writer << ' '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package groowt.view.web.lib
|
|||||||
|
|
||||||
import groowt.view.web.DefaultWebViewComponent
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
|
|
||||||
class Fragment extends DefaultWebViewComponent {
|
final class Fragment extends DefaultWebViewComponent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void renderTo(Writer out) throws IOException {
|
void renderTo(Writer out) throws IOException {
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package groowt.view.web;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.AbstractViewComponent;
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.TemplateSource;
|
||||||
|
import groowt.view.web.runtime.DefaultWebViewComponentWriter;
|
||||||
|
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
||||||
|
|
||||||
|
private List<WebViewChildRenderer> childRenderers;
|
||||||
|
|
||||||
|
public AbstractWebViewComponent() {}
|
||||||
|
|
||||||
|
public AbstractWebViewComponent(ComponentTemplate template) {
|
||||||
|
super(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractWebViewComponent(Class<? extends ComponentTemplate> templateClass) {
|
||||||
|
super(templateClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractWebViewComponent(TemplateSource source) {
|
||||||
|
super(source, packageName -> new DefaultWebViewComponentTemplateCompiler(
|
||||||
|
CompilerConfiguration.DEFAULT,
|
||||||
|
packageName
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractWebViewComponent(TemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
||||||
|
super(source, compiler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<WebViewChildRenderer> getChildRenderers() {
|
||||||
|
if (this.childRenderers == null) {
|
||||||
|
this.childRenderers = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return this.childRenderers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasChildren() {
|
||||||
|
return !this.getChildRenderers().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
||||||
|
this.childRenderers = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderChildren() {
|
||||||
|
for (final var childRenderer : this.getChildRenderers()) {
|
||||||
|
try {
|
||||||
|
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||||
|
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
||||||
|
}
|
||||||
|
childRenderer.render(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ChildRenderException(e);
|
||||||
|
} finally {
|
||||||
|
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||||
|
this.getContext().afterComponentRender(childComponentRenderer.getComponent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderTo(Writer out) throws IOException {
|
||||||
|
final WebViewComponentWriter webWriter = new DefaultWebViewComponentWriter(out);
|
||||||
|
final Closure<?> renderer = this.getTemplate().getRenderer();
|
||||||
|
renderer.setDelegate(this);
|
||||||
|
renderer.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||||
|
this.beforeRender();
|
||||||
|
renderer.call(this.getContext(), webWriter);
|
||||||
|
this.afterRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.view.component.CachingComponentTemplateCompiler;
|
import groowt.view.component.*;
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ComponentTemplateCreateException;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.web.antlr.CompilationUnitParseResult;
|
import groowt.view.web.antlr.CompilationUnitParseResult;
|
||||||
import groowt.view.web.antlr.ParserUtil;
|
import groowt.view.web.antlr.ParserUtil;
|
||||||
import groowt.view.web.antlr.TokenList;
|
import groowt.view.web.antlr.TokenList;
|
||||||
@ -21,15 +18,14 @@ import org.codehaus.groovy.tools.GroovyClass;
|
|||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplateCompiler {
|
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
||||||
|
implements WebViewComponentTemplateCompiler {
|
||||||
|
|
||||||
private final CompilerConfiguration configuration;
|
private final CompilerConfiguration configuration;
|
||||||
private final String defaultPackageName;
|
private final String defaultPackageName;
|
||||||
@ -37,15 +33,12 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
|
|
||||||
private GroovyClassLoader groovyClassLoader;
|
private GroovyClassLoader groovyClassLoader;
|
||||||
|
|
||||||
public DefaultWebComponentTemplateCompiler(
|
public DefaultWebViewComponentTemplateCompiler(CompilerConfiguration configuration, String defaultPackageName) {
|
||||||
CompilerConfiguration configuration,
|
|
||||||
String defaultPackageName
|
|
||||||
) {
|
|
||||||
this(configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
this(configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public DefaultWebComponentTemplateCompiler(
|
public DefaultWebViewComponentTemplateCompiler(
|
||||||
CompilerConfiguration configuration,
|
CompilerConfiguration configuration,
|
||||||
String defaultPackageName,
|
String defaultPackageName,
|
||||||
int phase
|
int phase
|
||||||
@ -70,11 +63,30 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
this.groovyClassLoader = null;
|
this.groovyClassLoader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComponentTemplate doCompile(@Nullable Class<? extends ViewComponent> forClass, Reader reader) {
|
@Override
|
||||||
return this.doCompile(forClass, reader, null);
|
protected ComponentTemplate doCompile(
|
||||||
|
@Nullable TemplateSource source,
|
||||||
|
@Nullable Class<? extends ViewComponent> forClass,
|
||||||
|
Reader sourceReader
|
||||||
|
) {
|
||||||
|
if (source instanceof TemplateSource.URISource uriSource) {
|
||||||
|
return this.doCompile(forClass, sourceReader, uriSource.templateURI());
|
||||||
|
} else if (source instanceof TemplateSource.URLSource urlSource) {
|
||||||
|
try {
|
||||||
|
return this.doCompile(forClass, sourceReader, urlSource.templateURL().toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.doCompile(forClass, sourceReader, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComponentTemplate doCompile(@Nullable Class<? extends ViewComponent> forClass, Reader reader, @Nullable URI uri) {
|
protected ComponentTemplate doCompile(
|
||||||
|
@Nullable Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader,
|
||||||
|
@Nullable URI uri
|
||||||
|
) {
|
||||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
||||||
|
|
||||||
// TODO: analysis
|
// TODO: analysis
|
||||||
@ -153,56 +165,8 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, File templateFile) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> {
|
|
||||||
try {
|
|
||||||
return this.doCompile(forClass, new FileReader(templateFile));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new ComponentTemplateCreateException(e, forClass, templateFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, String template) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> this.doCompile(forClass, new StringReader(template)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, URI templateURI) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> {
|
|
||||||
final Path path = Paths.get(templateURI);
|
|
||||||
try {
|
|
||||||
return this.doCompile(forClass, Files.newBufferedReader(path), templateURI);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ComponentTemplateCreateException(e, forClass, templateURI);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, URL templateURL) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> {
|
|
||||||
try {
|
|
||||||
return this.doCompile(forClass, new InputStreamReader(templateURL.openStream()), templateURL.toURI());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ComponentTemplateCreateException(e, forClass, templateURL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, InputStream inputStream) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> this.doCompile(forClass, new InputStreamReader(inputStream)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, Reader reader) {
|
|
||||||
return this.getFromCacheOrElse(forClass, () -> this.doCompile(forClass, reader));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentTemplate compileAnonymous(Reader reader) {
|
public ComponentTemplate compileAnonymous(Reader reader) {
|
||||||
return this.doCompile(null, reader);
|
return this.doCompile(null, null, reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package groowt.view.web;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ComponentContext;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
public interface WebViewComponentContext extends ComponentContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For use only by compiled web view component templates.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Internal
|
||||||
|
ViewComponent createFragment(Closure<?> childCollector);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package groowt.view.web;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.ComponentTemplateCompiler;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
|
public interface WebViewComponentTemplateCompiler extends ComponentTemplateCompiler {
|
||||||
|
ComponentTemplate compileAnonymous(Reader reader);
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public sealed interface WebViewTemplateComponentSource {
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(String template) {
|
|
||||||
return new StringSource(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(File templateFile) {
|
|
||||||
return new FileSource(templateFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(URI templateURI) {
|
|
||||||
return new URISource(templateURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(URL url) {
|
|
||||||
return new URLSource(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(InputStream templateInputStream) {
|
|
||||||
return new InputStreamSource(templateInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
static WebViewTemplateComponentSource of(Reader templateReader) {
|
|
||||||
return new ReaderSource(templateReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param resourceName An <strong>absolute</strong> path resource name.
|
|
||||||
* @return A template source
|
|
||||||
*/
|
|
||||||
static WebViewTemplateComponentSource fromResource(String resourceName) {
|
|
||||||
return of(WebViewTemplateComponentSource.class.getClassLoader().getResource(resourceName));
|
|
||||||
}
|
|
||||||
|
|
||||||
record StringSource(String template) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
record FileSource(File templateFile) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
record URISource(URI templateURI) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
record URLSource(URL templateURL) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
record InputStreamSource(InputStream templateInputStream) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
record ReaderSource(Reader templateReader) implements WebViewTemplateComponentSource {}
|
|
||||||
|
|
||||||
}
|
|
@ -11,22 +11,26 @@ import groowt.view.web.WebViewChildRenderer;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class WebViewComponentChildCollector {
|
public class DefaultWebViewComponentChildCollection implements WebViewComponentChildCollection {
|
||||||
|
|
||||||
private final List<WebViewChildRenderer> children = new ArrayList<>();
|
private final List<WebViewChildRenderer> children = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
public void add(String jString, Closure<Void> renderer) {
|
public void add(String jString, Closure<Void> renderer) {
|
||||||
this.children.add(new WebViewChildJStringRenderer(jString, renderer));
|
this.children.add(new WebViewChildJStringRenderer(jString, renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void add(GString gString, Closure<Void> renderer) {
|
public void add(GString gString, Closure<Void> renderer) {
|
||||||
this.children.add(new WebViewChildGStringRenderer(gString, renderer));
|
this.children.add(new WebViewChildGStringRenderer(gString, renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void add(ViewComponent component, Closure<Void> renderer) {
|
public void add(ViewComponent component, Closure<Void> renderer) {
|
||||||
this.children.add(new WebViewChildComponentRenderer(component, renderer));
|
this.children.add(new WebViewChildComponentRenderer(component, renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<WebViewChildRenderer> getChildren() {
|
public List<WebViewChildRenderer> getChildren() {
|
||||||
return this.children;
|
return this.children;
|
||||||
}
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
import groovy.lang.GString;
|
||||||
|
import groowt.view.component.ComponentRenderException;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
||||||
|
|
||||||
|
private final Writer delegate;
|
||||||
|
|
||||||
|
public DefaultWebViewComponentWriter(Writer delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(String string) {
|
||||||
|
try {
|
||||||
|
this.delegate.append(string);
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(GString gString) {
|
||||||
|
try {
|
||||||
|
gString.writeTo(this.delegate);
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new ComponentRenderException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(GString gString, int line, int column) {
|
||||||
|
final String content;
|
||||||
|
try {
|
||||||
|
content = gString.toString();
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new ComponentRenderException(line, column, exception);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.delegate.append(content);
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(ViewComponent viewComponent) {
|
||||||
|
try {
|
||||||
|
viewComponent.renderTo(this.delegate);
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new ComponentRenderException(viewComponent, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(ViewComponent viewComponent, int line, int column) {
|
||||||
|
try {
|
||||||
|
viewComponent.renderTo(this.delegate);
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new ComponentRenderException(viewComponent, line, column, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(Object object) {
|
||||||
|
try {
|
||||||
|
this.delegate.append(object.toString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leftShift(Object object) {
|
||||||
|
switch (object) {
|
||||||
|
case String s -> this.append(s);
|
||||||
|
case GString gs -> this.append(gs);
|
||||||
|
case ViewComponent viewComponent -> this.append(viewComponent);
|
||||||
|
default -> this.append(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groovy.lang.GString;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.web.WebViewChildRenderer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface WebViewComponentChildCollection {
|
||||||
|
|
||||||
|
void add(String jString, Closure<Void> renderer);
|
||||||
|
|
||||||
|
void add(GString gString, Closure<Void> renderer);
|
||||||
|
|
||||||
|
void add(ViewComponent component, Closure<Void> renderer);
|
||||||
|
|
||||||
|
List<WebViewChildRenderer> getChildren();
|
||||||
|
|
||||||
|
}
|
@ -1,87 +1,14 @@
|
|||||||
package groowt.view.web.runtime;
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
import groovy.lang.GString;
|
import groovy.lang.GString;
|
||||||
import groowt.view.component.ComponentRenderException;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
import java.io.IOException;
|
public interface WebViewComponentWriter {
|
||||||
import java.io.Writer;
|
void append(String string);
|
||||||
|
void append(GString gString);
|
||||||
public class WebViewComponentWriter {
|
void append(GString gString, int line, int column);
|
||||||
|
void append(ViewComponent viewComponent);
|
||||||
private final Writer delegate;
|
void append(ViewComponent viewComponent, int line, int column);
|
||||||
|
void append(Object object);
|
||||||
public WebViewComponentWriter(Writer delegate) {
|
void leftShift(Object object);
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(String string) {
|
|
||||||
try {
|
|
||||||
this.delegate.append(string);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(GString gString) {
|
|
||||||
try {
|
|
||||||
gString.writeTo(this.delegate);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new ComponentRenderException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(GString gString, int line, int column) {
|
|
||||||
final String content;
|
|
||||||
try {
|
|
||||||
content = gString.toString();
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new ComponentRenderException(line, column, exception);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.delegate.append(content);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(ViewComponent viewComponent) {
|
|
||||||
try {
|
|
||||||
viewComponent.renderTo(this.delegate);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new ComponentRenderException(viewComponent, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(ViewComponent viewComponent, int line, int column) {
|
|
||||||
try {
|
|
||||||
viewComponent.renderTo(this.delegate);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new ComponentRenderException(viewComponent, line, column, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void append(Object object) {
|
|
||||||
try {
|
|
||||||
this.delegate.append(object.toString());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void leftShift(Object object) {
|
|
||||||
switch (object) {
|
|
||||||
case String s -> this.append(s);
|
|
||||||
case GString gs -> this.append(gs);
|
|
||||||
case ViewComponent viewComponent -> this.append(viewComponent);
|
|
||||||
default -> this.append(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package groowt.view.web.transpile;
|
|||||||
import groovy.lang.Tuple2;
|
import groovy.lang.Tuple2;
|
||||||
import groowt.view.component.*;
|
import groowt.view.component.*;
|
||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollector;
|
import groowt.view.web.runtime.WebViewComponentChildCollection;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
||||||
import org.codehaus.groovy.ast.*;
|
import org.codehaus.groovy.ast.*;
|
||||||
@ -22,7 +22,7 @@ import static groowt.view.web.transpile.TranspilerUtil.*;
|
|||||||
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||||
|
|
||||||
private static final ClassNode VIEW_COMPONENT = ClassHelper.make(ViewComponent.class);
|
private static final ClassNode VIEW_COMPONENT = ClassHelper.make(ViewComponent.class);
|
||||||
private static final ClassNode CHILD_COLLECTOR = ClassHelper.make(WebViewComponentChildCollector.class);
|
private static final ClassNode CHILD_COLLECTION = ClassHelper.make(WebViewComponentChildCollection.class);
|
||||||
|
|
||||||
private static final ClassNode EXCEPTION = ClassHelper.make(Exception.class);
|
private static final ClassNode EXCEPTION = ClassHelper.make(Exception.class);
|
||||||
private static final ClassNode COMPONENT_CREATE = ClassHelper.make(ComponentCreateException.class);
|
private static final ClassNode COMPONENT_CREATE = ClassHelper.make(ComponentCreateException.class);
|
||||||
@ -182,7 +182,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
String componentVariableName
|
String componentVariableName
|
||||||
) {
|
) {
|
||||||
final Parameter childCollectorParam = new Parameter(
|
final Parameter childCollectorParam = new Parameter(
|
||||||
CHILD_COLLECTOR,
|
CHILD_COLLECTION,
|
||||||
componentVariableName + "_childCollector"
|
componentVariableName + "_childCollector"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<$name
|
@ -1,98 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.*;
|
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
public class DefaultWebComponentTemplateCompilerTests {
|
|
||||||
|
|
||||||
private static ComponentTemplate doCompile(Class<? extends ViewComponent> componentClass, Reader source) {
|
|
||||||
final var compiler = new DefaultWebComponentTemplateCompiler(
|
|
||||||
CompilerConfiguration.DEFAULT,
|
|
||||||
componentClass.getPackageName()
|
|
||||||
);
|
|
||||||
return compiler.compile(componentClass, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ComponentTemplate doCompile(Class<? extends ViewComponent> componentClass, String source) {
|
|
||||||
return doCompile(componentClass, new StringReader(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Greeter extends DefaultWebViewComponent {
|
|
||||||
|
|
||||||
private final String target;
|
|
||||||
|
|
||||||
public Greeter(Map<String, Object> attr) {
|
|
||||||
super(doCompile(Greeter.class, "Hello, $target!"));
|
|
||||||
this.target = (String) Objects.requireNonNull(attr.get("target"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTarget() {
|
|
||||||
return this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class GreeterFactory extends ComponentFactoryBase<Greeter> {
|
|
||||||
|
|
||||||
public Greeter doCreate(Map<String, Object> attr) {
|
|
||||||
return new Greeter(attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class UsingGreeter extends DefaultWebViewComponent {
|
|
||||||
|
|
||||||
public UsingGreeter(ComponentContext context) {
|
|
||||||
super(doCompile(UsingGreeter.class, "<Greeter target='World' />"));
|
|
||||||
this.setContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void usingGreeter() {
|
|
||||||
final var context = new DefaultComponentContext();
|
|
||||||
final var scope = new DefaultComponentScope();
|
|
||||||
scope.add("Greeter", new GreeterFactory());
|
|
||||||
context.pushScope(scope);
|
|
||||||
|
|
||||||
final UsingGreeter usingGreeter = new UsingGreeter(context);
|
|
||||||
assertEquals("Hello, World!", usingGreeter.render());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void withPreambleImport() {
|
|
||||||
final ComponentTemplate template = doCompile(DefaultWebViewComponent.class,
|
|
||||||
"""
|
|
||||||
---
|
|
||||||
import groovy.transform.Field
|
|
||||||
|
|
||||||
@Field
|
|
||||||
String greeting = 'Hello, World!'
|
|
||||||
---
|
|
||||||
$greeting
|
|
||||||
""".stripIndent()
|
|
||||||
);
|
|
||||||
final var context = new DefaultComponentContext();
|
|
||||||
context.pushDefaultScope();
|
|
||||||
|
|
||||||
final var sw = new StringWriter();
|
|
||||||
final var out = new WebViewComponentWriter(sw);
|
|
||||||
final Closure<?> renderer = template.getRenderer();
|
|
||||||
renderer.call(context, out);
|
|
||||||
|
|
||||||
assertEquals("Hello, World!", sw.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,82 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentFactoryBase
|
||||||
|
import groowt.view.web.lib.WithContext
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
||||||
|
class DefaultWebViewComponentTests implements WithContext {
|
||||||
|
|
||||||
|
private static final class Greeter extends DefaultWebViewComponent {
|
||||||
|
|
||||||
|
private final String target
|
||||||
|
|
||||||
|
Greeter(Map<String, Object> attr) {
|
||||||
|
super('Hello, $target!')
|
||||||
|
this.target = Objects.requireNonNull(attr.get("target"))
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTarget() {
|
||||||
|
return this.target
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class GreeterFactory extends ComponentFactoryBase<Greeter> {
|
||||||
|
|
||||||
|
Greeter doCreate(Map<String, Object> attr) {
|
||||||
|
return new Greeter(attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class UsingGreeter extends DefaultWebViewComponent {
|
||||||
|
|
||||||
|
UsingGreeter() {
|
||||||
|
super("<Greeter target='World' />")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void withPreambleImport() {
|
||||||
|
def c = new DefaultWebViewComponent(
|
||||||
|
'''
|
||||||
|
---
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
@Field
|
||||||
|
String greeting = 'Hello, World!'
|
||||||
|
---
|
||||||
|
$greeting
|
||||||
|
'''.stripIndent().trim()
|
||||||
|
)
|
||||||
|
c.context = this.context()
|
||||||
|
assertEquals("Hello, World!", c.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nestedGreeter() {
|
||||||
|
def context = this.context {
|
||||||
|
this.configureContext(it)
|
||||||
|
currentScope.add('Greeter', new GreeterFactory())
|
||||||
|
}
|
||||||
|
def c = new DefaultWebViewComponent('<Greeter target="World" />')
|
||||||
|
c.context = context
|
||||||
|
assertEquals('Hello, World!', c.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doubleNested() {
|
||||||
|
def context = this.context {
|
||||||
|
this.configureContext(it)
|
||||||
|
currentScope.add('UsingGreeter') { new UsingGreeter() }
|
||||||
|
currentScope.add('Greeter', new GreeterFactory())
|
||||||
|
}
|
||||||
|
def c = new DefaultWebViewComponent('<UsingGreeter />')
|
||||||
|
c.context = context
|
||||||
|
assertEquals('Hello, World!', c.render())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,31 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import groowt.view.web.DefaultWebViewComponentContext
|
import groowt.view.web.WebViewComponentContext
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class EchoTests extends AbstractWebViewComponentTests {
|
class EchoTests extends AbstractWebViewComponentTests {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void configureContext(WebViewComponentContext context) {
|
||||||
|
super.configureContext(context)
|
||||||
|
context.currentScope.add('Echo', Echo.FACTORY)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeOnlySelfClose() {
|
void selfClose() {
|
||||||
def context = new DefaultWebViewComponentContext()
|
this.doTest('<Echo />', '<Echo />')
|
||||||
context.pushDefaultScope()
|
}
|
||||||
context.currentScope.add('Echo', new Echo.EchoFactory())
|
|
||||||
this.doTest('<Echo(true) />', '<Echo />', context)
|
@Test
|
||||||
|
void noSelfClose() {
|
||||||
|
this.doTest('<Echo(false) />', '<Echo></Echo>')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("Not possible to render children directly to a writer yet.")
|
||||||
|
void withChildren() {
|
||||||
|
this.doTest('<Echo>Hello, World!</Echo>', '<Echo>Hello, World!</Echo>')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import groowt.view.component.ComponentContext
|
|||||||
import groowt.view.component.ComponentFactory
|
import groowt.view.component.ComponentFactory
|
||||||
import groowt.view.web.DefaultWebViewComponent
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
import groowt.view.web.DefaultWebViewComponentContext
|
import groowt.view.web.DefaultWebViewComponentContext
|
||||||
import groowt.view.web.WebViewTemplateComponentSource
|
import groowt.view.component.TemplateSource
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
import static groowt.view.web.WebViewComponentFactories.withAttr
|
||||||
|
|
||||||
class FragmentTests extends AbstractWebViewComponentTests {
|
class FragmentTests extends AbstractWebViewComponentTests {
|
||||||
|
|
||||||
static class Greeter extends DefaultWebViewComponent {
|
static class Greeter extends DefaultWebViewComponent {
|
||||||
@ -14,7 +16,7 @@ class FragmentTests extends AbstractWebViewComponentTests {
|
|||||||
String greeting
|
String greeting
|
||||||
|
|
||||||
Greeter(Map<String, Object> attr) {
|
Greeter(Map<String, Object> attr) {
|
||||||
super(WebViewTemplateComponentSource.of('$greeting'))
|
super(TemplateSource.of('$greeting'))
|
||||||
greeting = attr.greeting
|
greeting = attr.greeting
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,9 +24,7 @@ class FragmentTests extends AbstractWebViewComponentTests {
|
|||||||
|
|
||||||
private final ComponentContext greeterContext = new DefaultWebViewComponentContext().tap {
|
private final ComponentContext greeterContext = new DefaultWebViewComponentContext().tap {
|
||||||
pushDefaultScope()
|
pushDefaultScope()
|
||||||
def greeterFactory = ComponentFactory.ofClosure { type, componentContext, attr ->
|
def greeterFactory = withAttr(Greeter.&new)
|
||||||
new Greeter(attr)
|
|
||||||
}
|
|
||||||
currentScope.add('Greeter', greeterFactory)
|
currentScope.add('Greeter', greeterFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentContext
|
||||||
|
import groowt.view.web.DefaultWebViewComponentTemplateCompiler
|
||||||
|
import groowt.view.web.WebViewComponentTemplateCompiler
|
||||||
|
import groowt.view.web.runtime.DefaultWebViewComponentWriter
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
||||||
|
abstract class AbstractWebViewComponentTests implements WithContext {
|
||||||
|
|
||||||
|
protected WebViewComponentTemplateCompiler compiler() {
|
||||||
|
new DefaultWebViewComponentTemplateCompiler(
|
||||||
|
CompilerConfiguration.DEFAULT,
|
||||||
|
this.class.packageName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doTest(Reader source, String expected, ComponentContext context) {
|
||||||
|
def template = this.compiler().compileAnonymous(source)
|
||||||
|
def renderer = template.getRenderer()
|
||||||
|
def sw = new StringWriter()
|
||||||
|
def out = new DefaultWebViewComponentWriter(sw)
|
||||||
|
renderer.call(context, out)
|
||||||
|
assertEquals(expected, sw.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doTest(String source, String expected, ComponentContext context = this.context()) {
|
||||||
|
this.doTest(new StringReader(source), expected, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
import groowt.view.web.DefaultWebViewComponentContext
|
||||||
|
import groowt.view.web.WebViewComponentContext
|
||||||
|
|
||||||
|
trait WithContext {
|
||||||
|
|
||||||
|
WebViewComponentContext context(
|
||||||
|
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentContext')
|
||||||
|
@DelegatesTo(value = WebViewComponentContext)
|
||||||
|
Closure configure
|
||||||
|
) {
|
||||||
|
new DefaultWebViewComponentContext().tap {
|
||||||
|
pushDefaultScope()
|
||||||
|
configure.delegate = it
|
||||||
|
configure(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebViewComponentContext context() {
|
||||||
|
new DefaultWebViewComponentContext().tap {
|
||||||
|
configureContext(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureContext(WebViewComponentContext context) {
|
||||||
|
context.pushDefaultScope()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package groowt.view.web.lib;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ComponentContext;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.web.DefaultWebComponentTemplateCompiler;
|
|
||||||
import groowt.view.web.DefaultWebViewComponentContext;
|
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.StringReader;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
public abstract class AbstractWebViewComponentTests {
|
|
||||||
|
|
||||||
protected void doTest(Reader source, String expected, ComponentContext context) {
|
|
||||||
final var compiler = new DefaultWebComponentTemplateCompiler(
|
|
||||||
CompilerConfiguration.DEFAULT, this.getClass().getPackageName()
|
|
||||||
);
|
|
||||||
final ComponentTemplate template = compiler.compileAnonymous(source);
|
|
||||||
final Closure<?> renderer = template.getRenderer();
|
|
||||||
final StringWriter sw = new StringWriter();
|
|
||||||
final WebViewComponentWriter out = new WebViewComponentWriter(sw);
|
|
||||||
renderer.call(context, out);
|
|
||||||
assertEquals(expected, sw.toString().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doTest(String source, String expected, ComponentContext context) {
|
|
||||||
this.doTest(new StringReader(source), expected, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doTest(String source, String expected) {
|
|
||||||
final var context = new DefaultWebViewComponentContext();
|
|
||||||
context.pushDefaultScope();
|
|
||||||
this.doTest(source, expected, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +1,7 @@
|
|||||||
package groowt.view.web.tools
|
package groowt.view.web.tools
|
||||||
|
|
||||||
import groowt.view.component.DefaultComponentContext
|
import groowt.view.component.DefaultComponentContext
|
||||||
import groowt.view.web.DefaultWebComponentTemplateCompiler
|
|
||||||
import groowt.view.web.DefaultWebViewComponent
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
import groowt.view.web.WebViewTemplateComponentSource
|
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration
|
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.Option
|
import picocli.CommandLine.Option
|
||||||
@ -30,7 +26,7 @@ class RunTemplate implements Callable<Integer> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer call() throws Exception {
|
Integer call() throws Exception {
|
||||||
def component = new DefaultWebViewComponent(WebViewTemplateComponentSource.of(this.template))
|
def component = new DefaultWebViewComponent(this.template)
|
||||||
|
|
||||||
def context = new DefaultComponentContext()
|
def context = new DefaultComponentContext()
|
||||||
context.pushDefaultScope()
|
context.pushDefaultScope()
|
||||||
|
Loading…
Reference in New Issue
Block a user