/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Dec 10, 2006 * @author Fabio */ package com.python.pydev.refactoring.refactorer.refactorings.rename; import java.io.File; 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.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jface.text.Document; import org.eclipse.text.edits.TextEdit; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.IModule; import org.python.pydev.core.IProjectModulesManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.ModulesKey; import org.python.pydev.core.TestDependent; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.editor.codecompletion.revisited.ASTManager; import org.python.pydev.editor.codecompletion.revisited.ProjectStub; import org.python.pydev.editor.codecompletion.revisited.modules.ASTEntryWithSourceModule; import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule; import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule; import org.python.pydev.editor.refactoring.RefactoringRequest; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.io.FileUtils; import org.python.pydev.shared_core.resource_stubs.FileStub; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.ui.pythonpathconf.InterpreterInfo; import org.python.pydev.utils.PyFileListing; import org.python.pydev.utils.PyFileListing.PyFileInfo; import com.python.pydev.analysis.additionalinfo.AbstractAdditionalTokensInfo; import com.python.pydev.analysis.additionalinfo.AdditionalProjectInterpreterInfo; import com.python.pydev.analysis.scopeanalysis.AstEntryScopeAnalysisConstants; import com.python.pydev.refactoring.refactorer.refactorings.renamelocal.RefactoringLocalTestBase; import com.python.pydev.refactoring.wizards.IRefactorRenameProcess; import com.python.pydev.refactoring.wizards.rename.PyRenameEntryPoint; import com.python.pydev.refactoring.wizards.rename.TextEditCreation; /** * A class used for the refactorings that need the rename project (in pysrcrefactoring) * * @author Fabio */ public abstract class RefactoringRenameTestBase extends RefactoringLocalTestBase { /** * We want to keep it initialized among runs from the same class. * Check the restorePythonPath function. */ public static PythonNature natureRefactoring; /** * This is the last rename processor used (so, we may query it about other things) */ protected PyRenameEntryPoint lastProcessorUsed; /** * Backwards-compatibility interface */ protected boolean restoreProjectPythonPathRefactoring(boolean force, String path) { return restoreProjectPythonPathRefactoring(force, path, "testProjectStubRefactoring"); } /** * The list of python files contained in the refactoring pysrc project */ protected static Collection<PyFileInfo> filesInRefactoringProject; public static final String CURRENT_MODULE_IN_REFERENCES = "__current_module__"; protected static boolean DEBUG_REFERENCES = false; /** * In the setUp, it initializes the files in the refactoring project * @see com.python.pydev.refactoring.refactorer.refactorings.renamelocal.RefactoringLocalTestBase#setUp() */ @Override public void setUp() throws Exception { super.setUp(); if (filesInRefactoringProject == null) { filesInRefactoringProject = PyFileListing.getPyFilesBelow( new File(TestDependent.TEST_COM_REFACTORING_PYSRC_LOC), new NullProgressMonitor(), true, false) .getFoundPyFileInfos(); ArrayList<Tuple<List<ModulesKey>, IPythonNature>> iFiles = new ArrayList<Tuple<List<ModulesKey>, IPythonNature>>(); List<ModulesKey> modules = new ArrayList<ModulesKey>(); iFiles.add(new Tuple<List<ModulesKey>, IPythonNature>(modules, natureRefactoring)); FastStringBuffer tempBuf = new FastStringBuffer(); for (PyFileInfo info : filesInRefactoringProject) { File f = info.getFile(); String modName = info.getModuleName(tempBuf); ModulesKey modulesKey = new ModulesKey(modName, f); modules.add(modulesKey); SourceModule mod = (SourceModule) AbstractModule.createModule(modName, f, natureRefactoring, true); //also create the additional info so that it can be used for finds AbstractAdditionalTokensInfo additionalInfo = AdditionalProjectInterpreterInfo .getAdditionalInfoForProject(natureRefactoring); additionalInfo.addAstInfo(mod.getAst(), modulesKey, false); } // RefactorerFindReferences.FORCED_RETURN = iFiles; } setUpConfigWorkspaceFiles(); } private org.python.pydev.shared_core.resource_stubs.ProjectStub projectStub; public void setUpConfigWorkspaceFiles() throws Exception { projectStub = new org.python.pydev.shared_core.resource_stubs.ProjectStub( new File(TestDependent.TEST_COM_REFACTORING_PYSRC_LOC), natureRefactoring); TextEditCreation.createWorkspaceFile = new ICallback<IFile, File>() { @Override public IFile call(File file) { return new FileStub(projectStub, file) { @Override public IPath getFullPath() { return Path.fromOSString(this.file.getAbsolutePath()); } }; } }; } @Override public void tearDown() throws Exception { super.tearDown(); } /** * Checks if the refactor processes are all of the type we're testing here. */ protected void checkProcessors() { if (lastProcessorUsed != null) { List<IRefactorRenameProcess> processes = lastProcessorUsed.getAllProcesses(); assertEquals(1, processes.size()); Class processUnderTest = getProcessUnderTest(); if (processUnderTest != null) { for (IRefactorRenameProcess p : processes) { assertTrue(StringUtils.format("Expected %s. Received:%s", processUnderTest, p.getClass()), processUnderTest.isInstance(p)); //we should only activate the rename class process in this test case } } } } /** * @return the process class that we're testing. */ protected abstract Class getProcessUnderTest(); /** * A method that creates a project that references no other project * * @param force whether the creation of the new nature should be forced * @param path the pythonpath for the new nature * @param name the name for the project * @return true if the creation was needed and false if it wasn't */ protected boolean restoreProjectPythonPathRefactoring(boolean force, String path, String name) { PythonNature n = checkNewNature(name, force); if (n != null) { natureRefactoring = n; ProjectStub projectFromNatureRefactoring = new ProjectStub(name, path, new IProject[0], new IProject[0]); setAstManager(path, projectFromNatureRefactoring, natureRefactoring); return true; } return false; } /** * Overriden so that the pythonpath is only restored for the system and the refactoring nature * * @param force whether this should be forced, even if it was previously created for this class */ @Override public void restorePythonPath(boolean force) { if (DEBUG_TESTS_BASE) { System.out.println("-------------- Restoring system pythonpath"); } restoreSystemPythonPath(force, TestDependent.PYTHON_LIB); if (DEBUG_TESTS_BASE) { System.out.println("-------------- Restoring project pythonpath for refactoring nature"); } restoreProjectPythonPathRefactoring(force, TestDependent.TEST_COM_REFACTORING_PYSRC_LOC); if (DEBUG_TESTS_BASE) { System.out.println("-------------- Checking size (for projrefactoring)"); } checkSize(); } /** * checks if the size of the system modules manager and the project moule manager are coherent * (we must have more modules in the system than in the project) */ @Override protected void checkSize() { try { IInterpreterManager iMan = getInterpreterManager(); InterpreterInfo info = (InterpreterInfo) iMan.getDefaultInterpreterInfo(false); assertTrue(info.getModulesManager().getSize(true) > 0); int size = ((ASTManager) natureRefactoring.getAstManager()).getSize(); assertTrue("Interpreter size:" + info.getModulesManager().getSize(true) + " should be smaller than project size:" + size + " " + "(because it contains system+project info)", info.getModulesManager().getSize(true) < size); } catch (MisconfigurationException e) { throw new RuntimeException(e); } } /** * Gets the references for the rename without expecting any error. * @param line: starts at 0 * @param col: starts at 0 */ protected Map<Tuple<String, File>, HashSet<ASTEntry>> getReferencesForRenameSimple(String moduleName, int line, int col) { Map<Tuple<String, File>, HashSet<ASTEntry>> referencesForRename = getReferencesForRenameSimple(moduleName, line, col, false); if (DEBUG_REFERENCES) { for (Map.Entry<Tuple<String, File>, HashSet<ASTEntry>> entry : referencesForRename.entrySet()) { System.out.println(entry.getKey()); for (ASTEntry e : entry.getValue()) { System.out.println(e); } } } return referencesForRename; } /** * Same as {@link #getReferencesForRename(String, int, int, boolean)} but returning * the key for the map as a string with the module name. */ protected Map<Tuple<String, File>, HashSet<ASTEntry>> getReferencesForRenameSimple(String moduleName, int line, int col, boolean expectError) { Map<String, HashSet<ASTEntry>> occurrencesToReturn = new HashMap<String, HashSet<ASTEntry>>(); Map<Tuple<String, File>, HashSet<ASTEntry>> referencesForRename = getReferencesForRename(moduleName, line, col, expectError); for (Map.Entry<Tuple<String, File>, HashSet<ASTEntry>> entry : referencesForRename.entrySet()) { if (occurrencesToReturn.get(entry.getKey()) != null) { throw new RuntimeException("Error. Module: " + entry.getKey() + " already exists."); } occurrencesToReturn.put(entry.getKey().o1, entry.getValue()); } return referencesForRename; } /** * Goes through all the workspace (in this case the refactoring project) and gathers the references * for the current selection. * * @param moduleName the name of the module we're currently in * @param line the line we're in * @param col the col we're in * @param expectError whether we are expecting some error or not * @return a map with the name of the module and the file representing it pointing to the * references found in that module. */ protected Map<Tuple<String, File>, HashSet<ASTEntry>> getReferencesForRename(String moduleName, int line, int col, boolean expectError) { Map<Tuple<String, File>, HashSet<ASTEntry>> occurrencesToReturn = null; try { IProjectModulesManager modulesManager = (IProjectModulesManager) natureRefactoring.getAstManager() .getModulesManager(); IModule module = modulesManager.getModuleInDirectManager(moduleName, natureRefactoring, true); if (module == null) { throw new RuntimeException("Unable to get source module for module:" + moduleName); } String strDoc = FileUtils.getFileContents(module.getFile()); Document doc = new Document(strDoc); PySelection ps = new PySelection(doc, line, col); RefactoringRequest request = new RefactoringRequest(null, ps, natureRefactoring); request.setAdditionalInfo(RefactoringRequest.FIND_REFERENCES_ONLY_IN_LOCAL_SCOPE, false); request.moduleName = moduleName; request.inputName = "new_name"; request.fillInitialNameAndOffset(); PyRenameEntryPoint processor = new PyRenameEntryPoint(request); NullProgressMonitor nullProgressMonitor = new NullProgressMonitor(); checkStatus(processor.checkInitialConditions(nullProgressMonitor), expectError); lastProcessorUsed = processor; checkProcessors(); checkStatus(processor.checkFinalConditions(nullProgressMonitor, null), expectError); occurrencesToReturn = processor.getOccurrencesInOtherFiles(); occurrencesToReturn.put(new Tuple<String, File>(moduleName, module.getFile()), processor.getOccurrences()); } catch (Exception e) { throw new RuntimeException(e); } return occurrencesToReturn; } /** * Used to see if some line/col is available in a list of entries. */ protected void assertContains(int line, int col, HashSet<ASTEntry> names) { for (ASTEntry name : names) { SimpleNode node = name.node; if (node instanceof ClassDef) { node = ((ClassDef) node).name; } if (node instanceof FunctionDef) { node = ((FunctionDef) node).name; } if (node.beginLine == line && node.beginColumn == col) { return; } } fail(StringUtils.format("Unable to find line:%s col:%s in %s", line, col, names)); } @SuppressWarnings("unchecked") protected String asStr(Map<Tuple<String, File>, HashSet<ASTEntry>> referencesForModuleRename) throws Exception { Set<Entry<Tuple<String, File>, HashSet<ASTEntry>>> entrySet = referencesForModuleRename.entrySet(); FastStringBuffer buf = new FastStringBuffer(); ArrayList<Entry<Tuple<String, File>, HashSet<ASTEntry>>> lst = new ArrayList<>(entrySet); Comparator<Entry<Tuple<String, File>, HashSet<ASTEntry>>> c = new Comparator<Entry<Tuple<String, File>, HashSet<ASTEntry>>>() { @Override public int compare(Entry<Tuple<String, File>, HashSet<ASTEntry>> o1, Entry<Tuple<String, File>, HashSet<ASTEntry>> o2) { return o1.getKey().o1.compareTo(o2.getKey().o1); } }; Collections.sort(lst, c); for (Entry<Tuple<String, File>, HashSet<ASTEntry>> entry : lst) { HashSet<ASTEntry> value = entry.getValue(); if (value.size() > 0) { ArrayList<ASTEntry> lst2 = new ArrayList<>(value); Comparator<ASTEntry> c2 = new Comparator<ASTEntry>() { @Override public int compare(ASTEntry o1, ASTEntry o2) { return o1.toString().compareTo(o2.toString()); } }; Collections.sort(lst2, c2); File f = entry.getKey().o2; String fileContents = FileUtils.getFileContents(f); Document initialDoc = new Document(fileContents); buf.append(entry.getKey().o1).append("\n"); for (ASTEntry e : lst2) { buf.append(" "); buf.append(e.toString()).append("\n"); List<TextEdit> edits = (List<TextEdit>) e.getAdditionalInfo( AstEntryScopeAnalysisConstants.AST_ENTRY_REPLACE_EDIT, null); if (edits == null) { if (!(e instanceof ASTEntryWithSourceModule)) { throw new AssertionError("Only ASTEntryWithSourceModule can have null edits. Found: " + e); } } else { Document changedDoc = new Document(fileContents); for (TextEdit textEdit : edits) { textEdit.apply(changedDoc); } List<String> changedLines = getChangedLines(initialDoc, changedDoc); for (String i : changedLines) { buf.append(" "); buf.append(StringUtils.rightTrim(i)).append("\n"); } } } buf.append("\n"); } } return buf.toString(); } private List<String> getChangedLines(Document initialDoc, Document changedDoc) { int numberOfLines = initialDoc.getNumberOfLines(); int numberOfLines2 = changedDoc.getNumberOfLines(); List<String> ret = new ArrayList<>(); if (numberOfLines != numberOfLines2) { ret.add("Initial:\n" + StringUtils.replaceNewLines(initialDoc.get(), "\n")); ret.add("Final:\n" + StringUtils.replaceNewLines(changedDoc.get(), "\n")); } else { for (int i = 0; i < numberOfLines; i++) { String l1 = PySelection.getLine(initialDoc, i); String l2 = PySelection.getLine(changedDoc, i); if (!l1.equals(l2)) { ret.add(StringUtils.format("Line: %s %s --> %s", i, l1, l2)); } } } return ret; } }