Compare commits

..

8 Commits

32 changed files with 186 additions and 585 deletions

View File

@ -24,9 +24,12 @@ For example:
- [ ] Get rid of wvc compiler dependency on di
## 0.1.3
- [ ] refactor tools/gradle start scripts to use dist instead of custom bin script
- [ ] have custom bin/* scripts which point to dist(s) for convenience
- [ ] di bug: @Singleton toSelf() causes stack overflow
- [ ] ~~refactor tools/gradle start scripts to use dist instead of custom bin script~~
- [ ] ~~have custom bin/* scripts which point to dist(s) for convenience~~
- [x] di bug: @Singleton toSelf() causes stack overflow
- [x] wvcc bug: Nested static view classes are not seen by compiler
- This required tweaking how the configurations are passed around. Ultimately, we should strive for less complexity
in this regard.
- [ ] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
- [ ] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.

View File

@ -3,31 +3,58 @@ package groowt.util.di;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class DefaultRegistry implements Registry {
protected record ClassKeyBinding<T>(Class<T> key, Binding<T> binding) {}
protected static class BindingContainer {
protected final Collection<ClassKeyBinding<?>> classBindings = new ArrayList<>();
private final Map<Class<?>, Binding<?>> bindings = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> @Nullable Binding<T> get(Class<T> key) {
for (final var entry : bindings.entrySet()) {
if (entry.getKey().isAssignableFrom(key)) {
return (Binding<T>) entry.getValue();
}
}
return null;
}
public <T> void put(Class<T> key, Binding<T> binding) {
this.bindings.put(key, binding);
}
public void remove(Class<?> key) {
this.bindings.remove(key);
}
public <T> void removeIf(Class<T> key, Predicate<? super Binding<T>> filter) {
if (filter.test(this.get(key))) {
this.bindings.remove(key);
}
}
public void clear() {
this.bindings.clear();
}
}
protected final BindingContainer bindingContainer = new BindingContainer();
protected final Collection<RegistryExtension> extensions = new ArrayList<>();
@Override
public void removeBinding(Class<?> key) {
this.classBindings.removeIf(classKeyBinding -> classKeyBinding.key().equals(key));
this.bindingContainer.remove(key);
}
@SuppressWarnings("unchecked")
@Override
public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) {
this.classBindings.removeIf(classKeyBinding ->
classKeyBinding.key().equals(key) && filter.test((Binding<T>) classKeyBinding.binding())
);
this.bindingContainer.removeIf(key, filter);
}
private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) {
@ -117,18 +144,12 @@ public class DefaultRegistry implements Registry {
public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) {
final var configurator = new SimpleBindingConfigurator<>(key);
configure.accept(configurator);
this.classBindings.add(new ClassKeyBinding<>(key, configurator.getBinding()));
this.bindingContainer.put(key, configurator.getBinding());
}
@SuppressWarnings("unchecked")
@Override
public @Nullable <T> Binding<T> getBinding(Class<T> key) {
for (final var classKeyBinding : this.classBindings) {
if (key.isAssignableFrom(classKeyBinding.key())) {
return (Binding<T>) classKeyBinding.binding();
}
}
return null;
public <T> @Nullable Binding<T> getBinding(Class<T> key) {
return this.bindingContainer.get(key);
}
private KeyBinder<?> findKeyBinder(Class<?> keyClass) {
@ -189,7 +210,7 @@ public class DefaultRegistry implements Registry {
@Override
public void clearAllBindings() {
this.classBindings.clear();
this.bindingContainer.clear();
for (final var extension : this.extensions) {
if (extension instanceof KeyBinder<?> keyBinder) {
keyBinder.clearAllBindings();

View File

@ -1,8 +1,9 @@
package groowt.util.di;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import static groowt.util.di.BindingUtil.toSingleton;
import static groowt.util.di.BindingUtil.toLazySingleton;
public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
@ -19,12 +20,22 @@ public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
RegistryObjectFactory objectFactory
) {
final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass);
if (potentialBinding != null) {
return potentialBinding;
} else {
this.owner.bind(dependencyClass, toSingleton(objectFactory.createInstance(dependencyClass)));
return this.owner.getBinding(dependencyClass);
}
return switch (potentialBinding) {
case ClassBinding<T>(Class<T> from, Class<? extends T> to) -> {
this.owner.bind(from, toLazySingleton(() -> objectFactory.createInstance(to)));
yield this.owner.getBinding(from);
}
case ProviderBinding<T>(Class<T> from, Provider<? extends T> provider) -> {
this.owner.bind(from, toLazySingleton(provider::get));
yield this.owner.getBinding(from);
}
case SingletonBinding<T> singletonBinding -> singletonBinding;
case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding;
case null -> {
this.owner.bind(dependencyClass, toLazySingleton(() -> objectFactory.createInstance(dependencyClass)));
yield this.owner.getBinding(dependencyClass);
}
};
}
@Override

View File

@ -2,6 +2,7 @@ package groowt.util.di;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.junit.jupiter.api.Test;
import static groowt.util.di.BindingUtil.*;
@ -250,4 +251,25 @@ public class DefaultRegistryObjectFactoryTests {
assertEquals("Given Greeting", g.greet());
}
@Singleton
public static final class SingletonGreeter implements Greeter {
@Override
public String greet() {
return "Hello, World!";
}
}
@Test
public void singletonDoesNotOverflow() {
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
b.configureRegistry(r -> {
r.bind(SingletonGreeter.class, toSelf());
});
final var f = b.build();
final var g = f.get(SingletonGreeter.class);
assertEquals("Hello, World!", g.greet());
}
}

View File

@ -10,41 +10,8 @@ public abstract class CachingComponentTemplateCompiler<U extends ComponentTempla
private final Map<Class<? extends ViewComponent>, ComponentTemplateCompileResult> cache = new HashMap<>();
// 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 ComponentTemplateCompileResult compile(U compileUnit)
throws ComponentTemplateCompileException {
public final ComponentTemplateCompileResult compile(U compileUnit) throws ComponentTemplateCompileException {
if (this.cache.containsKey(compileUnit.getForClass())) {
return this.cache.get(compileUnit.getForClass());
} else {

View File

@ -4,16 +4,9 @@ import groowt.view.component.ViewComponent;
import groowt.view.component.compiler.source.ComponentTemplateSource;
public interface ComponentTemplateCompileUnit {
String getDescriptiveName();
Class<? extends ViewComponent> getForClass();
String getDefaultPackageName();
ComponentTemplateSource getSource();
ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
throws ComponentTemplateCompileException;
default ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
return this.compile(new DefaultComponentTemplateCompilerConfiguration());
}
ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException;
}

View File

@ -1,11 +0,0 @@
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();
}

View File

@ -1,48 +0,0 @@
package groowt.view.component.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import static java.util.Objects.requireNonNull;
public class DefaultComponentTemplateCompilerConfiguration implements ComponentTemplateCompilerConfiguration {
private GroovyClassLoader groovyClassLoader;
private CompilerConfiguration groovyCompilerConfiguration;
private CompilePhase toCompilePhase;
public DefaultComponentTemplateCompilerConfiguration() {
this.groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
this.groovyCompilerConfiguration = new CompilerConfiguration();
this.toCompilePhase = CompilePhase.CLASS_GENERATION;
}
@Override
public GroovyClassLoader getGroovyClassLoader() {
return this.groovyClassLoader;
}
public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
this.groovyClassLoader = requireNonNull(groovyClassLoader);
}
@Override
public CompilerConfiguration getGroovyCompilerConfiguration() {
return this.groovyCompilerConfiguration;
}
public void setGroovyCompilerConfiguration(CompilerConfiguration groovyCompilerConfiguration) {
this.groovyCompilerConfiguration = requireNonNull(groovyCompilerConfiguration);
}
@Override
public CompilePhase getToCompilePhase() {
return this.toCompilePhase;
}
public void setToCompilePhase(CompilePhase toCompilePhase) {
this.toCompilePhase = requireNonNull(toCompilePhase);
}
}

View File

@ -199,7 +199,9 @@ tasks.register('uberJar', Jar) {
group 'groovyc'
archiveBaseName = 'web-view-components-uber'
from sourceSets.main.output
from sourceSets.main.runtimeClasspath.filter(File.&exists).collect { it.isDirectory() ? it : zipTree(it) }
from sourceSets.main.runtimeClasspath
.filter(File.&exists)
.collect { it.isDirectory() ? it : zipTree(it) }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

View File

@ -4,14 +4,15 @@ if [ "$1" == "--debug" ]; then
shift
gradle -q uberJar && \
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 \
-cp build/libs/web-view-components-uber-0.1.0.jar \
-cp build/libs/web-view-components-uber-0.1.2.jar \
org.codehaus.groovy.tools.FileSystemCompiler \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"
else
gradle -q uberJar && \
groovyc -cp build/libs/web-view-components-uber-0.1.0.jar \
java -cp build/libs/web-view-components-uber-0.1.2.jar \
org.codehaus.groovy.tools.FileSystemCompiler \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"

View File

@ -0,0 +1 @@
<Echo>Hello, World!</Echo>

View File

@ -4,7 +4,6 @@ import groowt.view.component.compiler.*;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.transpile.DefaultGroovyTranspiler;
import org.antlr.v4.runtime.ParserRuleContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.tools.GroovyClass;
@ -16,25 +15,12 @@ public class DefaultWebViewComponentTemplateCompiler
extends CachingComponentTemplateCompiler<WebViewComponentTemplateCompileUnit>
implements WebViewComponentTemplateCompiler {
private final ComponentTemplateCompilerConfiguration configuration;
private final WebViewComponentTemplateCompilerConfiguration configuration;
public DefaultWebViewComponentTemplateCompiler(ComponentTemplateCompilerConfiguration configuration) {
public DefaultWebViewComponentTemplateCompiler(WebViewComponentTemplateCompilerConfiguration configuration) {
this.configuration = configuration;
}
protected WebViewComponentTemplateCompileException getException(
WebViewComponentTemplateCompileUnit compileUnit,
ParserRuleContext parserRuleContext
) {
final var exception = new WebViewComponentTemplateCompileException(
compileUnit,
"Parser error: " + parserRuleContext.exception.getMessage(),
parserRuleContext.exception
);
exception.setParserRuleContext(parserRuleContext);
return exception;
}
@Override
protected ComponentTemplateCompileResult doCompile(WebViewComponentTemplateCompileUnit compileUnit)
throws ComponentTemplateCompileException {
@ -49,22 +35,12 @@ public class DefaultWebViewComponentTemplateCompiler
: "AnonymousWebViewComponent" + System.nanoTime();
final var templateClassSimpleName = ownerComponentName + "Template";
final SourceUnit sourceUnit = transpiler.transpile(
this.configuration,
compileUnit,
cuNode,
templateClassSimpleName
);
final SourceUnit sourceUnit = transpiler.transpile(compileUnit, cuNode, templateClassSimpleName);
compileUnit.getGroovyCompilationUnit().addSource(sourceUnit);
// set the groovy compile unit's class loader to the configuration's classloader.
compileUnit.getGroovyCompilationUnit().setClassLoader(
this.configuration.getGroovyClassLoader()
);
// compile groovy
try {
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getToCompilePhase().getPhaseNumber());
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getCompilePhase().getPhaseNumber());
} catch (CompilationFailedException compilationFailedException) {
throw new WebViewComponentTemplateCompileException(
compileUnit,

View File

@ -1,11 +1,9 @@
package groowt.view.component.web.compiler;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
public class DefaultWebViewComponentTemplateCompilerFactory implements WebViewComponentTemplateCompilerFactory {
@Override
public WebViewComponentTemplateCompiler create(ComponentTemplateCompilerConfiguration configuration) {
public WebViewComponentTemplateCompiler create(WebViewComponentTemplateCompilerConfiguration configuration) {
return new DefaultWebViewComponentTemplateCompiler(configuration);
}

View File

@ -1,7 +1,6 @@
package groowt.view.component.web.groovyc;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration;
import groowt.view.component.compiler.source.ComponentTemplateSource;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.CompilationUnitNode;
@ -80,13 +79,12 @@ public class DelegatingWebViewComponentTemplateParserPlugin implements ParserPlu
}
final var groovyTranspiler = new DefaultGroovyTranspiler();
final String teplateClassSimpleName = sourceUnitFileName.substring(0, sourceUnitFileName.length() - 4);
final String templateClassName = sourceUnitFileName.substring(0, sourceUnitFileName.length() - 4);
try {
final SourceUnit transpiledSourceUnit = groovyTranspiler.transpile(
new DefaultComponentTemplateCompilerConfiguration(),
compileUnit,
cuNode,
teplateClassSimpleName
templateClassName
);
return transpiledSourceUnit.getAST();
} catch (ComponentTemplateCompileException e) {

View File

@ -6,7 +6,6 @@ import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.groovy.GroovyUtil.ConvertResult;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import groowt.view.component.web.util.SourcePosition;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
@ -15,7 +14,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static groowt.view.component.web.transpile.TranspilerUtil.*;
@ -25,13 +23,9 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
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 Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\.");
private LeftShiftFactory leftShiftFactory;
private ValueNodeTranspiler valueNodeTranspiler;
private BodyTranspiler bodyTranspiler;
private ComponentClassNodeResolver componentClassNodeResolver;
public void setLeftShiftFactory(LeftShiftFactory leftShiftFactory) {
this.leftShiftFactory = leftShiftFactory;
@ -45,10 +39,6 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
this.bodyTranspiler = bodyTranspiler;
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
/* UTIL */
protected String getComponentName(int componentNumber) {
@ -74,49 +64,14 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* RESOLVE */
protected List<Expression> getArgsAsList(
TypedComponentNode componentNode,
TranspilerState state
) {
protected List<Expression> getArgsAsList(TypedComponentNode componentNode) {
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.componentClassNodeResolver.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.componentClassNodeResolver.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);
}
}
}
final ClassNode classNode = ClassHelper.make(identifier);
final var classExpression = new ClassExpression(classNode);
yield List.of(alias, classExpression);
}
case StringComponentTypeNode stringComponentTypeNode -> {
final String identifier = stringComponentTypeNode.getIdentifier();
@ -127,8 +82,8 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
}
// 'h1' | 'MyComponent', MyComponent(.class)
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode, TranspilerState state) {
final List<Expression> args = this.getArgsAsList(componentNode, state);
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode) {
final List<Expression> args = this.getArgsAsList(componentNode);
final ArgumentListExpression argsListExpr = new ArgumentListExpression();
args.forEach(argsListExpr::addExpression);
return argsListExpr;
@ -142,7 +97,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
return new MethodCallExpression(
new VariableExpression(state.getRenderContext()),
"resolve",
this.getResolveArgs(componentNode, state)
this.getResolveArgs(componentNode)
);
}

