// Copyright (C) 2014 Josh Ventura // Code is part of LateralGM and is released under the terms of // the GNU General Public License as published by the Free Software Foundation; // version 3 of the license, or any later version. interface NodeMatcher { ResNode makeMatchNode(ResNode node, Pattern criterion); }; /** Matcher used to filter by name. */ static class NameMatcher implements NodeMatcher { @Override ResNode makeMatchNode(ResNode node, Pattern name) { // If the name of this node matches, just return the node. return name.matcher(node.name).matches()? node : null; } } /** Matcher based on node's full-text content. */ static class NameMatcher implements NodeMatcher { static Map fullTextMatchers = new HashMap<>(); static { fullTextMatchers.put(GmObject.class, new ObjectMatcher()); fullTextMatchers.put(Timeline.class, new TimelineMatcher()); // ... } @Override ResNode makeMatchNode(ResNode node, Pattern content) { if (!fullTextMatchers.containsKey(node.type)) { return null; } return fullTextMatchers.makeMatchNode(node, content); } } static class ObjectMatcher implements NodeMatcher { @Override makeMatchNode(ResNode node, Pattern content) { ResNode rnode = null; int numMatchingEvents = 0; // for (Event event : ((GmObject)node.getResource).getEvents()) { // LineMatch[] lines = getMatchingLines(event.getCode(), content); // if (lines.length == 0) { // continue; // } // int ind = 0; // if (rnode == null) { // rnode = node.clone(); // } // MutableTreeNode evnode = buildEventNode(event); // for (LineMatch line : lines) { // evnode.insert(new MatchNode(line, ind++); // } // rnode.addChild(evnode, numMatchingEvents++); // } return rnode; } } class LineMatch { class Block { String content; boolean highlighted; Block(String content, boolean highlighted) { this.content = content; this.highlighted = highlighted; } } int lineNum; List matchedText; } class MatchNode extends DefaultMutableTreeNode { MatchNode(LineMatch match) { // Set icon to red arrow // Assemble rich text from line number and matched text; // concatenate match.matchedText, highlighting highlighted blocks } } private static final Pattern newline = Pattern.compile("\r\n|\r|\n"); LineMatch[] getMatchingLines(String code, Pattern content) { List res = new ArrayList<>(); Matcher m = content.matcher(code), nl = newline.matcher(code); int lineNum = 1, lineAt = 0, lastEnd = -1; LineMatch lastMatch = null; while (m.find()) { nl.region(lineAt, m.start()); int firstSkippedLineAt = lineAt; if (nl.find() { firstSkippedLineAt = nl.start(); lineAt = nl.end(); while (nl.find()) { ++lineNum; lineAt = nl.end(); } } if (lastMatch != null) { // We have to add the rest of the line to the old match, either way. // And if we're matching on the same line, we add that match, too. if (lineNum == lastMatch.lineNum) { lastMatch.matchedText.add(new LineMatch.Block(code.substring(lastEnd, m.start()), false); lastMatch.matchedText.add(new LineMatch.Block(code.substring(m.start(), m.end()), true); } else { lastMatch.matchedText.add( new LineMatch.Block(code.substring(lastEnd, firstSkippedLineAt), false); } } if (lastMatch == null || lineNum == lastMatch.lineNum) { lastMatch = new LineMatch(); lastMatch.lineNum = lineNum; if (m.start() > lineAt) { lastMatch.add(new Block(code.substring(lineAt, m.start()), false)); } lastMatch.add(new Block(code.substring(m.start(), m.end()), false)); res.add(lastMatch); } lastEnd = m.end(); } return res.toArray(new LineMatch[] {}); } void buildTree(DefaultMutableTreeNode node, DefaultMutableTreeNode newNode, NodeMatcher matcher, Pattern pattern) { int numChildren = node.getChildCount(); for (int i = 0; i < numChildren; ++i) { DefaultMutableTreeNode child = node.getChildAt(i); if (child instanceof ResNode) { ResNode resNode = (ResNode)child; if (child.status == ResNode.STATUS_GROUP) { ResNode copy = resNode.clone(); buildTree(resNode, copy, matcher); if (copy.getChildCount() > 0) { node.add(copy); } } else { ResNode add = matcher.makeMatchNode(node, pattern); if (add != null) { node.add(copy); } } } } }