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 ad1b99b..e202d84 100644 --- a/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java +++ b/view-components/src/main/java/groowt/view/component/AbstractViewComponent.java @@ -1,6 +1,7 @@ package groowt.view.component; import groovy.lang.Closure; +import groowt.view.component.compiler.ComponentTemplateCompileErrorException; import groowt.view.component.compiler.ComponentTemplateCompiler; import groowt.view.component.context.ComponentContext; import groowt.view.component.factory.ComponentTemplateSource; @@ -29,13 +30,24 @@ public abstract class AbstractViewComponent implements ViewComponent { } } - protected AbstractViewComponent(ComponentTemplateSource source, Function getCompiler) { - final Class selfClass = this.getSelfClass(); - this.template = getCompiler.apply(selfClass.getPackageName()).compile(selfClass, source); + protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) { + try { + this.template = compiler.compileAndGet(this.getSelfClass(), source); + } catch (ComponentTemplateCompileErrorException e) { + throw new RuntimeException(e); + } } - protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) { - this.template = compiler.compile(this.getSelfClass(), source); + protected AbstractViewComponent( + ComponentTemplateSource source, + Function, ? 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 getSelfClass(); diff --git a/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java index e7d279c..91ab045 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java +++ b/view-components/src/main/java/groowt/view/component/compiler/AbstractComponentTemplateCompiler.java @@ -1,52 +1,41 @@ package groowt.view.component.compiler; +import groovy.lang.GroovyClassLoader; import groowt.view.component.ComponentTemplate; import groowt.view.component.ViewComponent; import groowt.view.component.factory.ComponentTemplateSource; -import groowt.view.component.factory.ComponentTemplateSource.*; -import java.io.*; -import java.net.URI; -import java.net.URL; +import java.io.IOException; +import java.io.Reader; 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, Class forClass, Reader actualSource - ); + ) throws ComponentTemplateCompileErrorException; @Override - public ComponentTemplate compile(Class forClass, ComponentTemplateSource source) { - return switch (source) { - case FileSource(File file) -> { - try { - 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) { - throw new RuntimeException(e); - } - } - case URLSource(URL url) -> { - try { - yield this.compile(source, forClass, new InputStreamReader(url.openStream())); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - case ReaderSource(Reader reader) -> this.compile(source, forClass, reader); - }; + public ComponentTemplateCompileResult compile(Class forClass, ComponentTemplateSource source) + throws ComponentTemplateCompileErrorException { + try (final Reader reader = ComponentTemplateSource.toReader(source)) { + return this.compile(source, forClass, reader); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ComponentTemplate compileAndGet(Class forClass, ComponentTemplateSource source) + throws ComponentTemplateCompileErrorException { + return this.compileAndGet(this.groovyClassLoader, forClass, source); } } diff --git a/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java index ebc32aa..e4ae34b 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java +++ b/view-components/src/main/java/groowt/view/component/compiler/CachingComponentTemplateCompiler.java @@ -1,8 +1,11 @@ package groowt.view.component.compiler; +import groovy.lang.GroovyClassLoader; import groowt.view.component.ComponentTemplate; import groowt.view.component.ViewComponent; import groowt.view.component.factory.ComponentTemplateSource; +import org.codehaus.groovy.tools.GroovyClass; +import org.jetbrains.annotations.Nullable; import java.io.Reader; import java.util.HashMap; @@ -10,21 +13,92 @@ import java.util.Map; public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler { - private final Map, ComponentTemplate> cache = new HashMap<>(); + private record CachedTemplate( + ComponentTemplateCompileResult compileResult, + @Nullable ComponentTemplate template + ) {} - @Override - protected final ComponentTemplate compile( - ComponentTemplateSource source, - Class forClass, - Reader sourceReader - ) { - return this.cache.computeIfAbsent(forClass, ignored -> this.doCompile(source, forClass, sourceReader)); + private final Map, CachedTemplate> cache = new HashMap<>(); + + public CachingComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) { + super(groovyClassLoader); } - 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 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 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, Class forClass, - Reader sourceReader - ); + Reader reader + ) throws ComponentTemplateCompileErrorException; } diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java similarity index 69% rename from view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java rename to view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java index 56a3c7e..70e5ca0 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileException.java +++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompileErrorException.java @@ -5,12 +5,12 @@ import groowt.view.component.ViewComponent; /** * 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 forClass; private final Object templateSource; - public ComponentTemplateCompileException( + public ComponentTemplateCompileErrorException( String message, Class forClass, Object templateSource @@ -20,7 +20,7 @@ public class ComponentTemplateCompileException extends RuntimeException { this.templateSource = templateSource; } - public ComponentTemplateCompileException( + public ComponentTemplateCompileErrorException( String message, Throwable cause, Class forClass, @@ -31,7 +31,7 @@ public class ComponentTemplateCompileException extends RuntimeException { this.templateSource = templateSource; } - public ComponentTemplateCompileException( + public ComponentTemplateCompileErrorException( Throwable cause, Class forClass, Object templateSource @@ -41,6 +41,15 @@ public class ComponentTemplateCompileException extends RuntimeException { this.templateSource = templateSource; } + public ComponentTemplateCompileErrorException( + Class forClass, + Object templateSource + ) { + super("Compile error in " + templateSource + " for " + forClass.getName()); + this.forClass = forClass; + this.templateSource = templateSource; + } + public Class getForClass() { return this.forClass; } diff --git a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java index 23f0599..842bc62 100644 --- a/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java +++ b/view-components/src/main/java/groowt/view/component/compiler/ComponentTemplateCompiler.java @@ -1,9 +1,34 @@ package groowt.view.component.compiler; +import groovy.lang.GroovyClassLoader; import groowt.view.component.ComponentTemplate; import groowt.view.component.ViewComponent; import groowt.view.component.factory.ComponentTemplateSource; +import org.codehaus.groovy.tools.GroovyClass; + +import java.io.IOException; +import java.util.List; public interface ComponentTemplateCompiler { - ComponentTemplate compile(Class forClass, ComponentTemplateSource source); + + record ComponentTemplateCompileResult(GroovyClass templateClass, List otherClasses) {} + + ComponentTemplateCompileResult compile(Class forClass, ComponentTemplateSource source) + throws ComponentTemplateCompileErrorException; + + ComponentTemplate compileAndGet( + GroovyClassLoader groovyClassLoader, + Class forClass, + ComponentTemplateSource source + ) throws ComponentTemplateCompileErrorException; + + default ComponentTemplate compileAndGet(Class 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); + } + } + } diff --git a/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java b/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java index 9ccd803..d670dd6 100644 --- a/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java +++ b/view-components/src/main/java/groowt/view/component/context/DefaultComponentContext.java @@ -54,7 +54,7 @@ public class DefaultComponentContext implements ComponentContext { final var missingStack = new LinkedList<>(this.scopeStack); NoFactoryMissingException first = null; while (!missingStack.isEmpty()) { - final ComponentScope scope = getStack.pop(); + final ComponentScope scope = missingStack.pop(); try { return new DefaultResolved(component, scope.factoryMissing(component)); } catch (NoFactoryMissingException e) { diff --git a/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java b/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java index 8f86323..19ff547 100644 --- a/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java +++ b/view-components/src/main/java/groowt/view/component/factory/ComponentTemplateSource.java @@ -1,8 +1,6 @@ package groowt.view.component.factory; -import java.io.File; -import java.io.InputStream; -import java.io.Reader; +import java.io.*; import java.net.URI; import java.net.URL; @@ -40,6 +38,35 @@ public sealed interface ComponentTemplateSource { return of(ComponentTemplateSource.class.getClassLoader().getResource(resourceName)); } + static Reader toReader(ComponentTemplateSource source) { + return switch (source) { + case FileSource(File file) -> { + try { + yield new FileReader(file); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + case StringSource(String rawSource) -> new StringReader(rawSource); + case InputStreamSource(InputStream inputStream) -> new InputStreamReader(inputStream); + case URISource(URI uri) -> { + try { + yield new InputStreamReader(uri.toURL().openStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + case URLSource(URL url) -> { + try { + yield new InputStreamReader(url.openStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + case ReaderSource(Reader reader) -> reader; + }; + } + record StringSource(String template) implements ComponentTemplateSource {} record FileSource(File templateFile) implements ComponentTemplateSource {} diff --git a/web-views/sketching/preambleHelloTarget.wvc b/web-views/sketching/preambleHelloTarget.wvc index 0d0cc38..8a88719 100644 --- a/web-views/sketching/preambleHelloTarget.wvc +++ b/web-views/sketching/preambleHelloTarget.wvc @@ -6,5 +6,5 @@ void consume(out) { @groovy.transform.Field String greeting = 'Hello' --- -$greeting, ${consume(out)}! +$greeting, ${consume(it)}! What a nice day. diff --git a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy index 9a33c71..7f571b2 100644 --- a/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy +++ b/web-views/src/main/groovy/groowt/view/web/DefaultWebViewComponent.groovy @@ -2,28 +2,60 @@ package groowt.view.web import groowt.view.component.AbstractViewComponent import groowt.view.component.ComponentTemplate +import groowt.view.component.compiler.ComponentTemplateCompiler import groowt.view.component.factory.ComponentTemplateSource +import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler +import groowt.view.web.compiler.WebViewComponentTemplateCompiler +import org.codehaus.groovy.control.CompilerConfiguration -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 compilerFunction = { Class givenSelfClass -> + new DefaultWebViewComponentTemplateCompiler( + groovyClassLoader, + CompilerConfiguration.DEFAULT, + givenSelfClass.packageName + ) + } + + protected DefaultWebViewComponent() {} + + protected DefaultWebViewComponent(ComponentTemplate template) { super(template) } - DefaultWebViewComponent(Class templateType) { + protected DefaultWebViewComponent(Class templateType) { super(templateType) } - DefaultWebViewComponent(ComponentTemplateSource source) { - super(source) + protected DefaultWebViewComponent(ComponentTemplateSource source) { + super(source, compilerFunction) } - DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) { + protected DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) { super(source, compiler) } + /** + * A convenience constructor which creates a {@link ComponentTemplateSource} + * from the given {@code source} parameter and passes it to super. See + * {@link ComponentTemplateSource} for possible types. + * + * @param source the object passed to {@link ComponentTemplateSource#of} + * @param compiler the compiler to use + * + * @see ComponentTemplateSource + */ + @SuppressWarnings('GroovyAssignabilityCheck') + protected DefaultWebViewComponent(Object source, WebViewComponentTemplateCompiler compiler) { + super(ComponentTemplateSource.of(source), compiler) + } + /** * A convenience constructor which creates a {@link ComponentTemplateSource} * from the given {@code source} parameter and passes it to super. See @@ -34,8 +66,8 @@ class DefaultWebViewComponent extends AbstractWebViewComponent { * @see ComponentTemplateSource */ @SuppressWarnings('GroovyAssignabilityCheck') - DefaultWebViewComponent(Object source) { - super(ComponentTemplateSource.of(source)) + protected DefaultWebViewComponent(Object source) { + super(ComponentTemplateSource.of(source), compilerFunction) } @Override diff --git a/web-views/src/main/java/groowt/view/web/AbstractWebViewComponent.java b/web-views/src/main/java/groowt/view/web/AbstractWebViewComponent.java index 07d76fe..7b1d43f 100644 --- a/web-views/src/main/java/groowt/view/web/AbstractWebViewComponent.java +++ b/web-views/src/main/java/groowt/view/web/AbstractWebViewComponent.java @@ -3,15 +3,17 @@ package groowt.view.web; import groovy.lang.Closure; import groowt.view.component.AbstractViewComponent; import groowt.view.component.ComponentTemplate; +import groowt.view.component.compiler.ComponentTemplateCompiler; import groowt.view.component.factory.ComponentTemplateSource; +import groowt.view.web.compiler.WebViewComponentTemplateCompiler; import groowt.view.web.runtime.DefaultWebViewComponentWriter; import groowt.view.web.runtime.WebViewComponentWriter; -import org.codehaus.groovy.control.CompilerConfiguration; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent { @@ -27,17 +29,18 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp super(templateClass); } - protected AbstractWebViewComponent(ComponentTemplateSource source) { - super(source, packageName -> new DefaultWebViewComponentTemplateCompiler( - CompilerConfiguration.DEFAULT, - packageName - )); - } - protected AbstractWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) { super(source, compiler); } + @SuppressWarnings("unchecked") + protected AbstractWebViewComponent( + ComponentTemplateSource source, + Function, ? extends ComponentTemplateCompiler> compilerFunction + ) { + super(source, selfClass -> compilerFunction.apply((Class) selfClass)); + } + @Override public List getChildRenderers() { if (this.childRenderers == null) { diff --git a/web-views/src/main/java/groowt/view/web/DefaultWebViewComponentTemplateCompiler.java b/web-views/src/main/java/groowt/view/web/DefaultWebViewComponentTemplateCompiler.java deleted file mode 100644 index 82cdaf4..0000000 --- a/web-views/src/main/java/groowt/view/web/DefaultWebViewComponentTemplateCompiler.java +++ /dev/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 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 forClass, - Reader reader, - @Nullable URI uri - ) { - final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader); - - final List 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); - } - -} diff --git a/web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompiler.java b/web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompiler.java deleted file mode 100644 index fca82a7..0000000 --- a/web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompiler.java +++ /dev/null @@ -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); -} diff --git a/web-views/src/main/java/groowt/view/web/analysis/MismatchedComponentTypeAnalysis.kt b/web-views/src/main/java/groowt/view/web/analysis/MismatchedComponentTypeAnalysis.kt index 76ed74e..e2fe8e9 100644 --- a/web-views/src/main/java/groowt/view/web/analysis/MismatchedComponentTypeAnalysis.kt +++ b/web-views/src/main/java/groowt/view/web/analysis/MismatchedComponentTypeAnalysis.kt @@ -17,8 +17,8 @@ private fun getErrorMessage( openType: ComponentTypeContext, closingType: ComponentTypeContext ) = "The component's opening and closing tags' types must match exactly. " + - "Found ${openType.text} at [${SourcePosition.formatStartOfToken(openType.start)}] " + - "and ${closingType.text} at [${SourcePosition.formatStartOfToken(closingType.start)}]." + "Found '${openType.text}' at ${SourcePosition.formatStartOfTokenLong(openType.start)} " + + "and '${closingType.text}' at ${SourcePosition.formatStartOfTokenLong(closingType.start)}." private fun test( openIdentifiers: List, @@ -52,7 +52,7 @@ private fun doCheck(tree: ParseTree, destination: MutableList { val result: MutableList = ArrayList() diff --git a/web-views/src/main/java/groowt/view/web/antlr/AntlrUtil.java b/web-views/src/main/java/groowt/view/web/antlr/AntlrUtil.java index 26d9fb8..7351654 100644 --- a/web-views/src/main/java/groowt/view/web/antlr/AntlrUtil.java +++ b/web-views/src/main/java/groowt/view/web/antlr/AntlrUtil.java @@ -5,7 +5,6 @@ import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.Tree; import java.util.ArrayList; -import java.util.Collection; import java.util.List; public final class AntlrUtil { @@ -36,8 +35,8 @@ public final class AntlrUtil { return this.nodesWithRecognitionException.size() + this.errorNodes.size(); } - public Collection getAll() { - final Collection all = new ArrayList<>(this.nodesWithRecognitionException); + public List getAll() { + final List all = new ArrayList<>(this.nodesWithRecognitionException); all.addAll(this.errorNodes); return all; } diff --git a/web-views/src/main/java/groowt/view/web/compiler/DefaultWebViewComponentTemplateCompiler.java b/web-views/src/main/java/groowt/view/web/compiler/DefaultWebViewComponentTemplateCompiler.java new file mode 100644 index 0000000..15d5a1a --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/compiler/DefaultWebViewComponentTemplateCompiler.java @@ -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 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 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 forClass, + Reader reader + ) { + return new WebViewComponentTemplateCompileException( + "Compile error at " + SourcePosition.fromStartOfToken(parserRuleContext.getStart()).toStringLong() + + ".", + forClass, + reader, + parserRuleContext + ); + } + + protected WebViewComponentTemplateCompileException mapToErrorException( + Tree tree, + Class 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 forClass, + Reader reader + ) { + return new WebViewComponentTemplateCompileException( + error.getMessage(), + forClass, + reader, + error.getComponent() + ); + } + + protected ComponentTemplateCompileResult doCompile( + Class 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 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 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 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); + } + +} diff --git a/web-views/src/main/java/groowt/view/web/compiler/MultipleWebViewComponentCompileErrorsException.java b/web-views/src/main/java/groowt/view/web/compiler/MultipleWebViewComponentCompileErrorsException.java new file mode 100644 index 0000000..186ffdd --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/compiler/MultipleWebViewComponentCompileErrorsException.java @@ -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 errors = new ArrayList<>(); + + public MultipleWebViewComponentCompileErrorsException( + String message, + List errors, + Class forClass, + Object templateSource + ) { + super(message, forClass, templateSource); + this.errors.addAll(errors); + } + + public MultipleWebViewComponentCompileErrorsException( + List errors, + Class forClass, + Object templateSource + ) { + super(forClass, templateSource); + this.errors.addAll(errors); + } + + public List getErrors() { + return this.errors; + } + +} diff --git a/web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompileException.java b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java similarity index 66% rename from web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompileException.java rename to web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java index 8161b20..02ad2d2 100644 --- a/web-views/src/main/java/groowt/view/web/WebViewComponentTemplateCompileException.java +++ b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompileException.java @@ -1,19 +1,19 @@ -package groowt.view.web; +package groowt.view.web.compiler; 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.util.SourcePosition; -public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileException { +public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileErrorException { - private final Node node; + private final Object node; public WebViewComponentTemplateCompileException( String message, Class forClass, Object templateSource, - Node node + Object node ) { super(message, forClass, templateSource); this.node = node; @@ -24,7 +24,7 @@ public class WebViewComponentTemplateCompileException extends ComponentTemplateC Throwable cause, Class forClass, Object templateSource, - Node node + Object node ) { super(message, cause, forClass, templateSource); this.node = node; @@ -34,20 +34,24 @@ public class WebViewComponentTemplateCompileException extends ComponentTemplateC Throwable cause, Class forClass, Object templateSource, - Node node + Object node ) { super(cause, forClass, templateSource); this.node = node; } - public Node getNode() { + public Object getNode() { return this.node; } @Override public String getMessage() { - final SourcePosition start = this.node.getTokenRange().getStartPosition(); - return "Line " + start.line() + ", column " + start.column() + ": " + super.getMessage(); + if (this.node instanceof Node asNode) { + final SourcePosition start = asNode.getTokenRange().getStartPosition(); + return "At " + start.toStringLong() + ": " + super.getMessage(); + } else { + return super.getMessage(); + } } } diff --git a/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompiler.java b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompiler.java new file mode 100644 index 0000000..3f713cf --- /dev/null +++ b/web-views/src/main/java/groowt/view/web/compiler/WebViewComponentTemplateCompiler.java @@ -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; +} diff --git a/web-views/src/main/java/groowt/view/web/util/SourcePosition.java b/web-views/src/main/java/groowt/view/web/util/SourcePosition.java index 3312f83..fd3a159 100644 --- a/web-views/src/main/java/groowt/view/web/util/SourcePosition.java +++ b/web-views/src/main/java/groowt/view/web/util/SourcePosition.java @@ -6,10 +6,14 @@ public record SourcePosition(int line, int column) { 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(); } + public static String formatStartOfTokenLong(Token token) { + return fromStartOfToken(token).toStringLong(); + } + public static SourcePosition fromStartOfToken(Token token) { return new SourcePosition(token.getLine(), token.getCharPositionInLine() + 1); } @@ -46,4 +50,8 @@ public record SourcePosition(int line, int column) { return this.line + "," + this.column; } + public String toStringLong() { + return "line " + this.line + ", column " + this.column; + } + } diff --git a/web-views/src/test/groovy/groowt/view/web/DefaultWebViewComponentTests.groovy b/web-views/src/test/groovy/groowt/view/web/DefaultWebViewComponentTests.groovy index 30b82bd..19eb6f6 100644 --- a/web-views/src/test/groovy/groowt/view/web/DefaultWebViewComponentTests.groovy +++ b/web-views/src/test/groovy/groowt/view/web/DefaultWebViewComponentTests.groovy @@ -1,12 +1,13 @@ package groowt.view.web import groowt.view.component.factory.ComponentFactoryBase +import groowt.view.web.lib.AbstractWebViewComponentTests import groowt.view.web.lib.WithContext import org.junit.jupiter.api.Test import static org.junit.jupiter.api.Assertions.assertEquals -class DefaultWebViewComponentTests implements WithContext { +class DefaultWebViewComponentTests extends AbstractWebViewComponentTests { private static final class Greeter extends DefaultWebViewComponent { @@ -41,8 +42,7 @@ class DefaultWebViewComponentTests implements WithContext { @Test void withPreambleImport() { - def c = new DefaultWebViewComponent( - ''' + this.doTest(''' --- import groovy.transform.Field @@ -50,10 +50,7 @@ class DefaultWebViewComponentTests implements WithContext { String greeting = 'Hello, World!' --- $greeting - '''.stripIndent().trim() - ) - c.context = this.context() - assertEquals("Hello, World!", c.render()) + '''.stripIndent().trim(), "Hello, World!") } @Test @@ -62,9 +59,7 @@ class DefaultWebViewComponentTests implements WithContext { this.configureContext(it) currentScope.add('Greeter', new GreeterFactory()) } - def c = new DefaultWebViewComponent('') - c.context = context - assertEquals('Hello, World!', c.render()) + this.doTest('', 'Hello, World!', context) } @Test @@ -74,9 +69,7 @@ class DefaultWebViewComponentTests implements WithContext { currentScope.add('UsingGreeter') { new UsingGreeter() } currentScope.add('Greeter', new GreeterFactory()) } - def c = new DefaultWebViewComponent('') - c.context = context - assertEquals('Hello, World!', c.render()) + this.doTest('', 'Hello, World!', context) } } diff --git a/web-views/src/testFixtures/groovy/groowt/view/web/lib/AbstractWebViewComponentTests.groovy b/web-views/src/testFixtures/groovy/groowt/view/web/lib/AbstractWebViewComponentTests.groovy index df4007d..ea98ec0 100644 --- a/web-views/src/testFixtures/groovy/groowt/view/web/lib/AbstractWebViewComponentTests.groovy +++ b/web-views/src/testFixtures/groovy/groowt/view/web/lib/AbstractWebViewComponentTests.groovy @@ -2,8 +2,9 @@ package groowt.view.web.lib import groowt.view.component.context.ComponentContext -import groowt.view.web.DefaultWebViewComponentTemplateCompiler -import groowt.view.web.WebViewComponentTemplateCompiler +import groowt.view.component.factory.ComponentTemplateSource +import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler +import groowt.view.web.compiler.WebViewComponentTemplateCompiler import groowt.view.web.runtime.DefaultWebViewComponentWriter import org.codehaus.groovy.control.CompilerConfiguration @@ -13,13 +14,14 @@ abstract class AbstractWebViewComponentTests implements WithContext { protected WebViewComponentTemplateCompiler compiler() { new DefaultWebViewComponentTemplateCompiler( + new GroovyClassLoader(this.class.classLoader), CompilerConfiguration.DEFAULT, this.class.packageName ) } - protected void doTest(Reader source, String expected, ComponentContext context) { - def template = this.compiler().compileAnonymous(source) + protected void doTest(Reader sourceReader, String expected, ComponentContext context) { + def template = this.compiler().compileAndGetAnonymous(ComponentTemplateSource.of(sourceReader)) def renderer = template.getRenderer() def sw = new StringWriter() def out = new DefaultWebViewComponentWriter(sw) 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 f4d1d3e..f19cd4d 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,7 +1,10 @@ package groowt.view.web.tools 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.Command import picocli.CommandLine.Option @@ -26,13 +29,20 @@ class RunTemplate implements Callable { @Override 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() context.pushDefaultScope() - component.context = context - println component.render() + def componentWriter = new DefaultWebViewComponentWriter(new OutputStreamWriter(System.out)) + + template.renderer.call(context, componentWriter) return 0 }