Skip to content

Commit

Permalink
Add a text banner formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
dmlloyd committed May 9, 2023
1 parent 752c564 commit bab11d4
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/main/java/org/jboss/logmanager/ExtFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.logging.Formatter;
import java.util.logging.Handler;
Expand Down Expand Up @@ -172,4 +173,47 @@ public String getTail(Handler h) {
return formatter.getTail(h);
}
}

/**
* A base class for formatters which wrap other formatters.
*/
public abstract static class Delegating extends ExtFormatter {
/**
* The delegate formatter.
*/
protected final ExtFormatter delegate;

/**
* Construct a new instance.
*
* @param delegate the delegate formatter (must not be {@code null})
*/
public Delegating(final ExtFormatter delegate) {
this.delegate = Objects.requireNonNull(delegate, "delegate");
}

public String format(final ExtLogRecord record) {
return delegate.format(record);
}

public String formatMessage(final LogRecord record) {
return delegate.formatMessage(record);
}

public boolean isCallerCalculationRequired() {
return delegate.isCallerCalculationRequired();
}

public String format(final LogRecord record) {
return delegate.format(record);
}

public String getHead(final Handler h) {
return delegate.getHead(h);
}

public String getTail(final Handler h) {
return delegate.getTail(h);
}
}
}
159 changes: 159 additions & 0 deletions src/main/java/org/jboss/logmanager/formatters/TextBannerFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package org.jboss.logmanager.formatters;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.logging.Handler;

import org.jboss.logmanager.ExtFormatter;

/**
* A formatter which prints a text banner ahead of the normal formatter header.
* The text banner is acquired from a {@link Supplier} which is passed in to the constructor.
* Several utility methods are also present which allow easy creation of {@code Supplier} instances.
*/
public final class TextBannerFormatter extends ExtFormatter.Delegating {
private final Supplier<String> bannerSupplier;

/**
* Construct a new instance.
*
* @param bannerSupplier the supplier for the banner (must not be {@code null})
* @param delegate the delegate formatter (must not be {@code null})
*/
public TextBannerFormatter(final Supplier<String> bannerSupplier, final ExtFormatter delegate) {
super(delegate);
this.bannerSupplier = Objects.requireNonNull(bannerSupplier, "bannerSupplier");
}

// doc inherited
public String getHead(final Handler h) {
final String dh = Objects.requireNonNullElse(delegate.getHead(h), "");
final String banner = Objects.requireNonNullElse(bannerSupplier.get(), "");
return banner + dh;
}

/**
* Get the empty supplier which always returns an empty string.
*
* @return the empty supplier (not {@code null})
*/
public static Supplier<String> getEmptySupplier() {
return EMPTY;
}

/**
* Create a supplier which always returns the given string.
*
* @param string the string (must not be {@code null})
* @return a supplier which returns the given string (not {@code null})
*/
public static Supplier<String> createStringSupplier(String string) {
Objects.requireNonNull(string, "string");
return () -> string;
}

/**
* Create a supplier which loads the banner from the given file path,
* falling back to the given fallback supplier on error.
*
* @param path the path to load from (must not be {@code null})
* @param fallback the fallback supplier (must not be {@code null})
* @return the supplier (not {@code null})
*/
public static Supplier<String> createFileSupplier(Path path, Supplier<String> fallback) {
Objects.requireNonNull(path, "path");
Objects.requireNonNull(fallback, "fallback");
return () -> {
try {
return Files.readString(path, StandardCharsets.UTF_8);
} catch (IOException ignored) {
return fallback.get();
}
};
}

/**
* Create a supplier which loads the banner from the given URL,
* falling back to the given fallback supplier on error.
*
* @param url the URL to load from (must not be {@code null})
* @param fallback the fallback supplier (must not be {@code null})
* @return the supplier (not {@code null})
*/
public static Supplier<String> createUrlSupplier(URL url, Supplier<String> fallback) {
Objects.requireNonNull(url, "url");
Objects.requireNonNull(fallback, "fallback");
return () -> {
try {
final InputStream is = url.openStream();
return is == null ? fallback.get() : loadStringFromStream(is);
} catch (IOException ignored) {
return fallback.get();
}
};
}

/**
* Create a supplier which loads the banner from a resource in the given class loader,
* falling back to the given fallback supplier on error.
*
* @param resource the resource name (must not be {@code null})
* @param classLoader the class loader to load from (must not be {@code null})
* @param fallback the fallback supplier (must not be {@code null})
* @return the supplier (not {@code null})
*/
public static Supplier<String> createResourceSupplier(String resource, ClassLoader classLoader, Supplier<String> fallback) {
Objects.requireNonNull(resource, "resource");
Objects.requireNonNull(classLoader, "classLoader");
Objects.requireNonNull(fallback, "fallback");
return () -> {
try {
final InputStream is = classLoader.getResourceAsStream(resource);
return is == null ? fallback.get() : loadStringFromStream(is);
} catch (IOException ignored) {
return fallback.get();
}
};
}

/**
* Create a supplier which loads the banner from a resource in the caller's class loader,
* falling back to the given fallback supplier on error.
*
* @param resource the resource name (must not be {@code null})
* @param fallback the fallback supplier (must not be {@code null})
* @return the supplier (not {@code null})
*/
public static Supplier<String> createResourceSupplier(String resource, Supplier<String> fallback) {
return createResourceSupplier(resource, getClassLoader(STACK_WALKER.getCallerClass()), fallback);
}

// private stuff

private static final Supplier<String> EMPTY = createStringSupplier("");
private static final StackWalker STACK_WALKER = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));

private static ClassLoader getClassLoader(Class<?> clazz) {
return AccessController.doPrivileged((PrivilegedAction<? extends ClassLoader>) clazz::getClassLoader);
}

private static String loadStringFromStream(final InputStream is) throws IOException {
try (is) {
try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) {
try (StringBuilderWriter sw = new StringBuilderWriter()) {
isr.transferTo(sw);
return sw.toString();
}
}
}
}
}

0 comments on commit bab11d4

Please sign in to comment.