View File

@ -2,7 +2,6 @@ package groowt.view.component.web.transpile;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.ast.node.PreambleNode;
@ -10,7 +9,6 @@ import groowt.view.component.web.compiler.MultipleWebViewComponentCompileErrorsE
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
@ -31,25 +29,13 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
protected TranspilerConfiguration getConfiguration(
ClassLoaderComponentClassNodeResolver classLoaderComponentClassNodeResolver
) {
return SimpleTranspilerConfiguration.withDefaults(classLoaderComponentClassNodeResolver);
}
protected void addAutomaticImports(WebViewComponentModuleNode moduleNode, TranspilerConfiguration configuration) {
configuration.getImports().forEach(moduleNode::addImport);
configuration.getStaticImports().forEach(staticImport -> moduleNode.addStaticImport(
staticImport.getV1(), staticImport.getV2(), staticImport.getV3()
));
configuration.getStarImports().forEach(moduleNode::addStarImport);
configuration.getStaticStarImports().forEach(moduleNode::addStaticStarImport);
protected TranspilerConfiguration getConfiguration() {
return SimpleTranspilerConfiguration.withDefaults();
}
protected WebViewComponentModuleNode initModuleNode(
ComponentTemplateCompileUnit compileUnit,
WebViewComponentSourceUnit sourceUnit,
TranspilerConfiguration configuration
WebViewComponentSourceUnit sourceUnit
) {
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
sourceUnit.setModuleNode(moduleNode);
@ -59,22 +45,16 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
moduleNode.setPackageName(defaultPackageName);
}
this.addAutomaticImports(moduleNode, configuration);
return moduleNode;
}
protected ClassNode initMainClassNode(
ComponentTemplateCompileUnit compileUnit,
String templateClassName,
WebViewComponentModuleNode moduleNode
) {
protected ClassNode initMainClassNode(ComponentTemplateCompileUnit compileUnit, String templateClassName) {
final ClassNode mainClassNode = new ClassNode(
compileUnit.getDefaultPackageName() + templateClassName,
ACC_PUBLIC,
ClassHelper.OBJECT_TYPE
);
mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE);
moduleNode.addClass(mainClassNode);
return mainClassNode;
}
@ -216,40 +196,32 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
@Override
public WebViewComponentSourceUnit transpile(
ComponentTemplateCompilerConfiguration compilerConfiguration,
WebViewComponentTemplateCompileUnit compileUnit,
CompilationUnitNode compilationUnitNode,
String templateClassSimpleName
) throws ComponentTemplateCompileException {
// resolver, transpilerConfiguration, and positionSetter
final ClassLoaderComponentClassNodeResolver resolver = new ClassLoaderComponentClassNodeResolver(
compileUnit,
compilerConfiguration.getGroovyClassLoader()
);
final var transpilerConfiguration = this.getConfiguration(resolver);
// transpilerConfiguration, and positionSetter
final var transpilerConfiguration = this.getConfiguration();
final PositionSetter positionSetter = transpilerConfiguration.getPositionSetter();
// prepare sourceUnit
final CompilerConfiguration groovyCompilerConfiguration =
compilerConfiguration.getGroovyCompilerConfiguration();
final CompilerConfiguration groovyCompilerConfiguration = compileUnit.getGroovyCompilationUnit()
.getConfiguration();
final WebViewComponentSourceUnit sourceUnit = new WebViewComponentSourceUnit(
compileUnit.getDescriptiveName(),
compileUnit.getGroovyReaderSource(),
groovyCompilerConfiguration,
compilerConfiguration.getGroovyClassLoader(),
compileUnit.getGroovyCompilationUnit().getClassLoader(),
new ErrorCollector(groovyCompilerConfiguration)
);
// prepare moduleNode
final WebViewComponentModuleNode moduleNode = this.initModuleNode(
compileUnit, sourceUnit, transpilerConfiguration
compileUnit, sourceUnit
);
// set resolver's moduleNode
resolver.setModuleNode(moduleNode);
// prepare mainClassNode
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName, moduleNode);
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName);
// handle preamble
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
@ -257,6 +229,9 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
this.handlePreamble(templateClassSimpleName, preambleNode, mainClassNode, moduleNode, positionSetter);
}
// Moved here so that moduleNode#getMainClassName reflects fqn of mainClassName
moduleNode.addClass(mainClassNode);
// getRenderer method and render closure
// first, getRenderer params
final Parameter componentContextParam = new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME);

