001/*
002 * Java Genetic Algorithm Library (jenetics-8.3.0).
003 * Copyright (c) 2007-2025 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020package io.jenetics.ext.rewriting;
021
022import static java.lang.String.format;
023import static java.util.stream.Collectors.toMap;
024import static io.jenetics.ext.internal.util.Names.isIdentifier;
025
026import java.io.IOException;
027import java.io.InvalidObjectException;
028import java.io.ObjectInput;
029import java.io.ObjectInputStream;
030import java.io.ObjectOutput;
031import java.io.Serial;
032import java.io.Serializable;
033import java.util.Collections;
034import java.util.HashMap;
035import java.util.Map;
036import java.util.Objects;
037import java.util.Optional;
038import java.util.SortedSet;
039import java.util.TreeSet;
040import java.util.function.Function;
041
042import io.jenetics.ext.internal.util.Escaper;
043import io.jenetics.ext.util.Tree;
044import io.jenetics.ext.util.Tree.Path;
045import io.jenetics.ext.util.TreeNode;
046
047/**
048 * This class serves two purposes. Firstly, it is used as a <em>classical</em>
049 * pattern, which is used to find <em>matches</em> against a <em>matching</em>
050 * tree. Secondly, it can <em>expand</em> a given pattern to a full tree with a
051 * given <em>pattern</em> variable to subtree mapping.
052 *
053 * <p><b>Matching trees</b></p>
054 *
055 * A compiled representation of a <em>tree</em> pattern. A tree pattern,
056 * specified as a parentheses string, must first be compiled into an instance of
057 * this class. The resulting pattern can then be used to create a
058 * {@link TreeMatcher} object that can match arbitrary trees against the tree
059 * pattern. All the states involved in performing a match reside in the
060 * matcher, so many matchers can share the same pattern.
061 * <p>
062 * The string representation of a tree pattern is a parenthesis tree string,
063 * with a special wildcard syntax for arbitrary subtrees. The subtree
064 * variables are prefixed with a '$' and must be a valid Java identifier.
065 * {@snippet lang="java":
066 * final TreePattern<String> p1 = TreePattern.compile("add($a,add($b,sin(x)))");
067 * final TreePattern<String> p2 = TreePattern.compile("pow($x,$y)");
068 * }
069 *
070 * If you need to have values which start with a '$' character, you can escape
071 * it with a '\'.
072 * {@snippet lang="java":
073 * final TreePattern<String> p1 = TreePattern.compile("concat($x,\\$foo)");
074 * }
075 *
076 * The second value, {@code $foo}, of the {@code concat} function is not treated
077 * as <em>pattern</em> variable.
078 * <p>
079 * If you want to match against trees with a different value type than
080 * {@code String}, you have to specify an additional type mapper function when
081 * compiling the pattern string.
082 * {@snippet lang="java":
083 * final TreePattern<Op<Double>> p = TreePattern.compile(
084 *     "add($a,add($b,sin(x)))",
085 *     MathOp::toMathOp
086 * );
087 * }
088 *
089 * <p><b>Expanding trees</b></p>
090 *
091 * The second functionality of the tree pattern is to expand a pattern to a whole
092 * tree with a given <em>pattern</em> variable to subtree mapping.
093 * {@snippet lang="java":
094 * final TreePattern<String> pattern = TreePattern.compile("add($x,$y,1)");
095 * final Map<Var<String>, Tree<String, ?>> vars = Map.of(
096 *     Var.of("x"), TreeNode.parse("sin(x)"),
097 *     Var.of("y"), TreeNode.parse("sin(y)")
098 * );
099 *
100 * final Tree<String, ?> tree = pattern.expand(vars);
101 * assertEquals(tree.toParenthesesString(), "add(sin(x),sin(y),1)");
102 * }
103 *
104 * @see TreeRewriteRule
105 * @see Tree#toParenthesesString()
106 * @see TreeMatcher
107 *
108 * @param <V> the value type of the tree than can match by this pattern
109 *
110 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
111 * @version 7.0
112 * @since 5.0
113 */
114public final class TreePattern<V> implements Serializable {
115
116        @Serial
117        private static final long serialVersionUID = 1L;
118
119        // Primary state of the tree pattern.
120        private final TreeNode<Decl<V>> _pattern;
121
122        // Cached variable set.
123        private final SortedSet<Var<V>> _vars;
124
125        /**
126         * Create a new tree-pattern object from the given pattern tree.
127         *
128         * @param pattern the pattern-tree
129         * @throws NullPointerException if the given {@code pattern} is {@code null}
130         * @throws IllegalArgumentException if {@link Var} nodes are not leaf nodes;
131         *         {@link Tree#isLeaf()} is {@code false}
132         */
133        public TreePattern(final Tree<Decl<V>, ?> pattern) {
134                _pattern = TreeNode.ofTree(pattern);
135                _vars = extractVars(_pattern);
136        }
137
138        // Extracts the variables from the pattern.
139        private static <V> SortedSet<Var<V>>
140        extractVars(final TreeNode<Decl<V>> pattern) {
141                final SortedSet<Var<V>> variables = new TreeSet<>();
142                for (Tree<Decl<V>, ?> n : pattern) {
143                        if (n.value() instanceof Var<V> var) {
144                                if (!n.isLeaf()) {
145                                        throw new IllegalArgumentException(format(
146                                                "Variable node '%s' is not a leaf: %s",
147                                                n.value(), n.toParenthesesString()
148                                        ));
149                                }
150
151                                variables.add(var);
152                        }
153                }
154
155                return Collections.unmodifiableSortedSet(variables);
156        }
157
158        TreeNode<Decl<V>> pattern() {
159                return _pattern;
160        }
161
162        /**
163         * Return the <em>unmodifiable</em> set of variables, defined in {@code this}
164         * pattern. The variables are returned without the angle brackets.
165         *
166         * @return the variables, defined in this pattern
167         */
168        public SortedSet<Var<V>> vars() {
169                return _vars;
170        }
171
172        /**
173         * Maps {@code this} tree-pattern from type {@code V} to type {@code B}.
174         *
175         * @param mapper the type mapper
176         * @param <B> the target type
177         * @return a new tree-pattern for the mapped type
178         */
179        public <B> TreePattern<B> map(final Function<? super V, ? extends B> mapper) {
180                return new TreePattern<>(_pattern.map(d -> d.map(mapper)));
181        }
182
183        /**
184         * Creates a matcher that will match the given input tree against
185         * {@code this} pattern.
186         *
187         * @param tree the tree to be matched
188         * @return a new matcher for {@code this} pattern
189         * @throws NullPointerException if the arguments is {@code null}
190         */
191        public TreeMatcher<V> matcher(final Tree<V, ?> tree) {
192                return TreeMatcher.of(this, tree);
193        }
194
195        /**
196         * Try to match the given {@code tree} against {@code this} pattern.
197         *
198         * @param tree the tree to be matched
199         * @return the match result, or {@link Optional#empty()} if the given
200         *         {@code tree} doesn't match
201         * @throws NullPointerException if the arguments is {@code null}
202         */
203        public Optional<TreeMatchResult<V>> match(final Tree<V, ?> tree) {
204                final Map<Var<V>, Tree<V, ?>> vars = new HashMap<>();
205                final boolean matches = matches(tree, _pattern, vars);
206
207                return matches
208                        ? Optional.of(TreeMatchResult.of(tree, vars))
209                        : Optional.empty();
210        }
211
212        /**
213         * Tests whether the given input {@code tree} matches {@code this} pattern.
214         *
215         * @param tree the tree to be matched
216         * @return {@code true} if the {@code tree} matches {@code this} pattern,
217         *         {@code false} otherwise
218         * @throws NullPointerException if one of the arguments is {@code null}
219         */
220        public boolean matches(final Tree<V, ?> tree) {
221                return matches(tree, _pattern, new HashMap<>());
222        }
223
224        private static <V> boolean matches(
225                final Tree<V, ?> node,
226                final Tree<Decl<V>, ?> pattern,
227                final Map<Var<V>, Tree<V, ?>> vars
228        ) {
229                final Decl<V> decl = pattern.value();
230
231                if (decl instanceof Var<V> var) {
232                        final Tree<? extends V, ?> tree = vars.get(decl);
233                        if (tree == null) {
234                                vars.put(var, node);
235                                return true;
236                        }
237
238                        return tree.equals(node);
239                } else {
240                        final Val<V> p = (Val<V>)decl;
241                        final V v = node.value();
242
243                        if (Objects.equals(v, p.value())) {
244                                if (node.childCount() == pattern.childCount()) {
245                                        for (int i = 0; i < node.childCount(); ++i) {
246                                                final Tree<V, ?> cn = node.childAt(i);
247                                                final Tree<Decl<V>, ?> cp = pattern.childAt(i);
248
249                                                if (!matches(cn, cp, vars)) {
250                                                        return false;
251                                                }
252                                        }
253                                        return true;
254                                } else {
255                                        return false;
256                                }
257                        } else {
258                                return false;
259                        }
260                }
261        }
262
263        /**
264         * Expands {@code this} pattern with the given variable mapping.
265         *
266         * @param vars the variables to use for expanding {@code this} pattern
267         * @return the expanded tree pattern
268         * @throws NullPointerException if one of the arguments is {@code null}
269         * @throws IllegalArgumentException if not all needed variables are part
270         *         of the {@code variables} map
271         */
272        public TreeNode<V> expand(final Map<Var<V>, Tree<V, ?>> vars) {
273                return expand(_pattern, vars);
274        }
275
276        // Expanding the template.
277        private static <V> TreeNode<V> expand(
278                final Tree<Decl<V>, ?> template,
279                final Map<Var<V>, Tree<V, ?>> vars
280        ) {
281                final Map<Path, Var<V>> paths = template.stream()
282                        .filter((Tree<Decl<V>, ?> n) -> n.value() instanceof Var)
283                        .collect(toMap(Tree::childPath, t -> (Var<V>)t.value()));
284
285                final TreeNode<V> tree = TreeNode.ofTree(
286                        template,
287                        n -> n instanceof Val<V> val ? val.value() : null
288                );
289
290                paths.forEach((path, decl) -> {
291                        final Tree<? extends V, ?> replacement = vars.get(decl);
292                        if (replacement != null) {
293                                tree.replaceAtPath(path, TreeNode.ofTree(replacement));
294                        } else {
295                                tree.removeAtPath(path);
296                        }
297                });
298
299                return tree;
300        }
301
302        @Override
303        public int hashCode() {
304                return _pattern.hashCode();
305        }
306
307        @Override
308        public boolean equals(final Object obj) {
309                return obj instanceof TreePattern<?> other &&
310                        _pattern.equals(other._pattern);
311        }
312
313        @Override
314        public String toString() {
315                return _pattern.toParenthesesString();
316        }
317
318        /* *************************************************************************
319         * Static factory methods.
320         * ************************************************************************/
321
322        /**
323         * Compiles the given tree pattern string.
324         *
325         * @param pattern the tree pattern string
326         * @return the compiled pattern
327         * @throws NullPointerException if the given pattern is {@code null}
328         * @throws IllegalArgumentException if the given parentheses tree string
329         *         doesn't represent a valid pattern tree or one of the variable
330         *         names is not a valid (Java) identifier
331         */
332        public static TreePattern<String> compile(final String pattern) {
333                return compile(pattern, Function.identity());
334        }
335
336        /**
337         * Compiles the given tree pattern string.
338         *
339         * @param pattern the tree pattern string
340         * @param mapper the mapper which converts the serialized string value to
341         *        the desired type
342         * @param <V> the value type of the tree than can be matched by the pattern
343         * @return the compiled pattern
344         * @throws NullPointerException if the given pattern is {@code null}
345         * @throws IllegalArgumentException if the given parentheses tree string
346         *         doesn't represent a valid pattern tree or one of the variable
347         *         names is not a valid (Java) identifier
348         */
349        public static <V> TreePattern<V> compile(
350                final String pattern,
351                final Function<? super String, ? extends V> mapper
352        ) {
353                return new TreePattern<>(
354                        TreeNode.parse(pattern, v -> Decl.of(v.trim(), mapper))
355                );
356        }
357
358        /* *************************************************************************
359         *  Java object serialization
360         * ************************************************************************/
361
362        @Serial
363        private Object writeReplace() {
364                return new SerialProxy(SerialProxy.TREE_PATTERN, this);
365        }
366
367        @Serial
368        private void readObject(final ObjectInputStream stream)
369                throws InvalidObjectException
370        {
371                throw new InvalidObjectException("Serialization proxy required.");
372        }
373
374        void write(final ObjectOutput out) throws IOException {
375                out.writeObject(_pattern);
376        }
377
378        @SuppressWarnings({"unchecked", "rawtypes"})
379        static Object read(final ObjectInput in)
380                throws IOException, ClassNotFoundException
381        {
382                final var pattern = (TreeNode)in.readObject();
383                return new TreePattern(pattern);
384        }
385
386
387        /* *************************************************************************
388         * Pattern node classes.
389         * ************************************************************************/
390
391        private static final char VAR_PREFIX = '$';
392        private static final char ESC_CHAR = '\\';
393
394        private static final Escaper ESCAPER = new Escaper(ESC_CHAR, VAR_PREFIX);
395
396        /**
397         * A sealed interface, which constitutes the nodes of a pattern tree.
398         * The only two implementations of this class are the {@link Var} and the
399         * {@link Val} class. The {@link Var} class represents a placeholder for an
400         * arbitrary subtree and the {@link Val} class stands for an arbitrary
401         * concrete subtree.
402         *
403         * @see Var
404         * @see Val
405         *
406         * @param <V> the node type the tree-pattern is working on
407         */
408        public sealed interface Decl<V> {
409
410                /**
411                 * Returns a new {@link Decl} object with the mapped type {@code B}.
412                 *
413                 * @param mapper the mapping function
414                 * @param <B> the mapped type
415                 * @return the mapped declaration
416                 * @throws NullPointerException if the mapping function is {@code null}
417                 */
418                <B> Decl<B> map(final Function<? super V, ? extends B> mapper);
419
420                static <V> Decl<V> of(
421                        final String value,
422                        final Function<? super String, ? extends V> mapper
423                ) {
424                        return Var.isVar(value)
425                                ? new Var<>(value.substring(1))
426                                : new Val<>(mapper.apply(ESCAPER.unescape(value)));
427                }
428        }
429
430        /**
431         * Represents a placeholder (variable) for an arbitrary subtree. A
432         * <em>pattern</em> variable is identified by its name. The pattern DSL
433         * denotes variable names with a leading '$' character, e.g. {@code $x},
434         * {@code $y} or {@code $my_var}.
435         *
436         * @see Val
437         *
438         * @implNote
439         * This class is comparable by its name.
440         *
441         @param <V> the node type the tree-pattern is working on
442         */
443        public record Var<V>(String name)
444                implements Decl<V>, Comparable<Var<V>>, Serializable
445        {
446                @Serial
447                private static final long serialVersionUID = 2L;
448
449                /**
450                 * @param name the name of the variable
451                 * @throws NullPointerException if the given {@code name} is {@code null}
452                 * @throws IllegalArgumentException if the given {@code name} is not a
453                 *         valid Java identifier
454                 */
455                public Var {
456                        if (!isIdentifier(name)) {
457                                throw new IllegalArgumentException(format(
458                                        "Variable is not valid identifier: '%s'",
459                                        name
460                                ));
461                        }
462                }
463
464                @Override
465                @SuppressWarnings("unchecked")
466                public <B> Var<B> map(final Function<? super V, ? extends B> mapper) {
467                        return (Var<B>)this;
468                }
469
470                @Override
471                public int compareTo(final Var<V> var) {
472                        return name.compareTo(var.name);
473                }
474
475                @Override
476                public String toString() {
477                        return format("%s%s", VAR_PREFIX, name);
478                }
479
480                static boolean isVar(final String name) {
481                        return !name.isEmpty() && name.charAt(0) == VAR_PREFIX;
482                }
483
484        }
485
486        /**
487         * This class represents a constant pattern value, which can be part of a
488         * whole subtree.
489         *
490         * @see Var
491         *
492         * @param <V> the node value type
493         * @param value the underlying pattern value
494         */
495        public record Val<V>(V value) implements Decl<V>, Serializable {
496                @Serial
497                private static final long serialVersionUID = 2L;
498
499                @Override
500                public <B> Val<B> map(final Function<? super V, ? extends B> mapper) {
501                        return new Val<>(mapper.apply(value));
502                }
503
504                @Override
505                public String toString() {
506                        return Objects.toString(value);
507                }
508
509        }
510
511}