All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jstach.rainbowgum.pattern.format.PatternCompiler Maven / Gradle / Ivy

package io.jstach.rainbowgum.pattern.format;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;

import io.jstach.rainbowgum.LogFormatter;
import io.jstach.rainbowgum.LogProperties;
import io.jstach.rainbowgum.LogProperty;
import io.jstach.rainbowgum.LogProvider;
import io.jstach.rainbowgum.ServiceRegistry;
import io.jstach.rainbowgum.pattern.format.PatternFormatterFactory.CompositeFactory;
import io.jstach.rainbowgum.pattern.format.PatternFormatterFactory.KeywordFactory;
import io.jstach.rainbowgum.pattern.internal.Node;
import io.jstach.rainbowgum.pattern.internal.Node.CompositeNode;
import io.jstach.rainbowgum.pattern.internal.Node.End;
import io.jstach.rainbowgum.pattern.internal.Node.FormattingNode;
import io.jstach.rainbowgum.pattern.internal.Node.KeywordNode;
import io.jstach.rainbowgum.pattern.internal.Node.LiteralNode;
import io.jstach.rainbowgum.pattern.internal.Parser;
import io.jstach.rainbowgum.pattern.internal.ScanException;

/**
 * Compiles a pattern into a formatter.
 */
public sealed interface PatternCompiler {

	/**
	 * Compiles a pattern into a formatter.
	 * @param pattern logback style pattern.
	 * @return formatter.
	 */
	public LogFormatter compile(String pattern);

	/**
	 * Creates a pattern compiler builder. If nothing is set the default pattern registry
	 * and formatting config will be used.
	 * @return builder.
	 */
	public static Builder builder() {
		return new Builder();
	}

	/**
	 * Creates a pattern compiler provider from a builder lambda.
	 * @param consumer builder will be provided to the consumer.
	 * @return provider of pattern compiler.
	 */
	public static LogProvider of(Consumer consumer) {
		return (name, config) -> {
			var services = config.serviceRegistry();
			var registry = findService(services, PatternRegistry.class, name, LogProperties.DEFAULT_NAME)
				.orElseGet(() -> PatternRegistry.of());
			var patternConfig = findService(services, PatternConfig.class, name, LogProperties.DEFAULT_NAME)
				.orElseGet(() -> {
					boolean ansiDisable = LogProperty.Property.builder() //
						.ofBoolean() //
						.build(LogProperties.GLOBAL_ANSI_DISABLE_PROPERTY) //
						.get(config.properties()) //
						.value(false);
					var b = PatternConfig.builder()
						.propertyFunction(PatternConfig.propertyFunction(config.properties(),
								PatternConfig.PATTERN_PROPERY_PREFIX))
						.fromProperties(config.properties());
					if (ansiDisable) {
						b.ansiDisabled(true);
					}

					return b.build();
				});
			Builder b = builder();
			b.patternRegistry(registry);
			b.patternConfig(patternConfig);
			consumer.accept(b);
			return b.build();
		};
	}

	private static  Optional findService(ServiceRegistry services, Class c, String... names) {
		Stream sn = Stream.of(names);
		return sn.flatMap(s -> Stream.ofNullable(services.findOrNull(c, s))).findFirst();
	}

	/**
	 * Builder for {@link PatternCompiler}.
	 */
	public final static class Builder {

		private PatternRegistry patternRegistry;

		private PatternConfig patternConfig;

		private Builder() {
		}

		/**
		 * Pattern keyword registry.
		 * @param patternRegistry keyword names registry.
		 * @return this.
		 */
		public Builder patternRegistry(PatternRegistry patternRegistry) {
			this.patternRegistry = patternRegistry;
			return this;
		}

		/**
		 * Formatting config that has platform config like time zone etc.
		 * @param patternConfig config for formatting of keywords.
		 * @return this.
		 */
		public Builder patternConfig(PatternConfig patternConfig) {
			this.patternConfig = patternConfig;
			return this;
		}

		/**
		 * Creates a pattern compiler.
		 * @return pattern compiler.
		 */
		public PatternCompiler build() {
			var patternRegistry_ = patternRegistry;
			var formatterConfig_ = patternConfig;
			if (patternRegistry_ == null) {
				patternRegistry_ = PatternRegistry.of();
			}
			if (formatterConfig_ == null) {
				formatterConfig_ = PatternConfig.of();
			}
			return new Compiler(patternRegistry_, formatterConfig_);
		}

	}

}

final class Compiler implements PatternCompiler {

	private final PatternConfig config;

	private final PatternRegistry registry;

	Compiler(PatternRegistry registry, PatternConfig config) {
		this.config = config;
		this.registry = registry;
	}

	@Override
	public LogFormatter compile(String pattern) {
		try {
			Parser p = new Parser(pattern);
			return compile(p.parse());
		}
		catch (ScanException | IllegalStateException e) {
			throw new IllegalArgumentException("Pattern is invalid: " + pattern, e);
		}
	}

	LogFormatter compile(Node start) {
		var b = LogFormatter.builder();
		if (start == Node.end()) {
			return b.build();
		}

		for (Node n = start; n != Node.end();) {
			n = switch (n) {
				case End e -> {
					yield e;
				}
				case LiteralNode ln -> {
					b.text(ln.value());
					yield ln.next();
				}
				case FormattingNode fn -> {
					PatternFormatterFactory f = registry.getOrNull(fn.keyword());
					if (f == null) {
						throw new IllegalStateException("Missing formatter for key: " + fn.keyword());
					}

					var _child = switch (fn) {
						case CompositeNode cn -> cn.childNode();
						case KeywordNode kn -> null;
					};

					var formatter = switch (f) {
						case KeywordFactory kf -> kf.create(config, fn);
						case CompositeFactory cf -> {
							LogFormatter child = _child == null ? null : compile(_child);
							yield cf.create(config, fn, child);
						}
					};
					b.add(formatter);
					yield fn.next();
				}
			};
		}
		return b.build();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy