Major refactoring of compiler pipeline.
This commit is contained in:
parent
45781209c0
commit
2d4e085bb3
@ -1,6 +1,7 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||||
import groowt.view.component.context.ComponentContext;
|
import groowt.view.component.context.ComponentContext;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
@ -29,13 +30,24 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractViewComponent(ComponentTemplateSource source, Function<String, ComponentTemplateCompiler> getCompiler) {
|
protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) {
|
||||||
final Class<? extends AbstractViewComponent> selfClass = this.getSelfClass();
|
try {
|
||||||
this.template = getCompiler.apply(selfClass.getPackageName()).compile(selfClass, source);
|
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
||||||
|
} catch (ComponentTemplateCompileErrorException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) {
|
protected AbstractViewComponent(
|
||||||
this.template = compiler.compile(this.getSelfClass(), source);
|
ComponentTemplateSource source,
|
||||||
|
Function<? super Class<? extends AbstractViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
||||||
|
) {
|
||||||
|
final var compiler = compilerFunction.apply(this.getSelfClass());
|
||||||
|
try {
|
||||||
|
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
||||||
|
} catch (ComponentTemplateCompileErrorException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Class<? extends AbstractViewComponent> getSelfClass();
|
protected abstract Class<? extends AbstractViewComponent> getSelfClass();
|
||||||
|
@ -1,52 +1,41 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.io.Reader;
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public abstract class AbstractComponentTemplateCompiler implements ComponentTemplateCompiler {
|
public abstract class AbstractComponentTemplateCompiler implements ComponentTemplateCompiler {
|
||||||
|
|
||||||
protected abstract ComponentTemplate compile(
|
private final GroovyClassLoader groovyClassLoader;
|
||||||
|
|
||||||
|
public AbstractComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
|
||||||
|
this.groovyClassLoader = groovyClassLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ComponentTemplateCompileResult compile(
|
||||||
ComponentTemplateSource componentTemplateSource,
|
ComponentTemplateSource componentTemplateSource,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Reader actualSource
|
Reader actualSource
|
||||||
);
|
) throws ComponentTemplateCompileErrorException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentTemplate compile(Class<? extends ViewComponent> forClass, ComponentTemplateSource source) {
|
public ComponentTemplateCompileResult compile(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
||||||
return switch (source) {
|
throws ComponentTemplateCompileErrorException {
|
||||||
case FileSource(File file) -> {
|
try (final Reader reader = ComponentTemplateSource.toReader(source)) {
|
||||||
try {
|
return this.compile(source, forClass, reader);
|
||||||
yield this.compile(source, forClass, new FileReader(file));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case StringSource(String rawSource) ->
|
|
||||||
this.compile(source, forClass, new StringReader(rawSource));
|
|
||||||
case InputStreamSource(InputStream inputStream) ->
|
|
||||||
this.compile(source, forClass, new InputStreamReader(inputStream));
|
|
||||||
case URISource(URI uri) -> {
|
|
||||||
try {
|
|
||||||
yield this.compile(source, forClass, new InputStreamReader(uri.toURL().openStream()));
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case URLSource(URL url) -> {
|
|
||||||
try {
|
@Override
|
||||||
yield this.compile(source, forClass, new InputStreamReader(url.openStream()));
|
public ComponentTemplate compileAndGet(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
||||||
} catch (IOException e) {
|
throws ComponentTemplateCompileErrorException {
|
||||||
throw new RuntimeException(e);
|
return this.compileAndGet(this.groovyClassLoader, forClass, source);
|
||||||
}
|
|
||||||
}
|
|
||||||
case ReaderSource(Reader reader) -> this.compile(source, forClass, reader);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -10,21 +13,92 @@ import java.util.Map;
|
|||||||
|
|
||||||
public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
|
public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
|
||||||
|
|
||||||
private final Map<Class<? extends ViewComponent>, ComponentTemplate> cache = new HashMap<>();
|
private record CachedTemplate(
|
||||||
|
ComponentTemplateCompileResult compileResult,
|
||||||
|
@Nullable ComponentTemplate template
|
||||||
|
) {}
|
||||||
|
|
||||||
@Override
|
private final Map<Class<? extends ViewComponent>, CachedTemplate> cache = new HashMap<>();
|
||||||
protected final ComponentTemplate compile(
|
|
||||||
ComponentTemplateSource source,
|
public CachingComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
|
||||||
Class<? extends ViewComponent> forClass,
|
super(groovyClassLoader);
|
||||||
Reader sourceReader
|
|
||||||
) {
|
|
||||||
return this.cache.computeIfAbsent(forClass, ignored -> this.doCompile(source, forClass, sourceReader));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ComponentTemplate doCompile(
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
} 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));
|
||||||
|
return compileResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ComponentTemplateCompileResult doCompile(
|
||||||
ComponentTemplateSource source,
|
ComponentTemplateSource source,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Reader sourceReader
|
Reader reader
|
||||||
);
|
) throws ComponentTemplateCompileErrorException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@ import groowt.view.component.ViewComponent;
|
|||||||
/**
|
/**
|
||||||
* Represents an exception thrown while attempting to instantiate a ComponentTemplate during compilation.
|
* Represents an exception thrown while attempting to instantiate a ComponentTemplate during compilation.
|
||||||
*/
|
*/
|
||||||
public class ComponentTemplateCompileException extends RuntimeException {
|
public class ComponentTemplateCompileErrorException extends Exception {
|
||||||
|
|
||||||
private final Class<? extends ViewComponent> forClass;
|
private final Class<? extends ViewComponent> forClass;
|
||||||
private final Object templateSource;
|
private final Object templateSource;
|
||||||
|
|
||||||
public ComponentTemplateCompileException(
|
public ComponentTemplateCompileErrorException(
|
||||||
String message,
|
String message,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Object templateSource
|
Object templateSource
|
||||||
@ -20,7 +20,7 @@ public class ComponentTemplateCompileException extends RuntimeException {
|
|||||||
this.templateSource = templateSource;
|
this.templateSource = templateSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentTemplateCompileException(
|
public ComponentTemplateCompileErrorException(
|
||||||
String message,
|
String message,
|
||||||
Throwable cause,
|
Throwable cause,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
@ -31,7 +31,7 @@ public class ComponentTemplateCompileException extends RuntimeException {
|
|||||||
this.templateSource = templateSource;
|
this.templateSource = templateSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentTemplateCompileException(
|
public ComponentTemplateCompileErrorException(
|
||||||
Throwable cause,
|
Throwable cause,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Object templateSource
|
Object templateSource
|
||||||
@ -41,6 +41,15 @@ public class ComponentTemplateCompileException extends RuntimeException {
|
|||||||
this.templateSource = templateSource;
|
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() {
|
public Class<? extends ViewComponent> getForClass() {
|
||||||
return this.forClass;
|
return this.forClass;
|
||||||
}
|
}
|
@ -1,9 +1,34 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ComponentTemplateCompiler {
|
public interface ComponentTemplateCompiler {
|
||||||
ComponentTemplate compile(Class<? extends ViewComponent> forClass, ComponentTemplateSource source);
|
|
||||||
|
record ComponentTemplateCompileResult(GroovyClass templateClass, List<GroovyClass> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
final var missingStack = new LinkedList<>(this.scopeStack);
|
final var missingStack = new LinkedList<>(this.scopeStack);
|
||||||
NoFactoryMissingException first = null;
|
NoFactoryMissingException first = null;
|
||||||
while (!missingStack.isEmpty()) {
|
while (!missingStack.isEmpty()) {
|
||||||
final ComponentScope scope = getStack.pop();
|
final ComponentScope scope = missingStack.pop();
|
||||||
try {
|
try {
|
||||||
return new DefaultResolved(component, scope.factoryMissing(component));
|
return new DefaultResolved(component, scope.factoryMissing(component));
|
||||||
} catch (NoFactoryMissingException e) {
|
} catch (NoFactoryMissingException e) {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package groowt.view.component.factory;
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
@ -40,6 +38,35 @@ public sealed interface ComponentTemplateSource {
|
|||||||
return of(ComponentTemplateSource.class.getClassLoader().getResource(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 StringSource(String template) implements ComponentTemplateSource {}
|
||||||
|
|
||||||
record FileSource(File templateFile) implements ComponentTemplateSource {}
|
record FileSource(File templateFile) implements ComponentTemplateSource {}
|
||||||
|
@ -6,5 +6,5 @@ void consume(out) {
|
|||||||
@groovy.transform.Field
|
@groovy.transform.Field
|
||||||
String greeting = 'Hello'
|
String greeting = 'Hello'
|
||||||
---
|
---
|
||||||
$greeting, ${consume(out)}!
|
$greeting, ${consume(it)}!
|
||||||
What a nice day.
|
What a nice day.
|
||||||
|
@ -2,28 +2,60 @@ package groowt.view.web
|
|||||||
|
|
||||||
import groowt.view.component.AbstractViewComponent
|
import groowt.view.component.AbstractViewComponent
|
||||||
import groowt.view.component.ComponentTemplate
|
import groowt.view.component.ComponentTemplate
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompiler
|
||||||
import groowt.view.component.factory.ComponentTemplateSource
|
import groowt.view.component.factory.ComponentTemplateSource
|
||||||
|
import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler
|
||||||
|
import groowt.view.web.compiler.WebViewComponentTemplateCompiler
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
|
||||||
class DefaultWebViewComponent extends AbstractWebViewComponent {
|
import java.util.function.Function
|
||||||
|
|
||||||
DefaultWebViewComponent() {}
|
abstract class DefaultWebViewComponent extends AbstractWebViewComponent {
|
||||||
|
|
||||||
DefaultWebViewComponent(ComponentTemplate template) {
|
private static final GroovyClassLoader groovyClassLoader =
|
||||||
|
new GroovyClassLoader(DefaultWebViewComponent.classLoader)
|
||||||
|
|
||||||
|
private static final Function<Class, ComponentTemplateCompiler> compilerFunction = { Class givenSelfClass ->
|
||||||
|
new DefaultWebViewComponentTemplateCompiler(
|
||||||
|
groovyClassLoader,
|
||||||
|
CompilerConfiguration.DEFAULT,
|
||||||
|
givenSelfClass.packageName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultWebViewComponent() {}
|
||||||
|
|
||||||
|
protected DefaultWebViewComponent(ComponentTemplate template) {
|
||||||
super(template)
|
super(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultWebViewComponent(Class<? extends ComponentTemplate> templateType) {
|
protected DefaultWebViewComponent(Class<? extends ComponentTemplate> templateType) {
|
||||||
super(templateType)
|
super(templateType)
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultWebViewComponent(ComponentTemplateSource source) {
|
protected DefaultWebViewComponent(ComponentTemplateSource source) {
|
||||||
super(source)
|
super(source, compilerFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
protected DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
||||||
super(source, 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}
|
* A convenience constructor which creates a {@link ComponentTemplateSource}
|
||||||
* from the given {@code source} parameter and passes it to super. See
|
* from the given {@code source} parameter and passes it to super. See
|
||||||
@ -34,8 +66,8 @@ class DefaultWebViewComponent extends AbstractWebViewComponent {
|
|||||||
* @see ComponentTemplateSource
|
* @see ComponentTemplateSource
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||||
DefaultWebViewComponent(Object source) {
|
protected DefaultWebViewComponent(Object source) {
|
||||||
super(ComponentTemplateSource.of(source))
|
super(ComponentTemplateSource.of(source), compilerFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,15 +3,17 @@ package groowt.view.web;
|
|||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groowt.view.component.AbstractViewComponent;
|
import groowt.view.component.AbstractViewComponent;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
|
import groowt.view.web.compiler.WebViewComponentTemplateCompiler;
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentWriter;
|
import groowt.view.web.runtime.DefaultWebViewComponentWriter;
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
||||||
|
|
||||||
@ -27,17 +29,18 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
|||||||
super(templateClass);
|
super(templateClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractWebViewComponent(ComponentTemplateSource source) {
|
|
||||||
super(source, packageName -> new DefaultWebViewComponentTemplateCompiler(
|
|
||||||
CompilerConfiguration.DEFAULT,
|
|
||||||
packageName
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
protected AbstractWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
||||||
super(source, compiler);
|
super(source, compiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected AbstractWebViewComponent(
|
||||||
|
ComponentTemplateSource source,
|
||||||
|
Function<? super Class<? extends AbstractWebViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
||||||
|
) {
|
||||||
|
super(source, selfClass -> compilerFunction.apply((Class<AbstractWebViewComponent>) selfClass));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WebViewChildRenderer> getChildRenderers() {
|
public List<WebViewChildRenderer> getChildRenderers() {
|
||||||
if (this.childRenderers == null) {
|
if (this.childRenderers == null) {
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.compiler.CachingComponentTemplateCompiler;
|
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
|
||||||
import groowt.view.web.analysis.MismatchedComponentTypeAnalysis;
|
|
||||||
import groowt.view.web.antlr.CompilationUnitParseResult;
|
|
||||||
import groowt.view.web.antlr.ParserUtil;
|
|
||||||
import groowt.view.web.antlr.TokenList;
|
|
||||||
import groowt.view.web.ast.DefaultAstBuilder;
|
|
||||||
import groowt.view.web.ast.DefaultNodeFactory;
|
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
|
||||||
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
|
||||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
|
||||||
import org.codehaus.groovy.control.CompilationUnit;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
import org.codehaus.groovy.control.Phases;
|
|
||||||
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
|
||||||
import org.codehaus.groovy.tools.GroovyClass;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
|
||||||
implements WebViewComponentTemplateCompiler {
|
|
||||||
|
|
||||||
private final CompilerConfiguration configuration;
|
|
||||||
private final String defaultPackageName;
|
|
||||||
private final int phase;
|
|
||||||
|
|
||||||
private GroovyClassLoader groovyClassLoader;
|
|
||||||
|
|
||||||
public DefaultWebViewComponentTemplateCompiler(CompilerConfiguration configuration, String defaultPackageName) {
|
|
||||||
this(configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public DefaultWebViewComponentTemplateCompiler(
|
|
||||||
CompilerConfiguration configuration,
|
|
||||||
String defaultPackageName,
|
|
||||||
int phase
|
|
||||||
) {
|
|
||||||
this.configuration = configuration;
|
|
||||||
this.defaultPackageName = defaultPackageName;
|
|
||||||
this.phase = phase;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected GroovyClassLoader getGroovyClassLoader() {
|
|
||||||
if (this.groovyClassLoader == null) {
|
|
||||||
this.groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader());
|
|
||||||
}
|
|
||||||
return this.groovyClassLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
|
|
||||||
this.groovyClassLoader = Objects.requireNonNull(groovyClassLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void useOwnClassLoader() {
|
|
||||||
this.groovyClassLoader = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ComponentTemplate doCompile(
|
|
||||||
@Nullable ComponentTemplateSource source,
|
|
||||||
@Nullable Class<? extends ViewComponent> forClass,
|
|
||||||
Reader sourceReader
|
|
||||||
) {
|
|
||||||
if (source instanceof ComponentTemplateSource.URISource uriSource) {
|
|
||||||
return this.doCompile(forClass, sourceReader, uriSource.templateURI());
|
|
||||||
} else if (source instanceof ComponentTemplateSource.URLSource urlSource) {
|
|
||||||
try {
|
|
||||||
return this.doCompile(forClass, sourceReader, urlSource.templateURL().toURI());
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.doCompile(forClass, sourceReader, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ComponentTemplate doCompile(
|
|
||||||
@Nullable Class<? extends ViewComponent> forClass,
|
|
||||||
Reader reader,
|
|
||||||
@Nullable URI uri
|
|
||||||
) {
|
|
||||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
|
||||||
|
|
||||||
final List<MismatchedComponentTypeError> mismatchedComponentTypeErrors =
|
|
||||||
MismatchedComponentTypeAnalysis.check(parseResult.getCompilationUnitContext());
|
|
||||||
|
|
||||||
final var tokenList = new TokenList(parseResult.getTokenStream());
|
|
||||||
final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
|
|
||||||
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
|
||||||
|
|
||||||
final var groovyCompilationUnit = new CompilationUnit(this.configuration);
|
|
||||||
final var transpiler = new DefaultGroovyTranspiler(
|
|
||||||
groovyCompilationUnit,
|
|
||||||
this.defaultPackageName,
|
|
||||||
DefaultTranspilerConfiguration::new
|
|
||||||
);
|
|
||||||
|
|
||||||
final var ownerComponentName = forClass != null ? forClass.getSimpleName() : "AnonymousComponent";
|
|
||||||
final var templateClassName = ownerComponentName + "Template";
|
|
||||||
final var fqn = this.defaultPackageName + "." + templateClassName;
|
|
||||||
|
|
||||||
final var readerSource = new AbstractReaderSource(this.configuration) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Reader getReader() throws IOException {
|
|
||||||
reader.reset();
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable URI getURI() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup() {
|
|
||||||
super.cleanup();
|
|
||||||
try {
|
|
||||||
reader.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
transpiler.transpile(cuNode, tokenList, ownerComponentName, readerSource);
|
|
||||||
|
|
||||||
groovyCompilationUnit.compile(this.phase);
|
|
||||||
|
|
||||||
final var classes = groovyCompilationUnit.getClasses();
|
|
||||||
Class<?> templateClass = null;
|
|
||||||
for (final GroovyClass groovyClass : classes) {
|
|
||||||
if (groovyClass.getName().equals(fqn)) {
|
|
||||||
if (templateClass == null) {
|
|
||||||
templateClass = this.getGroovyClassLoader().defineClass(
|
|
||||||
groovyClass.getName(), groovyClass.getBytes()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Somehow found two classes with same name.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.getGroovyClassLoader().defineClass(
|
|
||||||
groovyClass.getName(), groovyClass.getBytes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (templateClass == null) {
|
|
||||||
throw new IllegalStateException("Did not find templateClass");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (ComponentTemplate) templateClass.getConstructor().newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ComponentTemplateCompileException(e, forClass, reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compileAnonymous(Reader reader) {
|
|
||||||
return this.doCompile(null, null, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
public interface WebViewComponentTemplateCompiler extends ComponentTemplateCompiler {
|
|
||||||
ComponentTemplate compileAnonymous(Reader reader);
|
|
||||||
}
|
|
@ -17,8 +17,8 @@ private fun getErrorMessage(
|
|||||||
openType: ComponentTypeContext,
|
openType: ComponentTypeContext,
|
||||||
closingType: ComponentTypeContext
|
closingType: ComponentTypeContext
|
||||||
) = "The component's opening and closing tags' types must match exactly. " +
|
) = "The component's opening and closing tags' types must match exactly. " +
|
||||||
"Found ${openType.text} at [${SourcePosition.formatStartOfToken(openType.start)}] " +
|
"Found '${openType.text}' at ${SourcePosition.formatStartOfTokenLong(openType.start)} " +
|
||||||
"and ${closingType.text} at [${SourcePosition.formatStartOfToken(closingType.start)}]."
|
"and '${closingType.text}' at ${SourcePosition.formatStartOfTokenLong(closingType.start)}."
|
||||||
|
|
||||||
private fun test(
|
private fun test(
|
||||||
openIdentifiers: List<Token>,
|
openIdentifiers: List<Token>,
|
||||||
@ -52,7 +52,7 @@ private fun doCheck(tree: ParseTree, destination: MutableList<MismatchedComponen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MismatchedComponentTypeError(val tree: ParseTree, val message: String)
|
data class MismatchedComponentTypeError(val component: ComponentWithChildrenContext, val message: String)
|
||||||
|
|
||||||
fun check(tree: ParseTree): List<MismatchedComponentTypeError> {
|
fun check(tree: ParseTree): List<MismatchedComponentTypeError> {
|
||||||
val result: MutableList<MismatchedComponentTypeError> = ArrayList()
|
val result: MutableList<MismatchedComponentTypeError> = ArrayList()
|
||||||
|
@ -5,7 +5,6 @@ import org.antlr.v4.runtime.tree.ErrorNode;
|
|||||||
import org.antlr.v4.runtime.tree.Tree;
|
import org.antlr.v4.runtime.tree.Tree;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class AntlrUtil {
|
public final class AntlrUtil {
|
||||||
@ -36,8 +35,8 @@ public final class AntlrUtil {
|
|||||||
return this.nodesWithRecognitionException.size() + this.errorNodes.size();
|
return this.nodesWithRecognitionException.size() + this.errorNodes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Tree> getAll() {
|
public List<Tree> getAll() {
|
||||||
final Collection<Tree> all = new ArrayList<>(this.nodesWithRecognitionException);
|
final List<Tree> all = new ArrayList<>(this.nodesWithRecognitionException);
|
||||||
all.addAll(this.errorNodes);
|
all.addAll(this.errorNodes);
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,330 @@
|
|||||||
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import groowt.view.component.AbstractViewComponent;
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.compiler.CachingComponentTemplateCompiler;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
|
import groowt.view.web.analysis.MismatchedComponentTypeAnalysis;
|
||||||
|
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
||||||
|
import groowt.view.web.antlr.AntlrUtil;
|
||||||
|
import groowt.view.web.antlr.CompilationUnitParseResult;
|
||||||
|
import groowt.view.web.antlr.ParserUtil;
|
||||||
|
import groowt.view.web.antlr.TokenList;
|
||||||
|
import groowt.view.web.ast.DefaultAstBuilder;
|
||||||
|
import groowt.view.web.ast.DefaultNodeFactory;
|
||||||
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
|
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
||||||
|
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
||||||
|
import groowt.view.web.util.SourcePosition;
|
||||||
|
import org.antlr.v4.runtime.ParserRuleContext;
|
||||||
|
import org.antlr.v4.runtime.Token;
|
||||||
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
|
import org.antlr.v4.runtime.tree.Tree;
|
||||||
|
import org.codehaus.groovy.control.CompilationFailedException;
|
||||||
|
import org.codehaus.groovy.control.CompilationUnit;
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
|
import org.codehaus.groovy.control.Phases;
|
||||||
|
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
||||||
|
implements WebViewComponentTemplateCompiler {
|
||||||
|
|
||||||
|
protected static final class AnonymousWebViewComponent extends AbstractViewComponent {
|
||||||
|
|
||||||
|
// DO NOT INSTANTIATE, this is merely a marker class
|
||||||
|
private AnonymousWebViewComponent() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(ComponentContext context) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentContext getContext() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ComponentTemplate getTemplate() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setTemplate(ComponentTemplate template) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void beforeRender() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterRender() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderTo(Writer out) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<? extends AbstractViewComponent> getSelfClass() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final CompilerConfiguration configuration;
|
||||||
|
private final String defaultPackageName;
|
||||||
|
private final int phase;
|
||||||
|
|
||||||
|
public DefaultWebViewComponentTemplateCompiler(
|
||||||
|
GroovyClassLoader groovyClassLoader,
|
||||||
|
CompilerConfiguration configuration,
|
||||||
|
String defaultPackageName
|
||||||
|
) {
|
||||||
|
this(groovyClassLoader, configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public DefaultWebViewComponentTemplateCompiler(
|
||||||
|
GroovyClassLoader groovyClassLoader,
|
||||||
|
CompilerConfiguration configuration,
|
||||||
|
String defaultPackageName,
|
||||||
|
int phase
|
||||||
|
) {
|
||||||
|
super(groovyClassLoader);
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.defaultPackageName = defaultPackageName;
|
||||||
|
this.phase = phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
|
TerminalNode terminalNode,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader
|
||||||
|
) {
|
||||||
|
final Token offending = terminalNode.getSymbol();
|
||||||
|
return new WebViewComponentTemplateCompileException(
|
||||||
|
"Compile error on token at " + SourcePosition.fromStartOfToken(offending).toStringLong() + ".",
|
||||||
|
forClass,
|
||||||
|
reader,
|
||||||
|
offending
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
|
ParserRuleContext parserRuleContext,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader
|
||||||
|
) {
|
||||||
|
return new WebViewComponentTemplateCompileException(
|
||||||
|
"Compile error at " + SourcePosition.fromStartOfToken(parserRuleContext.getStart()).toStringLong()
|
||||||
|
+ ".",
|
||||||
|
forClass,
|
||||||
|
reader,
|
||||||
|
parserRuleContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WebViewComponentTemplateCompileException mapToErrorException(
|
||||||
|
Tree tree,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader
|
||||||
|
) {
|
||||||
|
if (tree instanceof TerminalNode terminalNode) {
|
||||||
|
return getException(terminalNode, forClass, reader);
|
||||||
|
} else if (tree instanceof ParserRuleContext parserRuleContext) {
|
||||||
|
return getException(parserRuleContext, forClass, reader);
|
||||||
|
} else {
|
||||||
|
return new WebViewComponentTemplateCompileException(
|
||||||
|
"Compile error with " + tree + ".",
|
||||||
|
forClass,
|
||||||
|
reader,
|
||||||
|
tree
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WebViewComponentTemplateCompileException mapToErrorException(
|
||||||
|
MismatchedComponentTypeError error,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader
|
||||||
|
) {
|
||||||
|
return new WebViewComponentTemplateCompileException(
|
||||||
|
error.getMessage(),
|
||||||
|
forClass,
|
||||||
|
reader,
|
||||||
|
error.getComponent()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ComponentTemplateCompileResult doCompile(
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader reader,
|
||||||
|
@Nullable URI uri
|
||||||
|
) throws ComponentTemplateCompileErrorException {
|
||||||
|
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
||||||
|
|
||||||
|
// check for parser/lexer errors
|
||||||
|
final var parseErrors = AntlrUtil.findErrorNodes(parseResult.getCompilationUnitContext());
|
||||||
|
if (!parseErrors.isEmpty()) {
|
||||||
|
if (parseErrors.getErrorCount() == 1) {
|
||||||
|
final var errorNode = parseErrors.getAll().getFirst();
|
||||||
|
throw mapToErrorException(errorNode, forClass, reader);
|
||||||
|
} else {
|
||||||
|
final var errorExceptions = parseErrors.getAll().stream()
|
||||||
|
.map(errorNode -> mapToErrorException(errorNode, forClass, reader))
|
||||||
|
.toList();
|
||||||
|
throw new MultipleWebViewComponentCompileErrorsException(errorExceptions, forClass, reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for mismatched type errors
|
||||||
|
final List<MismatchedComponentTypeError> mismatchedComponentTypeErrors =
|
||||||
|
MismatchedComponentTypeAnalysis.check(parseResult.getCompilationUnitContext());
|
||||||
|
|
||||||
|
if (!mismatchedComponentTypeErrors.isEmpty()) {
|
||||||
|
if (mismatchedComponentTypeErrors.size() == 1) {
|
||||||
|
throw mapToErrorException(mismatchedComponentTypeErrors.getFirst(), forClass, reader);
|
||||||
|
} else {
|
||||||
|
final var errorExceptions = mismatchedComponentTypeErrors.stream()
|
||||||
|
.map(error -> mapToErrorException(error, forClass, reader))
|
||||||
|
.toList();
|
||||||
|
throw new MultipleWebViewComponentCompileErrorsException(
|
||||||
|
errorExceptions,
|
||||||
|
forClass,
|
||||||
|
reader
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build ast
|
||||||
|
final var tokenList = new TokenList(parseResult.getTokenStream());
|
||||||
|
final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
|
||||||
|
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
||||||
|
|
||||||
|
// transpile to Groovy
|
||||||
|
final var groovyCompilationUnit = new CompilationUnit(this.configuration);
|
||||||
|
final var transpiler = new DefaultGroovyTranspiler(
|
||||||
|
groovyCompilationUnit,
|
||||||
|
this.defaultPackageName,
|
||||||
|
DefaultTranspilerConfiguration::new
|
||||||
|
);
|
||||||
|
|
||||||
|
final var ownerComponentName = forClass != null ? forClass.getSimpleName() : "AnonymousComponent" + System.nanoTime();
|
||||||
|
final var templateClassName = ownerComponentName + "Template";
|
||||||
|
final var fqn = this.defaultPackageName + "." + templateClassName;
|
||||||
|
|
||||||
|
final var readerSource = new AbstractReaderSource(this.configuration) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getReader() throws IOException {
|
||||||
|
reader.reset();
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable URI getURI() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
super.cleanup();
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
transpiler.transpile(cuNode, tokenList, ownerComponentName, readerSource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
groovyCompilationUnit.compile(this.phase);
|
||||||
|
} catch (CompilationFailedException compilationFailedException) {
|
||||||
|
throw new WebViewComponentTemplateCompileException(
|
||||||
|
"Error while compiling Groovy in " + templateClassName + " for component class " +
|
||||||
|
forClass.getName() + ".",
|
||||||
|
compilationFailedException,
|
||||||
|
forClass,
|
||||||
|
forClass,
|
||||||
|
reader
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the classes
|
||||||
|
final var allClasses = groovyCompilationUnit.getClasses();
|
||||||
|
GroovyClass templateGroovyClass = null;
|
||||||
|
final List<GroovyClass> otherClasses = new ArrayList<>();
|
||||||
|
for (final GroovyClass groovyClass : allClasses) {
|
||||||
|
if (groovyClass.getName().equals(fqn)) {
|
||||||
|
if (templateGroovyClass != null) {
|
||||||
|
throw new IllegalStateException("Already found a templateGroovyClass.");
|
||||||
|
}
|
||||||
|
templateGroovyClass = groovyClass;
|
||||||
|
} else {
|
||||||
|
otherClasses.add(groovyClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (templateGroovyClass == null) {
|
||||||
|
throw new IllegalStateException("Did not find templateClass");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ComponentTemplateCompileResult(templateGroovyClass, otherClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ComponentTemplateCompileResult doCompile(
|
||||||
|
@Nullable ComponentTemplateSource source,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Reader sourceReader
|
||||||
|
) throws ComponentTemplateCompileErrorException {
|
||||||
|
if (source instanceof ComponentTemplateSource.URISource uriSource) {
|
||||||
|
return this.doCompile(forClass, sourceReader, uriSource.templateURI());
|
||||||
|
} else if (source instanceof ComponentTemplateSource.URLSource urlSource) {
|
||||||
|
try {
|
||||||
|
return this.doCompile(forClass, sourceReader, urlSource.templateURL().toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.doCompile(forClass, sourceReader, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source)
|
||||||
|
throws ComponentTemplateCompileErrorException {
|
||||||
|
return this.compile(AnonymousWebViewComponent.class, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentTemplate compileAndGetAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException {
|
||||||
|
return this.compileAndGet(AnonymousWebViewComponent.class, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MultipleWebViewComponentCompileErrorsException extends ComponentTemplateCompileErrorException {
|
||||||
|
|
||||||
|
private final List<Throwable> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
public MultipleWebViewComponentCompileErrorsException(
|
||||||
|
String message,
|
||||||
|
List<? extends Throwable> errors,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Object templateSource
|
||||||
|
) {
|
||||||
|
super(message, forClass, templateSource);
|
||||||
|
this.errors.addAll(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleWebViewComponentCompileErrorsException(
|
||||||
|
List<? extends Throwable> errors,
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
Object templateSource
|
||||||
|
) {
|
||||||
|
super(forClass, templateSource);
|
||||||
|
this.errors.addAll(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Throwable> getErrors() {
|
||||||
|
return this.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,19 +1,19 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||||
import groowt.view.web.ast.node.Node;
|
import groowt.view.web.ast.node.Node;
|
||||||
import groowt.view.web.util.SourcePosition;
|
import groowt.view.web.util.SourcePosition;
|
||||||
|
|
||||||
public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileException {
|
public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileErrorException {
|
||||||
|
|
||||||
private final Node node;
|
private final Object node;
|
||||||
|
|
||||||
public WebViewComponentTemplateCompileException(
|
public WebViewComponentTemplateCompileException(
|
||||||
String message,
|
String message,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Object templateSource,
|
Object templateSource,
|
||||||
Node node
|
Object node
|
||||||
) {
|
) {
|
||||||
super(message, forClass, templateSource);
|
super(message, forClass, templateSource);
|
||||||
this.node = node;
|
this.node = node;
|
||||||
@ -24,7 +24,7 @@ public class WebViewComponentTemplateCompileException extends ComponentTemplateC
|
|||||||
Throwable cause,
|
Throwable cause,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Object templateSource,
|
Object templateSource,
|
||||||
Node node
|
Object node
|
||||||
) {
|
) {
|
||||||
super(message, cause, forClass, templateSource);
|
super(message, cause, forClass, templateSource);
|
||||||
this.node = node;
|
this.node = node;
|
||||||
@ -34,20 +34,24 @@ public class WebViewComponentTemplateCompileException extends ComponentTemplateC
|
|||||||
Throwable cause,
|
Throwable cause,
|
||||||
Class<? extends ViewComponent> forClass,
|
Class<? extends ViewComponent> forClass,
|
||||||
Object templateSource,
|
Object templateSource,
|
||||||
Node node
|
Object node
|
||||||
) {
|
) {
|
||||||
super(cause, forClass, templateSource);
|
super(cause, forClass, templateSource);
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getNode() {
|
public Object getNode() {
|
||||||
return this.node;
|
return this.node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
final SourcePosition start = this.node.getTokenRange().getStartPosition();
|
if (this.node instanceof Node asNode) {
|
||||||
return "Line " + start.line() + ", column " + start.column() + ": " + super.getMessage();
|
final SourcePosition start = asNode.getTokenRange().getStartPosition();
|
||||||
|
return "At " + start.toStringLong() + ": " + super.getMessage();
|
||||||
|
} else {
|
||||||
|
return super.getMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||||
|
import groowt.view.component.factory.ComponentTemplateSource;
|
||||||
|
|
||||||
|
public interface WebViewComponentTemplateCompiler extends ComponentTemplateCompiler {
|
||||||
|
ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException;
|
||||||
|
ComponentTemplate compileAndGetAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException;
|
||||||
|
}
|
@ -6,10 +6,14 @@ public record SourcePosition(int line, int column) {
|
|||||||
|
|
||||||
public static final SourcePosition UNKNOWN = new SourcePosition(-1, -1);
|
public static final SourcePosition UNKNOWN = new SourcePosition(-1, -1);
|
||||||
|
|
||||||
public static String formatStartOfToken(Token token) {
|
public static String formatStartOfTokenShort(Token token) {
|
||||||
return fromStartOfToken(token).toStringShort();
|
return fromStartOfToken(token).toStringShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatStartOfTokenLong(Token token) {
|
||||||
|
return fromStartOfToken(token).toStringLong();
|
||||||
|
}
|
||||||
|
|
||||||
public static SourcePosition fromStartOfToken(Token token) {
|
public static SourcePosition fromStartOfToken(Token token) {
|
||||||
return new SourcePosition(token.getLine(), token.getCharPositionInLine() + 1);
|
return new SourcePosition(token.getLine(), token.getCharPositionInLine() + 1);
|
||||||
}
|
}
|
||||||
@ -46,4 +50,8 @@ public record SourcePosition(int line, int column) {
|
|||||||
return this.line + "," + this.column;
|
return this.line + "," + this.column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toStringLong() {
|
||||||
|
return "line " + this.line + ", column " + this.column;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package groowt.view.web
|
package groowt.view.web
|
||||||
|
|
||||||
import groowt.view.component.factory.ComponentFactoryBase
|
import groowt.view.component.factory.ComponentFactoryBase
|
||||||
|
import groowt.view.web.lib.AbstractWebViewComponentTests
|
||||||
import groowt.view.web.lib.WithContext
|
import groowt.view.web.lib.WithContext
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
||||||
class DefaultWebViewComponentTests implements WithContext {
|
class DefaultWebViewComponentTests extends AbstractWebViewComponentTests {
|
||||||
|
|
||||||
private static final class Greeter extends DefaultWebViewComponent {
|
private static final class Greeter extends DefaultWebViewComponent {
|
||||||
|
|
||||||
@ -41,8 +42,7 @@ class DefaultWebViewComponentTests implements WithContext {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void withPreambleImport() {
|
void withPreambleImport() {
|
||||||
def c = new DefaultWebViewComponent(
|
this.doTest('''
|
||||||
'''
|
|
||||||
---
|
---
|
||||||
import groovy.transform.Field
|
import groovy.transform.Field
|
||||||
|
|
||||||
@ -50,10 +50,7 @@ class DefaultWebViewComponentTests implements WithContext {
|
|||||||
String greeting = 'Hello, World!'
|
String greeting = 'Hello, World!'
|
||||||
---
|
---
|
||||||
$greeting
|
$greeting
|
||||||
'''.stripIndent().trim()
|
'''.stripIndent().trim(), "Hello, World!")
|
||||||
)
|
|
||||||
c.context = this.context()
|
|
||||||
assertEquals("Hello, World!", c.render())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -62,9 +59,7 @@ class DefaultWebViewComponentTests implements WithContext {
|
|||||||
this.configureContext(it)
|
this.configureContext(it)
|
||||||
currentScope.add('Greeter', new GreeterFactory())
|
currentScope.add('Greeter', new GreeterFactory())
|
||||||
}
|
}
|
||||||
def c = new DefaultWebViewComponent('<Greeter target="World" />')
|
this.doTest('<Greeter target="World" />', 'Hello, World!', context)
|
||||||
c.context = context
|
|
||||||
assertEquals('Hello, World!', c.render())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -74,9 +69,7 @@ class DefaultWebViewComponentTests implements WithContext {
|
|||||||
currentScope.add('UsingGreeter') { new UsingGreeter() }
|
currentScope.add('UsingGreeter') { new UsingGreeter() }
|
||||||
currentScope.add('Greeter', new GreeterFactory())
|
currentScope.add('Greeter', new GreeterFactory())
|
||||||
}
|
}
|
||||||
def c = new DefaultWebViewComponent('<UsingGreeter />')
|
this.doTest('<UsingGreeter />', 'Hello, World!', context)
|
||||||
c.context = context
|
|
||||||
assertEquals('Hello, World!', c.render())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@ package groowt.view.web.lib
|
|||||||
|
|
||||||
|
|
||||||
import groowt.view.component.context.ComponentContext
|
import groowt.view.component.context.ComponentContext
|
||||||
import groowt.view.web.DefaultWebViewComponentTemplateCompiler
|
import groowt.view.component.factory.ComponentTemplateSource
|
||||||
import groowt.view.web.WebViewComponentTemplateCompiler
|
import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler
|
||||||
|
import groowt.view.web.compiler.WebViewComponentTemplateCompiler
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentWriter
|
import groowt.view.web.runtime.DefaultWebViewComponentWriter
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
|
||||||
@ -13,13 +14,14 @@ abstract class AbstractWebViewComponentTests implements WithContext {
|
|||||||
|
|
||||||
protected WebViewComponentTemplateCompiler compiler() {
|
protected WebViewComponentTemplateCompiler compiler() {
|
||||||
new DefaultWebViewComponentTemplateCompiler(
|
new DefaultWebViewComponentTemplateCompiler(
|
||||||
|
new GroovyClassLoader(this.class.classLoader),
|
||||||
CompilerConfiguration.DEFAULT,
|
CompilerConfiguration.DEFAULT,
|
||||||
this.class.packageName
|
this.class.packageName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doTest(Reader source, String expected, ComponentContext context) {
|
protected void doTest(Reader sourceReader, String expected, ComponentContext context) {
|
||||||
def template = this.compiler().compileAnonymous(source)
|
def template = this.compiler().compileAndGetAnonymous(ComponentTemplateSource.of(sourceReader))
|
||||||
def renderer = template.getRenderer()
|
def renderer = template.getRenderer()
|
||||||
def sw = new StringWriter()
|
def sw = new StringWriter()
|
||||||
def out = new DefaultWebViewComponentWriter(sw)
|
def out = new DefaultWebViewComponentWriter(sw)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package groowt.view.web.tools
|
package groowt.view.web.tools
|
||||||
|
|
||||||
import groowt.view.component.context.DefaultComponentContext
|
import groowt.view.component.context.DefaultComponentContext
|
||||||
import groowt.view.web.DefaultWebViewComponent
|
import groowt.view.component.factory.ComponentTemplateSource
|
||||||
|
import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler
|
||||||
|
import groowt.view.web.runtime.DefaultWebViewComponentWriter
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.Option
|
import picocli.CommandLine.Option
|
||||||
@ -26,13 +29,20 @@ class RunTemplate implements Callable<Integer> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer call() throws Exception {
|
Integer call() throws Exception {
|
||||||
def component = new DefaultWebViewComponent(this.template)
|
def gcl = new GroovyClassLoader(this.class.classLoader)
|
||||||
|
def compiler = new DefaultWebViewComponentTemplateCompiler(
|
||||||
|
gcl,
|
||||||
|
CompilerConfiguration.DEFAULT,
|
||||||
|
'groowt.view.web.tools'
|
||||||
|
)
|
||||||
|
def template = compiler.compileAndGetAnonymous(ComponentTemplateSource.of(this.template))
|
||||||
|
|
||||||
def context = new DefaultComponentContext()
|
def context = new DefaultComponentContext()
|
||||||
context.pushDefaultScope()
|
context.pushDefaultScope()
|
||||||
component.context = context
|
|
||||||
|
|
||||||
println component.render()
|
def componentWriter = new DefaultWebViewComponentWriter(new OutputStreamWriter(System.out))
|
||||||
|
|
||||||
|
template.renderer.call(context, componentWriter)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user