001package io.jstach.rainbowgum.pattern.format; 002 003import java.util.Optional; 004import java.util.function.Consumer; 005import java.util.stream.Stream; 006 007import io.jstach.rainbowgum.LogFormatter; 008import io.jstach.rainbowgum.LogProperties; 009import io.jstach.rainbowgum.LogProperty; 010import io.jstach.rainbowgum.LogProvider; 011import io.jstach.rainbowgum.ServiceRegistry; 012import io.jstach.rainbowgum.pattern.format.PatternFormatterFactory.CompositeFactory; 013import io.jstach.rainbowgum.pattern.format.PatternFormatterFactory.KeywordFactory; 014import io.jstach.rainbowgum.pattern.internal.Node; 015import io.jstach.rainbowgum.pattern.internal.Node.CompositeNode; 016import io.jstach.rainbowgum.pattern.internal.Node.End; 017import io.jstach.rainbowgum.pattern.internal.Node.FormattingNode; 018import io.jstach.rainbowgum.pattern.internal.Node.KeywordNode; 019import io.jstach.rainbowgum.pattern.internal.Node.LiteralNode; 020import io.jstach.rainbowgum.pattern.internal.Parser; 021import io.jstach.rainbowgum.pattern.internal.ScanException; 022 023/** 024 * Compiles a pattern into a formatter. 025 */ 026public sealed interface PatternCompiler { 027 028 /** 029 * Compiles a pattern into a formatter. 030 * @param pattern logback style pattern. 031 * @return formatter. 032 */ 033 public LogFormatter compile(String pattern); 034 035 /** 036 * Creates a pattern compiler builder. If nothing is set the default pattern registry 037 * and formatting config will be used. 038 * @return builder. 039 */ 040 public static Builder builder() { 041 return new Builder(); 042 } 043 044 /** 045 * Creates a pattern compiler provider from a builder lambda. 046 * @param consumer builder will be provided to the consumer. 047 * @return provider of pattern compiler. 048 */ 049 public static LogProvider<PatternCompiler> of(Consumer<Builder> consumer) { 050 return (name, config) -> { 051 var services = config.serviceRegistry(); 052 var registry = findService(services, PatternRegistry.class, name, LogProperties.DEFAULT_NAME) 053 .orElseGet(() -> PatternRegistry.of()); 054 var patternConfig = findService(services, PatternConfig.class, name, LogProperties.DEFAULT_NAME) 055 .orElseGet(() -> { 056 boolean ansiDisable = LogProperty.Property.builder() // 057 .ofBoolean() // 058 .build(LogProperties.GLOBAL_ANSI_DISABLE_PROPERTY) // 059 .get(config.properties()) // 060 .value(false); 061 var b = PatternConfig.builder().fromProperties(config.properties()); 062 if (ansiDisable) { 063 b.ansiDisabled(true); 064 } 065 return b.build(); 066 }); 067 Builder b = builder(); 068 b.patternRegistry(registry); 069 b.patternConfig(patternConfig); 070 consumer.accept(b); 071 return b.build(); 072 }; 073 } 074 075 private static <T> Optional<T> findService(ServiceRegistry services, Class<T> c, String... names) { 076 Stream<String> sn = Stream.of(names); 077 return sn.flatMap(s -> Stream.ofNullable(services.findOrNull(c, s))).findFirst(); 078 } 079 080 /** 081 * Builder for {@link PatternCompiler}. 082 */ 083 public final static class Builder { 084 085 private PatternRegistry patternRegistry; 086 087 private PatternConfig patternConfig; 088 089 private Builder() { 090 } 091 092 /** 093 * Pattern keyword registry. 094 * @param patternRegistry keyword names registry. 095 * @return this. 096 */ 097 public Builder patternRegistry(PatternRegistry patternRegistry) { 098 this.patternRegistry = patternRegistry; 099 return this; 100 } 101 102 /** 103 * Formatting config that has platform config like time zone etc. 104 * @param patternConfig config for formatting of keywords. 105 * @return this. 106 */ 107 public Builder patternConfig(PatternConfig patternConfig) { 108 this.patternConfig = patternConfig; 109 return this; 110 } 111 112 /** 113 * Creates a pattern compiler. 114 * @return pattern compiler. 115 */ 116 public PatternCompiler build() { 117 var patternRegistry_ = patternRegistry; 118 var formatterConfig_ = patternConfig; 119 if (patternRegistry_ == null) { 120 patternRegistry_ = PatternRegistry.of(); 121 } 122 if (formatterConfig_ == null) { 123 formatterConfig_ = PatternConfig.of(); 124 } 125 return new Compiler(patternRegistry_, formatterConfig_); 126 } 127 128 } 129 130} 131 132final class Compiler implements PatternCompiler { 133 134 private final PatternConfig config; 135 136 private final PatternRegistry registry; 137 138 Compiler(PatternRegistry registry, PatternConfig config) { 139 this.config = config; 140 this.registry = registry; 141 } 142 143 @Override 144 public LogFormatter compile(String pattern) { 145 try { 146 Parser p = new Parser(pattern); 147 return compile(p.parse()); 148 } 149 catch (ScanException | IllegalStateException e) { 150 throw new IllegalArgumentException("Pattern is invalid: " + pattern, e); 151 } 152 } 153 154 LogFormatter compile(Node start) { 155 var b = LogFormatter.builder(); 156 if (start == Node.end()) { 157 return b.build(); 158 } 159 160 for (Node n = start; n != Node.end();) { 161 n = switch (n) { 162 case End e -> { 163 yield e; 164 } 165 case LiteralNode ln -> { 166 b.text(ln.value()); 167 yield ln.next(); 168 } 169 case FormattingNode fn -> { 170 PatternFormatterFactory f = registry.getOrNull(fn.keyword()); 171 if (f == null) { 172 throw new IllegalStateException("Missing formatter for key: " + fn.keyword()); 173 } 174 175 var _child = switch (fn) { 176 case CompositeNode cn -> cn.childNode(); 177 case KeywordNode kn -> null; 178 }; 179 180 var formatter = switch (f) { 181 case KeywordFactory kf -> kf.create(config, fn); 182 case CompositeFactory cf -> { 183 LogFormatter child = _child == null ? null : compile(_child); 184 yield cf.create(config, fn, child); 185 } 186 }; 187 b.add(formatter); 188 yield fn.next(); 189 } 190 }; 191 } 192 return b.build(); 193 } 194 195}