diff --git a/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java b/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java index d5d22af..d8a4239 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java +++ b/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java @@ -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> 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 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) this.classLoader.loadClass( templateClassName ); diff --git a/view-components/src/main/java/groowt/view/component/compiler/util/ClassNameUtil.java b/view-components/src/main/java/groowt/view/component/compiler/util/ClassNameUtil.java new file mode 100644 index 0000000..b58c9fc --- /dev/null +++ b/view-components/src/main/java/groowt/view/component/compiler/util/ClassNameUtil.java @@ -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 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 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() {} + +} diff --git a/view-components/src/main/java/groowt/view/component/compiler/util/GroovyClassWriter.java b/view-components/src/main/java/groowt/view/component/compiler/util/GroovyClassWriter.java new file mode 100644 index 0000000..31f1d15 --- /dev/null +++ b/view-components/src/main/java/groowt/view/component/compiler/util/GroovyClassWriter.java @@ -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); + } + +} diff --git a/view-components/src/main/java/groowt/view/component/compiler/util/SimpleGroovyClassWriter.java b/view-components/src/main/java/groowt/view/component/compiler/util/SimpleGroovyClassWriter.java new file mode 100644 index 0000000..ded6e36 --- /dev/null +++ b/view-components/src/main/java/groowt/view/component/compiler/util/SimpleGroovyClassWriter.java @@ -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 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); + } + } + +} diff --git a/view-components/src/main/java/groowt/view/component/context/ComponentContext.java b/view-components/src/main/java/groowt/view/component/context/ComponentContext.java index 172f729..1985cd6 100644 --- a/view-components/src/main/java/groowt/view/component/context/ComponentContext.java +++ b/view-components/src/main/java/groowt/view/component/context/ComponentContext.java @@ -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 getScopeStack(); void pushScope(ComponentScope scope); + void pushDefaultScope(); + void popScope(); + ComponentScope getRootScope(); + default S getRootScope(Class scopeClass) { + return scopeClass.cast(this.getRootScope()); + } + default ComponentScope getCurrentScope() { final List scopeStack = this.getScopeStack(); if (scopeStack.isEmpty()) { @@ -23,8 +33,15 @@ public interface ComponentContext { return scopeStack.getFirst(); } + default S getCurrentScope(Class scopeClass) { + return scopeClass.cast(this.getCurrentScope()); + } + @Nullable ViewComponent getParent(); - @Nullable T getParent(Class parentClass); + + default @Nullable T getParent(Class parentClass) { + return parentClass.cast(this.getParent()); + } @Nullable ViewComponent findNearestAncestor(Predicate matching); diff --git a/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java b/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java index d4c257a..f806226 100644 --- a/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java +++ b/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java @@ -15,7 +15,7 @@ public class DefaultComponentContext implements ComponentContext { private final LinkedList 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 @Nullable T getParent(Class parentClass) { - return parentClass.cast(this.getParent()); - } - @Override public @Nullable ViewComponent findNearestAncestor(Predicate matching) { final List componentStack = this.getRenderContext().getComponentStack(); diff --git a/view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java b/view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java similarity index 70% rename from view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java rename to view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java index 6b3a716..f9d2eae 100644 --- a/view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java +++ b/view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java @@ -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 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); diff --git a/view-components/src/main/java/groowt/view/component/runtime/RenderContext.java b/view-components/src/main/java/groowt/view/component/runtime/RenderContext.java index 6c8fbd7..888f79d 100644 --- a/view-components/src/main/java/groowt/view/component/runtime/RenderContext.java +++ b/view-components/src/main/java/groowt/view/component/runtime/RenderContext.java @@ -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 { @@ -31,8 +29,6 @@ public interface RenderContext { Resolved resolve(String typeName) throws ComponentResolveException; Resolved resolve(String alias, Class type) throws ComponentResolveException; - ViewComponent create(Resolved resolved, Object... args); - void pushComponent(ViewComponent component); void popComponent(ViewComponent component); diff --git a/web-views/docs/asciidoc/componentTemplateSpec.asciidoc b/web-views/docs/asciidoc/componentTemplateSpec.asciidoc index e056018..babdbdf 100644 --- a/web-views/docs/asciidoc/componentTemplateSpec.asciidoc +++ b/web-views/docs/asciidoc/componentTemplateSpec.asciidoc @@ -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`. <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 diff --git a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy index 68ae298..9b3d3cf 100644 --- a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy +++ b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy @@ -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() } } diff --git a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy new file mode 100644 index 0000000..b66fc2e --- /dev/null +++ b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy @@ -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) + } + } + + void addWithAttr(Class componentClass) { + add(componentClass, withAttr(componentClass) { attr -> + InvokerHelper.invokeConstructorOf(componentClass, attr) as T + }) + } + + @Override + TypeAndFactory factoryMissing(String typeName) { + IntrinsicHtml.TYPE_AND_FACTORY + } + +} diff --git a/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy b/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy index efb6576..86caa7d 100644 --- a/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy +++ b/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy @@ -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') Closure closure ) { - ofClosureClassType(forClass) { Map attr -> closure(attr) } - } - - static ComponentFactory withChildren( - Class forClass, - @ClosureParams(value = FromString, options = 'java.util.List') - Closure closure - ) { - ofClosureClassType(forClass) { WebViewComponentChildCollector childCollector -> - closure(childCollector.children) - } - } - - static ComponentFactory withAttrAndChildren( - Class forClass, - @ClosureParams( - value = FromString, - options = 'java.util.Map, java.util.List' - ) - Closure closure - ) { - ofClosureClassType(forClass) { Map attr, WebViewComponentChildCollector childCollector -> - closure(attr, childCollector.children) - } + ofClosureClassType(forClass) { Map attr, Object[] ignored -> closure(attr) } } private WebViewComponentFactories() {} diff --git a/web-views/src/main/groovy/groowt/view/web/WebViewComponentScope.groovy b/web-views/src/main/groovy/groowt/view/web/WebViewComponentScope.groovy deleted file mode 100644 index 2e78ac9..0000000 --- a/web-views/src/main/groovy/groowt/view/web/WebViewComponentScope.groovy +++ /dev/null @@ -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) - } - } - - void addWithAttr(Class componentClass) { - add(componentClass, withAttr(componentClass) { attr -> - InvokerHelper.invokeConstructorOf(componentClass, attr) as T - }) - } - - void addWithChildren(Class componentClass) { - add(componentClass, withChildren(componentClass) { children -> - InvokerHelper.invokeConstructorOf(componentClass, children) as T - }) - } - - void addWithAttrAndChildren(Class 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 - } - -} diff --git a/web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy b/web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy index a176836..89b65ce 100644 --- a/web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy +++ b/web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy @@ -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 FACTORY = new EchoFactory() - - protected static class EchoFactory implements ComponentFactory { - - 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 } } } diff --git a/web-views/src/main/groovy/groowt/view/web/lib/Fragment.groovy b/web-views/src/main/groovy/groowt/view/web/lib/Fragment.groovy index c4c4334..7c6431b 100644 --- a/web-views/src/main/groovy/groowt/view/web/lib/Fragment.groovy +++ b/web-views/src/main/groovy/groowt/view/web/lib/Fragment.groovy @@ -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() } diff --git a/web-views/src/main/groovy/groowt/view/web/lib/IntrinsicHtml.groovy b/web-views/src/main/groovy/groowt/view/web/lib/IntrinsicHtml.groovy index 857e46f..f41d830 100644 --- a/web-views/src/main/groovy/groowt/view/web/lib/IntrinsicHtml.groovy +++ b/web-views/src/main/groovy/groowt/view/web/lib/IntrinsicHtml.groovy @@ -21,11 +21,7 @@ class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml { protected static class IntrinsicHtmlFactory implements ComponentFactory { - IntrinsicHtml doCreate(String typeName) { - new IntrinsicHtml([:], typeName, typeName in voidElements) - } - - IntrinsicHtml doCreate(String typeName, Map attr) { + IntrinsicHtml doCreate(String typeName, Map 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 << ' childRenderers; + private List children; public AbstractWebViewComponent() {} @@ -40,11 +40,11 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp } @Override - public List getChildren() { - if (this.childRenderers == null) { - this.childRenderers = new ArrayList<>(); + public List 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 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); } diff --git a/web-views/src/main/java/groowt/view/web/WebViewComponent.java b/web-views/src/main/java/groowt/view/web/WebViewComponent.java index 654fa34..6006240 100644 --- a/web-views/src/main/java/groowt/view/web/WebViewComponent.java +++ b/web-views/src/main/java/groowt/view/web/WebViewComponent.java @@ -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 getChildren(); + List getChildren(); boolean hasChildren(); - void setChildren(List children); - void renderChildren(); - - default List 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 getChildGStrings() { - return this.getChildren().stream() - .map(WebViewComponentChild::getChild) - .filter(GString.class::isInstance) - .map(GString.class::cast) - .toList(); - } - - default List 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); } diff --git a/web-views/src/main/java/groowt/view/web/WebViewComponentChild.java b/web-views/src/main/java/groowt/view/web/WebViewComponentChild.java deleted file mode 100644 index f6059d6..0000000 --- a/web-views/src/main/java/groowt/view/web/WebViewComponentChild.java +++ /dev/null @@ -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 { - - 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; - } - -} diff --git a/web-views/src/main/java/groowt/view/web/compiler/AnonymousWebViewComponent.java b/web-views/src/main/java/groowt/view/web/compiler/AnonymousWebViewComponent.java index 0dfa48a..df8ad14 100644 --- a/web-views/src/main/java/groowt/view/web/compiler/AnonymousWebViewComponent.java +++ b/web-views/src/main/java/groowt/view/web/compiler/AnonymousWebViewComponent.java @@ -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 getChildren() { + public List getChildren() { throw new UnsupportedOperationException(); } @@ -37,12 +36,12 @@ public final class AnonymousWebViewComponent implements WebViewComponent { } @Override - public void setChildren(List children) { + public void setChildren(List children) { throw new UnsupportedOperationException(); } @Override - public void renderChildren() { + public void renderChildren(Writer to) { throw new UnsupportedOperationException(); } diff --git a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentChildCollector.java b/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentChildCollector.java deleted file mode 100644 index 63e634b..0000000 --- a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentChildCollector.java +++ /dev/null @@ -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 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 getChildren() { - return this.children; - } - -} diff --git a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentRenderContext.java b/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentRenderContext.java deleted file mode 100644 index 0a0d9b9..0000000 --- a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentRenderContext.java +++ /dev/null @@ -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; - } - -} diff --git a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewRenderContext.java b/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewRenderContext.java new file mode 100644 index 0000000..37a3711 --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewRenderContext.java @@ -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 resolved, + Map attr, + Object[] constructorArgs + ) { + final WebViewComponent created; + if (resolved instanceof ResolvedStringType resolvedStringType) { + created = resolvedStringType.componentFactory().create( + resolvedStringType.typeName(), + this.getComponentContext(), + attr, + constructorArgs + ); + } else if (resolved instanceof ResolvedClassType 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 resolved, + Map attr, + Object[] constructorArgs, + Closure childrenClosure + ) { + final WebViewComponent created = this.create(resolved, attr, constructorArgs); + childrenClosure.setDelegate(created); + childrenClosure.setResolveStrategy(Closure.DELEGATE_FIRST); + final List children = new ArrayList<>(); + childrenClosure.call(children); + created.setChildren(children); + return created; + } + + @Override + public ViewComponent createFragment(WebViewComponent fragment, Closure childrenClosure) { + final List children = new ArrayList<>(); + childrenClosure.call(children); + fragment.setChildren(children); + fragment.setContext(this.getComponentContext()); + return fragment; + } + +} diff --git a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollector.java b/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollector.java deleted file mode 100644 index ec197eb..0000000 --- a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollector.java +++ /dev/null @@ -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 getChildren(); -} diff --git a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollectorClosure.java b/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollectorClosure.java deleted file mode 100644 index 9c9de82..0000000 --- a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollectorClosure.java +++ /dev/null @@ -1,29 +0,0 @@ -package groowt.view.web.runtime; - -import groovy.lang.Closure; -import groowt.view.component.ComponentTemplate; - -public class WebViewComponentChildCollectorClosure extends Closure { - - 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); - } - -} diff --git a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentRenderContext.java b/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentRenderContext.java index 4fc34eb..ded92b0 100644 --- a/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentRenderContext.java +++ b/web-views/src/main/java/groowt/view/web/runtime/WebViewComponentRenderContext.java @@ -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 EMPTY_ATTR = Map.of(); + Object[] EMPTY_CONSTRUCTOR_ARGS = {}; + + WebViewComponent create( + Resolved resolved, + Map attr, + Object[] constructorArgs + ); + + WebViewComponent create( + Resolved resolved, + Map attr, + Object[] constructorArgs, + Closure childrenClosure + ); + + ViewComponent createFragment(WebViewComponent fragment, Closure childrenClosure); } diff --git a/web-views/src/main/java/groowt/view/web/transpile/AppendOrAddStatementFactory.java b/web-views/src/main/java/groowt/view/web/transpile/AppendOrAddStatementFactory.java index 8c56e8e..2e5697a 100644 --- a/web-views/src/main/java/groowt/view/web/transpile/AppendOrAddStatementFactory.java +++ b/web-views/src/main/java/groowt/view/web/transpile/AppendOrAddStatementFactory.java @@ -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 getRightSide); default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) { diff --git a/web-views/src/main/java/groowt/view/web/transpile/DefaultAppendOrAddStatementFactory.java b/web-views/src/main/java/groowt/view/web/transpile/DefaultAppendOrAddStatementFactory.java index 0308c90..c95264b 100644 --- a/web-views/src/main/java/groowt/view/web/transpile/DefaultAppendOrAddStatementFactory.java +++ b/web-views/src/main/java/groowt/view/web/transpile/DefaultAppendOrAddStatementFactory.java @@ -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 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)); diff --git a/web-views/src/main/java/groowt/view/web/transpile/DefaultComponentTranspiler.java b/web-views/src/main/java/groowt/view/web/transpile/DefaultComponentTranspiler.java index 751d6bd..10a295a 100644 --- a/web-views/src/main/java/groowt/view/web/transpile/DefaultComponentTranspiler.java +++ b/web-views/src/main/java/groowt/view/web/transpile/DefaultComponentTranspiler.java @@ -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 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 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 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 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(), ) + // 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 diff --git a/web-views/src/main/java/groowt/view/web/transpile/DefaultGroovyTranspiler.java b/web-views/src/main/java/groowt/view/web/transpile/DefaultGroovyTranspiler.java index 86addef..b37c6eb 100644 --- a/web-views/src/main/java/groowt/view/web/transpile/DefaultGroovyTranspiler.java +++ b/web-views/src/main/java/groowt/view/web/transpile/DefaultGroovyTranspiler.java @@ -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 ) ); } diff --git a/web-views/src/main/java/groowt/view/web/transpile/TranspilerUtil.java b/web-views/src/main/java/groowt/view/web/transpile/TranspilerUtil.java index bb9e9e8..05a44a0 100644 --- a/web-views/src/main/java/groowt/view/web/transpile/TranspilerUtil.java +++ b/web-views/src/main/java/groowt/view/web/transpile/TranspilerUtil.java @@ -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 scopeStack = new LinkedList<>(); - private final Deque componentStack = new LinkedList<>(); - private final Deque resolvedStack = new LinkedList<>(); - private final Deque childCollectorStack = new LinkedList<>(); + private final Deque componentStack = new LinkedList<>(); + private final Deque resolvedStack = new LinkedList<>(); + private final Deque childListStack = new LinkedList<>(); private final List 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) { diff --git a/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy b/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy index a758d7f..61fd437 100644 --- a/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy @@ -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 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('', '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('', 'Hello, World!', context) } diff --git a/web-views/src/test/groovy/groowt/view/web/lib/EchoTests.groovy b/web-views/src/test/groovy/groowt/view/web/lib/EchoTests.groovy index 82d27e2..56b4c21 100644 --- a/web-views/src/test/groovy/groowt/view/web/lib/EchoTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/lib/EchoTests.groovy @@ -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('Hello, World!', 'Hello, World!') } + @Test + void childrenCanUseProperties() { + this.doTest('$greeting', 'Hello, World!') + } + } diff --git a/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy b/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy index 16c4566..ccc35b5 100644 --- a/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy @@ -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 diff --git a/web-views/src/test/groovy/groowt/view/web/lib/IntrinsicHtmlTests.groovy b/web-views/src/test/groovy/groowt/view/web/lib/IntrinsicHtmlTests.groovy index 796f015..1d64189 100644 --- a/web-views/src/test/groovy/groowt/view/web/lib/IntrinsicHtmlTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/lib/IntrinsicHtmlTests.groovy @@ -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('

${context}

', '

Hello!

') + this.doTest('

$greeting

', '

Hello!

') } } diff --git a/web-views/src/tools/groovy/groowt/view/web/tools/ConvertToGroovy.groovy b/web-views/src/tools/groovy/groowt/view/web/tools/ConvertToGroovy.groovy index 3c6ad35..2b0e09f 100644 --- a/web-views/src/tools/groovy/groowt/view/web/tools/ConvertToGroovy.groovy +++ b/web-views/src/tools/groovy/groowt/view/web/tools/ConvertToGroovy.groovy @@ -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 { @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 { } 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