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;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||
import groowt.view.component.compiler.*;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
@ -13,44 +11,45 @@ import java.util.function.Function;
|
||||
|
||||
public abstract class AbstractViewComponent implements ViewComponent {
|
||||
|
||||
private ComponentContext context;
|
||||
private ComponentTemplate template;
|
||||
private static final ComponentTemplateClassFactory templateClassFactory = new SimpleComponentTemplateClassFactory();
|
||||
|
||||
public AbstractViewComponent() {}
|
||||
|
||||
public AbstractViewComponent(ComponentTemplate template) {
|
||||
this.template = Objects.requireNonNull(template);
|
||||
}
|
||||
|
||||
public AbstractViewComponent(Class<? extends ComponentTemplate> templateClass) {
|
||||
private static ComponentTemplate instantiateTemplate(Class<? extends ComponentTemplate> templateClass) {
|
||||
try {
|
||||
this.template = templateClass.getConstructor().newInstance();
|
||||
return templateClass.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractViewComponent(ComponentTemplateSource source, ComponentTemplateCompiler compiler) {
|
||||
try {
|
||||
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
||||
} catch (ComponentTemplateCompileErrorException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private final ComponentTemplate template;
|
||||
private ComponentContext context;
|
||||
|
||||
public AbstractViewComponent() {
|
||||
this.template = null;
|
||||
}
|
||||
|
||||
protected AbstractViewComponent(
|
||||
ComponentTemplateSource source,
|
||||
Function<? super Class<? extends AbstractViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
||||
public AbstractViewComponent(ComponentTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
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 {
|
||||
this.template = compiler.compileAndGet(this.getSelfClass(), source);
|
||||
} catch (ComponentTemplateCompileErrorException e) {
|
||||
compileResult = compileUnitFunction.apply(this.getClass()).compile();
|
||||
} catch (ComponentTemplateCompileException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
final var templateClass = templateClassFactory.getTemplateClass(compileResult);
|
||||
this.template = instantiateTemplate(templateClass);
|
||||
}
|
||||
|
||||
protected abstract Class<? extends AbstractViewComponent> getSelfClass();
|
||||
|
||||
|
||||
@Override
|
||||
public void setContext(ComponentContext context) {
|
||||
@ -66,26 +65,20 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
||||
return Objects.requireNonNull(template);
|
||||
}
|
||||
|
||||
protected void setTemplate(ComponentTemplate template) {
|
||||
this.template = Objects.requireNonNull(template);
|
||||
}
|
||||
protected void beforeRender() {}
|
||||
|
||||
protected void beforeRender() {
|
||||
this.getContext().beforeComponentRender(this);
|
||||
}
|
||||
|
||||
protected void afterRender() {
|
||||
this.getContext().afterComponentRender(this);
|
||||
}
|
||||
protected void afterRender() {}
|
||||
|
||||
/**
|
||||
* @implSpec If overriding, <strong>please</strong> call
|
||||
* {@link #beforeRender()}and {@link #afterRender()} before
|
||||
* and after the actual rendering is done, respectively.
|
||||
* {@link #beforeRender()} and {@link #afterRender()} before
|
||||
* 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
|
||||
public void renderTo(Writer out) throws IOException {
|
||||
final Closure<?> closure = this.template.getRenderer();
|
||||
final Closure<?> closure = this.getTemplate().getRenderer();
|
||||
closure.setDelegate(this);
|
||||
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
|
||||
this.beforeRender();
|
||||
|
@ -5,10 +5,6 @@ import groowt.view.component.context.ComponentContext;
|
||||
|
||||
public interface ViewComponent extends View {
|
||||
|
||||
default String getTypeName() {
|
||||
return this.getClass().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* <em>Note:</em> compiled templates are required to automatically
|
||||
* 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;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
import org.codehaus.groovy.tools.GroovyClass;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class CachingComponentTemplateCompiler extends AbstractComponentTemplateCompiler {
|
||||
public abstract class CachingComponentTemplateCompiler<U extends ComponentTemplateCompileUnit>
|
||||
implements ComponentTemplateCompiler<U> {
|
||||
|
||||
private record CachedTemplate(
|
||||
ComponentTemplateCompileResult compileResult,
|
||||
@Nullable ComponentTemplate template
|
||||
) {}
|
||||
private final Map<Class<? extends ViewComponent>, ComponentTemplateCompileResult> cache = new HashMap<>();
|
||||
|
||||
private final Map<Class<? extends ViewComponent>, CachedTemplate> cache = new HashMap<>();
|
||||
|
||||
public CachingComponentTemplateCompiler(GroovyClassLoader groovyClassLoader) {
|
||||
super(groovyClassLoader);
|
||||
}
|
||||
|
||||
private ComponentTemplate instantiate(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
ComponentTemplateCompileResult compileResult
|
||||
) {
|
||||
for (final var groovyClass : compileResult.otherClasses()) {
|
||||
// Try to find it. If we can't, we need to load it via the groovy loader
|
||||
try {
|
||||
Class.forName(groovyClass.getName(), true, groovyClassLoader);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
|
||||
} catch (LinkageError ignored) {
|
||||
// no-op, because we already have it
|
||||
}
|
||||
}
|
||||
final GroovyClass templateGroovyClass = compileResult.templateClass();
|
||||
Class<?> templateClass;
|
||||
// Try to find it. If we can't, we need to load it via the groovy loader
|
||||
try {
|
||||
templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
templateClass = groovyClassLoader.defineClass(
|
||||
templateGroovyClass.getName(),
|
||||
templateGroovyClass.getBytes()
|
||||
);
|
||||
}
|
||||
try {
|
||||
return (ComponentTemplate) templateClass.getConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
|
||||
}
|
||||
}
|
||||
// private ComponentTemplate instantiate(
|
||||
// GroovyClassLoader groovyClassLoader,
|
||||
// CompileResult compileResult
|
||||
// ) {
|
||||
// for (final var groovyClass : compileResult.otherClasses()) {
|
||||
// // Try to find it. If we can't, we need to load it via the groovy loader
|
||||
// try {
|
||||
// Class.forName(groovyClass.getName(), true, groovyClassLoader);
|
||||
// } catch (ClassNotFoundException ignored) {
|
||||
// groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
|
||||
// } catch (LinkageError ignored) {
|
||||
// // no-op, because we already have it
|
||||
// }
|
||||
// }
|
||||
// final GroovyClass templateGroovyClass = compileResult.templateClass();
|
||||
// Class<?> templateClass;
|
||||
// // Try to find it. If we can't, we need to load it via the groovy loader
|
||||
// try {
|
||||
// templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
|
||||
// } catch (ClassNotFoundException ignored) {
|
||||
// templateClass = groovyClassLoader.defineClass(
|
||||
// templateGroovyClass.getName(),
|
||||
// templateGroovyClass.getBytes()
|
||||
// );
|
||||
// }
|
||||
// try {
|
||||
// return (ComponentTemplate) templateClass.getConstructor().newInstance();
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public final ComponentTemplate compileAndGet(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
ComponentTemplateSource source
|
||||
) throws ComponentTemplateCompileErrorException {
|
||||
if (this.cache.containsKey(forClass)) {
|
||||
final var cached = this.cache.get(forClass);
|
||||
if (cached.template() == null) {
|
||||
final ComponentTemplate template = this.instantiate(groovyClassLoader, cached.compileResult());
|
||||
this.cache.put(forClass, new CachedTemplate(cached.compileResult(), template));
|
||||
return template;
|
||||
} else {
|
||||
return cached.template();
|
||||
}
|
||||
public final ComponentTemplateCompileResult compile(U compileUnit)
|
||||
throws ComponentTemplateCompileException {
|
||||
if (this.cache.containsKey(compileUnit.getForClass())) {
|
||||
return this.cache.get(compileUnit.getForClass());
|
||||
} 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));
|
||||
final ComponentTemplateCompileResult compileResult = this.doCompile(compileUnit);
|
||||
this.cache.put(compileUnit.getForClass(), compileResult);
|
||||
return compileResult;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ComponentTemplateCompileResult doCompile(
|
||||
ComponentTemplateSource source,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader
|
||||
) throws ComponentTemplateCompileErrorException;
|
||||
protected abstract ComponentTemplateCompileResult doCompile(U compileUnit) throws ComponentTemplateCompileException;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
import org.codehaus.groovy.tools.GroovyClass;
|
||||
public interface ComponentTemplateCompiler<U extends ComponentTemplateCompileUnit> {
|
||||
|
||||
import java.io.IOException;
|
||||
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);
|
||||
}
|
||||
}
|
||||
ComponentTemplateCompileResult compile(U compileUnit) throws ComponentTemplateCompileException;
|
||||
|
||||
}
|
||||
|
@ -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,62 +1,28 @@
|
||||
package groowt.view.component.context;
|
||||
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.factory.ComponentFactory;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface ComponentContext {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
List<ComponentScope> getScopeStack();
|
||||
|
||||
void pushScope(ComponentScope scope);
|
||||
void pushDefaultScope();
|
||||
void popScope();
|
||||
ComponentScope getRootScope();
|
||||
|
||||
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 <T extends ViewComponent> T getParent(Class<T> parentClass);
|
||||
|
||||
@ -66,11 +32,7 @@ public interface ComponentContext {
|
||||
Class<T> ancestorClass,
|
||||
Predicate<? super ViewComponent> matching
|
||||
) {
|
||||
return ancestorClass.cast(matching.and(ancestorClass::isInstance));
|
||||
}
|
||||
|
||||
default @Nullable ViewComponent findNearestAncestorByTypeName(String typeName) {
|
||||
return this.findNearestAncestor(component -> component.getTypeName().equals(typeName));
|
||||
return ancestorClass.cast(this.findNearestAncestor(matching.and(ancestorClass::isInstance)));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.factory.ComponentFactory;
|
||||
|
||||
public interface ComponentScope {
|
||||
|
||||
void add(String name, ComponentFactory<?> factory);
|
||||
boolean contains(String name);
|
||||
void remove(String name);
|
||||
ComponentFactory<?> get(String name);
|
||||
record TypeAndFactory<T extends ViewComponent>(Class<? extends T> type, ComponentFactory<? extends T> factory) {}
|
||||
|
||||
default ComponentFactory<?> factoryMissing(String typeName) {
|
||||
throw new NoFactoryMissingException(this.getClass().getName() + " does not support factoryMissing()");
|
||||
//---- string types
|
||||
|
||||
<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) {
|
||||
this.add(clazz.getName(), factory);
|
||||
}
|
||||
//---- class types
|
||||
|
||||
default boolean contains(Class<? extends ViewComponent> clazz) {
|
||||
return this.contains(clazz.getName());
|
||||
}
|
||||
<T extends ViewComponent> void add(Class<T> forClass, ComponentFactory<? extends T> factory);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends ViewComponent> ComponentFactory<T> get(Class<T> clazz) {
|
||||
return (ComponentFactory<T>) this.get(clazz.getName());
|
||||
}
|
||||
<T extends ViewComponent> void add(
|
||||
Class<T> publicType,
|
||||
Class<? extends T> implementingType,
|
||||
ComponentFactory<? extends T> factory
|
||||
);
|
||||
|
||||
default void remove(Class<? extends ViewComponent> clazz) {
|
||||
this.remove(clazz.getName());
|
||||
}
|
||||
boolean contains(Class<? extends ViewComponent> type);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends ViewComponent> ComponentFactory<T> factoryMissing(Class<T> clazz) {
|
||||
return (ComponentFactory<T>) this.factoryMissing(clazz.getName());
|
||||
}
|
||||
<T extends ViewComponent> TypeAndFactory<T> get(Class<T> type);
|
||||
|
||||
default void add(String name, Closure<? extends ViewComponent> closure) {
|
||||
this.add(name, ComponentFactory.ofClosure(closure));
|
||||
}
|
||||
void remove(Class<? extends ViewComponent> type);
|
||||
|
||||
default <T extends ViewComponent> void add(Class<T> type, Closure<? extends T> closure) {
|
||||
this.add(type, ComponentFactory.ofClosure(closure));
|
||||
default <T extends ViewComponent> TypeAndFactory<?> factoryMissing(String typeName, Class<T> type)
|
||||
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;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
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;
|
||||
private final ComponentFactory<?> factory;
|
||||
|
||||
public DefaultResolved(String typeName, ComponentFactory<?> factory) {
|
||||
this.typeName = typeName;
|
||||
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
|
||||
@ApiStatus.Internal
|
||||
public RenderContext getRenderContext() {
|
||||
return Objects.requireNonNull(
|
||||
this.renderContext,
|
||||
"The renderContext is null. Did this method get called from outside of a rendering context?"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeComponentRender(ViewComponent component) {
|
||||
this.componentStack.push(component);
|
||||
@ApiStatus.Internal
|
||||
public void setRenderContext(RenderContext renderContext) {
|
||||
this.renderContext = Objects.requireNonNull(renderContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterComponentRender(ViewComponent component) {
|
||||
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() {
|
||||
public List<ComponentScope> getScopeStack() {
|
||||
return new LinkedList<>(this.scopeStack);
|
||||
}
|
||||
|
||||
@ -116,18 +53,15 @@ public class DefaultComponentContext implements ComponentContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deque<ViewComponent> getComponentStack() {
|
||||
return new LinkedList<>(this.componentStack);
|
||||
public ComponentScope getRootScope() {
|
||||
return this.scopeStack.getLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ViewComponent getParent() {
|
||||
if (this.componentStack.size() > 1) {
|
||||
final var child = this.componentStack.pop();
|
||||
final var parent = this.componentStack.pop();
|
||||
this.componentStack.push(parent);
|
||||
this.componentStack.push(child);
|
||||
return parent;
|
||||
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||
if (componentStack.size() > 1) {
|
||||
return componentStack.get(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -139,35 +73,21 @@ public class DefaultComponentContext implements ComponentContext {
|
||||
|
||||
@Override
|
||||
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
|
||||
if (this.componentStack.size() > 1) {
|
||||
final Deque<ViewComponent> tmp = new LinkedList<>();
|
||||
tmp.push(this.componentStack.pop()); // child
|
||||
ViewComponent result = null;
|
||||
while (result == null && !this.componentStack.isEmpty()) {
|
||||
final var ancestor = this.componentStack.pop();
|
||||
tmp.push(ancestor);
|
||||
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||
if (componentStack.size() > 1) {
|
||||
for (final var ancestor : componentStack.subList(1, componentStack.size() -1)) {
|
||||
if (matching.test(ancestor)) {
|
||||
result = ancestor;
|
||||
return ancestor;
|
||||
}
|
||||
}
|
||||
while (!tmp.isEmpty()) {
|
||||
this.componentStack.push(tmp.pop());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewComponent> getAllAncestors() {
|
||||
if (this.componentStack.size() > 1) {
|
||||
final var child = this.componentStack.pop();
|
||||
final List<ViewComponent> result = new ArrayList<>(this.componentStack);
|
||||
this.componentStack.push(child);
|
||||
return result;
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
|
||||
return componentStack.subList(1, componentStack.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,32 +1,65 @@
|
||||
package groowt.view.component.context;
|
||||
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.factory.ComponentFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
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
|
||||
public void add(String name, ComponentFactory<?> factory) {
|
||||
this.factories.put(name, factory);
|
||||
public <T extends ViewComponent> void add(String typeName, Class<T> forClass, ComponentFactory<? extends T> factory) {
|
||||
this.stringFactories.put(typeName, new TypeAndFactory<>(forClass, factory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return this.factories.containsKey(name);
|
||||
public boolean contains(String typeName) {
|
||||
return this.stringFactories.containsKey(typeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String name) {
|
||||
this.factories.remove(name);
|
||||
public TypeAndFactory<?> get(String typeName) {
|
||||
return Objects.requireNonNull(this.stringFactories.get(typeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentFactory<?> get(String name) {
|
||||
return this.factories.get(name);
|
||||
public void remove(String typeName) {
|
||||
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;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
|
||||
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 {
|
||||
ALL,
|
||||
@ -15,18 +16,18 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
||||
NONE
|
||||
}
|
||||
|
||||
private final Closure<T> closure;
|
||||
private final Closure<? extends T> closure;
|
||||
private final Type type;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ClosureComponentFactory(Closure<? extends T> closure) {
|
||||
this.closure = (Closure<T>) closure;
|
||||
public AbstractClosureComponentFactory(Closure<? extends T> closure) {
|
||||
super(closure.getOwner(), closure.getThisObject());
|
||||
this.closure = closure;
|
||||
final var paramTypes = this.closure.getParameterTypes();
|
||||
if (paramTypes.length == 0) {
|
||||
this.type = Type.NONE;
|
||||
} else if (paramTypes.length == 1) {
|
||||
final var paramType = paramTypes[0];
|
||||
if (paramType == String.class || paramType == Class.class) {
|
||||
if (paramType == String.class) {
|
||||
this.type = Type.NAME_ONLY;
|
||||
} else if (ComponentContext.class.isAssignableFrom(paramType)) {
|
||||
this.type = Type.CONTEXT_ONLY;
|
||||
@ -36,7 +37,7 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
||||
} else {
|
||||
final var firstParamType = paramTypes[0];
|
||||
final var secondParamType = paramTypes[1];
|
||||
if (firstParamType == String.class || firstParamType == Class.class) {
|
||||
if (firstParamType == String.class) {
|
||||
if (ComponentContext.class.isAssignableFrom(secondParamType)) {
|
||||
if (paramTypes.length > 2) {
|
||||
this.type = Type.ALL;
|
||||
@ -54,31 +55,17 @@ final class ClosureComponentFactory<T extends ViewComponent> implements Componen
|
||||
}
|
||||
}
|
||||
|
||||
private T flatCall(Object... args) {
|
||||
return this.closure.call(flatten(args));
|
||||
}
|
||||
|
||||
private T objTypeCreate(Object type, ComponentContext componentContext, Object... args) {
|
||||
protected T doCall(String typeNameOrAlias, ComponentContext componentContext, Object... args) {
|
||||
return switch (this.type) {
|
||||
case ALL -> this.flatCall(type, componentContext, args);
|
||||
case NAME_AND_CONTEXT -> this.closure.call(type, componentContext);
|
||||
case NAME_AND_ARGS -> this.flatCall(type, args);
|
||||
case CONTEXT_AND_ARGS -> this.flatCall(componentContext, args);
|
||||
case NAME_ONLY -> this.closure.call(type);
|
||||
case ALL -> this.closure.call(flatten(typeNameOrAlias, componentContext, args));
|
||||
case NAME_AND_CONTEXT -> this.closure.call(typeNameOrAlias, componentContext);
|
||||
case NAME_AND_ARGS -> this.closure.call(flatten(typeNameOrAlias, args));
|
||||
case CONTEXT_AND_ARGS -> this.closure.call(flatten(componentContext, args));
|
||||
case NAME_ONLY -> this.closure.call(typeNameOrAlias);
|
||||
case CONTEXT_ONLY -> this.closure.call(componentContext);
|
||||
case ARGS_ONLY -> this.closure.call(args);
|
||||
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;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
import groowt.view.component.ViewComponent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
|
||||
@FunctionalInterface
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -94,13 +94,14 @@ public abstract class ComponentFactoryBase<T extends ViewComponent> extends Groo
|
||||
}
|
||||
|
||||
@Override
|
||||
public T create(String type, ComponentContext componentContext, Object... args) {
|
||||
return this.findAndDoCreate(type, componentContext, args);
|
||||
public T create(String typeName, ComponentContext componentContext, Object... args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
// TODO: this needs to be updated.
|
||||
@Override
|
||||
public T create(Class<?> type, ComponentContext componentContext, Object... args) {
|
||||
return this.findAndDoCreate(type, componentContext, args);
|
||||
public T create(String alias, Class<?> type, ComponentContext componentContext, Object... 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 groowt.view.component.ComponentRenderException;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
||||
public class DefaultComponentWriter implements ComponentWriter {
|
||||
|
||||
private final Writer delegate;
|
||||
private RenderContext renderContext;
|
||||
private ComponentContext componentContext;
|
||||
|
||||
public DefaultWebViewComponentWriter(Writer delegate) {
|
||||
public DefaultComponentWriter(Writer 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
|
||||
public void append(String string) {
|
||||
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
|
||||
public void append(ViewComponent viewComponent) {
|
||||
try {
|
||||
viewComponent.renderTo(this.delegate);
|
||||
this.doComponentRender(viewComponent);
|
||||
} catch (IOException ioException) {
|
||||
throw new RuntimeException(ioException);
|
||||
} catch (ComponentRenderException componentRenderException) {
|
||||
throw componentRenderException;
|
||||
} catch (Exception exception) {
|
||||
throw new ComponentRenderException(viewComponent, exception);
|
||||
}
|
||||
@ -64,9 +96,11 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
||||
@Override
|
||||
public void append(ViewComponent viewComponent, int line, int column) {
|
||||
try {
|
||||
viewComponent.renderTo(this.delegate);
|
||||
this.doComponentRender(viewComponent);
|
||||
} catch (IOException ioException) {
|
||||
throw new RuntimeException(ioException);
|
||||
} catch (ComponentRenderException componentRenderException) {
|
||||
throw componentRenderException;
|
||||
} catch (Exception exception) {
|
||||
throw new ComponentRenderException(viewComponent, line, column, exception);
|
||||
}
|
||||
@ -74,20 +108,17 @@ public class DefaultWebViewComponentWriter implements WebViewComponentWriter {
|
||||
|
||||
@Override
|
||||
public void append(Object object) {
|
||||
try {
|
||||
this.delegate.append(object.toString());
|
||||
} catch (IOException 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 GString gString -> this.append(gString);
|
||||
case ViewComponent viewComponent -> this.append(viewComponent);
|
||||
default -> this.append(object);
|
||||
default -> {
|
||||
try {
|
||||
this.delegate.append(object.toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
id 'groowt-conventions'
|
||||
id 'groowt-antlr-plugin'
|
||||
id 'groowt-logging'
|
||||
id 'java-library'
|
||||
id 'groovy'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
@ -19,13 +20,23 @@ configurations {
|
||||
toolsImplementation {
|
||||
extendsFrom(apiElements, runtimeElements)
|
||||
}
|
||||
sketchingImplementation {
|
||||
extendsFrom(apiElements, runtimeElements)
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
sketching {
|
||||
java {
|
||||
compileClasspath += sourceSets.main.output
|
||||
runtimeClasspath += sourceSets.main.output
|
||||
}
|
||||
}
|
||||
tools {
|
||||
java {
|
||||
compileClasspath += sourceSets.main.output
|
||||
runtimeClasspath += sourceSets.main.output
|
||||
runtimeClasspath += sourceSets.sketching.output
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,7 +46,6 @@ dependencies {
|
||||
libs.groovy,
|
||||
libs.groovy.templates,
|
||||
libs.antlr.runtime,
|
||||
libs.classgraph,
|
||||
project(':view-components'),
|
||||
project(':views')
|
||||
)
|
||||
@ -64,6 +74,8 @@ dependencies {
|
||||
groovyConsole libs.groovy.console
|
||||
toolsApi libs.picocli
|
||||
toolsImplementation libs.groovy.console
|
||||
|
||||
sketchingApi libs.groovy
|
||||
}
|
||||
|
||||
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' />
|
||||
|
@ -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.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 {
|
||||
|
||||
@Override
|
||||
protected ComponentScope getNewDefaultScope() {
|
||||
new WebViewScope()
|
||||
DefaultWebViewComponentContext() {
|
||||
this.pushScope(WebViewComponentScope.getDefaultRootScope())
|
||||
}
|
||||
|
||||
@Override
|
||||
@ApiStatus.Internal
|
||||
ViewComponent createFragment(Closure<?> childCollector) {
|
||||
def childCollection = new DefaultWebViewComponentChildCollection()
|
||||
childCollector.call(childCollection)
|
||||
def fragment = new Fragment()
|
||||
fragment.childRenderers = childCollection.children
|
||||
fragment
|
||||
protected ComponentScope getNewDefaultScope() {
|
||||
new WebViewComponentScope()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,20 +3,42 @@ package groowt.view.web
|
||||
import groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.FromString
|
||||
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 {
|
||||
|
||||
static <T extends WebViewComponent> ComponentFactory<T> withAttr(
|
||||
Class<T> forClass,
|
||||
@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) {
|
||||
ComponentFactory.ofClosure { Map<String, Object> attr -> tFunction.apply(attr) }
|
||||
static <T extends WebViewComponent> ComponentFactory<T> withChildren(
|
||||
Class<T> forClass,
|
||||
@ClosureParams(value = FromString, options = 'java.util.List<groowt.view.web.WebViewComponentChild>')
|
||||
Closure<? extends T> closure
|
||||
) {
|
||||
ofClosureClassType(forClass) { WebViewComponentChildCollector childCollector ->
|
||||
closure(childCollector.children)
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends WebViewComponent> ComponentFactory<T> withAttrAndChildren(
|
||||
Class<T> forClass,
|
||||
@ClosureParams(
|
||||
value = FromString,
|
||||
options = 'java.util.Map<String, Object>, java.util.List<groowt.view.web.WebViewComponentChild>'
|
||||
)
|
||||
Closure<? extends T> closure
|
||||
) {
|
||||
ofClosureClassType(forClass) { Map<String, Object> attr, WebViewComponentChildCollector childCollector ->
|
||||
closure(attr, childCollector.children)
|
||||
}
|
||||
}
|
||||
|
||||
private WebViewComponentFactories() {}
|
||||
|
@ -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;
|
||||
|
||||
import groowt.view.View;
|
||||
import groowt.view.web.DefaultWebViewComponent;
|
||||
import groowt.view.web.BaseWebViewComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class DelegatingWebViewComponent extends DefaultWebViewComponent {
|
||||
|
||||
private final Map<String, Object> attr;
|
||||
|
||||
public DelegatingWebViewComponent(Map<String, Object> attr) {
|
||||
this.attr = attr;
|
||||
}
|
||||
|
||||
protected Map<String, Object> getAttr() {
|
||||
return this.attr;
|
||||
}
|
||||
public abstract class DelegatingWebViewComponent extends BaseWebViewComponent {
|
||||
|
||||
protected abstract View getDelegate();
|
||||
|
||||
|
@ -3,8 +3,7 @@ package groowt.view.web.lib
|
||||
import groowt.view.View
|
||||
import groowt.view.component.context.ComponentContext
|
||||
import groowt.view.component.factory.ComponentFactory
|
||||
import groowt.view.component.ComponentRenderException
|
||||
import groowt.view.web.WebViewChildComponentRenderer
|
||||
import groowt.view.web.runtime.WebViewComponentChildCollector
|
||||
|
||||
class Echo extends DelegatingWebViewComponent {
|
||||
|
||||
@ -12,95 +11,50 @@ class Echo extends DelegatingWebViewComponent {
|
||||
|
||||
protected static class EchoFactory implements ComponentFactory<Echo> {
|
||||
|
||||
Echo doCreate(String typeName) {
|
||||
doCreate(typeName, [:], true)
|
||||
protected Echo doCreate() {
|
||||
new Echo([:], [])
|
||||
}
|
||||
|
||||
Echo doCreate(String typeName, boolean selfClose) {
|
||||
doCreate(typeName, [:], selfClose)
|
||||
protected Echo doCreate(Map attr) {
|
||||
new Echo(attr, [])
|
||||
}
|
||||
|
||||
Echo doCreate(String typeName, Map<String, Object> attr) {
|
||||
doCreate(typeName, attr, true)
|
||||
protected Echo doCreate(WebViewComponentChildCollector childCollector) {
|
||||
new Echo([:], childCollector.children)
|
||||
}
|
||||
|
||||
Echo doCreate(String typeName, Map<String, Object> attr, boolean selfClose) {
|
||||
new Echo(attr, typeName, selfClose)
|
||||
}
|
||||
|
||||
Echo doCreate(
|
||||
String typeName,
|
||||
Map<String, Object> attr,
|
||||
List<WebViewChildComponentRenderer> children
|
||||
) {
|
||||
def echo = new Echo(attr, typeName, false)
|
||||
echo.childRenderers = children
|
||||
echo
|
||||
protected Echo doCreate(Map attr, WebViewComponentChildCollector childCollector) {
|
||||
new Echo(attr, childCollector.children)
|
||||
}
|
||||
|
||||
@Override
|
||||
Echo create(String type, ComponentContext componentContext, Object... args) {
|
||||
this.doCreate(type, *args) as Echo
|
||||
Echo create(String typeName, ComponentContext componentContext, Object... args) {
|
||||
throw new UnsupportedOperationException('Cannot create Echo for string type components')
|
||||
}
|
||||
|
||||
@Override
|
||||
Echo create(Class<?> type, ComponentContext componentContext, Object... args) {
|
||||
throw new UnsupportedOperationException('<Echo> can only be used with String types.')
|
||||
Echo create(String alias, Class<?> type, ComponentContext componentContext, Object... args) {
|
||||
this.doCreate(*args)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String name
|
||||
boolean selfClose
|
||||
Map attr
|
||||
|
||||
Echo(Map<String, Object> attr, String name, boolean selfClose) {
|
||||
super(attr)
|
||||
this.name = name
|
||||
this.selfClose = selfClose
|
||||
Echo(Map attr, List children) {
|
||||
this.attr = attr
|
||||
this.children = children
|
||||
}
|
||||
|
||||
Object propertyMissing(String propertyName) {
|
||||
attr[propertyName]
|
||||
}
|
||||
|
||||
@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 << ' '
|
||||
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 << ' '
|
||||
this.children.each {
|
||||
it.render(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
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
|
||||
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
|
||||
|
||||
import groowt.view.View
|
||||
import groowt.view.component.ComponentRenderException
|
||||
import groowt.view.component.context.ComponentContext
|
||||
import groowt.view.component.context.ComponentScope.TypeAndFactory
|
||||
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> {
|
||||
|
||||
IntrinsicHtml doCreate(String typeName) {
|
||||
new IntrinsicHtml([:], typeName, false)
|
||||
new IntrinsicHtml([:], typeName, typeName in voidElements)
|
||||
}
|
||||
|
||||
IntrinsicHtml doCreate(String typeName, boolean selfClose) {
|
||||
@ -18,16 +31,16 @@ class IntrinsicHtml extends Echo {
|
||||
}
|
||||
|
||||
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) {
|
||||
new IntrinsicHtml(attr, typeName, selfClose)
|
||||
}
|
||||
|
||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, List<WebViewChildComponentRenderer> children) {
|
||||
def intrinsicHtml = new IntrinsicHtml(attr, typeName, false)
|
||||
intrinsicHtml.childRenderers = children
|
||||
IntrinsicHtml doCreate(String typeName, Map<String, Object> attr, List<WebViewComponentChild> children) {
|
||||
def intrinsicHtml = new IntrinsicHtml(attr, typeName, typeName in voidElements)
|
||||
intrinsicHtml.children = children
|
||||
intrinsicHtml
|
||||
}
|
||||
|
||||
@ -37,14 +50,50 @@ class IntrinsicHtml extends Echo {
|
||||
}
|
||||
|
||||
@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.')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IntrinsicHtml(Map<String, Object> attr, String elementName, boolean selfClose) {
|
||||
super(attr, elementName, selfClose)
|
||||
Map attr
|
||||
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 groowt.view.component.AbstractViewComponent;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompiler;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
import groowt.view.web.compiler.WebViewComponentTemplateCompiler;
|
||||
import groowt.view.web.runtime.DefaultWebViewComponentWriter;
|
||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||
import groowt.view.component.compiler.source.ComponentTemplateSource;
|
||||
import groowt.view.component.runtime.ComponentWriter;
|
||||
import groowt.view.component.runtime.DefaultComponentWriter;
|
||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
@ -17,7 +17,7 @@ import java.util.function.Function;
|
||||
|
||||
public abstract class AbstractWebViewComponent extends AbstractViewComponent implements WebViewComponent {
|
||||
|
||||
private List<WebViewChildRenderer> childRenderers;
|
||||
private List<WebViewComponentChild> childRenderers;
|
||||
|
||||
public AbstractWebViewComponent() {}
|
||||
|
||||
@ -29,20 +29,18 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
||||
super(templateClass);
|
||||
}
|
||||
|
||||
protected AbstractWebViewComponent(ComponentTemplateSource source, WebViewComponentTemplateCompiler compiler) {
|
||||
super(source, compiler);
|
||||
public AbstractWebViewComponent(
|
||||
Function<? super Class<? extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
|
||||
) {
|
||||
super(compileUnitFunction);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AbstractWebViewComponent(
|
||||
ComponentTemplateSource source,
|
||||
Function<? super Class<? extends AbstractWebViewComponent>, ? extends ComponentTemplateCompiler> compilerFunction
|
||||
) {
|
||||
super(source, selfClass -> compilerFunction.apply((Class<AbstractWebViewComponent>) selfClass));
|
||||
public AbstractWebViewComponent(ComponentTemplateSource source) {
|
||||
this(selfClass -> new WebViewComponentTemplateCompileUnit(selfClass, source, selfClass.getPackageName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebViewChildRenderer> getChildRenderers() {
|
||||
public List<WebViewComponentChild> getChildren() {
|
||||
if (this.childRenderers == null) {
|
||||
this.childRenderers = new ArrayList<>();
|
||||
}
|
||||
@ -51,35 +49,28 @@ public abstract class AbstractWebViewComponent extends AbstractViewComponent imp
|
||||
|
||||
@Override
|
||||
public boolean hasChildren() {
|
||||
return !this.getChildRenderers().isEmpty();
|
||||
return !this.getChildren().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
||||
public void setChildren(List<WebViewComponentChild> children) {
|
||||
this.childRenderers = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderChildren() {
|
||||
for (final var childRenderer : this.getChildRenderers()) {
|
||||
for (final var childRenderer : this.getChildren()) {
|
||||
try {
|
||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
||||
}
|
||||
childRenderer.render(this);
|
||||
} catch (Exception e) {
|
||||
throw new ChildRenderException(e);
|
||||
} finally {
|
||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||
this.getContext().afterComponentRender(childComponentRenderer.getComponent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
renderer.setDelegate(this);
|
||||
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;
|
||||
|
||||
import groovy.lang.GString;
|
||||
import groowt.view.component.ViewComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface WebViewComponent extends ViewComponent {
|
||||
|
||||
List<WebViewChildRenderer> getChildRenderers();
|
||||
List<WebViewComponentChild> getChildren();
|
||||
boolean hasChildren();
|
||||
void setChildRenderers(List<WebViewChildRenderer> children);
|
||||
void setChildren(List<WebViewComponentChild> children);
|
||||
void renderChildren();
|
||||
|
||||
default List<Object> getChildren() {
|
||||
return this.getChildRenderers().stream()
|
||||
.map(childRenderer -> switch (childRenderer) {
|
||||
case WebViewChildComponentRenderer componentRenderer -> componentRenderer.getComponent();
|
||||
case WebViewChildGStringRenderer gStringRenderer -> gStringRenderer.getGString();
|
||||
case WebViewChildJStringRenderer jStringRenderer -> jStringRenderer.getContent();
|
||||
default List<String> getChildStrings() {
|
||||
return this.getChildren().stream()
|
||||
.map(WebViewComponentChild::getChild)
|
||||
.filter(obj -> obj instanceof String || obj instanceof GString)
|
||||
.map(obj -> {
|
||||
if (obj instanceof String s) {
|
||||
return s;
|
||||
} else {
|
||||
return ((GString) obj).toString();
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
default List<GString> getChildGStrings() {
|
||||
return this.getChildren().stream()
|
||||
.map(WebViewComponentChild::getChild)
|
||||
.filter(GString.class::isInstance)
|
||||
.map(GString.class::cast)
|
||||
.toList();
|
||||
}
|
||||
|
||||
default List<WebViewComponent> getChildComponents() {
|
||||
return this.getChildren().stream()
|
||||
.map(WebViewComponentChild::getChild)
|
||||
.filter(WebViewComponent.class::isInstance)
|
||||
.map(WebViewComponent.class::cast)
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public interface WebViewComponentContext extends ComponentContext {
|
||||
|
||||
/**
|
||||
* For use only by compiled web view component templates.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
ViewComponent createFragment(Closure<?> childCollector);
|
||||
|
||||
}
|
||||
public interface WebViewComponentContext extends ComponentContext {}
|
||||
|
@ -75,11 +75,11 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
|
||||
|
||||
@Override
|
||||
public void pushMode(int m) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
final var old = this._mode;
|
||||
super.pushMode(m);
|
||||
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 {
|
||||
super.pushMode(m);
|
||||
}
|
||||
@ -87,10 +87,10 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
|
||||
|
||||
@Override
|
||||
public int popMode() {
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
final var popped = this._mode;
|
||||
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;
|
||||
} else {
|
||||
return super.popMode();
|
||||
@ -273,4 +273,4 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
|
||||
return this.debugHookReturning(name, msgFunction, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,16 @@ fun shortFormatToken(token: Token): String =
|
||||
|
||||
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(
|
||||
parts: List<String>,
|
||||
startLength: Int = 30,
|
||||
|
@ -25,6 +25,7 @@ public non-sealed class ClassComponentTypeNode extends ComponentTypeNode {
|
||||
this.fqn = Objects.requireNonNull(fullyQualifiedName);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getFullyQualifiedName() {
|
||||
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;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.AbstractViewComponent;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.compiler.CachingComponentTemplateCompiler;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||
import groowt.view.component.context.ComponentContext;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
import groowt.view.component.compiler.*;
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
import groowt.view.web.analysis.MismatchedComponentTypeAnalysis;
|
||||
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
||||
import groowt.view.web.antlr.AntlrUtil;
|
||||
import groowt.view.web.antlr.CompilationUnitParseResult;
|
||||
import groowt.view.web.antlr.ParserUtil;
|
||||
import groowt.view.web.antlr.TokenList;
|
||||
import groowt.view.web.antlr.*;
|
||||
import groowt.view.web.ast.DefaultAstBuilder;
|
||||
import groowt.view.web.ast.DefaultNodeFactory;
|
||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
||||
import groowt.view.web.util.SourcePosition;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.antlr.v4.runtime.tree.Tree;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.Phases;
|
||||
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.tools.GroovyClass;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
||||
public class DefaultWebViewComponentTemplateCompiler
|
||||
extends CachingComponentTemplateCompiler<WebViewComponentTemplateCompileUnit>
|
||||
implements WebViewComponentTemplateCompiler {
|
||||
|
||||
protected static final class AnonymousWebViewComponent extends AbstractViewComponent {
|
||||
private final ComponentTemplateCompilerConfiguration configuration;
|
||||
|
||||
// DO NOT INSTANTIATE, this is merely a marker class
|
||||
private AnonymousWebViewComponent() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(ComponentContext context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentContext getContext() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentTemplate getTemplate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTemplate(ComponentTemplate template) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeRender() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterRender() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTo(Writer out) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends AbstractViewComponent> getSelfClass() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final CompilerConfiguration configuration;
|
||||
private final String defaultPackageName;
|
||||
private final int phase;
|
||||
|
||||
public DefaultWebViewComponentTemplateCompiler(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
CompilerConfiguration configuration,
|
||||
String defaultPackageName
|
||||
) {
|
||||
this(groovyClassLoader, configuration, defaultPackageName, Phases.CLASS_GENERATION);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public DefaultWebViewComponentTemplateCompiler(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
CompilerConfiguration configuration,
|
||||
String defaultPackageName,
|
||||
int phase
|
||||
) {
|
||||
super(groovyClassLoader);
|
||||
public DefaultWebViewComponentTemplateCompiler(ComponentTemplateCompilerConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
this.defaultPackageName = defaultPackageName;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
protected WebViewComponentTemplateCompileException getException(
|
||||
TerminalNode terminalNode,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
TerminalNode terminalNode
|
||||
) {
|
||||
final Token offending = terminalNode.getSymbol();
|
||||
return new WebViewComponentTemplateCompileException(
|
||||
"Compile error on token at " + SourcePosition.fromStartOfToken(offending).toStringLong() + ".",
|
||||
forClass,
|
||||
reader,
|
||||
offending
|
||||
final var exception = new WebViewComponentTemplateCompileException(
|
||||
compileUnit, "Invalid token '" + TokenUtil.excerptToken(offending) + "'."
|
||||
);
|
||||
exception.setTerminalNode(terminalNode);
|
||||
return exception;
|
||||
}
|
||||
|
||||
protected WebViewComponentTemplateCompileException getException(
|
||||
ParserRuleContext parserRuleContext,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
ParserRuleContext parserRuleContext
|
||||
) {
|
||||
return new WebViewComponentTemplateCompileException(
|
||||
"Compile error at " + SourcePosition.fromStartOfToken(parserRuleContext.getStart()).toStringLong()
|
||||
+ ".",
|
||||
forClass,
|
||||
reader,
|
||||
parserRuleContext
|
||||
final var exception = new WebViewComponentTemplateCompileException(
|
||||
compileUnit,
|
||||
"Parser error: " + parserRuleContext.exception.getMessage(),
|
||||
parserRuleContext.exception
|
||||
);
|
||||
exception.setParserRuleContext(parserRuleContext);
|
||||
return exception;
|
||||
}
|
||||
|
||||
protected WebViewComponentTemplateCompileException mapToErrorException(
|
||||
Tree tree,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader
|
||||
protected WebViewComponentTemplateCompileException getException(
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
Tree tree
|
||||
) {
|
||||
if (tree instanceof TerminalNode terminalNode) {
|
||||
return getException(terminalNode, forClass, reader);
|
||||
} else if (tree instanceof ParserRuleContext parserRuleContext) {
|
||||
return getException(parserRuleContext, forClass, reader);
|
||||
if (tree instanceof ParserRuleContext parserRuleContext) {
|
||||
return getException(compileUnit, parserRuleContext);
|
||||
} else if (tree instanceof TerminalNode terminalNode) {
|
||||
return getException(compileUnit, terminalNode);
|
||||
} else {
|
||||
return new WebViewComponentTemplateCompileException(
|
||||
"Compile error with " + tree + ".",
|
||||
forClass,
|
||||
reader,
|
||||
tree
|
||||
compileUnit,
|
||||
"Error at parser/lexer node " + tree.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected WebViewComponentTemplateCompileException mapToErrorException(
|
||||
MismatchedComponentTypeError error,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader
|
||||
protected WebViewComponentTemplateCompileException getException(
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
MismatchedComponentTypeError error
|
||||
) {
|
||||
return new WebViewComponentTemplateCompileException(
|
||||
error.getMessage(),
|
||||
forClass,
|
||||
reader,
|
||||
error.getComponent()
|
||||
final var exception = new WebViewComponentTemplateCompileException(
|
||||
compileUnit,
|
||||
error.getMessage()
|
||||
);
|
||||
exception.setParserRuleContext(error.getComponent());
|
||||
return exception;
|
||||
}
|
||||
|
||||
protected ComponentTemplateCompileResult doCompile(
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader reader,
|
||||
@Nullable URI uri
|
||||
) throws ComponentTemplateCompileErrorException {
|
||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
||||
@Override
|
||||
protected ComponentTemplateCompileResult doCompile(WebViewComponentTemplateCompileUnit compileUnit)
|
||||
throws ComponentTemplateCompileException {
|
||||
|
||||
final Reader sourceReader;
|
||||
try {
|
||||
sourceReader = compileUnit.getSource().toReader();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(sourceReader);
|
||||
|
||||
// check for parser/lexer errors
|
||||
final var parseErrors = AntlrUtil.findErrorNodes(parseResult.getCompilationUnitContext());
|
||||
if (!parseErrors.isEmpty()) {
|
||||
if (parseErrors.getErrorCount() == 1) {
|
||||
final var errorNode = parseErrors.getAll().getFirst();
|
||||
throw mapToErrorException(errorNode, forClass, reader);
|
||||
throw getException(compileUnit, errorNode);
|
||||
} else {
|
||||
final var errorExceptions = parseErrors.getAll().stream()
|
||||
.map(errorNode -> mapToErrorException(errorNode, forClass, reader))
|
||||
.map(errorNode -> getException(compileUnit, errorNode))
|
||||
.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.size() == 1) {
|
||||
throw mapToErrorException(mismatchedComponentTypeErrors.getFirst(), forClass, reader);
|
||||
throw getException(compileUnit, mismatchedComponentTypeErrors.getFirst());
|
||||
} else {
|
||||
final var errorExceptions = mismatchedComponentTypeErrors.stream()
|
||||
.map(error -> mapToErrorException(error, forClass, reader))
|
||||
.map(error -> getException(compileUnit, error))
|
||||
.toList();
|
||||
throw new MultipleWebViewComponentCompileErrorsException(
|
||||
errorExceptions,
|
||||
forClass,
|
||||
reader
|
||||
);
|
||||
throw new MultipleWebViewComponentCompileErrorsException(compileUnit, errorExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,63 +133,39 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
||||
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
||||
|
||||
// transpile to Groovy
|
||||
final var groovyCompilationUnit = new CompilationUnit(this.configuration);
|
||||
final var transpiler = new DefaultGroovyTranspiler(
|
||||
groovyCompilationUnit,
|
||||
this.defaultPackageName,
|
||||
DefaultTranspilerConfiguration::new
|
||||
final var transpiler = new DefaultGroovyTranspiler();
|
||||
|
||||
final var ownerComponentName = compileUnit.getForClass() != AnonymousWebViewComponent.class
|
||||
? compileUnit.getForClass().getSimpleName()
|
||||
: "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();
|
||||
final var templateClassName = ownerComponentName + "Template";
|
||||
final var fqn = this.defaultPackageName + "." + templateClassName;
|
||||
|
||||
final var readerSource = new AbstractReaderSource(this.configuration) {
|
||||
|
||||
@Override
|
||||
public Reader getReader() throws IOException {
|
||||
reader.reset();
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
super.cleanup();
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
transpiler.transpile(cuNode, tokenList, ownerComponentName, readerSource);
|
||||
|
||||
// compile groovy
|
||||
try {
|
||||
groovyCompilationUnit.compile(this.phase);
|
||||
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getToCompilePhase().getPhaseNumber());
|
||||
} catch (CompilationFailedException compilationFailedException) {
|
||||
throw new WebViewComponentTemplateCompileException(
|
||||
"Error while compiling Groovy in " + templateClassName + " for component class " +
|
||||
forClass.getName() + ".",
|
||||
compilationFailedException,
|
||||
forClass,
|
||||
forClass,
|
||||
reader
|
||||
compileUnit,
|
||||
"Error while compiling Groovy.",
|
||||
compilationFailedException
|
||||
);
|
||||
}
|
||||
|
||||
// get the classes
|
||||
final var allClasses = groovyCompilationUnit.getClasses();
|
||||
final var allClasses = compileUnit.getGroovyCompilationUnit().getClasses();
|
||||
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) {
|
||||
if (groovyClass.getName().equals(fqn)) {
|
||||
if (groovyClass.getName().equals(templateClassFqn)) {
|
||||
if (templateGroovyClass != null) {
|
||||
throw new IllegalStateException("Already found a templateGroovyClass.");
|
||||
}
|
||||
@ -291,40 +176,13 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
||||
}
|
||||
|
||||
if (templateGroovyClass == null) {
|
||||
throw new IllegalStateException("Did not find templateClass");
|
||||
throw new WebViewComponentBugError(new IllegalStateException("Did not find templateClass"));
|
||||
}
|
||||
|
||||
return new ComponentTemplateCompileResult(templateGroovyClass, otherClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ComponentTemplateCompileResult doCompile(
|
||||
@Nullable ComponentTemplateSource source,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Reader sourceReader
|
||||
) throws ComponentTemplateCompileErrorException {
|
||||
if (source instanceof ComponentTemplateSource.URISource uriSource) {
|
||||
return this.doCompile(forClass, sourceReader, uriSource.templateURI());
|
||||
} else if (source instanceof ComponentTemplateSource.URLSource urlSource) {
|
||||
try {
|
||||
return this.doCompile(forClass, sourceReader, urlSource.templateURL().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
return this.doCompile(forClass, sourceReader, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentTemplateCompileResult compileAnonymous(ComponentTemplateSource source)
|
||||
throws ComponentTemplateCompileErrorException {
|
||||
return this.compile(AnonymousWebViewComponent.class, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentTemplate compileAndGetAnonymous(ComponentTemplateSource source) throws ComponentTemplateCompileErrorException {
|
||||
return this.compileAndGet(AnonymousWebViewComponent.class, source);
|
||||
return new SimpleComponentTemplateCompileResult(
|
||||
templateGroovyClass,
|
||||
otherClasses
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,31 +1,22 @@
|
||||
package groowt.view.web.compiler;
|
||||
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MultipleWebViewComponentCompileErrorsException extends ComponentTemplateCompileErrorException {
|
||||
public class MultipleWebViewComponentCompileErrorsException extends ComponentTemplateCompileException {
|
||||
|
||||
private final List<Throwable> errors = new ArrayList<>();
|
||||
|
||||
public MultipleWebViewComponentCompileErrorsException(
|
||||
String message,
|
||||
List<? extends Throwable> errors,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource
|
||||
ComponentTemplateCompileUnit compileUnit,
|
||||
List<? extends Throwable> errors
|
||||
) {
|
||||
super(message, forClass, templateSource);
|
||||
this.errors.addAll(errors);
|
||||
}
|
||||
|
||||
public MultipleWebViewComponentCompileErrorsException(
|
||||
List<? extends Throwable> errors,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource
|
||||
) {
|
||||
super(forClass, templateSource);
|
||||
super(compileUnit, "There were multiple errors during compilation.");
|
||||
this.errors.addAll(errors);
|
||||
}
|
||||
|
||||
@ -33,4 +24,17 @@ public class MultipleWebViewComponentCompileErrorsException extends ComponentTem
|
||||
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;
|
||||
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
|
||||
import groowt.view.web.antlr.TokenUtil;
|
||||
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(
|
||||
ComponentTemplateCompileUnit compileUnit,
|
||||
String message
|
||||
) {
|
||||
super(compileUnit, message);
|
||||
}
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
ComponentTemplateCompileUnit compileUnit,
|
||||
String message,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Object node
|
||||
Throwable cause
|
||||
) {
|
||||
super(message, forClass, templateSource);
|
||||
this.node = node;
|
||||
super(compileUnit, message, cause);
|
||||
}
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
String message,
|
||||
Throwable cause,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Object node
|
||||
ComponentTemplateCompileUnit compileUnit,
|
||||
Throwable cause
|
||||
) {
|
||||
super(message, cause, forClass, templateSource);
|
||||
this.node = node;
|
||||
super(compileUnit, "There was an error during compilation.", cause);
|
||||
}
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
Throwable cause,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Object node
|
||||
) {
|
||||
super(cause, forClass, templateSource);
|
||||
this.node = node;
|
||||
public @Nullable TerminalNode getTerminalNode() {
|
||||
return this.terminalNode;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void setNode(@Nullable Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
if (this.node instanceof Node asNode) {
|
||||
final SourcePosition start = asNode.getTokenRange().getStartPosition();
|
||||
return "At " + start.toStringLong() + ": " + super.getMessage();
|
||||
protected @Nullable String getPosition() {
|
||||
if (this.node != null) {
|
||||
return this.node.getTokenRange().getStartPosition().toStringLong();
|
||||
} else if (this.parserRuleContext != null) {
|
||||
return TokenUtil.formatTokenPosition(this.parserRuleContext.start);
|
||||
} else if (this.terminalNode != null) {
|
||||
return TokenUtil.formatTokenPosition(terminalNode.getSymbol());
|
||||
} 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;
|
||||
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileErrorException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileResult;
|
||||
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 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.transpile.TranspilerUtil.TranspilerState;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.Statement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ComponentTranspiler {
|
||||
BlockStatement createComponentStatements(
|
||||
List<Statement> createComponentStatements(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state
|
||||
);
|
||||
|
@ -63,7 +63,7 @@ public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementF
|
||||
return this.doCreate(
|
||||
bodyChildNode,
|
||||
rightSide,
|
||||
new VariableExpression(state.out()),
|
||||
new VariableExpression(state.getWriter()),
|
||||
TranspilerUtil.APPEND,
|
||||
true
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package groowt.view.web.transpile;
|
||||
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
import groowt.view.web.ast.node.*;
|
||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||
import jakarta.inject.Inject;
|
||||
@ -30,7 +31,7 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
||||
TranspilerState state
|
||||
) {
|
||||
final BlockStatement block = new BlockStatement();
|
||||
block.setVariableScope(state.currentScope());
|
||||
block.setVariableScope(state.pushScope());
|
||||
for (final Node child : bodyNode.getChildren()) {
|
||||
switch (child) {
|
||||
case GStringBodyTextNode gStringBodyTextNode -> {
|
||||
@ -49,16 +50,17 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
||||
}
|
||||
case ComponentNode componentNode -> {
|
||||
// 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 -> {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
}
|
||||
default -> throw new UnsupportedOperationException(
|
||||
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
|
||||
"BodyNode child of type " + child.getClass().getSimpleName() + " is not supported."
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
state.popScope();
|
||||
return block;
|
||||
}
|
||||
|
||||
|
@ -1,106 +1,283 @@
|
||||
package groowt.view.web.transpile;
|
||||
|
||||
import groovy.lang.Tuple2;
|
||||
import groowt.view.component.*;
|
||||
import groowt.view.component.context.*;
|
||||
import groowt.view.component.context.ComponentResolveException;
|
||||
import groowt.view.component.runtime.ComponentCreateException;
|
||||
import groowt.view.component.runtime.RenderContext;
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
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.util.GroovyUtil;
|
||||
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.expr.*;
|
||||
import org.codehaus.groovy.ast.stmt.*;
|
||||
import org.codehaus.groovy.syntax.Token;
|
||||
import org.codehaus.groovy.syntax.Types;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||
|
||||
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
|
||||
private static final ClassNode VIEW_COMPONENT = ClassHelper.make(ViewComponent.class);
|
||||
private static final ClassNode CHILD_COLLECTION = ClassHelper.make(WebViewComponentChildCollection.class);
|
||||
private static final ClassNode CHILD_COLLECTOR_TYPE = ClassHelper.make(WebViewComponentChildCollector.class);
|
||||
private static final ClassNode FRAGMENT_TYPE = ClassHelper.make(GROOWT_VIEW_WEB + ".lib.Fragment");
|
||||
private static final ClassNode 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 ClassNode COMPONENT_CREATE = ClassHelper.make(ComponentCreateException.class);
|
||||
private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
|
||||
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);
|
||||
private static final ClassNode MISSING_CLASS_TYPE_EXCEPTION = ClassHelper.make(MissingClassTypeException.class);
|
||||
private static final ClassNode MISSING_STRING_TYPE_EXCEPTION = ClassHelper.make(MissingStringTypeException.class);
|
||||
private static final ClassNode MISSING_FRAGMENT_TYPE_EXCEPTION =
|
||||
ClassHelper.make(MissingFragmentTypeException.class);
|
||||
|
||||
private static final String CREATE = "create";
|
||||
private static final String CREATE_FRAGMENT = "createFragment";
|
||||
private static final String RESOLVE = "resolve";
|
||||
private static final String ADD = "add";
|
||||
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 DefaultComponentTranspiler(
|
||||
Provider<AppendOrAddStatementFactory> appendOrAddStatementFactoryProvider,
|
||||
Provider<ComponentClassNodeResolver> componentClassNodeResolverProvider,
|
||||
Provider<ValueNodeTranspiler> valueNodeTranspilerProvider,
|
||||
Provider<BodyTranspiler> bodyTranspilerProvider
|
||||
) {
|
||||
this.appendOrAddStatementFactoryProvider = appendOrAddStatementFactoryProvider;
|
||||
this.componentClassNodeResolverProvider = componentClassNodeResolverProvider;
|
||||
this.valueNodeTranspilerProvider = valueNodeTranspilerProvider;
|
||||
this.bodyTranspilerProvider = bodyTranspilerProvider;
|
||||
}
|
||||
|
||||
public void setBodyTranspiler(BodyTranspiler bodyTranspiler) {
|
||||
this.bodyTranspiler = bodyTranspiler;
|
||||
protected ValueNodeTranspiler getValueNodeTranspiler() {
|
||||
return this.valueNodeTranspilerProvider.get();
|
||||
}
|
||||
|
||||
public void setAppendOrAddStatementFactory(AppendOrAddStatementFactory appendOrAddStatementFactory) {
|
||||
this.appendOrAddStatementFactory = appendOrAddStatementFactory;
|
||||
protected BodyTranspiler getBodyTranspiler() {
|
||||
return this.bodyTranspilerProvider.get();
|
||||
}
|
||||
|
||||
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
|
||||
this.componentClassNodeResolver = componentClassNodeResolver;
|
||||
protected AppendOrAddStatementFactory getAppendOrAddStatementFactory() {
|
||||
return this.appendOrAddStatementFactoryProvider.get();
|
||||
}
|
||||
|
||||
// ViewComponent c0
|
||||
protected ExpressionStatement getComponentDeclaration(Variable component) {
|
||||
final var componentDeclaration = new DeclarationExpression(
|
||||
new VariableExpression(component),
|
||||
new Token(Types.ASSIGN, "=", -1, -1),
|
||||
protected ComponentClassNodeResolver getComponentClassNodeResolver() {
|
||||
return this.componentClassNodeResolverProvider.get();
|
||||
}
|
||||
|
||||
/* 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
|
||||
);
|
||||
return new ExpressionStatement(componentDeclaration);
|
||||
return new ExpressionStatement(declarationExpr);
|
||||
}
|
||||
|
||||
// 'ComponentName'
|
||||
protected ConstantExpression getComponentTypeNameExpression(ComponentNode componentNode) {
|
||||
final String componentTypeName = switch (componentNode) {
|
||||
case TypedComponentNode typedComponentNode -> switch (typedComponentNode.getArgs().getType()) {
|
||||
case ClassComponentTypeNode classComponentTypeNode -> classComponentTypeNode.getFullyQualifiedName();
|
||||
case StringComponentTypeNode stringComponentTypeNode -> stringComponentTypeNode.getIdentifier();
|
||||
};
|
||||
case FragmentComponentNode ignored -> FRAGMENT_FQN;
|
||||
/* RESOLVE */
|
||||
|
||||
protected List<Expression> getArgsAsList(
|
||||
TypedComponentNode componentNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
return makeStringLiteral(componentTypeName);
|
||||
}
|
||||
|
||||
// context.resolve('ComponentName')
|
||||
protected MethodCallExpression getContextResolveExpr(ComponentNode componentNode, Variable componentContext) {
|
||||
final var args = new ArgumentListExpression();
|
||||
args.addExpression(this.getComponentTypeNameExpression(componentNode));
|
||||
return new MethodCallExpression(new VariableExpression(componentContext), RESOLVE, args);
|
||||
// 'h1' | 'MyComponent', MyComponent(.class)
|
||||
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode, TranspilerState state) {
|
||||
final List<Expression> args = this.getArgsAsList(componentNode, state);
|
||||
final ArgumentListExpression argsListExpr = new ArgumentListExpression();
|
||||
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
|
||||
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) {
|
||||
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
||||
case KeyValueAttrNode keyValueAttrNode ->
|
||||
this.valueNodeTranspiler.createExpression(keyValueAttrNode.getValueNode(), state);
|
||||
this.getValueNodeTranspiler().createExpression(keyValueAttrNode.getValueNode(), state);
|
||||
};
|
||||
return new MapEntryExpression(keyExpr, valueExpr);
|
||||
}
|
||||
@ -108,7 +285,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
// [key: value, ...]
|
||||
protected MapExpression getAttrMap(List<AttrNode> attributeNodes, TranspilerState state) {
|
||||
if (attributeNodes.isEmpty()) {
|
||||
throw new IllegalArgumentException("attributeNodes cannot be empty");
|
||||
throw new WebViewComponentBugError(new IllegalArgumentException("attributeNodes cannot be empty."));
|
||||
}
|
||||
final var result = new MapExpression();
|
||||
attributeNodes.stream()
|
||||
@ -117,343 +294,234 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* TYPED COMPONENT CREATE: component constructor */
|
||||
|
||||
// arg0, arg1, arg2, etc
|
||||
protected List<Expression> getConstructorArgs(ComponentConstructorNode componentConstructorNode) {
|
||||
final ConvertResult convertResult = GroovyUtil.convert(componentConstructorNode.getGroovyCode()
|
||||
.getAsValidGroovyCode());
|
||||
final var blockStatement = convertResult.blockStatement();
|
||||
final BlockStatement blockStatement = convertResult.blockStatement();
|
||||
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) {
|
||||
throw new IllegalStateException("statements size is not 1");
|
||||
throw new WebViewComponentBugError(new IllegalStateException("statements.size() != 1"));
|
||||
}
|
||||
final ExpressionStatement exprStmt = (ExpressionStatement) statements.getFirst();
|
||||
final ListExpression listExpr = (ListExpression) exprStmt.getExpression();
|
||||
return listExpr.getExpressions();
|
||||
return listExpr.getExpressions(); // TODO: pos for each expression
|
||||
}
|
||||
|
||||
private void addLineAndColumn(Node sourceNode, ArgumentListExpression args) {
|
||||
final var lineAndColumn = lineAndColumn(sourceNode.getTokenRange().getStartPosition());
|
||||
args.addExpression(lineAndColumn.getV1());
|
||||
args.addExpression(lineAndColumn.getV2());
|
||||
}
|
||||
/* COMPONENT CHILDREN */
|
||||
|
||||
protected MethodCallExpression getOutCall(BodyChildNode sourceNode, TranspilerState state, Expression toOutput) {
|
||||
final VariableExpression outVariableExpr = new VariableExpression(state.out());
|
||||
final ArgumentListExpression args = new ArgumentListExpression();
|
||||
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);
|
||||
// c0cc.add (jString | gString | component)
|
||||
protected Statement getChildCollectorAdd(Variable childCollector, Expression toAdd) {
|
||||
final VariableExpression childCollectorVariableExpr = new VariableExpression(childCollector);
|
||||
final MethodCallExpression methodCall = new MethodCallExpression(
|
||||
childCollectorVariableExpr,
|
||||
ADD,
|
||||
new ArgumentListExpression(List.of(toAdd, renderChild))
|
||||
"add",
|
||||
new ArgumentListExpression(List.of(toAdd))
|
||||
);
|
||||
return new ExpressionStatement(methodCall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tuple containing 1. body ClosureExpression, and 2. childCollector Variable
|
||||
*/
|
||||
// { WebViewComponentChildCollector c0_childCollector -> ... }
|
||||
protected Tuple2<ClosureExpression, Variable> getBodyClosure(
|
||||
// { WebViewComponentChildCollector c0cc -> ... }
|
||||
protected ClosureExpression getChildCollectorClosure(
|
||||
BodyNode bodyNode,
|
||||
TranspilerState state,
|
||||
String componentVariableName
|
||||
TranspilerState state
|
||||
) {
|
||||
final Parameter childCollectorParam = new Parameter(
|
||||
CHILD_COLLECTION,
|
||||
componentVariableName + "_childCollector"
|
||||
final Parameter ccParam = new Parameter(
|
||||
CHILD_COLLECTOR_TYPE,
|
||||
this.getComponentName(state.getCurrentComponentNumber()) + "cc"
|
||||
);
|
||||
|
||||
final var scope = state.pushScope();
|
||||
scope.putDeclaredVariable(childCollectorParam);
|
||||
state.pushChildCollector(childCollectorParam);
|
||||
final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(
|
||||
scope.putDeclaredVariable(ccParam);
|
||||
state.pushChildCollector(ccParam);
|
||||
|
||||
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody(
|
||||
bodyNode,
|
||||
(sourceNode, expr) -> this.getChildCollectorAdd(sourceNode, state, childCollectorParam, expr),
|
||||
(sourceNode, expr) -> this.getChildCollectorAdd(ccParam, expr),
|
||||
state
|
||||
);
|
||||
|
||||
// clean up
|
||||
state.popChildCollector();
|
||||
state.popScope();
|
||||
|
||||
final ClosureExpression bodyClosure = new ClosureExpression(
|
||||
new Parameter[] { childCollectorParam },
|
||||
return new ClosureExpression(
|
||||
new Parameter[] { ccParam },
|
||||
bodyStatements
|
||||
);
|
||||
return new Tuple2<>(bodyClosure, childCollectorParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tuple containing 1. create Expression,
|
||||
* and 2. childCollector Variable, possibly {@code null}.
|
||||
*/
|
||||
protected StaticMethodCallExpression getChildCollectorGetter(
|
||||
BodyNode bodyNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
final ArgumentListExpression args = new ArgumentListExpression();
|
||||
args.addExpression(VariableExpression.THIS_EXPRESSION);
|
||||
args.addExpression(this.getChildCollectorClosure(bodyNode, state));
|
||||
|
||||
return new StaticMethodCallExpression(
|
||||
CHILD_COLLECTOR_CLOSURE_TYPE,
|
||||
"get",
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
/* TYPED COMPONENT CREATE: expression and statement */
|
||||
|
||||
// context.create(...) {...}
|
||||
protected Tuple2<MethodCallExpression, @Nullable Variable> getCreateExpression(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state,
|
||||
String componentVariableName
|
||||
protected MethodCallExpression getTypedComponentCreateExpression(
|
||||
TypedComponentNode componentNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
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();
|
||||
if (!attributeNodes.isEmpty()) {
|
||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
||||
}
|
||||
final ComponentConstructorNode constructorNode = typedComponentNode.getArgs().getConstructor();
|
||||
if (constructorNode != null) {
|
||||
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
||||
}
|
||||
createArgs.addExpression(new VariableExpression(state.getCurrentResolved()));
|
||||
|
||||
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
||||
if (bodyNode != null) {
|
||||
final var bodyResult = this.getBodyClosure(bodyNode, state, componentVariableName);
|
||||
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 List<AttrNode> attributeNodes = componentNode.getArgs().getAttributes();
|
||||
if (!attributeNodes.isEmpty()) {
|
||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
||||
}
|
||||
final ComponentConstructorNode constructorNode = componentNode.getArgs().getConstructor();
|
||||
if (constructorNode != null) {
|
||||
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
||||
}
|
||||
|
||||
final var createCall = new MethodCallExpression(
|
||||
new VariableExpression(state.context()),
|
||||
createName,
|
||||
createArgs
|
||||
);
|
||||
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
||||
if (bodyNode != null) {
|
||||
createArgs.addExpression(this.getChildCollectorGetter(bodyNode, state));
|
||||
}
|
||||
|
||||
return new Tuple2<>(createCall, childCollector);
|
||||
return new MethodCallExpression(new VariableExpression(state.getRenderContext()), "create", createArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tuple containing 1. assignment ExpressionStatement,
|
||||
* and 2. childCollector Variable, possibly {@code null}.
|
||||
*/
|
||||
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
||||
protected Tuple2<ExpressionStatement, @Nullable Variable> getCreateAssignStatement(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state,
|
||||
String componentVariableName,
|
||||
Variable component
|
||||
protected ExpressionStatement getTypedComponentCreateStatement(
|
||||
TypedComponentNode componentNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
final var componentAssignLeft = new VariableExpression(component);
|
||||
final var createExprResult = this.getCreateExpression(componentNode, state, componentVariableName);
|
||||
final var componentAssignExpr = new BinaryExpression(
|
||||
componentAssignLeft,
|
||||
new Token(Types.ASSIGN, "=", -1, -1),
|
||||
createExprResult.getV1()
|
||||
);
|
||||
return new Tuple2<>(new ExpressionStatement(componentAssignExpr), createExprResult.getV2());
|
||||
final var left = new VariableExpression(state.getCurrentComponent());
|
||||
final var right = this.getTypedComponentCreateExpression(componentNode, state);
|
||||
final var componentAssignExpr = new BinaryExpression(left, getAssignToken(), right);
|
||||
return new ExpressionStatement(componentAssignExpr);
|
||||
}
|
||||
|
||||
// catch (NoFactoryMissingException c0nfme) {
|
||||
// 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);
|
||||
/* CREATE CATCH */
|
||||
|
||||
final var lineAndColumn = lineAndColumn(componentNode.getTokenRange().getStartPosition());
|
||||
final ConstantExpression line = lineAndColumn.getV1();
|
||||
final ConstantExpression column = lineAndColumn.getV2();
|
||||
|
||||
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);
|
||||
// catch (ComponentCreateException c0CreateException) { ... }
|
||||
protected CatchStatement getTypedCreateCatch(TypedComponentNode componentNode, TranspilerState state) {
|
||||
final String exceptionName = this.getComponentName(state.getCurrentComponentNumber()) + "CreateException";
|
||||
final Parameter exceptionParam = new Parameter(COMPONENT_CREATE_EXCEPTION_TYPE, exceptionName);
|
||||
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
||||
|
||||
final ConstantExpression componentTypeExpression = switch (componentNode) {
|
||||
case TypedComponentNode typedComponentNode -> switch (typedComponentNode.getArgs().getType()) {
|
||||
case StringComponentTypeNode stringComponentTypeNode ->
|
||||
makeStringLiteral(stringComponentTypeNode.getIdentifier());
|
||||
case ClassComponentTypeNode classComponentTypeNode ->
|
||||
makeStringLiteral(classComponentTypeNode.getFullyQualifiedName());
|
||||
};
|
||||
case FragmentComponentNode ignored -> makeStringLiteral(FRAGMENT_FQN);
|
||||
};
|
||||
final VariableScope scope = new VariableScope();
|
||||
scope.putDeclaredVariable(exceptionParam);
|
||||
|
||||
final var lineAndColumn = lineAndColumn(componentNode.getTokenRange().getStartPosition());
|
||||
final List<Statement> statements = new ArrayList<>();
|
||||
|
||||
final ConstructorCallExpression cce = new ConstructorCallExpression(
|
||||
COMPONENT_CREATE,
|
||||
new ArgumentListExpression(List.of(
|
||||
componentTypeExpression,
|
||||
VariableExpression.THIS_EXPRESSION,
|
||||
lineAndColumn.getV1(),
|
||||
lineAndColumn.getV2(),
|
||||
exceptionVar
|
||||
))
|
||||
final BinaryExpression setTemplateExpression = new BinaryExpression(
|
||||
new PropertyExpression(exceptionVar, "template"),
|
||||
getAssignToken(),
|
||||
VariableExpression.THIS_EXPRESSION
|
||||
);
|
||||
final Statement throwCcStmt = new ThrowStatement(cce);
|
||||
return new CatchStatement(exceptionParam, throwCcStmt);
|
||||
}
|
||||
statements.add(new ExpressionStatement(setTemplateExpression));
|
||||
|
||||
protected List<CatchStatement> getCreateCatches(ComponentNode componentNode, String componentVariableName) {
|
||||
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;
|
||||
}
|
||||
final SourcePosition start = componentNode.getTokenRange().getStartPosition();
|
||||
|
||||
protected Statement createSetContext(TranspilerState state, Variable component) {
|
||||
final VariableExpression componentExpr = new VariableExpression(component);
|
||||
final VariableExpression contextExpr = new VariableExpression(state.context());
|
||||
final var args = new ArgumentListExpression(contextExpr);
|
||||
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
|
||||
final BinaryExpression setLineExpression = new BinaryExpression(
|
||||
new PropertyExpression(exceptionVar, "line"),
|
||||
getAssignToken(),
|
||||
new ConstantExpression(start.line())
|
||||
);
|
||||
statements.add(new ExpressionStatement(setLineExpression));
|
||||
|
||||
// try { ... } catch { ... }
|
||||
final var tryCreateStatement = new TryCatchStatement(
|
||||
createAssignStatementResult.getV1(),
|
||||
final BinaryExpression setColumnExpression = new BinaryExpression(
|
||||
new PropertyExpression(exceptionVar, "column"),
|
||||
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
|
||||
);
|
||||
this.getCreateCatches(componentNode, componentVariableName).forEach(tryCreateStatement::addCatch);
|
||||
result.addStatement(tryCreateStatement);
|
||||
createTryCatch.addCatch(this.getTypedCreateCatch(componentNode, state));
|
||||
return List.of(declaration, createTryCatch);
|
||||
}
|
||||
|
||||
// component.setContext(context)
|
||||
result.addStatement(this.createSetContext(state, component));
|
||||
/* FRAGMENT COMPONENT */
|
||||
|
||||
// out or collect
|
||||
final var addOrAppend = this.appendOrAddStatementFactory.addOrAppend(
|
||||
componentNode,
|
||||
state,
|
||||
action -> switch (action) {
|
||||
case ADD -> {
|
||||
final var args = new ArgumentListExpression();
|
||||
args.addExpression(component);
|
||||
final var outComponent = new VariableExpression(component);
|
||||
outComponent.setClosureSharedVariable(true);
|
||||
final Statement renderStatement = this.appendOrAddStatementFactory.appendOnly(
|
||||
componentNode,
|
||||
state,
|
||||
outComponent
|
||||
);
|
||||
final ClosureExpression renderArg = new ClosureExpression(
|
||||
Parameter.EMPTY_ARRAY,
|
||||
renderStatement
|
||||
);
|
||||
args.addExpression(renderArg);
|
||||
yield args;
|
||||
}
|
||||
case APPEND -> new VariableExpression(component);
|
||||
}
|
||||
// context.createFragment(new Fragment(), <child cl>)
|
||||
protected MethodCallExpression getFragmentCreateExpression(
|
||||
FragmentComponentNode componentNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
final Expression fragmentConstructor = new ConstructorCallExpression(
|
||||
FRAGMENT_TYPE,
|
||||
ArgumentListExpression.EMPTY_ARGUMENTS
|
||||
);
|
||||
result.addStatement(addOrAppend);
|
||||
final Expression ccClosure = this.getChildCollectorGetter(componentNode.getBody(), state);
|
||||
|
||||
return result;
|
||||
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,
|
||||
state,
|
||||
new VariableExpression(state.getCurrentComponent())
|
||||
);
|
||||
|
||||
// cleanup
|
||||
state.popResolved();
|
||||
state.popComponent();
|
||||
|
||||
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,
|
||||
state,
|
||||
this.getFragmentCreateExpression(fragmentComponentNode, state)
|
||||
);
|
||||
return List.of(addOrAppend);
|
||||
} else {
|
||||
throw new WebViewComponentBugError(new IllegalArgumentException(
|
||||
"Cannot handle a ComponentNode not of type TypedComponentNode or FragmentComponentNode."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,28 +1,32 @@
|
||||
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.CompilationUnitNode;
|
||||
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 org.codehaus.groovy.ast.*;
|
||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||
import org.codehaus.groovy.ast.expr.DeclarationExpression;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||
import org.codehaus.groovy.ast.stmt.ReturnStatement;
|
||||
import org.codehaus.groovy.ast.stmt.Statement;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.ErrorCollector;
|
||||
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.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||
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 final CompilationUnit groovyCompilationUnit;
|
||||
private final String defaultPackageName;
|
||||
private final Supplier<? extends TranspilerConfiguration> configurationSupplier;
|
||||
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
|
||||
private static final ClassNode RENDER_CONTEXT_IMPLEMENTATION =
|
||||
ClassHelper.make(DefaultWebViewComponentRenderContext.class);
|
||||
|
||||
public DefaultGroovyTranspiler(
|
||||
CompilationUnit groovyCompilationUnit,
|
||||
@Nullable String defaultPackageName,
|
||||
Supplier<? extends TranspilerConfiguration> configurationSupplier
|
||||
protected TranspilerConfiguration getConfiguration(
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
ModuleNode moduleNode,
|
||||
ClassLoader classLoader
|
||||
) {
|
||||
this.groovyCompilationUnit = groovyCompilationUnit;
|
||||
this.defaultPackageName = defaultPackageName;
|
||||
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();
|
||||
}
|
||||
return new DefaultTranspilerConfiguration(new ClassLoaderComponentClassNodeResolver(
|
||||
compileUnit, moduleNode, classLoader
|
||||
));
|
||||
}
|
||||
|
||||
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<>();
|
||||
for (final var classNode : classNodes) {
|
||||
if (classNode instanceof InnerClassNode innerClassNode) {
|
||||
@ -102,18 +93,23 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
}
|
||||
|
||||
protected void handlePreamble(
|
||||
String templateName,
|
||||
String templateClassName,
|
||||
PreambleNode preambleNode,
|
||||
ClassNode mainClassNode,
|
||||
WebViewComponentModuleNode moduleNode
|
||||
) {
|
||||
final GroovyUtil.ConvertResult preambleConvert = GroovyUtil.convert(
|
||||
final GroovyUtil.ConvertResult convertResult = GroovyUtil.convert(
|
||||
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) {
|
||||
// Fields
|
||||
final List<Statement> preambleStatements = preambleBlock.getStatements();
|
||||
@ -133,7 +129,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
"Currently, only classes, methods, and field declarations " +
|
||||
"(marked with @groovy.transform.Field) " +
|
||||
"are supported. The rest will be ignored.",
|
||||
templateName
|
||||
templateClassName
|
||||
);
|
||||
}
|
||||
declarationsWithField.forEach(declaration -> {
|
||||
@ -142,16 +138,16 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
}
|
||||
|
||||
// move methods from script class
|
||||
final ClassNode scriptClass = preambleConvert.scriptClass();
|
||||
final ClassNode scriptClass = convertResult.scriptClass();
|
||||
if (scriptClass != null) {
|
||||
scriptClass.getMethods().forEach(mainClassNode::addMethod);
|
||||
}
|
||||
|
||||
// handle classes
|
||||
final List<ClassNode> classNodes = preambleConvert.classNodes();
|
||||
this.checkPreambleClasses(templateName, classNodes);
|
||||
final List<ClassNode> classNodes = convertResult.classNodes();
|
||||
this.checkPreambleClasses(templateClassName, classNodes);
|
||||
final List<ClassNode> toInner = classNodes.stream()
|
||||
.filter(classNode -> classNode != preambleConvert.scriptClass())
|
||||
.filter(classNode -> classNode != convertResult.scriptClass())
|
||||
.filter(classNode -> !classNode.isScript())
|
||||
.toList();
|
||||
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,
|
||||
// and don't forget to call run in our render method
|
||||
@Override
|
||||
public void transpile(
|
||||
public WebViewComponentSourceUnit transpile(
|
||||
ComponentTemplateCompilerConfiguration compilerConfiguration,
|
||||
WebViewComponentTemplateCompileUnit compileUnit,
|
||||
CompilationUnitNode compilationUnitNode,
|
||||
TokenList tokens,
|
||||
String ownerComponentName,
|
||||
ReaderSource readerSource
|
||||
) {
|
||||
final var configuration = this.getConfiguration();
|
||||
final String templateName = ownerComponentName + "Template";
|
||||
|
||||
String templateClassName
|
||||
) throws ComponentTemplateCompileException {
|
||||
final var groovyCompilerConfiguration = compilerConfiguration.getGroovyCompilerConfiguration();
|
||||
final var sourceUnit = new WebViewComponentSourceUnit(
|
||||
templateName,
|
||||
readerSource,
|
||||
this.groovyCompilationUnit.getConfiguration(),
|
||||
this.groovyCompilationUnit.getClassLoader(),
|
||||
this.groovyCompilationUnit.getErrorCollector()
|
||||
templateClassName,
|
||||
compileUnit,
|
||||
groovyCompilerConfiguration,
|
||||
compilerConfiguration.getGroovyClassLoader(),
|
||||
new ErrorCollector(groovyCompilerConfiguration)
|
||||
);
|
||||
|
||||
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
|
||||
sourceUnit.setModuleNode(moduleNode);
|
||||
|
||||
final String packageName = this.getPackageName(moduleNode);
|
||||
moduleNode.setPackageName(packageName);
|
||||
moduleNode.setPackageName(compileUnit.getDefaultPackageName());
|
||||
|
||||
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(
|
||||
packageName + "." + templateName,
|
||||
compileUnit.getDefaultPackageName() + "." + templateClassName,
|
||||
ACC_PUBLIC,
|
||||
ClassHelper.OBJECT_TYPE
|
||||
);
|
||||
@ -199,15 +200,73 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
// preamble
|
||||
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
|
||||
if (preambleNode != null) {
|
||||
this.handlePreamble(templateName, preambleNode, mainClassNode, moduleNode);
|
||||
this.handlePreamble(templateClassName, preambleNode, mainClassNode, moduleNode);
|
||||
}
|
||||
|
||||
// renderer
|
||||
final var renderBlock = new BlockStatement();
|
||||
// getRenderer
|
||||
// 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();
|
||||
renderBlock.setVariableScope(state.currentScope());
|
||||
// closure body
|
||||
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();
|
||||
if (bodyNode != null) {
|
||||
final var appendOrAddStatementFactory = configuration.getAppendOrAddStatementFactory();
|
||||
@ -215,22 +274,26 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
configuration.getBodyTranspiler()
|
||||
.transpileBody(
|
||||
compilationUnitNode.getBodyNode(),
|
||||
(source, expr) -> appendOrAddStatementFactory.addOrAppend(source, state, action -> {
|
||||
if (action == AppendOrAddStatementFactory.Action.ADD) {
|
||||
throw new IllegalStateException("Should not be adding here!");
|
||||
}
|
||||
return expr;
|
||||
}),
|
||||
state
|
||||
(source, expr) -> appendOrAddStatementFactory.addOrAppend(
|
||||
source,
|
||||
state,
|
||||
action -> {
|
||||
if (action == AppendOrAddStatementFactory.Action.ADD) {
|
||||
throw new WebViewComponentBugError(new IllegalStateException(
|
||||
"Should not be adding from document root!"
|
||||
));
|
||||
}
|
||||
return expr;
|
||||
}),
|
||||
state
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderBlock.addStatement(new ReturnStatement(ConstantExpression.NULL));
|
||||
|
||||
final ClosureExpression renderer = new ClosureExpression(
|
||||
new Parameter[] {
|
||||
(Parameter) state.context(),
|
||||
(Parameter) state.out()
|
||||
},
|
||||
new Parameter[] { componentContextParam, writerParam },
|
||||
renderBlock
|
||||
);
|
||||
|
||||
@ -250,7 +313,16 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
);
|
||||
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;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
|
||||
import groowt.view.web.util.Provider;
|
||||
|
||||
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||
|
||||
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
|
||||
private final BodyTranspiler bodyTranspiler;
|
||||
private final ValueNodeTranspiler valueNodeTranspiler;
|
||||
|
||||
@Inject
|
||||
public DefaultTranspilerConfiguration() {
|
||||
public DefaultTranspilerConfiguration(ComponentClassNodeResolver classNodeResolver) {
|
||||
final var positionSetter = new SimplePositionSetter();
|
||||
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
||||
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
||||
final var componentTranspiler = new DefaultComponentTranspiler();
|
||||
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
||||
|
||||
final var componentTranspiler = new DefaultComponentTranspiler(
|
||||
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);
|
||||
|
||||
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
||||
componentTranspiler.setAppendOrAddStatementFactory(this.appendOrAddStatementFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,4 +33,8 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||
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