Lots of work on how components are created, as well as general transpilation cleanup.

This commit is contained in:
JesseBrault0709 2024-05-12 10:26:36 +02:00
parent 0526b3ef6e
commit 8953c57681
37 changed files with 473 additions and 683 deletions

View File

@ -1,63 +1,36 @@
package groowt.view.component.compiler; package groowt.view.component.compiler;
import groowt.view.component.ComponentTemplate; import groowt.view.component.ComponentTemplate;
import org.codehaus.groovy.tools.GroovyClass; import groowt.view.component.compiler.util.GroovyClassWriter;
import groowt.view.component.compiler.util.SimpleGroovyClassWriter;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
public final class SimpleComponentTemplateClassFactory implements ComponentTemplateClassFactory { public final class SimpleComponentTemplateClassFactory implements ComponentTemplateClassFactory {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static String[] classNameToPackageDirParts(String fullClassName) {
final String[] allParts = fullClassName.split("\\.");
if (allParts.length == 0) {
throw new RuntimeException("Did not expect allParts.length to be zero.");
} else if (allParts.length == 1) {
return EMPTY_STRING_ARRAY;
} else {
final var result = new String[allParts.length - 1];
System.arraycopy(allParts, 0, result, 0, allParts.length - 1);
return result;
}
}
private static Path resolvePackageDir(Path rootDir, String[] packageDirParts) {
return Path.of(rootDir.toString(), packageDirParts);
}
private static String isolateClassName(String fullClassName) {
final String[] parts = fullClassName.split("\\.");
if (parts.length == 0) {
throw new RuntimeException("Did not expect parts.length to be zero");
}
return parts[parts.length - 1];
}
private final Map<String, Class<? extends ComponentTemplate>> cache = new HashMap<>(); private final Map<String, Class<? extends ComponentTemplate>> cache = new HashMap<>();
private final ClassLoader classLoader; private final ClassLoader classLoader;
private final Path tempClassesDir; private final File tempClassesDir;
private final GroovyClassWriter groovyClassWriter;
public SimpleComponentTemplateClassFactory() { public SimpleComponentTemplateClassFactory() {
this.groovyClassWriter = new SimpleGroovyClassWriter();
try { try {
this.tempClassesDir = Files.createTempDirectory("view-component-classes-"); this.tempClassesDir = Files.createTempDirectory("view-component-classes-").toFile();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
try { try {
this.classLoader = new URLClassLoader( this.classLoader = new URLClassLoader(
"SimpleComponentTemplateClassFactoryClassLoader", "SimpleComponentTemplateClassFactoryClassLoader",
new URL[] { this.tempClassesDir.toUri().toURL() }, new URL[] { this.tempClassesDir.toURI().toURL() },
this.getClass().getClassLoader() this.getClass().getClassLoader()
); );
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
@ -65,23 +38,6 @@ public final class SimpleComponentTemplateClassFactory implements ComponentTempl
} }
} }
private void writeClassToDisk(GroovyClass groovyClass) {
final var className = groovyClass.getName();
final var packageDirParts = classNameToPackageDirParts(className);
final var packageDir = resolvePackageDir(this.tempClassesDir, packageDirParts);
try {
Files.createDirectories(packageDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
final var classFile = Path.of(packageDir.toString(), isolateClassName(className) + ".class");
try {
Files.write(classFile, groovyClass.getBytes(), CREATE_NEW, WRITE);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override @Override
public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) { public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) {
final String templateClassName = compileResult.getTemplateClass().getName(); final String templateClassName = compileResult.getTemplateClass().getName();
@ -89,11 +45,13 @@ public final class SimpleComponentTemplateClassFactory implements ComponentTempl
return this.cache.get(templateClassName); return this.cache.get(templateClassName);
} else { } else {
// write classes to disk // write classes to disk
this.writeClassToDisk(compileResult.getTemplateClass()); this.groovyClassWriter.writeTo(this.tempClassesDir, compileResult.getTemplateClass());
compileResult.getOtherClasses().forEach(this::writeClassToDisk); compileResult.getOtherClasses().forEach(groovyClass -> this.groovyClassWriter.writeTo(
this.tempClassesDir, groovyClass
));
// load the template class // load the template class
try { try {
//noinspection unchecked @SuppressWarnings("unchecked")
final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass( final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass(
templateClassName templateClassName
); );

View File

@ -0,0 +1,34 @@
package groowt.view.component.compiler.util;
import java.io.File;
import java.util.Arrays;
import java.util.List;
public final class ClassNameUtil {
public static List<String> classNameToPackageDirParts(String fullClassName) {
final String[] allParts = fullClassName.split("\\.");
if (allParts.length == 0) {
throw new RuntimeException("Did not expect allParts.length to be zero.");
} else if (allParts.length == 1) {
return List.of();
} else {
return Arrays.asList(allParts).subList(0, allParts.length - 1);
}
}
public static File resolvePackageDir(File rootDir, List<String> packageDirParts) {
return new File(rootDir, String.join(File.separator, packageDirParts));
}
public static String isolateClassName(String fullClassName) {
final String[] parts = fullClassName.split("\\.");
if (parts.length == 0) {
throw new RuntimeException("Did not expect parts.length to be zero");
}
return parts[parts.length - 1];
}
private ClassNameUtil() {}
}

View File

@ -0,0 +1,16 @@
package groowt.view.component.compiler.util;
import org.codehaus.groovy.tools.GroovyClass;
import java.io.File;
import java.nio.file.Path;
public interface GroovyClassWriter {
void writeTo(File base, GroovyClass groovyClass);
default void writeTo(Path base, GroovyClass groovyClass) {
this.writeTo(base.toFile(), groovyClass);
}
}

View File

@ -0,0 +1,30 @@
package groowt.view.component.compiler.util;
import org.codehaus.groovy.tools.GroovyClass;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import static groowt.view.component.compiler.util.ClassNameUtil.*;
public final class SimpleGroovyClassWriter implements GroovyClassWriter {
@Override
public void writeTo(File base, GroovyClass groovyClass) {
final String className = groovyClass.getName();
final List<String> packageDirParts = classNameToPackageDirParts(className);
final File packageDir = resolvePackageDir(base, packageDirParts);
if (!packageDir.exists() && !packageDir.mkdirs()) {
throw new RuntimeException(new IOException("Could not make package dir(s) at " + packageDir));
}
final var classFile = new File(packageDir, isolateClassName(className) + ".class");
try (final var fos = new FileOutputStream(classFile)) {
fos.write(groovyClass.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,6 +1,7 @@
package groowt.view.component.context; package groowt.view.component.context;
import groowt.view.component.ViewComponent; import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.RenderContext;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
@ -8,13 +9,22 @@ import java.util.function.Predicate;
public interface ComponentContext { public interface ComponentContext {
RenderContext getRenderContext();
List<ComponentScope> getScopeStack(); List<ComponentScope> getScopeStack();
void pushScope(ComponentScope scope); void pushScope(ComponentScope scope);
void pushDefaultScope(); void pushDefaultScope();
void popScope(); void popScope();
ComponentScope getRootScope(); ComponentScope getRootScope();
default <S extends ComponentScope> S getRootScope(Class<? extends S> scopeClass) {
return scopeClass.cast(this.getRootScope());
}
default ComponentScope getCurrentScope() { default ComponentScope getCurrentScope() {
final List<ComponentScope> scopeStack = this.getScopeStack(); final List<ComponentScope> scopeStack = this.getScopeStack();
if (scopeStack.isEmpty()) { if (scopeStack.isEmpty()) {
@ -23,8 +33,15 @@ public interface ComponentContext {
return scopeStack.getFirst(); return scopeStack.getFirst();
} }
default <S extends ComponentScope> S getCurrentScope(Class<? extends S> scopeClass) {
return scopeClass.cast(this.getCurrentScope());
}
@Nullable ViewComponent getParent(); @Nullable ViewComponent getParent();
@Nullable <T extends ViewComponent> T getParent(Class<T> parentClass);
default <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
return parentClass.cast(this.getParent());
}
@Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching); @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching);

View File

@ -15,7 +15,7 @@ public class DefaultComponentContext implements ComponentContext {
private final LinkedList<ComponentScope> scopeStack = new LinkedList<>(); private final LinkedList<ComponentScope> scopeStack = new LinkedList<>();
private RenderContext renderContext; private RenderContext renderContext;
@ApiStatus.Internal @Override
public RenderContext getRenderContext() { public RenderContext getRenderContext() {
return Objects.requireNonNull( return Objects.requireNonNull(
this.renderContext, this.renderContext,
@ -66,11 +66,6 @@ public class DefaultComponentContext implements ComponentContext {
return null; return null;
} }
@Override
public <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
return parentClass.cast(this.getParent());
}
@Override @Override
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) { public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack(); final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();

View File

@ -9,13 +9,13 @@ import groowt.view.component.context.ComponentScope.TypeAndFactory;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class DefaultRenderContext implements RenderContext { public abstract class AbstractRenderContext implements RenderContext {
private final ComponentContext componentContext; private final ComponentContext componentContext;
private final ComponentWriter writer; private final ComponentWriter writer;
private final LinkedList<ViewComponent> componentStack = new LinkedList<>(); private final LinkedList<ViewComponent> componentStack = new LinkedList<>();
public DefaultRenderContext(ComponentContext componentContext, ComponentWriter writer) { public AbstractRenderContext(ComponentContext componentContext, ComponentWriter writer) {
this.componentContext = componentContext; this.componentContext = componentContext;
this.writer = writer; this.writer = writer;
} }
@ -80,39 +80,6 @@ public class DefaultRenderContext implements RenderContext {
); );
} }
@Override
public ViewComponent create(Resolved<?> resolved, Object... args) {
final ViewComponent created;
if (resolved instanceof ResolvedStringType<?> resolvedStringType) {
try {
created = resolvedStringType.componentFactory().create(
resolvedStringType.typeName(),
this.getComponentContext(),
args
);
} catch (Exception createException) {
throw new ComponentCreateException(resolved, createException);
}
} else if (resolved instanceof ResolvedClassType<?> resolvedClassType) {
try {
created = resolvedClassType.componentFactory().create(
resolvedClassType.alias(),
resolvedClassType.resolvedType(),
this.getComponentContext(),
args
);
} catch (Exception createException) {
throw new ComponentCreateException(resolved, createException);
}
} else {
throw new UnsupportedOperationException(
this.getClass().getName() + " cannot handle Resolved of sub-type " + resolved.getClass().getName()
);
}
created.setContext(this.getComponentContext());
return created;
}
@Override @Override
public void pushComponent(ViewComponent component) { public void pushComponent(ViewComponent component) {
this.componentStack.push(component); this.componentStack.push(component);

View File

@ -3,11 +3,9 @@ package groowt.view.component.runtime;
import groowt.view.component.ViewComponent; import groowt.view.component.ViewComponent;
import groowt.view.component.context.ComponentResolveException; import groowt.view.component.context.ComponentResolveException;
import groowt.view.component.factory.ComponentFactory; import groowt.view.component.factory.ComponentFactory;
import org.jetbrains.annotations.ApiStatus;
import java.util.List; import java.util.List;
@ApiStatus.Internal
public interface RenderContext { public interface RenderContext {
interface Resolved<T extends ViewComponent> { interface Resolved<T extends ViewComponent> {
@ -31,8 +29,6 @@ public interface RenderContext {
Resolved<?> resolve(String typeName) throws ComponentResolveException; Resolved<?> resolve(String typeName) throws ComponentResolveException;
<T extends ViewComponent> Resolved<T> resolve(String alias, Class<T> type) throws ComponentResolveException; <T extends ViewComponent> Resolved<T> resolve(String alias, Class<T> type) throws ComponentResolveException;
ViewComponent create(Resolved<?> resolved, Object... args);
void pushComponent(ViewComponent component); void pushComponent(ViewComponent component);
void popComponent(ViewComponent component); void popComponent(ViewComponent component);

View File

@ -18,19 +18,18 @@ import groowt.view.web.runtime.*
class MyComponentTemplate implements ComponentTemplate { class MyComponentTemplate implements ComponentTemplate {
Closure getRenderer() { Closure getRenderer() {
return { ComponentContext componentContext, ComponentWriter out -> return { WebViewComponentContext componentContext, ComponentWriter writer -> // <1>
// <1> def renderContext = new DefaultWebViewComponentRenderContext(componentContext, writer) // <2>
final RenderContext renderContext = new DefaultWebViewComponentRenderContext(componentContext, out)
componentContext.setRenderContext(renderContext) componentContext.setRenderContext(renderContext)
out.setRenderContext(renderContext) writer.setRenderContext(renderContext)
out.setComponentContext(renderContext) writer.setComponentContext(renderContext)
out.append 'Hello from simple text!' // <2> writer << 'Hello from simple text!' // <3>
writer << someProp // <3>
out.append "Hello from GString!" // <3> writer << someMethod() // <3>
// <4> // <4>
ComponentContext.Resolved c0Resolved // <5> def c0Resolved // <5>
try { try {
c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6> c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6>
} catch (ComponentResolveException c0ResolveException) { // <7> } catch (ComponentResolveException c0ResolveException) { // <7>
@ -40,44 +39,38 @@ class MyComponentTemplate implements ComponentTemplate {
throw c0ResolveException throw c0ResolveException
} }
WebViewComponent c0 // <8> def c0 // <8>
try { try {
c0 = renderContext.create( // <9> c0 = renderContext.create( // <9>
c0Resolved, c0Resolved, // <10>
[greeting: 'Hello, World!'], [greeting: 'Hello, World!'], // <11>
"Some constructor arg", ["Some constructor arg"] // <12>
WebViewComponentChildCollectorClosure.get(this) { c0cc -> ) { c0childList -> // <13>
c0cc.add 'JString child.' // <10> c0childList << 'string child.' // <14>
c0cc.add "GString child"
ComponentContext.Resolved c1Resolved def c1Resolved
try { try {
c1Resolved = renderContext.resolve('h1') // <11> c1Resolved = renderContext.resolve('h1') // <15>
} catch (ComponentResolveException c1ResolveException) { } catch (ComponentResolveException c1ResolveException) {
c1ResolveException.type = IntrinsicHtml // <12>
c1ResolveException.template = this c1ResolveException.template = this
c1ResolveException.line = 1 c1ResolveException.line = 1
c1ResolveException.column = 10 c1ResolveException.column = 10
throw c1ResolveException throw c1ResolveException
} }
WebViewComponent c1 def c1
try { try {
c1 = renderContext.create( c1 = renderContext.create(c1_resolved) { c1cc ->
c1_resolved, c1cc << greeting
WebViewComponentChildCollectorClosure.get(this) { c1cc ->
c1cc.add "$greeting"
} }
)
} catch (ComponentCreateException c1CreateException) { } catch (ComponentCreateException c1CreateException) {
c1CreateException.template = this c1CreateException.template = this
c1CreateException.line = 1 c1CreateException.line = 1
c1CreateException.column = 1 c1CreateException.column = 1
} }
c0cc.add c1 c0childList << c1
} }
)
} catch (ComponentCreateException c0CreateException) { } catch (ComponentCreateException c0CreateException) {
c0CreateException.template = this c0CreateException.template = this
c0CreateException.line = 1 c0CreateException.line = 1
@ -85,38 +78,27 @@ class MyComponentTemplate implements ComponentTemplate {
throw c0CreateException throw c0CreateException
} }
// append it writer << c0
out.append c0
} }
} }
} }
---- ----
<1> Initialize the contexts. <1> The render `Closure` receives a `ComponentContext` and a `ComponentWriter`.
<2> Appending a plain old java string (jstring) to `out`. <2> A `RenderContext` is created which is then initialized with the values that the render `Closure` received.
<3> Appending a GString to `out`. <3> Strings and expressions of every type (except components) are simply left-shifted into the `ComponentWriter`.
<4> Now begins a component 'block', where a component is resolved, created, and either rendered or appended <4> Now begins a component 'block', where a component is resolved, created, and eventually left-shifted into either a `ComponentWriter` or a children `List` (see below).
to a child collector (see below). <5> First, we define the `resolved` variable, of the type `ComponentContext.Resolved<WebViewComponent>`.
<5> First, we define the `resolved` variable, of the type `ComponentContext.Resolved`.
<6> Resolve it from the context. <6> Resolve it from the context.
<7> If the context cannot resolve the component, it should throw a `ComponentResolveException`. We catch it <7> If the context cannot resolve the component, it should throw a `ComponentResolveException`. We catch it
here in order to set the template (`this`), line, and column information for debugging purposes. here in order to set the template (`this`), line, and column information for debugging purposes.
<8> Now we can start to create the component by first defining a variable for it. <8> Now we can start to create the component by first defining a variable for it.
<9> The create function takes a few things: <9> The create function takes up to four parameters.
. The relevant instance of `ComponentContext.Resolved`. <10> The relevant instance of `ComponentContext.Resolved` (required).
. Any attributes for the component, passed as a `Map`. <11> Attributes from the component, passed as a `Map` (required; if there are no attributes, an empty `Map` is passed).
. Any arguments from the component constructor. <12> Any arguments from the component constructor, passed as a `List` (required; if there are no arguments, an empty `List` is passed).
. A `WebViewComponentChildCollectorClosure`, which is a 'marker' subclass of `Closure` <13> A `Closure` (optional), which receives an argument of `List` and simply collects the children of the component.
(so that constructor args ending with a closure don't get confused), which simply collects <14> For children, we add the value of the child to the children `List`, rather than appending it directly to `out`.
the children of the component. <15> Here, a string type is passed, since all lowercase type names are treated as string types in web view components.
<10> For children, we add the value of the child, rather than appending it directly to `out`.
The collector itself will be given the `out` writer from the `context`, which will then
supply the parent with the ability to render to out. This way the parent doesn't ever have to worry about `out` itself
(though if the parent wants access to `out` (for example, it is using a non-wvc template) it can access the out writer
from the context).
<11> Here, a string type is passed, since all lowercase type names are treated as string types in web view components.
<12> Because this is an intrinsic html element, we set the type here.
<13> Finally, our child component is added as a `Closure` which accepts the component back again and appends
it to out.
== Items requiring Groovy ASTNode position adjustment == Items requiring Groovy ASTNode position adjustment

View File

@ -6,12 +6,12 @@ import groowt.view.component.context.DefaultComponentContext
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext { class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
DefaultWebViewComponentContext() { DefaultWebViewComponentContext() {
this.pushScope(WebViewComponentScope.getDefaultRootScope()) this.pushScope(DefaultWebViewComponentScope.getDefaultRootScope())
} }
@Override @Override
protected ComponentScope getNewDefaultScope() { protected ComponentScope getNewDefaultScope() {
new WebViewComponentScope() new DefaultWebViewComponentScope()
} }
} }

View File

@ -0,0 +1,29 @@
package groowt.view.web
import groowt.view.component.context.DefaultComponentScope
import groowt.view.web.lib.Echo
import groowt.view.web.lib.IntrinsicHtml
import org.codehaus.groovy.runtime.InvokerHelper
import static groowt.view.web.WebViewComponentFactories.withAttr
class DefaultWebViewComponentScope extends DefaultComponentScope {
static DefaultWebViewComponentScope getDefaultRootScope() {
new DefaultWebViewComponentScope().tap {
addWithAttr(Echo)
}
}
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass) {
add(componentClass, withAttr(componentClass) { attr ->
InvokerHelper.invokeConstructorOf(componentClass, attr) as T
})
}
@Override
TypeAndFactory factoryMissing(String typeName) {
IntrinsicHtml.TYPE_AND_FACTORY
}
}

View File

@ -3,8 +3,6 @@ package groowt.view.web
import groovy.transform.stc.ClosureParams import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FromString import groovy.transform.stc.FromString
import groowt.view.component.factory.ComponentFactory import groowt.view.component.factory.ComponentFactory
import groowt.view.web.runtime.DefaultWebViewComponentChildCollector
import groowt.view.web.runtime.WebViewComponentChildCollector
import static groowt.view.component.factory.ComponentFactories.ofClosureClassType import static groowt.view.component.factory.ComponentFactories.ofClosureClassType
@ -15,30 +13,7 @@ final class WebViewComponentFactories {
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>') @ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
Closure<? extends T> closure Closure<? extends T> closure
) { ) {
ofClosureClassType(forClass) { Map<String, Object> attr -> closure(attr) } ofClosureClassType(forClass) { Map<String, Object> attr, Object[] ignored -> closure(attr) }
}
static <T extends WebViewComponent> ComponentFactory<T> withChildren(
Class<T> forClass,
@ClosureParams(value = FromString, options = 'java.util.List<groowt.view.web.WebViewComponentChild>')
Closure<? extends T> closure
) {
ofClosureClassType(forClass) { WebViewComponentChildCollector childCollector ->
closure(childCollector.children)
}
}
static <T extends WebViewComponent> ComponentFactory<T> withAttrAndChildren(
Class<T> forClass,
@ClosureParams(
value = FromString,
options = 'java.util.Map<String, Object>, java.util.List<groowt.view.web.WebViewComponentChild>'
)
Closure<? extends T> closure
) {
ofClosureClassType(forClass) { Map<String, Object> attr, WebViewComponentChildCollector childCollector ->
closure(attr, childCollector.children)
}
} }
private WebViewComponentFactories() {} private WebViewComponentFactories() {}

View File

@ -1,41 +0,0 @@
package groowt.view.web
import groowt.view.component.context.DefaultComponentScope
import groowt.view.web.lib.Echo
import groowt.view.web.lib.IntrinsicHtml
import org.codehaus.groovy.runtime.InvokerHelper
import static groowt.view.web.WebViewComponentFactories.*
class WebViewComponentScope extends DefaultComponentScope {
static WebViewComponentScope getDefaultRootScope() {
new WebViewComponentScope().tap {
add(Echo, Echo.FACTORY)
}
}
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass) {
add(componentClass, withAttr(componentClass) { attr ->
InvokerHelper.invokeConstructorOf(componentClass, attr) as T
})
}
<T extends WebViewComponent> void addWithChildren(Class<T> componentClass) {
add(componentClass, withChildren(componentClass) { children ->
InvokerHelper.invokeConstructorOf(componentClass, children) as T
})
}
<T extends WebViewComponent> void addWithAttrAndChildren(Class<T> componentClass) {
add(componentClass, withAttrAndChildren(componentClass) { attr, children ->
InvokerHelper.invokeConstructorOf(componentClass, [attr, children] as Object[]) as T
})
}
@Override
TypeAndFactory factoryMissing(String typeName) {
IntrinsicHtml.TYPE_AND_FACTORY
}
}

View File

@ -1,35 +1,10 @@
package groowt.view.web.lib package groowt.view.web.lib
import groowt.view.View import groowt.view.View
import groowt.view.component.context.ComponentContext import groowt.view.component.runtime.DefaultComponentWriter
import groowt.view.component.factory.ComponentFactory
class Echo extends DelegatingWebViewComponent { class Echo extends DelegatingWebViewComponent {
static final ComponentFactory<Echo> FACTORY = new EchoFactory()
protected static class EchoFactory implements ComponentFactory<Echo> {
protected Echo doCreate() {
new Echo([:])
}
protected Echo doCreate(Map attr) {
new Echo(attr)
}
@Override
Echo create(String typeName, ComponentContext componentContext, Object... args) {
throw new UnsupportedOperationException('Cannot create Echo for string type components')
}
@Override
Echo create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
this.doCreate(*args)
}
}
Map attr Map attr
Echo(Map attr) { Echo(Map attr) {
@ -48,8 +23,11 @@ class Echo extends DelegatingWebViewComponent {
@Override @Override
protected View getDelegate() { protected View getDelegate() {
return { return {
def componentWriter = new DefaultComponentWriter(it)
componentWriter.setComponentContext(this.context)
componentWriter.setRenderContext(this.context.renderContext) // hacky
this.children.each { this.children.each {
it.render(this) componentWriter << it
} }
} }
} }

View File

@ -7,7 +7,7 @@ final class Fragment extends BaseWebViewComponent {
@Override @Override
void renderTo(Writer out) throws IOException { void renderTo(Writer out) throws IOException {
this.beforeRender() this.beforeRender()
this.renderChildren() this.renderChildren(out)
this.afterRender() this.afterRender()
} }

View File

@ -21,11 +21,7 @@ class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {
protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> { protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> {
IntrinsicHtml doCreate(String typeName) { IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, Object[] ignored) {
new IntrinsicHtml([:], typeName, typeName in voidElements)
}
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr) {
new IntrinsicHtml(attr, typeName, typeName in voidElements) new IntrinsicHtml(attr, typeName, typeName in voidElements)
} }
@ -64,11 +60,7 @@ class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {
this.formatAttr(writer) this.formatAttr(writer)
} }
writer << '>' writer << '>'
if (this.hasChildren()) { this.renderChildren(writer)
this.children.each {
it.renderTo(writer, this)
}
}
if (!this.isVoidElement) { if (!this.isVoidElement) {
writer << '</' writer << '</'
writer << this.name writer << this.name

View File

@ -2,8 +2,8 @@ package groowt.view.web.util
import groovy.transform.stc.ClosureParams import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType import groovy.transform.stc.SimpleType
import groowt.view.web.DefaultWebViewComponentScope
import groowt.view.web.WebViewComponentContext import groowt.view.web.WebViewComponentContext
import groowt.view.web.WebViewComponentScope
class ContextConfigurator { class ContextConfigurator {
@ -14,12 +14,12 @@ class ContextConfigurator {
} }
void rootScope( void rootScope(
@DelegatesTo(WebViewComponentScope) @DelegatesTo(DefaultWebViewComponentScope)
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentScope') @ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentScope')
Closure configureRootScope Closure configureRootScope
) { ) {
//noinspection GroovyAssignabilityCheck //noinspection GroovyAssignabilityCheck
WebViewComponentScope rootScope = context.rootScope DefaultWebViewComponentScope rootScope = context.rootScope
configureRootScope.delegate = rootScope configureRootScope.delegate = rootScope
configureRootScope(rootScope) configureRootScope(rootScope)
} }

View File

@ -17,7 +17,7 @@ import java.util.function.Function;
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent { public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
private List<WebViewComponentChild> childRenderers; private List<Object> children;
public AbstractWebViewComponent() {} public AbstractWebViewComponent() {}
@ -40,11 +40,11 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
} }
@Override @Override
public List<WebViewComponentChild> getChildren() { public List<Object> getChildren() {
if (this.childRenderers == null) { if (this.children == null) {
this.childRenderers = new ArrayList<>(); this.children = new ArrayList<>();
} }
return this.childRenderers; return this.children;
} }
@Override @Override
@ -53,15 +53,21 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
} }
@Override @Override
public void setChildren(List<WebViewComponentChild> children) { public void setChildren(List<?> children) {
this.childRenderers = children; if (this.children == null) {
this.children = new ArrayList<>();
}
this.children.addAll(children);
} }
@Override @Override
public void renderChildren() { public void renderChildren(Writer to) {
for (final var childRenderer : this.getChildren()) { final ComponentWriter componentWriter = new DefaultComponentWriter(to);
componentWriter.setComponentContext(this.getContext());
componentWriter.setRenderContext(this.getContext().getRenderContext());
for (final var child : this.getChildren()) {
try { try {
childRenderer.render(this); componentWriter.append(child);
} catch (Exception e) { } catch (Exception e) {
throw new ChildRenderException(e); throw new ChildRenderException(e);
} }

View File

@ -1,45 +1,15 @@
package groowt.view.web; package groowt.view.web;
import groovy.lang.GString;
import groowt.view.component.ViewComponent; import groowt.view.component.ViewComponent;
import java.io.Writer;
import java.util.List; import java.util.List;
public interface WebViewComponent extends ViewComponent { public interface WebViewComponent extends ViewComponent {
List<WebViewComponentChild> getChildren(); List<Object> getChildren();
boolean hasChildren(); boolean hasChildren();
void setChildren(List<WebViewComponentChild> children); void setChildren(List<?> children);
void renderChildren(); void renderChildren(Writer to);
default List<String> getChildStrings() {
return this.getChildren().stream()
.map(WebViewComponentChild::getChild)
.filter(obj -> obj instanceof String || obj instanceof GString)
.map(obj -> {
if (obj instanceof String s) {
return s;
} else {
return ((GString) obj).toString();
}
})
.toList();
}
default List<GString> getChildGStrings() {
return this.getChildren().stream()
.map(WebViewComponentChild::getChild)
.filter(GString.class::isInstance)
.map(GString.class::cast)
.toList();
}
default List<WebViewComponent> getChildComponents() {
return this.getChildren().stream()
.map(WebViewComponentChild::getChild)
.filter(WebViewComponent.class::isInstance)
.map(WebViewComponent.class::cast)
.toList();
}
} }

View File

@ -1,68 +0,0 @@
package groowt.view.web;
import groovy.lang.Closure;
import groowt.view.component.ComponentTemplate;
import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.ComponentWriter;
import groowt.view.component.runtime.DefaultComponentWriter;
import java.io.Writer;
public class WebViewComponentChild {
private static final class ChildRenderClosure extends Closure<Void> {
private final ViewComponent parent;
private final Object child;
private final ComponentWriter writer;
public ChildRenderClosure(
ComponentTemplate template,
ViewComponent parent,
ComponentWriter writer,
Object child
) {
super(template, template);
this.parent = parent;
this.child = child;
this.writer = writer;
this.setDelegate(this.parent);
this.setResolveStrategy(Closure.DELEGATE_FIRST);
}
public ViewComponent getParent() {
return this.parent;
}
public void doCall() {
this.writer.append(this.child);
}
}
private final ComponentTemplate template;
private final ComponentWriter componentWriter;
private final Object child;
public WebViewComponentChild(ComponentTemplate template, ComponentWriter componentWriter, Object child) {
this.template = template;
this.componentWriter = componentWriter;
this.child = child;
}
public Object getChild() {
return this.child;
}
public void render(ViewComponent parent) {
new ChildRenderClosure(this.template, parent, this.componentWriter, this.child).call();
}
public Writer renderTo(Writer out, ViewComponent parent) {
final var componentWriter = new DefaultComponentWriter(out);
final var childRenderClosure = new ChildRenderClosure(this.template, parent, componentWriter, this.child);
childRenderClosure.call();
return out;
}
}

View File

@ -1,7 +1,6 @@
package groowt.view.web.compiler; package groowt.view.web.compiler;
import groowt.view.component.context.ComponentContext; import groowt.view.component.context.ComponentContext;
import groowt.view.web.WebViewComponentChild;
import groowt.view.web.WebViewComponent; import groowt.view.web.WebViewComponent;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
@ -27,7 +26,7 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
} }
@Override @Override
public List<WebViewComponentChild> getChildren() { public List<Object> getChildren() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -37,12 +36,12 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
} }
@Override @Override
public void setChildren(List<WebViewComponentChild> children) { public void setChildren(List<?> children) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public void renderChildren() { public void renderChildren(Writer to) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -1,43 +0,0 @@
package groowt.view.web.runtime;
import groovy.lang.GString;
import groowt.view.component.ComponentTemplate;
import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.ComponentWriter;
import groowt.view.web.WebViewComponentChild;
import java.util.ArrayList;
import java.util.List;
public class DefaultWebViewComponentChildCollector implements WebViewComponentChildCollector {
private final ComponentTemplate template;
private final ComponentWriter out;
private final List<WebViewComponentChild> children = new ArrayList<>();
public DefaultWebViewComponentChildCollector(ComponentTemplate template, ComponentWriter out) {
this.template = template;
this.out = out;
}
@Override
public void add(String jString) {
this.children.add(new WebViewComponentChild(this.template, this.out, jString));
}
@Override
public void add(GString gString) {
this.children.add(new WebViewComponentChild(this.template, this.out, gString));
}
@Override
public void add(ViewComponent component) {
this.children.add(new WebViewComponentChild(this.template, this.out, component));
}
@Override
public List<WebViewComponentChild> getChildren() {
return this.children;
}
}

View File

@ -1,48 +0,0 @@
package groowt.view.web.runtime;
import groovy.lang.Closure;
import groowt.view.component.ViewComponent;
import groowt.view.component.context.ComponentContext;
import groowt.view.component.runtime.ComponentWriter;
import groowt.view.component.runtime.DefaultRenderContext;
import groowt.view.web.WebViewComponent;
import org.jetbrains.annotations.ApiStatus;
public class DefaultWebViewComponentRenderContext extends DefaultRenderContext
implements WebViewComponentRenderContext {
DefaultWebViewComponentRenderContext(ComponentContext componentContext, ComponentWriter writer) {
super(componentContext, writer);
}
@Override
public ViewComponent create(Resolved<?> resolved, Object... args) {
if (args != null && args.length > 0) {
final Object last = args[args.length - 1];
if (last instanceof WebViewComponentChildCollectorClosure cl) {
final Object[] argsWithoutChildren = new Object[args.length - 1];
System.arraycopy(args, 0, argsWithoutChildren, 0, args.length - 1);
final WebViewComponent self = (WebViewComponent) super.create(resolved, argsWithoutChildren);
final var childCollector = new DefaultWebViewComponentChildCollector(
cl.getTemplate(),
this.getWriter()
);
cl.setDelegate(self);
cl.setResolveStrategy(Closure.DELEGATE_FIRST);
cl.call(childCollector);
self.setChildren(childCollector.getChildren());
return self;
}
}
return super.create(resolved, args);
}
@ApiStatus.Internal
public ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl) {
final var childCollection = new DefaultWebViewComponentChildCollector(cl.getTemplate(), this.getWriter());
cl.call(childCollection);
fragment.setChildren(childCollection.getChildren());
return fragment;
}
}

View File

@ -0,0 +1,77 @@
package groowt.view.web.runtime;
import groovy.lang.Closure;
import groowt.view.component.ViewComponent;
import groowt.view.component.context.ComponentContext;
import groowt.view.component.runtime.AbstractRenderContext;
import groowt.view.component.runtime.ComponentWriter;
import groowt.view.web.WebViewComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class DefaultWebViewRenderContext extends AbstractRenderContext implements WebViewComponentRenderContext {
DefaultWebViewRenderContext(ComponentContext componentContext, ComponentWriter writer) {
super(componentContext, writer);
}
@Override
public WebViewComponent create(
Resolved<? extends WebViewComponent> resolved,
Map<String, Object> attr,
Object[] constructorArgs
) {
final WebViewComponent created;
if (resolved instanceof ResolvedStringType<? extends WebViewComponent> resolvedStringType) {
created = resolvedStringType.componentFactory().create(
resolvedStringType.typeName(),
this.getComponentContext(),
attr,
constructorArgs
);
} else if (resolved instanceof ResolvedClassType<? extends WebViewComponent> resolvedClassType) {
created = resolvedClassType.componentFactory().create(
resolvedClassType.alias(),
resolvedClassType.requestedType(),
this.getComponentContext(),
attr,
constructorArgs
);
} else {
throw new UnsupportedOperationException(
"Cannot create from a Resolved that is not a ResolvedStringType or ResolvedClassType."
);
}
created.setContext(this.getComponentContext());
created.setChildren(new ArrayList<>());
return created;
}
@Override
public WebViewComponent create(
Resolved<? extends WebViewComponent> resolved,
Map<String, Object> attr,
Object[] constructorArgs,
Closure<Void> childrenClosure
) {
final WebViewComponent created = this.create(resolved, attr, constructorArgs);
childrenClosure.setDelegate(created);
childrenClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
final List<Object> children = new ArrayList<>();
childrenClosure.call(children);
created.setChildren(children);
return created;
}
@Override
public ViewComponent createFragment(WebViewComponent fragment, Closure<Void> childrenClosure) {
final List<Object> children = new ArrayList<>();
childrenClosure.call(children);
fragment.setChildren(children);
fragment.setContext(this.getComponentContext());
return fragment;
}
}

View File

@ -1,14 +0,0 @@
package groowt.view.web.runtime;
import groovy.lang.GString;
import groowt.view.component.ViewComponent;
import groowt.view.web.WebViewComponentChild;
import java.util.List;
public interface WebViewComponentChildCollector {
void add(String jString);
void add(GString gString);
void add(ViewComponent component);
List<WebViewComponentChild> getChildren();
}

View File

@ -1,29 +0,0 @@
package groowt.view.web.runtime;
import groovy.lang.Closure;
import groowt.view.component.ComponentTemplate;
public class WebViewComponentChildCollectorClosure extends Closure<Object> {
public static Closure<?> get(ComponentTemplate template, Closure<?> collectorClosure) {
return new WebViewComponentChildCollectorClosure(template, collectorClosure);
}
private final ComponentTemplate template;
private final Closure<?> collectorClosure;
private WebViewComponentChildCollectorClosure(ComponentTemplate template, Closure<?> collectorClosure) {
super(template, template);
this.template = template;
this.collectorClosure = collectorClosure;
}
public ComponentTemplate getTemplate() {
return this.template;
}
public Object doCall(WebViewComponentChildCollector childCollector) {
return this.collectorClosure.call(childCollector);
}
}

View File

@ -1,14 +1,30 @@
package groowt.view.web.runtime; package groowt.view.web.runtime;
import groovy.lang.Closure;
import groowt.view.component.ViewComponent; import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.RenderContext; import groowt.view.component.runtime.RenderContext;
import groowt.view.web.WebViewComponent; import groowt.view.web.WebViewComponent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import java.util.Map;
public interface WebViewComponentRenderContext extends RenderContext { public interface WebViewComponentRenderContext extends RenderContext {
@ApiStatus.Internal Map<String, Object> EMPTY_ATTR = Map.of();
ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl); Object[] EMPTY_CONSTRUCTOR_ARGS = {};
WebViewComponent create(
Resolved<? extends WebViewComponent> resolved,
Map<String, Object> attr,
Object[] constructorArgs
);
WebViewComponent create(
Resolved<? extends WebViewComponent> resolved,
Map<String, Object> attr,
Object[] constructorArgs,
Closure<Void> childrenClosure
);
ViewComponent createFragment(WebViewComponent fragment, Closure<Void> childrenClosure);
} }

View File

@ -13,8 +13,6 @@ public interface AppendOrAddStatementFactory {
ADD, APPEND ADD, APPEND
} }
Statement addOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
Statement appendOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide); Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide);
default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) { default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) {

View File

@ -1,10 +1,7 @@
package groowt.view.web.transpile; package groowt.view.web.transpile;
import groovy.lang.Tuple2; import groovy.lang.Tuple2;
import groowt.view.web.ast.NodeUtil;
import groowt.view.web.ast.node.BodyChildNode; import groowt.view.web.ast.node.BodyChildNode;
import groowt.view.web.ast.node.ComponentNode;
import groowt.view.web.ast.node.GStringBodyTextNode;
import groowt.view.web.transpile.TranspilerUtil.TranspilerState; import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement;
@ -29,8 +26,8 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
BodyChildNode bodyChildNode, BodyChildNode bodyChildNode,
Expression rightSide, Expression rightSide,
VariableExpression target, VariableExpression target,
String methodName, String methodName // ,
boolean addLineAndColumn // boolean addLineAndColumn
) { ) {
final ArgumentListExpression args; final ArgumentListExpression args;
if (rightSide instanceof ArgumentListExpression argumentListExpression) { if (rightSide instanceof ArgumentListExpression argumentListExpression) {
@ -39,33 +36,31 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
args = new ArgumentListExpression(); args = new ArgumentListExpression();
args.addExpression(rightSide); args.addExpression(rightSide);
} }
if (addLineAndColumn && // if (addLineAndColumn &&
NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) { // NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
this.addLineAndColumn(bodyChildNode, args); // this.addLineAndColumn(bodyChildNode, args);
} // }
final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args); final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args);
return new ExpressionStatement(outExpression); return new ExpressionStatement(outExpression);
} }
@Override protected Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
public Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate( return this.doCreate(
bodyChildNode, bodyChildNode,
rightSide, rightSide,
new VariableExpression(state.getCurrentChildCollector()), state.getCurrentChildList(),
TranspilerUtil.ADD, TranspilerUtil.ADD //,
false // false
); );
} }
@Override protected Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
public Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate( return this.doCreate(
bodyChildNode, bodyChildNode,
rightSide, rightSide,
new VariableExpression(state.getWriter()), state.getWriter(),
TranspilerUtil.APPEND, TranspilerUtil.APPEND //,
true // false
); );
} }
@ -75,7 +70,7 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
TranspilerState state, TranspilerState state,
Function<Action, Expression> getRightSide Function<Action, Expression> getRightSide
) { ) {
if (state.hasCurrentChildCollector()) { if (state.hasCurrentChildList()) {
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD)); return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
} else { } else {
return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND)); return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND));

View File

@ -2,11 +2,8 @@ package groowt.view.web.transpile;
import groowt.view.component.context.ComponentResolveException; import groowt.view.component.context.ComponentResolveException;
import groowt.view.component.runtime.ComponentCreateException; import groowt.view.component.runtime.ComponentCreateException;
import groowt.view.component.runtime.RenderContext;
import groowt.view.web.WebViewComponentBugError; import groowt.view.web.WebViewComponentBugError;
import groowt.view.web.ast.node.*; import groowt.view.web.ast.node.*;
import groowt.view.web.runtime.WebViewComponentChildCollector;
import groowt.view.web.runtime.WebViewComponentChildCollectorClosure;
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver; import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
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;
@ -25,11 +22,7 @@ import static groowt.view.web.transpile.TranspilerUtil.*;
public class DefaultComponentTranspiler implements ComponentTranspiler { public class DefaultComponentTranspiler implements ComponentTranspiler {
private static final ClassNode CHILD_COLLECTOR_TYPE = ClassHelper.make(WebViewComponentChildCollector.class);
private static final ClassNode FRAGMENT_TYPE = ClassHelper.make(GROOWT_VIEW_WEB + ".lib.Fragment"); private static final ClassNode FRAGMENT_TYPE = ClassHelper.make(GROOWT_VIEW_WEB + ".lib.Fragment");
private static final ClassNode RESOLVED_TYPE = ClassHelper.make(RenderContext.Resolved.class);
private static final ClassNode CHILD_COLLECTOR_CLOSURE_TYPE =
ClassHelper.make(WebViewComponentChildCollectorClosure.class);
private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class); private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class);
private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class); private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
@ -71,27 +64,17 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* UTIL */ /* UTIL */
private void addLineAndColumn(Node sourceNode, ArgumentListExpression args) {
final var lineAndColumn = lineAndColumn(sourceNode.getTokenRange().getStartPosition());
args.addExpression(lineAndColumn.getV1());
args.addExpression(lineAndColumn.getV2());
}
protected String getComponentName(int componentNumber) { protected String getComponentName(int componentNumber) {
return "c" + componentNumber; return "c" + componentNumber;
} }
/* RESOLVED DECLARATION */ /* RESOLVED DECLARATION */
// RenderContext.Resolved c0Resolved // def c0Resolved
protected Statement getResolvedDeclaration(TranspilerState state) { protected Statement getResolvedDeclaration(TranspilerState state) {
final ClassNode resolvedType = RESOLVED_TYPE.getPlainNodeReference();
resolvedType.setGenericsTypes(
new GenericsType[] { new GenericsType(WEB_VIEW_COMPONENT_TYPE) }
);
final var resolvedVariable = new VariableExpression( final var resolvedVariable = new VariableExpression(
this.getComponentName(state.newComponentNumber()) + "Resolved", this.getComponentName(state.newComponentNumber()) + "Resolved",
resolvedType ClassHelper.dynamicType()
); );
state.pushResolved(resolvedVariable); state.pushResolved(resolvedVariable);
final var declarationExpr = new DeclarationExpression( final var declarationExpr = new DeclarationExpression(
@ -253,10 +236,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* TYPED COMPONENT DECLARATION */ /* TYPED COMPONENT DECLARATION */
// ViewComponent c0 // def c0
protected Statement getTypedComponentDeclaration(TranspilerState state) { protected Statement getTypedComponentDeclaration(TranspilerState state) {
final VariableExpression componentVariable = new VariableExpression( final VariableExpression componentVariable = new VariableExpression(
this.getComponentName(state.getCurrentComponentNumber()), WEB_VIEW_COMPONENT_TYPE this.getComponentName(state.getCurrentComponentNumber()), ClassHelper.dynamicType()
); );
state.pushComponent(componentVariable); state.pushComponent(componentVariable);
state.getCurrentScope().putDeclaredVariable(componentVariable); state.getCurrentScope().putDeclaredVariable(componentVariable);
@ -285,7 +268,11 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
// [key: value, ...] // [key: value, ...]
protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) { protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) {
if (attributeNodes.isEmpty()) { if (attributeNodes.isEmpty()) {
throw new WebViewComponentBugError(new IllegalArgumentException("attributeNodes cannot be empty.")); throw new WebViewComponentBugError(new IllegalArgumentException(
"Did not expect attributeNodes to be empty. " +
"If you intend to have it empty, use instead a static reference to " +
"WebViewRenderContext.EMPTY_ATTR."
));
} }
final var result = new MapExpression(); final var result = new MapExpression();
attributeNodes.stream() attributeNodes.stream()
@ -315,91 +302,108 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* COMPONENT CHILDREN */ /* COMPONENT CHILDREN */
// c0cc.add (jString | gString | component) // c0childList << (jString | gString | component)
protected Statement getChildCollectorAdd(Variable childCollector, Expression toAdd) { protected Statement getChildListAdd(Parameter childList, Expression toAdd) {
final VariableExpression childCollectorVariableExpr = new VariableExpression(childCollector); final BinaryExpression leftShiftExpression = new BinaryExpression(
final MethodCallExpression methodCall = new MethodCallExpression( new VariableExpression(childList),
childCollectorVariableExpr, getLeftShiftToken(),
"add", toAdd
new ArgumentListExpression(List.of(toAdd))
); );
return new ExpressionStatement(methodCall); return new ExpressionStatement(leftShiftExpression);
} }
// { WebViewComponentChildCollector c0cc -> ... } // { List c0childList -> ... }
protected ClosureExpression getChildCollectorClosure( protected ClosureExpression getChildrenClosure(
BodyNode bodyNode, BodyNode bodyNode,
TranspilerState state TranspilerState state
) { ) {
final Parameter ccParam = new Parameter( final ClassNode childListType = ClassHelper.LIST_TYPE.getPlainNodeReference();
CHILD_COLLECTOR_TYPE, childListType.setGenericsTypes(new GenericsType[] { new GenericsType(ClassHelper.OBJECT_TYPE) });
this.getComponentName(state.getCurrentComponentNumber()) + "cc" final Parameter childListParam = new Parameter(
childListType,
this.getComponentName(state.getCurrentComponentNumber()) + "childList"
); );
final var scope = state.pushScope(); final var scope = state.pushScope();
scope.putDeclaredVariable(ccParam); scope.putDeclaredVariable(childListParam);
state.pushChildCollector(ccParam); state.pushChildList(childListParam);
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody( final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
bodyNode, bodyNode,
(sourceNode, expr) -> this.getChildCollectorAdd(ccParam, expr), (sourceNode, expr) -> this.getChildListAdd(childListParam, expr),
state state
); );
// clean up // clean up
state.popChildCollector(); state.popChildList();
state.popScope(); state.popScope();
return new ClosureExpression( return new ClosureExpression(
new Parameter[] { ccParam }, new Parameter[] { childListParam },
bodyStatements bodyStatements
); );
} }
protected StaticMethodCallExpression getChildCollectorGetter(
BodyNode bodyNode,
TranspilerState state
) {
final ArgumentListExpression args = new ArgumentListExpression();
args.addExpression(VariableExpression.THIS_EXPRESSION);
args.addExpression(this.getChildCollectorClosure(bodyNode, state));
return new StaticMethodCallExpression(
CHILD_COLLECTOR_CLOSURE_TYPE,
"get",
args
);
}
/* TYPED COMPONENT CREATE: expression and statement */ /* TYPED COMPONENT CREATE: expression and statement */
// context.create(...) {...} // context.create(resolved, attr, constructorArgs) { ... }
protected MethodCallExpression getTypedComponentCreateExpression( protected MethodCallExpression getTypedComponentCreateExpression(
TypedComponentNode componentNode, TypedComponentNode componentNode,
TranspilerState state TranspilerState state
) { ) {
final var createArgs = new ArgumentListExpression(); final var createArgs = new ArgumentListExpression();
createArgs.addExpression(new VariableExpression(state.getCurrentResolved())); final VariableExpression resolvedVariableExpression;
final Variable currentResolved = state.getCurrentResolved();
final List<AttrNode> attributeNodes = componentNode.getArgs().getAttributes(); if (currentResolved instanceof VariableExpression) {
if (!attributeNodes.isEmpty()) { resolvedVariableExpression = (VariableExpression) currentResolved;
createArgs.addExpression(this.getAttrMap(attributeNodes, state)); } else {
resolvedVariableExpression = new VariableExpression(currentResolved);
} }
createArgs.addExpression(resolvedVariableExpression);
final List<AttrNode> attrNodes = componentNode.getArgs().getAttributes();
if (attrNodes.isEmpty()) {
final ClassExpression webViewComponentRenderContextClassExpression = new ClassExpression(
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
);
final PropertyExpression emptyAttrMapPropertyExpression = new PropertyExpression(
webViewComponentRenderContextClassExpression,
"EMPTY_ATTR"
);
createArgs.addExpression(emptyAttrMapPropertyExpression);
} else {
createArgs.addExpression(this.getAttrMap(attrNodes, state));
}
final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor(); final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor();
if (constructorNode != null) { if (constructorNode == null) {
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression); final ClassExpression webViewComponentRenderContextClassExpression = new ClassExpression(
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
);
final PropertyExpression emptyConstructorArgsPropertyExpression = new PropertyExpression(
webViewComponentRenderContextClassExpression,
"EMPTY_CONSTRUCTOR_ARGS"
);
createArgs.addExpression(emptyConstructorArgsPropertyExpression);
} else {
final List<Expression> constructorArgs = this.getConstructorArgs(constructorNode);
final ArrayExpression constructorArgsArrayExpr = new ArrayExpression(
ClassHelper.OBJECT_TYPE,
constructorArgs
);
createArgs.addExpression(constructorArgsArrayExpr);
} }
final @Nullable BodyNode bodyNode = componentNode.getBody(); final @Nullable BodyNode bodyNode = componentNode.getBody();
if (bodyNode != null) { if (bodyNode != null) {
createArgs.addExpression(this.getChildCollectorGetter(bodyNode, state)); createArgs.addExpression(this.getChildrenClosure(bodyNode, state));
} }
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs); return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
} }
// c0 = context.create(context.resolve(''), [:], ...) {...} // c0 = context.create(context.resolve(''), [:], new Object[] { ... }) {...}
protected ExpressionStatement getTypedComponentCreateStatement( protected ExpressionStatement getTypedComponentCreateStatement(
TypedComponentNode componentNode, TypedComponentNode componentNode,
TranspilerState state TranspilerState state
@ -463,7 +467,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* FRAGMENT COMPONENT */ /* FRAGMENT COMPONENT */
// context.createFragment(new Fragment(), <child cl>) // context.createFragment(new Fragment()) { ... }
protected MethodCallExpression getFragmentCreateExpression( protected MethodCallExpression getFragmentCreateExpression(
FragmentComponentNode componentNode, FragmentComponentNode componentNode,
TranspilerState state TranspilerState state
@ -472,7 +476,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
FRAGMENT_TYPE, FRAGMENT_TYPE,
ArgumentListExpression.EMPTY_ARGUMENTS ArgumentListExpression.EMPTY_ARGUMENTS
); );
final Expression ccClosure = this.getChildCollectorGetter(componentNode.getBody(), state); final Expression ccClosure = this.getChildrenClosure(componentNode.getBody(), state);
final ArgumentListExpression args = new ArgumentListExpression(List.of(fragmentConstructor, ccClosure)); final ArgumentListExpression args = new ArgumentListExpression(List.of(fragmentConstructor, ccClosure));
@ -496,7 +500,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend( final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
componentNode, componentNode,
state, state,
new VariableExpression(state.getCurrentComponent()) (VariableExpression) state.getCurrentComponent()
); );
// cleanup // cleanup

View File

@ -10,7 +10,7 @@ import groowt.view.web.ast.node.PreambleNode;
import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException; import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException;
import groowt.view.web.compiler.WebViewComponentTemplateCompileException; import groowt.view.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit; import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.web.runtime.DefaultWebViewComponentRenderContext; import groowt.view.web.runtime.DefaultWebViewRenderContext;
import groowt.view.web.transpile.resolve.ClassLoaderComponentClassNodeResolver; import groowt.view.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
import groowt.view.web.transpile.util.GroovyUtil; import groowt.view.web.transpile.util.GroovyUtil;
import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.*;
@ -44,7 +44,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class); private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION = private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION =
ClassHelper.make(DefaultWebViewComponentRenderContext.class); ClassHelper.make(DefaultWebViewRenderContext.class);
protected TranspilerConfiguration getConfiguration( protected TranspilerConfiguration getConfiguration(
WebViewComponentTemplateCompileUnit compileUnit, WebViewComponentTemplateCompileUnit compileUnit,
@ -183,7 +183,6 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib"); moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib");
moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE); moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE);
moduleNode.addImport(COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE); moduleNode.addImport(COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE);
moduleNode.addImport(WEB_VIEW_COMPONENT_TYPE.getNameWithoutPackage(), WEB_VIEW_COMPONENT_TYPE);
moduleNode.addStarImport("groowt.view.component.runtime"); moduleNode.addStarImport("groowt.view.component.runtime");
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".runtime"); moduleNode.addStarImport(GROOWT_VIEW_WEB + ".runtime");
@ -209,7 +208,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME); final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME);
final VariableExpression renderContextVariable = new VariableExpression( final VariableExpression renderContextVariable = new VariableExpression(
RENDER_CONTEXT_NAME, RENDER_CONTEXT_NAME,
RENDER_CONTEXT_TYPE WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
); );
// closure body // closure body
@ -284,7 +283,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
)); ));
} }
return expr; return expr;
}), }
),
state state
) )
); );

