@@ -417,6 +417,19 @@ private static List<TreePattern> optimizePatterns(final List<TreePattern> patter
417417 /**
418418 * Prunes patterns from the inclusion list that are fully covered by ANY pattern in the exclusion
419419 * list.
420+ *
421+ * <p><b>Optimization Strategy:</b>
422+ *
423+ * <ol>
424+ * <li><b>Build Exclusion Trie:</b> Construct a Trie containing all paths from the exclusion
425+ * list. This aggregates the coverage of all exclusion patterns into a single structure.
426+ * <li><b>Check Coverage:</b> Iterate through the inclusion list. For each inclusion pattern,
427+ * check if ALL of its represented paths are covered by the Exclusion Trie. If so, the
428+ * pattern is redundant and removed.
429+ * </ol>
430+ *
431+ * <p><b>Time Complexity:</b> O((N + M) * L), where N is the number of inclusion patterns, M is
432+ * the number of exclusion patterns, and L is the average path length.
420433 */
421434 private static List <TreePattern > pruneInclusionPatterns (
422435 final List <TreePattern > inclusion , final List <TreePattern > exclusion ) {
@@ -427,15 +440,29 @@ private static List<TreePattern> pruneInclusionPatterns(
427440 return inclusion ;
428441 }
429442
443+ // 1. Build Trie with all exclusion paths
444+ // The Trie represents the union of all excluded areas.
445+ final PatternTrie exclusionTrie = new PatternTrie ();
446+ for (final TreePattern exc : exclusion ) {
447+ for (final PartialPath path : exc .getBaseInclusionPaths ()) {
448+ exclusionTrie .add (path );
449+ }
450+ }
451+
430452 final List <TreePattern > prunedInclusion = new ArrayList <>();
453+ // 2. Filter inclusion patterns
431454 for (final TreePattern inc : inclusion ) {
432- boolean isFullyExcluded = false ;
433- for (final TreePattern exc : exclusion ) {
434- if (covers (exc , inc )) {
435- isFullyExcluded = true ;
455+ boolean isFullyExcluded = true ;
456+ // An inclusion pattern is fully excluded only if ALL its constituent base paths
457+ // are covered by the exclusion Trie.
458+ for (final PartialPath path : inc .getBaseInclusionPaths ()) {
459+ if (!exclusionTrie .isCovered (path )) {
460+ isFullyExcluded = false ;
436461 break ;
437462 }
438463 }
464+
465+ // If not fully excluded (i.e., at least one path survives), keep it.
439466 if (!isFullyExcluded ) {
440467 prunedInclusion .add (inc );
441468 }
@@ -446,105 +473,57 @@ private static List<TreePattern> pruneInclusionPatterns(
446473 /**
447474 * Prunes patterns from the exclusion list that do NOT overlap with any of the remaining inclusion
448475 * patterns.
476+ *
477+ * <p><b>Optimization Strategy:</b>
478+ *
479+ * <ol>
480+ * <li><b>Build Inclusion Trie:</b> Construct a Trie containing all paths from the inclusion
481+ * list. This aggregates the search space of inclusion patterns.
482+ * <li><b>Filter Exclusions:</b> Iterate through the exclusion list. For each exclusion pattern,
483+ * check if it overlaps with the Inclusion Trie. Only exclusions that overlap with at least
484+ * one inclusion pattern are kept.
485+ * </ol>
486+ *
487+ * <p><b>Time Complexity:</b> O((N + M) * L), where N is the number of inclusion patterns, M is
488+ * the number of exclusion patterns, and L is the average path length.
449489 */
450490 private static List <TreePattern > pruneIrrelevantExclusions (
451491 final List <TreePattern > inclusion , final List <TreePattern > exclusion ) {
452492 if (exclusion == null || exclusion .isEmpty ()) {
453493 return new ArrayList <>();
454494 }
455495 if (inclusion == null || inclusion .isEmpty ()) {
456- // If inclusion is empty, exclusion is irrelevant anyway, but usually this case
457- // throws exception earlier.
496+ // If inclusion is empty, exclusion is irrelevant anyway.
458497 return new ArrayList <>();
459498 }
460499
500+ // 1. Build Trie from Inclusion Patterns
501+ final PatternTrie inclusionTrie = new PatternTrie ();
502+ for (final TreePattern inc : inclusion ) {
503+ for (final PartialPath path : inc .getBaseInclusionPaths ()) {
504+ inclusionTrie .add (path );
505+ }
506+ }
507+
508+ // 2. Filter Exclusion Patterns using the Trie
461509 final List <TreePattern > relevantExclusion = new ArrayList <>();
462510 for (final TreePattern exc : exclusion ) {
463511 boolean overlapsWithAnyInclusion = false ;
464- for (final TreePattern inc : inclusion ) {
465- if (overlaps (exc , inc )) {
512+ // An exclusion pattern is relevant if ANY of its base paths overlap with the inclusion Trie
513+ for (final PartialPath path : exc .getBaseInclusionPaths ()) {
514+ if (inclusionTrie .overlaps (path )) {
466515 overlapsWithAnyInclusion = true ;
467516 break ;
468517 }
469518 }
519+
470520 if (overlapsWithAnyInclusion ) {
471521 relevantExclusion .add (exc );
472522 }
473523 }
474524 return relevantExclusion ;
475525 }
476526
477- /** Checks if 'coverer' pattern fully covers 'coveree' pattern. */
478- private static boolean covers (final TreePattern coverer , final TreePattern coveree ) {
479- try {
480- final List <PartialPath > covererPaths = coverer .getBaseInclusionPaths ();
481- final List <PartialPath > covereePaths = coveree .getBaseInclusionPaths ();
482-
483- if (covererPaths .isEmpty () || covereePaths .isEmpty ()) {
484- return false ;
485- }
486-
487- // Logic: For 'coverer' to cover 'coveree', ALL paths in 'coveree' must be included
488- // by at least one path in 'coverer'.
489- for (final PartialPath sub : covereePaths ) {
490- boolean isSubCovered = false ;
491- for (final PartialPath sup : covererPaths ) {
492- if (sup .include (sub )) {
493- isSubCovered = true ;
494- break ;
495- }
496- }
497- if (!isSubCovered ) {
498- return false ;
499- }
500- }
501- return true ;
502- } catch (final Exception e ) {
503- // In case of path parsing errors or unsupported operations, assume no coverage
504- // to be safe and avoid aggressive pruning.
505- LOGGER .warn (
506- "Pipe: Failed to check if pattern [{}] covers [{}]. Assuming false." ,
507- coverer .getPattern (),
508- coveree .getPattern (),
509- e );
510- return false ;
511- }
512- }
513-
514- /** Checks if 'patternA' overlaps with 'patternB' using a Trie optimization. */
515- private static boolean overlaps (final TreePattern patternA , final TreePattern patternB ) {
516- try {
517- final List <PartialPath > pathsA = patternA .getBaseInclusionPaths ();
518- final List <PartialPath > pathsB = patternB .getBaseInclusionPaths ();
519-
520- if (pathsA .isEmpty () || pathsB .isEmpty ()) {
521- return false ;
522- }
523-
524- // Optimization: Build Trie from the smaller list (usually) or just patternB
525- // to avoid O(N^2) comparisons.
526- final PatternTrie trie = new PatternTrie ();
527- for (final PartialPath path : pathsB ) {
528- trie .add (path );
529- }
530-
531- // Check if any path in A overlaps with the Trie constructed from B
532- for (final PartialPath pathA : pathsA ) {
533- if (trie .overlaps (pathA )) {
534- return true ;
535- }
536- }
537- return false ;
538- } catch (final Exception e ) {
539- LOGGER .warn (
540- "Pipe: Failed to check if pattern [{}] overlaps with [{}]. Assuming false." ,
541- patternA .getPattern (),
542- patternB .getPattern (),
543- e );
544- return false ;
545- }
546- }
547-
548527 /**
549528 * A private helper method to parse a list of {@link TreePattern}s from the "pattern" parameter,
550529 * considering its "format".
0 commit comments