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
	 JesseBrault0709
						JesseBrault0709