Added some convenience methods to ComponentScope and related factories, etc. Fixed RunTemplate.
This commit is contained in:
parent
e4e972ea7b
commit
e22fc1622e
@ -49,8 +49,6 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
||||
this.template = instantiateTemplate(templateClass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setContext(ComponentContext context) {
|
||||
this.context = context;
|
||||
|
@ -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 <S extends ComponentScope> void configureRootScope(
|
||||
Class<S> 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<ComponentScope> 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 <S extends ComponentScope> void configureCurrentScope(
|
||||
Class<S> 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 <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
|
||||
|
@ -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 {
|
||||
|
||||
<T extends ViewComponent> void add(String typeName, Class<T> forClass, ComponentFactory<? extends T> factory);
|
||||
|
||||
default <T extends ViewComponent> void addWithNoArgConstructor(String typeName, Class<T> forClass) {
|
||||
this.add(typeName, forClass, ComponentFactories.ofNoArgConstructor(forClass));
|
||||
}
|
||||
|
||||
boolean contains(String typeName);
|
||||
|
||||
void remove(String typeName);
|
||||
@ -34,6 +39,17 @@ public interface ComponentScope {
|
||||
ComponentFactory<? extends T> factory
|
||||
);
|
||||
|
||||
default <T extends ViewComponent> void addWithNoArgConstructor(Class<T> forClass) {
|
||||
this.add(forClass, ComponentFactories.ofNoArgConstructor(forClass));
|
||||
}
|
||||
|
||||
default <T extends ViewComponent> void addWithNoArgConstructor(
|
||||
Class<T> publicType,
|
||||
Class<? extends T> implementingClass
|
||||
) {
|
||||
this.add(publicType, implementingClass, ComponentFactories.ofNoArgConstructor(implementingClass));
|
||||
}
|
||||
|
||||
boolean contains(Class<? extends ViewComponent> type);
|
||||
|
||||
<T extends ViewComponent> TypeAndFactory<T> get(Class<T> type);
|
||||
|
@ -22,6 +22,18 @@ public final class ComponentFactories {
|
||||
return (typeName, componentContext, args) -> supplier.get();
|
||||
}
|
||||
|
||||
public static <T extends ViewComponent> ComponentFactory<T> ofNoArgConstructor(
|
||||
Class<? extends T> viewComponentClass
|
||||
) {
|
||||
return (typeName, componentContext, args) -> {
|
||||
try {
|
||||
return viewComponentClass.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ComponentFactories() {}
|
||||
|
||||
}
|
||||
|
@ -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:
|
||||
* <ul>
|
||||
* <li>{@code String | Class, ComponentContext, ... -> T}</li>
|
||||
* <li>{@code ComponentContext, ... -> T}</li>
|
||||
* <li>{@code String | Class, ... -> T}</li>
|
||||
* <li>{@code ... -> }</li>
|
||||
* </ul>
|
||||
* 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 <strong>needs</strong> 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 <T> The type of the ViewComponent produced by this factory.
|
||||
*/
|
||||
public abstract class ComponentFactoryBase<T extends ViewComponent> extends GroovyObjectSupport
|
||||
implements ComponentFactory<T> {
|
||||
|
||||
protected static final String DO_CREATE = "doCreate";
|
||||
|
||||
protected static MetaMethod findDoCreateMethod(MetaClass metaClass, Class<?>[] types) {
|
||||
return metaClass.getMetaMethod(DO_CREATE, types);
|
||||
}
|
||||
|
||||
protected final Map<Class<?>[], 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();
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,7 @@ public abstract class AbstractRenderContext implements RenderContext {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public ComponentContext getComponentContext() {
|
||||
protected ComponentContext getComponentContext() {
|
||||
return this.componentContext;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
import groowt.view.web.lib.Echo
|
||||
---
|
||||
<Echo greeting="Hello!"><p>${context}</p></Echo>
|
||||
<Echo greeting=${cliGreeting}><p>$greeting</p></Echo>
|
||||
|
@ -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
|
||||
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass) {
|
||||
add(componentClass, withAttr(componentClass) { attr ->
|
||||
InvokerHelper.invokeConstructorOf(componentClass, attr) as T
|
||||
|
@ -0,0 +1,7 @@
|
||||
package groowt.view.web;
|
||||
|
||||
import groowt.view.component.context.ComponentScope;
|
||||
|
||||
public interface WebViewComponentScope extends ComponentScope {
|
||||
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass);
|
||||
}
|
@ -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('<BaseWebViewComponentTests.UsingGreeter />', 'Hello, World!', context)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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<Integer> {
|
||||
names = ['-A', '--attr', '--attribute'],
|
||||
description = 'Attribute(s) to pass to the template.'
|
||||
)
|
||||
Map<String, String> properties
|
||||
Map<String, String> attr
|
||||
|
||||
static class PropertiesComponent extends BaseWebViewComponent {
|
||||
static class RunnableTemplate extends BaseWebViewComponent {
|
||||
|
||||
private final Map<String, String> properties
|
||||
private final Map<String, String> cliAttr
|
||||
|
||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||
PropertiesComponent(Map<String, Object> attr) {
|
||||
super('${renderChildren()}')
|
||||
this.properties = attr.properties ?: [:]
|
||||
RunnableTemplate(Class<? extends ComponentTemplate> templateClass, Map<String, String> cliAttr) {
|
||||
super(templateClass)
|
||||
this.cliAttr = cliAttr
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -44,7 +43,7 @@ class RunTemplate implements Callable<Integer> {
|
||||
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<Integer> {
|
||||
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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user