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;
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.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
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 {
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 ClassLoader classLoader;
private final Path tempClassesDir;
private final File tempClassesDir;
private final GroovyClassWriter groovyClassWriter;
public SimpleComponentTemplateClassFactory() {
this.groovyClassWriter = new SimpleGroovyClassWriter();
try {
this.tempClassesDir = Files.createTempDirectory("view-component-classes-");
this.tempClassesDir = Files.createTempDirectory("view-component-classes-").toFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
this.classLoader = new URLClassLoader(
"SimpleComponentTemplateClassFactoryClassLoader",
new URL[] { this.tempClassesDir.toUri().toURL() },
new URL[] { this.tempClassesDir.toURI().toURL() },
this.getClass().getClassLoader()
);
} 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
public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) {
final String templateClassName = compileResult.getTemplateClass().getName();
@ -89,11 +45,13 @@ public final class SimpleComponentTemplateClassFactory implements ComponentTempl
return this.cache.get(templateClassName);
} else {
// write classes to disk
this.writeClassToDisk(compileResult.getTemplateClass());
compileResult.getOtherClasses().forEach(this::writeClassToDisk);
this.groovyClassWriter.writeTo(this.tempClassesDir, compileResult.getTemplateClass());
compileResult.getOtherClasses().forEach(groovyClass -> this.groovyClassWriter.writeTo(
this.tempClassesDir, groovyClass
));
// load the template class
try {
//noinspection unchecked
@SuppressWarnings("unchecked")
final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass(
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;
import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.RenderContext;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -8,13 +9,22 @@ import java.util.function.Predicate;
public interface ComponentContext {
RenderContext getRenderContext();
List<ComponentScope> getScopeStack();
void pushScope(ComponentScope scope);
void pushDefaultScope();
void popScope();
ComponentScope getRootScope();
default <S extends ComponentScope> S getRootScope(Class<? extends S> scopeClass) {
return scopeClass.cast(this.getRootScope());
}
default ComponentScope getCurrentScope() {
final List<ComponentScope> scopeStack = this.getScopeStack();
if (scopeStack.isEmpty()) {
@ -23,8 +33,15 @@ public interface ComponentContext {
return scopeStack.getFirst();
}
default <S extends ComponentScope> S getCurrentScope(Class<? extends S> scopeClass) {
return scopeClass.cast(this.getCurrentScope());
}
@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);

View File

@ -15,7 +15,7 @@ public class DefaultComponentContext implements ComponentContext {
private final LinkedList<ComponentScope> scopeStack = new LinkedList<>();
private RenderContext renderContext;
@ApiStatus.Internal
@Override
public RenderContext getRenderContext() {
return Objects.requireNonNull(
this.renderContext,
@ -66,11 +66,6 @@ public class DefaultComponentContext implements ComponentContext {
return null;
}
@Override
public <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
return parentClass.cast(this.getParent());
}
@Override
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
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.List;
public class DefaultRenderContext implements RenderContext {
public abstract class AbstractRenderContext implements RenderContext {
private final ComponentContext componentContext;
private final ComponentWriter writer;
private final LinkedList<ViewComponent> componentStack = new LinkedList<>();
public DefaultRenderContext(ComponentContext componentContext, ComponentWriter writer) {
public AbstractRenderContext(ComponentContext componentContext, ComponentWriter writer) {
this.componentContext = componentContext;
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
public void pushComponent(ViewComponent 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.context.ComponentResolveException;
import groowt.view.component.factory.ComponentFactory;
import org.jetbrains.annotations.ApiStatus;
import java.util.List;
@ApiStatus.Internal
public interface RenderContext {
interface Resolved<T extends ViewComponent> {
@ -31,8 +29,6 @@ public interface RenderContext {
Resolved<?> resolve(String typeName) 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 popComponent(ViewComponent component);

View File

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

View File

@ -6,12 +6,12 @@ import groowt.view.component.context.DefaultComponentContext
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
DefaultWebViewComponentContext() {
this.pushScope(WebViewComponentScope.getDefaultRootScope())
this.pushScope(DefaultWebViewComponentScope.getDefaultRootScope())
}
@Override
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.FromString
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
@ -15,30 +13,7 @@ final class WebViewComponentFactories {
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
Closure<? extends T> closure
) {
ofClosureClassType(forClass) { Map<String, Object> attr -> 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)
}
ofClosureClassType(forClass) { Map<String, Object> attr, Object[] ignored -> closure(attr) }
}
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
import groowt.view.View
import groowt.view.component.context.ComponentContext
import groowt.view.component.factory.ComponentFactory
import groowt.view.component.runtime.DefaultComponentWriter
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
Echo(Map attr) {
@ -48,8 +23,11 @@ class Echo extends DelegatingWebViewComponent {
@Override
protected View getDelegate() {
return {
def componentWriter = new DefaultComponentWriter(it)
componentWriter.setComponentContext(this.context)
componentWriter.setRenderContext(this.context.renderContext) // hacky
this.children.each {
it.render(this)
componentWriter << it
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,45 +1,15 @@
package groowt.view.web;
import groovy.lang.GString;
import groowt.view.component.ViewComponent;
import java.io.Writer;
import java.util.List;
public interface WebViewComponent extends ViewComponent {
List<WebViewComponentChild> getChildren();
List<Object> getChildren();
boolean hasChildren();
void setChildren(List<WebViewComponentChild> children);
void renderChildren();
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();
}
void setChildren(List<?> children);
void renderChildren(Writer to);
}

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;
import groowt.view.component.context.ComponentContext;
import groowt.view.web.WebViewComponentChild;
import groowt.view.web.WebViewComponent;
import org.jetbrains.annotations.ApiStatus;
@ -27,7 +26,7 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
}
@Override
public List<WebViewComponentChild> getChildren() {
public List<Object> getChildren() {
throw new UnsupportedOperationException();
}
@ -37,12 +36,12 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
}
@Override
public void setChildren(List<WebViewComponentChild> children) {
public void setChildren(List<?> children) {
throw new UnsupportedOperationException();
}
@Override
public void renderChildren() {
public void renderChildren(Writer to) {
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;
import groovy.lang.Closure;
import groowt.view.component.ViewComponent;
import groowt.view.component.runtime.RenderContext;
import groowt.view.web.WebViewComponent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public interface WebViewComponentRenderContext extends RenderContext {
@ApiStatus.Internal
ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl);
Map<String, Object> EMPTY_ATTR = Map.of();
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
}
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);
default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) {

View File

@ -1,10 +1,7 @@
package groowt.view.web.transpile;
import groovy.lang.Tuple2;
import groowt.view.web.ast.NodeUtil;
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 org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
@ -29,8 +26,8 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
BodyChildNode bodyChildNode,
Expression rightSide,
VariableExpression target,
String methodName,
boolean addLineAndColumn
String methodName // ,
// boolean addLineAndColumn
) {
final ArgumentListExpression args;
if (rightSide instanceof ArgumentListExpression argumentListExpression) {
@ -39,33 +36,31 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
args = new ArgumentListExpression();
args.addExpression(rightSide);
}
if (addLineAndColumn &&
NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
this.addLineAndColumn(bodyChildNode, args);
}
// if (addLineAndColumn &&
// NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
// this.addLineAndColumn(bodyChildNode, args);
// }
final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args);
return new ExpressionStatement(outExpression);
}
@Override
public Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
protected Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate(
bodyChildNode,
rightSide,
new VariableExpression(state.getCurrentChildCollector()),
TranspilerUtil.ADD,
false
state.getCurrentChildList(),
TranspilerUtil.ADD //,
// false
);
}
@Override
public Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
protected Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate(
bodyChildNode,
rightSide,
new VariableExpression(state.getWriter()),
TranspilerUtil.APPEND,
true
state.getWriter(),
TranspilerUtil.APPEND //,
// false
);
}
@ -75,7 +70,7 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
TranspilerState state,
Function<Action, Expression> getRightSide
) {
if (state.hasCurrentChildCollector()) {
if (state.hasCurrentChildList()) {
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
} else {
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.runtime.ComponentCreateException;
import groowt.view.component.runtime.RenderContext;
import groowt.view.web.WebViewComponentBugError;
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.util.GroovyUtil;
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
@ -25,11 +22,7 @@ import static groowt.view.web.transpile.TranspilerUtil.*;
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 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_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
@ -71,27 +64,17 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* 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) {
return "c" + componentNumber;
}
/* RESOLVED DECLARATION */
// RenderContext.Resolved c0Resolved
// def c0Resolved
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(
this.getComponentName(state.newComponentNumber()) + "Resolved",
resolvedType
ClassHelper.dynamicType()
);
state.pushResolved(resolvedVariable);
final var declarationExpr = new DeclarationExpression(
@ -253,10 +236,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* TYPED COMPONENT DECLARATION */
// ViewComponent c0
// def c0
protected Statement getTypedComponentDeclaration(TranspilerState state) {
final VariableExpression componentVariable = new VariableExpression(
this.getComponentName(state.getCurrentComponentNumber()), WEB_VIEW_COMPONENT_TYPE
this.getComponentName(state.getCurrentComponentNumber()), ClassHelper.dynamicType()
);
state.pushComponent(componentVariable);
state.getCurrentScope().putDeclaredVariable(componentVariable);
@ -285,7 +268,11 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
// [key: value, ...]
protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) {
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();
attributeNodes.stream()
@ -315,91 +302,108 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* COMPONENT CHILDREN */
// c0cc.add (jString | gString | component)
protected Statement getChildCollectorAdd(Variable childCollector, Expression toAdd) {
final VariableExpression childCollectorVariableExpr = new VariableExpression(childCollector);
final MethodCallExpression methodCall = new MethodCallExpression(
childCollectorVariableExpr,
"add",
new ArgumentListExpression(List.of(toAdd))
// c0childList << (jString | gString | component)
protected Statement getChildListAdd(Parameter childList, Expression toAdd) {
final BinaryExpression leftShiftExpression = new BinaryExpression(
new VariableExpression(childList),
getLeftShiftToken(),
toAdd
);
return new ExpressionStatement(methodCall);
return new ExpressionStatement(leftShiftExpression);
}
// { WebViewComponentChildCollector c0cc -> ... }
protected ClosureExpression getChildCollectorClosure(
// { List c0childList -> ... }
protected ClosureExpression getChildrenClosure(
BodyNode bodyNode,
TranspilerState state
) {
final Parameter ccParam = new Parameter(
CHILD_COLLECTOR_TYPE,
this.getComponentName(state.getCurrentComponentNumber()) + "cc"
final ClassNode childListType = ClassHelper.LIST_TYPE.getPlainNodeReference();
childListType.setGenericsTypes(new GenericsType[] { new GenericsType(ClassHelper.OBJECT_TYPE) });
final Parameter childListParam = new Parameter(
childListType,
this.getComponentName(state.getCurrentComponentNumber()) + "childList"
);
final var scope = state.pushScope();
scope.putDeclaredVariable(ccParam);
state.pushChildCollector(ccParam);
scope.putDeclaredVariable(childListParam);
state.pushChildList(childListParam);
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
bodyNode,
(sourceNode, expr) -> this.getChildCollectorAdd(ccParam, expr),
(sourceNode, expr) -> this.getChildListAdd(childListParam, expr),
state
);
// clean up
state.popChildCollector();
state.popChildList();
state.popScope();
return new ClosureExpression(
new Parameter[] { ccParam },
new Parameter[] { childListParam },
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 */
// context.create(...) {...}
// context.create(resolved, attr, constructorArgs) { ... }
protected MethodCallExpression getTypedComponentCreateExpression(
TypedComponentNode componentNode,
TranspilerState state
) {
final var createArgs = new ArgumentListExpression();
createArgs.addExpression(new VariableExpression(state.getCurrentResolved()));
final List<AttrNode> attributeNodes = componentNode.getArgs().getAttributes();
if (!attributeNodes.isEmpty()) {
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
final VariableExpression resolvedVariableExpression;
final Variable currentResolved = state.getCurrentResolved();
if (currentResolved instanceof VariableExpression) {
resolvedVariableExpression = (VariableExpression) currentResolved;
} 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();
if (constructorNode != null) {
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
if (constructorNode == null) {
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();
if (bodyNode != null) {
createArgs.addExpression(this.getChildCollectorGetter(bodyNode, state));
createArgs.addExpression(this.getChildrenClosure(bodyNode, state));
}
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
}
// c0 = context.create(context.resolve(''), [:], ...) {...}
// c0 = context.create(context.resolve(''), [:], new Object[] { ... }) {...}
protected ExpressionStatement getTypedComponentCreateStatement(
TypedComponentNode componentNode,
TranspilerState state
@ -463,7 +467,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* FRAGMENT COMPONENT */
// context.createFragment(new Fragment(), <child cl>)
// context.createFragment(new Fragment()) { ... }
protected MethodCallExpression getFragmentCreateExpression(
FragmentComponentNode componentNode,
TranspilerState state
@ -472,7 +476,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
FRAGMENT_TYPE,
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));
@ -496,7 +500,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
componentNode,
state,
new VariableExpression(state.getCurrentComponent())
(VariableExpression) state.getCurrentComponent()
);
// 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.WebViewComponentTemplateCompileException;
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.util.GroovyUtil;
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 RENDER_CONTEXT_IMPLEMENTATION =
ClassHelper.make(DefaultWebViewComponentRenderContext.class);
ClassHelper.make(DefaultWebViewRenderContext.class);
protected TranspilerConfiguration getConfiguration(
WebViewComponentTemplateCompileUnit compileUnit,
@ -183,7 +183,6 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib");
moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE);
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_WEB + ".runtime");
@ -209,7 +208,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME);
final VariableExpression renderContextVariable = new VariableExpression(
RENDER_CONTEXT_NAME,
RENDER_CONTEXT_TYPE
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
);
// closure body
@ -284,8 +283,9 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
));
}
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_CONTEXT_TYPE = ClassHelper.make(ComponentContext.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 String GROOWT_VIEW_WEB = "groowt.view.web";
@ -51,6 +51,10 @@ public final class TranspilerUtil {
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 TranspilerState withRootScope(
@ -71,15 +75,17 @@ public final class TranspilerUtil {
final VariableScope rootScope = new VariableScope();
rootScope.putDeclaredVariable(new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_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);
}
private final AtomicInteger componentNumberCounter = new AtomicInteger();
private final Deque<VariableScope> scopeStack = new LinkedList<>();
private final Deque<Variable> componentStack = new LinkedList<>();
private final Deque<Variable> resolvedStack = new LinkedList<>();
private final Deque<Variable> childCollectorStack = new LinkedList<>();
private final Deque<VariableExpression> componentStack = new LinkedList<>();
private final Deque<VariableExpression> resolvedStack = new LinkedList<>();
private final Deque<Parameter> childListStack = new LinkedList<>();
private final List<ComponentTemplateCompileException> errors = new ArrayList<>();
private int lastComponentNumber;
@ -112,10 +118,6 @@ public final class TranspilerUtil {
return Objects.requireNonNull(this.scopeStack.peek());
}
public void putToCurrentScope(Variable variable) {
this.getCurrentScope().putDeclaredVariable(variable);
}
private Variable getDeclaredVariable(String name) {
VariableScope scope = this.getCurrentScope();
while (scope != null) {
@ -129,15 +131,15 @@ public final class TranspilerUtil {
throw new NullPointerException("Cannot find variable: " + name);
}
public Variable getWriter() {
return this.getDeclaredVariable(COMPONENT_WRITER_NAME);
public VariableExpression getWriter() {
return new VariableExpression(this.getDeclaredVariable(COMPONENT_WRITER_NAME));
}
public Variable getRenderContext() {
return this.getDeclaredVariable(RENDER_CONTEXT_NAME);
public VariableExpression getRenderContext() {
return (VariableExpression) this.getDeclaredVariable(RENDER_CONTEXT_NAME);
}
public void pushComponent(Variable componentVariable) {
public void pushComponent(VariableExpression componentVariable) {
this.componentStack.push(componentVariable);
}
@ -145,11 +147,11 @@ public final class TranspilerUtil {
this.componentStack.pop();
}
public Variable getCurrentComponent() {
public VariableExpression getCurrentComponent() {
return Objects.requireNonNull(this.componentStack.peek());
}
public void pushResolved(Variable resolvedVariable) {
public void pushResolved(VariableExpression resolvedVariable) {
this.resolvedStack.push(resolvedVariable);
}
@ -157,24 +159,25 @@ public final class TranspilerUtil {
this.resolvedStack.pop();
}
public Variable getCurrentResolved() {
public VariableExpression getCurrentResolved() {
return Objects.requireNonNull(this.resolvedStack.peek());
}
public void pushChildCollector(Variable childCollector) {
this.childCollectorStack.push(childCollector);
public void pushChildList(Parameter childCollector) {
this.childListStack.push(childCollector);
}
public void popChildCollector() {
this.childCollectorStack.pop();
public void popChildList() {
this.childListStack.pop();
}
public Variable getCurrentChildCollector() {
return Objects.requireNonNull(this.childCollectorStack.peek());
public VariableExpression getCurrentChildList() {
final Parameter childCollectorParam = Objects.requireNonNull(this.childListStack.peek());
return new VariableExpression(childCollectorParam);
}
public boolean hasCurrentChildCollector() {
return this.childCollectorStack.peek() != null;
public boolean hasCurrentChildList() {
return this.childListStack.peek() != null;
}
public void addError(ComponentTemplateCompileException error) {

View File

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

View File

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

View File

@ -1,6 +1,5 @@
package groowt.view.web.lib
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
@ -21,9 +20,8 @@ class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
}
@Test
@Disabled('Until we figure out nested closure delegates')
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 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.WebViewComponentTemplateCompileUnit
import org.codehaus.groovy.tools.GroovyClass
import picocli.CommandLine
import java.util.concurrent.Callable
@ -48,15 +49,12 @@ class ConvertToGroovy implements Callable<Integer> {
@CommandLine.Option(
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
private void writeClass(File classesDir, GroovyClass groovyClass) {
new File(classesDir, groovyClass.name + '.class').withOutputStream {
it.write(groovyClass.bytes)
}
}
private final GroovyClassWriter groovyClassWriter = new SimpleGroovyClassWriter()
@Override
Integer call() throws Exception {
@ -85,12 +83,11 @@ class ConvertToGroovy implements Callable<Integer> {
}
if (this.doClasses) {
def classesDir = this.classesDir != null
? this.classesDir
: new File(target.parentFile, 'classes')
classesDir.mkdirs()
this.writeClass(classesDir, compileResult.templateClass)
compileResult.otherClasses.each { this.writeClass(classesDir, it) }
def classesDir = target.parentFile.toPath().resolve(this.classesDir.toPath())
this.groovyClassWriter.writeTo(classesDir, compileResult.templateClass)
compileResult.otherClasses.each {
this.groovyClassWriter.writeTo(classesDir, it)
}
}
return true