package org.jetbrains.research.intellijdeodorant.ide.refactoring.typeStateChecking; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiUtil; import org.jetbrains.research.intellijdeodorant.core.ast.util.ExpressionExtractor; import org.jetbrains.research.intellijdeodorant.core.ast.util.MethodDeclarationUtility; import javax.swing.tree.DefaultMutableTreeNode; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Stream; public abstract class PolymorphismRefactoring { protected final PsiFile sourceFile; protected final PsiClass sourceTypeDeclaration; protected final Project project; protected final TypeCheckElimination typeCheckElimination; protected final PsiElementFactory elementFactory; protected final CodeStyleManager codeStyleManager; final protected PsiJavaToken semicolon; public PolymorphismRefactoring(PsiFile sourceFile, Project project, PsiClass sourceTypeDeclaration, TypeCheckElimination typeCheckElimination) { this.sourceFile = sourceFile; this.sourceTypeDeclaration = sourceTypeDeclaration; this.typeCheckElimination = typeCheckElimination; elementFactory = PsiElementFactory.getInstance(project); codeStyleManager = CodeStyleManager.getInstance(project); this.project = project; semicolon = (PsiJavaToken) elementFactory.createStatementFromText(";", null).getFirstChild(); } public abstract void apply(); protected void modifySourceMethodInvocationsInSubclass(List<PsiExpression> oldMethodInvocations, List<PsiExpression> newMethodInvocations, Set<PsiMethod> accessedMethods, Set<PsiMethod> superAccessedMethods) { int j = 0; for (PsiExpression expression : newMethodInvocations) { if (expression instanceof PsiMethodCallExpression) { PsiMethodCallExpression newMethodInvocation = (PsiMethodCallExpression) expression; PsiMethodCallExpression oldMethodInvocation = (PsiMethodCallExpression) oldMethodInvocations.get(j); for (PsiMethod methodDeclaration : accessedMethods) { if (methodDeclaration.equals(oldMethodInvocation.resolveMethod())) { addQualifierToSourceTypeMethodCalls(newMethodInvocation, methodDeclaration); break; } } for (PsiMethod superMethodBinding : superAccessedMethods) { if (superMethodBinding.equals(oldMethodInvocation.resolveMethod())) { PsiExpression qualifierExpression = oldMethodInvocation.getMethodExpression().getQualifierExpression(); if (qualifierExpression == null || qualifierExpression instanceof PsiThisExpression) { addQualifierToSourceTypeMethodCalls(newMethodInvocation, superMethodBinding); break; } } } } j++; } } private void addQualifierToSourceTypeMethodCalls(PsiMethodCallExpression newMethodInvocation, PsiMethod methodDeclaration) { String invokerName = sourceTypeDeclaration.getName(); if (!methodDeclaration.getModifierList().hasModifierProperty(PsiModifier.STATIC) && invokerName != null) { invokerName = invokerName.substring(0, 1).toLowerCase() + invokerName.substring(1); newMethodInvocation.getMethodExpression().setQualifierExpression(elementFactory.createExpressionFromText(invokerName, null)); } } protected void replaceThisExpressionWithContextParameterInMethodInvocationArguments(List<PsiExpression> oldMethodInvocations, List<PsiExpression> newMethodInvocations) { for (int expressionIndex = 0; expressionIndex < oldMethodInvocations.size(); expressionIndex++) { PsiExpression newExpression = newMethodInvocations.get(expressionIndex); PsiExpression oldExpression = oldMethodInvocations.get(expressionIndex); if (newExpression instanceof PsiMethodCallExpression) { PsiMethodCallExpression newMethodInvocation = (PsiMethodCallExpression) newExpression; PsiMethodCallExpression oldMethodInvocation = (PsiMethodCallExpression) oldExpression; PsiExpression[] newArguments = newMethodInvocation.getArgumentList().getExpressions(); PsiExpression[] oldArguments = oldMethodInvocation.getArgumentList().getExpressions(); for (int argumentIndex = 0; argumentIndex < oldArguments.length; argumentIndex++) { PsiExpression oldArgument = oldArguments[argumentIndex]; PsiExpression newArgument = newArguments[argumentIndex]; if (oldArgument instanceof PsiThisExpression) { String parameterName = sourceTypeDeclaration.getName(); parameterName = parameterName.substring(0, 1).toLowerCase() + parameterName.substring(1); newArgument.replace(elementFactory.createExpressionFromText(parameterName, null)); } } } } } protected void replaceThisExpressionWithContextParameterInClassInstanceCreationArguments(PsiStatement newStatement) { ExpressionExtractor expressionExtractor = new ExpressionExtractor(); List<PsiExpression> classInstanceCreations = expressionExtractor.getClassInstanceCreations(newStatement); for (PsiExpression creation : classInstanceCreations) { PsiNewExpression classInstanceCreation = (PsiNewExpression) creation; PsiExpressionList argumentList = classInstanceCreation.getArgumentList(); PsiExpression[] arguments = argumentList != null ? argumentList.getExpressions() : PsiExpression.EMPTY_ARRAY; for (PsiExpression argument : arguments) { if (argument instanceof PsiThisExpression) { String parameterName = sourceTypeDeclaration.getName(); parameterName = parameterName.substring(0, 1).toLowerCase() + parameterName.substring(1); argument.replace(elementFactory.createExpressionFromText( parameterName, null )); } } } } protected void modifySourceVariableInstructionsInSubclass(List<PsiExpression> oldVariableInstructions, List<PsiExpression> newVariableInstructions, Set<PsiField> accessedFields, Set<PsiField> assignedFields, Set<PsiField> superAccessedFields, Set<PsiField> superAssignedFields) { Set<PsiField> accessedFieldBindings = new LinkedHashSet<>(); accessedFieldBindings.addAll(accessedFields); accessedFieldBindings.addAll(superAccessedFields); Set<PsiField> assignedFieldBindings = new LinkedHashSet<>(); assignedFieldBindings.addAll(assignedFields); assignedFieldBindings.addAll(superAssignedFields); for (int i = 0; i < newVariableInstructions.size(); i++) { PsiReferenceExpression newSimpleName = (PsiReferenceExpression) newVariableInstructions.get(i); PsiReferenceExpression oldSimpleName = (PsiReferenceExpression) oldVariableInstructions.get(i); if (newSimpleName.getParent() instanceof PsiAssignmentExpression) { PsiAssignmentExpression newAssignment = (PsiAssignmentExpression) newSimpleName.getParent(); PsiAssignmentExpression oldAssignment = (PsiAssignmentExpression) oldSimpleName.getParent(); PsiExpression newLeftHandSide = newAssignment.getLExpression(); PsiExpression oldLeftHandSide = oldAssignment.getLExpression(); PsiReferenceExpression newLeftHandSideName = null; PsiReferenceExpression oldLeftHandSideName = null; if (newLeftHandSide instanceof PsiReferenceExpression) { newLeftHandSideName = (PsiReferenceExpression) newLeftHandSide; oldLeftHandSideName = (PsiReferenceExpression) oldLeftHandSide; } PsiExpression newRightHandSide = newAssignment.getRExpression(); PsiExpression oldRightHandSide = oldAssignment.getRExpression(); PsiReferenceExpression newRightHandSideName = null; PsiReferenceExpression oldRightHandSideName = null; if (newRightHandSide instanceof PsiReferenceExpression) { newRightHandSideName = (PsiReferenceExpression) newRightHandSide; oldRightHandSideName = (PsiReferenceExpression) oldRightHandSide; } String invokerName = sourceTypeDeclaration.getName(); invokerName = invokerName.substring(0, 1).toLowerCase() + invokerName.substring(1); if (newLeftHandSideName != null && newLeftHandSideName.equals(newSimpleName)) { for (PsiField assignedFieldBinding : assignedFieldBindings) { if (assignedFieldBinding.equals(oldLeftHandSideName.resolve())) { PsiMethodCallExpression setterMethodInvocation = generateSetterInvocation(superAssignedFields, invokerName, assignedFieldBinding); PsiBinaryExpression infixArgument = null; if (!newAssignment.getOperationTokenType().equals(JavaTokenType.EQ)) { String getterMethodName = getGetterName(superAccessedFields, assignedFieldBinding); infixArgument = (PsiBinaryExpression) elementFactory.createExpressionFromText( "a" + newAssignment.getOperationSign().getText() + "b", null ); infixArgument.getLOperand().replace( elementFactory.createExpressionFromText(invokerName + "." + getterMethodName + "()", null) ); } PsiExpressionList methodInvocationArgumentsRewrite = setterMethodInvocation.getArgumentList(); if (oldRightHandSideName != null) { boolean accessedFieldFound = false; for (PsiField accessedFieldBinding : accessedFieldBindings) { if (accessedFieldBinding.equals(oldRightHandSideName.resolve())) { if (accessedFieldBinding.getModifierList().hasModifierProperty(PsiModifier.STATIC)) { String qualifier = accessedFieldBinding.getContainingClass().getName(); PsiReferenceExpression reference = (PsiReferenceExpression) elementFactory.createExpressionFromText( qualifier + "." + newRightHandSideName.getReferenceName(), null ); if (infixArgument != null) { infixArgument.getROperand().replace(reference); methodInvocationArgumentsRewrite.add(infixArgument); } else { methodInvocationArgumentsRewrite.add(reference); } if (accessedFieldBinding.getContainingClass().equals(sourceTypeDeclaration)) { setPublicModifierToSourceField(accessedFieldBinding); } } else { String rightHandMethodName = getGetterName(superAccessedFields, accessedFieldBinding); PsiMethodCallExpression getterCallExpression = (PsiMethodCallExpression) elementFactory.createExpressionFromText( invokerName + "." + rightHandMethodName + "()", null ); if (infixArgument != null) { infixArgument.getROperand().replace(getterCallExpression); methodInvocationArgumentsRewrite.add(infixArgument); } else { methodInvocationArgumentsRewrite.add(getterCallExpression); } } accessedFieldFound = true; break; } } if (!accessedFieldFound) { if (infixArgument != null) { infixArgument.getROperand().replace(newAssignment.getRExpression()); methodInvocationArgumentsRewrite.add(infixArgument); } else { methodInvocationArgumentsRewrite.add(newAssignment.getRExpression()); } } } else { if (infixArgument != null) { infixArgument.getROperand().replace(newAssignment.getRExpression()); methodInvocationArgumentsRewrite.add(infixArgument); } else { methodInvocationArgumentsRewrite.add(newAssignment.getRExpression()); } } newAssignment.replace(setterMethodInvocation); break; } } } if (newRightHandSideName != null && newRightHandSideName.equals(newSimpleName)) { for (PsiField accessedFieldBinding : accessedFieldBindings) { if (accessedFieldBinding.equals(oldRightHandSideName.resolve())) { if (accessedFieldBinding.getModifierList().hasModifierProperty(PsiModifier.STATIC)) { String qualifier = accessedFieldBinding.getContainingClass().getName(); newSimpleName.replace(elementFactory.createExpressionFromText( qualifier + "." + newSimpleName.getReferenceName(), null )); if (accessedFieldBinding.getContainingClass().equals(sourceTypeDeclaration)) { setPublicModifierToSourceField(accessedFieldBinding); } } else { String rightHandMethodName = getGetterName(superAccessedFields, accessedFieldBinding); newAssignment.getRExpression().replace(elementFactory.createExpressionFromText( invokerName + '.' + rightHandMethodName + "()", null )); } break; } } } } else if (newSimpleName.getParent() instanceof PsiPostfixExpression) { // TODO: does not work for {a = b++} (even in the original plugin) PsiUnaryExpression newPostfixExpression = (PsiPostfixExpression) newSimpleName.getParent(); PsiPostfixExpression oldPostfixExpression = (PsiPostfixExpression) oldSimpleName.getParent(); PsiReferenceExpression newOperandReference = null; PsiReferenceExpression oldOperandReference = null; if (newPostfixExpression.getOperand() instanceof PsiReferenceExpression) { newOperandReference = (PsiReferenceExpression) newPostfixExpression.getOperand(); oldOperandReference = (PsiReferenceExpression) oldPostfixExpression.getOperand(); } String invokerName = sourceTypeDeclaration.getName(); invokerName = invokerName.substring(0, 1).toLowerCase() + invokerName.substring(1); if (newOperandReference != null && newOperandReference.equals(newSimpleName)) { handleBasicUnaryExpression( superAccessedFields, superAssignedFields, assignedFieldBindings, newPostfixExpression, oldOperandReference, invokerName ); } } else if (newSimpleName.getParent() instanceof PsiPrefixExpression) { PsiUnaryExpression newPrefixExpression = (PsiPrefixExpression) newSimpleName.getParent(); PsiPrefixExpression oldPrefixExpression = (PsiPrefixExpression) oldSimpleName.getParent(); PsiReferenceExpression newOperandSimpleName = null; PsiReferenceExpression oldOperandSimpleName = null; if (newPrefixExpression.getOperand() instanceof PsiReferenceExpression) { newOperandSimpleName = (PsiReferenceExpression) newPrefixExpression.getOperand(); oldOperandSimpleName = (PsiReferenceExpression) oldPrefixExpression.getOperand(); } String invokerName = sourceTypeDeclaration.getName(); invokerName = invokerName.substring(0, 1).toLowerCase() + invokerName.substring(1); if (newPrefixExpression.getOperationTokenType().equals(JavaTokenType.PLUSPLUS) || newPrefixExpression.getOperationTokenType().equals(JavaTokenType.MINUSMINUS)) { if (newOperandSimpleName != null && newOperandSimpleName.equals(newSimpleName)) { handleBasicUnaryExpression( superAccessedFields, superAssignedFields, assignedFieldBindings, newPrefixExpression, oldOperandSimpleName, invokerName ); } } else { if (newOperandSimpleName != null && newOperandSimpleName.equals(newSimpleName)) { for (PsiField accessedFieldBinding : accessedFieldBindings) { if (accessedFieldBinding.equals(oldOperandSimpleName.resolve())) { if (accessedFieldBinding.getModifierList().hasModifierProperty(PsiModifier.STATIC)) { String qualifier = accessedFieldBinding.getContainingClass().getName(); newOperandSimpleName.replace(elementFactory.createExpressionFromText( qualifier + "." + newOperandSimpleName.getReferenceName(), null )); if (accessedFieldBinding.getContainingClass().equals(sourceTypeDeclaration)) { setPublicModifierToSourceField(accessedFieldBinding); } } else { String methodName = getGetterName(superAccessedFields, accessedFieldBinding); PsiMethodCallExpression methodInvocation = (PsiMethodCallExpression) elementFactory.createExpressionFromText( invokerName + "." + methodName + "()", null ); newOperandSimpleName.replace(methodInvocation); } } } } } } else { for (PsiField accessedFieldBinding : accessedFieldBindings) { if (accessedFieldBinding.equals(oldSimpleName.resolve())) { if (accessedFieldBinding.getModifierList().hasModifierProperty(PsiModifier.STATIC)) { PsiExpression qualifier = elementFactory.createExpressionFromText( accessedFieldBinding.getContainingClass().getName(), null ); newSimpleName.setQualifierExpression(qualifier); if (accessedFieldBinding.getContainingClass().equals(sourceTypeDeclaration)) { setPublicModifierToSourceField(accessedFieldBinding); } } else { String methodName = getGetterName(superAccessedFields, accessedFieldBinding); String invokerName = sourceTypeDeclaration.getName(); invokerName = invokerName.substring(0, 1).toLowerCase() + invokerName.substring(1); PsiMethodCallExpression getterInvocation = (PsiMethodCallExpression) elementFactory.createExpressionFromText( invokerName + "." + methodName + "()", null ); newSimpleName.replace(getterInvocation); } break; } } } } } private void handleBasicUnaryExpression(Set<PsiField> superAccessedFields, Set<PsiField> superAssignedFields, Set<PsiField> assignedFieldBindings, PsiUnaryExpression newUnaryExpression, PsiReferenceExpression oldOperandReference, String invokerName) { for (PsiField assignedFieldBinding : assignedFieldBindings) { if (assignedFieldBinding.equals(oldOperandReference.resolve())) { PsiMethodCallExpression setterMethodInvocation = generateSetterInvocation(superAssignedFields, invokerName, assignedFieldBinding); String getterMethodName = getGetterName(superAccessedFields, assignedFieldBinding); String infixOperator = null; if (newUnaryExpression.getOperationTokenType().equals(JavaTokenType.PLUSPLUS)) { infixOperator = "+"; } else if (newUnaryExpression.getOperationTokenType().equals(JavaTokenType.MINUSMINUS)) { infixOperator = "-"; } PsiBinaryExpression infixArgument = (PsiBinaryExpression) elementFactory.createExpressionFromText( invokerName + "." + getterMethodName + "()" + infixOperator + "1", null ); PsiExpressionList setterMethodInvocationArgumentsRewrite = setterMethodInvocation.getArgumentList(); setterMethodInvocationArgumentsRewrite.add(infixArgument); newUnaryExpression.replace(setterMethodInvocation); } } } private String getGetterName(Set<PsiField> superAccessedFields, PsiField assignedFieldBinding) { PsiMethod getterMethodBinding; if (superAccessedFields.contains(assignedFieldBinding)) { getterMethodBinding = typeCheckElimination.getGetterMethodBindingOfSuperAccessedField(assignedFieldBinding); } else { getterMethodBinding = findGetterMethodInContext(assignedFieldBinding); } String getterMethodName; if (getterMethodBinding != null) { getterMethodName = getterMethodBinding.getName(); } else { getterMethodName = assignedFieldBinding.getName(); getterMethodName = "get" + getterMethodName.substring(0, 1).toUpperCase() + getterMethodName.substring(1); } return getterMethodName; } private PsiMethodCallExpression generateSetterInvocation(Set<PsiField> superAssignedFields, String invokerName, PsiField assignedFieldBinding) { PsiMethod setterMethodBinding; if (superAssignedFields.contains(assignedFieldBinding)) { setterMethodBinding = typeCheckElimination.getSetterMethodBindingOfSuperAssignedField(assignedFieldBinding); } else { setterMethodBinding = findSetterMethodInContext(assignedFieldBinding); } String leftHandMethodName; if (setterMethodBinding != null) { leftHandMethodName = setterMethodBinding.getName(); } else { leftHandMethodName = assignedFieldBinding.getName(); leftHandMethodName = "set" + leftHandMethodName.substring(0, 1).toUpperCase() + leftHandMethodName.substring(1); } return (PsiMethodCallExpression) elementFactory.createExpressionFromText( invokerName + "." + leftHandMethodName + "()", null ); } private void setPublicModifierToSourceField(PsiField variableBinding) { PsiUtil.setModifierProperty(variableBinding, PsiModifier.PUBLIC, true); } protected PsiMethod findSetterMethodInContext(PsiField fieldBinding) { PsiMethod[] contextMethods = sourceTypeDeclaration.getMethods(); for (PsiMethod methodDeclaration : contextMethods) { PsiElement simpleName = MethodDeclarationUtility.isSetter(methodDeclaration); if (fieldBinding.equals(simpleName)) { return methodDeclaration; } } return null; } protected PsiMethod findGetterMethodInContext(PsiField fieldBinding) { PsiMethod[] contextMethods = sourceTypeDeclaration.getMethods(); for (PsiMethod methodDeclaration : contextMethods) { PsiExpression simpleName = MethodDeclarationUtility.isGetter(methodDeclaration); if (simpleName instanceof PsiReferenceExpression && fieldBinding.equals(((PsiReferenceExpression) simpleName).resolve())) { return methodDeclaration; } } return null; } protected void generateGettersForAccessedFields() { Set<PsiField> accessedFields = new LinkedHashSet<>(); accessedFields.addAll(typeCheckElimination.getAccessedFields()); accessedFields.addAll(typeCheckElimination.getSuperAccessedFields()); for (PsiField fragment : accessedFields) { if (!fragment.getModifierList().hasModifierProperty(PsiModifier.STATIC)) { PsiMethod getterMethodBinding = null; if (typeCheckElimination.getSuperAccessedFields().contains(fragment)) { for (PsiField fieldBinding : typeCheckElimination.getSuperAccessedFieldBindings()) { if (fieldBinding.equals(fragment)) { getterMethodBinding = typeCheckElimination.getGetterMethodBindingOfSuperAccessedField(fieldBinding); break; } } } else { getterMethodBinding = findGetterMethodInContext(fragment); } if (getterMethodBinding == null) { PsiMethod getter = PropertyUtil.generateGetterPrototype(fragment); fragment.getContainingClass().add(getter); } } } } protected void generateSettersForAssignedFields() { Set<PsiField> assignedFields = new LinkedHashSet<>(); assignedFields.addAll(typeCheckElimination.getAssignedFields()); assignedFields.addAll(typeCheckElimination.getSuperAssignedFields()); for (PsiField fragment : assignedFields) { PsiMethod setterMethodBinding = null; if (typeCheckElimination.getSuperAssignedFields().contains(fragment)) { for (PsiField fieldBinding : typeCheckElimination.getSuperAssignedFieldBindings()) { if (fieldBinding.equals(fragment)) { setterMethodBinding = typeCheckElimination.getSetterMethodBindingOfSuperAssignedField(fieldBinding); break; } } } else { setterMethodBinding = findSetterMethodInContext(fragment); } if (setterMethodBinding == null) { PsiMethod setter = PropertyUtil.generateSetterPrototype(fragment); fragment.getContainingClass().add(setter); } } } protected void setPublicModifierToAccessedMethods() { for (PsiMethod methodDeclaration : typeCheckElimination.getAccessedMethods()) { PsiUtil.setModifierProperty(methodDeclaration, PsiModifier.PUBLIC, true); } } protected PsiExpression constructExpression(DefaultMutableTreeNode node) { Object object = node.getUserObject(); if (object instanceof PsiJavaToken) { PsiJavaToken operator = (PsiJavaToken) object; DefaultMutableTreeNode leftChild = (DefaultMutableTreeNode) node.getChildAt(0); DefaultMutableTreeNode rightChild = (DefaultMutableTreeNode) node.getChildAt(1); PsiExpression leftExpression = constructExpression(leftChild); PsiExpression rightExpression = constructExpression(rightChild); PsiBinaryExpression binaryExpression = (PsiBinaryExpression) elementFactory.createExpressionFromText("a" + operator.getText() + "b", null); binaryExpression.getLOperand().replace(leftExpression); binaryExpression.getROperand().replace(rightExpression); return binaryExpression; } else if (object instanceof PsiExpression) { return (PsiExpression) object; } return null; } protected boolean sourceTypeRequiredForExtraction() { return Stream.of( typeCheckElimination.getAccessedFields(), typeCheckElimination.getAssignedFields(), typeCheckElimination.getAccessedMethods(), typeCheckElimination.getAccessedMethods(), typeCheckElimination.getSuperAccessedFieldBindings(), typeCheckElimination.getSuperAssignedFieldBindings() ).flatMap(Collection::stream).anyMatch(element -> element.getModifierList() == null || !element.getModifierList().hasModifierProperty(PsiModifier.STATIC) ); } protected static PsiImportList getPsiImportList(PsiFile classFile) { PsiElement[] children = classFile.getChildren(); for (PsiElement child : children) { if (child instanceof PsiImportList) { return (PsiImportList) child; } } // will not happen return null; } public Project getProject() { return project; } public PsiClass getSourceTypeDeclaration() { return sourceTypeDeclaration; } public TypeCheckElimination getTypeCheckElimination() { return typeCheckElimination; } }