blob: 52657c67d9a81eba5aa88ab7169f2975b6b72167 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.google.common.base.Predicate;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.*;
import com.intellij.util.PathUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* @author Dennis.Ushakov
*/
public final class PyExtractSuperclassHelper {
private static final Logger LOG = Logger.getInstance(PyExtractSuperclassHelper.class.getName());
/**
* Accepts only those members whose element is PyClass object (new classes)
*/
private static final Predicate<PyMemberInfo<PyElement>> ALLOW_OBJECT = new PyUtil.ObjectPredicate(true);
private PyExtractSuperclassHelper() {
}
static void extractSuperclass(final PyClass clazz,
@NotNull Collection<PyMemberInfo<PyElement>> selectedMemberInfos,
final String superBaseName,
final String targetFile) {
//We will need to change it probably while param may be read-only
//noinspection AssignmentToMethodParameter
selectedMemberInfos = new ArrayList<PyMemberInfo<PyElement>>(selectedMemberInfos);
// PY-12171
final PyMemberInfo<PyElement> objectMember = MembersManager.findMember(selectedMemberInfos, ALLOW_OBJECT);
if (LanguageLevel.forElement(clazz).isPy3K()) {
// Remove object from list if Py3
if (objectMember != null) {
selectedMemberInfos.remove(objectMember);
}
} else {
// Always add object if < Py3
if (objectMember == null) {
final PyMemberInfo<PyElement> object = MembersManager.findMember(clazz, ALLOW_OBJECT);
if (object != null) {
selectedMemberInfos.add(object);
}
}
}
final Project project = clazz.getProject();
final String text = "class " + superBaseName + ":\n pass" + "\n";
PyClass newClass = PyElementGenerator.getInstance(project).createFromText(LanguageLevel.getDefault(), PyClass.class, text);
newClass = placeNewClass(project, newClass, clazz, targetFile);
MembersManager.moveAllMembers(selectedMemberInfos, clazz, newClass);
if (! newClass.getContainingFile().equals(clazz.getContainingFile())) {
PyClassRefactoringUtil.optimizeImports(clazz.getContainingFile()); // To remove unneeded imports only if user used different file
}
PyClassRefactoringUtil.addSuperclasses(project, clazz, null, newClass);
}
private static PyClass placeNewClass(final Project project, PyClass newClass, @NotNull final PyClass clazz, final String targetFile) {
VirtualFile file = VirtualFileManager.getInstance()
.findFileByUrl(ApplicationManagerEx.getApplicationEx().isUnitTestMode() ? targetFile : VfsUtilCore.pathToUrl(targetFile));
// file is the same as the source
if (Comparing.equal(file, clazz.getContainingFile().getVirtualFile())) {
return (PyClass)clazz.getParent().addBefore(newClass, clazz);
}
PsiFile psiFile = null;
try {
if (file == null) {
// file does not exist
final String filename;
final String path;
if (targetFile.endsWith(PythonFileType.INSTANCE.getDefaultExtension())) {
path = PathUtil.getParentPath(targetFile);
filename = PathUtil.getFileName(targetFile);
}
else {
path = targetFile;
filename = PyNames.INIT_DOT_PY; // user requested putting the class into this package directly
}
psiFile = placeFile(project, path, filename);
}
else if (file.isDirectory()) { // existing directory
psiFile = placeFile(project, file.getPath(), PyNames.INIT_DOT_PY);
}
else { // existing file
psiFile = PsiManager.getInstance(project).findFile(file);
}
}
catch (IOException e) {
LOG.error(e);
}
LOG.assertTrue(psiFile != null);
if (psiFile.getLastChild() != null) {
// TODO: make the number of newlines depend on style setting
psiFile.add(PyElementGenerator.getInstance(project).createFromText(LanguageLevel.PYTHON24, PsiWhiteSpace.class, "\n\n"));
}
newClass = (PyClass)psiFile.add(newClass);
PyClassRefactoringUtil.insertImport(clazz, Collections.singleton((PsiNamedElement)newClass));
return newClass;
}
/**
* Places a file at the end of given path, creating intermediate dirs and inits.
*
* @param project
* @param path
* @param filename
* @return the placed file
* @throws IOException
*/
public static PsiFile placeFile(Project project, String path, String filename) throws IOException {
return placeFile(project, path, filename, null);
}
//TODO: Mover to the other class? That is not good to dependent PyUtils on this class
public static PsiFile placeFile(Project project, String path, String filename, @Nullable String content) throws IOException {
PsiDirectory psiDir = createDirectories(project, path);
LOG.assertTrue(psiDir != null);
PsiFile psiFile = psiDir.findFile(filename);
if (psiFile == null) {
psiFile = psiDir.createFile(filename);
if (content != null) {
final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
final Document document = manager.getDocument(psiFile);
if (document != null) {
document.setText(content);
manager.commitDocument(document);
}
}
}
return psiFile;
}
/**
* Create all intermediate dirs with inits from one of roots up to target dir.
*
* @param project
* @param target a full path to target dir
* @return deepest child directory, or null if target is not in roots or process fails at some point.
*/
@Nullable
private static PsiDirectory createDirectories(Project project, String target) throws IOException {
String the_rest = null;
VirtualFile the_root = null;
PsiDirectory ret = null;
// NOTE: we don't canonicalize target; must be ok in reasonable cases, and is far easier in unit test mode
target = FileUtil.toSystemIndependentName(target);
for (VirtualFile file : ProjectRootManager.getInstance(project).getContentRoots()) {
final String root_path = file.getPath();
if (target.startsWith(root_path)) {
the_rest = target.substring(root_path.length());
the_root = file;
break;
}
}
if (the_root == null) {
throw new IOException("Can't find '" + target + "' among roots");
}
if (the_rest != null) {
final LocalFileSystem lfs = LocalFileSystem.getInstance();
final PsiManager psi_mgr = PsiManager.getInstance(project);
String[] dirs = the_rest.split("/");
int i = 0;
if ("".equals(dirs[0])) i = 1;
while (i < dirs.length) {
VirtualFile subdir = the_root.findChild(dirs[i]);
if (subdir != null) {
if (!subdir.isDirectory()) {
throw new IOException("Expected dir, but got non-dir: " + subdir.getPath());
}
}
else {
subdir = the_root.createChildDirectory(lfs, dirs[i]);
}
VirtualFile init_vfile = subdir.findChild(PyNames.INIT_DOT_PY);
if (init_vfile == null) init_vfile = subdir.createChildData(lfs, PyNames.INIT_DOT_PY);
/*
// here we could add an __all__ clause to the __init__.py.
// * there's no point to do so; we import the class directly;
// * we can't do this consistently since __init__.py may already exist and be nontrivial.
if (i == dirs.length - 1) {
PsiFile init_file = psi_mgr.findFile(init_vfile);
LOG.assertTrue(init_file != null);
final PyElementGenerator gen = PyElementGenerator.getInstance(project);
final PyStatement statement = gen.createFromText(LanguageLevel.getDefault(), PyStatement.class, PyNames.ALL + " = [\"" + lastName + "\"]");
init_file.add(statement);
}
*/
the_root = subdir;
i += 1;
}
ret = psi_mgr.findDirectory(the_root);
}
return ret;
}
}