blob: 357d9e896c004cb0148d69af110846dab2d8b5c0 [file] [log] [blame]
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.collect.Collections2;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Ilya.Kazakevich
*/
class InstanceFieldsManager extends FieldsManager {
private static final FieldsOnly FIELDS_ONLY = new FieldsOnly();
// PY-12170
InstanceFieldsManager() {
super(false);
}
@Override
public boolean hasConflict(@NotNull final PyTargetExpression member, @NotNull final PyClass aClass) {
return NamePredicate.hasElementWithSameName(member, aClass.getInstanceAttributes());
}
@Override
protected Collection<PyElement> moveAssignments(@NotNull final PyClass from,
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
//TODO: Copy/paste with ClassFieldsManager. Move to parent?
final List<PyElement> result = new ArrayList<PyElement>();
for (final PyClass destClass : to) {
result.addAll(copyInstanceFields(statements, destClass));
}
// Delete only declarations made in __init__ to prevent PY-12170
final PyFunction fromInitMethod = PyUtil.getInitMethod(from);
if (fromInitMethod != null) { // If class has no init method that means all its fields declared in other methods, so nothing to remove
deleteElements(Collections2.filter(statements, new InitsOnly(fromInitMethod)));
//We can't leave class constructor with empty body
PyClassRefactoringUtil.insertPassIfNeeded(fromInitMethod);
}
return result;
}
/**
* Copies class' fields in form of assignments (instance fields) to another class.
* Creates init method if there is no any
*
* @param members assignments to copy
* @param to destination
* @return newly created fields
*/
@NotNull
private static List<PyAssignmentStatement> copyInstanceFields(@NotNull final Collection<PyAssignmentStatement> members,
@NotNull final PyClass to) {
//We need __init__ method, and if there is no any -- we need to create it
PyFunction toInitMethod = PyUtil.getInitMethod(to);
if (toInitMethod == null) {
toInitMethod = createInitMethod(to);
}
final PyStatementList statementList = toInitMethod.getStatementList();
return PyClassRefactoringUtil.copyFieldDeclarationToStatement(members, statementList, null);
}
/**
* Creates init method and adds it to certain class.
*
* @param to Class where method should be added
* @return newly created method
*/
//TODO: Move to utils?
@NotNull
private static PyFunction createInitMethod(@NotNull final PyClass to) {
final PyFunctionBuilder functionBuilder = new PyFunctionBuilder(PyNames.INIT);
functionBuilder.parameter(PyNames.CANONICAL_SELF); //TODO: Take param from codestyle?
final PyFunction function = functionBuilder.buildFunction(to.getProject(), LanguageLevel.forElement(to));
return PyClassRefactoringUtil.addMethods(to, true, function).get(0);
}
@Override
protected boolean classHasField(@NotNull final PyClass pyClass, @NotNull final String fieldName) {
return pyClass.findInstanceAttribute(fieldName, true) != null;
}
@NotNull
@Override
protected Collection<PyTargetExpression> getFieldsByClass(@NotNull final PyClass pyClass) {
return Collections2.filter(pyClass.getInstanceAttributes(), FIELDS_ONLY);
}
private static class InitsOnly extends NotNullPredicate<PyAssignmentStatement> {
@NotNull
private final PyFunction myInitMethod;
private InitsOnly(@NotNull final PyFunction initMethod) {
myInitMethod = initMethod;
}
@Override
protected boolean applyNotNull(@NotNull final PyAssignmentStatement input) {
final PyExpression expression = input.getLeftHandSideExpression();
if (expression == null) {
return false;
}
final PyFunction functionWhereDeclared = PsiTreeUtil.getParentOfType(PyUtil.resolveToTheTop(expression), PyFunction.class);
return myInitMethod.equals(functionWhereDeclared);
}
}
private static class FieldsOnly extends NotNullPredicate<PyTargetExpression> {
@Override
protected boolean applyNotNull(@NotNull final PyTargetExpression input) {
return input.getReference().resolve() instanceof PyTargetExpression;
}
}
}