001/* 002 * Java Genetic Algorithm Library (jenetics-5.1.0). 003 * Copyright (c) 2007-2019 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.Names.isIdentifier; 025 026import java.io.IOException; 027import java.io.InvalidObjectException; 028import java.io.ObjectInput; 029import java.io.ObjectOutput; 030import java.io.ObjectOutputStream; 031import java.io.Serializable; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.Map; 035import java.util.Objects; 036import java.util.Optional; 037import java.util.SortedSet; 038import java.util.TreeSet; 039import java.util.function.Function; 040 041import io.jenetics.ext.internal.Escaper; 042import io.jenetics.ext.util.Tree; 043import io.jenetics.ext.util.Tree.Path; 044import io.jenetics.ext.util.TreeNode; 045 046/** 047 * This class serves two purposes. Firstly, it is used as a <em>classical</em> 048 * pattern, which is used to find <em>matches</em> against a <em>matching</em> 049 * tree. Secondly, it can <em>expand</em> a given pattern to a full tree with a 050 * given <em>pattern</em> variable to sub-tree mapping. 051 * 052 * <p><b>Matching trees</b></p> 053 * 054 * A compiled representation of a <em>tree</em> pattern. A tree pattern, 055 * specified as a parentheses string, must first be compiled into an instance of 056 * this class. The resulting pattern can then be used to create a 057 * {@link TreeMatcher} object that can match arbitrary trees against the tree 058 * pattern. All of the state involved in performing a match resides in the 059 * matcher, so many matchers can share the same pattern. 060 * <p> 061 * The string representation of a tree pattern is a parenthesis tree string, 062 * with a special wildcard syntax for arbitrary sub-trees. The sub-trees 063 * variables are prefixed with a '$' and must be a valid Java identifier. 064 * <pre>{@code 065 * final TreePattern<String> p1 = TreePattern.compile("add($a,add($b,sin(x)))"); 066 * final TreePattern<String> p2 = TreePattern.compile("pow($x,$y)"); 067 * }</pre> 068 * 069 * If you need to have values which starts with a '$' character, you can escape 070 * it with a '\'. 071 * <pre>{@code 072 * final TreePattern<String> p1 = TreePattern.compile("concat($x,\\$foo)"); 073 * }</pre> 074 * 075 * The second value, {@code $foo}, of the {@code concat} function is not treated 076 * as <em>pattern</em> variable. 077 * <p> 078 * If you want to match against trees with a different value type than 079 * {@code String}, you have to specify an additional type mapper function when 080 * compiling the pattern string. 081 * <pre>{@code 082 * final TreePattern<Op<Double>> p = TreePattern.compile( 083 * "add($a,add($b,sin(x)))", 084 * MathOp::toMathOp 085 * ); 086 * }</pre> 087 * 088 * <p><b>Expanding trees</b></p> 089 * 090 * The second functionality of the tree pattern is to expand a pattern to a whole 091 * tree with a given <em>pattern</em> variable to sub-tree mapping. 092 * <pre>{@code 093 * final TreePattern<String> pattern = TreePattern.compile("add($x,$y,1)"); 094 * final Map<Var<String>, Tree<String, ?>> vars = new HashMap<>(); 095 * vars.put(Var.of("x"), TreeNode.parse("sin(x)")); 096 * vars.put(Var.of("y"), TreeNode.parse("sin(y)")); 097 * 098 * final Tree<String, ?> tree = pattern.expand(vars); 099 * assertEquals(tree.toParenthesesString(), "add(sin(x),sin(y),1)"); 100 * }</pre> 101 * 102 * @see TreeRewriteRule 103 * @see Tree#toParenthesesString() 104 * @see TreeMatcher 105 * 106 * @param <V> the value type of the tree than can be matched by this pattern 107 * 108 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 109 * @version 5.0 110 * @since 5.0 111 */ 112public final class TreePattern<V> implements Serializable { 113 114 private static final long serialVersionUID = 1L; 115 116 // Primary state of the tree pattern. 117 private final TreeNode<Decl<V>> _pattern; 118 119 // Cached variable set. 120 private final SortedSet<Var<V>> _vars; 121 122 /** 123 * Create a new tree-pattern object from the given pattern tree. 124 * 125 * @param pattern the pattern-tree 126 * @throws NullPointerException if the given {@code pattern} is {@code null} 127 * @throws IllegalArgumentException if {@link Var} nodes are not leaf nodes; 128 * {@link Tree#isLeaf()} is {@code false} 129 */ 130 public TreePattern(final Tree<Decl<V>, ?> pattern) { 131 _pattern = TreeNode.ofTree(pattern); 132 _vars = extractVars(_pattern); 133 } 134 135 // Extracts the variables from the pattern. 136 private static <V> SortedSet<Var<V>> 137 extractVars(final TreeNode<Decl<V>> pattern) { 138 final SortedSet<Var<V>> variables = new TreeSet<>(); 139 for (Tree<Decl<V>, ?> n : pattern) { 140 if (n.getValue() instanceof Var) { 141 if (!n.isLeaf()) { 142 throw new IllegalArgumentException(format( 143 "Variable node '%s' is not a leaf: %s", 144 n.getValue(), n.toParenthesesString() 145 )); 146 } 147 148 variables.add((Var<V>)n.getValue()); 149 } 150 } 151 152 return Collections.unmodifiableSortedSet(variables); 153 } 154 155 TreeNode<Decl<V>> pattern() { 156 return _pattern; 157 } 158 159 /** 160 * Return the <em>unmodifiable</em> set of variables, defined in {@code this} 161 * pattern. The variables are returned without the angle brackets. 162 * 163 * @return the variables, defined in this pattern 164 */ 165 public SortedSet<Var<V>> vars() { 166 return _vars; 167 } 168 169 /** 170 * Maps {@code this} tree-pattern from type {@code V} to type {@code B}. 171 * 172 * @param mapper the type mapper 173 * @param <B> the target type 174 * @return a new tree-pattern for the mapped type 175 */ 176 public <B> TreePattern<B> map(final Function<? super V, ? extends B> mapper) { 177 return new TreePattern<>(_pattern.map(d -> d.map(mapper))); 178 } 179 180 /** 181 * Creates a matcher that will match the given input tree against 182 * {@code this} pattern. 183 * 184 * @param tree the tree to be matched 185 * @return a new matcher for {@code this} pattern 186 * @throws NullPointerException if the arguments is {@code null} 187 */ 188 public TreeMatcher<V> matcher(final Tree<V, ?> tree) { 189 return TreeMatcher.of(this, tree); 190 } 191 192 /** 193 * Try to match the given {@code tree} against {@code this} pattern. 194 * 195 * @param tree the tree to be matched 196 * @return the match result, or {@link Optional#empty()} if the given 197 * {@code tree} doesn't match 198 * @throws NullPointerException if the arguments is {@code null} 199 */ 200 public Optional<TreeMatchResult<V>> match(final Tree<V, ?> tree) { 201 final Map<Var<V>, Tree<V, ?>> vars = new HashMap<>(); 202 final boolean matches = matches(tree, _pattern, vars); 203 204 return matches 205 ? Optional.of(TreeMatchResult.of(tree, vars)) 206 : Optional.empty(); 207 } 208 209 /** 210 * Tests whether the given input {@code tree} matches {@code this} pattern. 211 * 212 * @param tree the tree to be matched 213 * @return {@code true} if the {@code tree} matches {@code this} pattern, 214 * {@code false} otherwise 215 * @throws NullPointerException if one of the arguments is {@code null} 216 */ 217 public boolean matches(final Tree<V, ?> tree) { 218 return matches(tree, _pattern, new HashMap<>()); 219 } 220 221 private static <V> boolean matches( 222 final Tree<V, ?> node, 223 final Tree<Decl<V>, ?> pattern, 224 final Map<Var<V>, Tree<V, ?>> vars 225 ) { 226 final Decl<V> decl = pattern.getValue(); 227 228 if (decl instanceof Var) { 229 final Tree<? extends V, ?> tree = vars.get(decl); 230 if (tree == null) { 231 vars.put((Var<V>)decl, node); 232 return true; 233 } 234 235 return tree.equals(node); 236 } else { 237 final Val<V> p = (Val<V>)pattern.getValue(); 238 final V v = node.getValue(); 239 240 if (Objects.equals(v, p.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 return false; 248 } 249 } 250 return true; 251 } else { 252 return false; 253 } 254 } else { 255 return false; 256 } 257 } 258 } 259 260 /** 261 * Expands {@code this} pattern with the given variable mapping. 262 * 263 * @param vars the variables to use for expanding {@code this} pattern 264 * @return the expanded tree pattern 265 * @throws NullPointerException if one of the arguments is {@code null} 266 * @throws IllegalArgumentException if not all needed variables are part 267 * of the {@code variables} map 268 */ 269 public TreeNode<V> expand(final Map<Var<V>, Tree<V, ?>> vars) { 270 return expand(_pattern, vars); 271 } 272 273 // Expanding the template. 274 private static <V> TreeNode<V> expand( 275 final Tree<Decl<V>, ?> template, 276 final Map<Var<V>, Tree<V, ?>> vars 277 ) { 278 final Map<Path, Var<V>> paths = template.stream() 279 .filter((Tree<Decl<V>, ?> n) -> n.getValue() instanceof Var) 280 .collect(toMap(t -> t.childPath(), t -> (Var<V>)t.getValue())); 281 282 final TreeNode<V> tree = TreeNode.ofTree( 283 template, 284 n -> n instanceof Val ? ((Val<V>)n).value() : null 285 ); 286 287 for (Map.Entry<Path, Var<V>> var : paths.entrySet()) { 288 final Path path = var.getKey(); 289 final Var<V> decl = var.getValue(); 290 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 && 311 _pattern.equals(((TreePattern)obj)._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 public static <V> TreePattern<V> compile( 338 final String pattern, 339 final Function<? super String, ? extends V> mapper 340 ) { 341 return new TreePattern<>( 342 TreeNode.parse(pattern, v -> Decl.of(v.trim(), mapper)) 343 ); 344 } 345 346 /* ************************************************************************* 347 * Java object serialization 348 * ************************************************************************/ 349 350 private Object writeReplace() { 351 return new Serial(Serial.TREE_PATTERN, this); 352 } 353 354 private void readObject(final ObjectOutputStream stream) 355 throws InvalidObjectException 356 { 357 throw new InvalidObjectException("Serialization proxy required."); 358 } 359 360 void write(final ObjectOutput out) throws IOException { 361 out.writeObject(_pattern); 362 } 363 364 @SuppressWarnings({"unchecked", "rawtypes"}) 365 static TreePattern read(final ObjectInput in) 366 throws IOException, ClassNotFoundException 367 { 368 final TreeNode pattern = (TreeNode)in.readObject(); 369 return new TreePattern(pattern); 370 } 371 372 373 /* ************************************************************************* 374 * Pattern node classes. 375 * ************************************************************************/ 376 377 /** 378 * A <em>sealed</em> class, which constitutes the nodes of a pattern tree. 379 * The only two implementations of this class are the {@link Var} and the 380 * {@link Val} class. The {@link Var} class represents a placeholder for an 381 * arbitrary sub-tree and the {@link Val} class stands for an arbitrary 382 * concrete sub-tree. 383 * 384 * @see Var 385 * @see Val 386 * 387 * @param <V> the node type the tree-pattern is working on 388 */ 389 public abstract static class Decl<V> { 390 private static final char VAR_PREFIX = '$'; 391 private static final char ESC_CHAR = '\\'; 392 393 private static final Escaper ESCAPER = new Escaper(ESC_CHAR, VAR_PREFIX); 394 395 private Decl() { 396 } 397 398 abstract <B> Decl<B> map(final Function<? super V, ? extends B> mapper); 399 400 static <V> Decl<V> of( 401 final String value, 402 final Function<? super String, ? extends V> mapper 403 ) { 404 return Var.isVar(value) 405 ? Var.of(value.substring(1)) 406 : Val.of(mapper.apply(ESCAPER.unescape(value))); 407 } 408 } 409 410 /** 411 * Represents a placeholder (variable) for an arbitrary sub-tree. A 412 * <em>pattern</em> variable is identified by its name. The pattern DSL 413 * denotes variable names with a leading '$' character, e.g. {@code $x}, 414 * {@code $y} or {@code $my_var}. It is one of two implementations of the 415 * <em>sealed</em> {@link Decl} class. 416 * 417 * @see Val 418 * 419 * @implNote 420 * This class is comparable by it's name. 421 * 422 * @param <V> the node type the tree-pattern is working 423 */ 424 public static final class Var<V> 425 extends Decl<V> 426 implements Comparable<Var<V>>, Serializable 427 { 428 private static final long serialVersionUID = 1L; 429 430 private final String _name; 431 432 private Var(final String name) { 433 if (!isIdentifier(name)) { 434 throw new IllegalArgumentException(format( 435 "Variable is not valid identifier: '%s'", 436 name 437 )); 438 } 439 _name = name; 440 } 441 442 @Override 443 @SuppressWarnings("unchecked") 444 <B> Var<B> map(final Function<? super V, ? extends B> mapper) { 445 return (Var<B>)this; 446 } 447 448 /** 449 * Return the name of the variable. 450 * 451 * @return the variable name 452 */ 453 public String name() { 454 return _name; 455 } 456 457 @Override 458 public int compareTo(final Var<V> var) { 459 return _name.compareTo(var._name); 460 } 461 462 @Override 463 public int hashCode() { 464 return _name.hashCode(); 465 } 466 467 @Override 468 public boolean equals(final Object obj) { 469 return obj == this || 470 obj instanceof Var && 471 Objects.equals(_name, ((Var)obj)._name); 472 } 473 474 @Override 475 public String toString() { 476 return format("%s%s", Decl.VAR_PREFIX, _name); 477 } 478 479 /** 480 * Return a new variable with the given name. 481 * 482 * @param name the name of the variable 483 * @param <V> the node type the tree-pattern is working on 484 * @return a new variable with the given name 485 * @throws NullPointerException if the given {@code name} is {@code null} 486 * @throws IllegalArgumentException if the given {@code name} is not a 487 * valid Java identifier 488 */ 489 public static <V> Var<V> of(final String name) { 490 return new Var<>(name); 491 } 492 493 static boolean isVar(final String name) { 494 return !name.isEmpty() && name.charAt(0) == Decl.VAR_PREFIX; 495 } 496 497 /* ********************************************************************* 498 * Java object serialization 499 * ********************************************************************/ 500 501 private Object writeReplace() { 502 return new Serial(Serial.TREE_PATTERN_VAR, this); 503 } 504 505 private void readObject(final ObjectOutputStream stream) 506 throws InvalidObjectException 507 { 508 throw new InvalidObjectException("Serialization proxy required."); 509 } 510 511 void write(final ObjectOutput out) throws IOException { 512 out.writeObject(_name); 513 } 514 515 @SuppressWarnings({"unchecked", "rawtypes"}) 516 static Var read(final ObjectInput in) 517 throws IOException, ClassNotFoundException 518 { 519 final String name = (String)in.readObject(); 520 return new Var(name); 521 } 522 523 } 524 525 /** 526 * This class represents a constant pattern value, which can be part of a 527 * whole sub-tree. It is one of two implementations of the <em>sealed</em> 528 * {@link Decl} class. 529 * 530 * @see Var 531 * 532 * @param <V> the node value type 533 */ 534 public static final class Val<V> extends Decl<V> implements Serializable { 535 private static final long serialVersionUID = 1L; 536 537 private final V _value; 538 539 private Val(final V value) { 540 _value = value; 541 } 542 543 public V value() { 544 return _value; 545 } 546 547 @Override 548 <B> Val<B> map(final Function<? super V, ? extends B> mapper) { 549 return of(mapper.apply(_value)); 550 } 551 552 @Override 553 public int hashCode() { 554 return Objects.hashCode(_value); 555 } 556 557 @Override 558 public boolean equals(final Object obj) { 559 return obj == this || 560 obj instanceof TreePattern.Val && 561 Objects.equals(_value, ((Val)obj)._value); 562 } 563 564 @Override 565 public String toString() { 566 return Objects.toString(_value); 567 } 568 569 /** 570 * Create a new <em>value</em> object. 571 * 572 * @param value the underlying pattern value 573 * @param <V> the node type 574 * @return a new <em>value</em> object 575 */ 576 public static <V> Val<V> of(final V value) { 577 return new Val<>(value); 578 } 579 580 581 /* ********************************************************************* 582 * Java object serialization 583 * ********************************************************************/ 584 585 private Object writeReplace() { 586 return new Serial(Serial.TREE_PATTERN_VAL, this); 587 } 588 589 private void readObject(final ObjectOutputStream stream) 590 throws InvalidObjectException 591 { 592 throw new InvalidObjectException("Serialization proxy required."); 593 } 594 595 void write(final ObjectOutput out) throws IOException { 596 out.writeObject(_value); 597 } 598 599 @SuppressWarnings({"unchecked", "rawtypes"}) 600 static Val read(final ObjectInput in) 601 throws IOException, ClassNotFoundException 602 { 603 return new Val(in.readObject()); 604 } 605 } 606 607}