View File

@ -23,7 +23,7 @@ public final class TranspilerUtil {
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class); public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
public static final ClassNode COMPONENT_CONTEXT_TYPE = ClassHelper.make(ComponentContext.class); public static final ClassNode COMPONENT_CONTEXT_TYPE = ClassHelper.make(ComponentContext.class);
public static final ClassNode COMPONENT_WRITER_TYPE = ClassHelper.make(ComponentWriter.class); public static final ClassNode COMPONENT_WRITER_TYPE = ClassHelper.make(ComponentWriter.class);
public static final ClassNode RENDER_CONTEXT_TYPE = ClassHelper.make(WebViewComponentRenderContext.class); public static final ClassNode WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE = ClassHelper.make(WebViewComponentRenderContext.class);
public static final ClassNode WEB_VIEW_COMPONENT_TYPE = ClassHelper.make(WebViewComponent.class); public static final ClassNode WEB_VIEW_COMPONENT_TYPE = ClassHelper.make(WebViewComponent.class);
public static final String GROOWT_VIEW_WEB = "groowt.view.web"; public static final String GROOWT_VIEW_WEB = "groowt.view.web";
@ -51,6 +51,10 @@ public final class TranspilerUtil {
return new Token(Types.ASSIGN, "=", -1, -1); return new Token(Types.ASSIGN, "=", -1, -1);
} }
public static Token getLeftShiftToken() {
return new Token(Types.LEFT_SHIFT, "<<", -1, -1);
}
public static final class TranspilerState { public static final class TranspilerState {
public static TranspilerState withRootScope( public static TranspilerState withRootScope(
@ -71,15 +75,17 @@ public final class TranspilerUtil {
final VariableScope rootScope = new VariableScope(); final VariableScope rootScope = new VariableScope();
rootScope.putDeclaredVariable(new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME)); rootScope.putDeclaredVariable(new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME));
rootScope.putDeclaredVariable(new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME)); rootScope.putDeclaredVariable(new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME));
rootScope.putDeclaredVariable(new VariableExpression(RENDER_CONTEXT_NAME, RENDER_CONTEXT_TYPE)); rootScope.putDeclaredVariable(new VariableExpression(RENDER_CONTEXT_NAME,
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
));
return new TranspilerState(rootScope); return new TranspilerState(rootScope);
} }
private final AtomicInteger componentNumberCounter = new AtomicInteger(); private final AtomicInteger componentNumberCounter = new AtomicInteger();
private final Deque<VariableScope> scopeStack = new LinkedList<>(); private final Deque<VariableScope> scopeStack = new LinkedList<>();
private final Deque<Variable> componentStack = new LinkedList<>(); private final Deque<VariableExpression> componentStack = new LinkedList<>();
private final Deque<Variable> resolvedStack = new LinkedList<>(); private final Deque<VariableExpression> resolvedStack = new LinkedList<>();
private final Deque<Variable> childCollectorStack = new LinkedList<>(); private final Deque<Parameter> childListStack = new LinkedList<>();
private final List<ComponentTemplateCompileException> errors = new ArrayList<>(); private final List<ComponentTemplateCompileException> errors = new ArrayList<>();
private int lastComponentNumber; private int lastComponentNumber;
@ -112,10 +118,6 @@ public final class TranspilerUtil {
return Objects.requireNonNull(this.scopeStack.peek()); return Objects.requireNonNull(this.scopeStack.peek());
} }
public void putToCurrentScope(Variable variable) {
this.getCurrentScope().putDeclaredVariable(variable);
}
private Variable getDeclaredVariable(String name) { private Variable getDeclaredVariable(String name) {
VariableScope scope = this.getCurrentScope(); VariableScope scope = this.getCurrentScope();
while (scope != null) { while (scope != null) {
@ -129,15 +131,15 @@ public final class TranspilerUtil {
throw new NullPointerException("Cannot find variable: " + name); throw new NullPointerException("Cannot find variable: " + name);
} }
public Variable getWriter() { public VariableExpression getWriter() {
return this.getDeclaredVariable(COMPONENT_WRITER_NAME); return new VariableExpression(this.getDeclaredVariable(COMPONENT_WRITER_NAME));
} }
public Variable getRenderContext() { public VariableExpression getRenderContext() {
return this.getDeclaredVariable(RENDER_CONTEXT_NAME); return (VariableExpression) this.getDeclaredVariable(RENDER_CONTEXT_NAME);
} }
public void pushComponent(Variable componentVariable) { public void pushComponent(VariableExpression componentVariable) {
this.componentStack.push(componentVariable); this.componentStack.push(componentVariable);
} }
@ -145,11 +147,11 @@ public final class TranspilerUtil {
this.componentStack.pop(); this.componentStack.pop();
} }
public Variable getCurrentComponent() { public VariableExpression getCurrentComponent() {
return Objects.requireNonNull(this.componentStack.peek()); return Objects.requireNonNull(this.componentStack.peek());
} }
public void pushResolved(Variable resolvedVariable) { public void pushResolved(VariableExpression resolvedVariable) {
this.resolvedStack.push(resolvedVariable); this.resolvedStack.push(resolvedVariable);
} }
@ -157,24 +159,25 @@ public final class TranspilerUtil {
this.resolvedStack.pop(); this.resolvedStack.pop();
} }
public Variable getCurrentResolved() { public VariableExpression getCurrentResolved() {
return Objects.requireNonNull(this.resolvedStack.peek()); return Objects.requireNonNull(this.resolvedStack.peek());
} }
public void pushChildCollector(Variable childCollector) { public void pushChildList(Parameter childCollector) {
this.childCollectorStack.push(childCollector); this.childListStack.push(childCollector);
} }
public void popChildCollector() { public void popChildList() {
this.childCollectorStack.pop(); this.childListStack.pop();
} }
public Variable getCurrentChildCollector() { public VariableExpression getCurrentChildList() {
return Objects.requireNonNull(this.childCollectorStack.peek()); final Parameter childCollectorParam = Objects.requireNonNull(this.childListStack.peek());
return new VariableExpression(childCollectorParam);
} }
public boolean hasCurrentChildCollector() { public boolean hasCurrentChildList() {
return this.childCollectorStack.peek() != null; return this.childListStack.peek() != null;
} }
public void addError(ComponentTemplateCompileException error) { public void addError(ComponentTemplateCompileException error) {

View File

@ -1,16 +1,11 @@
package groowt.view.web package groowt.view.web
import groowt.view.component.factory.ComponentFactories import groowt.view.component.factory.ComponentFactories
import groowt.view.component.factory.ComponentFactory
import groowt.view.web.lib.AbstractWebViewComponentTests import groowt.view.web.lib.AbstractWebViewComponentTests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class BaseWebViewComponentTests extends AbstractWebViewComponentTests { class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
private static final ComponentFactory<Greeter> greeterFactory = WebViewComponentFactories.withAttr(Greeter) {
new Greeter(it)
}
static final class Greeter extends BaseWebViewComponent { static final class Greeter extends BaseWebViewComponent {
private final String target private final String target
@ -50,8 +45,9 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
@Test @Test
void nestedGreeter() { void nestedGreeter() {
def context = this.context { def context = this.context {
this.configureContext(it) getRootScope(DefaultWebViewComponentScope).with {
currentScope.add(Greeter, greeterFactory) addWithAttr(Greeter)
}
} }
this.doTest('<BaseWebViewComponentTests.Greeter target="World" />', 'Hello, World!', context) this.doTest('<BaseWebViewComponentTests.Greeter target="World" />', 'Hello, World!', context)
} }
@ -59,9 +55,10 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
@Test @Test
void doubleNested() { void doubleNested() {
def context = this.context { def context = this.context {
this.configureContext(it) getRootScope(DefaultWebViewComponentScope).with {
currentScope.add(UsingGreeter, ComponentFactories.ofSupplier { new UsingGreeter() }) addWithAttr(Greeter)
currentScope.add(Greeter, greeterFactory) add(UsingGreeter, ComponentFactories.ofSupplier { new UsingGreeter() })
}
} }
this.doTest('<BaseWebViewComponentTests.UsingGreeter />', 'Hello, World!', context) this.doTest('<BaseWebViewComponentTests.UsingGreeter />', 'Hello, World!', context)
} }

View File

@ -1,6 +1,5 @@
package groowt.view.web.lib package groowt.view.web.lib
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class EchoTests extends AbstractWebViewComponentTests { class EchoTests extends AbstractWebViewComponentTests {
@ -20,4 +19,9 @@ class EchoTests extends AbstractWebViewComponentTests {
this.doTest('<Echo>Hello, World!</Echo>', 'Hello, World!') this.doTest('<Echo>Hello, World!</Echo>', 'Hello, World!')
} }
@Test
void childrenCanUseProperties() {
this.doTest('<Echo greeting="Hello, World!">$greeting</Echo>', 'Hello, World!')
}
} }

View File

@ -1,11 +1,10 @@
package groowt.view.web.lib package groowt.view.web.lib
import groowt.view.web.BaseWebViewComponent import groowt.view.web.BaseWebViewComponent
import groowt.view.web.DefaultWebViewComponentScope
import groowt.view.web.WebViewComponentContext import groowt.view.web.WebViewComponentContext
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 BaseWebViewComponent { static class Greeter extends BaseWebViewComponent {
@ -21,8 +20,9 @@ class FragmentTests extends AbstractWebViewComponentTests {
@Override @Override
void configureContext(WebViewComponentContext context) { void configureContext(WebViewComponentContext context) {
def greeterFactory = withAttr(Greeter, Greeter.&new) context.getRootScope(DefaultWebViewComponentScope).with {
context.currentScope.add(Greeter, greeterFactory) addWithAttr(Greeter)
}
} }
@Test @Test

View File

@ -1,6 +1,5 @@
package groowt.view.web.lib package groowt.view.web.lib
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class IntrinsicHtmlTests extends AbstractWebViewComponentTests { class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
@ -21,9 +20,8 @@ class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
} }
@Test @Test
@Disabled('Until we figure out nested closure delegates')
void canUseEchoAttrPropertyViaContext() { void canUseEchoAttrPropertyViaContext() {
this.doTest('<Echo greeting="Hello!"><p>${context}</p></Echo>', '<p>Hello!</p>') this.doTest('<Echo greeting="Hello!"><p>$greeting</p></Echo>', '<p>Hello!</p>')
} }
} }

View File

@ -2,9 +2,10 @@ package groowt.view.web.tools
import groovy.console.ui.AstNodeToScriptVisitor import groovy.console.ui.AstNodeToScriptVisitor
import groowt.view.component.compiler.source.ComponentTemplateSource import groowt.view.component.compiler.source.ComponentTemplateSource
import groowt.view.component.compiler.util.GroovyClassWriter
import groowt.view.component.compiler.util.SimpleGroovyClassWriter
import groowt.view.web.compiler.AnonymousWebViewComponent import groowt.view.web.compiler.AnonymousWebViewComponent
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit
import org.codehaus.groovy.tools.GroovyClass
import picocli.CommandLine import picocli.CommandLine
import java.util.concurrent.Callable import java.util.concurrent.Callable
@ -48,15 +49,12 @@ class ConvertToGroovy implements Callable<Integer> {
@CommandLine.Option( @CommandLine.Option(
names = ['-d', '--classesDir'], names = ['-d', '--classesDir'],
description = 'If the GroovyCompiler outputs classes, where to write them.' description = 'If the GroovyCompiler outputs classes, where to write them, relative to the target.',
defaultValue = 'classes'
) )
File classesDir File classesDir
private void writeClass(File classesDir, GroovyClass groovyClass) { private final GroovyClassWriter groovyClassWriter = new SimpleGroovyClassWriter()
new File(classesDir, groovyClass.name + '.class').withOutputStream {
it.write(groovyClass.bytes)
}
}
@Override @Override
Integer call() throws Exception { Integer call() throws Exception {
@ -85,12 +83,11 @@ class ConvertToGroovy implements Callable<Integer> {
} }
if (this.doClasses) { if (this.doClasses) {
def classesDir = this.classesDir != null def classesDir = target.parentFile.toPath().resolve(this.classesDir.toPath())
? this.classesDir this.groovyClassWriter.writeTo(classesDir, compileResult.templateClass)
: new File(target.parentFile, 'classes') compileResult.otherClasses.each {
classesDir.mkdirs() this.groovyClassWriter.writeTo(classesDir, it)
this.writeClass(classesDir, compileResult.templateClass) }
compileResult.otherClasses.each { this.writeClass(classesDir, it) }
} }
return true return true