View File

@ -1,14 +1,12 @@
package groowt.view.component.web.transpile;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
public interface GroovyTranspiler {
WebViewComponentSourceUnit transpile(
ComponentTemplateCompilerConfiguration compilerConfiguration,
WebViewComponentTemplateCompileUnit compileUnit,
CompilationUnitNode compilationUnitNode,
String templateClassSimpleName

View File

@ -1,7 +1,6 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple3;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import org.codehaus.groovy.ast.ClassNode;
import java.util.Map;
@ -12,9 +11,8 @@ import static groowt.view.component.web.transpile.TranspilerUtil.*;
public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
public static TranspilerConfiguration withDefaults(ComponentClassNodeResolver componentClassNodeResolver) {
public static TranspilerConfiguration withDefaults() {
final var c = new SimpleTranspilerConfiguration();
c.setComponentClassNodeResolver(componentClassNodeResolver);
final var ct = new DefaultComponentTranspiler();
final PositionSetter ps = new SimplePositionSetter();
@ -27,7 +25,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
ct.setLeftShiftFactory(lsf);
ct.setBodyTranspiler(bt);
ct.setValueNodeTranspiler(vnt);
ct.setComponentClassNodeResolver(componentClassNodeResolver);
c.setComponentTranspiler(ct);
c.setPositionSetter(ps);
@ -40,7 +37,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
return c;
}
private ComponentClassNodeResolver componentClassNodeResolver;
private ComponentTranspiler componentTranspiler;
private PositionSetter positionSetter;
private LeftShiftFactory leftShiftFactory;
@ -49,14 +45,6 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
private BodyTranspiler bodyTranspiler;
private ValueNodeTranspiler valueNodeTranspiler;
public ComponentClassNodeResolver getComponentClassNodeResolver() {
return Objects.requireNonNull(this.componentClassNodeResolver);
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
public ComponentTranspiler getComponentTranspiler() {
return Objects.requireNonNull(this.componentTranspiler);
}

View File

@ -1,60 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.WebViewComponent;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.ArrayList;
import java.util.List;
public class CachingComponentClassNodeResolver implements ComponentClassNodeResolver {
private final List<ClassNode> classNodes = new ArrayList<>();
protected final WebViewComponentTemplateCompileUnit compileUnit;
public CachingComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
this.compileUnit = compileUnit;
}
public void addClass(Class<? extends WebViewComponent> clazz) {
this.classNodes.add(ClassHelper.make(clazz));
}
public void addClassNode(ClassNode classNode) {
this.classNodes.add(classNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
for (final var classNode : this.classNodes) {
if (classNode.getName().equals(fqn)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not resolve ClassNode for fqn: " + fqn,
null
));
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
for (final var classNode : this.classNodes) {
if (classNode.getNameWithoutPackage().equals(nameWithoutPackage)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Could not resolve ClassNode for nameWithoutPackage: " + nameWithoutPackage,
null
));
}
}

View File

@ -1,53 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClassLoaderComponentClassNodeResolver extends ModuleNodeComponentClassNodeResolver {
private static final Logger logger = LoggerFactory.getLogger(ModuleNodeComponentClassNodeResolver.class);
protected final ClassLoader classLoader;
public ClassLoaderComponentClassNodeResolver(
WebViewComponentTemplateCompileUnit compileUnit,
ClassLoader classLoader
) {
super(compileUnit);
this.classLoader = classLoader;
}
protected final Either<ClassNodeResolveException, ClassNode> resolveWithClassLoader(String fqn) {
logger.debug("Trying to resolve {}", fqn);
try {
Class<?> clazz = this.classLoader.loadClass(ResolveUtil.convertCanonicalNameToBinaryName(fqn));
final var classNode = ResolveUtil.getClassNode(clazz);
return Either.right(classNode);
} catch (ClassNotFoundException classNotFoundException) {
return Either.left(
new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not find class " + fqn + " with classLoader " +
this.classLoader,
classNotFoundException
)
);
}
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
return super.getClassForFqn(fqn).flatMapLeft(ignored -> {
final var classLoaderResult = this.resolveWithClassLoader(fqn);
if (classLoaderResult.isRight()) {
this.addClassNode(classLoaderResult.getRight());
}
return classLoaderResult;
});
}
}

View File

@ -1,42 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
public interface ComponentClassNodeResolver {
final class ClassNodeResolveException extends WebViewComponentTemplateCompileException {
private final String identifier;
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message
) {
super(compileUnit, message);
this.identifier = identifier;
}
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message,
Throwable cause
) {
super(compileUnit, message, cause);
this.identifier = identifier;
}
public String getIdentifier() {
return this.identifier;
}
}
Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn);
Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage);
}

