TreePattern.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.1.2).
003  * Copyright (c) 2007-2023 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  */
020 package io.jenetics.ext.rewriting;
021 
022 import static java.lang.String.format;
023 import static java.util.stream.Collectors.toMap;
024 import static io.jenetics.ext.internal.util.Names.isIdentifier;
025 
026 import java.io.IOException;
027 import java.io.InvalidObjectException;
028 import java.io.ObjectInput;
029 import java.io.ObjectInputStream;
030 import java.io.ObjectOutput;
031 import java.io.Serial;
032 import java.io.Serializable;
033 import java.util.Collections;
034 import java.util.HashMap;
035 import java.util.Map;
036 import java.util.Objects;
037 import java.util.Optional;
038 import java.util.SortedSet;
039 import java.util.TreeSet;
040 import java.util.function.Function;
041 
042 import io.jenetics.ext.internal.util.Escaper;
043 import io.jenetics.ext.util.Tree;
044 import io.jenetics.ext.util.Tree.Path;
045 import 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 sub-tree 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 resides 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 sub-trees. The sub-trees
064  * variables are prefixed with a '$' and must be a valid Java identifier.
065  <pre>{@code
066  * final TreePattern<String> p1 = TreePattern.compile("add($a,add($b,sin(x)))");
067  * final TreePattern<String> p2 = TreePattern.compile("pow($x,$y)");
068  * }</pre>
069  *
070  * If you need to have values which starts with a '$' character, you can escape
071  * it with a '\'.
072  <pre>{@code
073  * final TreePattern<String> p1 = TreePattern.compile("concat($x,\\$foo)");
074  * }</pre>
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  <pre>{@code
083  * final TreePattern<Op<Double>> p = TreePattern.compile(
084  *     "add($a,add($b,sin(x)))",
085  *     MathOp::toMathOp
086  * );
087  * }</pre>
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 sub-tree mapping.
093  <pre>{@code
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  * }</pre>
103  *
104  @see TreeRewriteRule
105  @see Tree#toParenthesesString()
106  @see TreeMatcher
107  *
108  @param <V> the value type of the tree than can be matched 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  */
114 public 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 == this ||
310             obj instanceof TreePattern<?> other &&
311             _pattern.equals(other._pattern);
312     }
313 
314     @Override
315     public String toString() {
316         return _pattern.toParenthesesString();
317     }
318 
319     /* *************************************************************************
320      * Static factory methods.
321      * ************************************************************************/
322 
323     /**
324      * Compiles the given tree pattern string.
325      *
326      @param pattern the tree pattern string
327      @return the compiled pattern
328      @throws NullPointerException if the given pattern is {@code null}
329      @throws IllegalArgumentException if the given parentheses tree string
330      *         doesn't represent a valid pattern tree or one of the variable
331      *         name is not a valid (Java) identifier
332      */
333     public static TreePattern<String> compile(final String pattern) {
334         return compile(pattern, Function.identity());
335     }
336 
337     /**
338      * Compiles the given tree pattern string.
339      *
340      @param pattern the tree pattern string
341      @param mapper the mapper which converts the serialized string value to
342      *        the desired type
343      @param <V> the value type of the tree than can be matched by the pattern
344      @return the compiled pattern
345      @throws NullPointerException if the given pattern is {@code null}
346      @throws IllegalArgumentException if the given parentheses tree string
347      *         doesn't represent a valid pattern tree or one of the variable
348      *         name is not a valid (Java) identifier
349      */
350     public static <V> TreePattern<V> compile(
351         final String pattern,
352         final Function<? super String, ? extends V> mapper
353     ) {
354         return new TreePattern<>(
355             TreeNode.parse(pattern, v -> Decl.of(v.trim(), mapper))
356         );
357     }
358 
359     /* *************************************************************************
360      *  Java object serialization
361      * ************************************************************************/
362 
363     @Serial
364     private Object writeReplace() {
365         return new SerialProxy(SerialProxy.TREE_PATTERN, this);
366     }
367 
368     @Serial
369     private void readObject(final ObjectInputStream stream)
370         throws InvalidObjectException
371     {
372         throw new InvalidObjectException("Serialization proxy required.");
373     }
374 
375     void write(final ObjectOutput outthrows IOException {
376         out.writeObject(_pattern);
377     }
378 
379     @SuppressWarnings({"unchecked""rawtypes"})
380     static Object read(final ObjectInput in)
381         throws IOException, ClassNotFoundException
382     {
383         final var pattern = (TreeNode)in.readObject();
384         return new TreePattern(pattern);
385     }
386 
387 
388     /* *************************************************************************
389      * Pattern node classes.
390      * ************************************************************************/
391 
392     private static final char VAR_PREFIX = '$';
393     private static final char ESC_CHAR = '\\';
394 
395     private static final Escaper ESCAPER = new Escaper(ESC_CHAR, VAR_PREFIX);
396 
397     /**
398      * A sealed interface, which constitutes the nodes of a pattern tree.
399      * The only two implementations of this class are the {@link Var} and the
400      {@link Val} class. The {@link Var} class represents a placeholder for an
401      * arbitrary subtree and the {@link Val} class stands for an arbitrary
402      * concrete subtree.
403      *
404      @see Var
405      @see Val
406      *
407      @param <V> the node type the tree-pattern is working on
408      */
409     public sealed interface Decl<V> {
410 
411         /**
412          * Returns a new {@link Decl} object with the mapped type {@code B}.
413          *
414          @param mapper the mapping function
415          @param <B> the mapped type
416          @return the mapped declaration
417          @throws NullPointerException if the mapping function is {@code null}
418          */
419         <B> Decl<B> map(final Function<? super V, ? extends B> mapper);
420 
421         static <V> Decl<V> of(
422             final String value,
423             final Function<? super String, ? extends V> mapper
424         ) {
425             return Var.isVar(value)
426                 new Var<>(value.substring(1))
427                 new Val<>(mapper.apply(ESCAPER.unescape(value)));
428         }
429     }
430 
431     /**
432      * Represents a placeholder (variable) for an arbitrary sub-tree. A
433      <em>pattern</em> variable is identified by its name. The pattern DSL
434      * denotes variable names with a leading '$' character, e.g. {@code $x},
435      * {@code $y} or {@code $my_var}.
436      *
437      @see Val
438      *
439      * @implNote
440      * This class is comparable by its name.
441      *
442      @param <V> the node type the tree-pattern is working on
443      */
444     public record Var<V>(String name)
445         implements Decl<V>, Comparable<Var<V>>, Serializable
446     {
447         @Serial
448         private static final long serialVersionUID = 2L;
449 
450         /**
451          @param name the name of the variable
452          @throws NullPointerException if the given {@code name} is {@code null}
453          @throws IllegalArgumentException if the given {@code name} is not a
454          *         valid Java identifier
455          */
456         public Var {
457             if (!isIdentifier(name)) {
458                 throw new IllegalArgumentException(format(
459                     "Variable is not valid identifier: '%s'",
460                     name
461                 ));
462             }
463         }
464 
465         @Override
466         @SuppressWarnings("unchecked")
467         public <B> Var<B> map(final Function<? super V, ? extends B> mapper) {
468             return (Var<B>)this;
469         }
470 
471         @Override
472         public int compareTo(final Var<V> var) {
473             return name.compareTo(var.name);
474         }
475 
476         @Override
477         public String toString() {
478             return format("%s%s", VAR_PREFIX, name);
479         }
480 
481         static boolean isVar(final String name) {
482             return !name.isEmpty() && name.charAt(0== VAR_PREFIX;
483         }
484 
485     }
486 
487     /**
488      * This class represents a constant pattern value, which can be part of a
489      * whole subtree.
490      *
491      @see Var
492      *
493      @param <V> the node value type
494      @param value the underlying pattern value
495      */
496     public record Val<V>(V valueimplements Decl<V>, Serializable {
497         @Serial
498         private static final long serialVersionUID = 2L;
499 
500         @Override
501         public <B> Val<B> map(final Function<? super V, ? extends B> mapper) {
502             return new Val<>(mapper.apply(value));
503         }
504 
505         @Override
506         public String toString() {
507             return Objects.toString(value);
508         }
509 
510     }
511 
512 }