Major refactoring of compiler, transpiler, and general api.
This commit is contained in:
parent
2d4e085bb3
commit
3a171b8736
@ -1,10 +1,8 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
import groowt.view.component.compiler.*;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
import groowt.view.component.context.ComponentContext;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
@ -13,44 +11,45 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
public abstract class AbstractViewComponent implements ViewComponent {
|
public abstract class AbstractViewComponent implements ViewComponent {
|
||||||
|
|
||||||
private ComponentContext context;
|
private static final ComponentTemplateClassFactory templateClassFactory = new SimpleComponentTemplateClassFactory();
|
||||||
private ComponentTemplate template;
|
|
||||||
|
|
||||||
public AbstractViewComponent() {}
|
private static ComponentTemplate instantiateTemplate(Class<? extends ComponentTemplate> templateClass) {
|
||||||
|
|
||||||
public AbstractViewComponent(ComponentTemplate template) {
|
|
||||||
this.template = Objects.requireNonNull(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractViewComponent(Class<? extends ComponentTemplate> templateClass) {
|
|
||||||
try {
|
try {
|
||||||
this.template = templateClass.getConstructor().newInstance();
|
return templateClass.getConstructor().newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) {
|
private final ComponentTemplate template;
|
||||||
try {
|
private ComponentContext context;
|
||||||
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
|
||||||
} catch (ComponentTemplateCompileErrorException e) {
|
public AbstractViewComponent() {
|
||||||
throw new RuntimeException(e);
|
this.template = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractViewComponent(
|
public AbstractViewComponent(ComponentTemplate template) {
|
||||||
ComponentTemplateSource source,
|
this.template = template;
|
||||||
Function<? super Class<? extends AbstractViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
}
|
||||||
|
|
||||||
|
public AbstractViewComponent(Class<? extends ComponentTemplate> templateClass) {
|
||||||
|
this.template = instantiateTemplate(templateClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractViewComponent(
|
||||||
|
Function<? super Class<? extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
|
||||||
) {
|
) {
|
||||||
final var compiler = compilerFunction.apply(this.getSelfClass());
|
final ComponentTemplateCompileResult compileResult;
|
||||||
try {
|
try {
|
||||||
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
compileResult = compileUnitFunction.apply(this.getClass()).compile();
|
||||||
} catch (ComponentTemplateCompileErrorException e) {
|
} catch (ComponentTemplateCompileException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
final var templateClass = templateClassFactory.getTemplateClass(compileResult);
|
||||||
|
this.template = instantiateTemplate(templateClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Class<? extends AbstractViewComponent> getSelfClass();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContext(ComponentContext context) {
|
public void setContext(ComponentContext context) {
|
||||||
@ -66,26 +65,20 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
|||||||
return Objects.requireNonNull(template);
|
return Objects.requireNonNull(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setTemplate(ComponentTemplate template) {
|
protected void beforeRender() {}
|
||||||
this.template = Objects.requireNonNull(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void beforeRender() {
|
protected void afterRender() {}
|
||||||
this.getContext().beforeComponentRender(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void afterRender() {
|
|
||||||
this.getContext().afterComponentRender(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implSpec If overriding, <strong>please</strong> call
|
* @implSpec If overriding, <strong>please</strong> call
|
||||||
* {@link #beforeRender()} and {@link #afterRender()} before
|
* {@link #beforeRender()} and {@link #afterRender()} before
|
||||||
* and after the actual rendering is done, respectively.
|
* and after the actual rendering is done, respectively;
|
||||||
|
* this way, components can still do their before/after
|
||||||
|
* logic even if this method is overwritten.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void renderTo(Writer out) throws IOException {
|
public void renderTo(Writer out) throws IOException {
|
||||||
final Closure<?> closure = this.template.getRenderer();
|
final Closure<?> closure = this.getTemplate().getRenderer();
|
||||||
closure.setDelegate(this);
|
closure.setDelegate(this);
|
||||||
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
|
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||||
this.beforeRender();
|
this.beforeRender();
|
||||||
|
@ -5,10 +5,6 @@ import groowt.view.component.context.ComponentContext;
|
|||||||
|
|
||||||
public interface ViewComponent extends View {
|
public interface ViewComponent extends View {
|
||||||
|
|
||||||
default String getTypeName() {
|
|
||||||
return this.getClass().getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <em>Note:</em> compiled templates are required to automatically
|
* <em>Note:</em> compiled templates are required to automatically
|
||||||
* call this method after the component is constructed. One
|
* call this method after the component is constructed. One
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
public class ViewComponentBugError extends RuntimeException {
|
||||||
|
|
||||||
|
public ViewComponentBugError(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewComponentBugError(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewComponentBugError(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewComponentBugError(
|
||||||
|
String message,
|
||||||
|
Throwable cause,
|
||||||
|
boolean enableSuppression,
|
||||||
|
boolean writableStackTrace
|
||||||
|
) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "BUG! Please file an issue report at the github repository. " + super.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||||
|
|
||||||
|
public abstract class AbstractComponentTemplateCompileUnit implements
|
||||||
|
ComponentTemplateCompileUnit {
|
||||||
|
|
||||||
|
private final Class<? extends ViewComponent> forClass;
|
||||||
|
private final ComponentTemplateSource source;
|
||||||
|
|
||||||
|
public AbstractComponentTemplateCompileUnit(
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
ComponentTemplateSource source
|
||||||
|
) {
|
||||||
|
this.forClass = forClass;
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ViewComponent> getForClass() {
|
||||||
|
return this.forClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentTemplateSource getSource() {
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package groowt.view.component.compiler;
|
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
public abstract class AbstractComponentTemplateCompiler implements ComponentTemplateCompiler {
|
|
||||||
|
|
||||||
private final GroovyClassLoader groovyClassLoader;
|
|
||||||
|
|
||||||
public AbstractComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
|
|
||||||
this.groovyClassLoader = groovyClassLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ComponentTemplateCompileResult compile(
|
|
||||||
ComponentTemplateSource componentTemplateSource,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Reader actualSource
|
|
||||||
) throws ComponentTemplateCompileErrorException;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplateCompileResult compile(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
|
||||||
throws ComponentTemplateCompileErrorException {
|
|
||||||
try (final Reader reader = ComponentTemplateSource.toReader(source)) {
|
|
||||||
return this.compile(source, forClass, reader);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compileAndGet(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
|
||||||
throws ComponentTemplateCompileErrorException {
|
|
||||||
return this.compileAndGet(this.groovyClassLoader, forClass, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,104 +1,59 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
import org.codehaus.groovy.tools.GroovyClass;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
|
public abstract class CachingComponentTemplateCompiler<U extends ComponentTemplateCompileUnit>
|
||||||
|
implements ComponentTemplateCompiler<U> {
|
||||||
|
|
||||||
private record CachedTemplate(
|
private final Map<Class<? extends ViewComponent>, ComponentTemplateCompileResult> cache = new HashMap<>();
|
||||||
ComponentTemplateCompileResult compileResult,
|
|
||||||
@Nullable ComponentTemplate template
|
|
||||||
) {}
|
|
||||||
|
|
||||||
private final Map<Class<? extends ViewComponent>, CachedTemplate> cache = new HashMap<>();
|
// private ComponentTemplate instantiate(
|
||||||
|
// GroovyClassLoader groovyClassLoader,
|
||||||
public CachingComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
|
// CompileResult compileResult
|
||||||
super(groovyClassLoader);
|
// ) {
|
||||||
}
|
// for (final var groovyClass : compileResult.otherClasses()) {
|
||||||
|
// // Try to find it. If we can't, we need to load it via the groovy loader
|
||||||
private ComponentTemplate instantiate(
|
// try {
|
||||||
GroovyClassLoader groovyClassLoader,
|
// Class.forName(groovyClass.getName(), true, groovyClassLoader);
|
||||||
ComponentTemplateCompileResult compileResult
|
// } catch (ClassNotFoundException ignored) {
|
||||||
) {
|
// groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
|
||||||
for (final var groovyClass : compileResult.otherClasses()) {
|
// } catch (LinkageError ignored) {
|
||||||
// Try to find it. If we can't, we need to load it via the groovy loader
|
// // no-op, because we already have it
|
||||||
try {
|
// }
|
||||||
Class.forName(groovyClass.getName(), true, groovyClassLoader);
|
// }
|
||||||
} catch (ClassNotFoundException ignored) {
|
// final GroovyClass templateGroovyClass = compileResult.templateClass();
|
||||||
groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
|
// Class<?> templateClass;
|
||||||
} catch (LinkageError ignored) {
|
// // Try to find it. If we can't, we need to load it via the groovy loader
|
||||||
// no-op, because we already have it
|
// try {
|
||||||
}
|
// templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
|
||||||
}
|
// } catch (ClassNotFoundException ignored) {
|
||||||
final GroovyClass templateGroovyClass = compileResult.templateClass();
|
// templateClass = groovyClassLoader.defineClass(
|
||||||
Class<?> templateClass;
|
// templateGroovyClass.getName(),
|
||||||
// Try to find it. If we can't, we need to load it via the groovy loader
|
// templateGroovyClass.getBytes()
|
||||||
try {
|
// );
|
||||||
templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
|
// }
|
||||||
} catch (ClassNotFoundException ignored) {
|
// try {
|
||||||
templateClass = groovyClassLoader.defineClass(
|
// return (ComponentTemplate) templateClass.getConstructor().newInstance();
|
||||||
templateGroovyClass.getName(),
|
// } catch (Exception e) {
|
||||||
templateGroovyClass.getBytes()
|
// throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
|
||||||
);
|
// }
|
||||||
}
|
// }
|
||||||
try {
|
|
||||||
return (ComponentTemplate) templateClass.getConstructor().newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final ComponentTemplate compileAndGet(
|
public final ComponentTemplateCompileResult compile(U compileUnit)
|
||||||
GroovyClassLoader groovyClassLoader,
|
throws ComponentTemplateCompileException {
|
||||||
Class<? extends ViewComponent> forClass,
|
if (this.cache.containsKey(compileUnit.getForClass())) {
|
||||||
ComponentTemplateSource source
|
return this.cache.get(compileUnit.getForClass());
|
||||||
) throws ComponentTemplateCompileErrorException {
|
|
||||||
if (this.cache.containsKey(forClass)) {
|
|
||||||
final var cached = this.cache.get(forClass);
|
|
||||||
if (cached.template() == null) {
|
|
||||||
final ComponentTemplate template = this.instantiate(groovyClassLoader, cached.compileResult());
|
|
||||||
this.cache.put(forClass, new CachedTemplate(cached.compileResult(), template));
|
|
||||||
return template;
|
|
||||||
} else {
|
} else {
|
||||||
return cached.template();
|
final ComponentTemplateCompileResult compileResult = this.doCompile(compileUnit);
|
||||||
}
|
this.cache.put(compileUnit.getForClass(), compileResult);
|
||||||
} else {
|
|
||||||
final ComponentTemplateCompileResult compileResult = this.compile(forClass, source);
|
|
||||||
final ComponentTemplate template = this.instantiate(groovyClassLoader, compileResult);
|
|
||||||
this.cache.put(forClass, new CachedTemplate(compileResult, template));
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final ComponentTemplateCompileResult compile(
|
|
||||||
ComponentTemplateSource componentTemplateSource,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Reader actualSource
|
|
||||||
) throws ComponentTemplateCompileErrorException {
|
|
||||||
if (this.cache.containsKey(forClass)) {
|
|
||||||
return this.cache.get(forClass).compileResult();
|
|
||||||
} else {
|
|
||||||
final ComponentTemplateCompileResult compileResult =
|
|
||||||
this.doCompile(componentTemplateSource, forClass, actualSource);
|
|
||||||
this.cache.put(forClass, new CachedTemplate(compileResult, null));
|
|
||||||
return compileResult;
|
return compileResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ComponentTemplateCompileResult doCompile(
|
protected abstract ComponentTemplateCompileResult doCompile(U compileUnit) throws ComponentTemplateCompileException;
|
||||||
ComponentTemplateSource source,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Reader reader
|
|
||||||
) throws ComponentTemplateCompileErrorException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
|
||||||
|
public interface ComponentTemplateClassFactory {
|
||||||
|
Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult);
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package groowt.view.component.compiler;
|
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an exception thrown while attempting to instantiate a ComponentTemplate during compilation.
|
|
||||||
*/
|
|
||||||
public class ComponentTemplateCompileErrorException extends Exception {
|
|
||||||
|
|
||||||
private final Class<? extends ViewComponent> forClass;
|
|
||||||
private final Object templateSource;
|
|
||||||
|
|
||||||
public ComponentTemplateCompileErrorException(
|
|
||||||
String message,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
|
||||||
super(message);
|
|
||||||
this.forClass = forClass;
|
|
||||||
this.templateSource = templateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentTemplateCompileErrorException(
|
|
||||||
String message,
|
|
||||||
Throwable cause,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
|
||||||
super(message, cause);
|
|
||||||
this.forClass = forClass;
|
|
||||||
this.templateSource = templateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentTemplateCompileErrorException(
|
|
||||||
Throwable cause,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
|
||||||
super(cause);
|
|
||||||
this.forClass = forClass;
|
|
||||||
this.templateSource = templateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentTemplateCompileErrorException(
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
|
||||||
super("Compile error in " + templateSource + " for " + forClass.getName());
|
|
||||||
this.forClass = forClass;
|
|
||||||
this.templateSource = templateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<? extends ViewComponent> getForClass() {
|
|
||||||
return this.forClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getTemplateSource() {
|
|
||||||
return this.templateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class ComponentTemplateCompileException extends Exception {
|
||||||
|
|
||||||
|
private final ComponentTemplateCompileUnit compileUnit;
|
||||||
|
|
||||||
|
public ComponentTemplateCompileException(ComponentTemplateCompileUnit compileUnit, String message) {
|
||||||
|
super(message);
|
||||||
|
this.compileUnit = compileUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentTemplateCompileException(
|
||||||
|
ComponentTemplateCompileUnit compileUnit,
|
||||||
|
String message,
|
||||||
|
Throwable cause
|
||||||
|
) {
|
||||||
|
super(message, cause);
|
||||||
|
this.compileUnit = compileUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
final var sb = new StringBuilder("Error in ").append(compileUnit.getSource().getDescription());
|
||||||
|
final @Nullable String position = this.getPosition();
|
||||||
|
if (position != null) {
|
||||||
|
sb.append(" at ").append(position);
|
||||||
|
}
|
||||||
|
return sb.append(": ").append(super.getMessage()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @Nullable String getPosition() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface ComponentTemplateCompileResult {
|
||||||
|
GroovyClass getTemplateClass();
|
||||||
|
Set<GroovyClass> getOtherClasses();
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||||
|
|
||||||
|
public interface ComponentTemplateCompileUnit {
|
||||||
|
|
||||||
|
Class<? extends ViewComponent> getForClass();
|
||||||
|
String getDefaultPackageName();
|
||||||
|
ComponentTemplateSource getSource();
|
||||||
|
ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
|
||||||
|
throws ComponentTemplateCompileException;
|
||||||
|
|
||||||
|
default ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
|
||||||
|
return this.compile(new DefaultComponentTemplateCompilerConfiguration());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,34 +1,7 @@
|
|||||||
package groowt.view.component.compiler;
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
public interface ComponentTemplateCompiler<U extends ComponentTemplateCompileUnit> {
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
import org.codehaus.groovy.tools.GroovyClass;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
ComponentTemplateCompileResult compile(U compileUnit) throws ComponentTemplateCompileException;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface ComponentTemplateCompiler {
|
|
||||||
|
|
||||||
record ComponentTemplateCompileResult(GroovyClass templateClass, List<GroovyClass> otherClasses) {}
|
|
||||||
|
|
||||||
ComponentTemplateCompileResult compile(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
|
||||||
throws ComponentTemplateCompileErrorException;
|
|
||||||
|
|
||||||
ComponentTemplate compileAndGet(
|
|
||||||
GroovyClassLoader groovyClassLoader,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
ComponentTemplateSource source
|
|
||||||
) throws ComponentTemplateCompileErrorException;
|
|
||||||
|
|
||||||
default ComponentTemplate compileAndGet(Class<? extends ViewComponent> forClass, ComponentTemplateSource source)
|
|
||||||
throws ComponentTemplateCompileErrorException {
|
|
||||||
try (final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader())) {
|
|
||||||
return this.compileAndGet(groovyClassLoader, forClass, source);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import org.codehaus.groovy.control.CompilePhase;
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
|
|
||||||
|
public interface ComponentTemplateCompilerConfiguration {
|
||||||
|
GroovyClassLoader getGroovyClassLoader();
|
||||||
|
CompilerConfiguration getGroovyCompilerConfiguration();
|
||||||
|
CompilePhase getToCompilePhase();
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import org.codehaus.groovy.control.CompilePhase;
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
|
|
||||||
|
public class DefaultComponentTemplateCompilerConfiguration implements ComponentTemplateCompilerConfiguration {
|
||||||
|
|
||||||
|
private GroovyClassLoader groovyClassLoader;
|
||||||
|
private CompilerConfiguration groovyCompilerConfiguration;
|
||||||
|
private CompilePhase toCompilePhase;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroovyClassLoader getGroovyClassLoader() {
|
||||||
|
return this.groovyClassLoader != null
|
||||||
|
? this.groovyClassLoader
|
||||||
|
: new GroovyClassLoader(this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
|
||||||
|
this.groovyClassLoader = groovyClassLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompilerConfiguration getGroovyCompilerConfiguration() {
|
||||||
|
return this.groovyCompilerConfiguration != null
|
||||||
|
? this.groovyCompilerConfiguration
|
||||||
|
: CompilerConfiguration.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroovyCompilerConfiguration(CompilerConfiguration groovyCompilerConfiguration) {
|
||||||
|
this.groovyCompilerConfiguration = groovyCompilerConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompilePhase getToCompilePhase() {
|
||||||
|
return this.toCompilePhase != null ? this.toCompilePhase : CompilePhase.CLASS_GENERATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToCompilePhase(CompilePhase toCompilePhase) {
|
||||||
|
this.toCompilePhase = toCompilePhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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 ClassLoader classLoader;
|
||||||
|
private final Path tempClassesDir;
|
||||||
|
|
||||||
|
public SimpleComponentTemplateClassFactory() {
|
||||||
|
try {
|
||||||
|
this.tempClassesDir = Files.createTempDirectory("view-component-classes-");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.classLoader = new URLClassLoader(
|
||||||
|
"SimpleComponentTemplateClassFactoryClassLoader",
|
||||||
|
new URL[] { this.tempClassesDir.toUri().toURL() },
|
||||||
|
this.getClass().getClassLoader()
|
||||||
|
);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public Class<? extends ComponentTemplate> getTemplateClass(ComponentTemplateCompileResult compileResult) {
|
||||||
|
final String templateClassName = compileResult.getTemplateClass().getName();
|
||||||
|
if (this.cache.containsKey(templateClassName)) {
|
||||||
|
return this.cache.get(templateClassName);
|
||||||
|
} else {
|
||||||
|
// write classes to disk
|
||||||
|
this.writeClassToDisk(compileResult.getTemplateClass());
|
||||||
|
compileResult.getOtherClasses().forEach(this::writeClassToDisk);
|
||||||
|
// load the template class
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
final var templateClass = (Class<? extends ComponentTemplate>) this.classLoader.loadClass(
|
||||||
|
templateClassName
|
||||||
|
);
|
||||||
|
this.cache.put(templateClassName, templateClass);
|
||||||
|
return templateClass;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package groowt.view.component.compiler;
|
||||||
|
|
||||||
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SimpleComponentTemplateCompileResult implements ComponentTemplateCompileResult {
|
||||||
|
|
||||||
|
private final GroovyClass templateClass;
|
||||||
|
private final Set<GroovyClass> otherClasses;
|
||||||
|
|
||||||
|
public SimpleComponentTemplateCompileResult(
|
||||||
|
GroovyClass templateClass,
|
||||||
|
Set<GroovyClass> otherClasses
|
||||||
|
) {
|
||||||
|
this.templateClass = templateClass;
|
||||||
|
this.otherClasses = otherClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroovyClass getTemplateClass() {
|
||||||
|
return this.templateClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<GroovyClass> getOtherClasses() {
|
||||||
|
return new HashSet<>(this.otherClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.templateClass.getName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) return true;
|
||||||
|
if (obj instanceof ComponentTemplateCompileResult other) {
|
||||||
|
return this.templateClass.getName().equals(other.getTemplateClass().getName());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "sctCompileResult(" + this.templateClass.getName() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ComponentTemplateSource {
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(String template) {
|
||||||
|
return new StringSource(template, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(String template, String name) {
|
||||||
|
return new StringSource(template, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(File templateFile) {
|
||||||
|
return new FileSource(templateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(URI templateURI) {
|
||||||
|
return new URISource(templateURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(URL url) {
|
||||||
|
return new URLSource(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(InputStream templateInputStream) {
|
||||||
|
return new InputStreamSource(templateInputStream, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(InputStream templateInputStream, String description) {
|
||||||
|
return new InputStreamSource(templateInputStream, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(Reader templateReader) {
|
||||||
|
return new ReaderSource(templateReader, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ComponentTemplateSource of(Reader templateReader, String description) {
|
||||||
|
return new ReaderSource(templateReader, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader toReader() throws Exception;
|
||||||
|
String getDescription();
|
||||||
|
boolean canReopen();
|
||||||
|
List<String> getLines();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FileSource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final File templateFile;
|
||||||
|
private List<String> lines;
|
||||||
|
|
||||||
|
public FileSource(File templateFile) {
|
||||||
|
this.templateFile = templateFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() throws Exception {
|
||||||
|
return new FileReader(this.templateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.templateFile.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
if (this.lines == null) {
|
||||||
|
try (final var fis = new FileInputStream(this.templateFile)) {
|
||||||
|
final var allSource = new String(fis.readAllBytes());
|
||||||
|
this.lines = allSource.lines().toList();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable URI getURI() {
|
||||||
|
return templateFile.toURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class InputStreamSource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final InputStream templateInputStream;
|
||||||
|
private final @Nullable String description;
|
||||||
|
|
||||||
|
public InputStreamSource(InputStream templateInputStream, @Nullable String description) {
|
||||||
|
this.templateInputStream = templateInputStream;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() {
|
||||||
|
return new InputStreamReader(this.templateInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description != null ? this.description : "<anonymous InputStream source>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ReaderSource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final Reader reader;
|
||||||
|
private final @Nullable String description;
|
||||||
|
|
||||||
|
public ReaderSource(Reader reader, @Nullable String description) {
|
||||||
|
this.reader = reader;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() {
|
||||||
|
return this.reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description != null ? this.description : "<anonymous Reader source>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StringSource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final String template;
|
||||||
|
private final @Nullable String name;
|
||||||
|
|
||||||
|
public StringSource(String template, @Nullable String name) {
|
||||||
|
this.template = template;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() {
|
||||||
|
return new StringReader(this.template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.name != null ? this.name : "<anonymous string source>";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
return this.template.lines().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class URISource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final URI templateURI;
|
||||||
|
private List<String> lines;
|
||||||
|
|
||||||
|
public URISource(URI templateURI) {
|
||||||
|
this.templateURI = templateURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() throws Exception {
|
||||||
|
return new InputStreamReader(this.templateURI.toURL().openStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.templateURI.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI getURI() {
|
||||||
|
return this.templateURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
if (this.lines == null) {
|
||||||
|
try (final var inputStream = this.templateURI.toURL().openStream()) {
|
||||||
|
final String allSource = new String(inputStream.readAllBytes());
|
||||||
|
this.lines = allSource.lines().toList();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package groowt.view.component.compiler.source;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class URLSource implements ComponentTemplateSource {
|
||||||
|
|
||||||
|
private final URL url;
|
||||||
|
private List<String> lines;
|
||||||
|
|
||||||
|
public URLSource(URL url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader toReader() throws Exception {
|
||||||
|
return new InputStreamReader(this.url.openStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return this.url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopen() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable URI getURI() {
|
||||||
|
try {
|
||||||
|
return this.url.toURI();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getLines() {
|
||||||
|
if (this.lines == null) {
|
||||||
|
try (final var inputStream = this.url.openStream()) {
|
||||||
|
final String allSource = new String(inputStream.readAllBytes());
|
||||||
|
this.lines = allSource.lines().toList();
|
||||||
|
} catch (IOException ioException) {
|
||||||
|
throw new RuntimeException(ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,61 +1,27 @@
|
|||||||
package groowt.view.component.context;
|
package groowt.view.component.context;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentFactory;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public interface ComponentContext {
|
public interface ComponentContext {
|
||||||
|
|
||||||
/**
|
List<ComponentScope> getScopeStack();
|
||||||
* For use only by compiled templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
interface Resolved {
|
|
||||||
String getTypeName();
|
|
||||||
ComponentFactory<?> getComponentFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For use only by compiled templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
Resolved resolve(String component);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For use only by compiled templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
ViewComponent create(Resolved resolved, Object... args);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For use only by compiled templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
void beforeComponentRender(ViewComponent component);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For use only by compiled templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
void afterComponentRender(ViewComponent component);
|
|
||||||
|
|
||||||
Deque<ComponentScope> getScopeStack();
|
|
||||||
|
|
||||||
void pushScope(ComponentScope scope);
|
void pushScope(ComponentScope scope);
|
||||||
void pushDefaultScope();
|
void pushDefaultScope();
|
||||||
void popScope();
|
void popScope();
|
||||||
|
ComponentScope getRootScope();
|
||||||
|
|
||||||
default ComponentScope getCurrentScope() {
|
default ComponentScope getCurrentScope() {
|
||||||
return Objects.requireNonNull(this.getScopeStack().peek(), "There is no current scope.");
|
final List<ComponentScope> scopeStack = this.getScopeStack();
|
||||||
|
if (scopeStack.isEmpty()) {
|
||||||
|
throw new NullPointerException("There is no current scope.");
|
||||||
|
}
|
||||||
|
return scopeStack.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
Deque<ViewComponent> getComponentStack();
|
|
||||||
|
|
||||||
@Nullable ViewComponent getParent();
|
@Nullable ViewComponent getParent();
|
||||||
@Nullable <T extends ViewComponent> T getParent(Class<T> parentClass);
|
@Nullable <T extends ViewComponent> T getParent(Class<T> parentClass);
|
||||||
@ -66,11 +32,7 @@ public interface ComponentContext {
|
|||||||
Class<T> ancestorClass,
|
Class<T> ancestorClass,
|
||||||
Predicate<? super ViewComponent> matching
|
Predicate<? super ViewComponent> matching
|
||||||
) {
|
) {
|
||||||
return ancestorClass.cast(matching.and(ancestorClass::isInstance));
|
return ancestorClass.cast(this.findNearestAncestor(matching.and(ancestorClass::isInstance)));
|
||||||
}
|
|
||||||
|
|
||||||
default @Nullable ViewComponent findNearestAncestorByTypeName(String typeName) {
|
|
||||||
return this.findNearestAncestor(component -> component.getTypeName().equals(typeName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ViewComponent> getAllAncestors();
|
List<ViewComponent> getAllAncestors();
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception which signals that a component of the given type
|
|
||||||
* could not be created in the given template.
|
|
||||||
*/
|
|
||||||
public class ComponentCreateException extends RuntimeException {
|
|
||||||
|
|
||||||
private final Object componentType;
|
|
||||||
private final ComponentTemplate template;
|
|
||||||
private final int line;
|
|
||||||
private final int column;
|
|
||||||
|
|
||||||
public ComponentCreateException(
|
|
||||||
Object componentType,
|
|
||||||
ComponentTemplate template,
|
|
||||||
int line,
|
|
||||||
int column,
|
|
||||||
Throwable cause
|
|
||||||
) {
|
|
||||||
super(cause);
|
|
||||||
this.componentType = componentType;
|
|
||||||
this.template = template;
|
|
||||||
this.line = line;
|
|
||||||
this.column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return "Exception in " + this.template.getClass().getName() + " while creating "
|
|
||||||
+ this.componentType.getClass().getName() + " at line " + this.line
|
|
||||||
+ ", column " + this.column + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,141 @@
|
|||||||
|
package groowt.view.component.context;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ComponentResolveException extends Exception {
|
||||||
|
|
||||||
|
private final String typeNameOrAlias;
|
||||||
|
private @Nullable String message;
|
||||||
|
private @Nullable Class<? extends ViewComponent> type;
|
||||||
|
private @Nullable ComponentTemplate template;
|
||||||
|
private int line;
|
||||||
|
private int column;
|
||||||
|
|
||||||
|
public ComponentResolveException(String typeName) {
|
||||||
|
this.typeNameOrAlias = Objects.requireNonNull(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(String typeName, @Nullable Class<? extends ViewComponent> type) {
|
||||||
|
this.typeNameOrAlias = Objects.requireNonNull(typeName);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(
|
||||||
|
@Nullable String message,
|
||||||
|
String typeName,
|
||||||
|
@Nullable Class<? extends ViewComponent> type
|
||||||
|
) {
|
||||||
|
this.typeNameOrAlias = typeName;
|
||||||
|
this.message = message;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(String typeName, Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
this.typeNameOrAlias = Objects.requireNonNull(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(String typeName, @Nullable Class<? extends ViewComponent> type, Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
this.typeNameOrAlias = Objects.requireNonNull(typeName);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(
|
||||||
|
@NotNull ComponentTemplate template,
|
||||||
|
Throwable cause,
|
||||||
|
String typeName,
|
||||||
|
int line,
|
||||||
|
int column
|
||||||
|
) {
|
||||||
|
super(cause);
|
||||||
|
this.template = template;
|
||||||
|
this.typeNameOrAlias = typeName;
|
||||||
|
this.line = line;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentResolveException(
|
||||||
|
@NotNull ComponentTemplate template,
|
||||||
|
Throwable cause,
|
||||||
|
String alias,
|
||||||
|
@NotNull Class<? extends ViewComponent> type,
|
||||||
|
int line,
|
||||||
|
int column
|
||||||
|
) {
|
||||||
|
super(cause);
|
||||||
|
this.template = Objects.requireNonNull(template);
|
||||||
|
this.typeNameOrAlias = alias;
|
||||||
|
this.type = type;
|
||||||
|
this.line = line;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(@Nullable String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTypeNameOrAlias() {
|
||||||
|
return this.typeNameOrAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Class<? extends ViewComponent> getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setType(@Nullable Class<? extends ViewComponent> type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull ComponentTemplate getTemplate() {
|
||||||
|
return Objects.requireNonNull(this.template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setTemplate(ComponentTemplate template) {
|
||||||
|
this.template = Objects.requireNonNull(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLine() {
|
||||||
|
return this.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setLine(int line) {
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumn() {
|
||||||
|
return this.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setColumn(int column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
final var sb = new StringBuilder("Exception");
|
||||||
|
if (this.template != null) {
|
||||||
|
sb.append(" in ").append(this.template.getClass().getName());
|
||||||
|
}
|
||||||
|
sb.append(" while resolving component ").append(this.typeNameOrAlias);
|
||||||
|
if (this.type != null) {
|
||||||
|
sb.append(" of type ").append(this.type.getName());
|
||||||
|
}
|
||||||
|
sb.append(" at line ").append(this.line).append(", column ").append(this.column).append(".");
|
||||||
|
if (this.message != null) {
|
||||||
|
sb.append(" ").append(this.message);
|
||||||
|
} // else, assume caused by is not null
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,48 +1,52 @@
|
|||||||
package groowt.view.component.context;
|
package groowt.view.component.context;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentFactory;
|
import groowt.view.component.factory.ComponentFactory;
|
||||||
|
|
||||||
public interface ComponentScope {
|
public interface ComponentScope {
|
||||||
|
|
||||||
void add(String name, ComponentFactory<?> factory);
|
record TypeAndFactory<T extends ViewComponent>(Class<? extends T> type, ComponentFactory<? extends T> factory) {}
|
||||||
boolean contains(String name);
|
|
||||||
void remove(String name);
|
|
||||||
ComponentFactory<?> get(String name);
|
|
||||||
|
|
||||||
default ComponentFactory<?> factoryMissing(String typeName) {
|
//---- string types
|
||||||
throw new NoFactoryMissingException(this.getClass().getName() + " does not support factoryMissing()");
|
|
||||||
|
<T extends ViewComponent> void add(String typeName, Class<T> forClass, ComponentFactory<? extends T> factory);
|
||||||
|
|
||||||
|
boolean contains(String typeName);
|
||||||
|
|
||||||
|
void remove(String typeName);
|
||||||
|
|
||||||
|
TypeAndFactory<?> get(String typeName);
|
||||||
|
|
||||||
|
default TypeAndFactory<?> factoryMissing(String typeName) throws ComponentResolveException {
|
||||||
|
throw new FactoryMissingUnsupportedException(
|
||||||
|
this.getClass().getName() + " does not support factoryMissing() for string types.",
|
||||||
|
typeName
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
default <T extends ViewComponent> void add(Class<T> clazz, ComponentFactory<T> factory) {
|
//---- class types
|
||||||
this.add(clazz.getName(), factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean contains(Class<? extends ViewComponent> clazz) {
|
<T extends ViewComponent> void add(Class<T> forClass, ComponentFactory<? extends T> factory);
|
||||||
return this.contains(clazz.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
<T extends ViewComponent> void add(
|
||||||
default <T extends ViewComponent> ComponentFactory<T> get(Class<T> clazz) {
|
Class<T> publicType,
|
||||||
return (ComponentFactory<T>) this.get(clazz.getName());
|
Class<? extends T> implementingType,
|
||||||
}
|
ComponentFactory<? extends T> factory
|
||||||
|
);
|
||||||
|
|
||||||
default void remove(Class<? extends ViewComponent> clazz) {
|
boolean contains(Class<? extends ViewComponent> type);
|
||||||
this.remove(clazz.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
<T extends ViewComponent> TypeAndFactory<T> get(Class<T> type);
|
||||||
default <T extends ViewComponent> ComponentFactory<T> factoryMissing(Class<T> clazz) {
|
|
||||||
return (ComponentFactory<T>) this.factoryMissing(clazz.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
default void add(String name, Closure<? extends ViewComponent> closure) {
|
void remove(Class<? extends ViewComponent> type);
|
||||||
this.add(name, ComponentFactory.ofClosure(closure));
|
|
||||||
}
|
|
||||||
|
|
||||||
default <T extends ViewComponent> void add(Class<T> type, Closure<? extends T> closure) {
|
default <T extends ViewComponent> TypeAndFactory<?> factoryMissing(String typeName, Class<T> type)
|
||||||
this.add(type, ComponentFactory.ofClosure(closure));
|
throws ComponentResolveException {
|
||||||
|
throw new FactoryMissingUnsupportedException(
|
||||||
|
this.getClass().getName() + " does not support factoryMissing() for class component types.",
|
||||||
|
typeName,
|
||||||
|
type
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,98 +1,35 @@
|
|||||||
package groowt.view.component.context;
|
package groowt.view.component.context;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentFactory;
|
import groowt.view.component.runtime.RenderContext;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class DefaultComponentContext implements ComponentContext {
|
public class DefaultComponentContext implements ComponentContext {
|
||||||
|
|
||||||
protected static class DefaultResolved implements ComponentContext.Resolved {
|
private final LinkedList<ComponentScope> scopeStack = new LinkedList<>();
|
||||||
|
private RenderContext renderContext;
|
||||||
|
|
||||||
private final String typeName;
|
@ApiStatus.Internal
|
||||||
private final ComponentFactory<?> factory;
|
public RenderContext getRenderContext() {
|
||||||
|
return Objects.requireNonNull(
|
||||||
public DefaultResolved(String typeName, ComponentFactory<?> factory) {
|
this.renderContext,
|
||||||
this.typeName = typeName;
|
"The renderContext is null. Did this method get called from outside of a rendering context?"
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTypeName() {
|
|
||||||
return this.typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentFactory<?> getComponentFactory() {
|
|
||||||
return this.factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Deque<ComponentScope> scopeStack = new LinkedList<>();
|
|
||||||
private final Deque<ViewComponent> componentStack = new LinkedList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Resolved resolve(String component) {
|
|
||||||
if (scopeStack.isEmpty()) {
|
|
||||||
throw new IllegalStateException("There are no scopes on the scopeStack.");
|
|
||||||
}
|
|
||||||
|
|
||||||
final var getStack = new LinkedList<>(this.scopeStack);
|
|
||||||
while (!getStack.isEmpty()) {
|
|
||||||
final ComponentScope scope = getStack.pop();
|
|
||||||
if (scope.contains(component)) {
|
|
||||||
return new DefaultResolved(component, scope.get(component));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final var missingStack = new LinkedList<>(this.scopeStack);
|
|
||||||
NoFactoryMissingException first = null;
|
|
||||||
while (!missingStack.isEmpty()) {
|
|
||||||
final ComponentScope scope = missingStack.pop();
|
|
||||||
try {
|
|
||||||
return new DefaultResolved(component, scope.factoryMissing(component));
|
|
||||||
} catch (NoFactoryMissingException e) {
|
|
||||||
if (first == null) {
|
|
||||||
first = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first == null) {
|
|
||||||
throw new IllegalStateException("First FactoryMissingException is still null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw first;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ViewComponent create(Resolved resolved, Object... args) {
|
|
||||||
return resolved.getComponentFactory().create(
|
|
||||||
resolved.getTypeName(), this, args
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@ApiStatus.Internal
|
||||||
public void beforeComponentRender(ViewComponent component) {
|
public void setRenderContext(RenderContext renderContext) {
|
||||||
this.componentStack.push(component);
|
this.renderContext = Objects.requireNonNull(renderContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterComponentRender(ViewComponent component) {
|
public List<ComponentScope> getScopeStack() {
|
||||||
final var popped = this.componentStack.pop();
|
|
||||||
if (!popped.equals(component)) {
|
|
||||||
throw new IllegalStateException("Popped component does not equal arg to afterComponent()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Deque<ComponentScope> getScopeStack() {
|
|
||||||
return new LinkedList<>(this.scopeStack);
|
return new LinkedList<>(this.scopeStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,18 +53,15 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deque<ViewComponent> getComponentStack() {
|
public ComponentScope getRootScope() {
|
||||||
return new LinkedList<>(this.componentStack);
|
return this.scopeStack.getLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ViewComponent getParent() {
|
public @Nullable ViewComponent getParent() {
|
||||||
if (this.componentStack.size() > 1) {
|
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||||
final var child = this.componentStack.pop();
|
if (componentStack.size() > 1) {
|
||||||
final var parent = this.componentStack.pop();
|
return componentStack.get(1);
|
||||||
this.componentStack.push(parent);
|
|
||||||
this.componentStack.push(child);
|
|
||||||
return parent;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -139,35 +73,21 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
|
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
|
||||||
if (this.componentStack.size() > 1) {
|
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||||
final Deque<ViewComponent> tmp = new LinkedList<>();
|
if (componentStack.size() > 1) {
|
||||||
tmp.push(this.componentStack.pop()); // child
|
for (final var ancestor : componentStack.subList(1, componentStack.size() -1)) {
|
||||||
ViewComponent result = null;
|
|
||||||
while (result == null && !this.componentStack.isEmpty()) {
|
|
||||||
final var ancestor = this.componentStack.pop();
|
|
||||||
tmp.push(ancestor);
|
|
||||||
if (matching.test(ancestor)) {
|
if (matching.test(ancestor)) {
|
||||||
result = ancestor;
|
return ancestor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (!tmp.isEmpty()) {
|
|
||||||
this.componentStack.push(tmp.pop());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ViewComponent> getAllAncestors() {
|
public List<ViewComponent> getAllAncestors() {
|
||||||
if (this.componentStack.size() > 1) {
|
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||||
final var child = this.componentStack.pop();
|
return componentStack.subList(1, componentStack.size());
|
||||||
final List<ViewComponent> result = new ArrayList<>(this.componentStack);
|
|
||||||
this.componentStack.push(child);
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,65 @@
|
|||||||
package groowt.view.component.context;
|
package groowt.view.component.context;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
import groowt.view.component.factory.ComponentFactory;
|
import groowt.view.component.factory.ComponentFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DefaultComponentScope implements ComponentScope {
|
public class DefaultComponentScope implements ComponentScope {
|
||||||
|
|
||||||
private final Map<String, ComponentFactory<?>> factories = new HashMap<>();
|
private final Map<String, TypeAndFactory<?>> stringFactories = new HashMap<>();
|
||||||
|
private final Map<Class<?>, TypeAndFactory<?>> classFactories = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(String name, ComponentFactory<?> factory) {
|
public <T extends ViewComponent> void add(String typeName, Class<T> forClass, ComponentFactory<? extends T> factory) {
|
||||||
this.factories.put(name, factory);
|
this.stringFactories.put(typeName, new TypeAndFactory<>(forClass, factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String typeName) {
|
||||||
return this.factories.containsKey(name);
|
return this.stringFactories.containsKey(typeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(String name) {
|
public TypeAndFactory<?> get(String typeName) {
|
||||||
this.factories.remove(name);
|
return Objects.requireNonNull(this.stringFactories.get(typeName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentFactory<?> get(String name) {
|
public void remove(String typeName) {
|
||||||
return this.factories.get(name);
|
this.stringFactories.remove(typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ViewComponent> void add(Class<T> forClass, ComponentFactory<? extends T> factory) {
|
||||||
|
this.classFactories.put(forClass, new TypeAndFactory<>(forClass, factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ViewComponent> void add(
|
||||||
|
Class<T> publicType,
|
||||||
|
Class<? extends T> implementingType,
|
||||||
|
ComponentFactory<? extends T> factory
|
||||||
|
) {
|
||||||
|
this.classFactories.put(publicType, new TypeAndFactory<T>(implementingType, factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Class<? extends ViewComponent> type) {
|
||||||
|
return this.classFactories.containsKey(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T extends ViewComponent> TypeAndFactory<T> get(Class<T> type) {
|
||||||
|
return (TypeAndFactory<T>) Objects.requireNonNull(this.classFactories.get(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Class<? extends ViewComponent> type) {
|
||||||
|
this.classFactories.remove(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package groowt.view.component.context;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class FactoryMissingUnsupportedException extends ComponentResolveException {
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public FactoryMissingUnsupportedException(@Nullable String message, String typeName) {
|
||||||
|
super(typeName);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FactoryMissingUnsupportedException(
|
||||||
|
@Nullable String message,
|
||||||
|
String typeName,
|
||||||
|
@Nullable Class<? extends ViewComponent> type
|
||||||
|
) {
|
||||||
|
super(typeName, type);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FactoryMissingUnsupportedException(@Nullable String message, String typeName, Throwable cause) {
|
||||||
|
super(typeName, cause);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FactoryMissingUnsupportedException(
|
||||||
|
@Nullable String message,
|
||||||
|
String typeName,
|
||||||
|
@Nullable Class<? extends ViewComponent> type,
|
||||||
|
Throwable cause
|
||||||
|
) {
|
||||||
|
super(typeName, type, cause);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
if (this.message != null) {
|
||||||
|
return super.getMessage() + " " + this.message;
|
||||||
|
} else {
|
||||||
|
return super.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
|
|
||||||
public class MissingClassTypeException extends MissingComponentException {
|
|
||||||
|
|
||||||
private final String typeName;
|
|
||||||
|
|
||||||
public MissingClassTypeException(ComponentTemplate template, String typeName, int line, int col, Throwable cause) {
|
|
||||||
super(template, cause, line, col);
|
|
||||||
this.typeName = typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getMissingKeyName() {
|
|
||||||
return "class component " + this.typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception which represents that a component type could not be
|
|
||||||
* found by the {@link ComponentContext}.
|
|
||||||
*/
|
|
||||||
public abstract class MissingComponentException extends RuntimeException {
|
|
||||||
|
|
||||||
private final ComponentTemplate template;
|
|
||||||
private final int line;
|
|
||||||
private final int col;
|
|
||||||
|
|
||||||
public MissingComponentException(ComponentTemplate template, Throwable cause, int line, int col) {
|
|
||||||
super(cause);
|
|
||||||
this.template = template;
|
|
||||||
this.line = line;
|
|
||||||
this.col = col;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getMissingKeyName();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return "In " + this.template + " missing " + this.getMissingKeyName()
|
|
||||||
+ " on line " + this.line + ", column " + this.col + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
|
|
||||||
public class MissingFragmentTypeException extends MissingComponentException {
|
|
||||||
|
|
||||||
public MissingFragmentTypeException(ComponentTemplate template, int line, int col, Throwable cause) {
|
|
||||||
super(template, cause, line, col);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getMissingKeyName() {
|
|
||||||
return "fragment type";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
|
|
||||||
public abstract class MissingStringTypeException extends MissingComponentException {
|
|
||||||
|
|
||||||
private final String keyName;
|
|
||||||
|
|
||||||
public MissingStringTypeException(ComponentTemplate template, String keyName, int line, int col, Throwable cause) {
|
|
||||||
super(template, cause, line, col);
|
|
||||||
this.keyName = keyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getMissingKeyName() {
|
|
||||||
return "string-typed component " + this.keyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package groowt.view.component.context;
|
|
||||||
|
|
||||||
public class NoFactoryMissingException extends UnsupportedOperationException {
|
|
||||||
|
|
||||||
public NoFactoryMissingException() {}
|
|
||||||
|
|
||||||
public NoFactoryMissingException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoFactoryMissingException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoFactoryMissingException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,12 +1,13 @@
|
|||||||
package groowt.view.component.factory;
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
|
||||||
import static groowt.view.component.factory.ComponentFactoryUtil.flatten;
|
import static groowt.view.component.factory.ComponentFactoryUtil.flatten;
|
||||||
|
|
||||||
final class ClosureComponentFactory<T extends ViewComponent> implements ComponentFactory<T> {
|
public abstract class AbstractClosureComponentFactory<T extends ViewComponent> extends Closure<T> implements
|
||||||
|
ComponentFactory<T> {
|
||||||
|
|
||||||
private enum Type {
|
private enum Type {
|
||||||
ALL,
|
ALL,
|
||||||
@ -15,18 +16,18 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
|||||||
NONE
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Closure<T> closure;
|
private final Closure<? extends T> closure;
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public AbstractClosureComponentFactory(Closure<? extends T> closure) {
|
||||||
public ClosureComponentFactory(Closure<? extends T> closure) {
|
super(closure.getOwner(), closure.getThisObject());
|
||||||
this.closure = (Closure<T>) closure;
|
this.closure = closure;
|
||||||
final var paramTypes = this.closure.getParameterTypes();
|
final var paramTypes = this.closure.getParameterTypes();
|
||||||
if (paramTypes.length == 0) {
|
if (paramTypes.length == 0) {
|
||||||
this.type = Type.NONE;
|
this.type = Type.NONE;
|
||||||
} else if (paramTypes.length == 1) {
|
} else if (paramTypes.length == 1) {
|
||||||
final var paramType = paramTypes[0];
|
final var paramType = paramTypes[0];
|
||||||
if (paramType == String.class || paramType == Class.class) {
|
if (paramType == String.class) {
|
||||||
this.type = Type.NAME_ONLY;
|
this.type = Type.NAME_ONLY;
|
||||||
} else if (ComponentContext.class.isAssignableFrom(paramType)) {
|
} else if (ComponentContext.class.isAssignableFrom(paramType)) {
|
||||||
this.type = Type.CONTEXT_ONLY;
|
this.type = Type.CONTEXT_ONLY;
|
||||||
@ -36,7 +37,7 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
|||||||
} else {
|
} else {
|
||||||
final var firstParamType = paramTypes[0];
|
final var firstParamType = paramTypes[0];
|
||||||
final var secondParamType = paramTypes[1];
|
final var secondParamType = paramTypes[1];
|
||||||
if (firstParamType == String.class || firstParamType == Class.class) {
|
if (firstParamType == String.class) {
|
||||||
if (ComponentContext.class.isAssignableFrom(secondParamType)) {
|
if (ComponentContext.class.isAssignableFrom(secondParamType)) {
|
||||||
if (paramTypes.length > 2) {
|
if (paramTypes.length > 2) {
|
||||||
this.type = Type.ALL;
|
this.type = Type.ALL;
|
||||||
@ -54,31 +55,17 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private T flatCall(Object... args) {
|
protected T doCall(String typeNameOrAlias, ComponentContext componentContext, Object... args) {
|
||||||
return this.closure.call(flatten(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
private T objTypeCreate(Object type, ComponentContext componentContext, Object... args) {
|
|
||||||
return switch (this.type) {
|
return switch (this.type) {
|
||||||
case ALL -> this.flatCall(type, componentContext, args);
|
case ALL -> this.closure.call(flatten(typeNameOrAlias, componentContext, args));
|
||||||
case NAME_AND_CONTEXT -> this.closure.call(type, componentContext);
|
case NAME_AND_CONTEXT -> this.closure.call(typeNameOrAlias, componentContext);
|
||||||
case NAME_AND_ARGS -> this.flatCall(type, args);
|
case NAME_AND_ARGS -> this.closure.call(flatten(typeNameOrAlias, args));
|
||||||
case CONTEXT_AND_ARGS -> this.flatCall(componentContext, args);
|
case CONTEXT_AND_ARGS -> this.closure.call(flatten(componentContext, args));
|
||||||
case NAME_ONLY -> this.closure.call(type);
|
case NAME_ONLY -> this.closure.call(typeNameOrAlias);
|
||||||
case CONTEXT_ONLY -> this.closure.call(componentContext);
|
case CONTEXT_ONLY -> this.closure.call(componentContext);
|
||||||
case ARGS_ONLY -> this.closure.call(args);
|
case ARGS_ONLY -> this.closure.call(args);
|
||||||
case NONE -> this.closure.call();
|
case NONE -> this.closure.call();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public T create(String type, ComponentContext componentContext, Object... args) {
|
|
||||||
return this.objTypeCreate(type, componentContext, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
|
||||||
return this.objTypeCreate(type, componentContext, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
|
||||||
|
final class ClassTypeClosureComponentFactory<T extends ViewComponent> extends AbstractClosureComponentFactory<T> {
|
||||||
|
|
||||||
|
private final Class<T> forClass;
|
||||||
|
|
||||||
|
public ClassTypeClosureComponentFactory(Class<T> forClass, Closure<? extends T> closure) {
|
||||||
|
super(closure);
|
||||||
|
this.forClass = forClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(String typeName, ComponentContext componentContext, Object... args) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"ClassTypeClosureComponentFactory cannot handle string component types."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
|
if (!this.forClass.isAssignableFrom(type)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"This ClassTypeClosureComponentFactory cannot handle type " + type.getName()
|
||||||
|
+ "; can only handle " + this.forClass.getName() + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.doCall(alias, componentContext, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class ComponentFactories {
|
||||||
|
|
||||||
|
public static <T extends ViewComponent> ComponentFactory<T> ofClosureStringType(Closure<? extends T> closure) {
|
||||||
|
return new StringTypeClosureComponentFactory<>(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends ViewComponent> ComponentFactory<T> ofClosureClassType(
|
||||||
|
Class<T> forClass,
|
||||||
|
Closure<? extends T> closure
|
||||||
|
) {
|
||||||
|
return new ClassTypeClosureComponentFactory<>(forClass, closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends ViewComponent> ComponentFactory<T> ofSupplier(Supplier<? extends T> supplier) {
|
||||||
|
return (typeName, componentContext, args) -> supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ComponentFactories() {}
|
||||||
|
|
||||||
|
}
|
@ -1,25 +1,14 @@
|
|||||||
package groowt.view.component.factory;
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ComponentFactory<T extends ViewComponent> {
|
public interface ComponentFactory<T extends ViewComponent> {
|
||||||
|
|
||||||
static <T extends ViewComponent> ComponentFactory<T> ofClosure(Closure<? extends T> closure) {
|
|
||||||
return new ClosureComponentFactory<>(closure);
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T extends ViewComponent> ComponentFactory<T> ofSupplier(Supplier<T> supplier) {
|
|
||||||
return new SupplierComponentFactory<>(supplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
T create(String typeName, ComponentContext componentContext, Object... args);
|
T create(String typeName, ComponentContext componentContext, Object... args);
|
||||||
|
|
||||||
default T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
default T create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
return this.create(type.getName(), componentContext, args);
|
return this.create(type.getName(), componentContext, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +94,14 @@ public abstract class ComponentFactoryBase<T extends ViewComponent> extends Groo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T create(String type, ComponentContext componentContext, Object... args) {
|
public T create(String typeName, ComponentContext componentContext, Object... args) {
|
||||||
return this.findAndDoCreate(type, componentContext, args);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this needs to be updated.
|
||||||
@Override
|
@Override
|
||||||
public T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
public T create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
return this.findAndDoCreate(type, componentContext, args);
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package groowt.view.component.factory;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public sealed interface ComponentTemplateSource {
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(String template) {
|
|
||||||
return new StringSource(template);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(File templateFile) {
|
|
||||||
return new FileSource(templateFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(URI templateURI) {
|
|
||||||
return new URISource(templateURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(URL url) {
|
|
||||||
return new URLSource(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(InputStream templateInputStream) {
|
|
||||||
return new InputStreamSource(templateInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ComponentTemplateSource of(Reader templateReader) {
|
|
||||||
return new ReaderSource(templateReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param resourceName An <strong>absolute</strong> path resource name.
|
|
||||||
* @return A template source
|
|
||||||
*/
|
|
||||||
static ComponentTemplateSource fromResource(String resourceName) {
|
|
||||||
return of(ComponentTemplateSource.class.getClassLoader().getResource(resourceName));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Reader toReader(ComponentTemplateSource source) {
|
|
||||||
return switch (source) {
|
|
||||||
case FileSource(File file) -> {
|
|
||||||
try {
|
|
||||||
yield new FileReader(file);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case StringSource(String rawSource) -> new StringReader(rawSource);
|
|
||||||
case InputStreamSource(InputStream inputStream) -> new InputStreamReader(inputStream);
|
|
||||||
case URISource(URI uri) -> {
|
|
||||||
try {
|
|
||||||
yield new InputStreamReader(uri.toURL().openStream());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case URLSource(URL url) -> {
|
|
||||||
try {
|
|
||||||
yield new InputStreamReader(url.openStream());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ReaderSource(Reader reader) -> reader;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
record StringSource(String template) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
record FileSource(File templateFile) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
record URISource(URI templateURI) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
record URLSource(URL templateURL) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
record InputStreamSource(InputStream templateInputStream) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
record ReaderSource(Reader templateReader) implements ComponentTemplateSource {}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,25 @@
|
|||||||
|
package groowt.view.component.factory;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
|
||||||
|
final class StringTypeClosureComponentFactory<T extends ViewComponent> extends AbstractClosureComponentFactory<T> {
|
||||||
|
|
||||||
|
public StringTypeClosureComponentFactory(Closure<? extends T> closure) {
|
||||||
|
super(closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(String typeName, ComponentContext componentContext, Object... args) {
|
||||||
|
return this.doCall(typeName, componentContext, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"StringTypeClosureComponentFactory cannot handle class component types."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
package groowt.view.component.factory;
|
|
||||||
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
final class SupplierComponentFactory<T extends ViewComponent> extends ComponentFactoryBase<T> {
|
|
||||||
|
|
||||||
private final Supplier<T> tSupplier;
|
|
||||||
|
|
||||||
public SupplierComponentFactory(Supplier<T> tSupplier) {
|
|
||||||
this.tSupplier = tSupplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T doCreate(Object type, ComponentContext componentContext, Object... args) {
|
|
||||||
return this.tSupplier.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,91 @@
|
|||||||
|
package groowt.view.component.runtime;
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.component.runtime.RenderContext.ResolvedStringType;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception which signals that a component of the given type
|
||||||
|
* could not be created in the given template.
|
||||||
|
*/
|
||||||
|
public class ComponentCreateException extends RuntimeException {
|
||||||
|
|
||||||
|
private final RenderContext.Resolved<?> resolved;
|
||||||
|
|
||||||
|
private ComponentTemplate template;
|
||||||
|
private int line;
|
||||||
|
private int column;
|
||||||
|
|
||||||
|
public ComponentCreateException(
|
||||||
|
RenderContext.Resolved<?> resolved,
|
||||||
|
Throwable cause
|
||||||
|
) {
|
||||||
|
super(cause);
|
||||||
|
this.resolved = resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderContext.Resolved<?> getResolved() {
|
||||||
|
return this.resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentTemplate getTemplate() {
|
||||||
|
return this.template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setTemplate(ComponentTemplate template) {
|
||||||
|
this.template = Objects.requireNonNull(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLine() {
|
||||||
|
return this.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setLine(int line) {
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumn() {
|
||||||
|
return this.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public void setColumn(int column) {
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
final var sb = new StringBuilder("Exception in ")
|
||||||
|
.append(this.template.getClass().getName());
|
||||||
|
if (this.resolved instanceof ResolvedStringType<?> resolvedStringType) {
|
||||||
|
sb.append(" while creating string-typed component ")
|
||||||
|
.append(resolvedStringType.typeName())
|
||||||
|
.append(" (using ")
|
||||||
|
.append(resolvedStringType.resolvedType().getName())
|
||||||
|
.append(") ");
|
||||||
|
} else if (this.resolved instanceof RenderContext.ResolvedClassType<?> resolvedClassType) {
|
||||||
|
sb.append(" while creating class-typed component ")
|
||||||
|
.append(resolvedClassType.alias())
|
||||||
|
.append(" of public type ")
|
||||||
|
.append(resolvedClassType.requestedType())
|
||||||
|
.append(" (using ")
|
||||||
|
.append(resolvedClassType.resolvedType())
|
||||||
|
.append(") ");
|
||||||
|
} else {
|
||||||
|
sb.append(" while creating unknown-typed component (using ")
|
||||||
|
.append(resolved.resolvedType().getName())
|
||||||
|
.append(") ");
|
||||||
|
}
|
||||||
|
return sb.append(" at line ")
|
||||||
|
.append(this.line)
|
||||||
|
.append(", column ")
|
||||||
|
.append(this.column)
|
||||||
|
.append(".")
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package groowt.view.component.runtime;
|
||||||
|
|
||||||
|
import groovy.lang.GString;
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
public interface ComponentWriter {
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
void setRenderContext(RenderContext renderContext);
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
void setComponentContext(ComponentContext componentContext);
|
||||||
|
|
||||||
|
void append(String string);
|
||||||
|
void append(GString gString);
|
||||||
|
void append(GString gString, int line, int column);
|
||||||
|
void append(ViewComponent viewComponent);
|
||||||
|
void append(ViewComponent viewComponent, int line, int column);
|
||||||
|
void append(Object object);
|
||||||
|
|
||||||
|
default void leftShift(Object object) {
|
||||||
|
switch (object) {
|
||||||
|
case String s -> this.append(s);
|
||||||
|
case GString gs -> this.append(gs);
|
||||||
|
case ViewComponent viewComponent -> this.append(viewComponent);
|
||||||
|
default -> this.append(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,20 +1,42 @@
|
|||||||
package groowt.view.web.runtime;
|
package groowt.view.component.runtime;
|
||||||
|
|
||||||
import groovy.lang.GString;
|
import groovy.lang.GString;
|
||||||
import groowt.view.component.ComponentRenderException;
|
import groowt.view.component.ComponentRenderException;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
public class DefaultComponentWriter implements ComponentWriter {
|
||||||
|
|
||||||
private final Writer delegate;
|
private final Writer delegate;
|
||||||
|
private RenderContext renderContext;
|
||||||
|
private ComponentContext componentContext;
|
||||||
|
|
||||||
public DefaultWebViewComponentWriter(Writer delegate) {
|
public DefaultComponentWriter(Writer delegate) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected RenderContext getRenderContext() {
|
||||||
|
return Objects.requireNonNull(this.renderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRenderContext(RenderContext renderContext) {
|
||||||
|
this.renderContext = Objects.requireNonNull(renderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ComponentContext getComponentContext() {
|
||||||
|
return this.componentContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setComponentContext(ComponentContext componentContext) {
|
||||||
|
this.componentContext = Objects.requireNonNull(componentContext);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void append(String string) {
|
public void append(String string) {
|
||||||
try {
|
try {
|
||||||
@ -50,12 +72,22 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doComponentRender(ViewComponent viewComponent) throws IOException {
|
||||||
|
this.getRenderContext().pushComponent(viewComponent);
|
||||||
|
this.getComponentContext().pushDefaultScope();
|
||||||
|
viewComponent.renderTo(this.delegate);
|
||||||
|
this.getComponentContext().popScope();
|
||||||
|
this.getRenderContext().popComponent(viewComponent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void append(ViewComponent viewComponent) {
|
public void append(ViewComponent viewComponent) {
|
||||||
try {
|
try {
|
||||||
viewComponent.renderTo(this.delegate);
|
this.doComponentRender(viewComponent);
|
||||||
} catch (IOException ioException) {
|
} catch (IOException ioException) {
|
||||||
throw new RuntimeException(ioException);
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (ComponentRenderException componentRenderException) {
|
||||||
|
throw componentRenderException;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new ComponentRenderException(viewComponent, exception);
|
throw new ComponentRenderException(viewComponent, exception);
|
||||||
}
|
}
|
||||||
@ -64,9 +96,11 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
|||||||
@Override
|
@Override
|
||||||
public void append(ViewComponent viewComponent, int line, int column) {
|
public void append(ViewComponent viewComponent, int line, int column) {
|
||||||
try {
|
try {
|
||||||
viewComponent.renderTo(this.delegate);
|
this.doComponentRender(viewComponent);
|
||||||
} catch (IOException ioException) {
|
} catch (IOException ioException) {
|
||||||
throw new RuntimeException(ioException);
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (ComponentRenderException componentRenderException) {
|
||||||
|
throw componentRenderException;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new ComponentRenderException(viewComponent, line, column, exception);
|
throw new ComponentRenderException(viewComponent, line, column, exception);
|
||||||
}
|
}
|
||||||
@ -74,20 +108,17 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void append(Object object) {
|
public void append(Object object) {
|
||||||
|
switch (object) {
|
||||||
|
case String s -> this.append(s);
|
||||||
|
case GString gString -> this.append(gString);
|
||||||
|
case ViewComponent viewComponent -> this.append(viewComponent);
|
||||||
|
default -> {
|
||||||
try {
|
try {
|
||||||
this.delegate.append(object.toString());
|
this.delegate.append(object.toString());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void leftShift(Object object) {
|
|
||||||
switch (object) {
|
|
||||||
case String s -> this.append(s);
|
|
||||||
case GString gs -> this.append(gs);
|
|
||||||
case ViewComponent viewComponent -> this.append(viewComponent);
|
|
||||||
default -> this.append(object);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
|||||||
|
package groowt.view.component.runtime;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.ViewComponentBugError;
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
import groowt.view.component.context.ComponentResolveException;
|
||||||
|
import groowt.view.component.context.ComponentScope.TypeAndFactory;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DefaultRenderContext implements RenderContext {
|
||||||
|
|
||||||
|
private final ComponentContext componentContext;
|
||||||
|
private final ComponentWriter writer;
|
||||||
|
private final LinkedList<ViewComponent> componentStack = new LinkedList<>();
|
||||||
|
|
||||||
|
public DefaultRenderContext(ComponentContext componentContext, ComponentWriter writer) {
|
||||||
|
this.componentContext = componentContext;
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentContext getComponentContext() {
|
||||||
|
return this.componentContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resolved<?> resolve(String typeName) throws ComponentResolveException {
|
||||||
|
for (final var scope : this.getComponentContext().getScopeStack()) {
|
||||||
|
if (scope.contains(typeName)) {
|
||||||
|
try {
|
||||||
|
final var typeAndFactory = scope.get(typeName);
|
||||||
|
return new ResolvedStringType<>(typeName, typeAndFactory.type(), typeAndFactory.factory());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ComponentResolveException(typeName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Exception firstException = null;
|
||||||
|
for (final var scope : this.getComponentContext().getScopeStack()) {
|
||||||
|
try {
|
||||||
|
final var typeAndFactory = (TypeAndFactory<?>) scope.factoryMissing(typeName);
|
||||||
|
return new ResolvedStringType<>(typeName, typeAndFactory.type(), typeAndFactory.factory());
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (firstException == null) {
|
||||||
|
firstException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstException != null) {
|
||||||
|
throw new ComponentResolveException(typeName, firstException);
|
||||||
|
} else {
|
||||||
|
throw new ViewComponentBugError(
|
||||||
|
"Could not resolve factory for " + typeName + " and firstException was null."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ViewComponent> Resolved<T> resolve(String alias, Class<T> type) throws ComponentResolveException {
|
||||||
|
for (final var scope : this.getComponentContext().getScopeStack()) {
|
||||||
|
if (scope.contains(type)) {
|
||||||
|
try {
|
||||||
|
final var typeAndFactory = (TypeAndFactory<T>) scope.get(type);
|
||||||
|
return new ResolvedClassType<>(
|
||||||
|
alias,
|
||||||
|
type,
|
||||||
|
typeAndFactory.type(),
|
||||||
|
typeAndFactory.factory()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ComponentResolveException(alias, type, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ComponentResolveException(
|
||||||
|
"Could not find a factory for " + alias + " of type " + type.getName() + " in scope.",
|
||||||
|
alias,
|
||||||
|
type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
public void pushComponent(ViewComponent component) {
|
||||||
|
this.componentStack.push(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popComponent(ViewComponent component) {
|
||||||
|
final var popped = this.componentStack.pop();
|
||||||
|
if (!popped.equals(component)) {
|
||||||
|
throw new ViewComponentBugError(
|
||||||
|
"Popped component != expected component; popped: " + popped + ", expected: " + component + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ViewComponent> getComponentStack() {
|
||||||
|
return new LinkedList<>(this.componentStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentWriter getWriter() {
|
||||||
|
return this.writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package groowt.view.component.runtime;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.context.ComponentResolveException;
|
||||||
|
import groowt.view.component.factory.ComponentFactory;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public interface RenderContext {
|
||||||
|
|
||||||
|
interface Resolved<T extends ViewComponent> {
|
||||||
|
Class<? extends T> resolvedType();
|
||||||
|
ComponentFactory<? extends T> componentFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
record ResolvedStringType<T extends ViewComponent>(
|
||||||
|
String typeName,
|
||||||
|
Class<? extends T> resolvedType,
|
||||||
|
ComponentFactory<? extends T> componentFactory
|
||||||
|
) implements Resolved<T> {}
|
||||||
|
|
||||||
|
record ResolvedClassType<T extends ViewComponent>(
|
||||||
|
String alias,
|
||||||
|
Class<T> requestedType,
|
||||||
|
Class<? extends T> resolvedType,
|
||||||
|
ComponentFactory<? extends T> componentFactory
|
||||||
|
) implements Resolved<T> {}
|
||||||
|
|
||||||
|
Resolved<?> resolve(String typeName) 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 popComponent(ViewComponent component);
|
||||||
|
|
||||||
|
List<ViewComponent> getComponentStack();
|
||||||
|
|
||||||
|
ComponentWriter getWriter();
|
||||||
|
|
||||||
|
}
|
@ -4,6 +4,7 @@ import groowt.gradle.antlr.GroowtAntlrExecTask
|
|||||||
plugins {
|
plugins {
|
||||||
id 'groowt-conventions'
|
id 'groowt-conventions'
|
||||||
id 'groowt-antlr-plugin'
|
id 'groowt-antlr-plugin'
|
||||||
|
id 'groowt-logging'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
id 'groovy'
|
id 'groovy'
|
||||||
id 'org.jetbrains.kotlin.jvm'
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
@ -19,13 +20,23 @@ configurations {
|
|||||||
toolsImplementation {
|
toolsImplementation {
|
||||||
extendsFrom(apiElements, runtimeElements)
|
extendsFrom(apiElements, runtimeElements)
|
||||||
}
|
}
|
||||||
|
sketchingImplementation {
|
||||||
|
extendsFrom(apiElements, runtimeElements)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
sketching {
|
||||||
|
java {
|
||||||
|
compileClasspath += sourceSets.main.output
|
||||||
|
runtimeClasspath += sourceSets.main.output
|
||||||
|
}
|
||||||
|
}
|
||||||
tools {
|
tools {
|
||||||
java {
|
java {
|
||||||
compileClasspath += sourceSets.main.output
|
compileClasspath += sourceSets.main.output
|
||||||
runtimeClasspath += sourceSets.main.output
|
runtimeClasspath += sourceSets.main.output
|
||||||
|
runtimeClasspath += sourceSets.sketching.output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +46,6 @@ dependencies {
|
|||||||
libs.groovy,
|
libs.groovy,
|
||||||
libs.groovy.templates,
|
libs.groovy.templates,
|
||||||
libs.antlr.runtime,
|
libs.antlr.runtime,
|
||||||
libs.classgraph,
|
|
||||||
project(':view-components'),
|
project(':view-components'),
|
||||||
project(':views')
|
project(':views')
|
||||||
)
|
)
|
||||||
@ -64,6 +74,8 @@ dependencies {
|
|||||||
groovyConsole libs.groovy.console
|
groovyConsole libs.groovy.console
|
||||||
toolsApi libs.picocli
|
toolsApi libs.picocli
|
||||||
toolsImplementation libs.groovy.console
|
toolsImplementation libs.groovy.console
|
||||||
|
|
||||||
|
sketchingApi libs.groovy
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
130
web-views/docs/asciidoc/componentTemplateSpec.asciidoc
Normal file
130
web-views/docs/asciidoc/componentTemplateSpec.asciidoc
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
= Component Template Specification
|
||||||
|
|
||||||
|
== Compiled Component Template Code
|
||||||
|
|
||||||
|
The following code represents a typical (transpiled) component template:
|
||||||
|
|
||||||
|
[source, groovy]
|
||||||
|
----
|
||||||
|
package com.jessebrault.website
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentTemplate
|
||||||
|
import groowt.view.component.context.ComponentContext
|
||||||
|
import groowt.view.component.runtime.*
|
||||||
|
import groowt.view.web.WebViewComponent
|
||||||
|
import groowt.view.web.lib.*
|
||||||
|
import groowt.view.web.runtime.*
|
||||||
|
|
||||||
|
class MyComponentTemplate implements ComponentTemplate {
|
||||||
|
|
||||||
|
Closure getRenderer() {
|
||||||
|
return { ComponentContext componentContext, ComponentWriter out ->
|
||||||
|
// <1>
|
||||||
|
final RenderContext renderContext = new DefaultWebViewComponentRenderContext(componentContext, out)
|
||||||
|
componentContext.setRenderContext(renderContext)
|
||||||
|
out.setRenderContext(renderContext)
|
||||||
|
out.setComponentContext(renderContext)
|
||||||
|
|
||||||
|
out.append 'Hello from simple text!' // <2>
|
||||||
|
|
||||||
|
out.append "Hello from GString!" // <3>
|
||||||
|
|
||||||
|
// <4>
|
||||||
|
ComponentContext.Resolved c0Resolved // <5>
|
||||||
|
try {
|
||||||
|
c0Resolved = renderContext.resolve('MySubComponent', MySubComponent) // <6>
|
||||||
|
} catch (ComponentResolveException c0ResolveException) { // <7>
|
||||||
|
c0ResolveException.template = this
|
||||||
|
c0ResolveException.line = 1
|
||||||
|
c0ResolveException.column = 1
|
||||||
|
throw c0ResolveException
|
||||||
|
}
|
||||||
|
|
||||||
|
WebViewComponent c0 // <8>
|
||||||
|
try {
|
||||||
|
c0 = renderContext.create( // <9>
|
||||||
|
c0Resolved,
|
||||||
|
[greeting: 'Hello, World!'],
|
||||||
|
"Some constructor arg",
|
||||||
|
WebViewComponentChildCollectorClosure.get(this) { c0cc ->
|
||||||
|
c0cc.add 'JString child.' // <10>
|
||||||
|
c0cc.add "GString child"
|
||||||
|
|
||||||
|
ComponentContext.Resolved c1Resolved
|
||||||
|
try {
|
||||||
|
c1Resolved = renderContext.resolve('h1') // <11>
|
||||||
|
} catch (ComponentResolveException c1ResolveException) {
|
||||||
|
c1ResolveException.type = IntrinsicHtml // <12>
|
||||||
|
c1ResolveException.template = this
|
||||||
|
c1ResolveException.line = 1
|
||||||
|
c1ResolveException.column = 10
|
||||||
|
throw c1ResolveException
|
||||||
|
}
|
||||||
|
|
||||||
|
WebViewComponent c1
|
||||||
|
try {
|
||||||
|
c1 = renderContext.create(
|
||||||
|
c1_resolved,
|
||||||
|
WebViewComponentChildCollectorClosure.get(this) { c1cc ->
|
||||||
|
c1cc.add "$greeting"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (ComponentCreateException c1CreateException) {
|
||||||
|
c1CreateException.template = this
|
||||||
|
c1CreateException.line = 1
|
||||||
|
c1CreateException.column = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c0cc.add c1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (ComponentCreateException c0CreateException) {
|
||||||
|
c0CreateException.template = this
|
||||||
|
c0CreateException.line = 1
|
||||||
|
c0CreateException.column = 1
|
||||||
|
throw c0CreateException
|
||||||
|
}
|
||||||
|
|
||||||
|
// append it
|
||||||
|
out.append c0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Initialize the contexts.
|
||||||
|
<2> Appending a plain old java string (jstring) to `out`.
|
||||||
|
<3> Appending a GString to `out`.
|
||||||
|
<4> Now begins a component 'block', where a component is resolved, created, and either rendered or appended
|
||||||
|
to a child collector (see below).
|
||||||
|
<5> First, we define the `resolved` variable, of the type `ComponentContext.Resolved`.
|
||||||
|
<6> Resolve it from the context.
|
||||||
|
<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.
|
||||||
|
<8> Now we can start to create the component by first defining a variable for it.
|
||||||
|
<9> The create function takes a few things:
|
||||||
|
. The relevant instance of `ComponentContext.Resolved`.
|
||||||
|
. Any attributes for the component, passed as a `Map`.
|
||||||
|
. Any arguments from the component constructor.
|
||||||
|
. A `WebViewComponentChildCollectorClosure`, which is a 'marker' subclass of `Closure`
|
||||||
|
(so that constructor args ending with a closure don't get confused), which simply collects
|
||||||
|
the children of the component.
|
||||||
|
<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
|
||||||
|
|
||||||
|
The following items all need to have their transpiled Groovy ASTNodes' positions adjusted to match the original source
|
||||||
|
file, in case there is a Groovy compilation error involved.
|
||||||
|
|
||||||
|
* JStrings
|
||||||
|
* GStrings
|
||||||
|
* Component types (Class and String expressions)
|
||||||
|
* Attribute keys and values
|
||||||
|
* Component constructor args
|
4
web-views/sketching/memberClassComponentType.wvc
Normal file
4
web-views/sketching/memberClassComponentType.wvc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
package groowt.view.web.sketching
|
||||||
|
---
|
||||||
|
<Greeters.Simple target='World' />
|
@ -1 +1,11 @@
|
|||||||
|
---
|
||||||
|
class Greeter extends BaseWebViewComponent {
|
||||||
|
String target
|
||||||
|
|
||||||
|
Greeter(Map attr) {
|
||||||
|
super('Hello, $target!')
|
||||||
|
this.target = attr.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
<Greeter target='World' />
|
<Greeter target='World' />
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.AbstractViewComponent
|
||||||
|
import groowt.view.component.ComponentTemplate
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileUnit
|
||||||
|
import groowt.view.component.compiler.source.ComponentTemplateSource
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
abstract class BaseWebViewComponent extends AbstractWebViewComponent {
|
||||||
|
|
||||||
|
BaseWebViewComponent() {}
|
||||||
|
|
||||||
|
BaseWebViewComponent(ComponentTemplate template) {
|
||||||
|
super(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseWebViewComponent(Class<? extends ComponentTemplate> templateClass) {
|
||||||
|
super(templateClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseWebViewComponent(
|
||||||
|
Function<? super Class<? extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
|
||||||
|
) {
|
||||||
|
super(compileUnitFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseWebViewComponent(ComponentTemplateSource source) {
|
||||||
|
super(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience constructor which creates a {@link ComponentTemplateSource}
|
||||||
|
* from the given {@code source} parameter and passes it to super. See
|
||||||
|
* {@link ComponentTemplateSource} for possible types.
|
||||||
|
*
|
||||||
|
* @param source the object passed to {@link ComponentTemplateSource#of}
|
||||||
|
*
|
||||||
|
* @see ComponentTemplateSource
|
||||||
|
*/
|
||||||
|
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||||
|
BaseWebViewComponent(Object source) {
|
||||||
|
super(ComponentTemplateSource.of(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
package groowt.view.web
|
|
||||||
|
|
||||||
import groowt.view.component.AbstractViewComponent
|
|
||||||
import groowt.view.component.ComponentTemplate
|
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler
|
|
||||||
import groowt.view.component.factory.ComponentTemplateSource
|
|
||||||
import groowt.view.web.compiler.DefaultWebViewComponentTemplateCompiler
|
|
||||||
import groowt.view.web.compiler.WebViewComponentTemplateCompiler
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration
|
|
||||||
|
|
||||||
import java.util.function.Function
|
|
||||||
|
|
||||||
abstract class DefaultWebViewComponent extends AbstractWebViewComponent {
|
|
||||||
|
|
||||||
private static final GroovyClassLoader groovyClassLoader =
|
|
||||||
new GroovyClassLoader(DefaultWebViewComponent.classLoader)
|
|
||||||
|
|
||||||
private static final Function<Class, ComponentTemplateCompiler> compilerFunction = { Class givenSelfClass ->
|
|
||||||
new DefaultWebViewComponentTemplateCompiler(
|
|
||||||
groovyClassLoader,
|
|
||||||
CompilerConfiguration.DEFAULT,
|
|
||||||
givenSelfClass.packageName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DefaultWebViewComponent() {}
|
|
||||||
|
|
||||||
protected DefaultWebViewComponent(ComponentTemplate template) {
|
|
||||||
super(template)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DefaultWebViewComponent(Class<? extends ComponentTemplate> templateType) {
|
|
||||||
super(templateType)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DefaultWebViewComponent(ComponentTemplateSource source) {
|
|
||||||
super(source, compilerFunction)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DefaultWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
|
||||||
super(source, compiler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience constructor which creates a {@link ComponentTemplateSource}
|
|
||||||
* from the given {@code source} parameter and passes it to super. See
|
|
||||||
* {@link ComponentTemplateSource} for possible types.
|
|
||||||
*
|
|
||||||
* @param source the object passed to {@link ComponentTemplateSource#of}
|
|
||||||
* @param compiler the compiler to use
|
|
||||||
*
|
|
||||||
* @see ComponentTemplateSource
|
|
||||||
*/
|
|
||||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
|
||||||
protected DefaultWebViewComponent(Object source, WebViewComponentTemplateCompiler compiler) {
|
|
||||||
super(ComponentTemplateSource.of(source), compiler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience constructor which creates a {@link ComponentTemplateSource}
|
|
||||||
* from the given {@code source} parameter and passes it to super. See
|
|
||||||
* {@link ComponentTemplateSource} for possible types.
|
|
||||||
*
|
|
||||||
* @param source the object passed to {@link ComponentTemplateSource#of}
|
|
||||||
*
|
|
||||||
* @see ComponentTemplateSource
|
|
||||||
*/
|
|
||||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
|
||||||
protected DefaultWebViewComponent(Object source) {
|
|
||||||
super(ComponentTemplateSource.of(source), compilerFunction)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final Class<? extends AbstractViewComponent> getSelfClass() {
|
|
||||||
this.class
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,26 +2,16 @@ package groowt.view.web
|
|||||||
|
|
||||||
import groowt.view.component.context.ComponentScope
|
import groowt.view.component.context.ComponentScope
|
||||||
import groowt.view.component.context.DefaultComponentContext
|
import groowt.view.component.context.DefaultComponentContext
|
||||||
import groowt.view.component.ViewComponent
|
|
||||||
import groowt.view.web.lib.Fragment
|
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentChildCollection
|
|
||||||
import org.jetbrains.annotations.ApiStatus
|
|
||||||
|
|
||||||
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
|
class DefaultWebViewComponentContext extends DefaultComponentContext implements WebViewComponentContext {
|
||||||
|
|
||||||
|
DefaultWebViewComponentContext() {
|
||||||
|
this.pushScope(WebViewComponentScope.getDefaultRootScope())
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ComponentScope getNewDefaultScope() {
|
protected ComponentScope getNewDefaultScope() {
|
||||||
new WebViewScope()
|
new WebViewComponentScope()
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@ApiStatus.Internal
|
|
||||||
ViewComponent createFragment(Closure<?> childCollector) {
|
|
||||||
def childCollection = new DefaultWebViewComponentChildCollection()
|
|
||||||
childCollector.call(childCollection)
|
|
||||||
def fragment = new Fragment()
|
|
||||||
fragment.childRenderers = childCollection.children
|
|
||||||
fragment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,42 @@ 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 java.util.function.Function
|
import static groowt.view.component.factory.ComponentFactories.ofClosureClassType
|
||||||
|
|
||||||
final class WebViewComponentFactories {
|
final class WebViewComponentFactories {
|
||||||
|
|
||||||
static <T extends WebViewComponent> ComponentFactory<T> withAttr(
|
static <T extends WebViewComponent> ComponentFactory<T> withAttr(
|
||||||
|
Class<T> forClass,
|
||||||
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
|
@ClosureParams(value = FromString, options = 'java.util.Map<String, Object>')
|
||||||
Closure<T> closure
|
Closure<? extends T> closure
|
||||||
) {
|
) {
|
||||||
ComponentFactory.ofClosure { Map<String, Object> attr -> closure(attr) }
|
ofClosureClassType(forClass) { Map<String, Object> attr -> closure(attr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T extends WebViewComponent> ComponentFactory<T> withAttr(Function<Map<String, Object>, T> tFunction) {
|
static <T extends WebViewComponent> ComponentFactory<T> withChildren(
|
||||||
ComponentFactory.ofClosure { Map<String, Object> attr -> tFunction.apply(attr) }
|
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() {}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
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,14 +0,0 @@
|
|||||||
package groowt.view.web
|
|
||||||
|
|
||||||
import groowt.view.component.factory.ComponentFactory
|
|
||||||
import groowt.view.component.context.DefaultComponentScope
|
|
||||||
import groowt.view.web.lib.Echo
|
|
||||||
|
|
||||||
class WebViewScope extends DefaultComponentScope {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
ComponentFactory factoryMissing(String typeName) {
|
|
||||||
Echo.FACTORY
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +1,12 @@
|
|||||||
package groowt.view.web.lib;
|
package groowt.view.web.lib;
|
||||||
|
|
||||||
import groowt.view.View;
|
import groowt.view.View;
|
||||||
import groowt.view.web.DefaultWebViewComponent;
|
import groowt.view.web.BaseWebViewComponent;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public abstract class DelegatingWebViewComponent extends DefaultWebViewComponent {
|
public abstract class DelegatingWebViewComponent extends BaseWebViewComponent {
|
||||||
|
|
||||||
private final Map<String, Object> attr;
|
|
||||||
|
|
||||||
public DelegatingWebViewComponent(Map<String, Object> attr) {
|
|
||||||
this.attr = attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Map<String, Object> getAttr() {
|
|
||||||
return this.attr;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract View getDelegate();
|
protected abstract View getDelegate();
|
||||||
|
|
||||||
|
@ -3,8 +3,7 @@ package groowt.view.web.lib
|
|||||||
import groowt.view.View
|
import groowt.view.View
|
||||||
import groowt.view.component.context.ComponentContext
|
import groowt.view.component.context.ComponentContext
|
||||||
import groowt.view.component.factory.ComponentFactory
|
import groowt.view.component.factory.ComponentFactory
|
||||||
import groowt.view.component.ComponentRenderException
|
import groowt.view.web.runtime.WebViewComponentChildCollector
|
||||||
import groowt.view.web.WebViewChildComponentRenderer
|
|
||||||
|
|
||||||
class Echo extends DelegatingWebViewComponent {
|
class Echo extends DelegatingWebViewComponent {
|
||||||
|
|
||||||
@ -12,95 +11,50 @@ class Echo extends DelegatingWebViewComponent {
|
|||||||
|
|
||||||
protected static class EchoFactory implements ComponentFactory<Echo> {
|
protected static class EchoFactory implements ComponentFactory<Echo> {
|
||||||
|
|
||||||
Echo doCreate(String typeName) {
|
protected Echo doCreate() {
|
||||||
doCreate(typeName, [:], true)
|
new Echo([:], [])
|
||||||
}
|
}
|
||||||
|
|
||||||
Echo doCreate(String typeName, boolean selfClose) {
|
protected Echo doCreate(Map attr) {
|
||||||
doCreate(typeName, [:], selfClose)
|
new Echo(attr, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
Echo doCreate(String typeName, Map<String, Object> attr) {
|
protected Echo doCreate(WebViewComponentChildCollector childCollector) {
|
||||||
doCreate(typeName, attr, true)
|
new Echo([:], childCollector.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
Echo doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
protected Echo doCreate(Map attr, WebViewComponentChildCollector childCollector) {
|
||||||
new Echo(attr, typeName, selfClose)
|
new Echo(attr, childCollector.children)
|
||||||
}
|
|
||||||
|
|
||||||
Echo doCreate(
|
|
||||||
String typeName,
|
|
||||||
Map<String, Object> attr,
|
|
||||||
List<WebViewChildComponentRenderer> children
|
|
||||||
) {
|
|
||||||
def echo = new Echo(attr, typeName, false)
|
|
||||||
echo.childRenderers = children
|
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Echo create(String type, ComponentContext componentContext, Object... args) {
|
Echo create(String typeName, ComponentContext componentContext, Object... args) {
|
||||||
this.doCreate(type, *args) as Echo
|
throw new UnsupportedOperationException('Cannot create Echo for string type components')
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Echo create(Class<?> type, ComponentContext componentContext, Object... args) {
|
Echo create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
throw new UnsupportedOperationException('<Echo> can only be used with String types.')
|
this.doCreate(*args)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String name
|
Map attr
|
||||||
boolean selfClose
|
|
||||||
|
|
||||||
Echo(Map<String, Object> attr, String name, boolean selfClose) {
|
Echo(Map attr, List children) {
|
||||||
super(attr)
|
this.attr = attr
|
||||||
this.name = name
|
this.children = children
|
||||||
this.selfClose = selfClose
|
}
|
||||||
|
|
||||||
|
Object propertyMissing(String propertyName) {
|
||||||
|
attr[propertyName]
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View getDelegate() {
|
protected View getDelegate() {
|
||||||
if (this.selfClose && this.hasChildren()) {
|
|
||||||
throw new ComponentRenderException('Cannot have selfClose set to true and have children.')
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
it << '<'
|
this.children.each {
|
||||||
it << this.name
|
it.render(this)
|
||||||
if (!this.attr.isEmpty()) {
|
|
||||||
it << ' '
|
|
||||||
formatAttr(it)
|
|
||||||
}
|
|
||||||
if (this.selfClose) {
|
|
||||||
it << ' /'
|
|
||||||
}
|
|
||||||
it << '>'
|
|
||||||
if (this.hasChildren()) {
|
|
||||||
this.renderChildren() // TODO: fix this
|
|
||||||
}
|
|
||||||
if (this.hasChildren() || !this.selfClose) {
|
|
||||||
it << '</'
|
|
||||||
it << this.name
|
|
||||||
it << '>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void formatAttr(Writer writer) {
|
|
||||||
def iter = this.attr.iterator()
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
def entry = iter.next()
|
|
||||||
writer << entry.key
|
|
||||||
def value = entry.value
|
|
||||||
if (value instanceof Boolean) {
|
|
||||||
// no-op, because we already wrote the key
|
|
||||||
} else {
|
|
||||||
writer << '="'
|
|
||||||
writer << value
|
|
||||||
writer << '"'
|
|
||||||
}
|
|
||||||
if (iter.hasNext()) {
|
|
||||||
writer << ' '
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
import groowt.view.web.DefaultWebViewComponent
|
import groowt.view.web.BaseWebViewComponent
|
||||||
|
|
||||||
final class Fragment extends DefaultWebViewComponent {
|
final class Fragment extends BaseWebViewComponent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void renderTo(Writer out) throws IOException {
|
void renderTo(Writer out) throws IOException {
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groowt.view.web.BaseWebViewComponent
|
||||||
|
import groowt.view.web.util.ConfigurableComponent
|
||||||
|
|
||||||
|
class HtmlPage extends BaseWebViewComponent implements ConfigurableComponent<HtmlPage> {}
|
@ -1,16 +1,29 @@
|
|||||||
package groowt.view.web.lib
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groowt.view.View
|
||||||
|
import groowt.view.component.ComponentRenderException
|
||||||
import groowt.view.component.context.ComponentContext
|
import groowt.view.component.context.ComponentContext
|
||||||
|
import groowt.view.component.context.ComponentScope.TypeAndFactory
|
||||||
import groowt.view.component.factory.ComponentFactory
|
import groowt.view.component.factory.ComponentFactory
|
||||||
import groowt.view.web.WebViewChildComponentRenderer
|
import groowt.view.web.WebViewComponentChild
|
||||||
|
import groowt.view.web.util.WithHtml
|
||||||
|
|
||||||
class IntrinsicHtml extends Echo {
|
class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {
|
||||||
|
|
||||||
|
static final ComponentFactory<IntrinsicHtml> FACTORY = new IntrinsicHtmlFactory()
|
||||||
|
static final TypeAndFactory<IntrinsicHtml> TYPE_AND_FACTORY = new TypeAndFactory<>(IntrinsicHtml, FACTORY)
|
||||||
|
|
||||||
|
private static final Set<String> voidElements = Set.of(
|
||||||
|
'area', 'base', 'br', 'col',
|
||||||
|
'embed', 'hr', 'img', 'input',
|
||||||
|
'link', 'meta', 'param', 'source',
|
||||||
|
'track', 'wbr'
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: check type name for HTML 5 validity
|
|
||||||
protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> {
|
protected static class IntrinsicHtmlFactory implements ComponentFactory<IntrinsicHtml> {
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName) {
|
IntrinsicHtml doCreate(String typeName) {
|
||||||
new IntrinsicHtml([:], typeName, false)
|
new IntrinsicHtml([:], typeName, typeName in voidElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName, boolean selfClose) {
|
IntrinsicHtml doCreate(String typeName, boolean selfClose) {
|
||||||
@ -18,16 +31,16 @@ class IntrinsicHtml extends Echo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr) {
|
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr) {
|
||||||
new IntrinsicHtml(attr, typeName, false)
|
new IntrinsicHtml(attr, typeName, typeName in voidElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
||||||
new IntrinsicHtml(attr, typeName, selfClose)
|
new IntrinsicHtml(attr, typeName, selfClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, List<WebViewChildComponentRenderer> children) {
|
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, List<WebViewComponentChild> children) {
|
||||||
def intrinsicHtml = new IntrinsicHtml(attr, typeName, false)
|
def intrinsicHtml = new IntrinsicHtml(attr, typeName, typeName in voidElements)
|
||||||
intrinsicHtml.childRenderers = children
|
intrinsicHtml.children = children
|
||||||
intrinsicHtml
|
intrinsicHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,14 +50,50 @@ class IntrinsicHtml extends Echo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
IntrinsicHtml create(Class<?> type, ComponentContext componentContext, Object... args) {
|
IntrinsicHtml create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||||
throw new UnsupportedOperationException('Cannot create an IntrinsicHtml component with a class type.')
|
throw new UnsupportedOperationException('Cannot create an IntrinsicHtml component with a class type.')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IntrinsicHtml(Map<String, Object> attr, String elementName, boolean selfClose) {
|
Map attr
|
||||||
super(attr, elementName, selfClose)
|
String name
|
||||||
|
boolean selfClose
|
||||||
|
|
||||||
|
IntrinsicHtml(Map attr, String elementName, boolean selfClose) {
|
||||||
|
this.attr = attr
|
||||||
|
this.name = elementName
|
||||||
|
this.selfClose = selfClose
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getDelegate() {
|
||||||
|
if (this.selfClose && this.hasChildren()) {
|
||||||
|
throw new ComponentRenderException('Cannot have selfClose set to true and have children.')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
it << '<'
|
||||||
|
it << this.name
|
||||||
|
if (!this.attr.isEmpty()) {
|
||||||
|
it << ' '
|
||||||
|
this.formatAttr(it)
|
||||||
|
}
|
||||||
|
if (this.selfClose) {
|
||||||
|
it << ' /'
|
||||||
|
}
|
||||||
|
it << '>'
|
||||||
|
if (this.hasChildren()) {
|
||||||
|
this.children.each {
|
||||||
|
def renderer = it.getRenderer(this)
|
||||||
|
renderer.call(it.child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.hasChildren() || !this.selfClose) {
|
||||||
|
it << '</'
|
||||||
|
it << this.name
|
||||||
|
it << '>'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package groowt.view.web.util
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
import groowt.view.web.WebViewComponent
|
||||||
|
import groowt.view.web.WebViewComponentContext
|
||||||
|
|
||||||
|
class ComponentConfigurator {
|
||||||
|
|
||||||
|
private final WebViewComponent self
|
||||||
|
|
||||||
|
ComponentConfigurator(WebViewComponent self) {
|
||||||
|
this.self = self
|
||||||
|
}
|
||||||
|
|
||||||
|
void context(
|
||||||
|
@DelegatesTo(ContextConfigurator)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentContext')
|
||||||
|
Closure configureContext
|
||||||
|
) {
|
||||||
|
//noinspection GroovyAssignabilityCheck
|
||||||
|
WebViewComponentContext context = self.context
|
||||||
|
configureContext.delegate = new ContextConfigurator(context)
|
||||||
|
configureContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package groowt.view.web.util
|
||||||
|
|
||||||
|
import groovy.transform.SelfType
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
import groowt.view.web.WebViewComponent
|
||||||
|
|
||||||
|
@SelfType(WebViewComponent)
|
||||||
|
trait ConfigurableComponent<T extends WebViewComponent> {
|
||||||
|
|
||||||
|
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||||
|
T configure(
|
||||||
|
@DelegatesTo(ComponentConfigurator)
|
||||||
|
@ClosureParams(value = FromString, options = 'T')
|
||||||
|
Closure configure
|
||||||
|
) {
|
||||||
|
configure.delegate = new ComponentConfigurator(this)
|
||||||
|
configure(this)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package groowt.view.web.util
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
import groowt.view.web.WebViewComponentContext
|
||||||
|
import groowt.view.web.WebViewComponentScope
|
||||||
|
|
||||||
|
class ContextConfigurator {
|
||||||
|
|
||||||
|
private final WebViewComponentContext context
|
||||||
|
|
||||||
|
ContextConfigurator(WebViewComponentContext context) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
void rootScope(
|
||||||
|
@DelegatesTo(WebViewComponentScope)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'groowt.view.web.WebViewComponentScope')
|
||||||
|
Closure configureRootScope
|
||||||
|
) {
|
||||||
|
//noinspection GroovyAssignabilityCheck
|
||||||
|
WebViewComponentScope rootScope = context.rootScope
|
||||||
|
configureRootScope.delegate = rootScope
|
||||||
|
configureRootScope(rootScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package groowt.view.web.util
|
||||||
|
|
||||||
|
trait WithHtml {
|
||||||
|
|
||||||
|
abstract Map getAttr()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param writer A {@link java.io.Writer}, a {@link groowt.view.component.runtime.ComponentWriter},
|
||||||
|
* or anything which has {@code leftShift(String | Object)} as a method.
|
||||||
|
*/
|
||||||
|
void formatAttr(writer) {
|
||||||
|
def iter = attr.iterator()
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
def entry = iter.next()
|
||||||
|
writer << entry.key
|
||||||
|
def value = entry.value
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
// no-op, because we already wrote the key
|
||||||
|
} else {
|
||||||
|
writer << '="'
|
||||||
|
writer << value
|
||||||
|
writer << '"'
|
||||||
|
}
|
||||||
|
if (iter.hasNext()) {
|
||||||
|
writer << ' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,11 +3,11 @@ package groowt.view.web;
|
|||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groowt.view.component.AbstractViewComponent;
|
import groowt.view.component.AbstractViewComponent;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||||
import groowt.view.web.compiler.WebViewComponentTemplateCompiler;
|
import groowt.view.component.runtime.ComponentWriter;
|
||||||
import groowt.view.web.runtime.DefaultWebViewComponentWriter;
|
import groowt.view.component.runtime.DefaultComponentWriter;
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
@ -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<WebViewChildRenderer> childRenderers;
|
private List<WebViewComponentChild> childRenderers;
|
||||||
|
|
||||||
public AbstractWebViewComponent() {}
|
public AbstractWebViewComponent() {}
|
||||||
|
|
||||||
@ -29,20 +29,18 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
|||||||
super(templateClass);
|
super(templateClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AbstractWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
public AbstractWebViewComponent(
|
||||||
super(source, compiler);
|
Function<? super Class<? extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
|
||||||
|
) {
|
||||||
|
super(compileUnitFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public AbstractWebViewComponent(ComponentTemplateSource source) {
|
||||||
protected AbstractWebViewComponent(
|
this(selfClass -> new WebViewComponentTemplateCompileUnit(selfClass, source, selfClass.getPackageName()));
|
||||||
ComponentTemplateSource source,
|
|
||||||
Function<? super Class<? extends AbstractWebViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
|
||||||
) {
|
|
||||||
super(source, selfClass -> compilerFunction.apply((Class<AbstractWebViewComponent>) selfClass));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WebViewChildRenderer> getChildRenderers() {
|
public List<WebViewComponentChild> getChildren() {
|
||||||
if (this.childRenderers == null) {
|
if (this.childRenderers == null) {
|
||||||
this.childRenderers = new ArrayList<>();
|
this.childRenderers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
@ -51,35 +49,28 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasChildren() {
|
public boolean hasChildren() {
|
||||||
return !this.getChildRenderers().isEmpty();
|
return !this.getChildren().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
public void setChildren(List<WebViewComponentChild> children) {
|
||||||
this.childRenderers = children;
|
this.childRenderers = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderChildren() {
|
public void renderChildren() {
|
||||||
for (final var childRenderer : this.getChildRenderers()) {
|
for (final var childRenderer : this.getChildren()) {
|
||||||
try {
|
try {
|
||||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
|
||||||
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
|
||||||
}
|
|
||||||
childRenderer.render(this);
|
childRenderer.render(this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ChildRenderException(e);
|
throw new ChildRenderException(e);
|
||||||
} finally {
|
|
||||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
|
||||||
this.getContext().afterComponentRender(childComponentRenderer.getComponent());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderTo(Writer out) throws IOException {
|
public void renderTo(Writer out) throws IOException {
|
||||||
final WebViewComponentWriter webWriter = new DefaultWebViewComponentWriter(out);
|
final ComponentWriter webWriter = new DefaultComponentWriter(out);
|
||||||
final Closure<?> renderer = this.getTemplate().getRenderer();
|
final Closure<?> renderer = this.getTemplate().getRenderer();
|
||||||
renderer.setDelegate(this);
|
renderer.setDelegate(this);
|
||||||
renderer.setResolveStrategy(Closure.DELEGATE_FIRST);
|
renderer.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
public non-sealed class WebViewChildComponentRenderer extends WebViewChildRenderer {
|
|
||||||
|
|
||||||
private final ViewComponent component;
|
|
||||||
|
|
||||||
public WebViewChildComponentRenderer(ViewComponent component, Closure<Void> renderer) {
|
|
||||||
super(renderer);
|
|
||||||
this.component = component;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewComponent getComponent() {
|
|
||||||
return this.component;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groovy.lang.GString;
|
|
||||||
|
|
||||||
public non-sealed class WebViewChildGStringRenderer extends WebViewChildRenderer {
|
|
||||||
|
|
||||||
private final GString gString;
|
|
||||||
|
|
||||||
public WebViewChildGStringRenderer(GString gString, Closure<Void> renderer) {
|
|
||||||
super(renderer);
|
|
||||||
this.gString = gString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GString getGString() {
|
|
||||||
return this.gString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContent() {
|
|
||||||
return this.gString.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
|
|
||||||
public non-sealed class WebViewChildJStringRenderer extends WebViewChildRenderer {
|
|
||||||
|
|
||||||
private final String content;
|
|
||||||
|
|
||||||
public WebViewChildJStringRenderer(String content, Closure<Void> renderer) {
|
|
||||||
super(renderer);
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContent() {
|
|
||||||
return this.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
public sealed abstract class WebViewChildRenderer permits WebViewChildComponentRenderer,
|
|
||||||
WebViewChildGStringRenderer,
|
|
||||||
WebViewChildJStringRenderer {
|
|
||||||
|
|
||||||
private final Closure<Void> renderer;
|
|
||||||
|
|
||||||
public WebViewChildRenderer(Closure<Void> renderer) {
|
|
||||||
this.renderer = renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void render(ViewComponent parent) {
|
|
||||||
this.renderer.setDelegate(parent);
|
|
||||||
this.renderer.setResolveStrategy(Closure.DELEGATE_FIRST);
|
|
||||||
this.renderer.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,24 +1,45 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web;
|
||||||
|
|
||||||
|
import groovy.lang.GString;
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface WebViewComponent extends ViewComponent {
|
public interface WebViewComponent extends ViewComponent {
|
||||||
|
|
||||||
List<WebViewChildRenderer> getChildRenderers();
|
List<WebViewComponentChild> getChildren();
|
||||||
boolean hasChildren();
|
boolean hasChildren();
|
||||||
void setChildRenderers(List<WebViewChildRenderer> children);
|
void setChildren(List<WebViewComponentChild> children);
|
||||||
void renderChildren();
|
void renderChildren();
|
||||||
|
|
||||||
default List<Object> getChildren() {
|
default List<String> getChildStrings() {
|
||||||
return this.getChildRenderers().stream()
|
return this.getChildren().stream()
|
||||||
.map(childRenderer -> switch (childRenderer) {
|
.map(WebViewComponentChild::getChild)
|
||||||
case WebViewChildComponentRenderer componentRenderer -> componentRenderer.getComponent();
|
.filter(obj -> obj instanceof String || obj instanceof GString)
|
||||||
case WebViewChildGStringRenderer gStringRenderer -> gStringRenderer.getGString();
|
.map(obj -> {
|
||||||
case WebViewChildJStringRenderer jStringRenderer -> jStringRenderer.getContent();
|
if (obj instanceof String s) {
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return ((GString) obj).toString();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.toList();
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package groowt.view.web;
|
||||||
|
|
||||||
|
public class WebViewComponentBugError extends RuntimeException {
|
||||||
|
|
||||||
|
public WebViewComponentBugError(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebViewComponentBugError(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebViewComponentBugError(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebViewComponentBugError(
|
||||||
|
String message,
|
||||||
|
Throwable cause,
|
||||||
|
boolean enableSuppression,
|
||||||
|
boolean writableStackTrace
|
||||||
|
) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return "BUG! Please file an issue at the github repository. " + super.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
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 org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class WebViewComponentChild {
|
||||||
|
|
||||||
|
private static final class ChildRenderClosure extends Closure<Void> {
|
||||||
|
|
||||||
|
private final ViewComponent parent;
|
||||||
|
private final @Nullable Object child;
|
||||||
|
|
||||||
|
public ChildRenderClosure(ComponentTemplate template, ViewComponent parent, @Nullable Object child) {
|
||||||
|
super(template, template);
|
||||||
|
this.parent = parent;
|
||||||
|
this.child = child;
|
||||||
|
this.setDelegate(this.parent);
|
||||||
|
this.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewComponent getParent() {
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doCall(ComponentWriter writer) {
|
||||||
|
writer.append(Objects.requireNonNull(this.child));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doCall(ComponentWriter writer, Object givenChild) {
|
||||||
|
writer.append(givenChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ComponentTemplate template;
|
||||||
|
private final ComponentWriter out;
|
||||||
|
private final Object child;
|
||||||
|
|
||||||
|
public WebViewComponentChild(ComponentTemplate template, ComponentWriter out, Object child) {
|
||||||
|
this.template = template;
|
||||||
|
this.out = out;
|
||||||
|
this.child = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getChild() {
|
||||||
|
return this.child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(ViewComponent parent) {
|
||||||
|
final var cl = this.getRenderer(parent);
|
||||||
|
cl.call(this.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Closure<Void> getRenderer(ViewComponent parent) {
|
||||||
|
final var cl = new ChildRenderClosure(this.template, parent, null);
|
||||||
|
return cl.curry(this.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,5 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
import groowt.view.component.context.ComponentContext;
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
|
|
||||||
public interface WebViewComponentContext extends ComponentContext {
|
public interface WebViewComponentContext extends ComponentContext {}
|
||||||
|
|
||||||
/**
|
|
||||||
* For use only by compiled web view component templates.
|
|
||||||
*/
|
|
||||||
@ApiStatus.Internal
|
|
||||||
ViewComponent createFragment(Closure<?> childCollector);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -75,11 +75,11 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pushMode(int m) {
|
public void pushMode(int m) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
final var old = this._mode;
|
final var old = this._mode;
|
||||||
super.pushMode(m);
|
super.pushMode(m);
|
||||||
final var delta = this._mode;
|
final var delta = this._mode;
|
||||||
logger.debug("pushMode: target {} to {}", this.getModeName(old), this.getModeName(delta));
|
logger.trace("pushMode: target {} to {}", this.getModeName(old), this.getModeName(delta));
|
||||||
} else {
|
} else {
|
||||||
super.pushMode(m);
|
super.pushMode(m);
|
||||||
}
|
}
|
||||||
@ -87,10 +87,10 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int popMode() {
|
public int popMode() {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
final var popped = this._mode;
|
final var popped = this._mode;
|
||||||
final var delta = super.popMode();
|
final var delta = super.popMode();
|
||||||
logger.debug("popMode: to {} target {}", this.getModeName(popped), this.getModeName(delta));
|
logger.trace("popMode: to {} target {}", this.getModeName(popped), this.getModeName(delta));
|
||||||
return popped;
|
return popped;
|
||||||
} else {
|
} else {
|
||||||
return super.popMode();
|
return super.popMode();
|
||||||
|
@ -32,6 +32,16 @@ fun shortFormatToken(token: Token): String =
|
|||||||
|
|
||||||
fun formatTokenText(text: String): String = excerptTokenParts(escapeTokenPartsToList(text))
|
fun formatTokenText(text: String): String = excerptTokenParts(escapeTokenPartsToList(text))
|
||||||
|
|
||||||
|
fun formatTokenPosition(token: Token): String {
|
||||||
|
return "line ${token.line}, column ${token.charPositionInLine + 1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun excerptToken(token: Token) = excerptToken(token, 30, 7, "...")
|
||||||
|
|
||||||
|
fun excerptToken(token: Token, startLength: Int = 30, endLength: Int = 7, separator: String = "..."): String {
|
||||||
|
return excerptTokenParts(escapeTokenPartsToList(token.text), startLength, endLength, separator)
|
||||||
|
}
|
||||||
|
|
||||||
fun excerptTokenParts(
|
fun excerptTokenParts(
|
||||||
parts: List<String>,
|
parts: List<String>,
|
||||||
startLength: Int = 30,
|
startLength: Int = 30,
|
||||||
|
@ -25,6 +25,7 @@ public non-sealed class ClassComponentTypeNode extends ComponentTypeNode {
|
|||||||
this.fqn = Objects.requireNonNull(fullyQualifiedName);
|
this.fqn = Objects.requireNonNull(fullyQualifiedName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public String getFullyQualifiedName() {
|
public String getFullyQualifiedName() {
|
||||||
return Objects.requireNonNullElse(this.fqn, this.getIdentifier());
|
return Objects.requireNonNullElse(this.fqn, this.getIdentifier());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.context.ComponentContext;
|
||||||
|
import groowt.view.web.WebViewComponentChild;
|
||||||
|
import groowt.view.web.WebViewComponent;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public final class AnonymousWebViewComponent implements WebViewComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(ComponentContext context) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentContext getContext() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderTo(Writer writer) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<WebViewComponentChild> getChildren() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasChildren() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChildren(List<WebViewComponentChild> children) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderChildren() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,201 +1,114 @@
|
|||||||
package groowt.view.web.compiler;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groowt.view.component.compiler.*;
|
||||||
import groowt.view.component.AbstractViewComponent;
|
import groowt.view.web.WebViewComponentBugError;
|
||||||
import groowt.view.component.ComponentTemplate;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.component.compiler.CachingComponentTemplateCompiler;
|
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
|
||||||
import groowt.view.component.context.ComponentContext;
|
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
|
||||||
import groowt.view.web.analysis.MismatchedComponentTypeAnalysis;
|
import groowt.view.web.analysis.MismatchedComponentTypeAnalysis;
|
||||||
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
||||||
import groowt.view.web.antlr.AntlrUtil;
|
import groowt.view.web.antlr.*;
|
||||||
import groowt.view.web.antlr.CompilationUnitParseResult;
|
|
||||||
import groowt.view.web.antlr.ParserUtil;
|
|
||||||
import groowt.view.web.antlr.TokenList;
|
|
||||||
import groowt.view.web.ast.DefaultAstBuilder;
|
import groowt.view.web.ast.DefaultAstBuilder;
|
||||||
import groowt.view.web.ast.DefaultNodeFactory;
|
import groowt.view.web.ast.DefaultNodeFactory;
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
||||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
|
||||||
import groowt.view.web.util.SourcePosition;
|
|
||||||
import org.antlr.v4.runtime.ParserRuleContext;
|
import org.antlr.v4.runtime.ParserRuleContext;
|
||||||
import org.antlr.v4.runtime.Token;
|
import org.antlr.v4.runtime.Token;
|
||||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
import org.antlr.v4.runtime.tree.Tree;
|
import org.antlr.v4.runtime.tree.Tree;
|
||||||
import org.codehaus.groovy.control.CompilationFailedException;
|
import org.codehaus.groovy.control.CompilationFailedException;
|
||||||
import org.codehaus.groovy.control.CompilationUnit;
|
import org.codehaus.groovy.control.SourceUnit;
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
|
||||||
import org.codehaus.groovy.control.Phases;
|
|
||||||
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
|
||||||
import org.codehaus.groovy.tools.GroovyClass;
|
import org.codehaus.groovy.tools.GroovyClass;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.Writer;
|
import java.util.HashSet;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
public class DefaultWebViewComponentTemplateCompiler
|
||||||
|
extends CachingComponentTemplateCompiler<WebViewComponentTemplateCompileUnit>
|
||||||
implements WebViewComponentTemplateCompiler {
|
implements WebViewComponentTemplateCompiler {
|
||||||
|
|
||||||
protected static final class AnonymousWebViewComponent extends AbstractViewComponent {
|
private final ComponentTemplateCompilerConfiguration configuration;
|
||||||
|
|
||||||
// DO NOT INSTANTIATE, this is merely a marker class
|
public DefaultWebViewComponentTemplateCompiler(ComponentTemplateCompilerConfiguration configuration) {
|
||||||
private AnonymousWebViewComponent() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(ComponentContext context) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentContext getContext() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ComponentTemplate getTemplate() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setTemplate(ComponentTemplate template) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void beforeRender() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void afterRender() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renderTo(Writer out) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Class<? extends AbstractViewComponent> getSelfClass() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final CompilerConfiguration configuration;
|
|
||||||
private final String defaultPackageName;
|
|
||||||
private final int phase;
|
|
||||||
|
|
||||||
public DefaultWebViewComponentTemplateCompiler(
|
|
||||||
GroovyClassLoader groovyClassLoader,
|
|
||||||
CompilerConfiguration configuration,
|
|
||||||
String defaultPackageName
|
|
||||||
) {
|
|
||||||
this(groovyClassLoader, configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public DefaultWebViewComponentTemplateCompiler(
|
|
||||||
GroovyClassLoader groovyClassLoader,
|
|
||||||
CompilerConfiguration configuration,
|
|
||||||
String defaultPackageName,
|
|
||||||
int phase
|
|
||||||
) {
|
|
||||||
super(groovyClassLoader);
|
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.defaultPackageName = defaultPackageName;
|
|
||||||
this.phase = phase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebViewComponentTemplateCompileException getException(
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
TerminalNode terminalNode,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
Class<? extends ViewComponent> forClass,
|
TerminalNode terminalNode
|
||||||
Reader reader
|
|
||||||
) {
|
) {
|
||||||
final Token offending = terminalNode.getSymbol();
|
final Token offending = terminalNode.getSymbol();
|
||||||
return new WebViewComponentTemplateCompileException(
|
final var exception = new WebViewComponentTemplateCompileException(
|
||||||
"Compile error on token at " + SourcePosition.fromStartOfToken(offending).toStringLong() + ".",
|
compileUnit, "Invalid token '" + TokenUtil.excerptToken(offending) + "'."
|
||||||
forClass,
|
|
||||||
reader,
|
|
||||||
offending
|
|
||||||
);
|
);
|
||||||
|
exception.setTerminalNode(terminalNode);
|
||||||
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebViewComponentTemplateCompileException getException(
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
ParserRuleContext parserRuleContext,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
Class<? extends ViewComponent> forClass,
|
ParserRuleContext parserRuleContext
|
||||||
Reader reader
|
|
||||||
) {
|
) {
|
||||||
return new WebViewComponentTemplateCompileException(
|
final var exception = new WebViewComponentTemplateCompileException(
|
||||||
"Compile error at " + SourcePosition.fromStartOfToken(parserRuleContext.getStart()).toStringLong()
|
compileUnit,
|
||||||
+ ".",
|
"Parser error: " + parserRuleContext.exception.getMessage(),
|
||||||
forClass,
|
parserRuleContext.exception
|
||||||
reader,
|
|
||||||
parserRuleContext
|
|
||||||
);
|
);
|
||||||
|
exception.setParserRuleContext(parserRuleContext);
|
||||||
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebViewComponentTemplateCompileException mapToErrorException(
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
Tree tree,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
Class<? extends ViewComponent> forClass,
|
Tree tree
|
||||||
Reader reader
|
|
||||||
) {
|
) {
|
||||||
if (tree instanceof TerminalNode terminalNode) {
|
if (tree instanceof ParserRuleContext parserRuleContext) {
|
||||||
return getException(terminalNode, forClass, reader);
|
return getException(compileUnit, parserRuleContext);
|
||||||
} else if (tree instanceof ParserRuleContext parserRuleContext) {
|
} else if (tree instanceof TerminalNode terminalNode) {
|
||||||
return getException(parserRuleContext, forClass, reader);
|
return getException(compileUnit, terminalNode);
|
||||||
} else {
|
} else {
|
||||||
return new WebViewComponentTemplateCompileException(
|
return new WebViewComponentTemplateCompileException(
|
||||||
"Compile error with " + tree + ".",
|
compileUnit,
|
||||||
forClass,
|
"Error at parser/lexer node " + tree.toString()
|
||||||
reader,
|
|
||||||
tree
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebViewComponentTemplateCompileException mapToErrorException(
|
protected WebViewComponentTemplateCompileException getException(
|
||||||
MismatchedComponentTypeError error,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
Class<? extends ViewComponent> forClass,
|
MismatchedComponentTypeError error
|
||||||
Reader reader
|
|
||||||
) {
|
) {
|
||||||
return new WebViewComponentTemplateCompileException(
|
final var exception = new WebViewComponentTemplateCompileException(
|
||||||
error.getMessage(),
|
compileUnit,
|
||||||
forClass,
|
error.getMessage()
|
||||||
reader,
|
|
||||||
error.getComponent()
|
|
||||||
);
|
);
|
||||||
|
exception.setParserRuleContext(error.getComponent());
|
||||||
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComponentTemplateCompileResult doCompile(
|
@Override
|
||||||
Class<? extends ViewComponent> forClass,
|
protected ComponentTemplateCompileResult doCompile(WebViewComponentTemplateCompileUnit compileUnit)
|
||||||
Reader reader,
|
throws ComponentTemplateCompileException {
|
||||||
@Nullable URI uri
|
|
||||||
) throws ComponentTemplateCompileErrorException {
|
final Reader sourceReader;
|
||||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
try {
|
||||||
|
sourceReader = compileUnit.getSource().toReader();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(sourceReader);
|
||||||
|
|
||||||
// check for parser/lexer errors
|
// check for parser/lexer errors
|
||||||
final var parseErrors = AntlrUtil.findErrorNodes(parseResult.getCompilationUnitContext());
|
final var parseErrors = AntlrUtil.findErrorNodes(parseResult.getCompilationUnitContext());
|
||||||
if (!parseErrors.isEmpty()) {
|
if (!parseErrors.isEmpty()) {
|
||||||
if (parseErrors.getErrorCount() == 1) {
|
if (parseErrors.getErrorCount() == 1) {
|
||||||
final var errorNode = parseErrors.getAll().getFirst();
|
final var errorNode = parseErrors.getAll().getFirst();
|
||||||
throw mapToErrorException(errorNode, forClass, reader);
|
throw getException(compileUnit, errorNode);
|
||||||
} else {
|
} else {
|
||||||
final var errorExceptions = parseErrors.getAll().stream()
|
final var errorExceptions = parseErrors.getAll().stream()
|
||||||
.map(errorNode -> mapToErrorException(errorNode, forClass, reader))
|
.map(errorNode -> getException(compileUnit, errorNode))
|
||||||
.toList();
|
.toList();
|
||||||
throw new MultipleWebViewComponentCompileErrorsException(errorExceptions, forClass, reader);
|
throw new MultipleWebViewComponentCompileErrorsException(compileUnit, errorExceptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,16 +118,12 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
|||||||
|
|
||||||
if (!mismatchedComponentTypeErrors.isEmpty()) {
|
if (!mismatchedComponentTypeErrors.isEmpty()) {
|
||||||
if (mismatchedComponentTypeErrors.size() == 1) {
|
if (mismatchedComponentTypeErrors.size() == 1) {
|
||||||
throw mapToErrorException(mismatchedComponentTypeErrors.getFirst(), forClass, reader);
|
throw getException(compileUnit, mismatchedComponentTypeErrors.getFirst());
|
||||||
} else {
|
} else {
|
||||||
final var errorExceptions = mismatchedComponentTypeErrors.stream()
|
final var errorExceptions = mismatchedComponentTypeErrors.stream()
|
||||||
.map(error -> mapToErrorException(error, forClass, reader))
|
.map(error -> getException(compileUnit, error))
|
||||||
.toList();
|
.toList();
|
||||||
throw new MultipleWebViewComponentCompileErrorsException(
|
throw new MultipleWebViewComponentCompileErrorsException(compileUnit, errorExceptions);
|
||||||
errorExceptions,
|
|
||||||
forClass,
|
|
||||||
reader
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,63 +133,39 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
|||||||
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
||||||
|
|
||||||
// transpile to Groovy
|
// transpile to Groovy
|
||||||
final var groovyCompilationUnit = new CompilationUnit(this.configuration);
|
final var transpiler = new DefaultGroovyTranspiler();
|
||||||
final var transpiler = new DefaultGroovyTranspiler(
|
|
||||||
groovyCompilationUnit,
|
final var ownerComponentName = compileUnit.getForClass() != AnonymousWebViewComponent.class
|
||||||
this.defaultPackageName,
|
? compileUnit.getForClass().getSimpleName()
|
||||||
DefaultTranspilerConfiguration::new
|
: "AnonymousWebViewComponent" + System.nanoTime();
|
||||||
|
final var templateClassSimpleName = ownerComponentName + "Template";
|
||||||
|
|
||||||
|
final SourceUnit sourceUnit = transpiler.transpile(
|
||||||
|
this.configuration,
|
||||||
|
compileUnit,
|
||||||
|
cuNode,
|
||||||
|
templateClassSimpleName
|
||||||
);
|
);
|
||||||
|
compileUnit.getGroovyCompilationUnit().addSource(sourceUnit);
|
||||||
|
|
||||||
final var ownerComponentName = forClass != null ? forClass.getSimpleName() : "AnonymousComponent" + System.nanoTime();
|
// compile groovy
|
||||||
final var templateClassName = ownerComponentName + "Template";
|
|
||||||
final var fqn = this.defaultPackageName + "." + templateClassName;
|
|
||||||
|
|
||||||
final var readerSource = new AbstractReaderSource(this.configuration) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Reader getReader() throws IOException {
|
|
||||||
reader.reset();
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable URI getURI() {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup() {
|
|
||||||
super.cleanup();
|
|
||||||
try {
|
try {
|
||||||
reader.close();
|
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getToCompilePhase().getPhaseNumber());
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
transpiler.transpile(cuNode, tokenList, ownerComponentName, readerSource);
|
|
||||||
|
|
||||||
try {
|
|
||||||
groovyCompilationUnit.compile(this.phase);
|
|
||||||
} catch (CompilationFailedException compilationFailedException) {
|
} catch (CompilationFailedException compilationFailedException) {
|
||||||
throw new WebViewComponentTemplateCompileException(
|
throw new WebViewComponentTemplateCompileException(
|
||||||
"Error while compiling Groovy in " + templateClassName + " for component class " +
|
compileUnit,
|
||||||
forClass.getName() + ".",
|
"Error while compiling Groovy.",
|
||||||
compilationFailedException,
|
compilationFailedException
|
||||||
forClass,
|
|
||||||
forClass,
|
|
||||||
reader
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the classes
|
// get the classes
|
||||||
final var allClasses = groovyCompilationUnit.getClasses();
|
final var allClasses = compileUnit.getGroovyCompilationUnit().getClasses();
|
||||||
GroovyClass templateGroovyClass = null;
|
GroovyClass templateGroovyClass = null;
|
||||||
final List<GroovyClass> otherClasses = new ArrayList<>();
|
final Set<GroovyClass> otherClasses = new HashSet<>();
|
||||||
|
final String templateClassFqn = sourceUnit.getAST().getPackageName() + "." + templateClassSimpleName;
|
||||||
for (final GroovyClass groovyClass : allClasses) {
|
for (final GroovyClass groovyClass : allClasses) {
|
||||||
if (groovyClass.getName().equals(fqn)) {
|
if (groovyClass.getName().equals(templateClassFqn)) {
|
||||||
if (templateGroovyClass != null) {
|
if (templateGroovyClass != null) {
|
||||||
throw new IllegalStateException("Already found a templateGroovyClass.");
|
throw new IllegalStateException("Already found a templateGroovyClass.");
|
||||||
}
|
}
|
||||||
@ -291,40 +176,13 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (templateGroovyClass == null) {
|
if (templateGroovyClass == null) {
|
||||||
throw new IllegalStateException("Did not find templateClass");
|
throw new WebViewComponentBugError(new IllegalStateException("Did not find templateClass"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ComponentTemplateCompileResult(templateGroovyClass, otherClasses);
|
return new SimpleComponentTemplateCompileResult(
|
||||||
}
|
templateGroovyClass,
|
||||||
|
otherClasses
|
||||||
@Override
|
);
|
||||||
protected ComponentTemplateCompileResult doCompile(
|
|
||||||
@Nullable ComponentTemplateSource source,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Reader sourceReader
|
|
||||||
) throws ComponentTemplateCompileErrorException {
|
|
||||||
if (source instanceof ComponentTemplateSource.URISource uriSource) {
|
|
||||||
return this.doCompile(forClass, sourceReader, uriSource.templateURI());
|
|
||||||
} else if (source instanceof ComponentTemplateSource.URLSource urlSource) {
|
|
||||||
try {
|
|
||||||
return this.doCompile(forClass, sourceReader, urlSource.templateURL().toURI());
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.doCompile(forClass, sourceReader, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source)
|
|
||||||
throws ComponentTemplateCompileErrorException {
|
|
||||||
return this.compile(AnonymousWebViewComponent.class, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComponentTemplate compileAndGetAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException {
|
|
||||||
return this.compileAndGet(AnonymousWebViewComponent.class, source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,22 @@
|
|||||||
package groowt.view.web.compiler;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MultipleWebViewComponentCompileErrorsException extends ComponentTemplateCompileErrorException {
|
public class MultipleWebViewComponentCompileErrorsException extends ComponentTemplateCompileException {
|
||||||
|
|
||||||
private final List<Throwable> errors = new ArrayList<>();
|
private final List<Throwable> errors = new ArrayList<>();
|
||||||
|
|
||||||
public MultipleWebViewComponentCompileErrorsException(
|
public MultipleWebViewComponentCompileErrorsException(
|
||||||
String message,
|
ComponentTemplateCompileUnit compileUnit,
|
||||||
List<? extends Throwable> errors,
|
List<? extends Throwable> errors
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
) {
|
||||||
super(message, forClass, templateSource);
|
super(compileUnit, "There were multiple errors during compilation.");
|
||||||
this.errors.addAll(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultipleWebViewComponentCompileErrorsException(
|
|
||||||
List<? extends Throwable> errors,
|
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource
|
|
||||||
) {
|
|
||||||
super(forClass, templateSource);
|
|
||||||
this.errors.addAll(errors);
|
this.errors.addAll(errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,4 +24,17 @@ public class MultipleWebViewComponentCompileErrorsException extends ComponentTem
|
|||||||
return this.errors;
|
return this.errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
final var sw = new StringWriter();
|
||||||
|
sw.append(super.getMessage()).append("\n\n");
|
||||||
|
for (int i = 0; i < this.errors.size(); i++) {
|
||||||
|
final var error = this.errors.get(i);
|
||||||
|
sw.append(String.format("Error no. %d:\n", i + 1));
|
||||||
|
error.printStackTrace(new PrintWriter(sw));
|
||||||
|
sw.append("\n");
|
||||||
|
}
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,75 @@
|
|||||||
package groowt.view.web.compiler;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||||
|
import groowt.view.web.antlr.TokenUtil;
|
||||||
import groowt.view.web.ast.node.Node;
|
import groowt.view.web.ast.node.Node;
|
||||||
import groowt.view.web.util.SourcePosition;
|
import org.antlr.v4.runtime.ParserRuleContext;
|
||||||
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileErrorException {
|
public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileException {
|
||||||
|
|
||||||
private final Object node;
|
private @Nullable TerminalNode terminalNode;
|
||||||
|
private @Nullable ParserRuleContext parserRuleContext;
|
||||||
|
private @Nullable Node node;
|
||||||
|
|
||||||
public WebViewComponentTemplateCompileException(
|
public WebViewComponentTemplateCompileException(
|
||||||
|
ComponentTemplateCompileUnit compileUnit,
|
||||||
|
String message
|
||||||
|
) {
|
||||||
|
super(compileUnit, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebViewComponentTemplateCompileException(
|
||||||
|
ComponentTemplateCompileUnit compileUnit,
|
||||||
String message,
|
String message,
|
||||||
Class<? extends ViewComponent> forClass,
|
Throwable cause
|
||||||
Object templateSource,
|
|
||||||
Object node
|
|
||||||
) {
|
) {
|
||||||
super(message, forClass, templateSource);
|
super(compileUnit, message, cause);
|
||||||
this.node = node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebViewComponentTemplateCompileException(
|
public WebViewComponentTemplateCompileException(
|
||||||
String message,
|
ComponentTemplateCompileUnit compileUnit,
|
||||||
Throwable cause,
|
Throwable cause
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource,
|
|
||||||
Object node
|
|
||||||
) {
|
) {
|
||||||
super(message, cause, forClass, templateSource);
|
super(compileUnit, "There was an error during compilation.", cause);
|
||||||
this.node = node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebViewComponentTemplateCompileException(
|
public @Nullable TerminalNode getTerminalNode() {
|
||||||
Throwable cause,
|
return this.terminalNode;
|
||||||
Class<? extends ViewComponent> forClass,
|
|
||||||
Object templateSource,
|
|
||||||
Object node
|
|
||||||
) {
|
|
||||||
super(cause, forClass, templateSource);
|
|
||||||
this.node = node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getNode() {
|
public void setTerminalNode(@Nullable TerminalNode terminalNode) {
|
||||||
|
this.terminalNode = terminalNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ParserRuleContext getParserRuleContext() {
|
||||||
|
return this.parserRuleContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParserRuleContext(@Nullable ParserRuleContext parserRuleContext) {
|
||||||
|
this.parserRuleContext = parserRuleContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Node getNode() {
|
||||||
return this.node;
|
return this.node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNode(@Nullable Node node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
protected @Nullable String getPosition() {
|
||||||
if (this.node instanceof Node asNode) {
|
if (this.node != null) {
|
||||||
final SourcePosition start = asNode.getTokenRange().getStartPosition();
|
return this.node.getTokenRange().getStartPosition().toStringLong();
|
||||||
return "At " + start.toStringLong() + ": " + super.getMessage();
|
} else if (this.parserRuleContext != null) {
|
||||||
|
return TokenUtil.formatTokenPosition(this.parserRuleContext.start);
|
||||||
|
} else if (this.terminalNode != null) {
|
||||||
|
return TokenUtil.formatTokenPosition(terminalNode.getSymbol());
|
||||||
} else {
|
} else {
|
||||||
return super.getMessage();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.compiler.AbstractComponentTemplateCompileUnit;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileResult;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
|
||||||
|
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||||
|
import groowt.view.component.compiler.source.FileSource;
|
||||||
|
import groowt.view.component.compiler.source.URISource;
|
||||||
|
import groowt.view.component.compiler.source.URLSource;
|
||||||
|
import org.codehaus.groovy.control.CompilationUnit;
|
||||||
|
import org.codehaus.groovy.control.Janitor;
|
||||||
|
import org.codehaus.groovy.control.io.ReaderSource;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
public class WebViewComponentTemplateCompileUnit extends AbstractComponentTemplateCompileUnit implements ReaderSource {
|
||||||
|
|
||||||
|
private final String defaultPackageName;
|
||||||
|
private final CompilationUnit groovyCompilationUnit = new CompilationUnit();
|
||||||
|
|
||||||
|
public WebViewComponentTemplateCompileUnit(
|
||||||
|
Class<? extends ViewComponent> forClass,
|
||||||
|
ComponentTemplateSource source,
|
||||||
|
String defaultPackageName
|
||||||
|
) {
|
||||||
|
super(forClass, source);
|
||||||
|
this.defaultPackageName = defaultPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultPackageName() {
|
||||||
|
return this.defaultPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompilationUnit getGroovyCompilationUnit() {
|
||||||
|
return this.groovyCompilationUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
|
||||||
|
throws ComponentTemplateCompileException {
|
||||||
|
final var compiler = new DefaultWebViewComponentTemplateCompiler(configuration);
|
||||||
|
return compiler.compile(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getReader() {
|
||||||
|
try {
|
||||||
|
return this.getSource().toReader();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReopenSource() {
|
||||||
|
return this.getSource().canReopen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getLine(int lineNumber, Janitor janitor) {
|
||||||
|
if (this.getSource().canReopen()) {
|
||||||
|
return this.getSource().getLines().get(lineNumber);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable URI getURI() {
|
||||||
|
return switch (this.getSource()) {
|
||||||
|
case FileSource fileSource -> fileSource.getURI();
|
||||||
|
case URISource uriSource -> uriSource.getURI();
|
||||||
|
case URLSource urlSource -> urlSource.getURI();
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,20 @@
|
|||||||
package groowt.view.web.compiler;
|
package groowt.view.web.compiler;
|
||||||
|
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
import groowt.view.component.compiler.ComponentTemplateCompileResult;
|
||||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||||
import groowt.view.component.factory.ComponentTemplateSource;
|
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||||
|
|
||||||
|
public interface WebViewComponentTemplateCompiler
|
||||||
|
extends ComponentTemplateCompiler<WebViewComponentTemplateCompileUnit> {
|
||||||
|
|
||||||
|
default ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source, String packageName)
|
||||||
|
throws ComponentTemplateCompileException {
|
||||||
|
return this.compile(new WebViewComponentTemplateCompileUnit(
|
||||||
|
AnonymousWebViewComponent.class,
|
||||||
|
source,
|
||||||
|
packageName
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public interface WebViewComponentTemplateCompiler extends ComponentTemplateCompiler {
|
|
||||||
ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException;
|
|
||||||
ComponentTemplate compileAndGetAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException;
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.web.WebViewChildComponentRenderer;
|
|
||||||
import groowt.view.web.WebViewChildGStringRenderer;
|
|
||||||
import groowt.view.web.WebViewChildJStringRenderer;
|
|
||||||
import groowt.view.web.WebViewChildRenderer;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DefaultWebViewComponentChildCollection implements WebViewComponentChildCollection {
|
|
||||||
|
|
||||||
private final List<WebViewChildRenderer> children = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(String jString, Closure<Void> renderer) {
|
|
||||||
this.children.add(new WebViewChildJStringRenderer(jString, renderer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(GString gString, Closure<Void> renderer) {
|
|
||||||
this.children.add(new WebViewChildGStringRenderer(gString, renderer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(ViewComponent component, Closure<Void> renderer) {
|
|
||||||
this.children.add(new WebViewChildComponentRenderer(component, renderer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<WebViewChildRenderer> getChildren() {
|
|
||||||
return this.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
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 var childCollector = new DefaultWebViewComponentChildCollector(
|
||||||
|
cl.getTemplate(),
|
||||||
|
this.getWriter()
|
||||||
|
);
|
||||||
|
args[args.length - 1] = childCollector;
|
||||||
|
cl.call(childCollector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
import groowt.view.web.WebViewChildRenderer;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface WebViewComponentChildCollection {
|
|
||||||
|
|
||||||
void add(String jString, Closure<Void> renderer);
|
|
||||||
|
|
||||||
void add(GString gString, Closure<Void> renderer);
|
|
||||||
|
|
||||||
void add(ViewComponent component, Closure<Void> renderer);
|
|
||||||
|
|
||||||
List<WebViewChildRenderer> getChildren();
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
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();
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package groowt.view.web.runtime;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
import groowt.view.component.runtime.RenderContext;
|
||||||
|
import groowt.view.web.WebViewComponent;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface WebViewComponentRenderContext extends RenderContext {
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
ViewComponent createFragment(WebViewComponent fragment, WebViewComponentChildCollectorClosure cl);
|
||||||
|
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package groowt.view.web.runtime;
|
|
||||||
|
|
||||||
import groovy.lang.GString;
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
public interface WebViewComponentWriter {
|
|
||||||
void append(String string);
|
|
||||||
void append(GString gString);
|
|
||||||
void append(GString gString, int line, int column);
|
|
||||||
void append(ViewComponent viewComponent);
|
|
||||||
void append(ViewComponent viewComponent, int line, int column);
|
|
||||||
void append(Object object);
|
|
||||||
void leftShift(Object object);
|
|
||||||
}
|
|
@ -17,4 +17,8 @@ public interface AppendOrAddStatementFactory {
|
|||||||
Statement appendOnly(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) {
|
||||||
|
return this.addOrAppend(sourceNode, state, ignored -> rightSide);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,12 @@ package groowt.view.web.transpile;
|
|||||||
import groowt.view.web.ast.node.ComponentNode;
|
import groowt.view.web.ast.node.ComponentNode;
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ComponentTranspiler {
|
public interface ComponentTranspiler {
|
||||||
BlockStatement createComponentStatements(
|
List<Statement> createComponentStatements(
|
||||||
ComponentNode componentNode,
|
ComponentNode componentNode,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
);
|
);
|
||||||
|
@ -63,7 +63,7 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
|
|||||||
return this.doCreate(
|
return this.doCreate(
|
||||||
bodyChildNode,
|
bodyChildNode,
|
||||||
rightSide,
|
rightSide,
|
||||||
new VariableExpression(state.out()),
|
new VariableExpression(state.getWriter()),
|
||||||
TranspilerUtil.APPEND,
|
TranspilerUtil.APPEND,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
|
import groowt.view.web.WebViewComponentBugError;
|
||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@ -30,7 +31,7 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
|||||||
TranspilerState state
|
TranspilerState state
|
||||||
) {
|
) {
|
||||||
final BlockStatement block = new BlockStatement();
|
final BlockStatement block = new BlockStatement();
|
||||||
block.setVariableScope(state.currentScope());
|
block.setVariableScope(state.pushScope());
|
||||||
for (final Node child : bodyNode.getChildren()) {
|
for (final Node child : bodyNode.getChildren()) {
|
||||||
switch (child) {
|
switch (child) {
|
||||||
case GStringBodyTextNode gStringBodyTextNode -> {
|
case GStringBodyTextNode gStringBodyTextNode -> {
|
||||||
@ -49,16 +50,17 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
|||||||
}
|
}
|
||||||
case ComponentNode componentNode -> {
|
case ComponentNode componentNode -> {
|
||||||
// DO NOT add/append this, because the component transpiler does it already
|
// DO NOT add/append this, because the component transpiler does it already
|
||||||
block.addStatement(this.componentTranspiler.createComponentStatements(componentNode, state));
|
block.addStatements(this.componentTranspiler.createComponentStatements(componentNode, state));
|
||||||
}
|
}
|
||||||
case PlainScriptletNode plainScriptletNode -> {
|
case PlainScriptletNode plainScriptletNode -> {
|
||||||
throw new UnsupportedOperationException("TODO");
|
throw new UnsupportedOperationException("TODO");
|
||||||
}
|
}
|
||||||
default -> throw new UnsupportedOperationException(
|
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
|
||||||
"BodyNode child of type " + child.getClass().getSimpleName() + " is not supported."
|
"BodyNode child of type " + child.getClass().getSimpleName() + " is not supported."
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
state.popScope();
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,106 +1,283 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
import groovy.lang.Tuple2;
|
import groowt.view.component.context.ComponentResolveException;
|
||||||
import groowt.view.component.*;
|
import groowt.view.component.runtime.ComponentCreateException;
|
||||||
import groowt.view.component.context.*;
|
import groowt.view.component.runtime.RenderContext;
|
||||||
|
import groowt.view.web.WebViewComponentBugError;
|
||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollection;
|
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;
|
||||||
|
import groowt.view.web.util.Provider;
|
||||||
|
import groowt.view.web.util.SourcePosition;
|
||||||
import org.codehaus.groovy.ast.*;
|
import org.codehaus.groovy.ast.*;
|
||||||
import org.codehaus.groovy.ast.expr.*;
|
import org.codehaus.groovy.ast.expr.*;
|
||||||
import org.codehaus.groovy.ast.stmt.*;
|
import org.codehaus.groovy.ast.stmt.*;
|
||||||
import org.codehaus.groovy.syntax.Token;
|
|
||||||
import org.codehaus.groovy.syntax.Types;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||||
|
|
||||||
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||||
|
|
||||||
private static final ClassNode VIEW_COMPONENT = ClassHelper.make(ViewComponent.class);
|
private static final ClassNode CHILD_COLLECTOR_TYPE = ClassHelper.make(WebViewComponentChildCollector.class);
|
||||||
private static final ClassNode CHILD_COLLECTION = ClassHelper.make(WebViewComponentChildCollection.class);
|
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_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
|
||||||
|
|
||||||
private static final ClassNode EXCEPTION = ClassHelper.make(Exception.class);
|
private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
|
||||||
private static final ClassNode COMPONENT_CREATE = ClassHelper.make(ComponentCreateException.class);
|
private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\.");
|
||||||
|
|
||||||
private static final ClassNode NO_FACTORY_MISSING_EXCEPTION = ClassHelper.make(NoFactoryMissingException.class);
|
private final Provider<AppendOrAddStatementFactory> appendOrAddStatementFactoryProvider;
|
||||||
|
private final Provider<ComponentClassNodeResolver> componentClassNodeResolverProvider;
|
||||||
|
private final Provider<ValueNodeTranspiler> valueNodeTranspilerProvider;
|
||||||
|
private final Provider<BodyTranspiler> bodyTranspilerProvider;
|
||||||
|
|
||||||
private static final ClassNode MISSING_COMPONENT_EXCEPTION = ClassHelper.make(MissingComponentException.class);
|
public DefaultComponentTranspiler(
|
||||||
private static final ClassNode MISSING_CLASS_TYPE_EXCEPTION = ClassHelper.make(MissingClassTypeException.class);
|
Provider<AppendOrAddStatementFactory> appendOrAddStatementFactoryProvider,
|
||||||
private static final ClassNode MISSING_STRING_TYPE_EXCEPTION = ClassHelper.make(MissingStringTypeException.class);
|
Provider<ComponentClassNodeResolver> componentClassNodeResolverProvider,
|
||||||
private static final ClassNode MISSING_FRAGMENT_TYPE_EXCEPTION =
|
Provider<ValueNodeTranspiler> valueNodeTranspilerProvider,
|
||||||
ClassHelper.make(MissingFragmentTypeException.class);
|
Provider<BodyTranspiler> bodyTranspilerProvider
|
||||||
|
) {
|
||||||
private static final String CREATE = "create";
|
this.appendOrAddStatementFactoryProvider = appendOrAddStatementFactoryProvider;
|
||||||
private static final String CREATE_FRAGMENT = "createFragment";
|
this.componentClassNodeResolverProvider = componentClassNodeResolverProvider;
|
||||||
private static final String RESOLVE = "resolve";
|
this.valueNodeTranspilerProvider = valueNodeTranspilerProvider;
|
||||||
private static final String ADD = "add";
|
this.bodyTranspilerProvider = bodyTranspilerProvider;
|
||||||
private static final String APPEND = "append";
|
|
||||||
private static final String FRAGMENT_FQN = GROOWT_VIEW_WEB + ".lib.Fragment";
|
|
||||||
|
|
||||||
private ValueNodeTranspiler valueNodeTranspiler;
|
|
||||||
private BodyTranspiler bodyTranspiler;
|
|
||||||
private AppendOrAddStatementFactory appendOrAddStatementFactory;
|
|
||||||
private ComponentClassNodeResolver componentClassNodeResolver;
|
|
||||||
|
|
||||||
public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
|
|
||||||
this.valueNodeTranspiler = valueNodeTranspiler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBodyTranspiler(BodyTranspiler bodyTranspiler) {
|
protected ValueNodeTranspiler getValueNodeTranspiler() {
|
||||||
this.bodyTranspiler = bodyTranspiler;
|
return this.valueNodeTranspilerProvider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppendOrAddStatementFactory(AppendOrAddStatementFactory appendOrAddStatementFactory) {
|
protected BodyTranspiler getBodyTranspiler() {
|
||||||
this.appendOrAddStatementFactory = appendOrAddStatementFactory;
|
return this.bodyTranspilerProvider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
|
protected AppendOrAddStatementFactory getAppendOrAddStatementFactory() {
|
||||||
this.componentClassNodeResolver = componentClassNodeResolver;
|
return this.appendOrAddStatementFactoryProvider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewComponent c0
|
protected ComponentClassNodeResolver getComponentClassNodeResolver() {
|
||||||
protected ExpressionStatement getComponentDeclaration(Variable component) {
|
return this.componentClassNodeResolverProvider.get();
|
||||||
final var componentDeclaration = new DeclarationExpression(
|
}
|
||||||
new VariableExpression(component),
|
|
||||||
new Token(Types.ASSIGN, "=", -1, -1),
|
/* 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) {
|
||||||
|
return "c" + componentNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESOLVED DECLARATION */
|
||||||
|
|
||||||
|
// RenderContext.Resolved c0Resolved
|
||||||
|
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(
|
||||||
|
this.getComponentName(state.newComponentNumber()) + "Resolved",
|
||||||
|
resolvedType
|
||||||
|
);
|
||||||
|
state.pushResolved(resolvedVariable);
|
||||||
|
final var declarationExpr = new DeclarationExpression(
|
||||||
|
resolvedVariable,
|
||||||
|
getAssignToken(),
|
||||||
EmptyExpression.INSTANCE
|
EmptyExpression.INSTANCE
|
||||||
);
|
);
|
||||||
return new ExpressionStatement(componentDeclaration);
|
return new ExpressionStatement(declarationExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'ComponentName'
|
/* RESOLVE */
|
||||||
protected ConstantExpression getComponentTypeNameExpression(ComponentNode componentNode) {
|
|
||||||
final String componentTypeName = switch (componentNode) {
|
protected List<Expression> getArgsAsList(
|
||||||
case TypedComponentNode typedComponentNode -> switch (typedComponentNode.getArgs().getType()) {
|
TypedComponentNode componentNode,
|
||||||
case ClassComponentTypeNode classComponentTypeNode -> classComponentTypeNode.getFullyQualifiedName();
|
TranspilerState state
|
||||||
case StringComponentTypeNode stringComponentTypeNode -> stringComponentTypeNode.getIdentifier();
|
) {
|
||||||
|
return switch (componentNode.getArgs().getType()) {
|
||||||
|
case ClassComponentTypeNode classComponentTypeNode -> {
|
||||||
|
final String identifier = classComponentTypeNode.getIdentifier();
|
||||||
|
final ConstantExpression alias = getStringLiteral(identifier);
|
||||||
|
final var matcher = isFqn.matcher(identifier);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final ClassNode classNode = ClassHelper.make(identifier);
|
||||||
|
final ClassExpression classExpression = new ClassExpression(classNode);
|
||||||
|
yield List.of(alias, classExpression);
|
||||||
|
} else {
|
||||||
|
// we need to resolve it
|
||||||
|
final var isWithPackageMatcher = isWithPackage.matcher(identifier);
|
||||||
|
if (isWithPackageMatcher.matches()) {
|
||||||
|
final var resolveResult = this.getComponentClassNodeResolver().getClassForFqn(identifier);
|
||||||
|
if (resolveResult.isLeft()) {
|
||||||
|
final var error = resolveResult.getLeft();
|
||||||
|
error.setNode(componentNode.getArgs().getType());
|
||||||
|
state.addError(error);
|
||||||
|
yield List.of();
|
||||||
|
} else {
|
||||||
|
final ClassNode classNode = resolveResult.getRight();
|
||||||
|
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
|
||||||
|
yield List.of(alias, classExpression);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final var resolveResult =
|
||||||
|
this.getComponentClassNodeResolver().getClassForNameWithoutPackage(identifier);
|
||||||
|
if (resolveResult.isLeft()) {
|
||||||
|
final var error = resolveResult.getLeft();
|
||||||
|
error.setNode(componentNode.getArgs().getType());
|
||||||
|
state.addError(error);
|
||||||
|
yield List.of();
|
||||||
|
} else {
|
||||||
|
final ClassNode classNode = resolveResult.getRight();
|
||||||
|
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
|
||||||
|
yield List.of(alias, classExpression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case StringComponentTypeNode stringComponentTypeNode -> {
|
||||||
|
final String identifier = stringComponentTypeNode.getIdentifier();
|
||||||
|
final ConstantExpression typeName = getStringLiteral(identifier);
|
||||||
|
yield List.of(typeName);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
case FragmentComponentNode ignored -> FRAGMENT_FQN;
|
|
||||||
};
|
|
||||||
return makeStringLiteral(componentTypeName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// context.resolve('ComponentName')
|
// 'h1' | 'MyComponent', MyComponent(.class)
|
||||||
protected MethodCallExpression getContextResolveExpr(ComponentNode componentNode, Variable componentContext) {
|
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode, TranspilerState state) {
|
||||||
final var args = new ArgumentListExpression();
|
final List<Expression> args = this.getArgsAsList(componentNode, state);
|
||||||
args.addExpression(this.getComponentTypeNameExpression(componentNode));
|
final ArgumentListExpression argsListExpr = new ArgumentListExpression();
|
||||||
return new MethodCallExpression(new VariableExpression(componentContext), RESOLVE, args);
|
args.forEach(argsListExpr::addExpression);
|
||||||
|
return argsListExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// context.resolve('h1' | 'MyComponent', MyComponent.class)
|
||||||
|
protected MethodCallExpression getContextResolveExpr(
|
||||||
|
TypedComponentNode componentNode,
|
||||||
|
TranspilerState state
|
||||||
|
) {
|
||||||
|
return new MethodCallExpression(
|
||||||
|
new VariableExpression(state.getRenderContext()),
|
||||||
|
"resolve",
|
||||||
|
this.getResolveArgs(componentNode, state)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// context.resolve('h1' | 'MyComponent', MyComponent.class)
|
||||||
|
protected ExpressionStatement getContextResolveStmt(
|
||||||
|
TypedComponentNode componentNode,
|
||||||
|
TranspilerState state
|
||||||
|
) {
|
||||||
|
final BinaryExpression assignment = new BinaryExpression(
|
||||||
|
new VariableExpression(state.getCurrentResolved()),
|
||||||
|
getAssignToken(),
|
||||||
|
this.getContextResolveExpr(componentNode, state)
|
||||||
|
);
|
||||||
|
return new ExpressionStatement(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESOLVE CATCH */
|
||||||
|
|
||||||
|
protected CatchStatement getResolveCatch(TypedComponentNode componentNode) {
|
||||||
|
final Parameter exceptionParameter = new Parameter(
|
||||||
|
COMPONENT_RESOLVE_EXCEPTION_TYPE,
|
||||||
|
"componentResolveException"
|
||||||
|
);
|
||||||
|
final VariableExpression exceptionVariable = new VariableExpression(exceptionParameter);
|
||||||
|
|
||||||
|
final VariableScope variableScope = new VariableScope();
|
||||||
|
variableScope.putDeclaredVariable(exceptionParameter);
|
||||||
|
|
||||||
|
final BinaryExpression setTemplateExpression = new BinaryExpression(
|
||||||
|
new PropertyExpression(exceptionVariable, new ConstantExpression("template")),
|
||||||
|
getAssignToken(),
|
||||||
|
VariableExpression.THIS_EXPRESSION
|
||||||
|
);
|
||||||
|
final Statement setTemplateStatement = new ExpressionStatement(setTemplateExpression);
|
||||||
|
|
||||||
|
final SourcePosition position = componentNode.getTokenRange().getStartPosition();
|
||||||
|
|
||||||
|
final BinaryExpression setLineExpression = new BinaryExpression(
|
||||||
|
new PropertyExpression(exceptionVariable, new ConstantExpression("line")),
|
||||||
|
getAssignToken(),
|
||||||
|
new ConstantExpression(position.line())
|
||||||
|
);
|
||||||
|
final Statement setLineStatement = new ExpressionStatement(setLineExpression);
|
||||||
|
|
||||||
|
final BinaryExpression setColumnExpression = new BinaryExpression(
|
||||||
|
new PropertyExpression(exceptionVariable, new ConstantExpression("column")),
|
||||||
|
getAssignToken(),
|
||||||
|
new ConstantExpression(position.column())
|
||||||
|
);
|
||||||
|
final Statement setColumnStatement = new ExpressionStatement(setColumnExpression);
|
||||||
|
|
||||||
|
final Statement throwStatement = new ThrowStatement(exceptionVariable);
|
||||||
|
|
||||||
|
final List<Statement> statements = new ArrayList<>();
|
||||||
|
statements.add(setTemplateStatement);
|
||||||
|
statements.add(setLineStatement);
|
||||||
|
statements.add(setColumnStatement);
|
||||||
|
statements.add(throwStatement);
|
||||||
|
|
||||||
|
final BlockStatement block = new BlockStatement(statements, variableScope);
|
||||||
|
|
||||||
|
return new CatchStatement(exceptionParameter, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESOLVE BLOCK */
|
||||||
|
|
||||||
|
protected List<Statement> getResolveStatements(
|
||||||
|
TypedComponentNode componentNode,
|
||||||
|
TranspilerState state
|
||||||
|
) {
|
||||||
|
final Statement declaration = this.getResolvedDeclaration(state);
|
||||||
|
final Statement resolveStatement = this.getContextResolveStmt(componentNode, state);
|
||||||
|
|
||||||
|
final TryCatchStatement resolveTryCatch = new TryCatchStatement(resolveStatement, EmptyStatement.INSTANCE);
|
||||||
|
resolveTryCatch.addCatch(this.getResolveCatch(componentNode));
|
||||||
|
return List.of(declaration, resolveTryCatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TYPED COMPONENT DECLARATION */
|
||||||
|
|
||||||
|
// ViewComponent c0
|
||||||
|
protected Statement getTypedComponentDeclaration(TranspilerState state) {
|
||||||
|
final VariableExpression componentVariable = new VariableExpression(
|
||||||
|
this.getComponentName(state.getCurrentComponentNumber()), WEB_VIEW_COMPONENT_TYPE
|
||||||
|
);
|
||||||
|
state.pushComponent(componentVariable);
|
||||||
|
state.getCurrentScope().putDeclaredVariable(componentVariable);
|
||||||
|
|
||||||
|
final Expression declarationExpr = new DeclarationExpression(
|
||||||
|
componentVariable,
|
||||||
|
getAssignToken(),
|
||||||
|
EmptyExpression.INSTANCE
|
||||||
|
);
|
||||||
|
return new ExpressionStatement(declarationExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TYPED COMPONENT CREATE: attributes map */
|
||||||
|
|
||||||
// key: value
|
// key: value
|
||||||
protected MapEntryExpression getAttrExpression(AttrNode attrNode, TranspilerState state) {
|
protected MapEntryExpression getAttrExpression(AttrNode attrNode, TranspilerState state) {
|
||||||
final var keyExpr = makeStringLiteral(attrNode.getKeyNode().getKey());
|
final ConstantExpression keyExpr = getStringLiteral(attrNode.getKeyNode().getKey()); // TODO: pos
|
||||||
final Expression valueExpr = switch (attrNode) {
|
final Expression valueExpr = switch (attrNode) {
|
||||||
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
||||||
case KeyValueAttrNode keyValueAttrNode ->
|
case KeyValueAttrNode keyValueAttrNode ->
|
||||||
this.valueNodeTranspiler.createExpression(keyValueAttrNode.getValueNode(), state);
|
this.getValueNodeTranspiler().createExpression(keyValueAttrNode.getValueNode(), state);
|
||||||
};
|
};
|
||||||
return new MapEntryExpression(keyExpr, valueExpr);
|
return new MapEntryExpression(keyExpr, valueExpr);
|
||||||
}
|
}
|
||||||
@ -108,7 +285,7 @@ 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 IllegalArgumentException("attributeNodes cannot be empty");
|
throw new WebViewComponentBugError(new IllegalArgumentException("attributeNodes cannot be empty."));
|
||||||
}
|
}
|
||||||
final var result = new MapExpression();
|
final var result = new MapExpression();
|
||||||
attributeNodes.stream()
|
attributeNodes.stream()
|
||||||
@ -117,343 +294,234 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TYPED COMPONENT CREATE: component constructor */
|
||||||
|
|
||||||
// arg0, arg1, arg2, etc
|
// arg0, arg1, arg2, etc
|
||||||
protected List<Expression> getConstructorArgs(ComponentConstructorNode componentConstructorNode) {
|
protected List<Expression> getConstructorArgs(ComponentConstructorNode componentConstructorNode) {
|
||||||
final ConvertResult convertResult = GroovyUtil.convert(componentConstructorNode.getGroovyCode()
|
final ConvertResult convertResult = GroovyUtil.convert(componentConstructorNode.getGroovyCode()
|
||||||
.getAsValidGroovyCode());
|
.getAsValidGroovyCode());
|
||||||
final var blockStatement = convertResult.blockStatement();
|
final BlockStatement blockStatement = convertResult.blockStatement();
|
||||||
if (blockStatement == null) {
|
if (blockStatement == null) {
|
||||||
throw new IllegalStateException("Did not expect blockStatement to be null");
|
throw new WebViewComponentBugError(new IllegalStateException("Did not expect blockStatement to be null."));
|
||||||
}
|
}
|
||||||
final var statements = blockStatement.getStatements();
|
final List<Statement> statements = blockStatement.getStatements();
|
||||||
if (statements.size() != 1) {
|
if (statements.size() != 1) {
|
||||||
throw new IllegalStateException("statements size is not 1");
|
throw new WebViewComponentBugError(new IllegalStateException("statements.size() != 1"));
|
||||||
}
|
}
|
||||||
final ExpressionStatement exprStmt = (ExpressionStatement) statements.getFirst();
|
final ExpressionStatement exprStmt = (ExpressionStatement) statements.getFirst();
|
||||||
final ListExpression listExpr = (ListExpression) exprStmt.getExpression();
|
final ListExpression listExpr = (ListExpression) exprStmt.getExpression();
|
||||||
return listExpr.getExpressions();
|
return listExpr.getExpressions(); // TODO: pos for each expression
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLineAndColumn(Node sourceNode, ArgumentListExpression args) {
|
/* COMPONENT CHILDREN */
|
||||||
final var lineAndColumn = lineAndColumn(sourceNode.getTokenRange().getStartPosition());
|
|
||||||
args.addExpression(lineAndColumn.getV1());
|
|
||||||
args.addExpression(lineAndColumn.getV2());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MethodCallExpression getOutCall(BodyChildNode sourceNode, TranspilerState state, Expression toOutput) {
|
// c0cc.add (jString | gString | component)
|
||||||
final VariableExpression outVariableExpr = new VariableExpression(state.out());
|
protected Statement getChildCollectorAdd(Variable childCollector, Expression toAdd) {
|
||||||
final ArgumentListExpression args = new ArgumentListExpression();
|
final VariableExpression childCollectorVariableExpr = new VariableExpression(childCollector);
|
||||||
args.addExpression(toOutput);
|
|
||||||
switch (sourceNode) {
|
|
||||||
case GStringBodyTextNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
|
||||||
case ComponentNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
|
||||||
default -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new MethodCallExpression(outVariableExpr, APPEND, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// { out << jString | gString | component }
|
|
||||||
protected ClosureExpression getOutClosure(BodyChildNode sourceNode, TranspilerState state, Expression toRender) {
|
|
||||||
if (toRender instanceof VariableExpression variableExpression) {
|
|
||||||
variableExpression.setClosureSharedVariable(true);
|
|
||||||
}
|
|
||||||
final Statement stmt = new ExpressionStatement(this.getOutCall(sourceNode, state, toRender));
|
|
||||||
return new ClosureExpression(Parameter.EMPTY_ARRAY, stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// c0_childCollector.add (jString | gString | component) { out << ... }
|
|
||||||
protected Statement getChildCollectorAdd(
|
|
||||||
BodyChildNode sourceNode,
|
|
||||||
TranspilerState state,
|
|
||||||
Variable childCollector,
|
|
||||||
Expression toAdd
|
|
||||||
) {
|
|
||||||
final var childCollectorVariableExpr = new VariableExpression(childCollector);
|
|
||||||
final ClosureExpression renderChild = this.getOutClosure(sourceNode, state, toAdd);
|
|
||||||
final MethodCallExpression methodCall = new MethodCallExpression(
|
final MethodCallExpression methodCall = new MethodCallExpression(
|
||||||
childCollectorVariableExpr,
|
childCollectorVariableExpr,
|
||||||
ADD,
|
"add",
|
||||||
new ArgumentListExpression(List.of(toAdd, renderChild))
|
new ArgumentListExpression(List.of(toAdd))
|
||||||
);
|
);
|
||||||
return new ExpressionStatement(methodCall);
|
return new ExpressionStatement(methodCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// { WebViewComponentChildCollector c0cc -> ... }
|
||||||
* @return Tuple containing 1. body ClosureExpression, and 2. childCollector Variable
|
protected ClosureExpression getChildCollectorClosure(
|
||||||
*/
|
|
||||||
// { WebViewComponentChildCollector c0_childCollector -> ... }
|
|
||||||
protected Tuple2<ClosureExpression, Variable> getBodyClosure(
|
|
||||||
BodyNode bodyNode,
|
BodyNode bodyNode,
|
||||||
TranspilerState state,
|
TranspilerState state
|
||||||
String componentVariableName
|
|
||||||
) {
|
) {
|
||||||
final Parameter childCollectorParam = new Parameter(
|
final Parameter ccParam = new Parameter(
|
||||||
CHILD_COLLECTION,
|
CHILD_COLLECTOR_TYPE,
|
||||||
componentVariableName + "_childCollector"
|
this.getComponentName(state.getCurrentComponentNumber()) + "cc"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var scope = state.pushScope();
|
final var scope = state.pushScope();
|
||||||
scope.putDeclaredVariable(childCollectorParam);
|
scope.putDeclaredVariable(ccParam);
|
||||||
state.pushChildCollector(childCollectorParam);
|
state.pushChildCollector(ccParam);
|
||||||
final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(
|
|
||||||
|
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
|
||||||
bodyNode,
|
bodyNode,
|
||||||
(sourceNode, expr) -> this.getChildCollectorAdd(sourceNode, state, childCollectorParam, expr),
|
(sourceNode, expr) -> this.getChildCollectorAdd(ccParam, expr),
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// clean up
|
||||||
state.popChildCollector();
|
state.popChildCollector();
|
||||||
state.popScope();
|
state.popScope();
|
||||||
|
|
||||||
final ClosureExpression bodyClosure = new ClosureExpression(
|
return new ClosureExpression(
|
||||||
new Parameter[] { childCollectorParam },
|
new Parameter[] { ccParam },
|
||||||
bodyStatements
|
bodyStatements
|
||||||
);
|
);
|
||||||
return new Tuple2<>(bodyClosure, childCollectorParam);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected StaticMethodCallExpression getChildCollectorGetter(
|
||||||
* @return Tuple containing 1. create Expression,
|
BodyNode bodyNode,
|
||||||
* and 2. childCollector Variable, possibly {@code null}.
|
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 */
|
||||||
|
|
||||||
// context.create(...) {...}
|
// context.create(...) {...}
|
||||||
protected Tuple2<MethodCallExpression, @Nullable Variable> getCreateExpression(
|
protected MethodCallExpression getTypedComponentCreateExpression(
|
||||||
ComponentNode componentNode,
|
TypedComponentNode componentNode,
|
||||||
TranspilerState state,
|
TranspilerState state
|
||||||
String componentVariableName
|
|
||||||
) {
|
) {
|
||||||
final var createArgs = new ArgumentListExpression();
|
final var createArgs = new ArgumentListExpression();
|
||||||
final String createName;
|
|
||||||
Variable childCollector = null;
|
|
||||||
if (componentNode instanceof TypedComponentNode typedComponentNode) {
|
|
||||||
createName = CREATE;
|
|
||||||
final var contextResolve = this.getContextResolveExpr(componentNode, state.context());
|
|
||||||
createArgs.addExpression(contextResolve);
|
|
||||||
|
|
||||||
final List<AttrNode> attributeNodes = typedComponentNode.getArgs().getAttributes();
|
createArgs.addExpression(new VariableExpression(state.getCurrentResolved()));
|
||||||
|
|
||||||
|
final List<AttrNode> attributeNodes = componentNode.getArgs().getAttributes();
|
||||||
if (!attributeNodes.isEmpty()) {
|
if (!attributeNodes.isEmpty()) {
|
||||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
||||||
}
|
}
|
||||||
final ComponentConstructorNode constructorNode = typedComponentNode.getArgs().getConstructor();
|
final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor();
|
||||||
if (constructorNode != null) {
|
if (constructorNode != null) {
|
||||||
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
||||||
if (bodyNode != null) {
|
if (bodyNode != null) {
|
||||||
final var bodyResult = this.getBodyClosure(bodyNode, state, componentVariableName);
|
createArgs.addExpression(this.getChildCollectorGetter(bodyNode, state));
|
||||||
childCollector = bodyResult.getV2();
|
|
||||||
createArgs.addExpression(bodyResult.getV1());
|
|
||||||
}
|
|
||||||
} else if (componentNode instanceof FragmentComponentNode fragmentComponentNode) {
|
|
||||||
createName = CREATE_FRAGMENT;
|
|
||||||
final BodyNode bodyNode = Objects.requireNonNull(
|
|
||||||
fragmentComponentNode.getBody(),
|
|
||||||
"FragmentComponentNode cannot have a null body."
|
|
||||||
);
|
|
||||||
final var bodyResult = this.getBodyClosure(bodyNode, state, componentVariableName);
|
|
||||||
childCollector = bodyResult.getV2();
|
|
||||||
createArgs.addExpression(bodyResult.getV1());
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unsupported ComponentNode type: " + componentNode.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final var createCall = new MethodCallExpression(
|
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
|
||||||
new VariableExpression(state.context()),
|
|
||||||
createName,
|
|
||||||
createArgs
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Tuple2<>(createCall, childCollector);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Tuple containing 1. assignment ExpressionStatement,
|
|
||||||
* and 2. childCollector Variable, possibly {@code null}.
|
|
||||||
*/
|
|
||||||
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
||||||
protected Tuple2<ExpressionStatement, @Nullable Variable> getCreateAssignStatement(
|
protected ExpressionStatement getTypedComponentCreateStatement(
|
||||||
ComponentNode componentNode,
|
TypedComponentNode componentNode,
|
||||||
TranspilerState state,
|
TranspilerState state
|
||||||
String componentVariableName,
|
|
||||||
Variable component
|
|
||||||
) {
|
) {
|
||||||
final var componentAssignLeft = new VariableExpression(component);
|
final var left = new VariableExpression(state.getCurrentComponent());
|
||||||
final var createExprResult = this.getCreateExpression(componentNode, state, componentVariableName);
|
final var right = this.getTypedComponentCreateExpression(componentNode, state);
|
||||||
final var componentAssignExpr = new BinaryExpression(
|
final var componentAssignExpr = new BinaryExpression(left, getAssignToken(), right);
|
||||||
componentAssignLeft,
|
return new ExpressionStatement(componentAssignExpr);
|
||||||
new Token(Types.ASSIGN, "=", -1, -1),
|
|
||||||
createExprResult.getV1()
|
|
||||||
);
|
|
||||||
return new Tuple2<>(new ExpressionStatement(componentAssignExpr), createExprResult.getV2());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// catch (NoFactoryMissingException c0nfme) {
|
/* CREATE CATCH */
|
||||||
// throw new MissingClassComponentException(this, 'ComponentType', c0nfme)
|
|
||||||
// }
|
|
||||||
protected CatchStatement getNoMissingFactoryExceptionCatch(
|
|
||||||
ComponentNode componentNode,
|
|
||||||
String componentVariableName
|
|
||||||
) {
|
|
||||||
final String exceptionName = componentVariableName + "nfme";
|
|
||||||
final Parameter fmeParam = new Parameter(NO_FACTORY_MISSING_EXCEPTION, exceptionName);
|
|
||||||
final VariableExpression fmeVar = new VariableExpression(exceptionName);
|
|
||||||
|
|
||||||
final var lineAndColumn = lineAndColumn(componentNode.getTokenRange().getStartPosition());
|
// catch (ComponentCreateException c0CreateException) { ... }
|
||||||
final ConstantExpression line = lineAndColumn.getV1();
|
protected CatchStatement getTypedCreateCatch(TypedComponentNode componentNode, TranspilerState state) {
|
||||||
final ConstantExpression column = lineAndColumn.getV2();
|
final String exceptionName = this.getComponentName(state.getCurrentComponentNumber()) + "CreateException";
|
||||||
|
final Parameter exceptionParam = new Parameter(COMPONENT_CREATE_EXCEPTION_TYPE, exceptionName);
|
||||||
final ConstructorCallExpression mcceConstructorExpr = switch (componentNode) {
|
|
||||||
case TypedComponentNode typedComponentNode -> switch (typedComponentNode.getArgs().getType()) {
|
|
||||||
case StringComponentTypeNode stringComponentTypeNode ->
|
|
||||||
new ConstructorCallExpression(MISSING_STRING_TYPE_EXCEPTION, new ArgumentListExpression(List.of(
|
|
||||||
VariableExpression.THIS_EXPRESSION,
|
|
||||||
makeStringLiteral(stringComponentTypeNode.getIdentifier()),
|
|
||||||
line,
|
|
||||||
column,
|
|
||||||
fmeVar
|
|
||||||
)));
|
|
||||||
case ClassComponentTypeNode classComponentTypeNode ->
|
|
||||||
new ConstructorCallExpression(MISSING_CLASS_TYPE_EXCEPTION, new ArgumentListExpression(List.of(
|
|
||||||
VariableExpression.THIS_EXPRESSION,
|
|
||||||
makeStringLiteral(classComponentTypeNode.getIdentifier()),
|
|
||||||
line,
|
|
||||||
column,
|
|
||||||
fmeVar
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
case FragmentComponentNode ignored -> new ConstructorCallExpression(
|
|
||||||
MISSING_FRAGMENT_TYPE_EXCEPTION,
|
|
||||||
new ArgumentListExpression(List.of(VariableExpression.THIS_EXPRESSION, line, column, fmeVar))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
final Statement throwMcceStmt = new ThrowStatement(mcceConstructorExpr);
|
|
||||||
return new CatchStatement(fmeParam, throwMcceStmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch (MissingComponentException c0mce) { throw c0mce }
|
|
||||||
protected CatchStatement getMissingComponentExceptionCatch(String componentVariableName) {
|
|
||||||
final String exceptionName = componentVariableName + "mce";
|
|
||||||
final Parameter exceptionParam = new Parameter(MISSING_COMPONENT_EXCEPTION, exceptionName);
|
|
||||||
final VariableExpression mceVar = new VariableExpression(exceptionName);
|
|
||||||
final Statement throwMceStmt = new ThrowStatement(mceVar);
|
|
||||||
return new CatchStatement(exceptionParam, throwMceStmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// catch (Exception c0ce) { throw new ComponentCreateException(c0ce) }
|
|
||||||
protected CatchStatement getGeneralCreateExceptionCatch(
|
|
||||||
ComponentNode componentNode,
|
|
||||||
String componentVariableName
|
|
||||||
) {
|
|
||||||
final String exceptionName = componentVariableName + "ce";
|
|
||||||
final Parameter exceptionParam = new Parameter(EXCEPTION, exceptionName);
|
|
||||||
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
||||||
|
|
||||||
final ConstantExpression componentTypeExpression = switch (componentNode) {
|
final VariableScope scope = new VariableScope();
|
||||||
case TypedComponentNode typedComponentNode -> switch (typedComponentNode.getArgs().getType()) {
|
scope.putDeclaredVariable(exceptionParam);
|
||||||
case StringComponentTypeNode stringComponentTypeNode ->
|
|
||||||
makeStringLiteral(stringComponentTypeNode.getIdentifier());
|
|
||||||
case ClassComponentTypeNode classComponentTypeNode ->
|
|
||||||
makeStringLiteral(classComponentTypeNode.getFullyQualifiedName());
|
|
||||||
};
|
|
||||||
case FragmentComponentNode ignored -> makeStringLiteral(FRAGMENT_FQN);
|
|
||||||
};
|
|
||||||
|
|
||||||
final var lineAndColumn = lineAndColumn(componentNode.getTokenRange().getStartPosition());
|
final List<Statement> statements = new ArrayList<>();
|
||||||
|
|
||||||
final ConstructorCallExpression cce = new ConstructorCallExpression(
|
final BinaryExpression setTemplateExpression = new BinaryExpression(
|
||||||
COMPONENT_CREATE,
|
new PropertyExpression(exceptionVar, "template"),
|
||||||
new ArgumentListExpression(List.of(
|
getAssignToken(),
|
||||||
componentTypeExpression,
|
VariableExpression.THIS_EXPRESSION
|
||||||
VariableExpression.THIS_EXPRESSION,
|
|
||||||
lineAndColumn.getV1(),
|
|
||||||
lineAndColumn.getV2(),
|
|
||||||
exceptionVar
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
final Statement throwCcStmt = new ThrowStatement(cce);
|
statements.add(new ExpressionStatement(setTemplateExpression));
|
||||||
return new CatchStatement(exceptionParam, throwCcStmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<CatchStatement> getCreateCatches(ComponentNode componentNode, String componentVariableName) {
|
final SourcePosition start = componentNode.getTokenRange().getStartPosition();
|
||||||
final List<CatchStatement> catches = new ArrayList<>();
|
|
||||||
catches.add(this.getNoMissingFactoryExceptionCatch(componentNode, componentVariableName));
|
|
||||||
catches.add(this.getMissingComponentExceptionCatch(componentVariableName));
|
|
||||||
catches.add(this.getGeneralCreateExceptionCatch(componentNode, componentVariableName));
|
|
||||||
return catches;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Statement createSetContext(TranspilerState state, Variable component) {
|
final BinaryExpression setLineExpression = new BinaryExpression(
|
||||||
final VariableExpression componentExpr = new VariableExpression(component);
|
new PropertyExpression(exceptionVar, "line"),
|
||||||
final VariableExpression contextExpr = new VariableExpression(state.context());
|
getAssignToken(),
|
||||||
final var args = new ArgumentListExpression(contextExpr);
|
new ConstantExpression(start.line())
|
||||||
final var setContext = new MethodCallExpression(componentExpr, "setContext", args);
|
|
||||||
return new ExpressionStatement(setContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getComponentVariableName(int componentNumber) {
|
|
||||||
return "c" + componentNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BlockStatement createComponentStatements(ComponentNode componentNode, TranspilerState state) {
|
|
||||||
final var componentVariableName = this.getComponentVariableName(state.newComponentNumber());
|
|
||||||
final VariableExpression component = new VariableExpression(componentVariableName, VIEW_COMPONENT);
|
|
||||||
|
|
||||||
final BlockStatement result = new BlockStatement();
|
|
||||||
final VariableScope scope = state.currentScope();
|
|
||||||
result.setVariableScope(scope);
|
|
||||||
scope.putDeclaredVariable(component);
|
|
||||||
|
|
||||||
// ViewComponent c0;
|
|
||||||
result.addStatement(this.getComponentDeclaration(component));
|
|
||||||
|
|
||||||
// c0 = context.create(...) { ... }
|
|
||||||
final var createAssignStatementResult = this.getCreateAssignStatement(
|
|
||||||
componentNode,
|
|
||||||
state,
|
|
||||||
componentVariableName,
|
|
||||||
component
|
|
||||||
);
|
);
|
||||||
|
statements.add(new ExpressionStatement(setLineExpression));
|
||||||
|
|
||||||
// try { ... } catch { ... }
|
final BinaryExpression setColumnExpression = new BinaryExpression(
|
||||||
final var tryCreateStatement = new TryCatchStatement(
|
new PropertyExpression(exceptionVar, "column"),
|
||||||
createAssignStatementResult.getV1(),
|
getAssignToken(),
|
||||||
|
new ConstantExpression(start.column())
|
||||||
|
);
|
||||||
|
statements.add(new ExpressionStatement(setColumnExpression));
|
||||||
|
|
||||||
|
statements.add(new ThrowStatement(exceptionVar));
|
||||||
|
|
||||||
|
return new CatchStatement(exceptionParam, new BlockStatement(statements, scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Statement> getTypedCreateStatements(TypedComponentNode componentNode, TranspilerState state) {
|
||||||
|
final Statement declaration = this.getTypedComponentDeclaration(state);
|
||||||
|
final TryCatchStatement createTryCatch = new TryCatchStatement(
|
||||||
|
this.getTypedComponentCreateStatement(componentNode, state),
|
||||||
EmptyStatement.INSTANCE
|
EmptyStatement.INSTANCE
|
||||||
);
|
);
|
||||||
this.getCreateCatches(componentNode, componentVariableName).forEach(tryCreateStatement::addCatch);
|
createTryCatch.addCatch(this.getTypedCreateCatch(componentNode, state));
|
||||||
result.addStatement(tryCreateStatement);
|
return List.of(declaration, createTryCatch);
|
||||||
|
}
|
||||||
|
|
||||||
// component.setContext(context)
|
/* FRAGMENT COMPONENT */
|
||||||
result.addStatement(this.createSetContext(state, component));
|
|
||||||
|
|
||||||
// out or collect
|
// context.createFragment(new Fragment(), <child cl>)
|
||||||
final var addOrAppend = this.appendOrAddStatementFactory.addOrAppend(
|
protected MethodCallExpression getFragmentCreateExpression(
|
||||||
|
FragmentComponentNode componentNode,
|
||||||
|
TranspilerState state
|
||||||
|
) {
|
||||||
|
final Expression fragmentConstructor = new ConstructorCallExpression(
|
||||||
|
FRAGMENT_TYPE,
|
||||||
|
ArgumentListExpression.EMPTY_ARGUMENTS
|
||||||
|
);
|
||||||
|
final Expression ccClosure = this.getChildCollectorGetter(componentNode.getBody(), state);
|
||||||
|
|
||||||
|
final ArgumentListExpression args = new ArgumentListExpression(List.of(fragmentConstructor, ccClosure));
|
||||||
|
|
||||||
|
return new MethodCallExpression(
|
||||||
|
new VariableExpression(state.getRenderContext()),
|
||||||
|
"createFragment",
|
||||||
|
args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAIN */
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Statement> createComponentStatements(ComponentNode componentNode, TranspilerState state) {
|
||||||
|
if (componentNode instanceof TypedComponentNode typedComponentNode) {
|
||||||
|
// Resolve
|
||||||
|
final List<Statement> resolveStatements = this.getResolveStatements(typedComponentNode, state);
|
||||||
|
// Create
|
||||||
|
final List<Statement> createStatements = this.getTypedCreateStatements(typedComponentNode, state);
|
||||||
|
// Append/Add
|
||||||
|
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
|
||||||
componentNode,
|
componentNode,
|
||||||
state,
|
state,
|
||||||
action -> switch (action) {
|
new VariableExpression(state.getCurrentComponent())
|
||||||
case ADD -> {
|
);
|
||||||
final var args = new ArgumentListExpression();
|
|
||||||
args.addExpression(component);
|
// cleanup
|
||||||
final var outComponent = new VariableExpression(component);
|
state.popResolved();
|
||||||
outComponent.setClosureSharedVariable(true);
|
state.popComponent();
|
||||||
final Statement renderStatement = this.appendOrAddStatementFactory.appendOnly(
|
|
||||||
|
final List<Statement> allStatements = new ArrayList<>();
|
||||||
|
allStatements.addAll(resolveStatements);
|
||||||
|
allStatements.addAll(createStatements);
|
||||||
|
allStatements.add(addOrAppend);
|
||||||
|
|
||||||
|
return allStatements;
|
||||||
|
} else if (componentNode instanceof FragmentComponentNode fragmentComponentNode) {
|
||||||
|
// Create and add all at once
|
||||||
|
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend(
|
||||||
componentNode,
|
componentNode,
|
||||||
state,
|
state,
|
||||||
outComponent
|
this.getFragmentCreateExpression(fragmentComponentNode, state)
|
||||||
);
|
);
|
||||||
final ClosureExpression renderArg = new ClosureExpression(
|
return List.of(addOrAppend);
|
||||||
Parameter.EMPTY_ARRAY,
|
} else {
|
||||||
renderStatement
|
throw new WebViewComponentBugError(new IllegalArgumentException(
|
||||||
);
|
"Cannot handle a ComponentNode not of type TypedComponentNode or FragmentComponentNode."
|
||||||
args.addExpression(renderArg);
|
));
|
||||||
yield args;
|
|
||||||
}
|
}
|
||||||
case APPEND -> new VariableExpression(component);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
result.addStatement(addOrAppend);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
import groowt.view.web.antlr.TokenList;
|
import groovy.transform.Field;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||||
|
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
|
||||||
|
import groowt.view.web.WebViewComponentBugError;
|
||||||
import groowt.view.web.ast.node.BodyNode;
|
import groowt.view.web.ast.node.BodyNode;
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.ast.node.PreambleNode;
|
import groowt.view.web.ast.node.PreambleNode;
|
||||||
|
import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException;
|
||||||
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileException;
|
||||||
|
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
||||||
|
import groowt.view.web.runtime.DefaultWebViewComponentRenderContext;
|
||||||
|
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.*;
|
||||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
import org.codehaus.groovy.ast.expr.*;
|
||||||
import org.codehaus.groovy.ast.expr.DeclarationExpression;
|
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.ReturnStatement;
|
import org.codehaus.groovy.ast.stmt.ReturnStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
import org.codehaus.groovy.control.CompilationUnit;
|
import org.codehaus.groovy.control.CompilationUnit;
|
||||||
|
import org.codehaus.groovy.control.ErrorCollector;
|
||||||
import org.codehaus.groovy.control.SourceUnit;
|
import org.codehaus.groovy.control.SourceUnit;
|
||||||
import org.codehaus.groovy.control.io.ReaderSource;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||||
@ -38,34 +42,18 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
|
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
|
||||||
|
|
||||||
private final CompilationUnit groovyCompilationUnit;
|
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
|
||||||
private final String defaultPackageName;
|
private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION =
|
||||||
private final Supplier<? extends TranspilerConfiguration> configurationSupplier;
|
ClassHelper.make(DefaultWebViewComponentRenderContext.class);
|
||||||
|
|
||||||
public DefaultGroovyTranspiler(
|
protected TranspilerConfiguration getConfiguration(
|
||||||
CompilationUnit groovyCompilationUnit,
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
@Nullable String defaultPackageName,
|
ModuleNode moduleNode,
|
||||||
Supplier<? extends TranspilerConfiguration> configurationSupplier
|
ClassLoader classLoader
|
||||||
) {
|
) {
|
||||||
this.groovyCompilationUnit = groovyCompilationUnit;
|
return new DefaultTranspilerConfiguration(new ClassLoaderComponentClassNodeResolver(
|
||||||
this.defaultPackageName = defaultPackageName;
|
compileUnit, moduleNode, classLoader
|
||||||
this.configurationSupplier = configurationSupplier;
|
));
|
||||||
}
|
|
||||||
|
|
||||||
protected TranspilerConfiguration getConfiguration() {
|
|
||||||
return this.configurationSupplier.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull String getDefaultPackageName() {
|
|
||||||
return this.defaultPackageName != null ? this.defaultPackageName : GROOWT_VIEW_WEB;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected @NotNull String getPackageName(ModuleNode moduleNode) {
|
|
||||||
if (moduleNode.hasPackageName()) {
|
|
||||||
return moduleNode.getPackageName();
|
|
||||||
} else {
|
|
||||||
return this.getDefaultPackageName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkPreambleClasses(String templateName, List<ClassNode> classNodes) {
|
protected void checkPreambleClasses(String templateName, List<ClassNode> classNodes) {
|
||||||
@ -81,7 +69,10 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<InnerClassNode> convertPreambleClassesToInnerClasses(ClassNode mainClassNode, List<ClassNode> classNodes) {
|
protected List<InnerClassNode> convertPreambleClassesToInnerClasses(
|
||||||
|
ClassNode mainClassNode,
|
||||||
|
List<ClassNode> classNodes
|
||||||
|
) {
|
||||||
final List<InnerClassNode> result = new ArrayList<>();
|
final List<InnerClassNode> result = new ArrayList<>();
|
||||||
for (final var classNode : classNodes) {
|
for (final var classNode : classNodes) {
|
||||||
if (classNode instanceof InnerClassNode innerClassNode) {
|
if (classNode instanceof InnerClassNode innerClassNode) {
|
||||||
@ -102,18 +93,23 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePreamble(
|
protected void handlePreamble(
|
||||||
String templateName,
|
String templateClassName,
|
||||||
PreambleNode preambleNode,
|
PreambleNode preambleNode,
|
||||||
ClassNode mainClassNode,
|
ClassNode mainClassNode,
|
||||||
WebViewComponentModuleNode moduleNode
|
WebViewComponentModuleNode moduleNode
|
||||||
) {
|
) {
|
||||||
final GroovyUtil.ConvertResult preambleConvert = GroovyUtil.convert(
|
final GroovyUtil.ConvertResult convertResult = GroovyUtil.convert(
|
||||||
preambleNode.getGroovyCode().getAsValidGroovyCode()
|
preambleNode.getGroovyCode().getAsValidGroovyCode()
|
||||||
);
|
);
|
||||||
|
|
||||||
WebViewComponentModuleNode.copyTo(preambleConvert.moduleNode(), moduleNode);
|
WebViewComponentModuleNode.copyTo(convertResult.moduleNode(), moduleNode);
|
||||||
|
|
||||||
final BlockStatement preambleBlock = preambleConvert.blockStatement();
|
if (convertResult.moduleNode().hasPackage()) {
|
||||||
|
moduleNode.setPackage(convertResult.moduleNode().getPackage());
|
||||||
|
mainClassNode.setName(moduleNode.getPackageName() + "." + templateClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
final BlockStatement preambleBlock = convertResult.blockStatement();
|
||||||
if (preambleBlock != null) {
|
if (preambleBlock != null) {
|
||||||
// Fields
|
// Fields
|
||||||
final List<Statement> preambleStatements = preambleBlock.getStatements();
|
final List<Statement> preambleStatements = preambleBlock.getStatements();
|
||||||
@ -133,7 +129,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
"Currently, only classes, methods, and field declarations " +
|
"Currently, only classes, methods, and field declarations " +
|
||||||
"(marked with @groovy.transform.Field) " +
|
"(marked with @groovy.transform.Field) " +
|
||||||
"are supported. The rest will be ignored.",
|
"are supported. The rest will be ignored.",
|
||||||
templateName
|
templateClassName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
declarationsWithField.forEach(declaration -> {
|
declarationsWithField.forEach(declaration -> {
|
||||||
@ -142,16 +138,16 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// move methods from script class
|
// move methods from script class
|
||||||
final ClassNode scriptClass = preambleConvert.scriptClass();
|
final ClassNode scriptClass = convertResult.scriptClass();
|
||||||
if (scriptClass != null) {
|
if (scriptClass != null) {
|
||||||
scriptClass.getMethods().forEach(mainClassNode::addMethod);
|
scriptClass.getMethods().forEach(mainClassNode::addMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle classes
|
// handle classes
|
||||||
final List<ClassNode> classNodes = preambleConvert.classNodes();
|
final List<ClassNode> classNodes = convertResult.classNodes();
|
||||||
this.checkPreambleClasses(templateName, classNodes);
|
this.checkPreambleClasses(templateClassName, classNodes);
|
||||||
final List<ClassNode> toInner = classNodes.stream()
|
final List<ClassNode> toInner = classNodes.stream()
|
||||||
.filter(classNode -> classNode != preambleConvert.scriptClass())
|
.filter(classNode -> classNode != convertResult.scriptClass())
|
||||||
.filter(classNode -> !classNode.isScript())
|
.filter(classNode -> !classNode.isScript())
|
||||||
.toList();
|
.toList();
|
||||||
final List<InnerClassNode> innerClassNodes = this.convertPreambleClassesToInnerClasses(mainClassNode, toInner);
|
final List<InnerClassNode> innerClassNodes = this.convertPreambleClassesToInnerClasses(mainClassNode, toInner);
|
||||||
@ -164,30 +160,35 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
// - preamble with script -> use the script class from the converted preamble,
|
// - preamble with script -> use the script class from the converted preamble,
|
||||||
// and don't forget to call run in our render method
|
// and don't forget to call run in our render method
|
||||||
@Override
|
@Override
|
||||||
public void transpile(
|
public WebViewComponentSourceUnit transpile(
|
||||||
|
ComponentTemplateCompilerConfiguration compilerConfiguration,
|
||||||
|
WebViewComponentTemplateCompileUnit compileUnit,
|
||||||
CompilationUnitNode compilationUnitNode,
|
CompilationUnitNode compilationUnitNode,
|
||||||
TokenList tokens,
|
String templateClassName
|
||||||
String ownerComponentName,
|
) throws ComponentTemplateCompileException {
|
||||||
ReaderSource readerSource
|
final var groovyCompilerConfiguration = compilerConfiguration.getGroovyCompilerConfiguration();
|
||||||
) {
|
|
||||||
final var configuration = this.getConfiguration();
|
|
||||||
final String templateName = ownerComponentName + "Template";
|
|
||||||
|
|
||||||
final var sourceUnit = new WebViewComponentSourceUnit(
|
final var sourceUnit = new WebViewComponentSourceUnit(
|
||||||
templateName,
|
templateClassName,
|
||||||
readerSource,
|
compileUnit,
|
||||||
this.groovyCompilationUnit.getConfiguration(),
|
groovyCompilerConfiguration,
|
||||||
this.groovyCompilationUnit.getClassLoader(),
|
compilerConfiguration.getGroovyClassLoader(),
|
||||||
this.groovyCompilationUnit.getErrorCollector()
|
new ErrorCollector(groovyCompilerConfiguration)
|
||||||
);
|
);
|
||||||
|
|
||||||
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
|
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
|
||||||
sourceUnit.setModuleNode(moduleNode);
|
sourceUnit.setModuleNode(moduleNode);
|
||||||
|
|
||||||
final String packageName = this.getPackageName(moduleNode);
|
moduleNode.setPackageName(compileUnit.getDefaultPackageName());
|
||||||
moduleNode.setPackageName(packageName);
|
|
||||||
|
moduleNode.addStarImport(GROOWT_VIEW_WEB + ".lib");
|
||||||
|
moduleNode.addImport(COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE);
|
||||||
|
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_WEB + ".runtime");
|
||||||
|
|
||||||
final ClassNode mainClassNode = new ClassNode(
|
final ClassNode mainClassNode = new ClassNode(
|
||||||
packageName + "." + templateName,
|
compileUnit.getDefaultPackageName() + "." + templateClassName,
|
||||||
ACC_PUBLIC,
|
ACC_PUBLIC,
|
||||||
ClassHelper.OBJECT_TYPE
|
ClassHelper.OBJECT_TYPE
|
||||||
);
|
);
|
||||||
@ -199,15 +200,73 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
// preamble
|
// preamble
|
||||||
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
|
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
|
||||||
if (preambleNode != null) {
|
if (preambleNode != null) {
|
||||||
this.handlePreamble(templateName, preambleNode, mainClassNode, moduleNode);
|
this.handlePreamble(templateClassName, preambleNode, mainClassNode, moduleNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderer
|
// getRenderer
|
||||||
final var renderBlock = new BlockStatement();
|
// params
|
||||||
|
final Parameter componentContextParam = new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME);
|
||||||
|
final Parameter writerParam = new Parameter(COMPONENT_WRITER_TYPE, COMPONENT_WRITER_NAME);
|
||||||
|
final VariableExpression renderContextVariable = new VariableExpression(
|
||||||
|
RENDER_CONTEXT_NAME,
|
||||||
|
RENDER_CONTEXT_TYPE
|
||||||
|
);
|
||||||
|
|
||||||
final TranspilerState state = TranspilerState.withDefaultRootScope();
|
// closure body
|
||||||
renderBlock.setVariableScope(state.currentScope());
|
final BlockStatement renderBlock = new BlockStatement();
|
||||||
|
|
||||||
|
final TranspilerState state = TranspilerState.withRootScope(
|
||||||
|
componentContextParam,
|
||||||
|
writerParam,
|
||||||
|
renderContextVariable
|
||||||
|
);
|
||||||
|
renderBlock.setVariableScope(state.getCurrentScope());
|
||||||
|
|
||||||
|
// init: construct RenderContext
|
||||||
|
final ConstructorCallExpression renderContextConstructor = new ConstructorCallExpression(
|
||||||
|
RENDER_CONTEXT_IMPLEMENTATION,
|
||||||
|
new ArgumentListExpression(
|
||||||
|
new VariableExpression(componentContextParam), // component context
|
||||||
|
new VariableExpression(writerParam)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
final BinaryExpression renderContextAssignExpr = new DeclarationExpression(
|
||||||
|
renderContextVariable,
|
||||||
|
getAssignToken(),
|
||||||
|
renderContextConstructor
|
||||||
|
);
|
||||||
|
renderBlock.addStatement(new ExpressionStatement(renderContextAssignExpr));
|
||||||
|
|
||||||
|
// init: componentContext.renderContext = renderContext
|
||||||
|
final BinaryExpression componentContextRenderContextAssign = new BinaryExpression(
|
||||||
|
new PropertyExpression(new VariableExpression(componentContextParam), "renderContext"),
|
||||||
|
getAssignToken(),
|
||||||
|
renderContextVariable
|
||||||
|
);
|
||||||
|
renderBlock.addStatement(new ExpressionStatement(componentContextRenderContextAssign));
|
||||||
|
|
||||||
|
// init: writer.renderContext = renderContext
|
||||||
|
final BinaryExpression writerRenderContextAssign = new BinaryExpression(
|
||||||
|
new PropertyExpression(new VariableExpression(writerParam), "renderContext"),
|
||||||
|
getAssignToken(),
|
||||||
|
renderContextVariable
|
||||||
|
);
|
||||||
|
renderBlock.addStatement(new ExpressionStatement(writerRenderContextAssign));
|
||||||
|
|
||||||
|
// init: writer.componentContext = componentContext
|
||||||
|
final BinaryExpression writerComponentContextAssign = new BinaryExpression(
|
||||||
|
new PropertyExpression(new VariableExpression(writerParam), "componentContext"),
|
||||||
|
getAssignToken(),
|
||||||
|
new VariableExpression(componentContextParam)
|
||||||
|
);
|
||||||
|
renderBlock.addStatement(new ExpressionStatement(writerComponentContextAssign));
|
||||||
|
|
||||||
|
// actual rendering of body
|
||||||
|
final var configuration = this.getConfiguration(
|
||||||
|
compileUnit,
|
||||||
|
moduleNode,
|
||||||
|
compilerConfiguration.getGroovyClassLoader()
|
||||||
|
);
|
||||||
final BodyNode bodyNode = compilationUnitNode.getBodyNode();
|
final BodyNode bodyNode = compilationUnitNode.getBodyNode();
|
||||||
if (bodyNode != null) {
|
if (bodyNode != null) {
|
||||||
final var appendOrAddStatementFactory = configuration.getAppendOrAddStatementFactory();
|
final var appendOrAddStatementFactory = configuration.getAppendOrAddStatementFactory();
|
||||||
@ -215,9 +274,14 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
configuration.getBodyTranspiler()
|
configuration.getBodyTranspiler()
|
||||||
.transpileBody(
|
.transpileBody(
|
||||||
compilationUnitNode.getBodyNode(),
|
compilationUnitNode.getBodyNode(),
|
||||||
(source, expr) -> appendOrAddStatementFactory.addOrAppend(source, state, action -> {
|
(source, expr) -> appendOrAddStatementFactory.addOrAppend(
|
||||||
|
source,
|
||||||
|
state,
|
||||||
|
action -> {
|
||||||
if (action == AppendOrAddStatementFactory.Action.ADD) {
|
if (action == AppendOrAddStatementFactory.Action.ADD) {
|
||||||
throw new IllegalStateException("Should not be adding here!");
|
throw new WebViewComponentBugError(new IllegalStateException(
|
||||||
|
"Should not be adding from document root!"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}),
|
}),
|
||||||
@ -226,11 +290,10 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderBlock.addStatement(new ReturnStatement(ConstantExpression.NULL));
|
||||||
|
|
||||||
final ClosureExpression renderer = new ClosureExpression(
|
final ClosureExpression renderer = new ClosureExpression(
|
||||||
new Parameter[] {
|
new Parameter[] { componentContextParam, writerParam },
|
||||||
(Parameter) state.context(),
|
|
||||||
(Parameter) state.out()
|
|
||||||
},
|
|
||||||
renderBlock
|
renderBlock
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -250,7 +313,16 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
);
|
);
|
||||||
mainClassNode.addMethod(getRenderer);
|
mainClassNode.addMethod(getRenderer);
|
||||||
|
|
||||||
this.groovyCompilationUnit.addSource(sourceUnit);
|
if (state.hasErrors()) {
|
||||||
|
final List<ComponentTemplateCompileException> errors = state.getErrors();
|
||||||
|
if (errors.size() == 1) {
|
||||||
|
throw new WebViewComponentTemplateCompileException(compileUnit, errors.getFirst());
|
||||||
|
} else {
|
||||||
|
throw new MultipleWebViewComponentCompileErrorsException(compileUnit, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
|
||||||
|
import groowt.view.web.util.Provider;
|
||||||
|
|
||||||
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||||
|
|
||||||
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
|
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
|
||||||
private final BodyTranspiler bodyTranspiler;
|
private final BodyTranspiler bodyTranspiler;
|
||||||
|
private final ValueNodeTranspiler valueNodeTranspiler;
|
||||||
|
|
||||||
@Inject
|
public DefaultTranspilerConfiguration(ComponentClassNodeResolver classNodeResolver) {
|
||||||
public DefaultTranspilerConfiguration() {
|
|
||||||
final var positionSetter = new SimplePositionSetter();
|
final var positionSetter = new SimplePositionSetter();
|
||||||
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
||||||
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
||||||
final var componentTranspiler = new DefaultComponentTranspiler();
|
final var componentTranspiler = new DefaultComponentTranspiler(
|
||||||
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
Provider.of(this.appendOrAddStatementFactory),
|
||||||
|
Provider.of(classNodeResolver),
|
||||||
|
Provider.ofLazy(this::getValueNodeTranspiler),
|
||||||
|
Provider.ofLazy(this::getBodyTranspiler)
|
||||||
|
);
|
||||||
|
this.valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
||||||
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
||||||
|
|
||||||
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
|
||||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
|
||||||
componentTranspiler.setAppendOrAddStatementFactory(this.appendOrAddStatementFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -32,4 +33,8 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
|||||||
return this.appendOrAddStatementFactory;
|
return this.appendOrAddStatementFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ValueNodeTranspiler getValueNodeTranspiler() {
|
||||||
|
return this.valueNodeTranspiler;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user