Working on mismatched component error analysis and component-type ClassNode resolution.
This commit is contained in:
parent
1b851d2def
commit
8b3dc7a476
@ -14,6 +14,7 @@ slf4j = '2.0.12'
|
||||
antlr = { module = 'org.antlr:antlr4', version.ref = 'antlr' }
|
||||
antlr-runtime = { module = 'org.antlr:antlr4-runtime', version.ref = 'antlr' }
|
||||
asm = 'org.ow2.asm:asm:9.7'
|
||||
classgraph = 'io.github.classgraph:classgraph:4.8.172'
|
||||
gradle-tooling = 'org.gradle:gradle-tooling-api:8.6'
|
||||
groovy = { module = 'org.apache.groovy:groovy', version.ref = 'groovy' }
|
||||
groovy-all = { module = 'org.apache.groovy:groovy-all', version.ref = 'groovy' }
|
||||
|
@ -35,6 +35,7 @@ dependencies {
|
||||
libs.groovy,
|
||||
libs.groovy.templates,
|
||||
libs.antlr.runtime,
|
||||
libs.classgraph,
|
||||
project(':view-components'),
|
||||
project(':views')
|
||||
)
|
||||
|
@ -1,10 +1,13 @@
|
||||
package groowt.view.web;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.*;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.compiler.CachingComponentTemplateCompiler;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||
import groowt.view.component.factory.ComponentTemplateSource;
|
||||
import groowt.view.web.analysis.MismatchedComponentTypeError;
|
||||
import groowt.view.web.analysis.MismatchedComponentTypeErrorAnalysis;
|
||||
import groowt.view.web.antlr.CompilationUnitParseResult;
|
||||
import groowt.view.web.antlr.ParserUtil;
|
||||
import groowt.view.web.antlr.TokenList;
|
||||
@ -25,6 +28,7 @@ import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTemplateCompiler
|
||||
@ -92,7 +96,8 @@ public class DefaultWebViewComponentTemplateCompiler extends CachingComponentTem
|
||||
) {
|
||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
||||
|
||||
// TODO: analysis
|
||||
final List<MismatchedComponentTypeError> mismatchedComponentTypeErrors =
|
||||
MismatchedComponentTypeErrorAnalysis.check(parseResult.getCompilationUnitContext());
|
||||
|
||||
final var tokenList = new TokenList(parseResult.getTokenStream());
|
||||
final var astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList));
|
||||
|
@ -0,0 +1,53 @@
|
||||
package groowt.view.web;
|
||||
|
||||
import groowt.view.component.ViewComponent;
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileException;
|
||||
import groowt.view.web.ast.node.Node;
|
||||
import groowt.view.web.util.SourcePosition;
|
||||
|
||||
public class WebViewComponentTemplateCompileException extends ComponentTemplateCompileException {
|
||||
|
||||
private final Node node;
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
String message,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Node node
|
||||
) {
|
||||
super(message, forClass, templateSource);
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
String message,
|
||||
Throwable cause,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Node node
|
||||
) {
|
||||
super(message, cause, forClass, templateSource);
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public WebViewComponentTemplateCompileException(
|
||||
Throwable cause,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
Object templateSource,
|
||||
Node node
|
||||
) {
|
||||
super(cause, forClass, templateSource);
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
final SourcePosition start = this.node.getTokenRange().getStartPosition();
|
||||
return "Line " + start.line() + ", column " + start.column() + ": " + super.getMessage();
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
public interface AnalysisError<T> {
|
||||
T subject();
|
||||
String message();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public sealed interface Analyzer<T, E> permits ParseTreeAnalyzer, AstAnalyzer {
|
||||
List<E> analyze(T t);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import groowt.view.web.ast.node.Node;
|
||||
|
||||
@FunctionalInterface
|
||||
public non-sealed interface AstAnalyzer<T extends Node, E extends AstError<T>> extends Analyzer<Node, E> {}
|
@ -1,5 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import groowt.view.web.ast.node.Node;
|
||||
|
||||
public interface AstError<T extends Node> extends AnalysisError<T> {}
|
@ -1,16 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import groowt.view.web.antlr.WebViewComponentsParser.ComponentWithChildrenContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class MismatchedComponentTypeAnalyzer
|
||||
implements ParseTreeAnalyzer<ComponentWithChildrenContext, MismatchedComponentTypeError> {
|
||||
|
||||
@Override
|
||||
public List<MismatchedComponentTypeError> analyze(ParseTree parseTree) {
|
||||
return MismatchedComponentTypeErrorAnalyzerKt.check(parseTree);
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import groowt.view.web.antlr.WebViewComponentsParser.ComponentWithChildrenContext;
|
||||
|
||||
public record MismatchedComponentTypeError(ComponentWithChildrenContext subject, String message)
|
||||
implements ParseTreeAnalysisError<ComponentWithChildrenContext> {}
|
@ -1,3 +1,4 @@
|
||||
@file:JvmName("MismatchedComponentTypeErrorAnalysis")
|
||||
package groowt.view.web.analysis
|
||||
|
||||
import groowt.view.web.antlr.WebViewComponentsParser.ComponentTypeContext
|
||||
@ -51,6 +52,8 @@ private fun doCheck(tree: ParseTree, destination: MutableList<MismatchedComponen
|
||||
}
|
||||
}
|
||||
|
||||
data class MismatchedComponentTypeError(val tree: ParseTree, val message: String)
|
||||
|
||||
fun check(tree: ParseTree): List<MismatchedComponentTypeError> {
|
||||
val result: MutableList<MismatchedComponentTypeError> = ArrayList()
|
||||
doCheck(tree, result)
|
@ -1,5 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
public interface ParseTreeAnalysisError<T extends ParseTree> extends AnalysisError<T> {}
|
@ -1,7 +0,0 @@
|
||||
package groowt.view.web.analysis;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
@FunctionalInterface
|
||||
public non-sealed interface ParseTreeAnalyzer<T extends ParseTree, E extends ParseTreeAnalysisError<T>>
|
||||
extends Analyzer<ParseTree, E> {}
|
@ -1,129 +0,0 @@
|
||||
package groowt.view.web.analysis.classes;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public non-sealed class ClassLoaderClassLocator implements ClassLocator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClassLoaderClassLocator.class);
|
||||
|
||||
protected sealed interface CachedLocatedClass
|
||||
permits ClazzCachedLocatedClass, FailedGroovyCachedLocatedClass, CustomCachedLocatedClass {}
|
||||
|
||||
protected record ClazzCachedLocatedClass(Class<?> cached) implements CachedLocatedClass {}
|
||||
|
||||
protected record FailedGroovyCachedLocatedClass(
|
||||
CompilationFailedException exception) implements CachedLocatedClass {}
|
||||
|
||||
protected non-sealed interface CustomCachedLocatedClass extends CachedLocatedClass {
|
||||
Class<?> get();
|
||||
}
|
||||
|
||||
protected final ClassLoader classLoader;
|
||||
private final Map<String, CachedLocatedClass> cache = new HashMap<>();
|
||||
|
||||
public ClassLoaderClassLocator(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public ClassLoaderClassLocator() {
|
||||
this.classLoader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
|
||||
protected final void addToCache(String name, CachedLocatedClass cachedLocatedClass) {
|
||||
this.cache.put(name, cachedLocatedClass);
|
||||
}
|
||||
|
||||
protected final boolean cacheHas(String name) {
|
||||
return this.cache.containsKey(name);
|
||||
}
|
||||
|
||||
protected final void removeFromCacheIf(BiPredicate<? super String, ? super CachedLocatedClass> predicate) {
|
||||
final List<String> targets = new ArrayList<>();
|
||||
this.cache.forEach((name, cached) -> {
|
||||
if (predicate.test(name, cached)) {
|
||||
targets.add(name);
|
||||
}
|
||||
});
|
||||
targets.forEach(this.cache::remove);
|
||||
}
|
||||
|
||||
protected final <T extends CachedLocatedClass> void removeFromCacheIf(Class<T> ofType, BiPredicate<? super String, T> predicate) {
|
||||
this.removeFromCacheIf((name, cached) -> {
|
||||
if (ofType.isAssignableFrom(cached.getClass())) {
|
||||
return predicate.test(name, ofType.cast(cached));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
protected final <T extends CachedLocatedClass> void removeFromCacheByType(Class<T> type) {
|
||||
this.removeFromCacheIf((name, cached) -> type.isAssignableFrom(cached.getClass()));
|
||||
}
|
||||
|
||||
protected final <T extends CachedLocatedClass> Map<String, T> getFromCacheByType(Class<T> type) {
|
||||
final Map<String, T> result = new HashMap<>();
|
||||
this.cache.forEach((name, cached) -> {
|
||||
if (type.isAssignableFrom(cached.getClass())) {
|
||||
result.put(name, type.cast(cached));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected final @Nullable Class<?> loadFromCache(String name) {
|
||||
final var cachedLocated = this.cache.getOrDefault(name, null);
|
||||
if (cachedLocated == null) {
|
||||
return null;
|
||||
} else {
|
||||
return switch (cachedLocated) {
|
||||
case ClazzCachedLocatedClass(var cached) -> cached;
|
||||
case CustomCachedLocatedClass custom -> custom.get();
|
||||
case FailedGroovyCachedLocatedClass(var exception) ->
|
||||
throw new RuntimeException("Cannot load Groovy class because compilation failed.", exception);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable Class<?> searchClassLoader(String name) {
|
||||
if (classLoader instanceof GroovyClassLoader gcl) {
|
||||
try {
|
||||
Class<?> clazz = gcl.loadClass(name, true, true, false);
|
||||
this.addToCache(name, new ClazzCachedLocatedClass(clazz));
|
||||
return clazz;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Ignored
|
||||
} catch (CompilationFailedException cfe) {
|
||||
logger.warn("Could not compile class: {}", name);
|
||||
this.addToCache(name, new FailedGroovyCachedLocatedClass(cfe));
|
||||
// return null because we don't actually have a class
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Class<?> clazz = classLoader.loadClass(name);
|
||||
this.addToCache(name, new ClazzCachedLocatedClass(clazz));
|
||||
return clazz;
|
||||
} catch (ClassNotFoundException ignored) {}
|
||||
} return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasClassForFQN(String name) {
|
||||
return this.cacheHas(name) || this.searchClassLoader(name) != null;
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package groowt.view.web.analysis.classes;
|
||||
|
||||
public sealed interface ClassLocator permits ClassLoaderClassLocator, PreambleAwareClassLocator {
|
||||
boolean hasClassForFQN(String name);
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package groowt.view.web.analysis.classes;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.web.antlr.MergedGroovyCodeToken;
|
||||
import groowt.view.web.antlr.WebViewComponentsParser.PreambleContext;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.builder.AstStringCompiler;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public non-sealed class PreambleAwareClassLocator extends ClassLoaderClassLocator implements ClassLocator {
|
||||
|
||||
private final List<ClassNode> currentClassNodes = new ArrayList<>();
|
||||
private GroovyClassLoader currentGroovyClassLoader;
|
||||
|
||||
public PreambleAwareClassLocator(ClassLoader classLoader) {
|
||||
super(classLoader);
|
||||
}
|
||||
|
||||
protected class ClassNodeCachedLocatedClass implements CustomCachedLocatedClass {
|
||||
private final ClassNode classNode;
|
||||
private Class<?> lazyLoadedClass;
|
||||
|
||||
public ClassNodeCachedLocatedClass(ClassNode classNode) {
|
||||
this.classNode = classNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> get() {
|
||||
if (this.lazyLoadedClass == null) {
|
||||
try {
|
||||
final File tmp = File.createTempFile("preambleContextAwareClassLocator", "_" + System.currentTimeMillis());
|
||||
this.lazyLoadedClass = currentGroovyClassLoader.defineClass(this.classNode, null, tmp.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return this.lazyLoadedClass;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentPreamble(PreambleContext preambleContext) {
|
||||
this.currentGroovyClassLoader = new GroovyClassLoader(this.classLoader);
|
||||
this.currentClassNodes.clear();
|
||||
final MergedGroovyCodeToken groovyCodeToken = (MergedGroovyCodeToken) preambleContext.GroovyCode().getSymbol();
|
||||
final String groovyCode = groovyCodeToken.getText();
|
||||
final List<ASTNode> astNodes = new AstStringCompiler().compile(groovyCode);
|
||||
astNodes.forEach(groovyASTNode -> {
|
||||
if (groovyASTNode instanceof ClassNode classNode) {
|
||||
this.currentClassNodes.add(classNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @Nullable ClassNode searchPreambleSimpleName(String simpleName) {
|
||||
for (final ClassNode classNode : this.currentClassNodes) {
|
||||
if (classNode.getNameWithoutPackage().equals(simpleName)) {
|
||||
this.addToCache(classNode.getName(), new ClassNodeCachedLocatedClass(classNode));
|
||||
return classNode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasClassForFQN(String name) {
|
||||
return super.hasClassForFQN(name) || this.searchPreambleSimpleName(name) != null;
|
||||
}
|
||||
|
||||
private boolean hasSimpleNameInCache(String simpleName) {
|
||||
final Collection<ClassNodeCachedLocatedClass> allCached = this.getFromCacheByType(ClassNodeCachedLocatedClass.class).values();
|
||||
for (final ClassNodeCachedLocatedClass cached : allCached) {
|
||||
if (cached.classNode.getNameWithoutPackage().equals(simpleName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasClassForSimpleName(String simpleName) {
|
||||
return this.hasSimpleNameInCache(simpleName) || this.searchPreambleSimpleName(simpleName) != null;
|
||||
}
|
||||
|
||||
}
|
@ -56,7 +56,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
}
|
||||
|
||||
public void setAppendOrAddStatementFactory(AppendOrAddStatementFactory appendOrAddStatementFactory) {
|
||||
this.appendOrAddStatementFactory = Objects.requireNonNull(appendOrAddStatementFactory);
|
||||
this.appendOrAddStatementFactory = appendOrAddStatementFactory;
|
||||
}
|
||||
|
||||
// ViewComponent c0
|
||||
|
@ -0,0 +1,39 @@
|
||||
package groowt.view.web.transpile.resolve;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public sealed class ClassIdentifier permits ClassIdentifierWithFqn {
|
||||
|
||||
private final String alias;
|
||||
|
||||
public ClassIdentifier(@NotNull String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the alias (the name without the package name)
|
||||
*/
|
||||
public String getAlias() {
|
||||
return this.alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof ClassIdentifier other) {
|
||||
return this.alias.equals(other.alias);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.alias.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassIdentifier(" + this.alias + ")";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package groowt.view.web.transpile.resolve;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class ClassIdentifierWithFqn extends ClassIdentifier {
|
||||
|
||||
private final String fqn;
|
||||
|
||||
public ClassIdentifierWithFqn(@NotNull String alias, @NotNull String fqn) {
|
||||
super(alias);
|
||||
this.fqn = fqn;
|
||||
}
|
||||
|
||||
public String getFqn() {
|
||||
return this.fqn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClassIdentifierWithFqn(" + this.getAlias() + ", " + this.fqn + ")";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package groowt.view.web.transpile.resolve;
|
||||
|
||||
import groowt.view.web.util.Either;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ComponentClassNodeResolver {
|
||||
|
||||
record ClassNodeResolveError(ClassIdentifier identifier, String getMessage, @Nullable Throwable getCause) {}
|
||||
|
||||
Either<ClassNodeResolveError, ClassNode> getClassForFqn(String fqn);
|
||||
Either<ClassNodeResolveError, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage);
|
||||
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package groowt.view.web.transpile.resolve;
|
||||
|
||||
import groowt.view.web.util.Either;
|
||||
import io.github.classgraph.ClassInfo;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
import org.codehaus.groovy.ast.ClassHelper;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DefaultComponentClassNodeResolver implements ComponentClassNodeResolver {
|
||||
|
||||
private static final Pattern aliasPattern =
|
||||
Pattern.compile("^(\\P{Lu}+\\.)*(?<alias>\\p{Lu}.+(\\.\\p{Lu}.+)*)$");
|
||||
|
||||
protected static ClassNode getClassNode(Class<?> clazz) {
|
||||
return ClassHelper.makeCached(clazz);
|
||||
}
|
||||
|
||||
protected static String getAlias(String name) {
|
||||
final var matcher = aliasPattern.matcher(name);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group("alias");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot determine alias from " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private final ModuleNode moduleNode;
|
||||
protected final ClassLoader classLoader;
|
||||
private final ClassInfoList classInfoList;
|
||||
private final Map<ClassIdentifier, @Nullable ClassNode> cache = new HashMap<>();
|
||||
|
||||
public DefaultComponentClassNodeResolver(
|
||||
ModuleNode moduleNode,
|
||||
ClassLoader classLoader,
|
||||
ClassInfoList webViewComponentClassInfoList
|
||||
) {
|
||||
this.moduleNode = moduleNode;
|
||||
this.classLoader = classLoader;
|
||||
this.classInfoList = webViewComponentClassInfoList;
|
||||
}
|
||||
|
||||
protected final void addToCache(ClassIdentifier identifier, ClassNode clazz) {
|
||||
this.cache.put(identifier, clazz);
|
||||
}
|
||||
|
||||
protected final Either<ClassNodeResolveError, ClassNode> resolveWithClassLoader(ClassIdentifierWithFqn identifier) {
|
||||
try {
|
||||
Class<?> clazz = this.classLoader.loadClass(identifier.getFqn());
|
||||
final var classNode = getClassNode(clazz);
|
||||
return Either.right(classNode);
|
||||
} catch (ClassNotFoundException classNotFoundException) {
|
||||
return Either.left(
|
||||
new ClassNodeResolveError(
|
||||
identifier,
|
||||
"Could not find class " + identifier.getFqn() + " with classLoader " +
|
||||
this.classLoader,
|
||||
classNotFoundException
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Either<ClassNodeResolveError, ClassNode> resolveWithClassGraph(ClassIdentifier identifier) {
|
||||
final List<ClassInfo> potential = this.classInfoList.stream()
|
||||
.filter(classInfo -> classInfo.getSimpleName().equals(identifier.getAlias()))
|
||||
.toList();
|
||||
if (potential.size() > 1) {
|
||||
final var error = new ClassNodeResolveError(
|
||||
identifier,
|
||||
"There is more than one class on the classpath implementing WebViewComponent that has " +
|
||||
"the simple name " + identifier.getAlias() + ". Please explicitly import the desired " +
|
||||
"component class in the preamble, or use the fully qualified name of the component " +
|
||||
"you wish to use.",
|
||||
null
|
||||
);
|
||||
return Either.left(error);
|
||||
} else if (potential.size() == 1) {
|
||||
final var classInfo = potential.getFirst();
|
||||
final ClassNode result = getClassNode(classInfo.loadClass());
|
||||
return Either.right(result);
|
||||
} else {
|
||||
final var error = new ClassNodeResolveError(
|
||||
identifier,
|
||||
"Could not resolve a class implementing WebViewComponent for " + identifier.getAlias(),
|
||||
null
|
||||
);
|
||||
return Either.left(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected final @Nullable ClassNode findInCacheFqn(String fqn) {
|
||||
for (final var entry : this.cache.entrySet()) {
|
||||
final var identifier = entry.getKey();
|
||||
final var classNode = entry.getValue();
|
||||
if (classNode != null
|
||||
&& identifier instanceof ClassIdentifierWithFqn withFqn
|
||||
&& withFqn.getFqn().equals(fqn)
|
||||
) {
|
||||
return classNode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final @Nullable ClassNode findInCacheSimpleName(String simpleName) {
|
||||
for (final var entry : this.cache.entrySet()) {
|
||||
final var identifier = entry.getKey();
|
||||
final var classNode = entry.getValue();
|
||||
if (classNode != null && identifier.getAlias().equals(simpleName)) {
|
||||
return classNode;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<ClassNodeResolveError, ClassNode> getClassForFqn(String fqn) {
|
||||
// try cache
|
||||
final var identifier = new ClassIdentifierWithFqn(getAlias(fqn), fqn);
|
||||
final var fromCache = this.findInCacheFqn(fqn);
|
||||
if (fromCache != null) {
|
||||
return Either.right(fromCache);
|
||||
}
|
||||
|
||||
// do not try preamble, because it is a fully qualified name; i.e., it needs no import
|
||||
|
||||
// try classLoader
|
||||
final var classLoaderResolved = this.resolveWithClassLoader(identifier);
|
||||
if (classLoaderResolved.isRight()) {
|
||||
this.addToCache(identifier, classLoaderResolved.asRight().get());
|
||||
}
|
||||
return classLoaderResolved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<ClassNodeResolveError, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
|
||||
// try cache
|
||||
final var identifier = new ClassIdentifier(nameWithoutPackage);
|
||||
final var fromCache = this.findInCacheSimpleName(nameWithoutPackage);
|
||||
if (fromCache != null) {
|
||||
return Either.right(fromCache);
|
||||
}
|
||||
|
||||
// try imports
|
||||
final var importedClassNode = this.moduleNode.getImportType(nameWithoutPackage);
|
||||
if (importedClassNode != null) {
|
||||
this.addToCache(
|
||||
new ClassIdentifierWithFqn(
|
||||
importedClassNode.getName(),
|
||||
importedClassNode.getNameWithoutPackage()
|
||||
),
|
||||
importedClassNode
|
||||
);
|
||||
return Either.right(importedClassNode);
|
||||
}
|
||||
|
||||
// try classgraph
|
||||
final var classGraphResolved = this.resolveWithClassGraph(identifier);
|
||||
if (classGraphResolved.isRight()) {
|
||||
final ClassNode resolvedClassNode = classGraphResolved.asRight().get();
|
||||
final var withFqn = new ClassIdentifierWithFqn(nameWithoutPackage, resolvedClassNode.getName());
|
||||
this.addToCache(withFqn, resolvedClassNode);
|
||||
}
|
||||
return classGraphResolved;
|
||||
}
|
||||
|
||||
}
|
48
web-views/src/main/java/groowt/view/web/util/Either.java
Normal file
48
web-views/src/main/java/groowt/view/web/util/Either.java
Normal file
@ -0,0 +1,48 @@
|
||||
package groowt.view.web.util;
|
||||
|
||||
public sealed interface Either<E, T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E, T> Either<E, T> left(E error) {
|
||||
return (Either<E, T>) new Left<>((Class<E>) error.getClass(), error);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <E, T> Either<E, T> right(T item) {
|
||||
return (Either<E, T>) new Right<>((Class<T>) item.getClass(), item);
|
||||
}
|
||||
|
||||
record Left<E>(Class<E> errorClass, E error) implements Either<E, Object> {
|
||||
|
||||
public E get() {
|
||||
return this.errorClass.cast(this.error);
|
||||
}
|
||||
}
|
||||
|
||||
record Right<T>(Class<T> itemClass, T item) implements Either<Object, T> {
|
||||
|
||||
public T get() {
|
||||
return this.itemClass.cast(this.item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
default boolean isLeft() {
|
||||
return this instanceof Either.Left;
|
||||
}
|
||||
|
||||
default boolean isRight() {
|
||||
return this instanceof Either.Right;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Left<E> asLeft() {
|
||||
return (Left<E>) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default Right<T> asRight() {
|
||||
return (Right<T>) this;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user