View File

@ -1,84 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import java.util.Objects;
public class ModuleNodeComponentClassNodeResolver extends CachingComponentClassNodeResolver {
private ModuleNode moduleNode;
public ModuleNodeComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
super(compileUnit);
}
public ModuleNode getModuleNode() {
return Objects.requireNonNull(this.moduleNode);
}
public void setModuleNode(ModuleNode moduleNode) {
this.moduleNode = Objects.requireNonNull(moduleNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
return super.getClassForNameWithoutPackage(nameWithoutPackage).flatMapLeft(ignored -> {
// try regular imports first
final var importedClassNode = this.getModuleNode().getImportType(nameWithoutPackage);
if (importedClassNode != null) {
this.addClassNode(importedClassNode);
return Either.right(importedClassNode);
}
// try star imports
final var starImports = this.getModuleNode().getStarImports();
for (final var starImport : starImports) {
final var packageName = starImport.getPackageName();
final String fqn;
if (!packageName.equals(".") && packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
}
}
// try pre-pending package and asking for fqn
final String moduleNodePackageName = this.getModuleNode().getPackageName();
final String packageName;
if (moduleNodePackageName != null) {
packageName = moduleNodePackageName;
} else {
packageName = "";
}
final String fqn;
if (packageName.equals(".") || packageName.isEmpty()) {
fqn = nameWithoutPackage;
} else if (packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
} else {
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Cannot resolve " + nameWithoutPackage
+ " from imports, package-local classes, or pre-added classes."
));
}
});
}
}

