package trees; import java.util.ArrayList; import java.util.Collections; import java.util.List; import parser.Match; import util.ArrayIterator; import static util.ArrayUtils.arr; /** * The class implements the ability to find sub-match(es) of a match matching * certain criteria. */ public class MatchFinder { /***************************************************************************** * Returns an array containing zero or more sub-matches of $root that match * the specification $spec. * * If $inclusive is true, those matches should be situated "between" a * sequence of matches that satisfy the specifications in $before; and a * sequence of matches that satisfy the specifications in $after. * * If $inclusive is false, the matches should be "outside", i.e. not between * or part of $before and $after. * * A Match matched by $spec cannot be a sub-match of any Match used to satisfy * the requirements expressed by $before and $after. * * The matches used to satisfy the specifications in $before and $after don't * need to be contiguous, but they need to appear in the same order as in the * specification array. If a specification is matched by a Match, no other * specification in the same array may be matched by that Match or by one of * its sub-matches. * * The $finder indicates which match(es) of the remaining interval we want to * gather. */ public static Match[] find( final Match root, final MatchSpec spec, final Finder finder, final MatchSpec[] before, final MatchSpec[] after, boolean inclusive) { List<Match> left = trace(root, before, true, !inclusive); if (left == null) { return Match.EMPTY; } List<Match> right = trace(root, after, false, !inclusive); if (right == null) { return Match.EMPTY; } BoundedMatchIterator iter = new BoundedMatchIterator( root, left, right, finder.leftToRight, inclusive); return finder.find(spec, iter); } /***************************************************************************** * Returns a trace (see {@link MatchIterator#trace()}) from $root to the * Match matching the last specification (see below) from $Lspecs. Returns an * empty trace if $Lspecs is empty. If not all specifications in $Lspecs can * be satisfied, returns null instead. * * If $firstSpec is true, returns the trace for the element matching the first * specification; else use the last specification instead. "first" and "last" are * relative to the order indicated by $leftToRight. */ private static List<Match> trace(Match root, MatchSpec[] Lspecs, boolean leftToRight, boolean firstSpec) { ArrayIterator<MatchSpec> specs = new ArrayIterator<>(Lspecs, !leftToRight); MatchIterator iter = new MatchIterator(root, leftToRight); if (!specs.hasNext()) { return Collections.emptyList(); } List<Match> trace = null; specs.forward(); while (iter.hasNext() && !specs.pastEdge()) { if (specs.current().matches(iter.next())) { if (firstSpec && trace == null) { trace = iter.trace(); } specs.forward(); } } return specs.pastEdge() ? trace != null ? trace : iter.trace() : null; } /****************************************************************************/ public enum Finder { /** * A finder expresses a strategy to gather matches that are between $before * and $after, given a specification. */ /**************************************************************************/ FIRST (true), /**************************************************************************/ LAST (false), /**************************************************************************/ ALL (true) { @Override Match[] find(MatchSpec spec, BoundedMatchIterator iter) { List<Match> matches = new ArrayList<>(); for (Match m : iter) { if (spec.matches(m)) { matches.add(m); iter.skipChilds(); } } return matches.toArray(Match.EMPTY); } }; /**************************************************************************/ final boolean leftToRight; /**************************************************************************/ Finder(boolean leftToRight) { this.leftToRight = leftToRight; } /*************************************************************************** * Implements the FIRST or LAST, strategy, depending on the order of * iteration; Finder#leftToRight should be used to select the correct * ordering. */ Match[] find(MatchSpec spec, BoundedMatchIterator iter) { for (Match m : iter) { if (spec.matches(m)) { return arr(m); } } return Match.EMPTY; } } }