001/* 002 * Java Genetic Algorithm Library (jenetics-9.1.0). 003 * Copyright (c) 2007-2026 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 return switch (pattern.value()) { 230 case Var<V> var -> { 231 final Tree<? extends V, ?> tree = vars.get(var); 232 if (tree == null) { 233 vars.put(var, node); 234 yield true; 235 } 236 237 yield tree.equals(node); 238 } 239 case Val<V>(var value) -> { 240 if (Objects.equals(node.value(), value)) { 241 if (node.childCount() == pattern.childCount()) { 242 for (int i = 0; i < node.childCount(); ++i) { 243 final Tree<V, ?> cn = node.childAt(i); 244 final Tree<Decl<V>, ?> cp = pattern.childAt(i); 245 246 if (!matches(cn, cp, vars)) { 247 yield false; 248 } 249 } 250 yield true; 251 } else { 252 yield false; 253 } 254 } else { 255 yield false; 256 } 257 } 258 }; 259 } 260 261 /** 262 * Expands {@code this} pattern with the given variable mapping. 263 * 264 * @param vars the variables to use for expanding {@code this} pattern 265 * @return the expanded tree pattern 266 * @throws NullPointerException if one of the arguments is {@code null} 267 * @throws IllegalArgumentException if not all needed variables are part 268 * of the {@code variables} map 269 */ 270 public TreeNode<V> expand(final Map<Var<V>, Tree<V, ?>> vars) { 271 return expand(_pattern, vars); 272 } 273 274 // Expanding the template. 275 private static <V> TreeNode<V> expand( 276 final Tree<Decl<V>, ?> template, 277 final Map<Var<V>, Tree<V, ?>> vars 278 ) { 279 final Map<Path, Var<V>> paths = template.stream() 280 .filter((Tree<Decl<V>, ?> n) -> n.value() instanceof Var) 281 .collect(toMap(Tree::childPath, t -> (Var<V>)t.value())); 282 283 final TreeNode<V> tree = TreeNode.ofTree( 284 template, 285 n -> n instanceof Val<V>(V value) ? value : null 286 ); 287 288 paths.forEach((path, decl) -> { 289 final Tree<? extends V, ?> replacement = vars.get(decl); 290 if (replacement != null) { 291 tree.replaceAtPath(path, TreeNode.ofTree(replacement)); 292 } else { 293 tree.removeAtPath(path); 294 } 295 }); 296 297 return tree; 298 } 299 300 @Override 301 public int hashCode() { 302 return _pattern.hashCode(); 303 } 304 305 @Override 306 public boolean equals(final Object obj) { 307 return obj instanceof TreePattern<?> other && 308 _pattern.equals(other._pattern); 309 } 310 311 @Override 312 public String toString() { 313 return _pattern.toParenthesesString(); 314 } 315 316 /* ************************************************************************* 317 * Static factory methods. 318 * ************************************************************************/ 319 320 /** 321 * Compiles the given tree pattern string. 322 * 323 * @param pattern the tree pattern string 324 * @return the compiled pattern 325 * @throws NullPointerException if the given pattern is {@code null} 326 * @throws IllegalArgumentException if the given parentheses tree string 327 * doesn't represent a valid pattern tree or one of the variable 328 * names is not a valid (Java) identifier 329 */ 330 public static TreePattern<String> compile(final String pattern) { 331 return compile(pattern, Function.identity()); 332 } 333 334 /** 335 * Compiles the given tree pattern string. 336 * 337 * @param pattern the tree pattern string 338 * @param mapper the mapper which converts the serialized string value to 339 * the desired type 340 * @param <V> the value type of the tree than can be matched by the pattern 341 * @return the compiled pattern 342 * @throws NullPointerException if the given pattern is {@code null} 343 * @throws IllegalArgumentException if the given parentheses tree string 344 * doesn't represent a valid pattern tree or one of the variable 345 * names is not a valid (Java) identifier 346 */ 347 public static <V> TreePattern<V> compile( 348 final String pattern, 349 final Function<? super String, ? extends V> mapper 350 ) { 351 return new TreePattern<>( 352 TreeNode.parse(pattern, v -> Decl.of(v.trim(), mapper)) 353 ); 354 } 355 356 /* ************************************************************************* 357 * Java object serialization 358 * ************************************************************************/ 359 360 @Serial 361 private Object writeReplace() { 362 return new SerialProxy(SerialProxy.TREE_PATTERN, this); 363 } 364 365 @Serial 366 private void readObject(final ObjectInputStream stream) 367 throws InvalidObjectException 368 { 369 throw new InvalidObjectException("Serialization proxy required."); 370 } 371 372 void write(final ObjectOutput out) throws IOException { 373 out.writeObject(_pattern); 374 } 375 376 @SuppressWarnings({"unchecked", "rawtypes"}) 377 static Object read(final ObjectInput in) 378 throws IOException, ClassNotFoundException 379 { 380 final var pattern = (TreeNode)in.readObject(); 381 return new TreePattern(pattern); 382 } 383 384 385 /* ************************************************************************* 386 * Pattern node classes. 387 * ************************************************************************/ 388 389 private static final char VAR_PREFIX = '$'; 390 private static final char ESC_CHAR = '\\'; 391 392 private static final Escaper ESCAPER = new Escaper(ESC_CHAR, VAR_PREFIX); 393 394 /** 395 * A sealed interface, which constitutes the nodes of a pattern tree. 396 * The only two implementations of this class are the {@link Var} and the 397 * {@link Val} class. The {@link Var} class represents a placeholder for an 398 * arbitrary subtree and the {@link Val} class stands for an arbitrary 399 * concrete subtree. 400 * 401 * @see Var 402 * @see Val 403 * 404 * @param <V> the node type the tree-pattern is working on 405 */ 406 public sealed interface Decl<V> { 407 408 /** 409 * Returns a new {@link Decl} object with the mapped type {@code B}. 410 * 411 * @param mapper the mapping function 412 * @param <B> the mapped type 413 * @return the mapped declaration 414 * @throws NullPointerException if the mapping function is {@code null} 415 */ 416 <B> Decl<B> map(final Function<? super V, ? extends B> mapper); 417 418 static <V> Decl<V> of( 419 final String value, 420 final Function<? super String, ? extends V> mapper 421 ) { 422 return Var.isVar(value) 423 ? new Var<>(value.substring(1)) 424 : new Val<>(mapper.apply(ESCAPER.unescape(value))); 425 } 426 } 427 428 /** 429 * Represents a placeholder (variable) for an arbitrary subtree. A 430 * <em>pattern</em> variable is identified by its name. The pattern DSL 431 * denotes variable names with a leading '$' character, e.g. {@code $x}, 432 * {@code $y} or {@code $my_var}. 433 * 434 * @see Val 435 * 436 * @implNote 437 * This class is comparable by its name. 438 * 439 @param <V> the node type the tree-pattern is working on 440 */ 441 public record Var<V>(String name) 442 implements Decl<V>, Comparable<Var<V>>, Serializable 443 { 444 @Serial 445 private static final long serialVersionUID = 2L; 446 447 /** 448 * @param name the name of the variable 449 * @throws NullPointerException if the given {@code name} is {@code null} 450 * @throws IllegalArgumentException if the given {@code name} is not a 451 * valid Java identifier 452 */ 453 public Var { 454 if (!isIdentifier(name)) { 455 throw new IllegalArgumentException(format( 456 "Variable is not valid identifier: '%s'", 457 name 458 )); 459 } 460 } 461 462 @Override 463 @SuppressWarnings("unchecked") 464 public <B> Var<B> map(final Function<? super V, ? extends B> mapper) { 465 return (Var<B>)this; 466 } 467 468 @Override 469 public int compareTo(final Var<V> var) { 470 return name.compareTo(var.name); 471 } 472 473 @Override 474 public String toString() { 475 return format("%s%s", VAR_PREFIX, name); 476 } 477 478 static boolean isVar(final String name) { 479 return !name.isEmpty() && name.charAt(0) == VAR_PREFIX; 480 } 481 482 } 483 484 /** 485 * This class represents a constant pattern value, which can be part of a 486 * whole subtree. 487 * 488 * @see Var 489 * 490 * @param <V> the node value type 491 * @param value the underlying pattern value 492 */ 493 public record Val<V>(V value) implements Decl<V>, Serializable { 494 @Serial 495 private static final long serialVersionUID = 2L; 496 497 @Override 498 public <B> Val<B> map(final Function<? super V, ? extends B> mapper) { 499 return new Val<>(mapper.apply(value)); 500 } 501 502 @Override 503 public String toString() { 504 return Objects.toString(value); 505 } 506 507 } 508 509}