View File

@ -1,31 +0,0 @@
package groowt.view.component.web.transpile.resolve;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.regex.Pattern;
public final class ResolveUtil {
private static final Pattern packageSplitter = Pattern.compile("^(?<package>(?>\\p{Ll}[^.]*\\.)*)(?<top>\\p{Lu}[^.]*)(?<members>(?>\\.\\p{Lu}[^.]*)*)$");
public static ClassNode getClassNode(Class<?> clazz) {
return ClassHelper.makeCached(clazz);
}
public static String convertCanonicalNameToBinaryName(String canonicalName) {
final var matcher = packageSplitter.matcher(canonicalName);
if (matcher.matches()) {
return new StringBuilder()
.append(matcher.group("package"))
.append(matcher.group("top"))
.append(matcher.group("members").replaceAll("\\.", "\\$"))
.toString();
} else {
throw new IllegalArgumentException("Cannot split apart " + canonicalName);
}
}
private ResolveUtil() {}
}

View File

@ -1,5 +1,16 @@
package groowt.view.component.web.groovyc
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
(configuration as CompilerConfiguration).tap {
pluginFactory = new WebViewComponentParserPluginFactory()
addCompilationCustomizers(new ImportCustomizer().tap {
addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
)
})
}
(configuration as CompilerConfiguration).pluginFactory = new WebViewComponentParserPluginFactory()

