From 3a171b8736d14936820fa1df1dad16969dacd62a Mon Sep 17 00:00:00 2001
From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com>
Date: Wed, 8 May 2024 13:51:49 +0200
Subject: [PATCH] Major refactoring of compiler, transpiler, and general api.
---
.../view/component/AbstractViewComponent.java | 71 +-
.../groowt/view/component/ViewComponent.java | 4 -
.../view/component/ViewComponentBugError.java | 31 +
.../AbstractComponentTemplateCompileUnit.java | 30 +
.../AbstractComponentTemplateCompiler.java | 41 -
.../CachingComponentTemplateCompiler.java | 127 +--
.../ComponentTemplateClassFactory.java | 7 +
...omponentTemplateCompileErrorException.java | 61 --
.../ComponentTemplateCompileException.java | 37 +
.../ComponentTemplateCompileResult.java | 10 +
.../ComponentTemplateCompileUnit.java | 18 +
.../compiler/ComponentTemplateCompiler.java | 31 +-
...omponentTemplateCompilerConfiguration.java | 11 +
...omponentTemplateCompilerConfiguration.java | 44 ++
.../SimpleComponentTemplateClassFactory.java | 108 +++
.../SimpleComponentTemplateCompileResult.java | 50 ++
.../source/ComponentTemplateSource.java | 53 ++
.../component/compiler/source/FileSource.java | 50 ++
.../compiler/source/InputStreamSource.java | 40 +
.../compiler/source/ReaderSource.java | 38 +
.../compiler/source/StringSource.java | 39 +
.../component/compiler/source/URISource.java | 50 ++
.../component/compiler/source/URLSource.java | 58 ++
.../component/context/ComponentContext.java | 54 +-
.../context/ComponentCreateException.java | 37 -
.../context/ComponentResolveException.java | 141 ++++
.../component/context/ComponentScope.java | 62 +-
.../context/DefaultComponentContext.java | 130 +---
.../context/DefaultComponentScope.java | 51 +-
.../FactoryMissingUnsupportedException.java | 48 ++
.../context/MissingClassTypeException.java | 19 -
.../context/MissingComponentException.java | 31 -
.../context/MissingFragmentTypeException.java | 16 -
.../context/MissingStringTypeException.java | 19 -
.../context/NoFactoryMissingException.java | 19 -
...a => AbstractClosureComponentFactory.java} | 43 +-
.../ClassTypeClosureComponentFactory.java | 34 +
.../component/factory/ComponentFactories.java | 27 +
.../component/factory/ComponentFactory.java | 15 +-
.../factory/ComponentFactoryBase.java | 9 +-
.../factory/ComponentTemplateSource.java | 82 --
.../StringTypeClosureComponentFactory.java | 25 +
.../factory/SupplierComponentFactory.java | 20 -
.../runtime/ComponentCreateException.java | 91 +++
.../component/runtime/ComponentWriter.java | 32 +
.../runtime/DefaultComponentWriter.java | 63 +-
.../runtime/DefaultRenderContext.java | 141 ++++
.../view/component/runtime/RenderContext.java | 43 +
web-views/build.gradle | 14 +-
.../asciidoc/componentTemplateSpec.asciidoc | 130 ++++
.../sketching/memberClassComponentType.wvc | 4 +
web-views/sketching/simpleGreeter.wvc | 10 +
.../view/web/BaseWebViewComponent.groovy | 46 ++
.../view/web/DefaultWebViewComponent.groovy | 78 --
.../web/DefaultWebViewComponentContext.groovy | 18 +-
.../view/web/WebViewComponentFactories.groovy | 32 +-
.../view/web/WebViewComponentScope.groovy | 41 +
.../groowt/view/web/WebViewScope.groovy | 14 -
.../web/lib/DelegatingWebViewComponent.java | 15 +-
.../groovy/groowt/view/web/lib/Echo.groovy | 92 +--
.../groowt/view/web/lib/Fragment.groovy | 4 +-
.../groowt/view/web/lib/HtmlPage.groovy | 6 +
.../groowt/view/web/lib/IntrinsicHtml.groovy | 71 +-
.../web/util/ComponentConfigurator.groovy | 27 +
.../web/util/ConfigurableComponent.groovy | 22 +
.../view/web/util/ContextConfigurator.groovy | 27 +
.../groowt/view/web/util/WithHtml.groovy | 30 +
.../view/web/AbstractWebViewComponent.java | 43 +-
.../web/WebViewChildComponentRenderer.java | 19 -
.../view/web/WebViewChildGStringRenderer.java | 23 -
.../view/web/WebViewChildJStringRenderer.java | 18 -
.../groowt/view/web/WebViewChildRenderer.java | 22 -
.../groowt/view/web/WebViewComponent.java | 37 +-
.../view/web/WebViewComponentBugError.java | 31 +
.../view/web/WebViewComponentChild.java | 64 ++
.../view/web/WebViewComponentContext.java | 13 +-
.../antlr/AbstractWebViewComponentsLexer.java | 10 +-
.../java/groowt/view/web/antlr/TokenUtil.kt | 10 +
.../web/ast/node/ClassComponentTypeNode.java | 1 +
.../compiler/AnonymousWebViewComponent.java | 49 ++
...faultWebViewComponentTemplateCompiler.java | 314 ++------
...ebViewComponentCompileErrorsException.java | 38 +-
...ViewComponentTemplateCompileException.java | 81 +-
.../WebViewComponentTemplateCompileUnit.java | 86 ++
.../WebViewComponentTemplateCompiler.java | 21 +-
...efaultWebViewComponentChildCollection.java | 38 -
...DefaultWebViewComponentChildCollector.java | 43 +
.../DefaultWebViewComponentRenderContext.java | 41 +
.../WebViewComponentChildCollection.java | 20 -
.../WebViewComponentChildCollector.java | 14 +
...WebViewComponentChildCollectorClosure.java | 29 +
.../WebViewComponentRenderContext.java | 14 +
.../web/runtime/WebViewComponentWriter.java | 14 -
.../AppendOrAddStatementFactory.java | 4 +
.../web/transpile/ComponentTranspiler.java | 5 +-
.../DefaultAppendOrAddStatementFactory.java | 2 +-
.../web/transpile/DefaultBodyTranspiler.java | 10 +-
.../transpile/DefaultComponentTranspiler.java | 736 ++++++++++--------
.../transpile/DefaultGroovyTranspiler.java | 224 ++++--
.../DefaultTranspilerConfiguration.java | 25 +-
.../transpile/DefaultValueNodeTranspiler.java | 16 +-
.../view/web/transpile/GroovyTranspiler.java | 15 +-
.../view/web/transpile/TranspilerUtil.java | 141 +++-
.../CachingComponentClassNodeResolver.java | 60 ++
.../transpile/resolve/ClassIdentifier.java | 39 -
.../resolve/ClassIdentifierWithFqn.java | 23 -
...ClassLoaderComponentClassNodeResolver.java | 55 ++
.../resolve/ComponentClassNodeResolver.java | 36 +-
.../DefaultComponentClassNodeResolver.java | 187 -----
.../ModuleNodeComponentClassNodeResolver.java | 51 ++
.../web/transpile/resolve/ResolveUtil.java | 31 +
.../java/groowt/view/web/util/Either.java | 54 +-
.../groowt/view/web/util/LazyProvider.java | 26 +
.../java/groowt/view/web/util/Property.java | 5 +
.../java/groowt/view/web/util/Provider.java | 17 +
.../groowt/view/web/util/SimpleProvider.java | 19 +
.../groowt/view/web/sketching/Greeters.groovy | 18 +
...roovy => BaseWebViewComponentTests.groovy} | 34 +-
.../groowt/view/web/lib/EchoTests.groovy | 16 +-
.../groowt/view/web/lib/FragmentTests.groovy | 24 +-
.../DefaultBodyTranspilerTests.java | 15 +-
.../DefaultGroovyTranspilerTests.java | 8 +-
.../transpiler/resolve/ResolveUtilTests.java | 25 +
.../src/{main => test}/resources/log4j2.xml | 2 +-
.../lib/AbstractWebViewComponentTests.groovy | 30 +-
.../groowt/view/web/lib/WithContext.groovy | 13 +-
.../web/transpiler/BodyTranspilerTests.java | 44 +-
.../web/transpiler/GroovyTranspilerTests.java | 46 +-
web-views/src/tools/binTemplate.gst | 2 +-
.../view/web/tools/ConvertToGroovy.groovy | 66 +-
.../groowt/view/web/tools/RunTemplate.groovy | 46 +-
web-views/src/tools/resources/log4j2.xml | 17 +
132 files changed, 3879 insertions(+), 2343 deletions(-)
create mode 100644 view-components/src/main/java/groowt/view/component/ViewComponentBugError.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompileUnit.java
delete mode 100644 view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateClassFactory.java
delete mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileResult.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileUnit.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompilerConfiguration.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/DefaultComponentTemplateCompilerConfiguration.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateCompileResult.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/ComponentTemplateSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/FileSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/InputStreamSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/ReaderSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/StringSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/URISource.java
create mode 100644 view-components/src/main/java/groowt/view/component/compiler/source/URLSource.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/ComponentCreateException.java
create mode 100644 view-components/src/main/java/groowt/view/component/context/ComponentResolveException.java
create mode 100644 view-components/src/main/java/groowt/view/component/context/FactoryMissingUnsupportedException.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/MissingClassTypeException.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/MissingComponentException.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/MissingFragmentTypeException.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/MissingStringTypeException.java
delete mode 100644 view-components/src/main/java/groowt/view/component/context/NoFactoryMissingException.java
rename view-components/src/main/java/groowt/view/component/factory/{ClosureComponentFactory.java => AbstractClosureComponentFactory.java} (58%)
create mode 100644 view-components/src/main/java/groowt/view/component/factory/ClassTypeClosureComponentFactory.java
create mode 100644 view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java
delete mode 100644 view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java
create mode 100644 view-components/src/main/java/groowt/view/component/factory/StringTypeClosureComponentFactory.java
delete mode 100644 view-components/src/main/java/groowt/view/component/factory/SupplierComponentFactory.java
create mode 100644 view-components/src/main/java/groowt/view/component/runtime/ComponentCreateException.java
create mode 100644 view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java
rename web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentWriter.java => view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java (54%)
create mode 100644 view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java
create mode 100644 view-components/src/main/java/groowt/view/component/runtime/RenderContext.java
create mode 100644 web-views/docs/asciidoc/componentTemplateSpec.asciidoc
create mode 100644 web-views/sketching/memberClassComponentType.wvc
create mode 100644 web-views/src/main/groovy/groowt/view/web/BaseWebViewComponent.groovy
delete mode 100644 web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/WebViewComponentScope.groovy
delete mode 100644 web-views/src/main/groovy/groowt/view/web/WebViewScope.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/lib/HtmlPage.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/util/ComponentConfigurator.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/util/ConfigurableComponent.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/util/ContextConfigurator.groovy
create mode 100644 web-views/src/main/groovy/groowt/view/web/util/WithHtml.groovy
delete mode 100644 web-views/src/main/java/groowt/view/web/WebViewChildComponentRenderer.java
delete mode 100644 web-views/src/main/java/groowt/view/web/WebViewChildGStringRenderer.java
delete mode 100644 web-views/src/main/java/groowt/view/web/WebViewChildJStringRenderer.java
delete mode 100644 web-views/src/main/java/groowt/view/web/WebViewChildRenderer.java
create mode 100644 web-views/src/main/java/groowt/view/web/WebViewComponentBugError.java
create mode 100644 web-views/src/main/java/groowt/view/web/WebViewComponentChild.java
create mode 100644 web-views/src/main/java/groowt/view/web/compiler/AnonymousWebViewComponent.java
create mode 100644 web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileUnit.java
delete mode 100644 web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentChildCollection.java
create mode 100644 web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentChildCollector.java
create mode 100644 web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentRenderContext.java
delete mode 100644 web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollection.java
create mode 100644 web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollector.java
create mode 100644 web-views/src/main/java/groowt/view/web/runtime/WebViewComponentChildCollectorClosure.java
create mode 100644 web-views/src/main/java/groowt/view/web/runtime/WebViewComponentRenderContext.java
delete mode 100644 web-views/src/main/java/groowt/view/web/runtime/WebViewComponentWriter.java
create mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/CachingComponentClassNodeResolver.java
delete mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/ClassIdentifier.java
delete mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/ClassIdentifierWithFqn.java
create mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/ClassLoaderComponentClassNodeResolver.java
delete mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/DefaultComponentClassNodeResolver.java
create mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/ModuleNodeComponentClassNodeResolver.java
create mode 100644 web-views/src/main/java/groowt/view/web/transpile/resolve/ResolveUtil.java
create mode 100644 web-views/src/main/java/groowt/view/web/util/LazyProvider.java
create mode 100644 web-views/src/main/java/groowt/view/web/util/Property.java
create mode 100644 web-views/src/main/java/groowt/view/web/util/Provider.java
create mode 100644 web-views/src/main/java/groowt/view/web/util/SimpleProvider.java
create mode 100644 web-views/src/sketching/groovy/groowt/view/web/sketching/Greeters.groovy
rename web-views/src/test/groovy/groowt/view/web/{DefaultWebViewComponentTests.groovy => BaseWebViewComponentTests.groovy} (51%)
create mode 100644 web-views/src/test/groovy/groowt/view/web/transpiler/resolve/ResolveUtilTests.java
rename web-views/src/{main => test}/resources/log4j2.xml (95%)
create mode 100644 web-views/src/tools/resources/log4j2.xml
diff --git a/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java b/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java
index e202d84..e53f4e5 100644
--- a/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java
+++ b/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java
@@ -1,10 +1,8 @@
package groowt.view.component;
import groovy.lang.Closure;
-import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
-import groowt.view.component.compiler.ComponentTemplateCompiler;
+import groowt.view.component.compiler.*;
import groowt.view.component.context.ComponentContext;
-import groowt.view.component.factory.ComponentTemplateSource;
import java.io.IOException;
import java.io.Writer;
@@ -13,44 +11,45 @@ import java.util.function.Function;
public abstract class AbstractViewComponent implements ViewComponent {
- private ComponentContext context;
- private ComponentTemplate template;
+ private static final ComponentTemplateClassFactory templateClassFactory = new SimpleComponentTemplateClassFactory();
- public AbstractViewComponent() {}
-
- public AbstractViewComponent(ComponentTemplate template) {
- this.template = Objects.requireNonNull(template);
- }
-
- public AbstractViewComponent(Class extends ComponentTemplate> templateClass) {
+ private static ComponentTemplate instantiateTemplate(Class extends ComponentTemplate> templateClass) {
try {
- this.template = templateClass.getConstructor().newInstance();
+ return templateClass.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
- protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) {
- try {
- this.template = compiler.compileAndGet(this.getSelfClass(), source);
- } catch (ComponentTemplateCompileErrorException e) {
- throw new RuntimeException(e);
- }
+ private final ComponentTemplate template;
+ private ComponentContext context;
+
+ public AbstractViewComponent() {
+ this.template = null;
}
- protected AbstractViewComponent(
- ComponentTemplateSource source,
- Function super Class extends AbstractViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
+ public AbstractViewComponent(ComponentTemplate template) {
+ this.template = template;
+ }
+
+ public AbstractViewComponent(Class extends ComponentTemplate> templateClass) {
+ this.template = instantiateTemplate(templateClass);
+ }
+
+ public AbstractViewComponent(
+ Function super Class extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
) {
- final var compiler = compilerFunction.apply(this.getSelfClass());
+ final ComponentTemplateCompileResult compileResult;
try {
- this.template = compiler.compileAndGet(this.getSelfClass(), source);
- } catch (ComponentTemplateCompileErrorException e) {
+ compileResult = compileUnitFunction.apply(this.getClass()).compile();
+ } catch (ComponentTemplateCompileException e) {
throw new RuntimeException(e);
}
+ final var templateClass = templateClassFactory.getTemplateClass(compileResult);
+ this.template = instantiateTemplate(templateClass);
}
- protected abstract Class extends AbstractViewComponent> getSelfClass();
+
@Override
public void setContext(ComponentContext context) {
@@ -66,26 +65,20 @@ public abstract class AbstractViewComponent implements ViewComponent {
return Objects.requireNonNull(template);
}
- protected void setTemplate(ComponentTemplate template) {
- this.template = Objects.requireNonNull(template);
- }
+ protected void beforeRender() {}
- protected void beforeRender() {
- this.getContext().beforeComponentRender(this);
- }
-
- protected void afterRender() {
- this.getContext().afterComponentRender(this);
- }
+ protected void afterRender() {}
/**
* @implSpec If overriding, please call
- * {@link #beforeRender()}and {@link #afterRender()} before
- * and after the actual rendering is done, respectively.
+ * {@link #beforeRender()} and {@link #afterRender()} before
+ * and after the actual rendering is done, respectively;
+ * this way, components can still do their before/after
+ * logic even if this method is overwritten.
*/
@Override
public void renderTo(Writer out) throws IOException {
- final Closure> closure = this.template.getRenderer();
+ final Closure> closure = this.getTemplate().getRenderer();
closure.setDelegate(this);
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
this.beforeRender();
diff --git a/view-components/src/main/java/groowt/view/component/ViewComponent.java b/view-components/src/main/java/groowt/view/component/ViewComponent.java
index bdcda41..713a2fc 100644
--- a/view-components/src/main/java/groowt/view/component/ViewComponent.java
+++ b/view-components/src/main/java/groowt/view/component/ViewComponent.java
@@ -5,10 +5,6 @@ import groowt.view.component.context.ComponentContext;
public interface ViewComponent extends View {
- default String getTypeName() {
- return this.getClass().getName();
- }
-
/**
* Note: compiled templates are required to automatically
* call this method after the component is constructed. One
diff --git a/view-components/src/main/java/groowt/view/component/ViewComponentBugError.java b/view-components/src/main/java/groowt/view/component/ViewComponentBugError.java
new file mode 100644
index 0000000..61fa95a
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/ViewComponentBugError.java
@@ -0,0 +1,31 @@
+package groowt.view.component;
+
+public class ViewComponentBugError extends RuntimeException {
+
+ public ViewComponentBugError(String message) {
+ super(message);
+ }
+
+ public ViewComponentBugError(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ViewComponentBugError(Throwable cause) {
+ super(cause);
+ }
+
+ public ViewComponentBugError(
+ String message,
+ Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace
+ ) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ @Override
+ public String getMessage() {
+ return "BUG! Please file an issue report at the github repository. " + super.getMessage();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompileUnit.java b/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompileUnit.java
new file mode 100644
index 0000000..6db1fc4
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompileUnit.java
@@ -0,0 +1,30 @@
+package groowt.view.component.compiler;
+
+import groowt.view.component.ViewComponent;
+import groowt.view.component.compiler.source.ComponentTemplateSource;
+
+public abstract class AbstractComponentTemplateCompileUnit implements
+ ComponentTemplateCompileUnit {
+
+ private final Class extends ViewComponent> forClass;
+ private final ComponentTemplateSource source;
+
+ public AbstractComponentTemplateCompileUnit(
+ Class extends ViewComponent> forClass,
+ ComponentTemplateSource source
+ ) {
+ this.forClass = forClass;
+ this.source = source;
+ }
+
+ @Override
+ public Class extends ViewComponent> getForClass() {
+ return this.forClass;
+ }
+
+ @Override
+ public ComponentTemplateSource getSource() {
+ return this.source;
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java
deleted file mode 100644
index 91ab045..0000000
--- a/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package groowt.view.component.compiler;
-
-import groovy.lang.GroovyClassLoader;
-import groowt.view.component.ComponentTemplate;
-import groowt.view.component.ViewComponent;
-import groowt.view.component.factory.ComponentTemplateSource;
-
-import java.io.IOException;
-import java.io.Reader;
-
-public abstract class AbstractComponentTemplateCompiler implements ComponentTemplateCompiler {
-
- private final GroovyClassLoader groovyClassLoader;
-
- public AbstractComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
- this.groovyClassLoader = groovyClassLoader;
- }
-
- protected abstract ComponentTemplateCompileResult compile(
- ComponentTemplateSource componentTemplateSource,
- Class extends ViewComponent> forClass,
- Reader actualSource
- ) throws ComponentTemplateCompileErrorException;
-
- @Override
- public ComponentTemplateCompileResult compile(Class extends ViewComponent> forClass, ComponentTemplateSource source)
- throws ComponentTemplateCompileErrorException {
- try (final Reader reader = ComponentTemplateSource.toReader(source)) {
- return this.compile(source, forClass, reader);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public ComponentTemplate compileAndGet(Class extends ViewComponent> forClass, ComponentTemplateSource source)
- throws ComponentTemplateCompileErrorException {
- return this.compileAndGet(this.groovyClassLoader, forClass, source);
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java
index e4ae34b..b59def4 100644
--- a/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java
+++ b/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java
@@ -1,104 +1,59 @@
package groowt.view.component.compiler;
-import groovy.lang.GroovyClassLoader;
-import groowt.view.component.ComponentTemplate;
import groowt.view.component.ViewComponent;
-import groowt.view.component.factory.ComponentTemplateSource;
-import org.codehaus.groovy.tools.GroovyClass;
-import org.jetbrains.annotations.Nullable;
-import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
-public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
+public abstract class CachingComponentTemplateCompiler
+ implements ComponentTemplateCompiler {
- private record CachedTemplate(
- ComponentTemplateCompileResult compileResult,
- @Nullable ComponentTemplate template
- ) {}
+ private final Map, ComponentTemplateCompileResult> cache = new HashMap<>();
- private final Map, CachedTemplate> cache = new HashMap<>();
-
- public CachingComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
- super(groovyClassLoader);
- }
-
- private ComponentTemplate instantiate(
- GroovyClassLoader groovyClassLoader,
- ComponentTemplateCompileResult compileResult
- ) {
- for (final var groovyClass : compileResult.otherClasses()) {
- // Try to find it. If we can't, we need to load it via the groovy loader
- try {
- Class.forName(groovyClass.getName(), true, groovyClassLoader);
- } catch (ClassNotFoundException ignored) {
- groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
- } catch (LinkageError ignored) {
- // no-op, because we already have it
- }
- }
- final GroovyClass templateGroovyClass = compileResult.templateClass();
- Class> templateClass;
- // Try to find it. If we can't, we need to load it via the groovy loader
- try {
- templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
- } catch (ClassNotFoundException ignored) {
- templateClass = groovyClassLoader.defineClass(
- templateGroovyClass.getName(),
- templateGroovyClass.getBytes()
- );
- }
- try {
- return (ComponentTemplate) templateClass.getConstructor().newInstance();
- } catch (Exception e) {
- throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
- }
- }
+// private ComponentTemplate instantiate(
+// GroovyClassLoader groovyClassLoader,
+// CompileResult compileResult
+// ) {
+// for (final var groovyClass : compileResult.otherClasses()) {
+// // Try to find it. If we can't, we need to load it via the groovy loader
+// try {
+// Class.forName(groovyClass.getName(), true, groovyClassLoader);
+// } catch (ClassNotFoundException ignored) {
+// groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
+// } catch (LinkageError ignored) {
+// // no-op, because we already have it
+// }
+// }
+// final GroovyClass templateGroovyClass = compileResult.templateClass();
+// Class> templateClass;
+// // Try to find it. If we can't, we need to load it via the groovy loader
+// try {
+// templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
+// } catch (ClassNotFoundException ignored) {
+// templateClass = groovyClassLoader.defineClass(
+// templateGroovyClass.getName(),
+// templateGroovyClass.getBytes()
+// );
+// }
+// try {
+// return (ComponentTemplate) templateClass.getConstructor().newInstance();
+// } catch (Exception e) {
+// throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
+// }
+// }
@Override
- public final ComponentTemplate compileAndGet(
- GroovyClassLoader groovyClassLoader,
- Class extends ViewComponent> forClass,
- ComponentTemplateSource source
- ) throws ComponentTemplateCompileErrorException {
- if (this.cache.containsKey(forClass)) {
- final var cached = this.cache.get(forClass);
- if (cached.template() == null) {
- final ComponentTemplate template = this.instantiate(groovyClassLoader, cached.compileResult());
- this.cache.put(forClass, new CachedTemplate(cached.compileResult(), template));
- return template;
- } else {
- return cached.template();
- }
+ public final ComponentTemplateCompileResult compile(U compileUnit)
+ throws ComponentTemplateCompileException {
+ if (this.cache.containsKey(compileUnit.getForClass())) {
+ return this.cache.get(compileUnit.getForClass());
} else {
- final ComponentTemplateCompileResult compileResult = this.compile(forClass, source);
- final ComponentTemplate template = this.instantiate(groovyClassLoader, compileResult);
- this.cache.put(forClass, new CachedTemplate(compileResult, template));
- return template;
- }
- }
-
- @Override
- protected final ComponentTemplateCompileResult compile(
- ComponentTemplateSource componentTemplateSource,
- Class extends ViewComponent> forClass,
- Reader actualSource
- ) throws ComponentTemplateCompileErrorException {
- if (this.cache.containsKey(forClass)) {
- return this.cache.get(forClass).compileResult();
- } else {
- final ComponentTemplateCompileResult compileResult =
- this.doCompile(componentTemplateSource, forClass, actualSource);
- this.cache.put(forClass, new CachedTemplate(compileResult, null));
+ final ComponentTemplateCompileResult compileResult = this.doCompile(compileUnit);
+ this.cache.put(compileUnit.getForClass(), compileResult);
return compileResult;
}
}
- protected abstract ComponentTemplateCompileResult doCompile(
- ComponentTemplateSource source,
- Class extends ViewComponent> forClass,
- Reader reader
- ) throws ComponentTemplateCompileErrorException;
+ protected abstract ComponentTemplateCompileResult doCompile(U compileUnit) throws ComponentTemplateCompileException;
}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateClassFactory.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateClassFactory.java
new file mode 100644
index 0000000..ecc9d07
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateClassFactory.java
@@ -0,0 +1,7 @@
+package groowt.view.component.compiler;
+
+import groowt.view.component.ComponentTemplate;
+
+public interface ComponentTemplateClassFactory {
+ Class extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult);
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java
deleted file mode 100644
index 70e5ca0..0000000
--- a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package groowt.view.component.compiler;
-
-import groowt.view.component.ViewComponent;
-
-/**
- * Represents an exception thrown while attempting to instantiate a ComponentTemplate during compilation.
- */
-public class ComponentTemplateCompileErrorException extends Exception {
-
- private final Class extends ViewComponent> forClass;
- private final Object templateSource;
-
- public ComponentTemplateCompileErrorException(
- String message,
- Class extends ViewComponent> forClass,
- Object templateSource
- ) {
- super(message);
- this.forClass = forClass;
- this.templateSource = templateSource;
- }
-
- public ComponentTemplateCompileErrorException(
- String message,
- Throwable cause,
- Class extends ViewComponent> forClass,
- Object templateSource
- ) {
- super(message, cause);
- this.forClass = forClass;
- this.templateSource = templateSource;
- }
-
- public ComponentTemplateCompileErrorException(
- Throwable cause,
- Class extends ViewComponent> forClass,
- Object templateSource
- ) {
- super(cause);
- this.forClass = forClass;
- this.templateSource = templateSource;
- }
-
- public ComponentTemplateCompileErrorException(
- Class extends ViewComponent> forClass,
- Object templateSource
- ) {
- super("Compile error in " + templateSource + " for " + forClass.getName());
- this.forClass = forClass;
- this.templateSource = templateSource;
- }
-
- public Class extends ViewComponent> getForClass() {
- return this.forClass;
- }
-
- public Object getTemplateSource() {
- return this.templateSource;
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java
new file mode 100644
index 0000000..d5e1d4e
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java
@@ -0,0 +1,37 @@
+package groowt.view.component.compiler;
+
+import org.jetbrains.annotations.Nullable;
+
+public class ComponentTemplateCompileException extends Exception {
+
+ private final ComponentTemplateCompileUnit compileUnit;
+
+ public ComponentTemplateCompileException(ComponentTemplateCompileUnit compileUnit, String message) {
+ super(message);
+ this.compileUnit = compileUnit;
+ }
+
+ public ComponentTemplateCompileException(
+ ComponentTemplateCompileUnit compileUnit,
+ String message,
+ Throwable cause
+ ) {
+ super(message, cause);
+ this.compileUnit = compileUnit;
+ }
+
+ @Override
+ public String getMessage() {
+ final var sb = new StringBuilder("Error in ").append(compileUnit.getSource().getDescription());
+ final @Nullable String position = this.getPosition();
+ if (position != null) {
+ sb.append(" at ").append(position);
+ }
+ return sb.append(": ").append(super.getMessage()).toString();
+ }
+
+ protected @Nullable String getPosition() {
+ return null;
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileResult.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileResult.java
new file mode 100644
index 0000000..da3b003
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileResult.java
@@ -0,0 +1,10 @@
+package groowt.view.component.compiler;
+
+import org.codehaus.groovy.tools.GroovyClass;
+
+import java.util.Set;
+
+public interface ComponentTemplateCompileResult {
+ GroovyClass getTemplateClass();
+ Set getOtherClasses();
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileUnit.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileUnit.java
new file mode 100644
index 0000000..4dc8bec
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileUnit.java
@@ -0,0 +1,18 @@
+package groowt.view.component.compiler;
+
+import groowt.view.component.ViewComponent;
+import groowt.view.component.compiler.source.ComponentTemplateSource;
+
+public interface ComponentTemplateCompileUnit {
+
+ Class extends ViewComponent> getForClass();
+ String getDefaultPackageName();
+ ComponentTemplateSource getSource();
+ ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
+ throws ComponentTemplateCompileException;
+
+ default ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
+ return this.compile(new DefaultComponentTemplateCompilerConfiguration());
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java
index 842bc62..d6a5a64 100644
--- a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java
@@ -1,34 +1,7 @@
package groowt.view.component.compiler;
-import groovy.lang.GroovyClassLoader;
-import groowt.view.component.ComponentTemplate;
-import groowt.view.component.ViewComponent;
-import groowt.view.component.factory.ComponentTemplateSource;
-import org.codehaus.groovy.tools.GroovyClass;
+public interface ComponentTemplateCompiler {
-import java.io.IOException;
-import java.util.List;
-
-public interface ComponentTemplateCompiler {
-
- record ComponentTemplateCompileResult(GroovyClass templateClass, List otherClasses) {}
-
- ComponentTemplateCompileResult compile(Class extends ViewComponent> forClass, ComponentTemplateSource source)
- throws ComponentTemplateCompileErrorException;
-
- ComponentTemplate compileAndGet(
- GroovyClassLoader groovyClassLoader,
- Class extends ViewComponent> forClass,
- ComponentTemplateSource source
- ) throws ComponentTemplateCompileErrorException;
-
- default ComponentTemplate compileAndGet(Class extends ViewComponent> forClass, ComponentTemplateSource source)
- throws ComponentTemplateCompileErrorException {
- try (final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader())) {
- return this.compileAndGet(groovyClassLoader, forClass, source);
- } catch (IOException ioException) {
- throw new RuntimeException(ioException);
- }
- }
+ ComponentTemplateCompileResult compile(U compileUnit) throws ComponentTemplateCompileException;
}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompilerConfiguration.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompilerConfiguration.java
new file mode 100644
index 0000000..0149a9a
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompilerConfiguration.java
@@ -0,0 +1,11 @@
+package groowt.view.component.compiler;
+
+import groovy.lang.GroovyClassLoader;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.CompilerConfiguration;
+
+public interface ComponentTemplateCompilerConfiguration {
+ GroovyClassLoader getGroovyClassLoader();
+ CompilerConfiguration getGroovyCompilerConfiguration();
+ CompilePhase getToCompilePhase();
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/DefaultComponentTemplateCompilerConfiguration.java b/view-components/src/main/java/groowt/view/component/compiler/DefaultComponentTemplateCompilerConfiguration.java
new file mode 100644
index 0000000..2a02ed9
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/DefaultComponentTemplateCompilerConfiguration.java
@@ -0,0 +1,44 @@
+package groowt.view.component.compiler;
+
+import groovy.lang.GroovyClassLoader;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.CompilerConfiguration;
+
+public class DefaultComponentTemplateCompilerConfiguration implements ComponentTemplateCompilerConfiguration {
+
+ private GroovyClassLoader groovyClassLoader;
+ private CompilerConfiguration groovyCompilerConfiguration;
+ private CompilePhase toCompilePhase;
+
+ @Override
+ public GroovyClassLoader getGroovyClassLoader() {
+ return this.groovyClassLoader != null
+ ? this.groovyClassLoader
+ : new GroovyClassLoader(this.getClass().getClassLoader());
+ }
+
+ public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
+ this.groovyClassLoader = groovyClassLoader;
+ }
+
+ @Override
+ public CompilerConfiguration getGroovyCompilerConfiguration() {
+ return this.groovyCompilerConfiguration != null
+ ? this.groovyCompilerConfiguration
+ : CompilerConfiguration.DEFAULT;
+ }
+
+ public void setGroovyCompilerConfiguration(CompilerConfiguration groovyCompilerConfiguration) {
+ this.groovyCompilerConfiguration = groovyCompilerConfiguration;
+ }
+
+ @Override
+ public CompilePhase getToCompilePhase() {
+ return this.toCompilePhase != null ? this.toCompilePhase : CompilePhase.CLASS_GENERATION;
+ }
+
+ public void setToCompilePhase(CompilePhase toCompilePhase) {
+ this.toCompilePhase = toCompilePhase;
+ }
+
+}
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
new file mode 100644
index 0000000..d5d22af
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateClassFactory.java
@@ -0,0 +1,108 @@
+package groowt.view.component.compiler;
+
+import groowt.view.component.ComponentTemplate;
+import org.codehaus.groovy.tools.GroovyClass;
+
+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;
+
+ public SimpleComponentTemplateClassFactory() {
+ try {
+ this.tempClassesDir = Files.createTempDirectory("view-component-classes-");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ try {
+ this.classLoader = new URLClassLoader(
+ "SimpleComponentTemplateClassFactoryClassLoader",
+ new URL[] { this.tempClassesDir.toUri().toURL() },
+ this.getClass().getClassLoader()
+ );
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ 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();
+ if (this.cache.containsKey(templateClassName)) {
+ return this.cache.get(templateClassName);
+ } else {
+ // write classes to disk
+ this.writeClassToDisk(compileResult.getTemplateClass());
+ compileResult.getOtherClasses().forEach(this::writeClassToDisk);
+ // load the template class
+ try {
+ //noinspection unchecked
+ final var templateClass = (Class extends ComponentTemplate>) this.classLoader.loadClass(
+ templateClassName
+ );
+ this.cache.put(templateClassName, templateClass);
+ return templateClass;
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateCompileResult.java b/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateCompileResult.java
new file mode 100644
index 0000000..ca78f8d
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/SimpleComponentTemplateCompileResult.java
@@ -0,0 +1,50 @@
+package groowt.view.component.compiler;
+
+import org.codehaus.groovy.tools.GroovyClass;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class SimpleComponentTemplateCompileResult implements ComponentTemplateCompileResult {
+
+ private final GroovyClass templateClass;
+ private final Set otherClasses;
+
+ public SimpleComponentTemplateCompileResult(
+ GroovyClass templateClass,
+ Set otherClasses
+ ) {
+ this.templateClass = templateClass;
+ this.otherClasses = otherClasses;
+ }
+
+ @Override
+ public GroovyClass getTemplateClass() {
+ return this.templateClass;
+ }
+
+ @Override
+ public Set getOtherClasses() {
+ return new HashSet<>(this.otherClasses);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.templateClass.getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (obj instanceof ComponentTemplateCompileResult other) {
+ return this.templateClass.getName().equals(other.getTemplateClass().getName());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "sctCompileResult(" + this.templateClass.getName() + ")";
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/ComponentTemplateSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/ComponentTemplateSource.java
new file mode 100644
index 0000000..bdee155
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/ComponentTemplateSource.java
@@ -0,0 +1,53 @@
+package groowt.view.component.compiler.source;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+
+public interface ComponentTemplateSource {
+
+ static ComponentTemplateSource of(String template) {
+ return new StringSource(template, null);
+ }
+
+ static ComponentTemplateSource of(String template, String name) {
+ return new StringSource(template, name);
+ }
+
+ static ComponentTemplateSource of(File templateFile) {
+ return new FileSource(templateFile);
+ }
+
+ static ComponentTemplateSource of(URI templateURI) {
+ return new URISource(templateURI);
+ }
+
+ static ComponentTemplateSource of(URL url) {
+ return new URLSource(url);
+ }
+
+ static ComponentTemplateSource of(InputStream templateInputStream) {
+ return new InputStreamSource(templateInputStream, null);
+ }
+
+ static ComponentTemplateSource of(InputStream templateInputStream, String description) {
+ return new InputStreamSource(templateInputStream, description);
+ }
+
+ static ComponentTemplateSource of(Reader templateReader) {
+ return new ReaderSource(templateReader, null);
+ }
+
+ static ComponentTemplateSource of(Reader templateReader, String description) {
+ return new ReaderSource(templateReader, description);
+ }
+
+ Reader toReader() throws Exception;
+ String getDescription();
+ boolean canReopen();
+ List getLines();
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/FileSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/FileSource.java
new file mode 100644
index 0000000..8e177bb
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/FileSource.java
@@ -0,0 +1,50 @@
+package groowt.view.component.compiler.source;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.net.URI;
+import java.util.List;
+
+public class FileSource implements ComponentTemplateSource {
+
+ private final File templateFile;
+ private List lines;
+
+ public FileSource(File templateFile) {
+ this.templateFile = templateFile;
+ }
+
+ @Override
+ public Reader toReader() throws Exception {
+ return new FileReader(this.templateFile);
+ }
+
+ @Override
+ public String getDescription() {
+ return this.templateFile.toString();
+ }
+
+ @Override
+ public boolean canReopen() {
+ return true;
+ }
+
+ @Override
+ public List getLines() {
+ if (this.lines == null) {
+ try (final var fis = new FileInputStream(this.templateFile)) {
+ final var allSource = new String(fis.readAllBytes());
+ this.lines = allSource.lines().toList();
+ } catch (IOException ioException) {
+ throw new RuntimeException(ioException);
+ }
+ }
+ return this.lines;
+ }
+
+ public @Nullable URI getURI() {
+ return templateFile.toURI();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/InputStreamSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/InputStreamSource.java
new file mode 100644
index 0000000..6c8dcef
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/InputStreamSource.java
@@ -0,0 +1,40 @@
+package groowt.view.component.compiler.source;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.List;
+
+public class InputStreamSource implements ComponentTemplateSource {
+
+ private final InputStream templateInputStream;
+ private final @Nullable String description;
+
+ public InputStreamSource(InputStream templateInputStream, @Nullable String description) {
+ this.templateInputStream = templateInputStream;
+ this.description = description;
+ }
+
+ @Override
+ public Reader toReader() {
+ return new InputStreamReader(this.templateInputStream);
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description != null ? this.description : "";
+ }
+
+ @Override
+ public boolean canReopen() {
+ return false;
+ }
+
+ @Override
+ public List getLines() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/ReaderSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/ReaderSource.java
new file mode 100644
index 0000000..18b6adc
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/ReaderSource.java
@@ -0,0 +1,38 @@
+package groowt.view.component.compiler.source;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.Reader;
+import java.util.List;
+
+public class ReaderSource implements ComponentTemplateSource {
+
+ private final Reader reader;
+ private final @Nullable String description;
+
+ public ReaderSource(Reader reader, @Nullable String description) {
+ this.reader = reader;
+ this.description = description;
+ }
+
+ @Override
+ public Reader toReader() {
+ return this.reader;
+ }
+
+ @Override
+ public String getDescription() {
+ return this.description != null ? this.description : "";
+ }
+
+ @Override
+ public boolean canReopen() {
+ return false;
+ }
+
+ @Override
+ public List getLines() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/StringSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/StringSource.java
new file mode 100644
index 0000000..0063aa1
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/StringSource.java
@@ -0,0 +1,39 @@
+package groowt.view.component.compiler.source;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+
+public class StringSource implements ComponentTemplateSource {
+
+ private final String template;
+ private final @Nullable String name;
+
+ public StringSource(String template, @Nullable String name) {
+ this.template = template;
+ this.name = name;
+ }
+
+ @Override
+ public Reader toReader() {
+ return new StringReader(this.template);
+ }
+
+ @Override
+ public String getDescription() {
+ return this.name != null ? this.name : "";
+ }
+
+ @Override
+ public boolean canReopen() {
+ return true;
+ }
+
+ @Override
+ public List getLines() {
+ return this.template.lines().toList();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/URISource.java b/view-components/src/main/java/groowt/view/component/compiler/source/URISource.java
new file mode 100644
index 0000000..ce9bb46
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/URISource.java
@@ -0,0 +1,50 @@
+package groowt.view.component.compiler.source;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.util.List;
+
+public class URISource implements ComponentTemplateSource {
+
+ private final URI templateURI;
+ private List lines;
+
+ public URISource(URI templateURI) {
+ this.templateURI = templateURI;
+ }
+
+ @Override
+ public Reader toReader() throws Exception {
+ return new InputStreamReader(this.templateURI.toURL().openStream());
+ }
+
+ @Override
+ public String getDescription() {
+ return this.templateURI.toString();
+ }
+
+ @Override
+ public boolean canReopen() {
+ return true;
+ }
+
+ public URI getURI() {
+ return this.templateURI;
+ }
+
+ @Override
+ public List getLines() {
+ if (this.lines == null) {
+ try (final var inputStream = this.templateURI.toURL().openStream()) {
+ final String allSource = new String(inputStream.readAllBytes());
+ this.lines = allSource.lines().toList();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return this.lines;
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/compiler/source/URLSource.java b/view-components/src/main/java/groowt/view/component/compiler/source/URLSource.java
new file mode 100644
index 0000000..6c31fe8
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/compiler/source/URLSource.java
@@ -0,0 +1,58 @@
+package groowt.view.component.compiler.source;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+public class URLSource implements ComponentTemplateSource {
+
+ private final URL url;
+ private List lines;
+
+ public URLSource(URL url) {
+ this.url = url;
+ }
+
+ @Override
+ public Reader toReader() throws Exception {
+ return new InputStreamReader(this.url.openStream());
+ }
+
+ @Override
+ public String getDescription() {
+ return this.url.toString();
+ }
+
+ @Override
+ public boolean canReopen() {
+ return true;
+ }
+
+ public @Nullable URI getURI() {
+ try {
+ return this.url.toURI();
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public List getLines() {
+ if (this.lines == null) {
+ try (final var inputStream = this.url.openStream()) {
+ final String allSource = new String(inputStream.readAllBytes());
+ this.lines = allSource.lines().toList();
+ } catch (IOException ioException) {
+ throw new RuntimeException(ioException);
+ }
+ }
+ return this.lines;
+ }
+
+}
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 30c0ff6..172f729 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,62 +1,28 @@
package groowt.view.component.context;
import groowt.view.component.ViewComponent;
-import groowt.view.component.factory.ComponentFactory;
-import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
-import java.util.Deque;
import java.util.List;
-import java.util.Objects;
import java.util.function.Predicate;
public interface ComponentContext {
- /**
- * For use only by compiled templates.
- */
- @ApiStatus.Internal
- interface Resolved {
- String getTypeName();
- ComponentFactory> getComponentFactory();
- }
-
- /**
- * For use only by compiled templates.
- */
- @ApiStatus.Internal
- Resolved resolve(String component);
-
- /**
- * For use only by compiled templates.
- */
- @ApiStatus.Internal
- ViewComponent create(Resolved resolved, Object... args);
-
- /**
- * For use only by compiled templates.
- */
- @ApiStatus.Internal
- void beforeComponentRender(ViewComponent component);
-
- /**
- * For use only by compiled templates.
- */
- @ApiStatus.Internal
- void afterComponentRender(ViewComponent component);
-
- Deque getScopeStack();
+ List getScopeStack();
void pushScope(ComponentScope scope);
void pushDefaultScope();
void popScope();
+ ComponentScope getRootScope();
default ComponentScope getCurrentScope() {
- return Objects.requireNonNull(this.getScopeStack().peek(), "There is no current scope.");
+ final List scopeStack = this.getScopeStack();
+ if (scopeStack.isEmpty()) {
+ throw new NullPointerException("There is no current scope.");
+ }
+ return scopeStack.getFirst();
}
- Deque getComponentStack();
-
@Nullable ViewComponent getParent();
@Nullable T getParent(Class parentClass);
@@ -66,11 +32,7 @@ public interface ComponentContext {
Class ancestorClass,
Predicate super ViewComponent> matching
) {
- return ancestorClass.cast(matching.and(ancestorClass::isInstance));
- }
-
- default @Nullable ViewComponent findNearestAncestorByTypeName(String typeName) {
- return this.findNearestAncestor(component -> component.getTypeName().equals(typeName));
+ return ancestorClass.cast(this.findNearestAncestor(matching.and(ancestorClass::isInstance)));
}
List getAllAncestors();
diff --git a/view-components/src/main/java/groowt/view/component/context/ComponentCreateException.java b/view-components/src/main/java/groowt/view/component/context/ComponentCreateException.java
deleted file mode 100644
index 41a864c..0000000
--- a/view-components/src/main/java/groowt/view/component/context/ComponentCreateException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package groowt.view.component.context;
-
-import groowt.view.component.ComponentTemplate;
-
-/**
- * An exception which signals that a component of the given type
- * could not be created in the given template.
- */
-public class ComponentCreateException extends RuntimeException {
-
- private final Object componentType;
- private final ComponentTemplate template;
- private final int line;
- private final int column;
-
- public ComponentCreateException(
- Object componentType,
- ComponentTemplate template,
- int line,
- int column,
- Throwable cause
- ) {
- super(cause);
- this.componentType = componentType;
- this.template = template;
- this.line = line;
- this.column = column;
- }
-
- @Override
- public String getMessage() {
- return "Exception in " + this.template.getClass().getName() + " while creating "
- + this.componentType.getClass().getName() + " at line " + this.line
- + ", column " + this.column + ".";
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/context/ComponentResolveException.java b/view-components/src/main/java/groowt/view/component/context/ComponentResolveException.java
new file mode 100644
index 0000000..a20bbfc
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/context/ComponentResolveException.java
@@ -0,0 +1,141 @@
+package groowt.view.component.context;
+
+import groowt.view.component.ComponentTemplate;
+import groowt.view.component.ViewComponent;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+
+public class ComponentResolveException extends Exception {
+
+ private final String typeNameOrAlias;
+ private @Nullable String message;
+ private @Nullable Class extends ViewComponent> type;
+ private @Nullable ComponentTemplate template;
+ private int line;
+ private int column;
+
+ public ComponentResolveException(String typeName) {
+ this.typeNameOrAlias = Objects.requireNonNull(typeName);
+ }
+
+ public ComponentResolveException(String typeName, @Nullable Class extends ViewComponent> type) {
+ this.typeNameOrAlias = Objects.requireNonNull(typeName);
+ this.type = type;
+ }
+
+ public ComponentResolveException(
+ @Nullable String message,
+ String typeName,
+ @Nullable Class extends ViewComponent> type
+ ) {
+ this.typeNameOrAlias = typeName;
+ this.message = message;
+ this.type = type;
+ }
+
+ public ComponentResolveException(String typeName, Throwable cause) {
+ super(cause);
+ this.typeNameOrAlias = Objects.requireNonNull(typeName);
+ }
+
+ public ComponentResolveException(String typeName, @Nullable Class extends ViewComponent> type, Throwable cause) {
+ super(cause);
+ this.typeNameOrAlias = Objects.requireNonNull(typeName);
+ this.type = type;
+ }
+
+ public ComponentResolveException(
+ @NotNull ComponentTemplate template,
+ Throwable cause,
+ String typeName,
+ int line,
+ int column
+ ) {
+ super(cause);
+ this.template = template;
+ this.typeNameOrAlias = typeName;
+ this.line = line;
+ this.column = column;
+ }
+
+ public ComponentResolveException(
+ @NotNull ComponentTemplate template,
+ Throwable cause,
+ String alias,
+ @NotNull Class extends ViewComponent> type,
+ int line,
+ int column
+ ) {
+ super(cause);
+ this.template = Objects.requireNonNull(template);
+ this.typeNameOrAlias = alias;
+ this.type = type;
+ this.line = line;
+ this.column = column;
+ }
+
+ public void setMessage(@Nullable String message) {
+ this.message = message;
+ }
+
+ public String getTypeNameOrAlias() {
+ return this.typeNameOrAlias;
+ }
+
+ public @Nullable Class extends ViewComponent> getType() {
+ return this.type;
+ }
+
+ @ApiStatus.Internal
+ public void setType(@Nullable Class extends ViewComponent> type) {
+ this.type = type;
+ }
+
+ public @NotNull ComponentTemplate getTemplate() {
+ return Objects.requireNonNull(this.template);
+ }
+
+ @ApiStatus.Internal
+ public void setTemplate(ComponentTemplate template) {
+ this.template = Objects.requireNonNull(template);
+ }
+
+ public int getLine() {
+ return this.line;
+ }
+
+ @ApiStatus.Internal
+ public void setLine(int line) {
+ this.line = line;
+ }
+
+ public int getColumn() {
+ return this.column;
+ }
+
+ @ApiStatus.Internal
+ public void setColumn(int column) {
+ this.column = column;
+ }
+
+ @Override
+ public String getMessage() {
+ final var sb = new StringBuilder("Exception");
+ if (this.template != null) {
+ sb.append(" in ").append(this.template.getClass().getName());
+ }
+ sb.append(" while resolving component ").append(this.typeNameOrAlias);
+ if (this.type != null) {
+ sb.append(" of type ").append(this.type.getName());
+ }
+ sb.append(" at line ").append(this.line).append(", column ").append(this.column).append(".");
+ if (this.message != null) {
+ sb.append(" ").append(this.message);
+ } // else, assume caused by is not null
+ return sb.toString();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/context/ComponentScope.java b/view-components/src/main/java/groowt/view/component/context/ComponentScope.java
index bcba8ab..6d2ee35 100644
--- a/view-components/src/main/java/groowt/view/component/context/ComponentScope.java
+++ b/view-components/src/main/java/groowt/view/component/context/ComponentScope.java
@@ -1,48 +1,52 @@
package groowt.view.component.context;
-import groovy.lang.Closure;
import groowt.view.component.ViewComponent;
import groowt.view.component.factory.ComponentFactory;
public interface ComponentScope {
- void add(String name, ComponentFactory> factory);
- boolean contains(String name);
- void remove(String name);
- ComponentFactory> get(String name);
+ record TypeAndFactory(Class extends T> type, ComponentFactory extends T> factory) {}
- default ComponentFactory> factoryMissing(String typeName) {
- throw new NoFactoryMissingException(this.getClass().getName() + " does not support factoryMissing()");
+ //---- string types
+
+ void add(String typeName, Class forClass, ComponentFactory extends T> factory);
+
+ boolean contains(String typeName);
+
+ void remove(String typeName);
+
+ TypeAndFactory> get(String typeName);
+
+ default TypeAndFactory> factoryMissing(String typeName) throws ComponentResolveException {
+ throw new FactoryMissingUnsupportedException(
+ this.getClass().getName() + " does not support factoryMissing() for string types.",
+ typeName
+ );
}
- default void add(Class clazz, ComponentFactory factory) {
- this.add(clazz.getName(), factory);
- }
+ //---- class types
- default boolean contains(Class extends ViewComponent> clazz) {
- return this.contains(clazz.getName());
- }
+ void add(Class forClass, ComponentFactory extends T> factory);
- @SuppressWarnings("unchecked")
- default ComponentFactory get(Class clazz) {
- return (ComponentFactory) this.get(clazz.getName());
- }
+ void add(
+ Class publicType,
+ Class extends T> implementingType,
+ ComponentFactory extends T> factory
+ );
- default void remove(Class extends ViewComponent> clazz) {
- this.remove(clazz.getName());
- }
+ boolean contains(Class extends ViewComponent> type);
- @SuppressWarnings("unchecked")
- default ComponentFactory factoryMissing(Class clazz) {
- return (ComponentFactory) this.factoryMissing(clazz.getName());
- }
+ TypeAndFactory get(Class type);
- default void add(String name, Closure extends ViewComponent> closure) {
- this.add(name, ComponentFactory.ofClosure(closure));
- }
+ void remove(Class extends ViewComponent> type);
- default void add(Class type, Closure extends T> closure) {
- this.add(type, ComponentFactory.ofClosure(closure));
+ default TypeAndFactory> factoryMissing(String typeName, Class type)
+ throws ComponentResolveException {
+ throw new FactoryMissingUnsupportedException(
+ this.getClass().getName() + " does not support factoryMissing() for class component types.",
+ typeName,
+ type
+ );
}
}
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 d670dd6..d4c257a 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
@@ -1,98 +1,35 @@
package groowt.view.component.context;
import groowt.view.component.ViewComponent;
-import groowt.view.component.factory.ComponentFactory;
+import groowt.view.component.runtime.RenderContext;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
public class DefaultComponentContext implements ComponentContext {
- protected static class DefaultResolved implements ComponentContext.Resolved {
+ private final LinkedList scopeStack = new LinkedList<>();
+ private RenderContext renderContext;
- private final String typeName;
- private final ComponentFactory> factory;
-
- public DefaultResolved(String typeName, ComponentFactory> factory) {
- this.typeName = typeName;
- this.factory = factory;
- }
-
- @Override
- public String getTypeName() {
- return this.typeName;
- }
-
- @Override
- public ComponentFactory> getComponentFactory() {
- return this.factory;
- }
-
- }
-
- private final Deque scopeStack = new LinkedList<>();
- private final Deque componentStack = new LinkedList<>();
-
- @Override
- public Resolved resolve(String component) {
- if (scopeStack.isEmpty()) {
- throw new IllegalStateException("There are no scopes on the scopeStack.");
- }
-
- final var getStack = new LinkedList<>(this.scopeStack);
- while (!getStack.isEmpty()) {
- final ComponentScope scope = getStack.pop();
- if (scope.contains(component)) {
- return new DefaultResolved(component, scope.get(component));
- }
- }
-
- final var missingStack = new LinkedList<>(this.scopeStack);
- NoFactoryMissingException first = null;
- while (!missingStack.isEmpty()) {
- final ComponentScope scope = missingStack.pop();
- try {
- return new DefaultResolved(component, scope.factoryMissing(component));
- } catch (NoFactoryMissingException e) {
- if (first == null) {
- first = e;
- }
- }
- }
-
- if (first == null) {
- throw new IllegalStateException("First FactoryMissingException is still null.");
- }
-
- throw first;
- }
-
- @Override
- public ViewComponent create(Resolved resolved, Object... args) {
- return resolved.getComponentFactory().create(
- resolved.getTypeName(), this, args
+ @ApiStatus.Internal
+ public RenderContext getRenderContext() {
+ return Objects.requireNonNull(
+ this.renderContext,
+ "The renderContext is null. Did this method get called from outside of a rendering context?"
);
}
- @Override
- public void beforeComponentRender(ViewComponent component) {
- this.componentStack.push(component);
+ @ApiStatus.Internal
+ public void setRenderContext(RenderContext renderContext) {
+ this.renderContext = Objects.requireNonNull(renderContext);
}
@Override
- public void afterComponentRender(ViewComponent component) {
- final var popped = this.componentStack.pop();
- if (!popped.equals(component)) {
- throw new IllegalStateException("Popped component does not equal arg to afterComponent()");
- }
- }
-
- @Override
- public Deque getScopeStack() {
+ public List getScopeStack() {
return new LinkedList<>(this.scopeStack);
}
@@ -116,18 +53,15 @@ public class DefaultComponentContext implements ComponentContext {
}
@Override
- public Deque getComponentStack() {
- return new LinkedList<>(this.componentStack);
+ public ComponentScope getRootScope() {
+ return this.scopeStack.getLast();
}
@Override
public @Nullable ViewComponent getParent() {
- if (this.componentStack.size() > 1) {
- final var child = this.componentStack.pop();
- final var parent = this.componentStack.pop();
- this.componentStack.push(parent);
- this.componentStack.push(child);
- return parent;
+ final List componentStack = this.getRenderContext().getComponentStack();
+ if (componentStack.size() > 1) {
+ return componentStack.get(1);
}
return null;
}
@@ -139,35 +73,21 @@ public class DefaultComponentContext implements ComponentContext {
@Override
public @Nullable ViewComponent findNearestAncestor(Predicate super ViewComponent> matching) {
- if (this.componentStack.size() > 1) {
- final Deque tmp = new LinkedList<>();
- tmp.push(this.componentStack.pop()); // child
- ViewComponent result = null;
- while (result == null && !this.componentStack.isEmpty()) {
- final var ancestor = this.componentStack.pop();
- tmp.push(ancestor);
+ final List componentStack = this.getRenderContext().getComponentStack();
+ if (componentStack.size() > 1) {
+ for (final var ancestor : componentStack.subList(1, componentStack.size() -1)) {
if (matching.test(ancestor)) {
- result = ancestor;
+ return ancestor;
}
}
- while (!tmp.isEmpty()) {
- this.componentStack.push(tmp.pop());
- }
- return result;
}
return null;
}
@Override
public List getAllAncestors() {
- if (this.componentStack.size() > 1) {
- final var child = this.componentStack.pop();
- final List result = new ArrayList<>(this.componentStack);
- this.componentStack.push(child);
- return result;
- } else {
- return List.of();
- }
+ final List componentStack = this.getRenderContext().getComponentStack();
+ return componentStack.subList(1, componentStack.size());
}
}
diff --git a/view-components/src/main/java/groowt/view/component/context/DefaultComponentScope.java b/view-components/src/main/java/groowt/view/component/context/DefaultComponentScope.java
index c92347c..22e2174 100644
--- a/view-components/src/main/java/groowt/view/component/context/DefaultComponentScope.java
+++ b/view-components/src/main/java/groowt/view/component/context/DefaultComponentScope.java
@@ -1,32 +1,65 @@
package groowt.view.component.context;
+import groowt.view.component.ViewComponent;
import groowt.view.component.factory.ComponentFactory;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
public class DefaultComponentScope implements ComponentScope {
- private final Map> factories = new HashMap<>();
+ private final Map> stringFactories = new HashMap<>();
+ private final Map, TypeAndFactory>> classFactories = new HashMap<>();
@Override
- public void add(String name, ComponentFactory> factory) {
- this.factories.put(name, factory);
+ public void add(String typeName, Class forClass, ComponentFactory extends T> factory) {
+ this.stringFactories.put(typeName, new TypeAndFactory<>(forClass, factory));
}
@Override
- public boolean contains(String name) {
- return this.factories.containsKey(name);
+ public boolean contains(String typeName) {
+ return this.stringFactories.containsKey(typeName);
}
@Override
- public void remove(String name) {
- this.factories.remove(name);
+ public TypeAndFactory> get(String typeName) {
+ return Objects.requireNonNull(this.stringFactories.get(typeName));
}
@Override
- public ComponentFactory> get(String name) {
- return this.factories.get(name);
+ public void remove(String typeName) {
+ this.stringFactories.remove(typeName);
+ }
+
+ @Override
+ public void add(Class forClass, ComponentFactory extends T> factory) {
+ this.classFactories.put(forClass, new TypeAndFactory<>(forClass, factory));
+ }
+
+ @Override
+ public void add(
+ Class publicType,
+ Class extends T> implementingType,
+ ComponentFactory extends T> factory
+ ) {
+ this.classFactories.put(publicType, new TypeAndFactory(implementingType, factory));
+ }
+
+ @Override
+ public boolean contains(Class extends ViewComponent> type) {
+ return this.classFactories.containsKey(type);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public TypeAndFactory get(Class type) {
+ return (TypeAndFactory) Objects.requireNonNull(this.classFactories.get(type));
+ }
+
+ @Override
+ public void remove(Class extends ViewComponent> type) {
+ this.classFactories.remove(type);
}
}
diff --git a/view-components/src/main/java/groowt/view/component/context/FactoryMissingUnsupportedException.java b/view-components/src/main/java/groowt/view/component/context/FactoryMissingUnsupportedException.java
new file mode 100644
index 0000000..ef7ff2b
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/context/FactoryMissingUnsupportedException.java
@@ -0,0 +1,48 @@
+package groowt.view.component.context;
+
+import groowt.view.component.ViewComponent;
+import org.jetbrains.annotations.Nullable;
+
+public class FactoryMissingUnsupportedException extends ComponentResolveException {
+
+ private final String message;
+
+ public FactoryMissingUnsupportedException(@Nullable String message, String typeName) {
+ super(typeName);
+ this.message = message;
+ }
+
+ public FactoryMissingUnsupportedException(
+ @Nullable String message,
+ String typeName,
+ @Nullable Class extends ViewComponent> type
+ ) {
+ super(typeName, type);
+ this.message = message;
+ }
+
+ public FactoryMissingUnsupportedException(@Nullable String message, String typeName, Throwable cause) {
+ super(typeName, cause);
+ this.message = message;
+ }
+
+ public FactoryMissingUnsupportedException(
+ @Nullable String message,
+ String typeName,
+ @Nullable Class extends ViewComponent> type,
+ Throwable cause
+ ) {
+ super(typeName, type, cause);
+ this.message = message;
+ }
+
+ @Override
+ public String getMessage() {
+ if (this.message != null) {
+ return super.getMessage() + " " + this.message;
+ } else {
+ return super.getMessage();
+ }
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/context/MissingClassTypeException.java b/view-components/src/main/java/groowt/view/component/context/MissingClassTypeException.java
deleted file mode 100644
index 58b128f..0000000
--- a/view-components/src/main/java/groowt/view/component/context/MissingClassTypeException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package groowt.view.component.context;
-
-import groowt.view.component.ComponentTemplate;
-
-public class MissingClassTypeException extends MissingComponentException {
-
- private final String typeName;
-
- public MissingClassTypeException(ComponentTemplate template, String typeName, int line, int col, Throwable cause) {
- super(template, cause, line, col);
- this.typeName = typeName;
- }
-
- @Override
- protected String getMissingKeyName() {
- return "class component " + this.typeName;
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/context/MissingComponentException.java b/view-components/src/main/java/groowt/view/component/context/MissingComponentException.java
deleted file mode 100644
index 21f9c74..0000000
--- a/view-components/src/main/java/groowt/view/component/context/MissingComponentException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package groowt.view.component.context;
-
-import groowt.view.component.ComponentTemplate;
-import groowt.view.component.context.ComponentContext;
-
-/**
- * An exception which represents that a component type could not be
- * found by the {@link ComponentContext}.
- */
-public abstract class MissingComponentException extends RuntimeException {
-
- private final ComponentTemplate template;
- private final int line;
- private final int col;
-
- public MissingComponentException(ComponentTemplate template, Throwable cause, int line, int col) {
- super(cause);
- this.template = template;
- this.line = line;
- this.col = col;
- }
-
- protected abstract String getMissingKeyName();
-
- @Override
- public String getMessage() {
- return "In " + this.template + " missing " + this.getMissingKeyName()
- + " on line " + this.line + ", column " + this.col + ".";
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/context/MissingFragmentTypeException.java b/view-components/src/main/java/groowt/view/component/context/MissingFragmentTypeException.java
deleted file mode 100644
index d6c2718..0000000
--- a/view-components/src/main/java/groowt/view/component/context/MissingFragmentTypeException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package groowt.view.component.context;
-
-import groowt.view.component.ComponentTemplate;
-
-public class MissingFragmentTypeException extends MissingComponentException {
-
- public MissingFragmentTypeException(ComponentTemplate template, int line, int col, Throwable cause) {
- super(template, cause, line, col);
- }
-
- @Override
- protected String getMissingKeyName() {
- return "fragment type";
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/context/MissingStringTypeException.java b/view-components/src/main/java/groowt/view/component/context/MissingStringTypeException.java
deleted file mode 100644
index d213bd1..0000000
--- a/view-components/src/main/java/groowt/view/component/context/MissingStringTypeException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package groowt.view.component.context;
-
-import groowt.view.component.ComponentTemplate;
-
-public abstract class MissingStringTypeException extends MissingComponentException {
-
- private final String keyName;
-
- public MissingStringTypeException(ComponentTemplate template, String keyName, int line, int col, Throwable cause) {
- super(template, cause, line, col);
- this.keyName = keyName;
- }
-
- @Override
- protected String getMissingKeyName() {
- return "string-typed component " + this.keyName;
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/context/NoFactoryMissingException.java b/view-components/src/main/java/groowt/view/component/context/NoFactoryMissingException.java
deleted file mode 100644
index 8b1fa01..0000000
--- a/view-components/src/main/java/groowt/view/component/context/NoFactoryMissingException.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package groowt.view.component.context;
-
-public class NoFactoryMissingException extends UnsupportedOperationException {
-
- public NoFactoryMissingException() {}
-
- public NoFactoryMissingException(String message) {
- super(message);
- }
-
- public NoFactoryMissingException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NoFactoryMissingException(Throwable cause) {
- super(cause);
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ClosureComponentFactory.java b/view-components/src/main/java/groowt/view/component/factory/AbstractClosureComponentFactory.java
similarity index 58%
rename from view-components/src/main/java/groowt/view/component/factory/ClosureComponentFactory.java
rename to view-components/src/main/java/groowt/view/component/factory/AbstractClosureComponentFactory.java
index fcc2ac4..ec606f3 100644
--- a/view-components/src/main/java/groowt/view/component/factory/ClosureComponentFactory.java
+++ b/view-components/src/main/java/groowt/view/component/factory/AbstractClosureComponentFactory.java
@@ -1,12 +1,13 @@
package groowt.view.component.factory;
import groovy.lang.Closure;
-import groowt.view.component.context.ComponentContext;
import groowt.view.component.ViewComponent;
+import groowt.view.component.context.ComponentContext;
import static groowt.view.component.factory.ComponentFactoryUtil.flatten;
-final class ClosureComponentFactory implements ComponentFactory {
+public abstract class AbstractClosureComponentFactory extends Closure implements
+ ComponentFactory {
private enum Type {
ALL,
@@ -15,18 +16,18 @@ final class ClosureComponentFactory implements Componen
NONE
}
- private final Closure closure;
+ private final Closure extends T> closure;
private final Type type;
- @SuppressWarnings("unchecked")
- public ClosureComponentFactory(Closure extends T> closure) {
- this.closure = (Closure) closure;
+ public AbstractClosureComponentFactory(Closure extends T> closure) {
+ super(closure.getOwner(), closure.getThisObject());
+ this.closure = closure;
final var paramTypes = this.closure.getParameterTypes();
if (paramTypes.length == 0) {
this.type = Type.NONE;
} else if (paramTypes.length == 1) {
final var paramType = paramTypes[0];
- if (paramType == String.class || paramType == Class.class) {
+ if (paramType == String.class) {
this.type = Type.NAME_ONLY;
} else if (ComponentContext.class.isAssignableFrom(paramType)) {
this.type = Type.CONTEXT_ONLY;
@@ -36,7 +37,7 @@ final class ClosureComponentFactory implements Componen
} else {
final var firstParamType = paramTypes[0];
final var secondParamType = paramTypes[1];
- if (firstParamType == String.class || firstParamType == Class.class) {
+ if (firstParamType == String.class) {
if (ComponentContext.class.isAssignableFrom(secondParamType)) {
if (paramTypes.length > 2) {
this.type = Type.ALL;
@@ -54,31 +55,17 @@ final class ClosureComponentFactory implements Componen
}
}
- private T flatCall(Object... args) {
- return this.closure.call(flatten(args));
- }
-
- private T objTypeCreate(Object type, ComponentContext componentContext, Object... args) {
+ protected T doCall(String typeNameOrAlias, ComponentContext componentContext, Object... args) {
return switch (this.type) {
- case ALL -> this.flatCall(type, componentContext, args);
- case NAME_AND_CONTEXT -> this.closure.call(type, componentContext);
- case NAME_AND_ARGS -> this.flatCall(type, args);
- case CONTEXT_AND_ARGS -> this.flatCall(componentContext, args);
- case NAME_ONLY -> this.closure.call(type);
+ case ALL -> this.closure.call(flatten(typeNameOrAlias, componentContext, args));
+ case NAME_AND_CONTEXT -> this.closure.call(typeNameOrAlias, componentContext);
+ case NAME_AND_ARGS -> this.closure.call(flatten(typeNameOrAlias, args));
+ case CONTEXT_AND_ARGS -> this.closure.call(flatten(componentContext, args));
+ case NAME_ONLY -> this.closure.call(typeNameOrAlias);
case CONTEXT_ONLY -> this.closure.call(componentContext);
case ARGS_ONLY -> this.closure.call(args);
case NONE -> this.closure.call();
};
}
- @Override
- public T create(String type, ComponentContext componentContext, Object... args) {
- return this.objTypeCreate(type, componentContext, args);
- }
-
- @Override
- public T create(Class> type, ComponentContext componentContext, Object... args) {
- return this.objTypeCreate(type, componentContext, args);
- }
-
}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ClassTypeClosureComponentFactory.java b/view-components/src/main/java/groowt/view/component/factory/ClassTypeClosureComponentFactory.java
new file mode 100644
index 0000000..6863d65
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/factory/ClassTypeClosureComponentFactory.java
@@ -0,0 +1,34 @@
+package groowt.view.component.factory;
+
+import groovy.lang.Closure;
+import groowt.view.component.ViewComponent;
+import groowt.view.component.context.ComponentContext;
+
+final class ClassTypeClosureComponentFactory extends AbstractClosureComponentFactory {
+
+ private final Class forClass;
+
+ public ClassTypeClosureComponentFactory(Class forClass, Closure extends T> closure) {
+ super(closure);
+ this.forClass = forClass;
+ }
+
+ @Override
+ public T create(String typeName, ComponentContext componentContext, Object... args) {
+ throw new UnsupportedOperationException(
+ "ClassTypeClosureComponentFactory cannot handle string component types."
+ );
+ }
+
+ @Override
+ public T create(String alias, Class> type, ComponentContext componentContext, Object... args) {
+ if (!this.forClass.isAssignableFrom(type)) {
+ throw new IllegalArgumentException(
+ "This ClassTypeClosureComponentFactory cannot handle type " + type.getName()
+ + "; can only handle " + this.forClass.getName() + "."
+ );
+ }
+ return this.doCall(alias, componentContext, args);
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java b/view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java
new file mode 100644
index 0000000..3e1d8ed
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java
@@ -0,0 +1,27 @@
+package groowt.view.component.factory;
+
+import groovy.lang.Closure;
+import groowt.view.component.ViewComponent;
+
+import java.util.function.Supplier;
+
+public final class ComponentFactories {
+
+ public static ComponentFactory ofClosureStringType(Closure extends T> closure) {
+ return new StringTypeClosureComponentFactory<>(closure);
+ }
+
+ public static ComponentFactory ofClosureClassType(
+ Class forClass,
+ Closure extends T> closure
+ ) {
+ return new ClassTypeClosureComponentFactory<>(forClass, closure);
+ }
+
+ public static ComponentFactory ofSupplier(Supplier extends T> supplier) {
+ return (typeName, componentContext, args) -> supplier.get();
+ }
+
+ private ComponentFactories() {}
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ComponentFactory.java b/view-components/src/main/java/groowt/view/component/factory/ComponentFactory.java
index 2c0009b..5c9a3b5 100644
--- a/view-components/src/main/java/groowt/view/component/factory/ComponentFactory.java
+++ b/view-components/src/main/java/groowt/view/component/factory/ComponentFactory.java
@@ -1,25 +1,14 @@
package groowt.view.component.factory;
-import groovy.lang.Closure;
-import groowt.view.component.context.ComponentContext;
import groowt.view.component.ViewComponent;
-
-import java.util.function.Supplier;
+import groowt.view.component.context.ComponentContext;
@FunctionalInterface
public interface ComponentFactory {
- static ComponentFactory ofClosure(Closure extends T> closure) {
- return new ClosureComponentFactory<>(closure);
- }
-
- static ComponentFactory ofSupplier(Supplier supplier) {
- return new SupplierComponentFactory<>(supplier);
- }
-
T create(String typeName, ComponentContext componentContext, Object... args);
- default T create(Class> type, ComponentContext componentContext, Object... args) {
+ default T create(String alias, Class> type, ComponentContext componentContext, Object... args) {
return this.create(type.getName(), componentContext, args);
}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java b/view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java
index 88ec06e..c3ad2ea 100644
--- a/view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java
+++ b/view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java
@@ -94,13 +94,14 @@ public abstract class ComponentFactoryBase extends Groo
}
@Override
- public T create(String type, ComponentContext componentContext, Object... args) {
- return this.findAndDoCreate(type, componentContext, args);
+ public T create(String typeName, ComponentContext componentContext, Object... args) {
+ throw new UnsupportedOperationException();
}
+ // TODO: this needs to be updated.
@Override
- public T create(Class> type, ComponentContext componentContext, Object... args) {
- return this.findAndDoCreate(type, componentContext, args);
+ public T create(String alias, Class> type, ComponentContext componentContext, Object... args) {
+ throw new UnsupportedOperationException();
}
}
diff --git a/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java b/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java
deleted file mode 100644
index 19ff547..0000000
--- a/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package groowt.view.component.factory;
-
-import java.io.*;
-import java.net.URI;
-import java.net.URL;
-
-public sealed interface ComponentTemplateSource {
-
- static ComponentTemplateSource of(String template) {
- return new StringSource(template);
- }
-
- static ComponentTemplateSource of(File templateFile) {
- return new FileSource(templateFile);
- }
-
- static ComponentTemplateSource of(URI templateURI) {
- return new URISource(templateURI);
- }
-
- static ComponentTemplateSource of(URL url) {
- return new URLSource(url);
- }
-
- static ComponentTemplateSource of(InputStream templateInputStream) {
- return new InputStreamSource(templateInputStream);
- }
-
- static ComponentTemplateSource of(Reader templateReader) {
- return new ReaderSource(templateReader);
- }
-
- /**
- * @param resourceName An absolute path resource name.
- * @return A template source
- */
- static ComponentTemplateSource fromResource(String resourceName) {
- return of(ComponentTemplateSource.class.getClassLoader().getResource(resourceName));
- }
-
- static Reader toReader(ComponentTemplateSource source) {
- return switch (source) {
- case FileSource(File file) -> {
- try {
- yield new FileReader(file);
- } catch (FileNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
- case StringSource(String rawSource) -> new StringReader(rawSource);
- case InputStreamSource(InputStream inputStream) -> new InputStreamReader(inputStream);
- case URISource(URI uri) -> {
- try {
- yield new InputStreamReader(uri.toURL().openStream());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- case URLSource(URL url) -> {
- try {
- yield new InputStreamReader(url.openStream());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- case ReaderSource(Reader reader) -> reader;
- };
- }
-
- record StringSource(String template) implements ComponentTemplateSource {}
-
- record FileSource(File templateFile) implements ComponentTemplateSource {}
-
- record URISource(URI templateURI) implements ComponentTemplateSource {}
-
- record URLSource(URL templateURL) implements ComponentTemplateSource {}
-
- record InputStreamSource(InputStream templateInputStream) implements ComponentTemplateSource {}
-
- record ReaderSource(Reader templateReader) implements ComponentTemplateSource {}
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/factory/StringTypeClosureComponentFactory.java b/view-components/src/main/java/groowt/view/component/factory/StringTypeClosureComponentFactory.java
new file mode 100644
index 0000000..dd3b1cc
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/factory/StringTypeClosureComponentFactory.java
@@ -0,0 +1,25 @@
+package groowt.view.component.factory;
+
+import groovy.lang.Closure;
+import groowt.view.component.ViewComponent;
+import groowt.view.component.context.ComponentContext;
+
+final class StringTypeClosureComponentFactory extends AbstractClosureComponentFactory {
+
+ public StringTypeClosureComponentFactory(Closure extends T> closure) {
+ super(closure);
+ }
+
+ @Override
+ public T create(String typeName, ComponentContext componentContext, Object... args) {
+ return this.doCall(typeName, componentContext, args);
+ }
+
+ @Override
+ public T create(String alias, Class> type, ComponentContext componentContext, Object... args) {
+ throw new UnsupportedOperationException(
+ "StringTypeClosureComponentFactory cannot handle class component types."
+ );
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/factory/SupplierComponentFactory.java b/view-components/src/main/java/groowt/view/component/factory/SupplierComponentFactory.java
deleted file mode 100644
index c52dd66..0000000
--- a/view-components/src/main/java/groowt/view/component/factory/SupplierComponentFactory.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package groowt.view.component.factory;
-
-import groowt.view.component.context.ComponentContext;
-import groowt.view.component.ViewComponent;
-
-import java.util.function.Supplier;
-
-final class SupplierComponentFactory extends ComponentFactoryBase {
-
- private final Supplier tSupplier;
-
- public SupplierComponentFactory(Supplier tSupplier) {
- this.tSupplier = tSupplier;
- }
-
- public T doCreate(Object type, ComponentContext componentContext, Object... args) {
- return this.tSupplier.get();
- }
-
-}
diff --git a/view-components/src/main/java/groowt/view/component/runtime/ComponentCreateException.java b/view-components/src/main/java/groowt/view/component/runtime/ComponentCreateException.java
new file mode 100644
index 0000000..b825d97
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/runtime/ComponentCreateException.java
@@ -0,0 +1,91 @@
+package groowt.view.component.runtime;
+
+import groowt.view.component.ComponentTemplate;
+import groowt.view.component.runtime.RenderContext.ResolvedStringType;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.Objects;
+
+/**
+ * An exception which signals that a component of the given type
+ * could not be created in the given template.
+ */
+public class ComponentCreateException extends RuntimeException {
+
+ private final RenderContext.Resolved> resolved;
+
+ private ComponentTemplate template;
+ private int line;
+ private int column;
+
+ public ComponentCreateException(
+ RenderContext.Resolved> resolved,
+ Throwable cause
+ ) {
+ super(cause);
+ this.resolved = resolved;
+ }
+
+ public RenderContext.Resolved> getResolved() {
+ return this.resolved;
+ }
+
+ public ComponentTemplate getTemplate() {
+ return this.template;
+ }
+
+ @ApiStatus.Internal
+ public void setTemplate(ComponentTemplate template) {
+ this.template = Objects.requireNonNull(template);
+ }
+
+ public int getLine() {
+ return this.line;
+ }
+
+ @ApiStatus.Internal
+ public void setLine(int line) {
+ this.line = line;
+ }
+
+ public int getColumn() {
+ return this.column;
+ }
+
+ @ApiStatus.Internal
+ public void setColumn(int column) {
+ this.column = column;
+ }
+
+ @Override
+ public String getMessage() {
+ final var sb = new StringBuilder("Exception in ")
+ .append(this.template.getClass().getName());
+ if (this.resolved instanceof ResolvedStringType> resolvedStringType) {
+ sb.append(" while creating string-typed component ")
+ .append(resolvedStringType.typeName())
+ .append(" (using ")
+ .append(resolvedStringType.resolvedType().getName())
+ .append(") ");
+ } else if (this.resolved instanceof RenderContext.ResolvedClassType> resolvedClassType) {
+ sb.append(" while creating class-typed component ")
+ .append(resolvedClassType.alias())
+ .append(" of public type ")
+ .append(resolvedClassType.requestedType())
+ .append(" (using ")
+ .append(resolvedClassType.resolvedType())
+ .append(") ");
+ } else {
+ sb.append(" while creating unknown-typed component (using ")
+ .append(resolved.resolvedType().getName())
+ .append(") ");
+ }
+ return sb.append(" at line ")
+ .append(this.line)
+ .append(", column ")
+ .append(this.column)
+ .append(".")
+ .toString();
+ }
+
+}
diff --git a/view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java b/view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java
new file mode 100644
index 0000000..adc86c5
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java
@@ -0,0 +1,32 @@
+package groowt.view.component.runtime;
+
+import groovy.lang.GString;
+import groowt.view.component.ViewComponent;
+import groowt.view.component.context.ComponentContext;
+import org.jetbrains.annotations.ApiStatus;
+
+public interface ComponentWriter {
+
+ @ApiStatus.Internal
+ void setRenderContext(RenderContext renderContext);
+
+ @ApiStatus.Internal
+ void setComponentContext(ComponentContext componentContext);
+
+ void append(String string);
+ void append(GString gString);
+ void append(GString gString, int line, int column);
+ void append(ViewComponent viewComponent);
+ void append(ViewComponent viewComponent, int line, int column);
+ void append(Object object);
+
+ default void leftShift(Object object) {
+ switch (object) {
+ case String s -> this.append(s);
+ case GString gs -> this.append(gs);
+ case ViewComponent viewComponent -> this.append(viewComponent);
+ default -> this.append(object);
+ }
+ }
+
+}
diff --git a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentWriter.java b/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java
similarity index 54%
rename from web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentWriter.java
rename to view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java
index 3ef07a8..4e6982b 100644
--- a/web-views/src/main/java/groowt/view/web/runtime/DefaultWebViewComponentWriter.java
+++ b/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java
@@ -1,20 +1,42 @@
-package groowt.view.web.runtime;
+package groowt.view.component.runtime;
import groovy.lang.GString;
import groowt.view.component.ComponentRenderException;
import groowt.view.component.ViewComponent;
+import groowt.view.component.context.ComponentContext;
import java.io.IOException;
import java.io.Writer;
+import java.util.Objects;
-public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
+public class DefaultComponentWriter implements ComponentWriter {
private final Writer delegate;
+ private RenderContext renderContext;
+ private ComponentContext componentContext;
- public DefaultWebViewComponentWriter(Writer delegate) {
+ public DefaultComponentWriter(Writer delegate) {
this.delegate = delegate;
}
+ protected RenderContext getRenderContext() {
+ return Objects.requireNonNull(this.renderContext);
+ }
+
+ @Override
+ public void setRenderContext(RenderContext renderContext) {
+ this.renderContext = Objects.requireNonNull(renderContext);
+ }
+
+ protected ComponentContext getComponentContext() {
+ return this.componentContext;
+ }
+
+ @Override
+ public void setComponentContext(ComponentContext componentContext) {
+ this.componentContext = Objects.requireNonNull(componentContext);
+ }
+
@Override
public void append(String string) {
try {
@@ -50,12 +72,22 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
}
}
+ private void doComponentRender(ViewComponent viewComponent) throws IOException {
+ this.getRenderContext().pushComponent(viewComponent);
+ this.getComponentContext().pushDefaultScope();
+ viewComponent.renderTo(this.delegate);
+ this.getComponentContext().popScope();
+ this.getRenderContext().popComponent(viewComponent);
+ }
+
@Override
public void append(ViewComponent viewComponent) {
try {
- viewComponent.renderTo(this.delegate);
+ this.doComponentRender(viewComponent);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
+ } catch (ComponentRenderException componentRenderException) {
+ throw componentRenderException;
} catch (Exception exception) {
throw new ComponentRenderException(viewComponent, exception);
}
@@ -64,9 +96,11 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
@Override
public void append(ViewComponent viewComponent, int line, int column) {
try {
- viewComponent.renderTo(this.delegate);
+ this.doComponentRender(viewComponent);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
+ } catch (ComponentRenderException componentRenderException) {
+ throw componentRenderException;
} catch (Exception exception) {
throw new ComponentRenderException(viewComponent, line, column, exception);
}
@@ -74,20 +108,17 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
@Override
public void append(Object object) {
- try {
- this.delegate.append(object.toString());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void leftShift(Object object) {
switch (object) {
case String s -> this.append(s);
- case GString gs -> this.append(gs);
+ case GString gString -> this.append(gString);
case ViewComponent viewComponent -> this.append(viewComponent);
- default -> this.append(object);
+ default -> {
+ try {
+ this.delegate.append(object.toString());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java b/view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java
new file mode 100644
index 0000000..6b3a716
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/runtime/DefaultRenderContext.java
@@ -0,0 +1,141 @@
+package groowt.view.component.runtime;
+
+import groowt.view.component.ViewComponent;
+import groowt.view.component.ViewComponentBugError;
+import groowt.view.component.context.ComponentContext;
+import groowt.view.component.context.ComponentResolveException;
+import groowt.view.component.context.ComponentScope.TypeAndFactory;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class DefaultRenderContext implements RenderContext {
+
+ private final ComponentContext componentContext;
+ private final ComponentWriter writer;
+ private final LinkedList componentStack = new LinkedList<>();
+
+ public DefaultRenderContext(ComponentContext componentContext, ComponentWriter writer) {
+ this.componentContext = componentContext;
+ this.writer = writer;
+ }
+
+ public ComponentContext getComponentContext() {
+ return this.componentContext;
+ }
+
+ @Override
+ public Resolved> resolve(String typeName) throws ComponentResolveException {
+ for (final var scope : this.getComponentContext().getScopeStack()) {
+ if (scope.contains(typeName)) {
+ try {
+ final var typeAndFactory = scope.get(typeName);
+ return new ResolvedStringType<>(typeName, typeAndFactory.type(), typeAndFactory.factory());
+ } catch (Exception e) {
+ throw new ComponentResolveException(typeName, e);
+ }
+ }
+ }
+ Exception firstException = null;
+ for (final var scope : this.getComponentContext().getScopeStack()) {
+ try {
+ final var typeAndFactory = (TypeAndFactory>) scope.factoryMissing(typeName);
+ return new ResolvedStringType<>(typeName, typeAndFactory.type(), typeAndFactory.factory());
+ } catch (Exception e) {
+ if (firstException == null) {
+ firstException = e;
+ }
+ }
+ }
+ if (firstException != null) {
+ throw new ComponentResolveException(typeName, firstException);
+ } else {
+ throw new ViewComponentBugError(
+ "Could not resolve factory for " + typeName + " and firstException was null."
+ );
+ }
+ }
+
+ @Override
+ public Resolved resolve(String alias, Class type) throws ComponentResolveException {
+ for (final var scope : this.getComponentContext().getScopeStack()) {
+ if (scope.contains(type)) {
+ try {
+ final var typeAndFactory = (TypeAndFactory) scope.get(type);
+ return new ResolvedClassType<>(
+ alias,
+ type,
+ typeAndFactory.type(),
+ typeAndFactory.factory()
+ );
+ } catch (Exception e) {
+ throw new ComponentResolveException(alias, type, e);
+ }
+ }
+ }
+ throw new ComponentResolveException(
+ "Could not find a factory for " + alias + " of type " + type.getName() + " in scope.",
+ alias,
+ type
+ );
+ }
+
+ @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);
+ }
+
+ @Override
+ public void popComponent(ViewComponent component) {
+ final var popped = this.componentStack.pop();
+ if (!popped.equals(component)) {
+ throw new ViewComponentBugError(
+ "Popped component != expected component; popped: " + popped + ", expected: " + component + "."
+ );
+ }
+ }
+
+ @Override
+ public List getComponentStack() {
+ return new LinkedList<>(this.componentStack);
+ }
+
+ @Override
+ public ComponentWriter getWriter() {
+ return this.writer;
+ }
+
+}
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
new file mode 100644
index 0000000..6c8fbd7
--- /dev/null
+++ b/view-components/src/main/java/groowt/view/component/runtime/RenderContext.java
@@ -0,0 +1,43 @@
+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 {
+ Class extends T> resolvedType();
+ ComponentFactory extends T> componentFactory();
+ }
+
+ record ResolvedStringType(
+ String typeName,
+ Class extends T> resolvedType,
+ ComponentFactory extends T> componentFactory
+ ) implements Resolved {}
+
+ record ResolvedClassType(
+ String alias,
+ Class requestedType,
+ Class extends T> resolvedType,
+ ComponentFactory extends T> componentFactory
+ ) implements Resolved {}
+
+ 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);
+
+ List getComponentStack();
+
+ ComponentWriter getWriter();
+
+}
diff --git a/web-views/build.gradle b/web-views/build.gradle
index 58adced..351d66c 100644
--- a/web-views/build.gradle
+++ b/web-views/build.gradle
@@ -4,6 +4,7 @@ import groowt.gradle.antlr.GroowtAntlrExecTask
plugins {
id 'groowt-conventions'
id 'groowt-antlr-plugin'
+ id 'groowt-logging'
id 'java-library'
id 'groovy'
id 'org.jetbrains.kotlin.jvm'
@@ -19,13 +20,23 @@ configurations {
toolsImplementation {
extendsFrom(apiElements, runtimeElements)
}
+ sketchingImplementation {
+ extendsFrom(apiElements, runtimeElements)
+ }
}
sourceSets {
+ sketching {
+ java {
+ compileClasspath += sourceSets.main.output
+ runtimeClasspath += sourceSets.main.output
+ }
+ }
tools {
java {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
+ runtimeClasspath += sourceSets.sketching.output
}
}
}
@@ -35,7 +46,6 @@ dependencies {
libs.groovy,
libs.groovy.templates,
libs.antlr.runtime,
- libs.classgraph,
project(':view-components'),
project(':views')
)
@@ -64,6 +74,8 @@ dependencies {
groovyConsole libs.groovy.console
toolsApi libs.picocli
toolsImplementation libs.groovy.console
+
+ sketchingApi libs.groovy
}
java {
diff --git a/web-views/docs/asciidoc/componentTemplateSpec.asciidoc b/web-views/docs/asciidoc/componentTemplateSpec.asciidoc
new file mode 100644
index 0000000..e056018
--- /dev/null
+++ b/web-views/docs/asciidoc/componentTemplateSpec.asciidoc
@@ -0,0 +1,130 @@
+= Component Template Specification
+
+== Compiled Component Template Code
+
+The following code represents a typical (transpiled) component template:
+
+[source, groovy]
+----
+package com.jessebrault.website
+
+import groowt.view.component.ComponentTemplate
+import groowt.view.component.context.ComponentContext
+import groowt.view.component.runtime.*
+import groowt.view.web.WebViewComponent
+import groowt.view.web.lib.*
+import groowt.view.web.runtime.*
+
+class MyComponentTemplate implements ComponentTemplate {
+
+ Closure getRenderer() {
+ return { ComponentContext componentContext, ComponentWriter out ->
+ // <1>
+ final RenderContext renderContext = new DefaultWebViewComponentRenderContext(componentContext, out)
+ componentContext.setRenderContext(renderContext)
+ out.setRenderContext(renderContext)
+ out.setComponentContext(renderContext)
+
+ out.append 'Hello from simple text!' // <2>
+
+ out.append "Hello from GString!" // <3>
+
+ // <4>
+ ComponentContext.Resolved c0Resolved // <5>
+ try {
+ c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6>
+ } catch (ComponentResolveException c0ResolveException) { // <7>
+ c0ResolveException.template = this
+ c0ResolveException.line = 1
+ c0ResolveException.column = 1
+ throw c0ResolveException
+ }
+
+ WebViewComponent 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"
+
+ 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
+ }
+
+ 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
+ }
+ )
+ } catch (ComponentCreateException c0CreateException) {
+ c0CreateException.template = this
+ c0CreateException.line = 1
+ c0CreateException.column = 1
+ throw c0CreateException
+ }
+
+ // append it
+ out.append 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`.
+<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.
+
+== Items requiring Groovy ASTNode position adjustment
+
+The following items all need to have their transpiled Groovy ASTNodes' positions adjusted to match the original source
+file, in case there is a Groovy compilation error involved.
+
+* JStrings
+* GStrings
+* Component types (Class and String expressions)
+* Attribute keys and values
+* Component constructor args
diff --git a/web-views/sketching/memberClassComponentType.wvc b/web-views/sketching/memberClassComponentType.wvc
new file mode 100644
index 0000000..a63dd90
--- /dev/null
+++ b/web-views/sketching/memberClassComponentType.wvc
@@ -0,0 +1,4 @@
+---
+package groowt.view.web.sketching
+---
+
diff --git a/web-views/sketching/simpleGreeter.wvc b/web-views/sketching/simpleGreeter.wvc
index fa47c6d..bcc823d 100644
--- a/web-views/sketching/simpleGreeter.wvc
+++ b/web-views/sketching/simpleGreeter.wvc
@@ -1 +1,11 @@
+---
+class Greeter extends BaseWebViewComponent {
+ String target
+
+ Greeter(Map attr) {
+ super('Hello, $target!')
+ this.target = attr.target
+ }
+}
+---
diff --git a/web-views/src/main/groovy/groowt/view/web/BaseWebViewComponent.groovy b/web-views/src/main/groovy/groowt/view/web/BaseWebViewComponent.groovy
new file mode 100644
index 0000000..be84900
--- /dev/null
+++ b/web-views/src/main/groovy/groowt/view/web/BaseWebViewComponent.groovy
@@ -0,0 +1,46 @@
+package groowt.view.web
+
+import groowt.view.component.AbstractViewComponent
+import groowt.view.component.ComponentTemplate
+import groowt.view.component.compiler.ComponentTemplateCompileUnit
+import groowt.view.component.compiler.source.ComponentTemplateSource
+
+import java.util.function.Function
+
+abstract class BaseWebViewComponent extends AbstractWebViewComponent {
+
+ BaseWebViewComponent() {}
+
+ BaseWebViewComponent(ComponentTemplate template) {
+ super(template)
+ }
+
+ BaseWebViewComponent(Class extends ComponentTemplate> templateClass) {
+ super(templateClass)
+ }
+
+ BaseWebViewComponent(
+ Function super Class extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
+ ) {
+ super(compileUnitFunction)
+ }
+
+ BaseWebViewComponent(ComponentTemplateSource source) {
+ super(source)
+ }
+
+ /**
+ * A convenience constructor which creates a {@link ComponentTemplateSource}
+ * from the given {@code source} parameter and passes it to super. See
+ * {@link ComponentTemplateSource} for possible types.
+ *
+ * @param source the object passed to {@link ComponentTemplateSource#of}
+ *
+ * @see ComponentTemplateSource
+ */
+ @SuppressWarnings('GroovyAssignabilityCheck')
+ BaseWebViewComponent(Object source) {
+ super(ComponentTemplateSource.of(source))
+ }
+
+}
diff --git a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy
deleted file mode 100644
index 7f571b2..0000000
--- a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy
+++ /dev/null
@@ -1,78 +0,0 @@
-package groowt.view.web
-
-import groowt.view.component.AbstractViewComponent
-import groowt.view.component.ComponentTemplate
-import groowt.view.component.compiler.ComponentTemplateCompiler
-import groowt.view.component.factory.ComponentTemplateSource
-import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler
-import groowt.view.web.compiler.WebViewComponentTemplateCompiler
-import org.codehaus.groovy.control.CompilerConfiguration
-
-import java.util.function.Function
-
-abstract class DefaultWebViewComponent extends AbstractWebViewComponent {
-
- private static final GroovyClassLoader groovyClassLoader =
- new GroovyClassLoader(DefaultWebViewComponent.classLoader)
-
- private static final Function compilerFunction = { Class givenSelfClass ->
- new DefaultWebViewComponentTemplateCompiler(
- groovyClassLoader,
- CompilerConfiguration.DEFAULT,
- givenSelfClass.packageName
- )
- }
-
- protected DefaultWebViewComponent() {}
-
- protected DefaultWebViewComponent(ComponentTemplate template) {
- super(template)
- }
-
- protected DefaultWebViewComponent(Class extends ComponentTemplate> templateType) {
- super(templateType)
- }
-
- protected DefaultWebViewComponent(ComponentTemplateSource source) {
- super(source, compilerFunction)
- }
-
- protected DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
- super(source, compiler)
- }
-
- /**
- * A convenience constructor which creates a {@link ComponentTemplateSource}
- * from the given {@code source} parameter and passes it to super. See
- * {@link ComponentTemplateSource} for possible types.
- *
- * @param source the object passed to {@link ComponentTemplateSource#of}
- * @param compiler the compiler to use
- *
- * @see ComponentTemplateSource
- */
- @SuppressWarnings('GroovyAssignabilityCheck')
- protected DefaultWebViewComponent(Object source, WebViewComponentTemplateCompiler compiler) {
- super(ComponentTemplateSource.of(source), compiler)
- }
-
- /**
- * A convenience constructor which creates a {@link ComponentTemplateSource}
- * from the given {@code source} parameter and passes it to super. See
- * {@link ComponentTemplateSource} for possible types.
- *
- * @param source the object passed to {@link ComponentTemplateSource#of}
- *
- * @see ComponentTemplateSource
- */
- @SuppressWarnings('GroovyAssignabilityCheck')
- protected DefaultWebViewComponent(Object source) {
- super(ComponentTemplateSource.of(source), compilerFunction)
- }
-
- @Override
- protected final Class extends AbstractViewComponent> getSelfClass() {
- this.class
- }
-
-}
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 a3b83db..68ae298 100644
--- a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy
+++ b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentContext.groovy
@@ -2,26 +2,16 @@ package groowt.view.web
import groowt.view.component.context.ComponentScope
import groowt.view.component.context.DefaultComponentContext
-import groowt.view.component.ViewComponent
-import groowt.view.web.lib.Fragment
-import groowt.view.web.runtime.DefaultWebViewComponentChildCollection
-import org.jetbrains.annotations.ApiStatus
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
- @Override
- protected ComponentScope getNewDefaultScope() {
- new WebViewScope()
+ DefaultWebViewComponentContext() {
+ this.pushScope(WebViewComponentScope.getDefaultRootScope())
}
@Override
- @ApiStatus.Internal
- ViewComponent createFragment(Closure> childCollector) {
- def childCollection = new DefaultWebViewComponentChildCollection()
- childCollector.call(childCollection)
- def fragment = new Fragment()
- fragment.childRenderers = childCollection.children
- fragment
+ protected ComponentScope getNewDefaultScope() {
+ new WebViewComponentScope()
}
}
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 0b649c8..efb6576 100644
--- a/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy
+++ b/web-views/src/main/groovy/groowt/view/web/WebViewComponentFactories.groovy
@@ -3,20 +3,42 @@ 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 java.util.function.Function
+import static groowt.view.component.factory.ComponentFactories.ofClosureClassType
final class WebViewComponentFactories {
static ComponentFactory withAttr(
+ Class forClass,
@ClosureParams(value = FromString, options = 'java.util.Map')
- Closure closure
+ Closure extends T> closure
) {
- ComponentFactory.ofClosure { Map attr -> closure(attr) }
+ ofClosureClassType(forClass) { Map attr -> closure(attr) }
}
- static ComponentFactory withAttr(Function