Lots of work on how components are created, as well as general transpilation cleanup.
This commit is contained in:
parent
0526b3ef6e
commit
8953c57681
@ -1,63 +1,36 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import org.codehaus.groovy.tools.GroovyClass;
|
import groowt.view.component.compiler.util.GroovyClassWriter;
|
||||||
|
import groowt.view.component.compiler.util.SimpleGroovyClassWriter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.nio.file.StandardOpenOption.CREATE_NEW;
|
|
||||||
import static java.nio.file.StandardOpenOption.WRITE;
|
|
||||||
|
|
||||||
public final class SimpleComponentTemplateClassFactory implements ComponentTemplateClassFactory {
|
public final class SimpleComponentTemplateClassFactory implements ComponentTemplateClassFactory {
|
||||||
|
|
||||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
|
||||||
|
|
||||||
private static String[] classNameToPackageDirParts(String fullClassName) {
|
|
||||||
final String[] allParts = fullClassName.split("\\.");
|
|
||||||
if (allParts.length == 0) {
|
|
||||||
throw new RuntimeException("Did not expect allParts.length to be zero.");
|
|
||||||
} else if (allParts.length == 1) {
|
|
||||||
return EMPTY_STRING_ARRAY;
|
|
||||||
} else {
|
|
||||||
final var result = new String[allParts.length - 1];
|
|
||||||
System.arraycopy(allParts, 0, result, 0, allParts.length - 1);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path resolvePackageDir(Path rootDir, String[] packageDirParts) {
|
|
||||||
return Path.of(rootDir.toString(), packageDirParts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String isolateClassName(String fullClassName) {
|
|
||||||
final String[] parts = fullClassName.split("\\.");
|
|
||||||
if (parts.length == 0) {
|
|
||||||
throw new RuntimeException("Did not expect parts.length to be zero");
|
|
||||||
}
|
|
||||||
return parts[parts.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, Class<? extends ComponentTemplate>> cache = new HashMap<>();
|
private final Map<String, Class<? extends ComponentTemplate>> cache = new HashMap<>();
|
||||||
private final ClassLoader classLoader;
|
private final ClassLoader classLoader;
|
||||||
private final Path tempClassesDir;
|
private final File tempClassesDir;
|
||||||
|
private final GroovyClassWriter groovyClassWriter;
|
||||||
|
|
||||||
public SimpleComponentTemplateClassFactory() {
|
public SimpleComponentTemplateClassFactory() {
|
||||||
|
this.groovyClassWriter = new SimpleGroovyClassWriter();
|
||||||
try {
|
try {
|
||||||
this.tempClassesDir = Files.createTempDirectory("view-component-classes-");
|
this.tempClassesDir = Files.createTempDirectory("view-component-classes-").toFile();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.classLoader = new URLClassLoader(
|
this.classLoader = new URLClassLoader(
|
||||||
"SimpleComponentTemplateClassFactoryClassLoader",
|
"SimpleComponentTemplateClassFactoryClassLoader",
|
||||||
new URL[] { this.tempClassesDir.toUri().toURL() },
|
new URL[] { this.tempClassesDir.toURI().toURL() },
|
||||||
this.getClass().getClassLoader()
|
this.getClass().getClassLoader()
|
||||||
);
|
);
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
@ -65,23 +38,6 @@ public final class SimpleComponentTemplateClassFactory implements ComponentTempl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeClassToDisk(GroovyClass groovyClass) {
|
|
||||||
final var className = groovyClass.getName();
|
|
||||||
final var packageDirParts = classNameToPackageDirParts(className);
|
|
||||||
final var packageDir = resolvePackageDir(this.tempClassesDir, packageDirParts);
|
|
||||||
try {
|
|
||||||
Files.createDirectories(packageDir);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
final var classFile = Path.of(packageDir.toString(), isolateClassName(className) + ".class");
|
|
||||||
try {
|
|
||||||
Files.write(classFile, groovyClass.getBytes(), CREATE_NEW, WRITE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) {
|
public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) {
|
||||||
final String templateClassName = compileResult.getTemplateClass().getName();
|
final String templateClassName = compileResult.getTemplateClass().getName();
|
||||||
@ -89,11 +45,13 @@ public final class SimpleComponentTemplateClassFactory implements ComponentTempl
|
|||||||
return this.cache.get(templateClassName);
|
return this.cache.get(templateClassName);
|
||||||
} else {
|
} else {
|
||||||
// write classes to disk
|
// write classes to disk
|
||||||
this.writeClassToDisk(compileResult.getTemplateClass());
|
this.groovyClassWriter.writeTo(this.tempClassesDir, compileResult.getTemplateClass());
|
||||||
compileResult.getOtherClasses().forEach(this::writeClassToDisk);
|
compileResult.getOtherClasses().forEach(groovyClass -> this.groovyClassWriter.writeTo(
|
||||||
|
this.tempClassesDir, groovyClass
|
||||||
|
));
|
||||||
// load the template class
|
// load the template class
|
||||||
try {
|
try {
|
||||||
//noinspection unchecked
|
@SuppressWarnings("unchecked")
|
||||||
final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass(
|
final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass(
|
||||||
templateClassName
|
templateClassName
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package groowt.view.component.compiler.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ClassNameUtil {
|
||||||
|
|
||||||
|
public static List<String> classNameToPackageDirParts(String fullClassName) {
|
||||||
|
final String[] allParts = fullClassName.split("\\.");
|
||||||
|
if (allParts.length == 0) {
|
||||||
|
throw new RuntimeException("Did not expect allParts.length to be zero.");
|
||||||
|
} else if (allParts.length == 1) {
|
||||||
|
return List.of();
|
||||||
|
} else {
|
||||||
|
return Arrays.asList(allParts).subList(0, allParts.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File resolvePackageDir(File rootDir, List<String> packageDirParts) {
|
||||||
|
return new File(rootDir, String.join(File.separator, packageDirParts));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String isolateClassName(String fullClassName) {
|
||||||
|
final String[] parts = fullClassName.split("\\.");
|
||||||
|
if (parts.length == 0) {
|
||||||
|
throw new RuntimeException("Did not expect parts.length to be zero");
|
||||||
|
}
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassNameUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package groowt.view.component.compiler.util;
|
||||||
|
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public interface GroovyClassWriter {
|
||||||
|
|
||||||
|
void writeTo(File base, GroovyClass groovyClass);
|
||||||
|
|
||||||
|
default void writeTo(Path base, GroovyClass groovyClass) {
|
||||||
|
this.writeTo(base.toFile(), groovyClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package groowt.view.component.compiler.util;
|
||||||
|
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static groowt.view.component.compiler.util.ClassNameUtil.*;
|
||||||
|
|
||||||
|
public final class SimpleGroovyClassWriter implements GroovyClassWriter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(File base, GroovyClass groovyClass) {
|
||||||
|
final String className = groovyClass.getName();
|
||||||
|
final List<String> packageDirParts = classNameToPackageDirParts(className);
|
||||||
|
final File packageDir = resolvePackageDir(base, packageDirParts);
|
||||||
|
if (!packageDir.exists() && !packageDir.mkdirs()) {
|
||||||
|
throw new RuntimeException(new IOException("Could not make package dir(s) at " + packageDir));
|
||||||
|
}
|
||||||
|
final var classFile = new File(packageDir, isolateClassName(className) + ".class");
|
||||||
|
try (final var fos = new FileOutputStream(classFile)) {
|
||||||
|
fos.write(groovyClass.getBytes());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package groowt.view.component.context;
|
package groowt.view.component.context;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.runtime.RenderContext;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -8,13 +9,22 @@ import java.util.function.Predicate;
|
|||||||
|
|
||||||
public interface ComponentContext {
|
public interface ComponentContext {
|
||||||
|
|
||||||
|
RenderContext getRenderContext();
|
||||||
|
|
||||||
List<ComponentScope> getScopeStack();
|
List<ComponentScope> getScopeStack();
|
||||||
|
|
||||||
void pushScope(ComponentScope scope);
|
void pushScope(ComponentScope scope);
|
||||||
|
|
||||||
void pushDefaultScope();
|
void pushDefaultScope();
|
||||||
|
|
||||||
void popScope();
|
void popScope();
|
||||||
|
|
||||||
ComponentScope getRootScope();
|
ComponentScope getRootScope();
|
||||||
|
|
||||||
|
default <S extends ComponentScope> S getRootScope(Class<? extends S> scopeClass) {
|
||||||
|
return scopeClass.cast(this.getRootScope());
|
||||||
|
}
|
||||||
|
|
||||||
default ComponentScope getCurrentScope() {
|
default ComponentScope getCurrentScope() {
|
||||||
final List<ComponentScope> scopeStack = this.getScopeStack();
|
final List<ComponentScope> scopeStack = this.getScopeStack();
|
||||||
if (scopeStack.isEmpty()) {
|
if (scopeStack.isEmpty()) {
|
||||||
@ -23,8 +33,15 @@ public interface ComponentContext {
|
|||||||
return scopeStack.getFirst();
|
return scopeStack.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default <S extends ComponentScope> S getCurrentScope(Class<? extends S> scopeClass) {
|
||||||
|
return scopeClass.cast(this.getCurrentScope());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable ViewComponent getParent();
|
@Nullable ViewComponent getParent();
|
||||||
@Nullable <T extends ViewComponent> T getParent(Class<T> parentClass);
|
|
||||||
|
default <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
|
||||||
|
return parentClass.cast(this.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching);
|
@Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching);
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
private final LinkedList<ComponentScope> scopeStack = new LinkedList<>();
|
private final LinkedList<ComponentScope> scopeStack = new LinkedList<>();
|
||||||
private RenderContext renderContext;
|
private RenderContext renderContext;
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@Override
|
||||||
public RenderContext getRenderContext() {
|
public RenderContext getRenderContext() {
|
||||||
return Objects.requireNonNull(
|
return Objects.requireNonNull(
|
||||||
this.renderContext,
|
this.renderContext,
|
||||||
@ -66,11 +66,6 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ViewComponent> @Nullable T getParent(Class<T> parentClass) {
|
|
||||||
return parentClass.cast(this.getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
|
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
|
||||||
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||||
|
@ -9,13 +9,13 @@ import groowt.view.component.context.ComponentScope.TypeAndFactory;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DefaultRenderContext implements RenderContext {
|
public abstract class AbstractRenderContext implements RenderContext {
|
||||||
|
|
||||||
private final ComponentContext componentContext;
|
private final ComponentContext componentContext;
|
||||||
private final ComponentWriter writer;
|
private final ComponentWriter writer;
|
||||||
private final LinkedList<ViewComponent> componentStack = new LinkedList<>();
|
private final LinkedList<ViewComponent> componentStack = new LinkedList<>();
|
||||||
|
|
||||||
public DefaultRenderContext(ComponentContext componentContext, ComponentWriter writer) {
|
public AbstractRenderContext(ComponentContext componentContext, ComponentWriter writer) {
|
||||||
this.componentContext = componentContext;
|
this.componentContext = componentContext;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
}
|
}
|
||||||
@ -80,39 +80,6 @@ public class DefaultRenderContext implements RenderContext {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewComponent create(Resolved<?> resolved, Object... args) {
|
|
||||||
final ViewComponent created;
|
|
||||||
if (resolved instanceof ResolvedStringType<?> resolvedStringType) {
|
|
||||||
try {
|
|
||||||
created = resolvedStringType.componentFactory().create(
|
|
||||||
resolvedStringType.typeName(),
|
|
||||||
this.getComponentContext(),
|
|
||||||
args
|
|
||||||
);
|
|
||||||
} catch (Exception createException) {
|
|
||||||
throw new ComponentCreateException(resolved, createException);
|
|
||||||
}
|
|
||||||
} else if (resolved instanceof ResolvedClassType<?> resolvedClassType) {
|
|
||||||
try {
|
|
||||||
created = resolvedClassType.componentFactory().create(
|
|
||||||
resolvedClassType.alias(),
|
|
||||||
resolvedClassType.resolvedType(),
|
|
||||||
this.getComponentContext(),
|
|
||||||
args
|
|
||||||
);
|
|
||||||
} catch (Exception createException) {
|
|
||||||
throw new ComponentCreateException(resolved, createException);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
this.getClass().getName() + " cannot handle Resolved of sub-type " + resolved.getClass().getName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
created.setContext(this.getComponentContext());
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pushComponent(ViewComponent component) {
|
public void pushComponent(ViewComponent component) {
|
||||||
this.componentStack.push(component);
|
this.componentStack.push(component);
|
@ -3,11 +3,9 @@ package groowt.view.component.runtime;
|
|||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.context.ComponentResolveException;
|
import groowt.view.component.context.ComponentResolveException;
|
||||||
import groowt.view.component.factory.ComponentFactory;
|
import groowt.view.component.factory.ComponentFactory;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public interface RenderContext {
|
public interface RenderContext {
|
||||||
|
|
||||||
interface Resolved<T extends ViewComponent> {
|
interface Resolved<T extends ViewComponent> {
|
||||||
@ -31,8 +29,6 @@ public interface RenderContext {
|
|||||||
Resolved<?> resolve(String typeName) throws ComponentResolveException;
|
Resolved<?> resolve(String typeName) throws ComponentResolveException;
|
||||||
<T extends ViewComponent> Resolved<T> resolve(String alias, Class<T> type) throws ComponentResolveException;
|
<T extends ViewComponent> Resolved<T> resolve(String alias, Class<T> type) throws ComponentResolveException;
|
||||||
|
|
||||||
ViewComponent create(Resolved<?> resolved, Object... args);
|
|
||||||
|
|
||||||
void pushComponent(ViewComponent component);
|
void pushComponent(ViewComponent component);
|
||||||
void popComponent(ViewComponent component);
|
void popComponent(ViewComponent component);
|
||||||
|
|
||||||
|
@ -18,19 +18,18 @@ import groowt.view.web.runtime.*
|
|||||||
class MyComponentTemplate implements ComponentTemplate {
|
class MyComponentTemplate implements ComponentTemplate {
|
||||||
|
|
||||||
Closure getRenderer() {
|
Closure getRenderer() {
|
||||||
return { ComponentContext componentContext, ComponentWriter out ->
|
return { WebViewComponentContext componentContext, ComponentWriter writer -> // <1>
|
||||||
// <1>
|
def renderContext = new DefaultWebViewComponentRenderContext(componentContext, writer) // <2>
|
||||||
final RenderContext renderContext = new DefaultWebViewComponentRenderContext(componentContext, out)
|
|
||||||
componentContext.setRenderContext(renderContext)
|
componentContext.setRenderContext(renderContext)
|
||||||
out.setRenderContext(renderContext)
|
writer.setRenderContext(renderContext)
|
||||||
out.setComponentContext(renderContext)
|
writer.setComponentContext(renderContext)
|
||||||
|
|
||||||
out.append 'Hello from simple text!' // <2>
|
writer << 'Hello from simple text!' // <3>
|
||||||
|
writer << someProp // <3>
|
||||||
out.append "Hello from GString!" // <3>
|
writer << someMethod() // <3>
|
||||||
|
|
||||||
// <4>
|
// <4>
|
||||||
ComponentContext.Resolved c0Resolved // <5>
|
def c0Resolved // <5>
|
||||||
try {
|
try {
|
||||||
c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6>
|
c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6>
|
||||||
} catch (ComponentResolveException c0ResolveException) { // <7>
|
} catch (ComponentResolveException c0ResolveException) { // <7>
|
||||||
@ -40,44 +39,38 @@ class MyComponentTemplate implements ComponentTemplate {
|
|||||||
throw c0ResolveException
|
throw c0ResolveException
|
||||||
}
|
}
|
||||||
|
|
||||||
WebViewComponent c0 // <8>
|
def c0 // <8>
|
||||||
try {
|
try {
|
||||||
c0 = renderContext.create( // <9>
|
c0 = renderContext.create( // <9>
|
||||||
c0Resolved,
|
c0Resolved, // <10>
|
||||||
[greeting: 'Hello, World!'],
|
[greeting: 'Hello, World!'], // <11>
|
||||||
"Some constructor arg",
|
["Some constructor arg"] // <12>
|
||||||
WebViewComponentChildCollectorClosure.get(this) { c0cc ->
|
) { c0childList -> // <13>
|
||||||
c0cc.add 'JString child.' // <10>
|
c0childList << 'string child.' // <14>
|
||||||
c0cc.add "GString child"
|
|
||||||
|
|
||||||
ComponentContext.Resolved c1Resolved
|
def c1Resolved
|
||||||
try {
|
try {
|
||||||
c1Resolved = renderContext.resolve('h1') // <11>
|
c1Resolved = renderContext.resolve('h1') // <15>
|
||||||
} catch (ComponentResolveException c1ResolveException) {
|
} catch (ComponentResolveException c1ResolveException) {
|
||||||
c1ResolveException.type = IntrinsicHtml // <12>
|
|
||||||
c1ResolveException.template = this
|
c1ResolveException.template = this
|
||||||
c1ResolveException.line = 1
|
c1ResolveException.line = 1
|
||||||
c1ResolveException.column = 10
|
c1ResolveException.column = 10
|
||||||
throw c1ResolveException
|
throw c1ResolveException
|
||||||
}
|
}
|
||||||
|
|
||||||
WebViewComponent c1
|
def c1
|
||||||
try {
|
try {
|
||||||
c1 = renderContext.create(
|
c1 = renderContext.create(c1_resolved) { c1cc ->
|
||||||
c1_resolved,
|
c1cc << greeting
|
||||||
WebViewComponentChildCollectorClosure.get(this) { c1cc ->
|
|
||||||
c1cc.add "$greeting"
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
} catch (ComponentCreateException c1CreateException) {
|
} catch (ComponentCreateException c1CreateException) {
|
||||||
c1CreateException.template = this
|
c1CreateException.template = this
|
||||||
c1CreateException.line = 1
|
c1CreateException.line = 1
|
||||||
c1CreateException.column = 1
|
c1CreateException.column = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c0cc.add c1
|
c0childList << c1
|
||||||
}
|
}
|
||||||
)
|
|
||||||
} catch (ComponentCreateException c0CreateException) {
|
} catch (ComponentCreateException c0CreateException) {
|
||||||
c0CreateException.template = this
|
c0CreateException.template = this
|
||||||
c0CreateException.line = 1
|
c0CreateException.line = 1
|
||||||
@ -85,38 +78,27 @@ class MyComponentTemplate implements ComponentTemplate {
|
|||||||
throw c0CreateException
|
throw c0CreateException
|
||||||
}
|
}
|
||||||
|
|
||||||
// append it
|
writer << c0
|
||||||
out.append c0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Initialize the contexts.
|
<1> The render `Closure` receives a `ComponentContext` and a `ComponentWriter`.
|
||||||
<2> Appending a plain old java string (jstring) to `out`.
|
<2> A `RenderContext` is created which is then initialized with the values that the render `Closure` received.
|
||||||
<3> Appending a GString to `out`.
|
<3> Strings and expressions of every type (except components) are simply left-shifted into the `ComponentWriter`.
|
||||||
<4> Now begins a component 'block', where a component is resolved, created, and either rendered or appended
|
<4> Now begins a component 'block', where a component is resolved, created, and eventually left-shifted into either a `ComponentWriter` or a children `List` (see below).
|
||||||
to a child collector (see below).
|
<5> First, we define the `resolved` variable, of the type `ComponentContext.Resolved<WebViewComponent>`.
|
||||||
<5> First, we define the `resolved` variable, of the type `ComponentContext.Resolved`.
|
|
||||||
<6> Resolve it from the context.
|
<6> Resolve it from the context.
|
||||||
<7> If the context cannot resolve the component, it should throw a `ComponentResolveException`. We catch it
|
<7> If the context cannot resolve the component, it should throw a `ComponentResolveException`. We catch it
|
||||||
here in order to set the template (`this`), line, and column information for debugging purposes.
|
here in order to set the template (`this`), line, and column information for debugging purposes.
|
||||||
<8> Now we can start to create the component by first defining a variable for it.
|
<8> Now we can start to create the component by first defining a variable for it.
|
||||||
<9> The create function takes a few things:
|
<9> The create function takes up to four parameters.
|
||||||
. The relevant instance of `ComponentContext.Resolved`.
|
<10> The relevant instance of `ComponentContext.Resolved` (required).
|
||||||
. Any attributes for the component, passed as a `Map`.
|
<11> Attributes from the component, passed as a `Map` (required; if there are no attributes, an empty `Map` is passed).
|
||||||
. Any arguments from the component constructor.
|
<12> Any arguments from the component constructor, passed as a `List` (required; if there are no arguments, an empty `List` is passed).
|
||||||
. A `WebViewComponentChildCollectorClosure`, which is a 'marker' subclass of `Closure`
|
<13> A `Closure` (optional), which receives an argument of `List` and simply collects the children of the component.
|
||||||
(so that constructor args ending with a closure don't get confused), which simply collects
|
<14> For children, we add the value of the child to the children `List`, rather than appending it directly to `out`.
|
||||||
the children of the component.
|
<15> Here, a string type is passed, since all lowercase type names are treated as string types in web view components.
|
||||||
<10> For children, we add the value of the child, rather than appending it directly to `out`.
|
|
||||||
The collector itself will be given the `out` writer from the `context`, which will then
|
|
||||||
supply the parent with the ability to render to out. This way the parent doesn't ever have to worry about `out` itself
|
|
||||||
(though if the parent wants access to `out` (for example, it is using a non-wvc template) it can access the out writer
|
|
||||||
from the context).
|
|
||||||
<11> Here, a string type is passed, since all lowercase type names are treated as string types in web view components.
|
|
||||||
<12> Because this is an intrinsic html element, we set the type here.
|
|
||||||
<13> Finally, our child component is added as a `Closure` which accepts the component back again and appends
|
|
||||||
it to out.
|
|
||||||
|
|
||||||
== Items requiring Groovy ASTNode position adjustment
|
== Items requiring Groovy ASTNode position adjustment
|
||||||
|
|
||||||
|
@ -6,12 +6,12 @@ import groowt.view.component.context.DefaultComponentContext
|
|||||||
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
|
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
|
||||||
|
|
||||||
DefaultWebViewComponentContext() {
|
DefaultWebViewComponentContext() {
|
||||||
this.pushScope(WebViewComponentScope.getDefaultRootScope())
|
this.pushScope(DefaultWebViewComponentScope.getDefaultRootScope())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ComponentScope getNewDefaultScope() {
|
protected ComponentScope getNewDefaultScope() {
|
||||||
new WebViewComponentScope()
|
new DefaultWebViewComponentScope()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.context.DefaultComponentScope
|
||||||
|
import groowt.view.web.lib.Echo
|
||||||
|
import groowt.view.web.lib.IntrinsicHtml
|
||||||
|
import org.codehaus.groovy.runtime.InvokerHelper
|
||||||
|
|
||||||
|
import static groowt.view.web.WebViewComponentFactories.withAttr
|
||||||
|
|
||||||
|
class DefaultWebViewComponentScope extends DefaultComponentScope {
|
||||||
|
|
||||||
|
static DefaultWebViewComponentScope getDefaultRootScope() {
|
||||||
|
new DefaultWebViewComponentScope().tap {
|
||||||
|
addWithAttr(Echo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass) {
|
||||||
|
add(componentClass, withAttr(componentClass) { attr ->
|
||||||
|
InvokerHelper.invokeConstructorOf(componentClass, attr) as T
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TypeAndFactory factoryMissing(String typeName) {
|
||||||
|
IntrinsicHtml.TYPE_AND_FACTORY
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,8 +3,6 @@ package groowt.view.web
|
|||||||
import groovy.transform.stc.ClosureParams
|
import groovy.transform.stc.ClosureParams
|
||||||
import groovy.transform.stc.FromString
|
import groovy.transform.stc.FromString
|
||||||
import groowt.view.component.factory.ComponentFactory
|
import groowt.view.component.factory.ComponentFactory
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentChildCollector
|
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollector
|
|
||||||
|
|
||||||
import static groowt.view.component.factory.ComponentFactories.ofClosureClassType
|
import static groowt.view.component.factory.ComponentFactories.ofClosureClassType
|
||||||
|
|
||||||
@ -15,30 +13,7 @@ final class WebViewComponentFactories {
|
|||||||
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
|
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
|
||||||
Closure<? extends T> closure
|
Closure<? extends T> closure
|
||||||
) {
|
) {
|
||||||
ofClosureClassType(forClass) { Map<String, Object> attr -> closure(attr) }
|
ofClosureClassType(forClass) { Map<String, Object> attr, Object[] ignored -> closure(attr) }
|
||||||
}
|
|
||||||
|
|
||||||
static <T extends WebViewComponent> ComponentFactory<T> withChildren(
|
|
||||||
Class<T> forClass,
|
|
||||||
@ClosureParams(value = FromString, options = 'java.util.List<groowt.view.web.WebViewComponentChild>')
|
|
||||||
Closure<? extends T> closure
|
|
||||||
) {
|
|
||||||
ofClosureClassType(forClass) { WebViewComponentChildCollector childCollector ->
|
|
||||||
closure(childCollector.children)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T extends WebViewComponent> ComponentFactory<T> withAttrAndChildren(
|
|
||||||
Class<T> forClass,
|
|
||||||
@ClosureParams(
|
|
||||||
value = FromString,
|
|
||||||
options = 'java.util.Map<String, Object>, java.util.List<groowt.view.web.WebViewComponentChild>'
|
|
||||||
)
|
|
||||||
Closure<? extends T> closure
|
|
||||||
) {
|
|
||||||
ofClosureClassType(forClass) { Map<String, Object> attr, WebViewComponentChildCollector childCollector ->
|
|
||||||
closure(attr, childCollector.children)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebViewComponentFactories() {}
|
private WebViewComponentFactories() {}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package groowt.view.web
|
|
||||||
|
|
||||||
import groowt.view.component.context.DefaultComponentScope
|
|
||||||
import groowt.view.web.lib.Echo
|
|
||||||
import groowt.view.web.lib.IntrinsicHtml
|
|
||||||
import org.codehaus.groovy.runtime.InvokerHelper
|
|
||||||
|
|
||||||
import static groowt.view.web.WebViewComponentFactories.*
|
|
||||||
|
|
||||||
class WebViewComponentScope extends DefaultComponentScope {
|
|
||||||
|
|
||||||
static WebViewComponentScope getDefaultRootScope() {
|
|
||||||
new WebViewComponentScope().tap {
|
|
||||||
add(Echo, Echo.FACTORY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<T extends WebViewComponent> void addWithAttr(Class<T> componentClass) {
|
|
||||||
add(componentClass, withAttr(componentClass) { attr ->
|
|
||||||
InvokerHelper.invokeConstructorOf(componentClass, attr) as T
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<T extends WebViewComponent> void addWithChildren(Class<T> componentClass) {
|
|
||||||
add(componentClass, withChildren(componentClass) { children ->
|
|
||||||
InvokerHelper.invokeConstructorOf(componentClass, children) as T
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<T extends WebViewComponent> void addWithAttrAndChildren(Class<T> componentClass) {
|
|
||||||
add(componentClass, withAttrAndChildren(componentClass) { attr, children ->
|
|
||||||
InvokerHelper.invokeConstructorOf(componentClass, [attr, children] as Object[]) as T
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
TypeAndFactory factoryMissing(String typeName) {
|
|
||||||
IntrinsicHtml.TYPE_AND_FACTORY
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +1,10 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import groowt.view.View
|
import groowt.view.View
|
||||||
import groowt.view.component.context.ComponentContext
|
import groowt.view.component.runtime.DefaultComponentWriter
|
||||||
import groowt.view.component.factory.ComponentFactory
|
|
||||||
|
|
||||||
class Echo extends DelegatingWebViewComponent {
|
class Echo extends DelegatingWebViewComponent {
|
||||||
|
|
||||||
static final ComponentFactory<Echo> FACTORY = new EchoFactory()
|
|
||||||
|
|
||||||
protected static class EchoFactory implements ComponentFactory<Echo> {
|
|
||||||
|
|
||||||
protected Echo doCreate() {
|
|
||||||
new Echo([:])
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Echo doCreate(Map attr) {
|
|
||||||
new Echo(attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Echo create(String typeName, ComponentContext componentContext, Object... args) {
|
|
||||||
throw new UnsupportedOperationException('Cannot create Echo for string type components')
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Echo create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
|
||||||
this.doCreate(*args)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Map attr
|
Map attr
|
||||||
|
|
||||||
Echo(Map attr) {
|
Echo(Map attr) {
|
||||||
@ -48,8 +23,11 @@ class Echo extends DelegatingWebViewComponent {
|
|||||||
@Override
|
@Override
|
||||||
protected View getDelegate() {
|
protected View getDelegate() {
|
||||||
return {
|
return {
|
||||||
|
def componentWriter = new DefaultComponentWriter(it)
|
||||||
|
componentWriter.setComponentContext(this.context)
|
||||||
|
componentWriter.setRenderContext(this.context.renderContext) // hacky
|
||||||
this.children.each {
|
this.children.each {
|
||||||
it.render(this)
|
componentWriter << it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ final class Fragment extends BaseWebViewComponent {
|
|||||||
@Override
|
@Override
|
||||||
void renderTo(Writer out) throws IOException {
|
void renderTo(Writer out) throws IOException {
|
||||||
this.beforeRender()
|
this.beforeRender()
|
||||||
this.renderChildren()
|
this.renderChildren(out)
|
||||||
this.afterRender()
|
this.afterRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,11 +21,7 @@ class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {
|
|||||||
|
|
||||||
protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> {
|
protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> {
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName) {
|
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, Object[] ignored) {
|
||||||
new IntrinsicHtml([:], typeName, typeName in voidElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr) {
|
|
||||||
new IntrinsicHtml(attr, typeName, typeName in voidElements)
|
new IntrinsicHtml(attr, typeName, typeName in voidElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +60,7 @@ class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {
|
|||||||
this.formatAttr(writer)
|
this.formatAttr(writer)
|
||||||
}
|
}
|
||||||
writer << '>'
|
writer << '>'
|
||||||
if (this.hasChildren()) {
|
this.renderChildren(writer)
|
||||||
this.children.each {
|
|
||||||
it.renderTo(writer, this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!this.isVoidElement) {
|
if (!this.isVoidElement) {
|
||||||
writer << '</'
|
writer << '</'
|
||||||
writer << this.name
|
writer << this.name
|
||||||
|
@ -2,8 +2,8 @@ package groowt.view.web.util
|
|||||||
|
|
||||||
import groovy.transform.stc.ClosureParams
|
import groovy.transform.stc.ClosureParams
|
||||||
import groovy.transform.stc.SimpleType
|
import groovy.transform.stc.SimpleType
|
||||||
|
import groowt.view.web.DefaultWebViewComponentScope
|
||||||
import groowt.view.web.WebViewComponentContext
|
import groowt.view.web.WebViewComponentContext
|
||||||
import groowt.view.web.WebViewComponentScope
|
|
||||||
|
|
||||||
class ContextConfigurator {
|
class ContextConfigurator {
|
||||||
|
|
||||||
@ -14,12 +14,12 @@ class ContextConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void rootScope(
|
void rootScope(
|
||||||
@DelegatesTo(WebViewComponentScope)
|
@DelegatesTo(DefaultWebViewComponentScope)
|
||||||
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentScope')
|
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentScope')
|
||||||
Closure configureRootScope
|
Closure configureRootScope
|
||||||
) {
|
) {
|
||||||
//noinspection GroovyAssignabilityCheck
|
//noinspection GroovyAssignabilityCheck
|
||||||
WebViewComponentScope rootScope = context.rootScope
|
DefaultWebViewComponentScope rootScope = context.rootScope
|
||||||
configureRootScope.delegate = rootScope
|
configureRootScope.delegate = rootScope
|
||||||
configureRootScope(rootScope)
|
configureRootScope(rootScope)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
||||||
|
|
||||||
private List<WebViewComponentChild> childRenderers;
|
private List<Object> children;
|
||||||
|
|
||||||
public AbstractWebViewComponent() {}
|
public AbstractWebViewComponent() {}
|
||||||
|
|
||||||
@ -40,11 +40,11 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WebViewComponentChild> getChildren() {
|
public List<Object> getChildren() {
|
||||||
if (this.childRenderers == null) {
|
if (this.children == null) {
|
||||||
this.childRenderers = new ArrayList<>();
|
this.children = new ArrayList<>();
|
||||||
}
|
}
|
||||||
return this.childRenderers;
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,15 +53,21 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChildren(List<WebViewComponentChild> children) {
|
public void setChildren(List<?> children) {
|
||||||
this.childRenderers = children;
|
if (this.children == null) {
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.children.addAll(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderChildren() {
|
public void renderChildren(Writer to) {
|
||||||
for (final var childRenderer : this.getChildren()) {
|
final ComponentWriter componentWriter = new DefaultComponentWriter(to);
|
||||||
|
componentWriter.setComponentContext(this.getContext());
|
||||||
|
componentWriter.setRenderContext(this.getContext().getRenderContext());
|
||||||
|
for (final var child : this.getChildren()) {
|
||||||
try {
|
try {
|
||||||
childRenderer.render(this);
|
componentWriter.append(child);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ChildRenderException(e);
|
throw new ChildRenderException(e);
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,15 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web;
|
||||||
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
|
import java.io.Writer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface WebViewComponent extends ViewComponent {
|
public interface WebViewComponent extends ViewComponent {
|
||||||
|
|
||||||
List<WebViewComponentChild> getChildren();
|
List<Object> getChildren();
|
||||||
boolean hasChildren();
|
boolean hasChildren();
|
||||||
void setChildren(List<WebViewComponentChild> children);
|
void setChildren(List<?> children);
|
||||||
void renderChildren();
|
void renderChildren(Writer to);
|
||||||
|
|
||||||
default List<String> getChildStrings() {
|
|
||||||
return this.getChildren().stream()
|
|
||||||
.map(WebViewComponentChild::getChild)
|
|
||||||
.filter(obj -> obj instanceof String || obj instanceof GString)
|
|
||||||
.map(obj -> {
|
|
||||||
if (obj instanceof String s) {
|
|
||||||
return s;
|
|
||||||
} else {
|
|
||||||
return ((GString) obj).toString();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<GString> getChildGStrings() {
|
|
||||||
return this.getChildren().stream()
|
|
||||||
.map(WebViewComponentChild::getChild)
|
|
||||||
.filter(GString.class::isInstance)
|
|
||||||
.map(GString.class::cast)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
default List<WebViewComponent> getChildComponents() {
|
|
||||||
return this.getChildren().stream()
|
|
||||||
.map(WebViewComponentChild::getChild)
|
|
||||||
.filter(WebViewComponent.class::isInstance)
|
|
||||||
.map(WebViewComponent.class::cast)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.runtime.ComponentWriter;
|
|
||||||
import groowt.view.component.runtime.DefaultComponentWriter;
|
|
||||||
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
public class WebViewComponentChild {
|
|
||||||
|
|
||||||
private static final class ChildRenderClosure extends Closure<Void> {
|
|
||||||
|
|
||||||
private final ViewComponent parent;
|
|
||||||
private final Object child;
|
|
||||||
private final ComponentWriter writer;
|
|
||||||
|
|
||||||
public ChildRenderClosure(
|
|
||||||
ComponentTemplate template,
|
|
||||||
ViewComponent parent,
|
|
||||||
ComponentWriter writer,
|
|
||||||
Object child
|
|
||||||
) {
|
|
||||||
super(template, template);
|
|
||||||
this.parent = parent;
|
|
||||||
this.child = child;
|
|
||||||
this.writer = writer;
|
|
||||||
this.setDelegate(this.parent);
|
|
||||||
this.setResolveStrategy(Closure.DELEGATE_FIRST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewComponent getParent() {
|
|
||||||
return this.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doCall() {
|
|
||||||
this.writer.append(this.child);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ComponentTemplate template;
|
|
||||||
private final ComponentWriter componentWriter;
|
|
||||||
private final Object child;
|
|
||||||
|
|
||||||
public WebViewComponentChild(ComponentTemplate template, ComponentWriter componentWriter, Object child) {
|
|
||||||
this.template = template;
|
|
||||||
this.componentWriter = componentWriter;
|
|
||||||
this.child = child;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getChild() {
|
|
||||||
return this.child;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void render(ViewComponent parent) {
|
|
||||||
new ChildRenderClosure(this.template, parent, this.componentWriter, this.child).call();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Writer renderTo(Writer out, ViewComponent parent) {
|
|
||||||
final var componentWriter = new DefaultComponentWriter(out);
|
|
||||||
final var childRenderClosure = new ChildRenderClosure(this.template, parent, componentWriter, this.child);
|
|
||||||
childRenderClosure.call();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package groowt.view.web.compiler;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
import groowt.view.component.context.ComponentContext;
|
||||||
import groowt.view.web.WebViewComponentChild;
|
|
||||||
import groowt.view.web.WebViewComponent;
|
import groowt.view.web.WebViewComponent;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WebViewComponentChild> getChildren() {
|
public List<Object> getChildren() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,12 +36,12 @@ public final class AnonymousWebViewComponent implements WebViewComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChildren(List<WebViewComponentChild> children) {
|
public void setChildren(List<?> children) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderChildren() {
|
public void renderChildren(Writer to) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.runtime.ComponentWriter;
|
|
||||||
import groowt.view.web.WebViewComponentChild;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DefaultWebViewComponentChildCollector implements WebViewComponentChildCollector {
|
|
||||||
|
|
||||||
private final ComponentTemplate template;
|
|
||||||
private final ComponentWriter out;
|
|
||||||
private final List<WebViewComponentChild> children = new ArrayList<>();
|
|
||||||
|
|
||||||
public DefaultWebViewComponentChildCollector(ComponentTemplate template, ComponentWriter out) {
|
|
||||||
this.template = template;
|
|
||||||
this.out = out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(String jString) {
|
|
||||||
this.children.add(new WebViewComponentChild(this.template, this.out, jString));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(GString gString) {
|
|
||||||
this.children.add(new WebViewComponentChild(this.template, this.out, gString));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(ViewComponent component) {
|
|
||||||
this.children.add(new WebViewComponentChild(this.template, this.out, component));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WebViewComponentChild> getChildren() {
|
|
||||||
return this.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
import groowt.view.component.runtime.ComponentWriter;
|
|
||||||
import groowt.view.component.runtime.DefaultRenderContext;
|
|
||||||
import groowt.view.web.WebViewComponent;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
|
|
||||||
public class DefaultWebViewComponentRenderContext extends DefaultRenderContext
|
|
||||||
implements WebViewComponentRenderContext {
|
|
||||||
|
|
||||||
DefaultWebViewComponentRenderContext(ComponentContext componentContext, ComponentWriter writer) {
|
|
||||||
super(componentContext, writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewComponent create(Resolved<?> resolved, Object... args) {
|
|
||||||
if (args != null && args.length > 0) {
|
|
||||||
final Object last = args[args.length - 1];
|
|
||||||
if (last instanceof WebViewComponentChildCollectorClosure cl) {
|
|
||||||
final Object[] argsWithoutChildren = new Object[args.length - 1];
|
|
||||||
System.arraycopy(args, 0, argsWithoutChildren, 0, args.length - 1);
|
|
||||||
final WebViewComponent self = (WebViewComponent) super.create(resolved, argsWithoutChildren);
|
|
||||||
final var childCollector = new DefaultWebViewComponentChildCollector(
|
|
||||||
cl.getTemplate(),
|
|
||||||
this.getWriter()
|
|
||||||
);
|
|
||||||
cl.setDelegate(self);
|
|
||||||
cl.setResolveStrategy(Closure.DELEGATE_FIRST);
|
|
||||||
cl.call(childCollector);
|
|
||||||
self.setChildren(childCollector.getChildren());
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.create(resolved, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl) {
|
|
||||||
final var childCollection = new DefaultWebViewComponentChildCollector(cl.getTemplate(), this.getWriter());
|
|
||||||
cl.call(childCollection);
|
|
||||||
fragment.setChildren(childCollection.getChildren());
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,77 @@
|
|||||||
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
import groowt.view.component.runtime.AbstractRenderContext;
|
||||||
|
import groowt.view.component.runtime.ComponentWriter;
|
||||||
|
import groowt.view.web.WebViewComponent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultWebViewRenderContext extends AbstractRenderContext implements WebViewComponentRenderContext {
|
||||||
|
|
||||||
|
DefaultWebViewRenderContext(ComponentContext componentContext, ComponentWriter writer) {
|
||||||
|
super(componentContext, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebViewComponent create(
|
||||||
|
Resolved<? extends WebViewComponent> resolved,
|
||||||
|
Map<String, Object> attr,
|
||||||
|
Object[] constructorArgs
|
||||||
|
) {
|
||||||
|
final WebViewComponent created;
|
||||||
|
if (resolved instanceof ResolvedStringType<? extends WebViewComponent> resolvedStringType) {
|
||||||
|
created = resolvedStringType.componentFactory().create(
|
||||||
|
resolvedStringType.typeName(),
|
||||||
|
this.getComponentContext(),
|
||||||
|
attr,
|
||||||
|
constructorArgs
|
||||||
|
);
|
||||||
|
} else if (resolved instanceof ResolvedClassType<? extends WebViewComponent> resolvedClassType) {
|
||||||
|
created = resolvedClassType.componentFactory().create(
|
||||||
|
resolvedClassType.alias(),
|
||||||
|
resolvedClassType.requestedType(),
|
||||||
|
this.getComponentContext(),
|
||||||
|
attr,
|
||||||
|
constructorArgs
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Cannot create from a Resolved that is not a ResolvedStringType or ResolvedClassType."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
created.setContext(this.getComponentContext());
|
||||||
|
created.setChildren(new ArrayList<>());
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebViewComponent create(
|
||||||
|
Resolved<? extends WebViewComponent> resolved,
|
||||||
|
Map<String, Object> attr,
|
||||||
|
Object[] constructorArgs,
|
||||||
|
Closure<Void> childrenClosure
|
||||||
|
) {
|
||||||
|
final WebViewComponent created = this.create(resolved, attr, constructorArgs);
|
||||||
|
childrenClosure.setDelegate(created);
|
||||||
|
childrenClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||||
|
final List<Object> children = new ArrayList<>();
|
||||||
|
childrenClosure.call(children);
|
||||||
|
created.setChildren(children);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewComponent createFragment(WebViewComponent fragment, Closure<Void> childrenClosure) {
|
||||||
|
final List<Object> children = new ArrayList<>();
|
||||||
|
childrenClosure.call(children);
|
||||||
|
fragment.setChildren(children);
|
||||||
|
fragment.setContext(this.getComponentContext());
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.web.WebViewComponentChild;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface WebViewComponentChildCollector {
|
|
||||||
void add(String jString);
|
|
||||||
void add(GString gString);
|
|
||||||
void add(ViewComponent component);
|
|
||||||
List<WebViewComponentChild> getChildren();
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
|
|
||||||
public class WebViewComponentChildCollectorClosure extends Closure<Object> {
|
|
||||||
|
|
||||||
public static Closure<?> get(ComponentTemplate template, Closure<?> collectorClosure) {
|
|
||||||
return new WebViewComponentChildCollectorClosure(template, collectorClosure);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ComponentTemplate template;
|
|
||||||
private final Closure<?> collectorClosure;
|
|
||||||
|
|
||||||
private WebViewComponentChildCollectorClosure(ComponentTemplate template, Closure<?> collectorClosure) {
|
|
||||||
super(template, template);
|
|
||||||
this.template = template;
|
|
||||||
this.collectorClosure = collectorClosure;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentTemplate getTemplate() {
|
|
||||||
return this.template;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object doCall(WebViewComponentChildCollector childCollector) {
|
|
||||||
return this.collectorClosure.call(childCollector);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +1,30 @@
|
|||||||
package groowt.view.web.runtime;
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.runtime.RenderContext;
|
import groowt.view.component.runtime.RenderContext;
|
||||||
import groowt.view.web.WebViewComponent;
|
import groowt.view.web.WebViewComponent;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface WebViewComponentRenderContext extends RenderContext {
|
public interface WebViewComponentRenderContext extends RenderContext {
|
||||||
|
|
||||||
@ApiStatus.Internal
|
Map<String, Object> EMPTY_ATTR = Map.of();
|
||||||
ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl);
|
Object[] EMPTY_CONSTRUCTOR_ARGS = {};
|
||||||
|
|
||||||
|
WebViewComponent create(
|
||||||
|
Resolved<? extends WebViewComponent> resolved,
|
||||||
|
Map<String, Object> attr,
|
||||||
|
Object[] constructorArgs
|
||||||
|
);
|
||||||
|
|
||||||
|
WebViewComponent create(
|
||||||
|
Resolved<? extends WebViewComponent> resolved,
|
||||||
|
Map<String, Object> attr,
|
||||||
|
Object[] constructorArgs,
|
||||||
|
Closure<Void> childrenClosure
|
||||||
|
);
|
||||||
|
|
||||||
|
ViewComponent createFragment(WebViewComponent fragment, Closure<Void> childrenClosure);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,6 @@ public interface AppendOrAddStatementFactory {
|
|||||||
ADD, APPEND
|
ADD, APPEND
|
||||||
}
|
}
|
||||||
|
|
||||||
Statement addOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
|
|
||||||
Statement appendOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
|
|
||||||
Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide);
|
Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide);
|
||||||
|
|
||||||
default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) {
|
default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
import groovy.lang.Tuple2;
|
import groovy.lang.Tuple2;
|
||||||
import groowt.view.web.ast.NodeUtil;
|
|
||||||
import groowt.view.web.ast.node.BodyChildNode;
|
import groowt.view.web.ast.node.BodyChildNode;
|
||||||
import groowt.view.web.ast.node.ComponentNode;
|
|
||||||
import groowt.view.web.ast.node.GStringBodyTextNode;
|
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import org.codehaus.groovy.ast.expr.*;
|
import org.codehaus.groovy.ast.expr.*;
|
||||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||||
@ -29,8 +26,8 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
|
|||||||
BodyChildNode bodyChildNode,
|
BodyChildNode bodyChildNode,
|
||||||
Expression rightSide,
|
Expression rightSide,
|
||||||
VariableExpression target,
|
VariableExpression target,
|
||||||
String methodName,
|
String methodName // ,
|
||||||
boolean addLineAndColumn
|
// boolean addLineAndColumn
|
||||||
) {
|
) {
|
||||||
final ArgumentListExpression args;
|
final ArgumentListExpression args;
|
||||||
if (rightSide instanceof ArgumentListExpression argumentListExpression) {
|
if (rightSide instanceof ArgumentListExpression argumentListExpression) {
|
||||||
@ -39,33 +36,31 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
|
|||||||
args = new ArgumentListExpression();
|
args = new ArgumentListExpression();
|
||||||
args.addExpression(rightSide);
|
args.addExpression(rightSide);
|
||||||
}
|
}
|
||||||
if (addLineAndColumn &&
|
// if (addLineAndColumn &&
|
||||||
NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
|
// NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
|
||||||
this.addLineAndColumn(bodyChildNode, args);
|
// this.addLineAndColumn(bodyChildNode, args);
|
||||||
}
|
// }
|
||||||
final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args);
|
final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args);
|
||||||
return new ExpressionStatement(outExpression);
|
return new ExpressionStatement(outExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
||||||
public Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
|
||||||
return this.doCreate(
|
return this.doCreate(
|
||||||
bodyChildNode,
|
bodyChildNode,
|
||||||
rightSide,
|
rightSide,
|
||||||
new VariableExpression(state.getCurrentChildCollector()),
|
state.getCurrentChildList(),
|
||||||
TranspilerUtil.ADD,
|
TranspilerUtil.ADD //,
|
||||||
false
|
// false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
||||||
public Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
|
||||||
return this.doCreate(
|
return this.doCreate(
|
||||||
bodyChildNode,
|
bodyChildNode,
|
||||||
rightSide,
|
rightSide,
|
||||||
new VariableExpression(state.getWriter()),
|
state.getWriter(),
|
||||||
TranspilerUtil.APPEND,
|
TranspilerUtil.APPEND //,
|
||||||
true
|
// false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +70,7 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
|
|||||||
TranspilerState state,
|
TranspilerState state,
|
||||||
Function<Action, Expression> getRightSide
|
Function<Action, Expression> getRightSide
|
||||||
) {
|
) {
|
||||||
if (state.hasCurrentChildCollector()) {
|
if (state.hasCurrentChildList()) {
|
||||||
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
|
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
|
||||||
} else {
|
} else {
|
||||||
return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND));
|
return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND));
|
||||||
|
@ -2,11 +2,8 @@ package groowt.view.web.transpile;
|
|||||||
|
|
||||||
import groowt.view.component.context.ComponentResolveException;
|
import groowt.view.component.context.ComponentResolveException;
|
||||||
import groowt.view.component.runtime.ComponentCreateException;
|
import groowt.view.component.runtime.ComponentCreateException;
|
||||||
import groowt.view.component.runtime.RenderContext;
|
|
||||||
import groowt.view.web.WebViewComponentBugError;
|
import groowt.view.web.WebViewComponentBugError;
|
||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollector;
|
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollectorClosure;
|
|
||||||
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
|
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
||||||
@ -25,11 +22,7 @@ import static groowt.view.web.transpile.TranspilerUtil.*;
|
|||||||
|
|
||||||
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||||
|
|
||||||
private static final ClassNode CHILD_COLLECTOR_TYPE = ClassHelper.make(WebViewComponentChildCollector.class);
|
|
||||||
private static final ClassNode FRAGMENT_TYPE = ClassHelper.make(GROOWT_VIEW_WEB + ".lib.Fragment");
|
private static final ClassNode FRAGMENT_TYPE = ClassHelper.make(GROOWT_VIEW_WEB + ".lib.Fragment");
|
||||||
private static final ClassNode RESOLVED_TYPE = ClassHelper.make(RenderContext.Resolved.class);
|
|
||||||
private static final ClassNode CHILD_COLLECTOR_CLOSURE_TYPE =
|
|
||||||
ClassHelper.make(WebViewComponentChildCollectorClosure.class);
|
|
||||||
private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class);
|
private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class);
|
||||||
private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
|
private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
|
||||||
|
|
||||||
@ -71,27 +64,17 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
/* UTIL */
|
/* UTIL */
|
||||||
|
|
||||||
private void addLineAndColumn(Node sourceNode, ArgumentListExpression args) {
|
|
||||||
final var lineAndColumn = lineAndColumn(sourceNode.getTokenRange().getStartPosition());
|
|
||||||
args.addExpression(lineAndColumn.getV1());
|
|
||||||
args.addExpression(lineAndColumn.getV2());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getComponentName(int componentNumber) {
|
protected String getComponentName(int componentNumber) {
|
||||||
return "c" + componentNumber;
|
return "c" + componentNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RESOLVED DECLARATION */
|
/* RESOLVED DECLARATION */
|
||||||
|
|
||||||
// RenderContext.Resolved c0Resolved
|
// def c0Resolved
|
||||||
protected Statement getResolvedDeclaration(TranspilerState state) {
|
protected Statement getResolvedDeclaration(TranspilerState state) {
|
||||||
final ClassNode resolvedType = RESOLVED_TYPE.getPlainNodeReference();
|
|
||||||
resolvedType.setGenericsTypes(
|
|
||||||
new GenericsType[] { new GenericsType(WEB_VIEW_COMPONENT_TYPE) }
|
|
||||||
);
|
|
||||||
final var resolvedVariable = new VariableExpression(
|
final var resolvedVariable = new VariableExpression(
|
||||||
this.getComponentName(state.newComponentNumber()) + "Resolved",
|
this.getComponentName(state.newComponentNumber()) + "Resolved",
|
||||||
resolvedType
|
ClassHelper.dynamicType()
|
||||||
);
|
);
|
||||||
state.pushResolved(resolvedVariable);
|
state.pushResolved(resolvedVariable);
|
||||||
final var declarationExpr = new DeclarationExpression(
|
final var declarationExpr = new DeclarationExpression(
|
||||||
@ -253,10 +236,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
/* TYPED COMPONENT DECLARATION */
|
/* TYPED COMPONENT DECLARATION */
|
||||||
|
|
||||||
// ViewComponent c0
|
// def c0
|
||||||
protected Statement getTypedComponentDeclaration(TranspilerState state) {
|
protected Statement getTypedComponentDeclaration(TranspilerState state) {
|
||||||
final VariableExpression componentVariable = new VariableExpression(
|
final VariableExpression componentVariable = new VariableExpression(
|
||||||
this.getComponentName(state.getCurrentComponentNumber()), WEB_VIEW_COMPONENT_TYPE
|
this.getComponentName(state.getCurrentComponentNumber()), ClassHelper.dynamicType()
|
||||||
);
|
);
|
||||||
state.pushComponent(componentVariable);
|
state.pushComponent(componentVariable);
|
||||||
state.getCurrentScope().putDeclaredVariable(componentVariable);
|
state.getCurrentScope().putDeclaredVariable(componentVariable);
|
||||||
@ -285,7 +268,11 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
// [key: value, ...]
|
// [key: value, ...]
|
||||||
protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) {
|
protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) {
|
||||||
if (attributeNodes.isEmpty()) {
|
if (attributeNodes.isEmpty()) {
|
||||||
throw new WebViewComponentBugError(new IllegalArgumentException("attributeNodes cannot be empty."));
|
throw new WebViewComponentBugError(new IllegalArgumentException(
|
||||||
|
"Did not expect attributeNodes to be empty. " +
|
||||||
|
"If you intend to have it empty, use instead a static reference to " +
|
||||||
|
"WebViewRenderContext.EMPTY_ATTR."
|
||||||
|
));
|
||||||
}
|
}
|
||||||
final var result = new MapExpression();
|
final var result = new MapExpression();
|
||||||
attributeNodes.stream()
|
attributeNodes.stream()
|
||||||
@ -315,91 +302,108 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
/* COMPONENT CHILDREN */
|
/* COMPONENT CHILDREN */
|
||||||
|
|
||||||
// c0cc.add (jString | gString | component)
|
// c0childList << (jString | gString | component)
|
||||||
protected Statement getChildCollectorAdd(Variable childCollector, Expression toAdd) {
|
protected Statement getChildListAdd(Parameter childList, Expression toAdd) {
|
||||||
final VariableExpression childCollectorVariableExpr = new VariableExpression(childCollector);
|
final BinaryExpression leftShiftExpression = new BinaryExpression(
|
||||||
final MethodCallExpression methodCall = new MethodCallExpression(
|
new VariableExpression(childList),
|
||||||
childCollectorVariableExpr,
|
getLeftShiftToken(),
|
||||||
"add",
|
toAdd
|
||||||
new ArgumentListExpression(List.of(toAdd))
|
|
||||||
);
|
);
|
||||||
return new ExpressionStatement(methodCall);
|
return new ExpressionStatement(leftShiftExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
// { WebViewComponentChildCollector c0cc -> ... }
|
// { List c0childList -> ... }
|
||||||
protected ClosureExpression getChildCollectorClosure(
|
protected ClosureExpression getChildrenClosure(
|
||||||
BodyNode bodyNode,
|
BodyNode bodyNode,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
) {
|
) {
|
||||||
final Parameter ccParam = new Parameter(
|
final ClassNode childListType = ClassHelper.LIST_TYPE.getPlainNodeReference();
|
||||||
CHILD_COLLECTOR_TYPE,
|
childListType.setGenericsTypes(new GenericsType[] { new GenericsType(ClassHelper.OBJECT_TYPE) });
|
||||||
this.getComponentName(state.getCurrentComponentNumber()) + "cc"
|
final Parameter childListParam = new Parameter(
|
||||||
|
childListType,
|
||||||
|
this.getComponentName(state.getCurrentComponentNumber()) + "childList"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var scope = state.pushScope();
|
final var scope = state.pushScope();
|
||||||
scope.putDeclaredVariable(ccParam);
|
scope.putDeclaredVariable(childListParam);
|
||||||
state.pushChildCollector(ccParam);
|
state.pushChildList(childListParam);
|
||||||
|
|
||||||
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
|
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
|
||||||
bodyNode,
|
bodyNode,
|
||||||
(sourceNode, expr) -> this.getChildCollectorAdd(ccParam, expr),
|
(sourceNode, expr) -> this.getChildListAdd(childListParam, expr),
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
state.popChildCollector();
|
state.popChildList();
|
||||||
state.popScope();
|
state.popScope();
|
||||||
|
|
||||||
return new ClosureExpression(
|
return new ClosureExpression(
|
||||||
new Parameter[] { ccParam },
|
new Parameter[] { childListParam },
|
||||||
bodyStatements
|
bodyStatements
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected StaticMethodCallExpression getChildCollectorGetter(
|
|
||||||
BodyNode bodyNode,
|
|
||||||
TranspilerState state
|
|
||||||
) {
|
|
||||||
final ArgumentListExpression args = new ArgumentListExpression();
|
|
||||||
args.addExpression(VariableExpression.THIS_EXPRESSION);
|
|
||||||
args.addExpression(this.getChildCollectorClosure(bodyNode, state));
|
|
||||||
|
|
||||||
return new StaticMethodCallExpression(
|
|
||||||
CHILD_COLLECTOR_CLOSURE_TYPE,
|
|
||||||
"get",
|
|
||||||
args
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TYPED COMPONENT CREATE: expression and statement */
|
/* TYPED COMPONENT CREATE: expression and statement */
|
||||||
|
|
||||||
// context.create(...) {...}
|
// context.create(resolved, attr, constructorArgs) { ... }
|
||||||
protected MethodCallExpression getTypedComponentCreateExpression(
|
protected MethodCallExpression getTypedComponentCreateExpression(
|
||||||
TypedComponentNode componentNode,
|
TypedComponentNode componentNode,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
) {
|
) {
|
||||||
final var createArgs = new ArgumentListExpression();
|
final var createArgs = new ArgumentListExpression();
|
||||||
|
|
||||||
createArgs.addExpression(new VariableExpression(state.getCurrentResolved()));
|
final VariableExpression resolvedVariableExpression;
|
||||||
|
final Variable currentResolved = state.getCurrentResolved();
|
||||||
final List<AttrNode> attributeNodes = componentNode.getArgs().getAttributes();
|
if (currentResolved instanceof VariableExpression) {
|
||||||
if (!attributeNodes.isEmpty()) {
|
resolvedVariableExpression = (VariableExpression) currentResolved;
|
||||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
} else {
|
||||||
|
resolvedVariableExpression = new VariableExpression(currentResolved);
|
||||||
}
|
}
|
||||||
|
createArgs.addExpression(resolvedVariableExpression);
|
||||||
|
|
||||||
|
final List<AttrNode> attrNodes = componentNode.getArgs().getAttributes();
|
||||||
|
if (attrNodes.isEmpty()) {
|
||||||
|
final ClassExpression webViewComponentRenderContextClassExpression = new ClassExpression(
|
||||||
|
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
|
||||||
|
);
|
||||||
|
final PropertyExpression emptyAttrMapPropertyExpression = new PropertyExpression(
|
||||||
|
webViewComponentRenderContextClassExpression,
|
||||||
|
"EMPTY_ATTR"
|
||||||
|
);
|
||||||
|
createArgs.addExpression(emptyAttrMapPropertyExpression);
|
||||||
|
} else {
|
||||||
|
createArgs.addExpression(this.getAttrMap(attrNodes, state));
|
||||||
|
}
|
||||||
|
|
||||||
final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor();
|
final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor();
|
||||||
if (constructorNode != null) {
|
if (constructorNode == null) {
|
||||||
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
final ClassExpression webViewComponentRenderContextClassExpression = new ClassExpression(
|
||||||
|
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
|
||||||
|
);
|
||||||
|
final PropertyExpression emptyConstructorArgsPropertyExpression = new PropertyExpression(
|
||||||
|
webViewComponentRenderContextClassExpression,
|
||||||
|
"EMPTY_CONSTRUCTOR_ARGS"
|
||||||
|
);
|
||||||
|
createArgs.addExpression(emptyConstructorArgsPropertyExpression);
|
||||||
|
} else {
|
||||||
|
final List<Expression> constructorArgs = this.getConstructorArgs(constructorNode);
|
||||||
|
final ArrayExpression constructorArgsArrayExpr = new ArrayExpression(
|
||||||
|
ClassHelper.OBJECT_TYPE,
|
||||||
|
constructorArgs
|
||||||
|
);
|
||||||
|
createArgs.addExpression(constructorArgsArrayExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
||||||
if (bodyNode != null) {
|
if (bodyNode != null) {
|
||||||
createArgs.addExpression(this.getChildCollectorGetter(bodyNode, state));
|
createArgs.addExpression(this.getChildrenClosure(bodyNode, state));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
|
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
// c0 = context.create(context.resolve(''), [:], new Object[] { ... }) {...}
|
||||||
protected ExpressionStatement getTypedComponentCreateStatement(
|
protected ExpressionStatement getTypedComponentCreateStatement(
|
||||||
TypedComponentNode componentNode,
|
TypedComponentNode componentNode,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
@ -463,7 +467,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
/* FRAGMENT COMPONENT */
|
/* FRAGMENT COMPONENT */
|
||||||
|
|
||||||
// context.createFragment(new Fragment(), <child cl>)
|
// context.createFragment(new Fragment()) { ... }
|
||||||
protected MethodCallExpression getFragmentCreateExpression(
|
protected MethodCallExpression getFragmentCreateExpression(
|
||||||
FragmentComponentNode componentNode,
|
FragmentComponentNode componentNode,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
@ -472,7 +476,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
FRAGMENT_TYPE,
|
FRAGMENT_TYPE,
|
||||||
ArgumentListExpression.EMPTY_ARGUMENTS
|
ArgumentListExpression.EMPTY_ARGUMENTS
|
||||||
);
|
);
|
||||||
final Expression ccClosure = this.getChildCollectorGetter(componentNode.getBody(), state);
|
final Expression ccClosure = this.getChildrenClosure(componentNode.getBody(), state);
|
||||||
|
|
||||||
final ArgumentListExpression args = new ArgumentListExpression(List.of(fragmentConstructor, ccClosure));
|
final ArgumentListExpression args = new ArgumentListExpression(List.of(fragmentConstructor, ccClosure));
|
||||||
|
|
||||||
@ -496,7 +500,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
|
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
|
||||||
componentNode,
|
componentNode,
|
||||||
state,
|
state,
|
||||||
new VariableExpression(state.getCurrentComponent())
|
(VariableExpression) state.getCurrentComponent()
|
||||||
);
|
);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
|
@ -10,7 +10,7 @@ import groowt.view.web.ast.node.PreambleNode;
|
|||||||
import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException;
|
import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException;
|
||||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileException;
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileException;
|
||||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentRenderContext;
|
import groowt.view.web.runtime.DefaultWebViewRenderContext;
|
||||||
import groowt.view.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
|
import groowt.view.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import org.codehaus.groovy.ast.*;
|
import org.codehaus.groovy.ast.*;
|
||||||
@ -44,7 +44,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
|
|
||||||
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
|
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
|
||||||
private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION =
|
private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION =
|
||||||
ClassHelper.make(DefaultWebViewComponentRenderContext.class);
|
ClassHelper.make(DefaultWebViewRenderContext.class);
|
||||||
|
|
||||||
protected TranspilerConfiguration getConfiguration(
|
protected TranspilerConfiguration getConfiguration(
|
||||||
WebViewComponentTemplateCompileUnit compileUnit,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
@ -183,7 +183,6 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib");
|
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib");
|
||||||
moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE);
|
moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE);
|
||||||
moduleNode.addImport(COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE);
|
moduleNode.addImport(COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE);
|
||||||
moduleNode.addImport(WEB_VIEW_COMPONENT_TYPE.getNameWithoutPackage(), WEB_VIEW_COMPONENT_TYPE);
|
|
||||||
moduleNode.addStarImport("groowt.view.component.runtime");
|
moduleNode.addStarImport("groowt.view.component.runtime");
|
||||||
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".runtime");
|
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".runtime");
|
||||||
|
|
||||||
@ -209,7 +208,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME);
|
final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME);
|
||||||
final VariableExpression renderContextVariable = new VariableExpression(
|
final VariableExpression renderContextVariable = new VariableExpression(
|
||||||
RENDER_CONTEXT_NAME,
|
RENDER_CONTEXT_NAME,
|
||||||
RENDER_CONTEXT_TYPE
|
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
|
||||||
);
|
);
|
||||||
|
|
||||||
// closure body
|
// closure body
|
||||||
@ -284,7 +283,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -23,7 +23,7 @@ public final class TranspilerUtil {
|
|||||||
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
|
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
|
||||||
public static final ClassNode COMPONENT_CONTEXT_TYPE = ClassHelper.make(ComponentContext.class);
|
public static final ClassNode COMPONENT_CONTEXT_TYPE = ClassHelper.make(ComponentContext.class);
|
||||||
public static final ClassNode COMPONENT_WRITER_TYPE = ClassHelper.make(ComponentWriter.class);
|
public static final ClassNode COMPONENT_WRITER_TYPE = ClassHelper.make(ComponentWriter.class);
|
||||||
public static final ClassNode RENDER_CONTEXT_TYPE = ClassHelper.make(WebViewComponentRenderContext.class);
|
public static final ClassNode WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE = ClassHelper.make(WebViewComponentRenderContext.class);
|
||||||
public static final ClassNode WEB_VIEW_COMPONENT_TYPE = ClassHelper.make(WebViewComponent.class);
|
public static final ClassNode WEB_VIEW_COMPONENT_TYPE = ClassHelper.make(WebViewComponent.class);
|
||||||
|
|
||||||
public static final String GROOWT_VIEW_WEB = "groowt.view.web";
|
public static final String GROOWT_VIEW_WEB = "groowt.view.web";
|
||||||
@ -51,6 +51,10 @@ public final class TranspilerUtil {
|
|||||||
return new Token(Types.ASSIGN, "=", -1, -1);
|
return new Token(Types.ASSIGN, "=", -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Token getLeftShiftToken() {
|
||||||
|
return new Token(Types.LEFT_SHIFT, "<<", -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
public static final class TranspilerState {
|
public static final class TranspilerState {
|
||||||
|
|
||||||
public static TranspilerState withRootScope(
|
public static TranspilerState withRootScope(
|
||||||
@ -71,15 +75,17 @@ public final class TranspilerUtil {
|
|||||||
final VariableScope rootScope = new VariableScope();
|
final VariableScope rootScope = new VariableScope();
|
||||||
rootScope.putDeclaredVariable(new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME));
|
rootScope.putDeclaredVariable(new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME));
|
||||||
rootScope.putDeclaredVariable(new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME));
|
rootScope.putDeclaredVariable(new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME));
|
||||||
rootScope.putDeclaredVariable(new VariableExpression(RENDER_CONTEXT_NAME, RENDER_CONTEXT_TYPE));
|
rootScope.putDeclaredVariable(new VariableExpression(RENDER_CONTEXT_NAME,
|
||||||
|
WEB_VIEW_COMPONENT_RENDER_CONTEXT_TYPE
|
||||||
|
));
|
||||||
return new TranspilerState(rootScope);
|
return new TranspilerState(rootScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AtomicInteger componentNumberCounter = new AtomicInteger();
|
private final AtomicInteger componentNumberCounter = new AtomicInteger();
|
||||||
private final Deque<VariableScope> scopeStack = new LinkedList<>();
|
private final Deque<VariableScope> scopeStack = new LinkedList<>();
|
||||||
private final Deque<Variable> componentStack = new LinkedList<>();
|
private final Deque<VariableExpression> componentStack = new LinkedList<>();
|
||||||
private final Deque<Variable> resolvedStack = new LinkedList<>();
|
private final Deque<VariableExpression> resolvedStack = new LinkedList<>();
|
||||||
private final Deque<Variable> childCollectorStack = new LinkedList<>();
|
private final Deque<Parameter> childListStack = new LinkedList<>();
|
||||||
private final List<ComponentTemplateCompileException> errors = new ArrayList<>();
|
private final List<ComponentTemplateCompileException> errors = new ArrayList<>();
|
||||||
|
|
||||||
private int lastComponentNumber;
|
private int lastComponentNumber;
|
||||||
@ -112,10 +118,6 @@ public final class TranspilerUtil {
|
|||||||
return Objects.requireNonNull(this.scopeStack.peek());
|
return Objects.requireNonNull(this.scopeStack.peek());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putToCurrentScope(Variable variable) {
|
|
||||||
this.getCurrentScope().putDeclaredVariable(variable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable getDeclaredVariable(String name) {
|
private Variable getDeclaredVariable(String name) {
|
||||||
VariableScope scope = this.getCurrentScope();
|
VariableScope scope = this.getCurrentScope();
|
||||||
while (scope != null) {
|
while (scope != null) {
|
||||||
@ -129,15 +131,15 @@ public final class TranspilerUtil {
|
|||||||
throw new NullPointerException("Cannot find variable: " + name);
|
throw new NullPointerException("Cannot find variable: " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable getWriter() {
|
public VariableExpression getWriter() {
|
||||||
return this.getDeclaredVariable(COMPONENT_WRITER_NAME);
|
return new VariableExpression(this.getDeclaredVariable(COMPONENT_WRITER_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable getRenderContext() {
|
public VariableExpression getRenderContext() {
|
||||||
return this.getDeclaredVariable(RENDER_CONTEXT_NAME);
|
return (VariableExpression) this.getDeclaredVariable(RENDER_CONTEXT_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushComponent(Variable componentVariable) {
|
public void pushComponent(VariableExpression componentVariable) {
|
||||||
this.componentStack.push(componentVariable);
|
this.componentStack.push(componentVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,11 +147,11 @@ public final class TranspilerUtil {
|
|||||||
this.componentStack.pop();
|
this.componentStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable getCurrentComponent() {
|
public VariableExpression getCurrentComponent() {
|
||||||
return Objects.requireNonNull(this.componentStack.peek());
|
return Objects.requireNonNull(this.componentStack.peek());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushResolved(Variable resolvedVariable) {
|
public void pushResolved(VariableExpression resolvedVariable) {
|
||||||
this.resolvedStack.push(resolvedVariable);
|
this.resolvedStack.push(resolvedVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,24 +159,25 @@ public final class TranspilerUtil {
|
|||||||
this.resolvedStack.pop();
|
this.resolvedStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable getCurrentResolved() {
|
public VariableExpression getCurrentResolved() {
|
||||||
return Objects.requireNonNull(this.resolvedStack.peek());
|
return Objects.requireNonNull(this.resolvedStack.peek());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void pushChildCollector(Variable childCollector) {
|
public void pushChildList(Parameter childCollector) {
|
||||||
this.childCollectorStack.push(childCollector);
|
this.childListStack.push(childCollector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void popChildCollector() {
|
public void popChildList() {
|
||||||
this.childCollectorStack.pop();
|
this.childListStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Variable getCurrentChildCollector() {
|
public VariableExpression getCurrentChildList() {
|
||||||
return Objects.requireNonNull(this.childCollectorStack.peek());
|
final Parameter childCollectorParam = Objects.requireNonNull(this.childListStack.peek());
|
||||||
|
return new VariableExpression(childCollectorParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasCurrentChildCollector() {
|
public boolean hasCurrentChildList() {
|
||||||
return this.childCollectorStack.peek() != null;
|
return this.childListStack.peek() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addError(ComponentTemplateCompileException error) {
|
public void addError(ComponentTemplateCompileException error) {
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
package groowt.view.web
|
package groowt.view.web
|
||||||
|
|
||||||
import groowt.view.component.factory.ComponentFactories
|
import groowt.view.component.factory.ComponentFactories
|
||||||
import groowt.view.component.factory.ComponentFactory
|
|
||||||
import groowt.view.web.lib.AbstractWebViewComponentTests
|
import groowt.view.web.lib.AbstractWebViewComponentTests
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
|
class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
|
||||||
|
|
||||||
private static final ComponentFactory<Greeter> greeterFactory = WebViewComponentFactories.withAttr(Greeter) {
|
|
||||||
new Greeter(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class Greeter extends BaseWebViewComponent {
|
static final class Greeter extends BaseWebViewComponent {
|
||||||
|
|
||||||
private final String target
|
private final String target
|
||||||
@ -50,8 +45,9 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
|
|||||||
@Test
|
@Test
|
||||||
void nestedGreeter() {
|
void nestedGreeter() {
|
||||||
def context = this.context {
|
def context = this.context {
|
||||||
this.configureContext(it)
|
getRootScope(DefaultWebViewComponentScope).with {
|
||||||
currentScope.add(Greeter, greeterFactory)
|
addWithAttr(Greeter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.doTest('<BaseWebViewComponentTests.Greeter target="World" />', 'Hello, World!', context)
|
this.doTest('<BaseWebViewComponentTests.Greeter target="World" />', 'Hello, World!', context)
|
||||||
}
|
}
|
||||||
@ -59,9 +55,10 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
|
|||||||
@Test
|
@Test
|
||||||
void doubleNested() {
|
void doubleNested() {
|
||||||
def context = this.context {
|
def context = this.context {
|
||||||
this.configureContext(it)
|
getRootScope(DefaultWebViewComponentScope).with {
|
||||||
currentScope.add(UsingGreeter, ComponentFactories.ofSupplier { new UsingGreeter() })
|
addWithAttr(Greeter)
|
||||||
currentScope.add(Greeter, greeterFactory)
|
add(UsingGreeter, ComponentFactories.ofSupplier { new UsingGreeter() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.doTest('<BaseWebViewComponentTests.UsingGreeter />', 'Hello, World!', context)
|
this.doTest('<BaseWebViewComponentTests.UsingGreeter />', 'Hello, World!', context)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class EchoTests extends AbstractWebViewComponentTests {
|
class EchoTests extends AbstractWebViewComponentTests {
|
||||||
@ -20,4 +19,9 @@ class EchoTests extends AbstractWebViewComponentTests {
|
|||||||
this.doTest('<Echo>Hello, World!</Echo>', 'Hello, World!')
|
this.doTest('<Echo>Hello, World!</Echo>', 'Hello, World!')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void childrenCanUseProperties() {
|
||||||
|
this.doTest('<Echo greeting="Hello, World!">$greeting</Echo>', 'Hello, World!')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import groowt.view.web.BaseWebViewComponent
|
import groowt.view.web.BaseWebViewComponent
|
||||||
|
import groowt.view.web.DefaultWebViewComponentScope
|
||||||
import groowt.view.web.WebViewComponentContext
|
import groowt.view.web.WebViewComponentContext
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
import static groowt.view.web.WebViewComponentFactories.withAttr
|
|
||||||
|
|
||||||
class FragmentTests extends AbstractWebViewComponentTests {
|
class FragmentTests extends AbstractWebViewComponentTests {
|
||||||
|
|
||||||
static class Greeter extends BaseWebViewComponent {
|
static class Greeter extends BaseWebViewComponent {
|
||||||
@ -21,8 +20,9 @@ class FragmentTests extends AbstractWebViewComponentTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void configureContext(WebViewComponentContext context) {
|
void configureContext(WebViewComponentContext context) {
|
||||||
def greeterFactory = withAttr(Greeter, Greeter.&new)
|
context.getRootScope(DefaultWebViewComponentScope).with {
|
||||||
context.currentScope.add(Greeter, greeterFactory)
|
addWithAttr(Greeter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
|
class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
|
||||||
@ -21,9 +20,8 @@ class IntrinsicHtmlTests extends AbstractWebViewComponentTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled('Until we figure out nested closure delegates')
|
|
||||||
void canUseEchoAttrPropertyViaContext() {
|
void canUseEchoAttrPropertyViaContext() {
|
||||||
this.doTest('<Echo greeting="Hello!"><p>${context}</p></Echo>', '<p>Hello!</p>')
|
this.doTest('<Echo greeting="Hello!"><p>$greeting</p></Echo>', '<p>Hello!</p>')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ package groowt.view.web.tools
|
|||||||
|
|
||||||
import groovy.console.ui.AstNodeToScriptVisitor
|
import groovy.console.ui.AstNodeToScriptVisitor
|
||||||
import groowt.view.component.compiler.source.ComponentTemplateSource
|
import groowt.view.component.compiler.source.ComponentTemplateSource
|
||||||
|
import groowt.view.component.compiler.util.GroovyClassWriter
|
||||||
|
import groowt.view.component.compiler.util.SimpleGroovyClassWriter
|
||||||
import groowt.view.web.compiler.AnonymousWebViewComponent
|
import groowt.view.web.compiler.AnonymousWebViewComponent
|
||||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit
|
||||||
import org.codehaus.groovy.tools.GroovyClass
|
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
|
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
@ -48,15 +49,12 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ['-d', '--classesDir'],
|
names = ['-d', '--classesDir'],
|
||||||
description = 'If the GroovyCompiler outputs classes, where to write them.'
|
description = 'If the GroovyCompiler outputs classes, where to write them, relative to the target.',
|
||||||
|
defaultValue = 'classes'
|
||||||
)
|
)
|
||||||
File classesDir
|
File classesDir
|
||||||
|
|
||||||
private void writeClass(File classesDir, GroovyClass groovyClass) {
|
private final GroovyClassWriter groovyClassWriter = new SimpleGroovyClassWriter()
|
||||||
new File(classesDir, groovyClass.name + '.class').withOutputStream {
|
|
||||||
it.write(groovyClass.bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Integer call() throws Exception {
|
Integer call() throws Exception {
|
||||||
@ -85,12 +83,11 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.doClasses) {
|
if (this.doClasses) {
|
||||||
def classesDir = this.classesDir != null
|
def classesDir = target.parentFile.toPath().resolve(this.classesDir.toPath())
|
||||||
? this.classesDir
|
this.groovyClassWriter.writeTo(classesDir, compileResult.templateClass)
|
||||||
: new File(target.parentFile, 'classes')
|
compileResult.otherClasses.each {
|
||||||
classesDir.mkdirs()
|
this.groovyClassWriter.writeTo(classesDir, it)
|
||||||
this.writeClass(classesDir, compileResult.templateClass)
|
}
|
||||||
compileResult.otherClasses.each { this.writeClass(classesDir, it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
Loading…
Reference in New Issue
Block a user