/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.core.search.matching2; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A set of matches and possible matches, which need to be resolved. */ public abstract class MatchingNodeSet<E> implements IMatchingNodeSet<E>, Comparator<E> { /** * Map of matching ast nodes that don't need to be resolved to their * accuracy level. Each node is removed as it is reported. */ private Map<E, MatchLevel> matchingNodes = new HashMap<>(); // sourceRange -> node private HashtableOfLong<E> matchingNodesKeys = new HashtableOfLong<>(); /** * Set of possible matching ast nodes. They need to be resolved to determine * if they really match the search pattern. */ private Set<E> possibleMatchingNodes = new HashSet<>(); // sourceRange -> node private HashtableOfLong<E> possibleMatchingNodesKeys = new HashtableOfLong<>(); @Override public MatchLevel addMatch(E node, MatchLevel matchLevel) { switch (matchLevel) { case POSSIBLE_MATCH: addPossibleMatch(node); break; case INACCURATE_MATCH: addTrustedMatch(node, MatchLevel.INACCURATE_MATCH); break; case ACCURATE_MATCH: addTrustedMatch(node, MatchLevel.ACCURATE_MATCH); break; } return matchLevel; } public void addPossibleMatch(E node) { // remove existing node at same position from set // (case of recovery that created the same node several time // see http://bugs.eclipse.org/bugs/show_bug.cgi?id=29366) long key = computeNodeKey(node); E existing = this.possibleMatchingNodesKeys.get(key); if (existing != null && existing.getClass().equals(node.getClass())) this.possibleMatchingNodes.remove(existing); // add node to set this.possibleMatchingNodes.add(node); this.possibleMatchingNodesKeys.put(key, node); } private void addTrustedMatch(E node, MatchLevel level) { // remove existing node at same position from set // (case of recovery that created the same node several time // see http://bugs.eclipse.org/bugs/show_bug.cgi?id=29366) long key = computeNodeKey(node); E existing = this.matchingNodesKeys.get(key); if (existing != null && existing.getClass().equals(node.getClass())) this.matchingNodes.remove(existing); // map node to its accuracy level this.matchingNodes.put(node, level); this.matchingNodesKeys.put(key, node); } protected boolean hasPossibleNodes(int start, int end) { for (E node : possibleMatchingNodes) { if (checkRange(node, start, end)) { return true; } } for (E node : matchingNodes.keySet()) { if (checkRange(node, start, end)) { return true; } } return false; } /** * Returns the matching nodes that are in the given range in the source * order. */ public List<E> matchingNodes(int start, int end) { List<E> nodes = null; for (E node : matchingNodes.keySet()) { if (checkRange(node, start, end)) { if (nodes == null) nodes = new ArrayList<>(); nodes.add(node); } } if (nodes == null) return Collections.emptyList(); Collections.sort(nodes, this); return nodes; } /** * Returns all the matching nodes */ public List<E> matchingNodes() { final List<E> nodes = new ArrayList<>(matchingNodes.keySet()); Collections.sort(nodes, this); return nodes; } public boolean removePossibleMatch(E node) { long key = computeNodeKey(node); E existing = this.possibleMatchingNodesKeys.get(key); if (existing == null) return false; this.possibleMatchingNodesKeys.put(key, null); return this.possibleMatchingNodes.remove(node); } protected abstract long computeNodeKey(E node); protected abstract boolean checkRange(E node, int start, int end); public MatchLevel removeTrustedMatch(E node) { long key = computeNodeKey(node); E existing = this.matchingNodesKeys.get(key); if (existing == null) return null; this.matchingNodesKeys.put(key, null); return this.matchingNodes.remove(node); } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append("Exact matches:"); //$NON-NLS-1$ List<E> nodes = new ArrayList<>(matchingNodes.keySet()); Collections.sort(nodes, this); for (E node : nodes) { result.append("\n\t"); //$NON-NLS-1$ result.append(matchingNodes.get(node)); result.append(' '); result.append(describeNode(node)); } result.append("\nPossible matches:"); //$NON-NLS-1$ nodes.clear(); nodes.addAll(possibleMatchingNodes); Collections.sort(nodes, this); for (E node : nodes) { result.append("\nPOSSIBLE_MATCH: "); //$NON-NLS-1$ result.append(describeNode(node)); } return result.toString(); } protected String describeNode(E node) { return node.toString(); } public void clear() { matchingNodes.clear(); matchingNodesKeys.clear(); clearPossibleMatchingNodes(); // possibleMatchingNodes.clear(); // possibleMatchingNodesKeys.clear(); } public void clearPossibleMatchingNodes() { possibleMatchingNodes.clear(); possibleMatchingNodesKeys.clear(); } public Collection<E> getPossibleMatchingNodes() { return possibleMatchingNodes; } public int countMatchingNodes() { return matchingNodes.size(); } public int countPossibleMatchingNodes() { return possibleMatchingNodes.size(); } /** * @return */ public boolean isEmpty() { return matchingNodes.isEmpty() && possibleMatchingNodes.isEmpty(); } }