View File

@ -3,7 +3,6 @@ package groowt.view.component.web.transpiler;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.SimpleTranspilerConfiguration;
import groowt.view.component.web.transpile.TranspilerConfiguration;
import groowt.view.component.web.transpile.resolve.CachingComponentClassNodeResolver;
import org.codehaus.groovy.ast.ModuleNode;
public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
@ -13,7 +12,7 @@ public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
WebViewComponentTemplateCompileUnit compileUnit,
ModuleNode moduleNode
) {
return SimpleTranspilerConfiguration.withDefaults(new CachingComponentClassNodeResolver(compileUnit));
return SimpleTranspilerConfiguration.withDefaults();
}
}

View File

@ -1,25 +0,0 @@
package groowt.view.component.web.transpiler;
import org.junit.jupiter.api.Test;
import static groowt.view.component.web.transpile.resolve.ResolveUtil.convertCanonicalNameToBinaryName;
import static org.junit.jupiter.api.Assertions.*;
public class ResolveUtilTests {
@Test
public void abcABC() {
assertEquals("a.b.c.A$B$C", convertCanonicalNameToBinaryName("a.b.c.A.B.C"));
}
@Test
public void ABC() {
assertEquals("A$B$C", convertCanonicalNameToBinaryName("A.B.C"));
}
@Test
public void abcA() {
assertEquals("a.b.c.A", convertCanonicalNameToBinaryName("a.b.c.A"));
}
}

