/* * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * 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.siyeh.ig.psiutils; import com.intellij.codeInspection.dataFlow.value.DfaRelationValue; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; public class EquivalenceChecker { protected static final Match EXACT_MATCH = new Match(true); protected static final Match EXACT_MISMATCH = new Match(false); private static final EquivalenceChecker ourCanonicalPsiEquivalence = new EquivalenceChecker(); public static EquivalenceChecker getCanonicalPsiEquivalence() { return ourCanonicalPsiEquivalence; } public static class Match { private final PsiElement myLeftDiff; private final PsiElement myRightDiff; private final Boolean myExactlyMatches; Match(boolean exactlyMatches) { myExactlyMatches = exactlyMatches; myLeftDiff = null; myRightDiff = null; } Match(PsiElement leftDiff, PsiElement rightDiff) { myExactlyMatches = null; myLeftDiff = leftDiff; myRightDiff = rightDiff; } public PsiElement getLeftDiff() { return myLeftDiff; } public PsiElement getRightDiff() { return myRightDiff; } public boolean isPartialMatch() { return myExactlyMatches == null; } public boolean isExactMatch() { return myExactlyMatches != null && myExactlyMatches; } public boolean isExactMismatch() { return myExactlyMatches != null && !myExactlyMatches; } Match partialIfExactMismatch(PsiElement left, PsiElement right) { return this == EXACT_MISMATCH ? new Match(left, right) : this; } static Match exact(boolean exactMatches) { return exactMatches ? EXACT_MATCH : EXACT_MISMATCH; } Match combine(Match other) { if (other.isExactMismatch() || isExactMatch()) { return other; } if (isExactMismatch() || other.isExactMatch()) { return this; } return EXACT_MISMATCH; } } public boolean statementsAreEquivalent(@Nullable PsiStatement statement1, @Nullable PsiStatement statement2) { return statementsMatch(statement1, statement2).isExactMatch(); } public Match statementsMatch(@Nullable PsiStatement statement1, @Nullable PsiStatement statement2) { statement1 = ControlFlowUtils.stripBraces(statement1); statement2 = ControlFlowUtils.stripBraces(statement2); if (statement1 == null) { return Match.exact(statement2 == null); } if (statement2 == null) { return EXACT_MISMATCH; } if (statement1.getClass() != statement2.getClass()) { return EXACT_MISMATCH; } if (statement1 instanceof PsiAssertStatement) { return assertStatementsMatch((PsiAssertStatement)statement1, (PsiAssertStatement)statement2); } if (statement1 instanceof PsiBlockStatement) { return blockStatementsMatch((PsiBlockStatement)statement1, (PsiBlockStatement)statement2); } if (statement1 instanceof PsiBreakStatement) { return breakStatementsMatch((PsiBreakStatement)statement1, (PsiBreakStatement)statement2); } if (statement1 instanceof PsiContinueStatement) { return continueStatementsMatch((PsiContinueStatement)statement1, (PsiContinueStatement)statement2); } if (statement1 instanceof PsiDeclarationStatement) { return declarationStatementsMatch((PsiDeclarationStatement)statement1, (PsiDeclarationStatement)statement2); } if (statement1 instanceof PsiDoWhileStatement) { return doWhileStatementsMatch((PsiDoWhileStatement)statement1, (PsiDoWhileStatement)statement2); } if (statement1 instanceof PsiEmptyStatement) { return EXACT_MATCH; } if (statement1 instanceof PsiExpressionListStatement) { return expressionListStatementsMatch((PsiExpressionListStatement)statement1, (PsiExpressionListStatement)statement2); } if (statement1 instanceof PsiExpressionStatement) { return expressionStatementsMatch((PsiExpressionStatement)statement1, (PsiExpressionStatement)statement2); } if (statement1 instanceof PsiForStatement) { return forStatementsMatch((PsiForStatement)statement1, (PsiForStatement)statement2); } if (statement1 instanceof PsiForeachStatement) { return forEachStatementsMatch((PsiForeachStatement)statement1, (PsiForeachStatement)statement2); } if (statement1 instanceof PsiIfStatement) { return ifStatementsMatch((PsiIfStatement)statement1, (PsiIfStatement)statement2); } if (statement1 instanceof PsiLabeledStatement) { return labeledStatementsMatch((PsiLabeledStatement)statement1, (PsiLabeledStatement)statement2); } if (statement1 instanceof PsiReturnStatement) { return returnStatementsMatch((PsiReturnStatement)statement1, (PsiReturnStatement)statement2); } if (statement1 instanceof PsiSwitchStatement) { return switchStatementsMatch((PsiSwitchStatement)statement1, (PsiSwitchStatement)statement2); } if (statement1 instanceof PsiSwitchLabelStatement) { return switchLabelStatementsMatch((PsiSwitchLabelStatement)statement1, (PsiSwitchLabelStatement)statement2); } if (statement1 instanceof PsiSynchronizedStatement) { return synchronizedStatementsMatch((PsiSynchronizedStatement)statement1, (PsiSynchronizedStatement)statement2); } if (statement1 instanceof PsiThrowStatement) { return throwStatementsMatch((PsiThrowStatement)statement1, (PsiThrowStatement)statement2); } if (statement1 instanceof PsiTryStatement) { return tryStatementsMatch((PsiTryStatement)statement1, (PsiTryStatement)statement2); } if (statement1 instanceof PsiWhileStatement) { return whileStatementsMatch((PsiWhileStatement)statement1, (PsiWhileStatement)statement2); } final String text1 = statement1.getText(); final String text2 = statement2.getText(); return Match.exact(text1.equals(text2)); } protected Match declarationStatementsMatch(@NotNull PsiDeclarationStatement statement1, @NotNull PsiDeclarationStatement statement2) { final PsiElement[] elements1 = statement1.getDeclaredElements(); final List<PsiLocalVariable> vars1 = new ArrayList<>(elements1.length); for (PsiElement anElement : elements1) { if (anElement instanceof PsiLocalVariable) { vars1.add((PsiLocalVariable)anElement); } } final PsiElement[] elements2 = statement2.getDeclaredElements(); final List<PsiLocalVariable> vars2 = new ArrayList<>(elements2.length); for (PsiElement anElement : elements2) { if (anElement instanceof PsiLocalVariable) { vars2.add((PsiLocalVariable)anElement); } } final int size = vars1.size(); if (size != vars2.size()) { return EXACT_MISMATCH; } for (int i = 0; i < size; i++) { final PsiLocalVariable var1 = vars1.get(i); final PsiLocalVariable var2 = vars2.get(i); if (!localVariablesAreEquivalent(var1, var2).isExactMatch()) { return EXACT_MISMATCH; } } return EXACT_MATCH; } protected Match localVariablesAreEquivalent(@NotNull PsiLocalVariable localVariable1, @NotNull PsiLocalVariable localVariable2) { final PsiType type1 = localVariable1.getType(); final PsiType type2 = localVariable2.getType(); if (!typesAreEquivalent(type1, type2)) { return EXACT_MISMATCH; } final String name1 = localVariable1.getName(); final String name2 = localVariable2.getName(); if (name1 == null) { return Match.exact(name2 == null); } if (!name1.equals(name2)) { return EXACT_MISMATCH; } final PsiExpression initializer1 = localVariable1.getInitializer(); final PsiExpression initializer2 = localVariable2.getInitializer(); return expressionsMatch(initializer1, initializer2).partialIfExactMismatch(initializer1, initializer2); } protected Match tryStatementsMatch(@NotNull PsiTryStatement statement1, @NotNull PsiTryStatement statement2) { final PsiCodeBlock tryBlock1 = statement1.getTryBlock(); final PsiCodeBlock tryBlock2 = statement2.getTryBlock(); if (!codeBlocksMatch(tryBlock1, tryBlock2).isExactMatch()) { return EXACT_MISMATCH; } final PsiCodeBlock finallyBlock1 = statement1.getFinallyBlock(); final PsiCodeBlock finallyBlock2 = statement2.getFinallyBlock(); if (!codeBlocksMatch(finallyBlock1, finallyBlock2).isExactMatch()) { return EXACT_MISMATCH; } final PsiCodeBlock[] catchBlocks1 = statement1.getCatchBlocks(); final PsiCodeBlock[] catchBlocks2 = statement2.getCatchBlocks(); if (catchBlocks1.length != catchBlocks2.length) { return EXACT_MISMATCH; } for (int i = 0; i < catchBlocks2.length; i++) { if (!codeBlocksMatch(catchBlocks1[i], catchBlocks2[i]).isExactMatch()) { return EXACT_MISMATCH; } } final PsiResourceList resourceList1 = statement1.getResourceList(); final PsiResourceList resourceList2 = statement2.getResourceList(); if (resourceList1 != null) { if (resourceList2 == null) { return EXACT_MISMATCH; } if (resourceList1.getResourceVariablesCount() != resourceList2.getResourceVariablesCount()) { return EXACT_MISMATCH; } final List<PsiResourceListElement> resources1 = PsiTreeUtil.getChildrenOfTypeAsList(resourceList1, PsiResourceListElement.class); final List<PsiResourceListElement> resources2 = PsiTreeUtil.getChildrenOfTypeAsList(resourceList2, PsiResourceListElement.class); for (int i = 0, size = resources1.size(); i < size; i++) { final PsiResourceListElement resource1 = resources1.get(i); final PsiResourceListElement resource2 = resources2.get(i); if (resource1 instanceof PsiResourceVariable && resource2 instanceof PsiResourceVariable) { if (!localVariablesAreEquivalent((PsiLocalVariable)resource1, (PsiLocalVariable)resource2).isExactMatch()) { return EXACT_MISMATCH; } } else if (resource1 instanceof PsiResourceExpression && resource2 instanceof PsiResourceExpression) { if (!expressionsMatch(((PsiResourceExpression)resource1).getExpression(), ((PsiResourceExpression)resource2).getExpression()).isExactMatch()) { return EXACT_MISMATCH; } } else { return EXACT_MISMATCH; } } } else if (resourceList2 != null) { return EXACT_MISMATCH; } final PsiParameter[] catchParameters1 = statement1.getCatchBlockParameters(); final PsiParameter[] catchParameters2 = statement2.getCatchBlockParameters(); if (catchParameters1.length != catchParameters2.length) { return EXACT_MISMATCH; } for (int i = 0; i < catchParameters2.length; i++) { if (!parametersAreEquivalent(catchParameters2[i], catchParameters1[i]).isExactMatch()) { return EXACT_MISMATCH; } } return EXACT_MATCH; } protected Match parametersAreEquivalent(@NotNull PsiParameter parameter1, @NotNull PsiParameter parameter2) { final PsiType type1 = parameter1.getType(); final PsiType type2 = parameter2.getType(); if (!typesAreEquivalent(type1, type2)) { return EXACT_MISMATCH; } final String name1 = parameter1.getName(); final String name2 = parameter2.getName(); if (name1 == null) { return Match.exact(name2 == null); } return Match.exact(name1.equals(name2)); } public boolean typesAreEquivalent(@Nullable PsiType type1, @Nullable PsiType type2) { if (type1 == null) { return type2 == null; } if (type2 == null) { return false; } final String type1Text = type1.getCanonicalText(); final String type2Text = type2.getCanonicalText(); return type1Text.equals(type2Text); } protected Match whileStatementsMatch(@NotNull PsiWhileStatement statement1, @NotNull PsiWhileStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement body1 = statement1.getBody(); final PsiStatement body2 = statement2.getBody(); final Match conditionEquivalence = expressionsMatch(condition1, condition2); final Match bodyEquivalence = statementsMatch(body1, body2); return getComplexElementDecision(bodyEquivalence, conditionEquivalence, body1, body2, condition1, condition2); } protected Match forStatementsMatch(@NotNull PsiForStatement statement1, @NotNull PsiForStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); if (!expressionsMatch(condition1, condition2).isExactMatch()) { return EXACT_MISMATCH; } final PsiStatement initialization1 = statement1.getInitialization(); final PsiStatement initialization2 = statement2.getInitialization(); if (!statementsMatch(initialization1, initialization2).isExactMatch()) { return EXACT_MISMATCH; } final PsiStatement update1 = statement1.getUpdate(); final PsiStatement update2 = statement2.getUpdate(); if (!statementsMatch(update1, update2).isExactMatch()) { return EXACT_MISMATCH; } final PsiStatement body1 = statement1.getBody(); final PsiStatement body2 = statement2.getBody(); return statementsMatch(body1, body2).partialIfExactMismatch(body1, body2); } protected Match forEachStatementsMatch(@NotNull PsiForeachStatement statement1, @NotNull PsiForeachStatement statement2) { final PsiExpression value1 = statement1.getIteratedValue(); final PsiExpression value2 = statement2.getIteratedValue(); if (!expressionsMatch(value1, value2).isExactMatch()) { return EXACT_MISMATCH; } final PsiParameter parameter1 = statement1.getIterationParameter(); final PsiParameter parameter2 = statement1.getIterationParameter(); final String name1 = parameter1.getName(); if (name1 == null) { return Match.exact(parameter2.getName() == null); } if (!name1.equals(parameter2.getName())) { return EXACT_MISMATCH; } final PsiType type1 = parameter1.getType(); if (!type1.equals(parameter2.getType())) { return EXACT_MISMATCH; } final PsiStatement body1 = statement1.getBody(); final PsiStatement body2 = statement2.getBody(); return statementsMatch(body1, body2).partialIfExactMismatch(body1, body2); } protected Match switchStatementsMatch(@NotNull PsiSwitchStatement statement1, @NotNull PsiSwitchStatement statement2) { final PsiExpression switchExpression1 = statement1.getExpression(); final PsiExpression switchExpression2 = statement2.getExpression(); final PsiCodeBlock body1 = statement1.getBody(); final PsiCodeBlock body2 = statement2.getBody(); final Match bodyEq = codeBlocksMatch(body1, body2); if (bodyEq != EXACT_MATCH) { return EXACT_MISMATCH; } return expressionsMatch(switchExpression1, switchExpression2).partialIfExactMismatch(switchExpression1, switchExpression2); } protected Match doWhileStatementsMatch(@NotNull PsiDoWhileStatement statement1, @NotNull PsiDoWhileStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement body1 = statement1.getBody(); final PsiStatement body2 = statement2.getBody(); final Match conditionEq = expressionsMatch(condition1, condition2); final Match bodyEq = statementsMatch(body1, body2); return getComplexElementDecision(bodyEq, conditionEq, body1, body2, condition1, condition2); } protected Match assertStatementsMatch(@NotNull PsiAssertStatement statement1, @NotNull PsiAssertStatement statement2) { final PsiExpression condition1 = statement1.getAssertCondition(); final PsiExpression condition2 = statement2.getAssertCondition(); final PsiExpression description1 = statement1.getAssertDescription(); final PsiExpression description2 = statement2.getAssertDescription(); final Match condEq = expressionsMatch(condition1, condition2); final Match exprEq = expressionsMatch(description1, description2); return getComplexElementDecision(condEq, exprEq, condition1, condition2, description1, description2); } protected Match synchronizedStatementsMatch(@NotNull PsiSynchronizedStatement statement1, @NotNull PsiSynchronizedStatement statement2) { final PsiExpression lock1 = statement1.getLockExpression(); final PsiExpression lock2 = statement2.getLockExpression(); final PsiCodeBlock body1 = statement1.getBody(); final PsiCodeBlock body2 = statement2.getBody(); final Match lockEq = expressionsMatch(lock1, lock2); final Match blockEq = codeBlocksMatch(body1, body2); return getComplexElementDecision(blockEq, lockEq, body1, body2, lock1, lock2); } protected Match blockStatementsMatch(@NotNull PsiBlockStatement statement1, @NotNull PsiBlockStatement statement2) { final PsiCodeBlock block1 = statement1.getCodeBlock(); final PsiCodeBlock block2 = statement2.getCodeBlock(); return codeBlocksMatch(block1, block2); } protected Match breakStatementsMatch(@NotNull PsiBreakStatement statement1, @NotNull PsiBreakStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); if (identifier1 == null) { return Match.exact(identifier2 == null); } if (identifier2 == null) { return EXACT_MISMATCH; } final String text1 = identifier1.getText(); final String text2 = identifier2.getText(); return Match.exact(text1.equals(text2)); } protected Match continueStatementsMatch(@NotNull PsiContinueStatement statement1, @NotNull PsiContinueStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); if (identifier1 == null) { return Match.exact(identifier2 == null); } if (identifier2 == null) { return EXACT_MISMATCH; } final String text1 = identifier1.getText(); final String text2 = identifier2.getText(); return Match.exact(text1.equals(text2)); } protected Match switchLabelStatementsMatch(@NotNull PsiSwitchLabelStatement statement1, @NotNull PsiSwitchLabelStatement statement2) { if (statement1.isDefaultCase()) { return Match.exact(statement2.isDefaultCase()); } if (statement2.isDefaultCase()) { return EXACT_MISMATCH; } final PsiExpression caseExpression1 = statement1.getCaseValue(); final PsiExpression caseExpression2 = statement2.getCaseValue(); return expressionsMatch(caseExpression1, caseExpression2).partialIfExactMismatch(caseExpression1, caseExpression2); } protected Match labeledStatementsMatch(@NotNull PsiLabeledStatement statement1, @NotNull PsiLabeledStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); final String text1 = identifier1.getText(); final String text2 = identifier2.getText(); return Match.exact(text1.equals(text2)); } public boolean codeBlocksAreEquivalent(@Nullable PsiCodeBlock block1, @Nullable PsiCodeBlock block2) { return codeBlocksMatch(block1, block2).isExactMatch(); } protected Match codeBlocksMatch(@Nullable PsiCodeBlock block1, @Nullable PsiCodeBlock block2) { if (block1 == null && block2 == null) { return EXACT_MATCH; } if (block1 == null || block2 == null) { return EXACT_MISMATCH; } final PsiStatement[] statements1 = block1.getStatements(); final PsiStatement[] statements2 = block2.getStatements(); if (statements2.length != statements1.length) { return EXACT_MISMATCH; } for (int i = 0; i < statements2.length; i++) { if (!statementsMatch(statements2[i], statements1[i]).isExactMatch()) { return EXACT_MISMATCH; } } return EXACT_MATCH; } protected Match ifStatementsMatch(@NotNull PsiIfStatement statement1, @NotNull PsiIfStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement thenBranch1 = statement1.getThenBranch(); final PsiStatement thenBranch2 = statement2.getThenBranch(); final PsiStatement elseBranch1 = statement1.getElseBranch(); final PsiStatement elseBranch2 = statement2.getElseBranch(); final Match conditionEq = expressionsMatch(condition1, condition2); final Match thenEq = statementsMatch(thenBranch1, thenBranch2); final Match elseEq = statementsMatch(elseBranch1, elseBranch2); if (conditionEq == EXACT_MATCH && thenEq == EXACT_MATCH && elseEq == EXACT_MATCH) { return EXACT_MATCH; } return EXACT_MISMATCH; } protected Match expressionStatementsMatch(@NotNull PsiExpressionStatement statement1, @NotNull PsiExpressionStatement statement2) { final PsiExpression expression1 = statement1.getExpression(); final PsiExpression expression2 = statement2.getExpression(); return expressionsMatch(expression1, expression2); } protected Match returnStatementsMatch(@NotNull PsiReturnStatement statement1, @NotNull PsiReturnStatement statement2) { final PsiExpression returnValue1 = statement1.getReturnValue(); final PsiExpression returnValue2 = statement2.getReturnValue(); final Match match = expressionsMatch(returnValue1, returnValue2); if (match.isExactMismatch()) { return new Match(returnValue1, returnValue2); } return match; } protected Match throwStatementsMatch(@NotNull PsiThrowStatement statement1, @NotNull PsiThrowStatement statement2) { final PsiExpression exception1 = statement1.getException(); final PsiExpression exception2 = statement2.getException(); return expressionsMatch(exception1, exception2); } protected Match expressionListStatementsMatch(@NotNull PsiExpressionListStatement statement1, @NotNull PsiExpressionListStatement statement2) { final PsiExpressionList expressionList1 = statement1.getExpressionList(); final PsiExpression[] expressions1 = expressionList1.getExpressions(); final PsiExpressionList expressionList2 = statement2.getExpressionList(); final PsiExpression[] expressions2 = expressionList2.getExpressions(); return expressionsAreEquivalent(expressions1, expressions2); } public boolean expressionsAreEquivalent(@Nullable PsiExpression expression1, @Nullable PsiExpression expression2) { return expressionsMatch(expression1, expression2).isExactMatch(); } public Match expressionsMatch(@Nullable PsiExpression expression1, @Nullable PsiExpression expression2) { expression1 = ParenthesesUtils.stripParentheses(expression1); expression2 = ParenthesesUtils.stripParentheses(expression2); if (expression1 == null) { return Match.exact(expression2 == null); } if (expression2 == null) { return EXACT_MISMATCH; } if (expression1.getClass() != expression2.getClass()) { return EXACT_MISMATCH; } if (expression1 instanceof PsiThisExpression) { return EXACT_MATCH; } if (expression1 instanceof PsiSuperExpression) { return EXACT_MATCH; } if (expression1 instanceof PsiLiteralExpression) { return literalExpressionsMatch((PsiLiteralExpression)expression1, (PsiLiteralExpression)expression2); } if (expression1 instanceof PsiClassObjectAccessExpression) { return classObjectAccessExpressionsMatch((PsiClassObjectAccessExpression)expression1, (PsiClassObjectAccessExpression)expression2); } if (expression1 instanceof PsiReferenceExpression) { return referenceExpressionsMatch((PsiReferenceExpression)expression1, (PsiReferenceExpression)expression2); } if (expression1 instanceof PsiMethodCallExpression) { return methodCallExpressionsMatch((PsiMethodCallExpression)expression1, (PsiMethodCallExpression)expression2); } if (expression1 instanceof PsiNewExpression) { return newExpressionsMatch((PsiNewExpression)expression1, (PsiNewExpression)expression2); } if (expression1 instanceof PsiArrayInitializerExpression) { return arrayInitializerExpressionsMatch((PsiArrayInitializerExpression)expression1, (PsiArrayInitializerExpression)expression2); } if (expression1 instanceof PsiTypeCastExpression) { return typeCastExpressionsMatch((PsiTypeCastExpression)expression1, (PsiTypeCastExpression)expression2); } if (expression1 instanceof PsiArrayAccessExpression) { return arrayAccessExpressionsMatch((PsiArrayAccessExpression)expression2, (PsiArrayAccessExpression)expression1); } if (expression1 instanceof PsiPrefixExpression) { return prefixExpressionsMatch((PsiPrefixExpression)expression1, (PsiPrefixExpression)expression2); } if (expression1 instanceof PsiPostfixExpression) { return postfixExpressionsMatch((PsiPostfixExpression)expression1, (PsiPostfixExpression)expression2); } if (expression1 instanceof PsiBinaryExpression) { return binaryExpressionsMatch((PsiBinaryExpression)expression1, (PsiBinaryExpression)expression2); } if (expression1 instanceof PsiPolyadicExpression) { return polyadicExpressionsMatch((PsiPolyadicExpression)expression1, (PsiPolyadicExpression)expression2); } if (expression1 instanceof PsiAssignmentExpression) { return assignmentExpressionsMatch((PsiAssignmentExpression)expression1, (PsiAssignmentExpression)expression2); } if (expression1 instanceof PsiConditionalExpression) { return conditionalExpressionsMatch((PsiConditionalExpression)expression1, (PsiConditionalExpression)expression2); } if (expression1 instanceof PsiInstanceOfExpression) { return instanceOfExpressionsMatch((PsiInstanceOfExpression)expression1, (PsiInstanceOfExpression)expression2); } if (expression1 instanceof PsiLambdaExpression) { return lambdaExpressionsMatch((PsiLambdaExpression)expression1, (PsiLambdaExpression)expression2); } return EXACT_MISMATCH; } protected Match lambdaExpressionsMatch(PsiLambdaExpression expression1, PsiLambdaExpression expression2) { final PsiParameterList parameterList1 = expression1.getParameterList(); final PsiParameterList parameterList2 = expression2.getParameterList(); final PsiParameter[] parameters1 = parameterList1.getParameters(); final PsiParameter[] parameters2 = parameterList2.getParameters(); if (parameters1.length != parameters2.length) { return EXACT_MISMATCH; } for (int i = 0, length = parameters1.length; i < length; i++) { if (!parametersAreEquivalent(parameters1[i], parameters2[i]).isExactMatch()) { return EXACT_MISMATCH; } } final PsiElement body1 = unwrapLambdaBody(expression1.getBody()); final PsiElement body2 = unwrapLambdaBody(expression2.getBody()); if (body1 instanceof PsiCodeBlock && body2 instanceof PsiCodeBlock) { return codeBlocksMatch((PsiCodeBlock)body1, (PsiCodeBlock)body2); } else if (body1 instanceof PsiExpression && body2 instanceof PsiExpression) { return expressionsMatch((PsiExpression)body1, (PsiExpression)body2); } return EXACT_MISMATCH; } private static PsiElement unwrapLambdaBody(PsiElement element) { while (element instanceof PsiCodeBlock) { final PsiCodeBlock codeBlock = (PsiCodeBlock)element; final PsiStatement[] statements = codeBlock.getStatements(); if (statements.length != 1) { break; } final PsiStatement statement = statements[0]; if (statement instanceof PsiReturnStatement) { return ((PsiReturnStatement)statement).getReturnValue(); } else if (statement instanceof PsiExpressionStatement) { return ((PsiExpressionStatement)statement).getExpression(); } else if (statement instanceof PsiBlockStatement) { element = ((PsiBlockStatement)statement).getCodeBlock(); } else { break; } } return element; } protected Match literalExpressionsMatch(PsiLiteralExpression expression1, PsiLiteralExpression expression2) { final Object value1 = expression1.getValue(); final Object value2 = expression2.getValue(); if (value1 == null) { return Match.exact(value2 == null); } if (value2 == null) { return EXACT_MISMATCH; } return Match.exact(value1.equals(value2)); } protected Match classObjectAccessExpressionsMatch(PsiClassObjectAccessExpression expression1, PsiClassObjectAccessExpression expression2) { final PsiTypeElement operand1 = expression1.getOperand(); final PsiTypeElement operand2 = expression2.getOperand(); return typeElementsAreEquivalent(operand1, operand2); } protected Match referenceExpressionsMatch(PsiReferenceExpression referenceExpression1, PsiReferenceExpression referenceExpression2) { final PsiElement element1 = referenceExpression1.resolve(); final PsiElement element2 = referenceExpression2.resolve(); if (element1 != null) { if (!element1.equals(element2)) { return EXACT_MISMATCH; } } else { return EXACT_MISMATCH; // incomplete code } if (element1 instanceof PsiMember) { final PsiMember member1 = (PsiMember)element1; if (member1.hasModifierProperty(PsiModifier.STATIC)) { return EXACT_MATCH; } if (member1 instanceof PsiClass) { return EXACT_MATCH; } } else { return EXACT_MATCH; } final PsiExpression qualifier1 = referenceExpression1.getQualifierExpression(); final PsiExpression qualifier2 = referenceExpression2.getQualifierExpression(); if (qualifier1 != null && !(qualifier1 instanceof PsiThisExpression || qualifier1 instanceof PsiSuperExpression)) { if (qualifier2 == null) { return EXACT_MISMATCH; } return expressionsMatch(qualifier1, qualifier2); } else { if (qualifier2 != null && !(qualifier2 instanceof PsiThisExpression || qualifier2 instanceof PsiSuperExpression)) { return EXACT_MISMATCH; } } return EXACT_MATCH; } protected Match instanceOfExpressionsMatch(PsiInstanceOfExpression instanceOfExpression1, PsiInstanceOfExpression instanceOfExpression2) { final PsiExpression operand1 = instanceOfExpression1.getOperand(); final PsiExpression operand2 = instanceOfExpression2.getOperand(); if (!expressionsMatch(operand1, operand2).isExactMatch()) { return EXACT_MISMATCH; } final PsiTypeElement typeElement1 = instanceOfExpression1.getCheckType(); final PsiTypeElement typeElement2 = instanceOfExpression2.getCheckType(); return typeElementsAreEquivalent(typeElement1, typeElement2); } protected Match typeElementsAreEquivalent(PsiTypeElement typeElement1, PsiTypeElement typeElement2) { if (typeElement1 == null) { return Match.exact(typeElement2 == null); } if (typeElement2 == null) { return EXACT_MISMATCH; } final PsiType type1 = typeElement1.getType(); final PsiType type2 = typeElement2.getType(); return Match.exact(typesAreEquivalent(type1, type2)); } protected Match methodCallExpressionsMatch(@NotNull PsiMethodCallExpression methodCallExpression1, @NotNull PsiMethodCallExpression methodCallExpression2) { final PsiReferenceExpression methodExpression1 = methodCallExpression1.getMethodExpression(); final PsiReferenceExpression methodExpression2 = methodCallExpression2.getMethodExpression(); Match match = expressionsMatch(methodExpression1, methodExpression2); if (match.isExactMismatch()) { return EXACT_MISMATCH; } final PsiExpressionList argumentList1 = methodCallExpression1.getArgumentList(); final PsiExpression[] args1 = argumentList1.getExpressions(); final PsiExpressionList argumentList2 = methodCallExpression2.getArgumentList(); final PsiExpression[] args2 = argumentList2.getExpressions(); match = match.combine(expressionsAreEquivalent(args1, args2)); if (args1.length != 0 && match.isPartialMatch()) { final PsiElement leftDiff = match.getLeftDiff(); final PsiExpression lastArg = args1[args1.length - 1]; if (Comparing.equal(leftDiff, lastArg)) { final PsiType type1 = lastArg.getType(); final PsiType type2 = args2[args2.length - 1].getType(); if (type2 instanceof PsiArrayType && !(type1 instanceof PsiArrayType)) { return EXACT_MISMATCH; } if (type1 instanceof PsiArrayType && !(type2 instanceof PsiArrayType)) { return EXACT_MISMATCH; } } } return match; } protected Match newExpressionsMatch(@NotNull PsiNewExpression newExpression1, @NotNull PsiNewExpression newExpression2) { final PsiJavaCodeReferenceElement classReference1 = newExpression1.getClassReference(); final PsiJavaCodeReferenceElement classReference2 = newExpression2.getClassReference(); if (classReference1 == null || classReference2 == null) { return EXACT_MISMATCH; } final PsiElement target1 = classReference1.resolve(); if (target1 == null) { return EXACT_MISMATCH; } final PsiElement target2 = classReference2.resolve(); if (!target1.equals(target2)) { return EXACT_MISMATCH; } final PsiExpression[] arrayDimensions1 = newExpression1.getArrayDimensions(); final PsiExpression[] arrayDimensions2 = newExpression2.getArrayDimensions(); if (!expressionsAreEquivalent(arrayDimensions1, arrayDimensions2).isExactMatch()) { return EXACT_MISMATCH; } final PsiArrayInitializerExpression arrayInitializer1 = newExpression1.getArrayInitializer(); final PsiArrayInitializerExpression arrayInitializer2 = newExpression2.getArrayInitializer(); if (!expressionsMatch(arrayInitializer1, arrayInitializer2).isExactMatch()) { return EXACT_MISMATCH; } final PsiMethod constructor1 = newExpression1.resolveConstructor(); final PsiMethod constructor2 = newExpression2.resolveConstructor(); if (!Comparing.equal(constructor1, constructor2)) { return EXACT_MISMATCH; } final PsiExpression qualifier1 = newExpression1.getQualifier(); final PsiExpression qualifier2 = newExpression2.getQualifier(); if (!expressionsMatch(qualifier1, qualifier2).isExactMatch()) { return EXACT_MISMATCH; } final PsiExpressionList argumentList1 = newExpression1.getArgumentList(); final PsiExpression[] args1 = argumentList1 == null ? null : argumentList1.getExpressions(); final PsiExpressionList argumentList2 = newExpression2.getArgumentList(); final PsiExpression[] args2 = argumentList2 == null ? null : argumentList2.getExpressions(); return expressionsAreEquivalent(args1, args2); } protected Match arrayInitializerExpressionsMatch(@NotNull PsiArrayInitializerExpression arrayInitializerExpression1, @NotNull PsiArrayInitializerExpression arrayInitializerExpression2) { final PsiExpression[] initializers1 = arrayInitializerExpression1.getInitializers(); final PsiExpression[] initializers2 = arrayInitializerExpression2.getInitializers(); return expressionsAreEquivalent(initializers1, initializers2); } protected Match typeCastExpressionsMatch(@NotNull PsiTypeCastExpression typeCastExpression1, @NotNull PsiTypeCastExpression typeCastExpression2) { final PsiTypeElement typeElement1 = typeCastExpression1.getCastType(); final PsiTypeElement typeElement2 = typeCastExpression2.getCastType(); if (!typeElementsAreEquivalent(typeElement1, typeElement2).isExactMatch()) { return EXACT_MISMATCH; } final PsiExpression operand1 = typeCastExpression1.getOperand(); final PsiExpression operand2 = typeCastExpression2.getOperand(); if (operand1 instanceof PsiFunctionalExpression || operand2 instanceof PsiFunctionalExpression) { return EXACT_MISMATCH; } return expressionsMatch(operand1, operand2).partialIfExactMismatch(operand1, operand2); } protected Match arrayAccessExpressionsMatch(@NotNull PsiArrayAccessExpression arrayAccessExpression1, @NotNull PsiArrayAccessExpression arrayAccessExpression2) { final PsiExpression arrayExpression2 = arrayAccessExpression1.getArrayExpression(); final PsiExpression arrayExpression1 = arrayAccessExpression2.getArrayExpression(); final PsiExpression indexExpression2 = arrayAccessExpression1.getIndexExpression(); final PsiExpression indexExpression1 = arrayAccessExpression2.getIndexExpression(); final Match arrayExpressionEq = expressionsMatch(arrayExpression2, arrayExpression1); if (arrayExpressionEq != EXACT_MATCH) { return EXACT_MISMATCH; } return expressionsMatch(indexExpression1, indexExpression2).partialIfExactMismatch(indexExpression1, indexExpression2); } protected Match prefixExpressionsMatch(@NotNull PsiPrefixExpression prefixExpression1, @NotNull PsiPrefixExpression prefixExpression2) { final IElementType tokenType1 = prefixExpression1.getOperationTokenType(); if (!tokenType1.equals(prefixExpression2.getOperationTokenType())) { return EXACT_MISMATCH; } final PsiExpression operand1 = prefixExpression1.getOperand(); final PsiExpression operand2 = prefixExpression2.getOperand(); return expressionsMatch(operand1, operand2); } protected Match postfixExpressionsMatch(@NotNull PsiPostfixExpression postfixExpression1, @NotNull PsiPostfixExpression postfixExpression2) { final IElementType tokenType1 = postfixExpression1.getOperationTokenType(); if (!tokenType1.equals(postfixExpression2.getOperationTokenType())) { return EXACT_MISMATCH; } final PsiExpression operand1 = postfixExpression1.getOperand(); final PsiExpression operand2 = postfixExpression2.getOperand(); return expressionsMatch(operand1, operand2); } protected Match polyadicExpressionsMatch(@NotNull PsiPolyadicExpression polyadicExpression1, @NotNull PsiPolyadicExpression polyadicExpression2) { final IElementType tokenType1 = polyadicExpression1.getOperationTokenType(); final IElementType tokenType2 = polyadicExpression2.getOperationTokenType(); if (!tokenType1.equals(tokenType2)) { return EXACT_MISMATCH; } final PsiExpression[] operands1 = polyadicExpression1.getOperands(); final PsiExpression[] operands2 = polyadicExpression2.getOperands(); return expressionsAreEquivalent(operands1, operands2); } protected Match binaryExpressionsMatch(@NotNull PsiBinaryExpression binaryExpression1, @NotNull PsiBinaryExpression binaryExpression2) { final IElementType tokenType1 = binaryExpression1.getOperationTokenType(); final IElementType tokenType2 = binaryExpression2.getOperationTokenType(); final PsiExpression left1 = binaryExpression1.getLOperand(); final PsiExpression left2 = binaryExpression2.getLOperand(); final PsiExpression right1 = binaryExpression1.getROperand(); final PsiExpression right2 = binaryExpression2.getROperand(); if (!tokenType1.equals(tokenType2)) { // process matches like "a < b" and "b > a" DfaRelationValue.RelationType rel1 = DfaRelationValue.RelationType.fromElementType(tokenType1); DfaRelationValue.RelationType rel2 = DfaRelationValue.RelationType.fromElementType(tokenType2); if(rel1 != null && rel2 != null && rel1.getFlipped() == rel2) { return expressionsAreEquivalent(new PsiExpression[] {left1, right1}, new PsiExpression[] {right2, left2}); } return EXACT_MISMATCH; } return expressionsAreEquivalent(new PsiExpression[] {left1, right1}, new PsiExpression[] {left2, right2}); } protected Match assignmentExpressionsMatch(@NotNull PsiAssignmentExpression assignmentExpression1, @NotNull PsiAssignmentExpression assignmentExpression2) { final IElementType tokenType1 = assignmentExpression1.getOperationTokenType(); if (!tokenType1.equals(assignmentExpression2.getOperationTokenType())) { return EXACT_MISMATCH; } final PsiExpression lhs1 = assignmentExpression1.getLExpression(); final PsiExpression lhs2 = assignmentExpression2.getLExpression(); final PsiExpression rhs1 = assignmentExpression1.getRExpression(); final PsiExpression rhs2 = assignmentExpression2.getRExpression(); final Match leftEq = expressionsMatch(lhs1, lhs2); final Match rightEq = expressionsMatch(rhs1, rhs2); return getComplexElementDecision(leftEq, rightEq, lhs1, lhs2, rhs1, rhs2); } protected Match conditionalExpressionsMatch(@NotNull PsiConditionalExpression conditionalExpression1, @NotNull PsiConditionalExpression conditionalExpression2) { final PsiExpression condition1 = conditionalExpression1.getCondition(); final PsiExpression condition2 = conditionalExpression2.getCondition(); final PsiExpression thenExpression1 = conditionalExpression1.getThenExpression(); final PsiExpression thenExpression2 = conditionalExpression2.getThenExpression(); final PsiExpression elseExpression1 = conditionalExpression1.getElseExpression(); final PsiExpression elseExpression2 = conditionalExpression2.getElseExpression(); if (expressionsMatch(condition1, condition2) == EXACT_MATCH && expressionsMatch(thenExpression1, thenExpression2) == EXACT_MATCH && expressionsMatch(elseExpression1, elseExpression2) == EXACT_MATCH) { return EXACT_MATCH; } return EXACT_MISMATCH; } protected Match expressionsAreEquivalent(@Nullable PsiExpression[] expressions1, @Nullable PsiExpression[] expressions2) { if (expressions1 == null && expressions2 == null) { return EXACT_MATCH; } if (expressions1 == null || expressions2 == null) { return EXACT_MISMATCH; } if (expressions1.length != expressions2.length) { return EXACT_MISMATCH; } Match incompleteMatch = null; for (int i = 0; i < expressions1.length; i++) { final Match match = expressionsMatch(expressions1[i], expressions2[i]); if (incompleteMatch == null && match.isPartialMatch()) { incompleteMatch = match; } else if (!match.isExactMatch()) { if (incompleteMatch != null) { return EXACT_MISMATCH; } incompleteMatch = match.partialIfExactMismatch(expressions1[i], expressions2[i]); } } return incompleteMatch == null ? EXACT_MATCH : incompleteMatch; } @NotNull private static Match getComplexElementDecision(Match equivalence1, Match equivalence2, PsiElement left1, PsiElement right1, PsiElement left2, PsiElement right2) { if (equivalence2 == EXACT_MATCH) { if (equivalence1 == EXACT_MATCH) { return EXACT_MATCH; } else if (equivalence1 == EXACT_MISMATCH) { return new Match(left1, right1); } } else if (equivalence2 == EXACT_MISMATCH) { if (equivalence1 == EXACT_MISMATCH) { return EXACT_MISMATCH; } else if (equivalence1 == EXACT_MATCH) { return new Match(left2, right2); } } return EXACT_MISMATCH; } }