001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023
002package io.cui.tools.formatting.template;
003
004import static io.cui.tools.base.Preconditions.checkState;
005import static java.util.Objects.requireNonNull;
006import java.util.ArrayList;
007import java.util.List;
008import io.cui.tools.formatting.template.lexer.Lexer;
009import io.cui.tools.formatting.template.lexer.LexerBuilder;
010import io.cui.tools.formatting.template.token.Token;
011
012/**
013 * Formatter which is able to replace parameter inside the template based on
014 * {@link FormatterSupport} information. See {@link io.cui.tools.formatting} for details.
015 *
016 * @param <T> at least {@link FormatterSupport}
017 *
018 * @author Eugen Fischer
019 */
020public final class TemplateFormatterImpl<T extends FormatterSupport> implements TemplateFormatter<T> {
021    @java.lang.SuppressWarnings("all")
022    @lombok.Generated
023    private final java.lang.Object $lock = new java.lang.Object[0];
024    private static final long serialVersionUID = -6297959581838201331L;
025    private final String template;
026    private ArrayList<Token> parsedTokens;
027    private Lexer<T> lexer;
028    private final boolean strict;
029
030    private TemplateFormatterImpl(final String template, final boolean strict) {
031        this.template = template;
032        this.strict = strict;
033    }
034
035    /**
036     * Lexer which should be used to scan the template
037     *
038     * @param lexerInstance must not be {@code null}
039     *
040     * @return reference to TemplateFormatter, fluent api style
041     */
042    TemplateFormatterImpl<T> scanBy(final Lexer<T> lexerInstance) {
043        this.lexer = requireNonNull(lexerInstance, "Parser must not be null");
044        this.parsedTokens = null;
045        return this;
046    }
047
048    /**
049     * replace attributes from template by attribute values from the map.
050     * missing template attributes will be ignored and doesn't add to result at
051     * all.
052     *
053     * @param reference must not be null
054     *
055     * @return completed template
056     */
057    @Override
058    public String format(final T reference) {
059        requireNonNull(reference, "Reference must not be null");
060        final var tokenList = getParsedTokens();
061        final var buffer = new StringBuilder(0);
062        for (var index = 0; index < tokenList.size(); index++) {
063            final var token = tokenList.get(index);
064            if (token.isStringToken()) {
065                if (lookUpLastTokenHasValue(tokenList, reference, index) && lookUpNextTokenHasValue(tokenList, reference, index)) {
066                    buffer.append(token.substituteAttribute(reference));
067                }
068            } else {
069                buffer.append(token.substituteAttribute(reference));
070            }
071        }
072        return buffer.toString();
073    }
074
075    private boolean lookUpNextTokenHasValue(final List<Token> tokenList, final T reference, final int currentTokenIndex) {
076        var nextTokenIndex = currentTokenIndex + 1;
077        while (nextTokenIndex < tokenList.size()) {
078            final var token = tokenList.get(nextTokenIndex);
079            if (!token.isStringToken()) {
080                final var value = token.substituteAttribute(reference);
081                if (!value.isEmpty()) {
082                    return true;
083                }
084            }
085            nextTokenIndex++;
086        }
087        return false;
088    }
089
090    private boolean lookUpLastTokenHasValue(final List<Token> tokenList, final T reference, final int currentTokenIndex) {
091        var result = false;
092        final var lastTokenIndex = currentTokenIndex - 1;
093        if (lastTokenIndex >= 0) {
094            final var value = tokenList.get(lastTokenIndex).substituteAttribute(reference);
095            result = !value.isEmpty();
096        }
097        return result;
098    }
099
100    private List<Token> getParsedTokens() {
101        synchronized (this.$lock) {
102            if (null == this.parsedTokens) {
103                checkState(null != this.lexer, "Parser must be initialized before.");
104                this.parsedTokens = new ArrayList<>(this.lexer.scan(this.template));
105            }
106            return this.parsedTokens;
107        }
108    }
109
110    /**
111     * The created TemplateFormatter provide only usage of simple expression
112     * language with squared brackets.
113     *
114     * @param template must not be null
115     * @param sourceType must not be null
116     *
117     * @return TemplateFormatter which using template and lexer which was
118     *         forwarded
119     */
120    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final Class<F> sourceType) {
121        return TemplateBuilder.useTemplate(template).forType(sourceType);
122    }
123
124    /**
125     * @param template must not be null
126     * @param source must not be null
127     *
128     * @return TemplateFormatter
129     */
130    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final F source) {
131        return TemplateBuilder.useTemplate(template).forSource(source);
132    }
133
134    /**
135     * @param template must not be null
136     * @param lexer must not be null
137     *
138     * @return TemplateFormatter which using template and lexer which was
139     *         forwarded
140     */
141    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final Lexer<F> lexer) {
142        return TemplateBuilder.useTemplate(template).scanBy(lexer);
143    }
144
145    /**
146     * The created TemplateFormatter provide only usage of simple expression
147     * language with squared brackets.
148     *
149     * @param template must not be null
150     * @param sourceType must not be null
151     * @param strict use strict mode for pattern matching (only match exact name) instead of best
152     *            fitting
153     *
154     * @return TemplateFormatter which using template and lexer which was
155     *         forwarded
156     */
157    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final Class<F> sourceType, final boolean strict) {
158        return TemplateBuilder.useTemplate(template).strict(strict).forType(sourceType);
159    }
160
161    /**
162     * @param template must not be null
163     * @param source must not be null
164     * @param strict use strict mode for pattern matching (only match exact name) instead of best
165     *            fitting
166     *
167     * @return TemplateFormatter
168     */
169    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final F source, final boolean strict) {
170        return TemplateBuilder.useTemplate(template).strict(strict).forSource(source);
171    }
172
173    /**
174     * @param template must not be null
175     * @param lexer must not be null
176     * @param strict use strict mode for pattern matching (only match exact name) instead of best
177     *            fitting
178     *
179     * @return TemplateFormatter which using template and lexer which was
180     *         forwarded
181     */
182    public static <F extends FormatterSupport> TemplateFormatter<F> createFormatter(final String template, final Lexer<F> lexer, final boolean strict) {
183        return TemplateBuilder.useTemplate(template).strict(strict).scanBy(lexer);
184    }
185
186    // owolff: False positive
187    /**
188     * @return a newly created {@link TemplateBuilder}
189     */
190    @SuppressWarnings("squid:S2440")
191    public static TemplateBuilder builder() {
192        return new TemplateBuilder();
193    }
194
195
196    /**
197     * Template Builder
198     *
199     * @author Eugen Fischer
200     */
201    public static final class TemplateBuilder {
202        private TemplateBuilder() {
203        }
204
205        static FormatterBuilder useTemplate(final String template) {
206            return new FormatterBuilder(template);
207        }
208
209
210        static final class FormatterBuilder {
211            private final String template;
212            private boolean strict;
213
214            FormatterBuilder(final String templateInput) {
215                template = templateInput;
216            }
217
218            /**
219             * @param strict use strict mode for pattern matching (only match exact name) instead of
220             *            best fitting
221             * @return a {@link FormatterBuilder} with strict set to given parameter
222             */
223            public FormatterBuilder strict(boolean strict) {
224                this.strict = strict;
225                return this;
226            }
227
228            public <F extends FormatterSupport> TemplateFormatter<F> scanBy(final Lexer<F> lexer) {
229                return new TemplateFormatterImpl<F>(template, strict).scanBy(lexer);
230            }
231
232            public <F extends FormatterSupport> TemplateFormatter<F> forSource(final F source) {
233                final Lexer<F> lexer = LexerBuilder.useSimpleElWithSquaredBrackets().strict(strict).build(source);
234                return this.scanBy(lexer);
235            }
236
237            public <F extends FormatterSupport> TemplateFormatter<F> forType(final Class<F> classType) {
238                final Lexer<F> lexer = LexerBuilder.useSimpleElWithSquaredBrackets().strict(strict).build(classType);
239                return scanBy(lexer);
240            }
241        }
242    }
243
244    @java.lang.Override
245    @java.lang.SuppressWarnings("all")
246    @lombok.Generated
247    public java.lang.String toString() {
248        return "TemplateFormatterImpl(template=" + this.template + ", parsedTokens=" + this.getParsedTokens() + ", lexer=" + this.lexer + ", strict=" + this.strict + ")";
249    }
250
251    @java.lang.Override
252    @java.lang.SuppressWarnings("all")
253    @lombok.Generated
254    public boolean equals(final java.lang.Object o) {
255        if (o == this) return true;
256        if (!(o instanceof TemplateFormatterImpl)) return false;
257        final TemplateFormatterImpl<?> other = (TemplateFormatterImpl<?>) o;
258        if (this.strict != other.strict) return false;
259        final java.lang.Object this$template = this.template;
260        final java.lang.Object other$template = other.template;
261        if (this$template == null ? other$template != null : !this$template.equals(other$template)) return false;
262        final java.lang.Object this$parsedTokens = this.getParsedTokens();
263        final java.lang.Object other$parsedTokens = other.getParsedTokens();
264        if (this$parsedTokens == null ? other$parsedTokens != null : !this$parsedTokens.equals(other$parsedTokens)) return false;
265        final java.lang.Object this$lexer = this.lexer;
266        final java.lang.Object other$lexer = other.lexer;
267        if (this$lexer == null ? other$lexer != null : !this$lexer.equals(other$lexer)) return false;
268        return true;
269    }
270
271    @java.lang.Override
272    @java.lang.SuppressWarnings("all")
273    @lombok.Generated
274    public int hashCode() {
275        final int PRIME = 59;
276        int result = 1;
277        result = result * PRIME + (this.strict ? 79 : 97);
278        final java.lang.Object $template = this.template;
279        result = result * PRIME + ($template == null ? 43 : $template.hashCode());
280        final java.lang.Object $parsedTokens = this.getParsedTokens();
281        result = result * PRIME + ($parsedTokens == null ? 43 : $parsedTokens.hashCode());
282        final java.lang.Object $lexer = this.lexer;
283        result = result * PRIME + ($lexer == null ? 43 : $lexer.hashCode());
284        return result;
285    }
286}