How this library was created
-
Import sources of slf4j 1.7.36
-
Add custom
pom.xml
and this document to document the creation process -
A naive j2cl compile lists 65 issues in
stripped_sources
for our code. -
Adding a
Platform
which encapsulates the diff between script-world and jre-world worked fine for the first issue, J2cl compile reports 64 remaining issues. -
Creating the actual log-implementation forwarding to
elemental2.dom.Console.debug/info/warn/error
✔️️ = Feature can be used in script mode
❌ = Feature not available in script mode
-
✔️
org.slf4j.event.EventRecodingLogger.java
:-
Issue: Thread cannot be resolved
-
Idea: In script-mode, the thread name is now always
n/a
.
-
-
-
✔️
org.slf4j.helpers.BasicMarker.java
:-
Issue: CopyOnWriteArrayList cannot be resolved to a type
-
Idea: Use a simple
ArrayList
in script-mode
-
-
-
❌
org.slf4j.helpers.BasicMDCAdapter.java
:-
Issue: InheritableThreadLocal cannot be resolved to a type
-
Comment: Although the JRE emulation includes a
java.lang.ThreadLocal
, there is noInheritableThreadLocal
. -
Idea: GwtIncompatible the whole class.
-
-
-
✔️
org.slf4j.helpers.MessageFormatter.java
:-
The import java.text.MessageFormat cannot be resolved
-
Idea: Was just imported for the JavaDocs. Added full package name to JavaDoc.
-
-
-
❌
org.slf4j.helpers.NamedLoggerBase.java
:-
The import java.io.ObjectStreamException cannot be resolved
-
Comment: Not part of JRE emulation. Used in readResolve() method, which is used when objects — here the logger instances — are deserialized. This feature is not required in script-mode.
-
Idea: GwtIncompatible the method
-
-
-
✔️
org.slf4j.helpers.SubstituteLogger.java
:-
Missing imports: java.lang.reflect.InvocationTargetException, java.lang.reflect.Method, java.util.concurrent.LinkedBlockingQueue
-
Missing types: IllegalAccessException, NoSuchMethodException
-
The method getMethod(String, Class<LoggingEvent>) is undefined for the type Class<capture#3-of ? extends Logger>
-
Idea: Too much reflection to work around;
@GwtIncompatible
this class and its factorySubstituteLoggerFactory
-
Issue: Used in many places
-
Idea: Provide a script-impl which is never delegate-aware and always sends
log(LoggingEvent event)
to nowhere. Normal debug/info/… log calls are forwarded to the underlying delegate just fine.
-
-
-
-
-
✔️
org.slf4j.helpers.Util.java
:-
SecurityManager cannot be resolved to a type
-
Idea: Just
@GwtIncompatible
these parts and deal with the call-sites later. Most features work.
-
-
-
✔️
org.slf4j.LoggerFactory.java
:-
✔️ binding phase
-
Missing imports/type: java.net.URL, ClassLoader, java.lang.NoSuchMethodError, NoSuchFieldError, NoClassDefFoundError
-
The method findPossibleStaticLoggerBinderPathSet() from the type LoggerFactory refers to the missing type URL
-
The method getClassLoader() is undefined for the type Class<LoggerFactory>
-
Idea: skip binding and hard-wire to a built-in logger
-
-
-
✔️ replay events after binding
-
The import java.util.concurrent.LinkedBlockingQueue cannot be resolved
-
Idea: replace
LinkedBlockingQueue
withQueue
and add a helper method fordrainTo
to drain a Queue to thePlatform
.
-
-
-
❌ detect logger name mismatch
-
The method format(String, String, String) is undefined for the type String
-
Idea: remove this feature in script mode
-
-
-
-
✔️
org.slf4j.MarkerFactory.java
:-
binding
-
NoClassDefFoundError cannot be resolved to a type
-
NoSuchMethodError cannot be resolved to a type
-
The method bwCompatibleGetMarkerFactoryFromBinder() from the type MarkerFactory refers to the missing type NoClassDefFoundError
-
Idea: hard-code binding
-
-
-
-
✔️
org.slf4j.MDC.java
:-
binding
-
NoClassDefFoundError cannot be resolved to a type
-
NoSuchMethodError cannot be resolved to a type
-
The method bwCompatibleGetMDCAdapterFromBinder() from the type MDC refers to the missing type NoClassDefFoundError
-
Idea: hard-code binding
-
-
-
The JRE whitelist http://www.gwtproject.org/doc/latest/RefJreEmulation.html is really important but not very detailed.
E.g. System.getProperty(String key)
works only, when the given key is known at compile-time.
When instances of AAA should behave different when run in JRE vs. when run in script-mode.
class AAA_script {
void aaa() { /* shared-code implementation for script-mode */ }
}
class AAA extends AAA_script {
@GwtIncompatible
@Override
void aaa() { /* JRE-only implementation */ }
}
class AAA_script { void aaa() { /* shared-code implementation for script-mode */ } } class AAA extends AAA_script { }
AAA a = new AAA(); a.aaa(); // -> polymorphism at work
-
The JRE sees the full code and calls the
AAA.aaa()
impl -
J2CL sees the method as not being overriden and calls
AAA_script.aaa()
As static code cannot overwrite methods, we need to introduce variance points using instances. This transformation was used in several places:
static class AAA { void bbb() { // do JRE-specific stuff } void ccc() { // some code that calls bbb bbb(); } }
static class AAA { private static Vary VARY = new Vary(); private static class Vary_script { void bbb() { // a j2cl-compatible shared code way to do 'bbb' // or do nothing } } private static class Vary implements Vary_script { @GwtIncompatible @Overrride void bbb() { // do JRE-specific stuff } } void ccc() { // some code that calls bbb VARY.bbb(); } }
This is a weird one, but it seems to work.
Use when: You want to run client-side code using e.g. elemental2.dom.Console
in JRE
@JsType(isNative = true, namespace = JsPackage.GLOBAL)
public class Console {
public native void debug(Object... var_data);
}
public class Console_script {
public void debug(Object... var_data) {
DomGlobals.console.debug(var_data);
}
}
public class Console_wrapped extends Console_script {
@Override
@GwtIncompatible
public void debug(Object... var_data) {
System.out.println("DEBUG " + Arrays.toString(var_data));
}
}
public class Console_script {
public void debug(Object... var_data) {
DomGlobals.console.debug(var_data);
}
}
public class Console_wrapped extends Console_script {
}
This is actually delegating twice. Quite cumbersome.
Caveat: Your code needs to use Console_wrapped
instead of Console
.