/******************************************************************************* * Copyright (c) 2014 Google, Inc 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 * * Contributors: * Danny Yoo (Google) - refactored parts of PyRenameEntryPoint into here. *******************************************************************************/ package com.python.pydev.refactoring.wizards.rename; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.python.pydev.core.IModule; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import org.python.pydev.editor.model.ItemPointer; import org.python.pydev.editor.refactoring.AbstractPyRefactoring; import org.python.pydev.editor.refactoring.IPyRefactoring; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.editor.refactoring.TooManyMatchesException; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.shared_core.structure.Location; import org.python.pydev.shared_core.structure.Tuple; import com.python.pydev.refactoring.wizards.IRefactorRenameProcess; import com.python.pydev.refactoring.wizards.RefactorProcessFactory; /** * Searches references to identifiers for operations such as {@code MarkOccurrences} and * {@link PyRenameEntryPoint}. * * <p>Clients are expected to call {@link #prepareSearch} to collect the processes used to search * for a given request. Once this succeeds, clients call {@link search} to search for references. * Finally, clients use {@link getLocalReferences} and {@link getWorkspaceReferences} to query for * references. */ public class PyReferenceSearcher { // This code used to be centralized in PyRenameEntryPoint, but the same logic was used // in MarkOccurrences too. The code paths share very similar logic, so are collected here. private final Map<RefactoringRequest, List<IRefactorRenameProcess>> requestToProcesses = new HashMap<>(); private static final String INVALID_DEFINITION = "The definition found is not valid: "; /** * Reports exceptions during a search. */ @SuppressWarnings("serial") public static class SearchException extends Exception { /** * Constructs an exception to report problems during search. * * @param message describes details about the exception. */ public SearchException(String message) { super(message); } } /** * Constructs a searcher for the given requests. * * @param requests the search requests. */ public PyReferenceSearcher(RefactoringRequest... requests) { for (RefactoringRequest refactoringRequest : requests) { requestToProcesses.put(refactoringRequest, new ArrayList<IRefactorRenameProcess>()); } } /** * Prepares for an upcoming use of {@link #search(RefactoringRequest)}. This must be called * before a search is performed. * * @param request the search request. * @throws SearchException if the AST can not be found or the definition for the * identifier isn't valid or can't otherwise be searched. * @throws BadLocationException * @throws TooManyMatchesException */ public void prepareSearch(RefactoringRequest request) throws SearchException, TooManyMatchesException, BadLocationException { List<IRefactorRenameProcess> processes = requestToProcesses.get(request); processes.clear(); // Clear the existing processes for the request ItemPointer[] pointers; if (request.isModuleRenameRefactoringRequest()) { IModule module = request.getModule(); pointers = new ItemPointer[] { new ItemPointer(request.file, new Location(0, 0), new Location(0, 0), new Definition(1, 1, "", null, null, module, false), null) }; } else { SimpleNode ast = request.getAST(); if (ast == null) { throw new SearchException("AST not generated (syntax error)."); } IPyRefactoring pyRefactoring = AbstractPyRefactoring.getPyRefactoring(); request.communicateWork("Finding definition"); pointers = pyRefactoring.findDefinition(request); } if (pointers.length == 0) { // no definition found IRefactorRenameProcess p = RefactorProcessFactory.getRenameAnyProcess(); processes.add(p); } else { for (ItemPointer pointer : pointers) { if (pointer.definition == null) { throw new SearchException(INVALID_DEFINITION + pointer); } IRefactorRenameProcess p = RefactorProcessFactory.getProcess(pointer.definition, request); if (p == null) { throw new SearchException(INVALID_DEFINITION + pointer.definition); } processes.add(p); } } if (processes.isEmpty()) { throw new SearchException("The pre-conditions were not satisfied."); } } /** * Searches for references to the identifier in the request. * * <p>{@link #prepareSearch(RefactoringRequest)} must be called before * {@link #search(RefactoringRequest)} or else results are undefined. * * @param request the search request * @throws SearchException if an exception occurs in the process of finding references. * @throws OperationCanceledException if the request is canceled. */ public void search(RefactoringRequest request) throws SearchException, OperationCanceledException { for (IRefactorRenameProcess p : requestToProcesses.get(request)) { request.checkCancelled(); p.clear(); // Clear references found from a previous invocation RefactoringStatus status = new RefactoringStatus(); request.pushMonitor(new SubProgressMonitor(request.getMonitor(), 1)); try { p.findReferencesToRename(request, status); } finally { request.popMonitor().done(); } if (status.hasFatalError()) { throw new SearchException(status.getEntryWithHighestSeverity().getMessage()); } } } /** * Returns the individual search processes used for a request. * * <p>{@link #prepareSearch(RefactoringRequest)} or {@link #search(RefactoringRequest)} must be * called before {@link #getProcesses(RefactoringRequest)}, or else results are undefined. * * @param request the search request * @return the list of processes that are used for the request. */ public List<IRefactorRenameProcess> getProcesses(RefactoringRequest request) { return requestToProcesses.get(request); } /** * Returns the set of references found locally. * * <p>{@link #prepareSearch(RefactoringRequest)} and {@link #search(RefactoringRequest)} must be * called before {@link #getProcesses(RefactoringRequest)}, or else results are undefined. * * @param request the search request * @return the set of references that are found in the current document. * Does not get the references from other files */ public HashSet<ASTEntry> getLocalReferences(RefactoringRequest request) { HashSet<ASTEntry> allReferences = new HashSet<>(); for (IRefactorRenameProcess p : requestToProcesses.get(request)) { HashSet<ASTEntry> references = p.getOccurrences(); if (references != null) { allReferences.addAll(references); } } return allReferences; } /** * Returns the set of references found in the workspace. * * <p>{@link #prepareSearch(RefactoringRequest)} and {@link #search(RefactoringRequest)} must be * called before {@link #getProcesses(RefactoringRequest)}, or else results are undefined. * * @param request the search request * @return a map that points the references found in other files, but excludes those found locally. */ public Map<Tuple<String, File>, HashSet<ASTEntry>> getWorkspaceReferences( RefactoringRequest request) { HashMap<Tuple<String, File>, HashSet<ASTEntry>> allReferences = new HashMap<>(); for (IRefactorRenameProcess p : requestToProcesses.get(request)) { Map<Tuple<String, File>, HashSet<ASTEntry>> references = p.getOccurrencesInOtherFiles(); if (references != null) { for (Map.Entry<Tuple<String, File>, HashSet<ASTEntry>> reference : references.entrySet()) { Tuple<String, File> key = reference.getKey(); HashSet<ASTEntry> existingReferences = allReferences.get(key); if (existingReferences == null) { existingReferences = new HashSet<>(); allReferences.put(key, existingReferences); } existingReferences.addAll(reference.getValue()); } } } return allReferences; } }