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}