[main] Rewrite logger to support hot-swapping strategies, utils for weak references

This commit is contained in:
Johannes Frohnmeyer 2022-06-15 14:49:57 +02:00
parent 5ca20446c6
commit eb71fb6533
Signed by: Johannes
GPG Key ID: E76429612C2929F4
12 changed files with 678 additions and 133 deletions

View File

@ -2,10 +2,10 @@ package io.gitlab.jfronny.commons.log;
import org.slf4j.LoggerFactory;
public class SLF4JLogger implements Logger {
public class SLF4JLogStrategy implements LogStrategy {
private final org.slf4j.Logger logger;
public SLF4JLogger(String name) {
public SLF4JLogStrategy(String name) {
this.logger = LoggerFactory.getLogger(name);
}

View File

@ -1,20 +1,19 @@
package io.gitlab.jfronny.commons.test;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.log.SLF4JLogger;
import io.gitlab.jfronny.commons.log.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class SLFLoggerTest {
public class SLFLogStrategyTest {
@BeforeEach
void prepare() {
Logger.resetFactory();
Logger.resetStrategy();
}
@Test
void testFactory() {
assertEquals(SLF4JLogger.class, Logger.forName("Joe").getClass());
assertEquals(SLF4JLogStrategy.class, Logger.forName("Joe").getDelegate().getClass());
}
}

View File

@ -0,0 +1,139 @@
package io.gitlab.jfronny.commons.log;
import org.jetbrains.annotations.*;
public class DelegateLogStrategy implements LogStrategy {
public DelegateLogStrategy() {
this(null);
}
public DelegateLogStrategy(LogStrategy delegate) {
this.delegate = delegate;
}
protected LogStrategy delegate;
@Override
public @Nullable String getName() {
return delegate.getName();
}
@Override
public void trace(String msg) {
delegate.trace(msg);
}
@Override
public void trace(String format, Object arg) {
delegate.trace(format, arg);
}
@Override
public void trace(String format, Object... args) {
delegate.trace(format, args);
}
@Override
public void trace(String msg, Throwable t) {
delegate.trace(msg, t);
}
@Override
public void debug(String msg) {
delegate.debug(msg);
}
@Override
public void debug(String format, Object arg) {
delegate.debug(format, arg);
}
@Override
public void debug(String format, Object... args) {
delegate.debug(format, args);
}
@Override
public void debug(String msg, Throwable t) {
delegate.debug(msg, t);
}
@Override
public void info(String msg) {
delegate.info(msg);
}
@Override
public void info(String format, Object arg) {
delegate.info(format, arg);
}
@Override
public void info(String format, Object... args) {
delegate.info(format, args);
}
@Override
public void info(String msg, Throwable t) {
delegate.info(msg, t);
}
@Override
public void warn(String msg) {
delegate.warn(msg);
}
@Override
public void warn(String format, Object arg) {
delegate.warn(format, arg);
}
@Override
public void warn(String format, Object... args) {
delegate.warn(format, args);
}
@Override
public void warn(String msg, Throwable t) {
delegate.warn(msg, t);
}
@Override
public void error(String msg) {
delegate.error(msg);
}
@Override
public void error(String format, Object arg) {
delegate.error(format, arg);
}
@Override
public void error(String format, Object... args) {
delegate.error(format, args);
}
@Override
public void error(String msg, Throwable t) {
delegate.error(msg, t);
}
@Override
public String format(String format, Object arg) {
return delegate.format(format, arg);
}
@Override
public String format(String format, Object... args) {
return delegate.format(format, args);
}
@Override
public String format(String msg, Throwable t) {
return delegate.format(msg, t);
}
public LogStrategy getDelegate() {
return delegate;
}
}

View File

@ -2,10 +2,10 @@ package io.gitlab.jfronny.commons.log;
import java.util.logging.Level;
public class JavaUtilLogger implements Logger {
public class JavaUtilLogStrategy implements LogStrategy {
private final java.util.logging.Logger logger;
public JavaUtilLogger(String name) {
public JavaUtilLogStrategy(String name) {
this.logger = java.util.logging.Logger.getLogger(name);
}

View File

@ -0,0 +1,92 @@
package io.gitlab.jfronny.commons.log;
import io.gitlab.jfronny.commons.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Function;
public interface LogStrategy {
@Nullable String getName();
default void trace(String msg) {
debug(msg);
}
default void trace(String format, Object arg) {
trace(format(format, arg));
}
default void trace(String format, Object... args) {
trace(format(format, args));
}
default void trace(String msg, Throwable t) {
trace(format(msg, t));
}
default void debug(String msg) {
info(msg);
}
default void debug(String format, Object arg) {
debug(format(format, arg));
}
default void debug(String format, Object... args) {
debug(format(format, args));
}
default void debug(String msg, Throwable t) {
debug(format(msg, t));
}
void info(String msg);
default void info(String format, Object arg) {
info(format(format, arg));
}
default void info(String format, Object... args) {
info(format(format, args));
}
default void info(String msg, Throwable t) {
info(format(msg, t));
}
default void warn(String msg) {
info(msg);
}
default void warn(String format, Object arg) {
warn(format(format, arg));
}
default void warn(String format, Object... args) {
warn(format(format, args));
}
default void warn(String msg, Throwable t) {
warn(format(msg, t));
}
default void error(String msg) {
warn(msg);
}
default void error(String format, Object arg) {
error(format(format, arg));
}
default void error(String format, Object... args) {
error(format(format, args));
}
default void error(String msg, Throwable t) {
error(format(msg, t));
}
default String format(String format, Object arg) {
return format.replaceFirst("\\{}", StringFormatter.toString(arg));
}
default String format(String format, Object... args) {
if (args == null || format == null) return format;
for (Object arg : args) {
format = format.replaceFirst("\\{}", StringFormatter.toString(arg));
}
return format;
}
default String format(String msg, Throwable t) {
if (t == null) return msg;
return msg + System.lineSeparator() + t;
}
}

View File

@ -1,104 +1,54 @@
package io.gitlab.jfronny.commons.log;
import io.gitlab.jfronny.commons.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.gitlab.jfronny.commons.ref.*;
import io.gitlab.jfronny.commons.reflect.Reflect;
import org.jetbrains.annotations.*;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
public interface Logger {
static Logger forName(@NotNull String name) {
return LoggerHolder.LOGGER_BUILDER.apply(name);
public class Logger extends DelegateLogStrategy {
public static Function<String, LogStrategy> LOGGER_BUILDER;
private static final Set<Logger> KNOWN_LOGGERS = new WeakSet<>();
static {
resetStrategy();
}
static void registerFactory(@NotNull Function<String, Logger> factory) {
LoggerHolder.LOGGER_BUILDER = Objects.requireNonNull(factory);
}
static void resetFactory() {
LoggerHolder.reset();
}
@Nullable String getName();
default void trace(String msg) {
debug(msg);
}
default void trace(String format, Object arg) {
trace(format(format, arg));
}
default void trace(String format, Object... args) {
trace(format(format, args));
}
default void trace(String msg, Throwable t) {
trace(format(msg, t));
}
default void debug(String msg) {
info(msg);
}
default void debug(String format, Object arg) {
debug(format(format, arg));
}
default void debug(String format, Object... args) {
debug(format(format, args));
}
default void debug(String msg, Throwable t) {
debug(format(msg, t));
}
void info(String msg);
default void info(String format, Object arg) {
info(format(format, arg));
}
default void info(String format, Object... args) {
info(format(format, args));
}
default void info(String msg, Throwable t) {
info(format(msg, t));
}
default void warn(String msg) {
info(msg);
}
default void warn(String format, Object arg) {
warn(format(format, arg));
}
default void warn(String format, Object... args) {
warn(format(format, args));
}
default void warn(String msg, Throwable t) {
warn(format(msg, t));
}
default void error(String msg) {
warn(msg);
}
default void error(String format, Object arg) {
error(format(format, arg));
}
default void error(String format, Object... args) {
error(format(format, args));
}
default void error(String msg, Throwable t) {
error(format(msg, t));
}
default String format(String format, Object arg) {
return format.replaceFirst("\\{}", StringFormatter.toString(arg));
}
default String format(String format, Object... args) {
if (args == null || format == null) return format;
for (Object arg : args) {
format = format.replaceFirst("\\{}", StringFormatter.toString(arg));
public static void resetStrategy() {
try {
LOGGER_BUILDER = Reflect.<String, LogStrategy>getConstructor("io.gitlab.jfronny.commons.log.SLF4JLogStrategy", String.class)
.orThrow();
} catch (ClassNotFoundException | NoSuchMethodException e) {
// SLF4J logger is unavailable, use java.util.logging
LOGGER_BUILDER = JavaUtilLogStrategy::new;
}
return format;
}
default String format(String msg, Throwable t) {
if (t == null) return msg;
return msg + System.lineSeparator() + t;
public static void updateStrategy(@NotNull Function<String, LogStrategy> factory) {
LOGGER_BUILDER = Objects.requireNonNull(factory);
synchronized (KNOWN_LOGGERS) {
for (Logger ref : KNOWN_LOGGERS) {
ref.updateStrategy();
}
}
}
public static Logger forName(@NotNull String name) {
return new Logger(name);
}
private final String name;
Logger(String name) {
this.name = name;
updateStrategy();
synchronized (KNOWN_LOGGERS) {
KNOWN_LOGGERS.add(this);
}
}
private void updateStrategy() {
delegate = LOGGER_BUILDER.apply(name);
}
}

View File

@ -1,25 +0,0 @@
package io.gitlab.jfronny.commons.log;
import io.gitlab.jfronny.commons.reflect.Reflect;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Function;
@ApiStatus.Internal
class LoggerHolder {
public static Function<String, Logger> LOGGER_BUILDER;
static {
reset();
}
public static void reset() {
try {
LOGGER_BUILDER = Reflect.<String, Logger>getConstructor("io.gitlab.jfronny.commons.log.SLF4JLogger", String.class)
.orThrow();
} catch (ClassNotFoundException | NoSuchMethodException e) {
// SLF4J logger is unavailable, use java.util.logging
LOGGER_BUILDER = JavaUtilLogger::new;
}
}
}

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.commons.log;
public class NopLogger implements Logger {
public class NopLogStrategy implements LogStrategy {
@Override
public String getName() {
return null;

View File

@ -1,10 +1,10 @@
package io.gitlab.jfronny.commons.log;
public class StdoutLogger implements Logger {
public class StdoutLogStrategy implements LogStrategy {
private final String name;
private final String prefix;
public StdoutLogger(String name) {
public StdoutLogStrategy(String name) {
this.name = name;
this.prefix = "[" + name + "] ";
}

View File

@ -0,0 +1,124 @@
package io.gitlab.jfronny.commons.ref;
import org.jetbrains.annotations.*;
import java.lang.ref.*;
import java.util.*;
public class WeakSet<E> extends AbstractSet<E> {
private final ReferenceQueue<E> queue = new ReferenceQueue<>();
private final HashSet<Reference<E>> delegate = new HashSet<>();
@NotNull
@Override
public synchronized Iterator<E> iterator() {
return new Iterator<>() {
final Iterator<Reference<E>> delegate = WeakSet.this.delegate.iterator();
E next = null;
@Override
public boolean hasNext() {
if (delegate.hasNext()) {
next = unwrap(delegate.next());
return true;
}
return false;
}
@Override
public E next() {
if ((next == null) && !hasNext())
throw new NoSuchElementException();
E e = next;
next = null;
return e;
}
@Override
public void remove() {
delegate.remove();
}
};
}
@Override
public synchronized int size() {
processQueue();
return delegate.size();
}
@Override
public synchronized boolean contains(Object o) {
return delegate.contains(WeakElement.create(o));
}
@Override
public synchronized boolean add(E e) {
processQueue();
return delegate.add(WeakElement.create(e, this.queue));
}
@Override
public synchronized boolean remove(Object o) {
boolean ret = delegate.remove(WeakElement.create(o));
processQueue();
return ret;
}
@Override
public void clear() {
processQueue();
delegate.clear();
}
@Nullable
private static <T> T unwrap(@Nullable Reference<T> ref) {
return ref == null ? null : ref.get();
}
private void processQueue() {
Reference<? extends E> wv;
while ((wv = this.queue.poll()) != null) {
super.remove(wv);
}
}
private static class WeakElement<E> extends WeakReference<E> {
private final int hash;
private WeakElement(E value) {
super(value);
hash = value.hashCode();
}
private WeakElement(E value, ReferenceQueue<E> queue) {
super(value, queue);
hash = value.hashCode();
}
private static <V> WeakElement<V> create(V value) {
return value == null ? null : new WeakElement<>(value);
}
private static <V> WeakElement<V> create(V value, ReferenceQueue<V> queue) {
return value == null ? null : new WeakElement<>(value, queue);
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof WeakElement<?> v)) return false;
return Objects.equals(this.get(), v.get());
}
@Override
public String toString() {
return Objects.toString(get());
}
}
}

View File

@ -0,0 +1,264 @@
package io.gitlab.jfronny.commons.ref;
import org.jetbrains.annotations.*;
import java.lang.ref.*;
import java.util.*;
public class WeakValueMap<K, V> implements Map<K, V> {
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
private final HashMap<K, WeakValue<K, V>> delegate = new HashMap<>();
private final EntrySet entrySet = new EntrySet();
private final ValueCollection values = new ValueCollection();
@Override
public synchronized int size() {
processQueue();
return delegate.size();
}
@Override
public synchronized boolean isEmpty() {
processQueue();
return delegate.isEmpty();
}
@Override
public synchronized boolean containsKey(Object o) {
processQueue();
return delegate.containsKey(o);
}
@Override
public synchronized boolean containsValue(Object o) {
return delegate.containsValue(WeakValue.create(o));
}
@Override
public synchronized V get(Object o) {
return unwrap(delegate.get(o));
}
@Nullable
@Override
public synchronized V put(K k, V v) {
processQueue();
return unwrap(delegate.put(k, WeakValue.create(k, v, queue)));
}
@Override
public synchronized V remove(Object o) {
return unwrap(delegate.remove(o));
}
@Override
public synchronized void putAll(@NotNull Map<? extends K, ? extends V> map) {
processQueue();
for (Entry<? extends K, ? extends V> entry : map.entrySet()) {
delegate.put(entry.getKey(), WeakValue.create(entry.getKey(), entry.getValue(), queue));
}
}
@Override
public synchronized void clear() {
delegate.clear();
}
@NotNull
@Override
public synchronized Set<K> keySet() {
processQueue();
return delegate.keySet();
}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
return entrySet;
}
@NotNull
@Override
public Collection<V> values() {
return values;
}
@Nullable
private static <T> T unwrap(@Nullable Reference<T> ref) {
return ref == null ? null : ref.get();
}
public void processQueue() {
WeakValue<K, V> ref;
while ((ref = (WeakValue<K, V>) queue.poll()) != null) {
delegate.remove(ref.key);
}
}
private static class WeakValue<K, V> extends WeakReference<V> {
private K key;
private WeakValue(V value) {
super(value);
}
private WeakValue(K key, V value, ReferenceQueue<V> queue) {
super(value, queue);
this.key = key;
}
private static <K, V> WeakValue<K, V> create(V value) {
if (value == null) return null;
else return new WeakValue<>(value);
}
private static <K, V> WeakValue<K, V> create(K key, V value, ReferenceQueue<V> queue) {
if (value == null) return null;
else return new WeakValue<>(key, value, queue);
}
@Override
public int hashCode() {
V ref = this.get();
return ref == null ? 0 : ref.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof WeakValueMap.WeakValue<?,?> v)) return false;
return Objects.equals(this.get(), v.get());
}
@Override
public String toString() {
return Objects.toString(get());
}
}
private class DelegateEntry implements Entry<K, V> {
private final Entry<K, WeakValue<K, V>> ent;
private V value;
DelegateEntry(Entry<K, WeakValue<K, V>> ent, V value) {
this.ent = ent;
this.value = value;
}
@Override
public K getKey() {
return ent.getKey();
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(Object o) {
Object oldValue = this.value;
this.value = (V) o;
ent.setValue(WeakValue.create(getKey(), this.value, queue));
return (V) oldValue;
}
@Override
public int hashCode() {
K k;
return ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
^ ((value == null) ? 0 : value.hashCode()));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Entry e)) return false;
return Objects.equals(getKey(), e.getKey()) && Objects.equals(getValue(), e.getValue());
}
}
private class EntrySet extends AbstractSet<Entry<K, V>> {
@Override
public @NotNull Iterator<Entry<K, V>> iterator() {
processQueue();
return new Iterator<>() {
final Iterator<Entry<K, WeakValue<K, V>>> delegate = WeakValueMap.this.delegate.entrySet().iterator();
Entry<K, V> next = null;
@Override
public boolean hasNext() {
if (delegate.hasNext()) {
Entry<K, WeakValue<K, V>> ent = delegate.next();
next = new DelegateEntry(ent, unwrap(ent.getValue()));
return true;
}
return false;
}
@Override
public Entry<K, V> next() {
if ((next == null) && !hasNext())
throw new NoSuchElementException();
Entry<K, V> e = next;
next = null;
return e;
}
@Override
public void remove() {
delegate.remove();
}
};
}
@Override
public boolean isEmpty() {
return WeakValueMap.this.isEmpty();
}
@Override
public int size() {
return WeakValueMap.this.size();
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Map.Entry<?,?> e)) return false;
return WeakValueMap.this.remove(e.getKey(), e.getValue());
}
}
private class ValueCollection extends AbstractCollection<V> {
@Override
public @NotNull Iterator<V> iterator() {
return new Iterator<>() {
final Iterator<Entry<K, V>> delegate = entrySet().iterator();
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public V next() {
return delegate.next().getValue();
}
@Override
public void remove() {
delegate.remove();
}
};
}
@Override
public int size() {
return WeakValueMap.this.size();
}
@Override
public boolean contains(Object o) {
return WeakValueMap.this.containsValue(o);
}
}
}

View File

@ -8,17 +8,19 @@ import static org.junit.jupiter.api.Assertions.*;
public class LoggerTest {
@BeforeEach
void prepare() {
Logger.resetFactory();
Logger.resetStrategy();
}
@Test
void testFactory() {
assertEquals(JavaUtilLogger.class, Logger.forName("Joe").getClass());
assertEquals(JavaUtilLogStrategy.class, Logger.forName("Joe").getDelegate().getClass());
}
@Test
void testRegisterFactory() {
Logger.registerFactory(s -> new NopLogger());
assertEquals(NopLogger.class, Logger.forName("Joe").getClass());
Logger logger = Logger.forName("Joe");
assertEquals(JavaUtilLogStrategy.class, logger.getDelegate().getClass());
Logger.updateStrategy(s -> new NopLogStrategy());
assertEquals(NopLogStrategy.class, logger.getDelegate().getClass());
}
}