Symptom
When beast-base is reachable via both the JPMS module path and the
classpath in the same JVM (a common surefire/IDE configuration), code
that resolves BEAST classes through BEASTClassLoader.forName(...)
gets a different Class<?> instance than code that resolves them
directly via import beast.base.... Two visible failures:
ClassCastException on beast.base.evolution.tree.Node:
class beast.base.evolution.tree.Node cannot be cast to class beast.base.evolution.tree.Node
(beast.base.evolution.tree.Node is in module BEAST.base of loader jdk.internal.loader.Loader @...;
beast.base.evolution.tree.Node is in unnamed module of loader 'app')
- Empty data-type registry:
data type 'nucleotide' cannot be found. Choose one of [],
even though the registry shows nucleotide was registered — the
registration happened on a different Class<?> instance of the
registry holder.
Originally surfaced when running mvn -pl lphybeast test against
LPhyBeast on the beast3 branch.
Root cause
BEASTClassLoader.addServices(String, Map) (instance method) and
BEASTClassLoader.addService(String, String, String) (static) both
register every provider class name in class2loaderMap against
fallbackClassLoader() — i.e. the thread context class-loader, which
in a surefire/JPMS run is the app class-loader.
Meanwhile, the JPMS-driven path (mergeAllProviders /
collectProviders) correctly uses the providing module's
getClassLoader(). So provider classes that exist in a named boot-layer
module get a different loader depending on which discovery path
registered them, and BEASTClassLoader.forName() returns the
app-loader copy regardless of where the caller is.
Proposed fix
Before storing fallbackClassLoader(), check whether a named boot-layer
module owns the provider's package. If so, prefer that module's
class-loader; otherwise fall back as today.
private static ClassLoader resolveLoaderFor(String provider) {
int dot = provider.lastIndexOf('.');
if (dot < 0) return fallbackClassLoader();
String pkg = provider.substring(0, dot);
for (Module m : ModuleLayer.boot().modules()) {
var desc = m.getDescriptor();
if (desc != null && desc.packages().contains(pkg)) {
ClassLoader loader = m.getClassLoader();
if (loader != null) return loader;
}
}
return fallbackClassLoader();
}
This makes the version.xml registration path consistent with the
provides/module-descriptor registration path, removing the
class-identity split.
Validation
mvn -pl beast-pkgmgmt test — 40/40 tests pass with the patch.
- LPhyBeast
mvn -pl lphybeast test — 3/3 tests pass against a
patched 2.8.0-beta4 install.
PR forthcoming.
Symptom
When
beast-baseis reachable via both the JPMS module path and theclasspath in the same JVM (a common surefire/IDE configuration), code
that resolves BEAST classes through
BEASTClassLoader.forName(...)gets a different
Class<?>instance than code that resolves themdirectly via
import beast.base.... Two visible failures:ClassCastExceptiononbeast.base.evolution.tree.Node:data type 'nucleotide' cannot be found. Choose one of [],even though the registry shows nucleotide was registered — the
registration happened on a different
Class<?>instance of theregistry holder.
Originally surfaced when running
mvn -pl lphybeast testagainstLPhyBeast on the
beast3branch.Root cause
BEASTClassLoader.addServices(String, Map)(instance method) andBEASTClassLoader.addService(String, String, String)(static) bothregister every provider class name in
class2loaderMapagainstfallbackClassLoader()— i.e. the thread context class-loader, whichin a surefire/JPMS run is the app class-loader.
Meanwhile, the JPMS-driven path (
mergeAllProviders/collectProviders) correctly uses the providing module'sgetClassLoader(). So provider classes that exist in a named boot-layermodule get a different loader depending on which discovery path
registered them, and
BEASTClassLoader.forName()returns theapp-loader copy regardless of where the caller is.
Proposed fix
Before storing
fallbackClassLoader(), check whether a named boot-layermodule owns the provider's package. If so, prefer that module's
class-loader; otherwise fall back as today.
This makes the version.xml registration path consistent with the
provides/module-descriptor registration path, removing theclass-identity split.
Validation
mvn -pl beast-pkgmgmt test— 40/40 tests pass with the patch.mvn -pl lphybeast test— 3/3 tests pass against apatched
2.8.0-beta4install.PR forthcoming.