View File

@ -2,7 +2,6 @@ package groowt.view.component.web.transpiler;
import groovy.lang.Tuple2;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration;
import groowt.view.component.compiler.source.StringSource;
import groowt.view.component.web.BaseWebViewComponent;
import groowt.view.component.web.antlr.ParserUtil;
@ -55,7 +54,6 @@ public abstract class GroovyTranspilerTests {
final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
try {
this.transpiler.transpile(
new DefaultComponentTemplateCompilerConfiguration(),
new DefaultWebViewComponentTemplateCompileUnit(
"<anonymous string source>",
AnonymousWebViewComponent.class,

View File

@ -9,6 +9,7 @@ import org.apache.logging.log4j.core.LoggerContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import picocli.CommandLine;
import picocli.CommandLine.Command;
@ -71,6 +72,15 @@ public final class GroovyWvcCompiler implements Callable<Integer> {
public Integer doCompile() {
final CompilerConfiguration configuration = new CompilerConfiguration();
final var addGroowtImports = new ImportCustomizer();
addGroowtImports.addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
);
configuration.addCompilationCustomizers(addGroowtImports);
configuration.setPluginFactory(new WebViewComponentParserPluginFactory());
final CompilationUnit compilationUnit = new CompilationUnit(configuration);

View File

@ -4,13 +4,14 @@ 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.CompilerConfiguration;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.control.io.ReaderSource;
import org.jetbrains.annotations.Nullable;
@ -20,8 +21,20 @@ import java.net.URI;
public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponentTemplateCompileUnit
implements ReaderSource, WebViewComponentTemplateCompileUnit {
private static CompilationUnit getCompilationUnit() {
final var configuration = new CompilerConfiguration();
final var addGroowtImports = new ImportCustomizer();
addGroowtImports.addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
);
configuration.addCompilationCustomizers(addGroowtImports);
return new CompilationUnit(configuration);
}
private final String defaultPackageName;
private final CompilationUnit groovyCompilationUnit = new CompilationUnit();
private final CompilationUnit groovyCompilationUnit;
public DefaultWebViewComponentTemplateCompileUnit(
String descriptiveName,
@ -35,6 +48,7 @@ public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponen
} else {
this.defaultPackageName = defaultPackageName;
}
this.groovyCompilationUnit = getCompilationUnit();
}
@Override
@ -53,9 +67,8 @@ public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponen
}
@Override
public ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
throws ComponentTemplateCompileException {
final WebViewComponentTemplateCompiler compiler = WebViewComponentTemplateCompiler.get(configuration);
public ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
final WebViewComponentTemplateCompiler compiler = WebViewComponentTemplateCompiler.get();
return compiler.compile(this);
}

View File

@ -9,10 +9,10 @@ public interface WebViewComponentTemplateCompiler
extends ComponentTemplateCompiler<WebViewComponentTemplateCompileUnit> {
static WebViewComponentTemplateCompiler get() {
return get(new DefaultComponentTemplateCompilerConfiguration());
return get(new WebViewComponentTemplateCompilerConfiguration());
}
static WebViewComponentTemplateCompiler get(ComponentTemplateCompilerConfiguration configuration) {
static WebViewComponentTemplateCompiler get(WebViewComponentTemplateCompilerConfiguration configuration) {
final ServiceLoader<WebViewComponentTemplateCompilerFactory> factoryServiceLoader =
ServiceLoader.load(WebViewComponentTemplateCompilerFactory.class);
final var factory = factoryServiceLoader.findFirst()

View File

@ -0,0 +1,17 @@
package groowt.view.component.web.compiler;
import org.codehaus.groovy.control.CompilePhase;
public class WebViewComponentTemplateCompilerConfiguration {
private CompilePhase compilePhase = CompilePhase.CLASS_GENERATION;
public CompilePhase getCompilePhase() {
return this.compilePhase;
}
public void setCompilePhase(CompilePhase compilePhase) {
this.compilePhase = compilePhase;
}
}

View File

@ -1,7 +1,5 @@
package groowt.view.component.web.compiler;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
public interface WebViewComponentTemplateCompilerFactory {
WebViewComponentTemplateCompiler create(ComponentTemplateCompilerConfiguration configuration);
WebViewComponentTemplateCompiler create(WebViewComponentTemplateCompilerConfiguration configuration);
}