From e22fc1622eb0d76f676f5b0c9ce3cb48ae518ba0 Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Sun, 12 May 2024 11:50:54 +0200 Subject: [PATCH] Added some convenience methods to ComponentScope and related factories, etc. Fixed RunTemplate. --- .../view/component/AbstractViewComponent.java | 2 - .../component/context/ComponentContext.java | 47 ++++++++ .../component/context/ComponentScope.java | 16 +++ .../component/factory/ComponentFactories.java | 12 ++ .../factory/ComponentFactoryBase.java | 107 ------------------ .../runtime/AbstractRenderContext.java | 2 +- .../component/runtime/ComponentWriter.java | 5 +- .../runtime/DefaultComponentWriter.java | 42 +------ web-views/sketching/echoElement.wvc | 2 +- .../web/DefaultWebViewComponentScope.groovy | 3 +- .../view/web/WebViewComponentScope.java | 7 ++ .../view/web/BaseWebViewComponentTests.groovy | 9 +- .../groowt/view/web/lib/FragmentTests.groovy | 4 +- .../groowt/view/web/tools/RunTemplate.groovy | 29 +++-- 14 files changed, 112 insertions(+), 175 deletions(-) delete mode 100644 view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java create mode 100644 web-views/src/main/java/groowt/view/web/WebViewComponentScope.java 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 e53f4e5..94889d4 100644 --- a/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java +++ b/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java @@ -49,8 +49,6 @@ public abstract class AbstractViewComponent implements ViewComponent { this.template = instantiateTemplate(templateClass); } - - @Override public void setContext(ComponentContext context) { this.context = context; 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 1985cd6..ce48887 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,5 +1,10 @@ package groowt.view.component.context; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import groovy.transform.stc.ClosureParams; +import groovy.transform.stc.FirstParam; +import groovy.transform.stc.SimpleType; import groowt.view.component.ViewComponent; import groowt.view.component.runtime.RenderContext; import org.jetbrains.annotations.Nullable; @@ -25,6 +30,27 @@ public interface ComponentContext { return scopeClass.cast(this.getRootScope()); } + default void configureRootScope( + @ClosureParams(value = SimpleType.class, options = "groowt.view.component.context.ComponentScope") + @DelegatesTo(ComponentScope.class) + Closure configure + ) { + final var rootScope = this.getRootScope(); + configure.setDelegate(rootScope); + configure.call(rootScope); + } + + default void configureRootScope( + Class scopeClass, + @ClosureParams(value = FirstParam.FirstGenericType.class) + @DelegatesTo(type = "S") + Closure configure + ) { + final var rootScope = this.getRootScope(scopeClass); + configure.setDelegate(rootScope); + configure.call(rootScope); + } + default ComponentScope getCurrentScope() { final List scopeStack = this.getScopeStack(); if (scopeStack.isEmpty()) { @@ -37,6 +63,27 @@ public interface ComponentContext { return scopeClass.cast(this.getCurrentScope()); } + default void configureCurrentScope( + @ClosureParams(value = SimpleType.class, options = "groowt.view.component.context.ComponentScope") + @DelegatesTo(ComponentScope.class) + Closure configure + ) { + final var currentScope = this.getCurrentScope(); + configure.setDelegate(currentScope); + configure.call(currentScope); + } + + default void configureCurrentScope( + Class scopeClass, + @ClosureParams(value = FirstParam.FirstGenericType.class) + @DelegatesTo(type = "S") + Closure configure + ) { + final var currentScope = this.getCurrentScope(scopeClass); + configure.setDelegate(currentScope); + configure.call(currentScope); + } + @Nullable ViewComponent getParent(); default @Nullable T getParent(Class parentClass) { 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 6d2ee35..9ab45f8 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,6 +1,7 @@ package groowt.view.component.context; import groowt.view.component.ViewComponent; +import groowt.view.component.factory.ComponentFactories; import groowt.view.component.factory.ComponentFactory; public interface ComponentScope { @@ -11,6 +12,10 @@ public interface ComponentScope { void add(String typeName, Class forClass, ComponentFactory factory); + default void addWithNoArgConstructor(String typeName, Class forClass) { + this.add(typeName, forClass, ComponentFactories.ofNoArgConstructor(forClass)); + } + boolean contains(String typeName); void remove(String typeName); @@ -34,6 +39,17 @@ public interface ComponentScope { ComponentFactory factory ); + default void addWithNoArgConstructor(Class forClass) { + this.add(forClass, ComponentFactories.ofNoArgConstructor(forClass)); + } + + default void addWithNoArgConstructor( + Class publicType, + Class implementingClass + ) { + this.add(publicType, implementingClass, ComponentFactories.ofNoArgConstructor(implementingClass)); + } + boolean contains(Class type); TypeAndFactory get(Class type); 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 index 3e1d8ed..ca2e209 100644 --- a/view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java +++ b/view-components/src/main/java/groowt/view/component/factory/ComponentFactories.java @@ -22,6 +22,18 @@ public final class ComponentFactories { return (typeName, componentContext, args) -> supplier.get(); } + public static ComponentFactory ofNoArgConstructor( + Class viewComponentClass + ) { + return (typeName, componentContext, args) -> { + try { + return viewComponentClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + private ComponentFactories() {} } 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 deleted file mode 100644 index c3ad2ea..0000000 --- a/view-components/src/main/java/groowt/view/component/factory/ComponentFactoryBase.java +++ /dev/null @@ -1,107 +0,0 @@ -package groowt.view.component.factory; - -import groovy.lang.GroovyObjectSupport; -import groovy.lang.MetaClass; -import groovy.lang.MetaMethod; -import groovy.lang.MissingMethodException; -import groowt.view.component.context.ComponentContext; -import groowt.view.component.ViewComponent; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class can be used to create custom implementations {@link ComponentFactory}. - * - * @implSpec All implementations must simply provide one or more {@code doCreate()} methods, - * which will be found by this class via the Groovy meta object protocol. The method(s) may - * have any of the following signatures: - *
    - *
  • {@code String | Class, ComponentContext, ... -> T}
  • - *
  • {@code ComponentContext, ... -> T}
  • - *
  • {@code String | Class, ... -> T}
  • - *
  • {@code ... -> }
  • - *
- * where '{@code ...}' represents zero or more additional arguments. - * - * @implNote In most cases, the implementation does not need to consume the - * {@link ComponentContext} argument, as the compiled template is required to contain - * {@code component.setContext(context)} statements following the component - * creation call. However, if the component needs the context - * (for example, to do custom scope logic), then it is more than okay to consume it. - * - * @implNote In the case Web View Components, the first additional argument will be - * a {@link Map} containing the attributes of the component, followed by any additional - * component constructor args. - * - * @param The type of the ViewComponent produced by this factory. - */ -public abstract class ComponentFactoryBase extends GroovyObjectSupport - implements ComponentFactory { - - protected static final String DO_CREATE = "doCreate"; - - protected static MetaMethod findDoCreateMethod(MetaClass metaClass, Class[] types) { - return metaClass.getMetaMethod(DO_CREATE, types); - } - - protected final Map[], MetaMethod> cache = new HashMap<>(); - - protected MetaMethod findDoCreateMethod(Object[] allArgs) { - return this.cache.computeIfAbsent(ComponentFactoryUtil.asTypes(allArgs), types -> - findDoCreateMethod(this.getMetaClass(), types) - ); - } - - @SuppressWarnings("unchecked") - protected T findAndDoCreate(Object type, ComponentContext componentContext, Object[] args) { - final Object[] typeContextAndArgs = ComponentFactoryUtil.flatten(type, componentContext, args); - final MetaMethod typeContextAndArgsMethod = this.findDoCreateMethod(typeContextAndArgs); - if (typeContextAndArgsMethod != null) { - return (T) typeContextAndArgsMethod.invoke(this, typeContextAndArgs); - } - - final Object[] typeAndContext = new Object[] { type, componentContext }; - final MetaMethod typeAndContextMethod = this.findDoCreateMethod(typeAndContext); - if (typeAndContextMethod != null) { - return (T) typeAndContextMethod.invoke(this, typeAndContext); - } - - final Object[] typeAndArgs = ComponentFactoryUtil.flatten(type, args); - final MetaMethod typeAndArgsMethod = this.findDoCreateMethod(typeAndArgs); - if (typeAndArgsMethod != null) { - return (T) typeAndArgsMethod.invoke(this, typeAndArgs); - } - - final Object[] typeOnly = new Object[] { type }; - final MetaMethod typeOnlyMethod = this.findDoCreateMethod(typeOnly); - if (typeOnlyMethod != null) { - return (T) typeOnlyMethod.invoke(this, typeOnly); - } - - final Object[] contextOnly = new Object[] { componentContext }; - final MetaMethod contextOnlyMethod = this.findDoCreateMethod(contextOnly); - if (contextOnlyMethod != null) { - return (T) contextOnlyMethod.invoke(this, contextOnly); - } - - final MetaMethod argsOnlyMethod = this.findDoCreateMethod(args); - if (argsOnlyMethod != null) { - return (T) argsOnlyMethod.invoke(this, args); - } - - throw new MissingMethodException(DO_CREATE, this.getClass(), args); - } - - @Override - public T create(String typeName, ComponentContext componentContext, Object... args) { - throw new UnsupportedOperationException(); - } - - // TODO: this needs to be updated. - @Override - 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/runtime/AbstractRenderContext.java b/view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java index f9d2eae..f52e7bf 100644 --- a/view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java +++ b/view-components/src/main/java/groowt/view/component/runtime/AbstractRenderContext.java @@ -20,7 +20,7 @@ public abstract class AbstractRenderContext implements RenderContext { this.writer = writer; } - public ComponentContext getComponentContext() { + protected ComponentContext getComponentContext() { return this.componentContext; } 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 index adc86c5..b157616 100644 --- a/view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java +++ b/view-components/src/main/java/groowt/view/component/runtime/ComponentWriter.java @@ -15,11 +15,10 @@ public interface ComponentWriter { 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); - + + @SuppressWarnings("unused") default void leftShift(Object object) { switch (object) { case String s -> this.append(s); diff --git a/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java b/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java index 4e6982b..f25d70f 100644 --- a/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java +++ b/view-components/src/main/java/groowt/view/component/runtime/DefaultComponentWriter.java @@ -57,33 +57,14 @@ public class DefaultComponentWriter implements ComponentWriter { } } - @Override - public void append(GString gString, int line, int column) { - final String content; - try { - content = gString.toString(); - } catch (Exception exception) { - throw new ComponentRenderException(line, column, exception); - } - try { - this.delegate.append(content); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } - } - - 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 { - this.doComponentRender(viewComponent); + this.getRenderContext().pushComponent(viewComponent); + this.getComponentContext().pushDefaultScope(); + viewComponent.renderTo(this.delegate); + this.getComponentContext().popScope(); + this.getRenderContext().popComponent(viewComponent); } catch (IOException ioException) { throw new RuntimeException(ioException); } catch (ComponentRenderException componentRenderException) { @@ -93,19 +74,6 @@ public class DefaultComponentWriter implements ComponentWriter { } } - @Override - public void append(ViewComponent viewComponent, int line, int column) { - try { - 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); - } - } - @Override public void append(Object object) { switch (object) { diff --git a/web-views/sketching/echoElement.wvc b/web-views/sketching/echoElement.wvc index 284adfd..3ab94a3 100644 --- a/web-views/sketching/echoElement.wvc +++ b/web-views/sketching/echoElement.wvc @@ -1,4 +1,4 @@ --- import groowt.view.web.lib.Echo --- -

${context}

+

$greeting

diff --git a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy index b66fc2e..64bd00f 100644 --- a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy +++ b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponentScope.groovy @@ -7,7 +7,7 @@ import org.codehaus.groovy.runtime.InvokerHelper import static groowt.view.web.WebViewComponentFactories.withAttr -class DefaultWebViewComponentScope extends DefaultComponentScope { +class DefaultWebViewComponentScope extends DefaultComponentScope implements WebViewComponentScope { static DefaultWebViewComponentScope getDefaultRootScope() { new DefaultWebViewComponentScope().tap { @@ -15,6 +15,7 @@ class DefaultWebViewComponentScope extends DefaultComponentScope { } } + @Override void addWithAttr(Class componentClass) { add(componentClass, withAttr(componentClass) { attr -> InvokerHelper.invokeConstructorOf(componentClass, attr) as T diff --git a/web-views/src/main/java/groowt/view/web/WebViewComponentScope.java b/web-views/src/main/java/groowt/view/web/WebViewComponentScope.java new file mode 100644 index 0000000..5f6753d --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/WebViewComponentScope.java @@ -0,0 +1,7 @@ +package groowt.view.web; + +import groowt.view.component.context.ComponentScope; + +public interface WebViewComponentScope extends ComponentScope { + void addWithAttr(Class componentClass); +} diff --git a/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy b/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy index 61fd437..8fd24fc 100644 --- a/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/BaseWebViewComponentTests.groovy @@ -1,6 +1,5 @@ package groowt.view.web -import groowt.view.component.factory.ComponentFactories import groowt.view.web.lib.AbstractWebViewComponentTests import org.junit.jupiter.api.Test @@ -44,8 +43,8 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests { @Test void nestedGreeter() { - def context = this.context { - getRootScope(DefaultWebViewComponentScope).with { + def context = this.context() { + configureRootScope(WebViewComponentScope) { addWithAttr(Greeter) } } @@ -55,9 +54,9 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests { @Test void doubleNested() { def context = this.context { - getRootScope(DefaultWebViewComponentScope).with { + configureRootScope(WebViewComponentScope) { addWithAttr(Greeter) - add(UsingGreeter, ComponentFactories.ofSupplier { new UsingGreeter() }) + addWithNoArgConstructor(UsingGreeter) } } this.doTest('', 'Hello, World!', context) diff --git a/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy b/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy index ccc35b5..648d84d 100644 --- a/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/lib/FragmentTests.groovy @@ -1,8 +1,8 @@ package groowt.view.web.lib import groowt.view.web.BaseWebViewComponent -import groowt.view.web.DefaultWebViewComponentScope import groowt.view.web.WebViewComponentContext +import groowt.view.web.WebViewComponentScope import org.junit.jupiter.api.Test class FragmentTests extends AbstractWebViewComponentTests { @@ -20,7 +20,7 @@ class FragmentTests extends AbstractWebViewComponentTests { @Override void configureContext(WebViewComponentContext context) { - context.getRootScope(DefaultWebViewComponentScope).with { + context.configureRootScope(WebViewComponentScope) { addWithAttr(Greeter) } } diff --git a/web-views/src/tools/groovy/groowt/view/web/tools/RunTemplate.groovy b/web-views/src/tools/groovy/groowt/view/web/tools/RunTemplate.groovy index c0917da..a2ec627 100644 --- a/web-views/src/tools/groovy/groowt/view/web/tools/RunTemplate.groovy +++ b/web-views/src/tools/groovy/groowt/view/web/tools/RunTemplate.groovy @@ -1,10 +1,10 @@ package groowt.view.web.tools +import groowt.view.component.ComponentTemplate import groowt.view.component.compiler.SimpleComponentTemplateClassFactory import groowt.view.component.compiler.source.ComponentTemplateSource -import groowt.view.component.context.DefaultComponentContext -import groowt.view.component.runtime.DefaultComponentWriter import groowt.view.web.BaseWebViewComponent +import groowt.view.web.DefaultWebViewComponentContext import groowt.view.web.compiler.AnonymousWebViewComponent import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit import picocli.CommandLine @@ -27,16 +27,15 @@ class RunTemplate implements Callable { names = ['-A', '--attr', '--attribute'], description = 'Attribute(s) to pass to the template.' ) - Map properties + Map attr - static class PropertiesComponent extends BaseWebViewComponent { + static class RunnableTemplate extends BaseWebViewComponent { - private final Map properties + private final Map cliAttr - @SuppressWarnings('GroovyAssignabilityCheck') - PropertiesComponent(Map attr) { - super('${renderChildren()}') - this.properties = attr.properties ?: [:] + RunnableTemplate(Class templateClass, Map cliAttr) { + super(templateClass) + this.cliAttr = cliAttr } @Override @@ -44,7 +43,7 @@ class RunTemplate implements Callable { try { return super.getProperty(propertyName) } catch (Exception ignored) { - return this.properties.get(propertyName) + return this.cliAttr.get(propertyName) } } @@ -61,14 +60,12 @@ class RunTemplate implements Callable { def compileResult = compileUnit.compile() def templateLoader = new SimpleComponentTemplateClassFactory() def templateClass = templateLoader.getTemplateClass(compileResult) - def template = templateClass.getConstructor().newInstance() - def context = new DefaultComponentContext() - context.pushDefaultScope() + def runnableTemplate = new RunnableTemplate(templateClass, this.attr) + def componentContext = new DefaultWebViewComponentContext() + runnableTemplate.context = componentContext - def componentWriter = new DefaultComponentWriter(new OutputStreamWriter(System.out)) - - template.renderer.call(context, componentWriter) + println runnableTemplate.render() return 0 }