/* * Copyright 2007-2008 Dave Griffith * * 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 org.jetbrains.plugins.groovy.codeInspection.utils; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiType; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseLabel; import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection; import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForClause; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.arithmetic.GrRangeExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; @SuppressWarnings({"OverlyComplexMethod", "MethodWithMultipleLoops", "OverlyComplexMethod", "OverlyLongMethod", "SwitchStatementWithTooManyBranches", "SwitchStatement", "OverlyComplexClass", "ClassWithTooManyMethods"}) public class EquivalenceChecker { private EquivalenceChecker() { super(); } private static final int LITERAL_EXPRESSION = 1; private static final int REFERENCE_EXPRESSION = 3; private static final int CALL_EXPRESSION = 5; private static final int NEW_EXPRESSION = 6; private static final int ARRAY_LITERAL_EXPRESSION = 7; private static final int CLOSABLE_BLOCK_EXPRESSION = 8; private static final int PREFIX_EXPRESSION = 10; private static final int POSTFIX_EXPRESSION = 11; private static final int BINARY_EXPRESSION = 12; private static final int CONDITIONAL_EXPRESSION = 13; private static final int ASSIGNMENT_EXPRESSION = 14; private static final int ELVIS_EXPRESSION = 15; private static final int TYPE_CAST_EXPRESSION = 16; private static final int SAFE_CAST_EXPRESSION = 17; private static final int INSTANCEOF_EXPRESSION = 18; private static final int RANGE_EXPRESSION = 19; private static final int LIST_OR_MAP_EXPRESSION = 20; private static final int INDEX_EXPRESSION = 21; private static final int PROPERTY_SELECTION_EXPRESSION = 22; private static final int BLOCK_STATEMENT = 1; private static final int BREAK_STATEMENT = 2; private static final int CONTINUE_STATEMENT = 3; private static final int VAR_STATEMENT = 4; private static final int EMPTY_STATEMENT = 6; private static final int EXPRESSION_STATEMENT = 8; private static final int FOR_STATEMENT = 9; private static final int IF_STATEMENT = 10; private static final int RETURN_STATEMENT = 12; private static final int SWITCH_STATEMENT = 14; private static final int THROW_STATEMENT = 16; private static final int TRY_STATEMENT = 17; private static final int WHILE_STATEMENT = 18; private static final int SYNCHRONIZED_STATEMENT = 19; private static final int ASSERT_STATEMENT = 20; private static final int APPLICATION_STATEMENT = 21; public static boolean statementsAreEquivalent(@Nullable GrStatement exp1, @Nullable GrStatement exp2) { if (exp1 == null && exp2 == null) { return true; } if (exp1 == null || exp2 == null) { return false; } final int type1 = getStatementType(exp1); final int type2 = getStatementType(exp2); if (type1 != type2) { return false; } switch (type1) { case BLOCK_STATEMENT: return blockStatementsAreEquivalent((GrBlockStatement) exp1, (GrBlockStatement) exp2); case BREAK_STATEMENT: return true; case CONTINUE_STATEMENT: return true; case VAR_STATEMENT: return varStatementsAreEquivalent((GrVariableDeclaration) exp1, (GrVariableDeclaration) exp2); case EMPTY_STATEMENT: return true; case APPLICATION_STATEMENT: return applicationStatementsAreEquivalent((GrApplicationStatement) exp1, (GrApplicationStatement) exp2); case EXPRESSION_STATEMENT: return expressionStatementsAreEquivalent((GrExpression) exp1, (GrExpression) exp2); case FOR_STATEMENT: return forInStatementsAreEquivalent((GrForStatement) exp1, (GrForStatement) exp2); case IF_STATEMENT: return ifStatementsAreEquivalent((GrIfStatement) exp1, (GrIfStatement) exp2); case RETURN_STATEMENT: return returnStatementsAreEquivalent((GrReturnStatement) exp1, (GrReturnStatement) exp2); case SWITCH_STATEMENT: return switchStatementsAreEquivalent((GrSwitchStatement) exp1, (GrSwitchStatement) exp2); case THROW_STATEMENT: return throwStatementsAreEquivalent((GrThrowStatement) exp1, (GrThrowStatement) exp2); case TRY_STATEMENT: return tryStatementsAreEquivalent((GrTryCatchStatement) exp1, (GrTryCatchStatement) exp2); case WHILE_STATEMENT: return whileStatementsAreEquivalent((GrWhileStatement) exp1, (GrWhileStatement) exp2); case SYNCHRONIZED_STATEMENT: return synchronizedStatementsAreEquivalent((GrSynchronizedStatement) exp1, (GrSynchronizedStatement) exp2); case ASSERT_STATEMENT: return assertStatementsAreEquivalent((GrAssertStatement) exp1, (GrAssertStatement) exp2); default: return false; } } private static boolean applicationStatementsAreEquivalent(GrApplicationStatement statement1, GrApplicationStatement statement2) { final GrExpression funExpression1 = statement1.getInvokedExpression(); final GrExpression funExpression2 = statement2.getInvokedExpression(); if (!expressionsAreEquivalent(funExpression1, funExpression2)) { return false; } final GrArgumentList argumentList1 = statement1.getArgumentList(); final GrArgumentList argumentList2 = statement2.getArgumentList(); final GrExpression[] args1 = argumentList1.getExpressionArguments(); final GrExpression[] args2 = argumentList2.getExpressionArguments(); if (!expressionListsAreEquivalent(args1, args2)) { return false; } final GrNamedArgument[] namedArgs1 = argumentList1.getNamedArguments(); final GrNamedArgument[] namedArgs2 = argumentList2.getNamedArguments(); if (!namedArgumentListsAreEquivalent(namedArgs1, namedArgs2)) { return false; } return true; } private static boolean assertStatementsAreEquivalent(GrAssertStatement statement1, GrAssertStatement statement2) { return expressionsAreEquivalent(statement1.getAssertion(), statement2.getAssertion()) && expressionsAreEquivalent(statement1.getErrorMessage(), statement2.getErrorMessage()); } private static boolean synchronizedStatementsAreEquivalent(GrSynchronizedStatement statement1, GrSynchronizedStatement statement2) { return expressionsAreEquivalent(statement1.getMonitor(), statement2.getMonitor()) && openBlocksAreEquivalent(statement1.getBody(), statement2.getBody()); } private static boolean varStatementsAreEquivalent(@NotNull GrVariableDeclaration statement1, @NotNull GrVariableDeclaration statement2) { final GrVariable[] variables1 = statement1.getVariables(); final GrVariable[] variables2 = statement2.getVariables(); if (variables1.length != variables2.length) { return false; } for (int i = 0; i < variables2.length; i++) { if (!variablesAreEquivalent(variables1[i], variables2[i])) { return false; } } return true; } private static boolean variablesAreEquivalent(@NotNull GrVariable var1, @NotNull GrVariable var2) { final GrExpression initializer1 = var1.getInitializerGroovy(); final GrExpression initializer2 = var2.getInitializerGroovy(); if (!expressionsAreEquivalent(initializer1, initializer2)) { return false; } final PsiType type1 = var1.getType(); final PsiType type2 = var2.getType(); if (!typesAreEquivalent(type1, type2)) { return false; } final String name1 = var1.getName(); final String name2 = var2.getName(); return name1.equals(name2); } private static boolean tryStatementsAreEquivalent(@NotNull GrTryCatchStatement statement1, @NotNull GrTryCatchStatement statement2) { final GrOpenBlock tryBlock1 = statement1.getTryBlock(); final GrOpenBlock tryBlock2 = statement2.getTryBlock(); if (!openBlocksAreEquivalent(tryBlock1, tryBlock2)) { return false; } final GrFinallyClause finallyBlock1 = statement1.getFinallyClause(); final GrFinallyClause finallyBlock2 = statement2.getFinallyClause(); if (finallyBlock1 != null) { if (finallyBlock2 == null || !openBlocksAreEquivalent(finallyBlock1.getBody(), finallyBlock2.getBody())) { return false; } } else if (finallyBlock2 != null) { return false; } final GrCatchClause[] catchBlocks1 = statement1.getCatchClauses(); final GrCatchClause[] catchBlocks2 = statement2.getCatchClauses(); if (catchBlocks1.length != catchBlocks2.length) { return false; } for (int i = 0; i < catchBlocks2.length; i++) { if (!catchClausesAreEquivalent(catchBlocks1[i], catchBlocks2[i])) { return false; } } return true; } private static boolean catchClausesAreEquivalent(GrCatchClause clause1, GrCatchClause clause2) { return parametersAreEquivalent(clause1.getParameter(), clause2.getParameter()) && openBlocksAreEquivalent(clause1.getBody(), clause2.getBody()); } private static boolean parametersAreEquivalent(@Nullable GrParameter parameter1, @Nullable GrParameter parameter2) { if (parameter1 == null || parameter2 == null) { return false; } final PsiType type1 = parameter1.getType(); final PsiType type2 = parameter2.getType(); if (!typesAreEquivalent(type1, type2)) { return false; } final String name1 = parameter1.getName(); final String name2 = parameter2.getName(); return name1.equals(name2); } private static boolean typesAreEquivalent(@Nullable PsiType type1, @Nullable PsiType type2) { if (type1 == null) { return type2 == null; } if (type2 == null) { return false; } return type1.equals(type2); } private static boolean whileStatementsAreEquivalent(@NotNull GrWhileStatement statement1, @NotNull GrWhileStatement statement2) { final GrExpression condition1 = (GrExpression) statement1.getCondition(); final GrExpression condition2 = (GrExpression) statement2.getCondition(); final GrStatement body1 = statement1.getBody(); final GrStatement body2 = statement2.getBody(); return expressionsAreEquivalent(condition1, condition2) && statementsAreEquivalent(body1, body2); } private static boolean forInStatementsAreEquivalent(@NotNull GrForStatement statement1, @NotNull GrForStatement statement2) { final GrForClause clause1 = statement1.getClause(); final GrForClause clause2 = statement2.getClause(); if (!forClausesAreEquivalent(clause1, clause2)) { return false; } final GrStatement body1 = statement1.getBody(); final GrStatement body2 = statement2.getBody(); return statementsAreEquivalent(body1, body2); } private static boolean forClausesAreEquivalent(@Nullable GrForClause statement1, @Nullable GrForClause statement2) { if (statement1 == null && statement2 == null) return true; if (statement1 == null || statement2 == null) return false; final GrVariable var1 = statement1.getDeclaredVariable(); final GrVariable var2 = statement2.getDeclaredVariable(); if (var1 == null && var2 == null) return true; if (var1 == null || var2 == null) return false; return variablesAreEquivalent(var1, var2); } private static boolean switchStatementsAreEquivalent(@NotNull GrSwitchStatement statement1, @NotNull GrSwitchStatement statement2) { final GrExpression switchExpression1 = statement1.getCondition(); final GrExpression switchExpression2 = statement2.getCondition(); if (!expressionsAreEquivalent(switchExpression1, switchExpression2)) { return false; } final GrCaseSection[] clauses1 = statement1.getCaseSections(); final GrCaseSection[] clauses2 = statement2.getCaseSections(); if (clauses1.length != clauses2.length) { return false; } for (int i = 0; i < clauses1.length; i++) { final GrCaseSection clause1 = clauses1[i]; final GrCaseSection clause2 = clauses2[i]; if (!caseClausesAreEquivalent(clause1, clause2)) { return false; } } return true; } private static boolean caseClausesAreEquivalent(GrCaseSection clause1, GrCaseSection clause2) { final GrCaseLabel[] label1 = clause1.getCaseLabels(); final GrCaseLabel[] label2 = clause2.getCaseLabels(); if (label1.length != label2.length) return false; for (int i = 0; i < label1.length; i++) { GrCaseLabel l1 = label1[i]; GrCaseLabel l2 = label2[i]; if (!expressionsAreEquivalent(l1.getValue(), l2.getValue())) { return false; } } final GrStatement[] statements1 = clause1.getStatements(); final GrStatement[] statements2 = clause2.getStatements(); if (statements1.length != statements2.length) { return false; } for (int i = 0; i < statements1.length; i++) { if (!statementsAreEquivalent(statements1[i], statements2[i])) { return false; } } return false; } private static boolean blockStatementsAreEquivalent(@NotNull GrBlockStatement statement1, @NotNull GrBlockStatement statement2) { final GrOpenBlock block1 = statement1.getBlock(); final GrOpenBlock block2 = statement2.getBlock(); return openBlocksAreEquivalent(block1, block2); } private static boolean openBlocksAreEquivalent(@Nullable GrOpenBlock block1, @Nullable GrOpenBlock block2) { if (block1 == null || block2 == null) return false; final GrStatement[] statements1 = block1.getStatements(); final GrStatement[] statements2 = block2.getStatements(); if (statements1.length != statements2.length) { return false; } for (int i = 0; i < statements1.length; i++) { if (!statementsAreEquivalent(statements1[i], statements2[i])) { return false; } } return true; } private static boolean ifStatementsAreEquivalent(@NotNull GrIfStatement statement1, @NotNull GrIfStatement statement2) { final GrExpression condition1 = statement1.getCondition(); final GrExpression condition2 = statement2.getCondition(); final GrStatement thenBranch1 = statement1.getThenBranch(); final GrStatement thenBranch2 = statement2.getThenBranch(); final GrStatement elseBranch1 = statement1.getElseBranch(); final GrStatement elseBranch2 = statement2.getElseBranch(); return expressionsAreEquivalent(condition1, condition2) && statementsAreEquivalent(thenBranch1, thenBranch2) && statementsAreEquivalent(elseBranch1, elseBranch2); } private static boolean expressionStatementsAreEquivalent(@NotNull GrExpression statement1, @NotNull GrExpression statement2) { return expressionsAreEquivalent(statement1, statement2); } private static boolean returnStatementsAreEquivalent(@NotNull GrReturnStatement statement1, @NotNull GrReturnStatement statement2) { final GrExpression returnValue1 = statement1.getReturnValue(); final GrExpression returnValue2 = statement2.getReturnValue(); return expressionsAreEquivalent(returnValue1, returnValue2); } private static boolean throwStatementsAreEquivalent(@NotNull GrThrowStatement statement1, @NotNull GrThrowStatement statement2) { final GrExpression exception1 = statement1.getException(); final GrExpression exception2 = statement2.getException(); return expressionsAreEquivalent(exception1, exception2); } @SuppressWarnings({"ConstantConditions"}) public static boolean expressionsAreEquivalent(@Nullable GrExpression exp1, @Nullable GrExpression exp2) { if (exp1 == null && exp2 == null) { return true; } if (exp1 == null || exp2 == null) { return false; } GrExpression expToCompare1 = (GrExpression)PsiUtil.skipParentheses(exp1, false); GrExpression expToCompare2 = (GrExpression)PsiUtil.skipParentheses(exp2, false); final int type1 = getExpressionType(expToCompare1); final int type2 = getExpressionType(expToCompare2); if (type1 != type2) { return false; } switch (type1) { case LITERAL_EXPRESSION: case REFERENCE_EXPRESSION: final String text1 = expToCompare1.getText(); final String text2 = expToCompare2.getText(); return text1.equals(text2); case CALL_EXPRESSION: return methodCallExpressionsAreEquivalent((GrMethodCall) expToCompare1, (GrMethodCall) expToCompare2); case NEW_EXPRESSION: return newExpressionsAreEquivalent((GrNewExpression) expToCompare1, (GrNewExpression) expToCompare2); case ARRAY_LITERAL_EXPRESSION: return arrayDeclarationsAreEquivalent((GrArrayDeclaration) expToCompare1, (GrArrayDeclaration) expToCompare2); case PREFIX_EXPRESSION: return prefixExpressionsAreEquivalent((GrUnaryExpression) expToCompare1, (GrUnaryExpression) expToCompare2); case POSTFIX_EXPRESSION: return postfixExpressionsAreEquivalent((GrUnaryExpression) expToCompare1, (GrUnaryExpression) expToCompare2); case BINARY_EXPRESSION: return binaryExpressionsAreEquivalent((GrBinaryExpression) expToCompare1, (GrBinaryExpression) expToCompare2); case ASSIGNMENT_EXPRESSION: return assignmentExpressionsAreEquivalent((GrAssignmentExpression) expToCompare1, (GrAssignmentExpression) expToCompare2); case CONDITIONAL_EXPRESSION: return conditionalExpressionsAreEquivalent((GrConditionalExpression) expToCompare1, (GrConditionalExpression) expToCompare2); case ELVIS_EXPRESSION: return elvisExpressionsAreEquivalent((GrElvisExpression) expToCompare1, (GrElvisExpression) expToCompare2); case RANGE_EXPRESSION: return rangeExpressionsAreEquivalent((GrRangeExpression) expToCompare1, (GrRangeExpression) expToCompare2); case TYPE_CAST_EXPRESSION: return typecastExpressionsAreEquivalent((GrTypeCastExpression) expToCompare1, (GrTypeCastExpression) expToCompare2); case SAFE_CAST_EXPRESSION: return safeCastExpressionsAreEquivalent((GrSafeCastExpression)expToCompare1, (GrSafeCastExpression)expToCompare2); case INSTANCEOF_EXPRESSION: return instanceofExpressionsAreEquivalent((GrInstanceOfExpression) expToCompare1, (GrInstanceOfExpression) expToCompare2); case INDEX_EXPRESSION: return indexExpressionsAreEquivalent((GrIndexProperty) expToCompare1, (GrIndexProperty) expToCompare2); case LIST_OR_MAP_EXPRESSION: return listOrMapExpressionsAreEquivalent((GrListOrMap) expToCompare1, (GrListOrMap) expToCompare2); case CLOSABLE_BLOCK_EXPRESSION: return closableBlockExpressionsAreEquivalent((GrClosableBlock) expToCompare1, (GrClosableBlock) expToCompare2); case PROPERTY_SELECTION_EXPRESSION: return textOfExpressionsIsEquivalent(expToCompare1, expToCompare2); // todo default: return false; } } private static boolean textOfExpressionsIsEquivalent(GrExpression expToCompare1, GrExpression expToCompare2) { final String text1 = expToCompare1.getText(); final String text2 = expToCompare2.getText(); return text1.equals(text2); } private static boolean closableBlockExpressionsAreEquivalent(GrClosableBlock closableBlock1, GrClosableBlock closableBlock2) { final GrStatement[] statements1 = closableBlock1.getStatements(); final GrStatement[] statements2 = closableBlock2.getStatements(); if (statements1.length != statements2.length) { return false; } for (int i = 0; i < statements1.length; i++) { if (!statementsAreEquivalent(statements1[i], statements2[i])) { return false; } } GrParameter[] parameters1 = closableBlock1.getParameters(); GrParameter[] parameters2 = closableBlock2.getParameters(); return parametersAreEquivalent(parameters1, parameters2); } private static boolean parametersAreEquivalent(GrParameter[] parameters1, GrParameter[] parameters2) { if (parameters1.length != parameters2.length) return false; for (int i = 0; i < parameters1.length; i++) { if (!parametersAreEquivalent(parameters1[i], parameters2[i])) return false; } return true; } private static boolean listOrMapExpressionsAreEquivalent(GrListOrMap expression1, GrListOrMap expression2) { return expressionListsAreEquivalent(expression1.getInitializers(), expression2.getInitializers()) && namedArgumentListsAreEquivalent(expression1.getNamedArguments(), expression2.getNamedArguments()); } private static boolean arrayDeclarationsAreEquivalent(GrArrayDeclaration expression1, GrArrayDeclaration expression2) { final int count1 = expression1.getArrayCount(); final int count2 = expression2.getArrayCount(); if (count1 != count2) { return false; } final GrExpression[] bounds1 = expression1.getBoundExpressions(); final GrExpression[] bounds2 = expression2.getBoundExpressions(); return expressionListsAreEquivalent(bounds1, bounds2); } private static boolean instanceofExpressionsAreEquivalent(GrInstanceOfExpression expression1, GrInstanceOfExpression expression2) { final GrExpression operand1 = expression1.getOperand(); final GrExpression operand2 = expression2.getOperand(); if (!expressionsAreEquivalent(operand1, operand2)) { return false; } GrTypeElement typeElement1 = expression1.getTypeElement(); GrTypeElement typeElement2 = expression2.getTypeElement(); if (typeElement1 == null || typeElement2 == null) return false; final PsiType type1 = typeElement1.getType(); final PsiType type2 = typeElement2.getType(); return typesAreEquivalent(type1, type2); } private static boolean indexExpressionsAreEquivalent(GrIndexProperty expression1, GrIndexProperty expression2) { return expressionsAreEquivalent(expression1.getInvokedExpression(), expression2.getInvokedExpression()) && argumentListsAreEquivalent(expression1.getArgumentList(), expression2.getArgumentList()); } private static boolean typecastExpressionsAreEquivalent(GrTypeCastExpression expression1, GrTypeCastExpression expression2) { final GrExpression operand1 = expression1.getOperand(); final GrExpression operand2 = expression2.getOperand(); if (!expressionsAreEquivalent(operand1, operand2)) { return false; } final PsiType type1 = expression1.getCastTypeElement().getType(); final PsiType type2 = expression2.getCastTypeElement().getType(); return typesAreEquivalent(type1, type2); } private static boolean safeCastExpressionsAreEquivalent(GrSafeCastExpression expression1, GrSafeCastExpression expression2) { final GrExpression operand1 = expression1.getOperand(); final GrExpression operand2 = expression2.getOperand(); if (!expressionsAreEquivalent(operand1, operand2)) { return false; } final GrTypeElement typeElement1 = expression1.getCastTypeElement(); final GrTypeElement typeElement2 = expression2.getCastTypeElement(); final PsiType safe1 = typeElement1 == null ? null : typeElement1.getType(); final PsiType safe2 = typeElement2 == null ? null : typeElement2.getType(); return typesAreEquivalent(safe1, safe2); } private static boolean methodCallExpressionsAreEquivalent(@NotNull GrMethodCall methodExp1, @NotNull GrMethodCall methodExp2) { final GrExpression methodExpression1 = methodExp1.getInvokedExpression(); final GrExpression methodExpression2 = methodExp2.getInvokedExpression(); if (!expressionsAreEquivalent(methodExpression1, methodExpression2)) { return false; } final GrClosableBlock[] closures1 = methodExp1.getClosureArguments(); final GrClosableBlock[] closures2 = methodExp2.getClosureArguments(); if (!expressionListsAreEquivalent(closures1, closures2)) { return false; } return argumentListsAreEquivalent(methodExp1.getArgumentList(), methodExp2.getArgumentList()); } private static boolean argumentListsAreEquivalent(@Nullable GrArgumentList list1, @Nullable GrArgumentList list2) { if (list1 == null && list2 == null) { return true; } if (list1 == null || list2 == null) { return false; } final GrExpression[] args1 = list1.getExpressionArguments(); final GrExpression[] args2 = list2.getExpressionArguments(); if (!expressionListsAreEquivalent(args1, args2)) { return false; } final GrNamedArgument[] namedArgs1 = list1.getNamedArguments(); final GrNamedArgument[] namedArgs2 = list2.getNamedArguments(); if (!namedArgumentListsAreEquivalent(namedArgs1, namedArgs2)) { return false; } return true; } private static boolean namedArgumentListsAreEquivalent(GrNamedArgument[] namedArgs1, GrNamedArgument[] namedArgs2) { if (namedArgs1.length != namedArgs2.length) { return false; } for (GrNamedArgument arg1 : namedArgs1) { final GrArgumentLabel label1 = arg1.getLabel(); if (label1 == null) { return false; } final String name1 = label1.getName(); boolean found = false; final GrExpression expression1 = arg1.getExpression(); for (GrNamedArgument arg2 : namedArgs2) { final GrArgumentLabel label2 = arg2.getLabel(); if (label2 == null) { return false; } final String name2 = label2.getName(); final GrExpression expression2 = arg2.getExpression(); if (name1 == null) { if (name2 == null && expressionsAreEquivalent(((GrExpression)label1.getNameElement()), (GrExpression)label2.getNameElement()) && expressionsAreEquivalent(expression1, expression2)) { found = true; break; } } else if (name1.equals(name2) && expressionsAreEquivalent(expression1, expression2)) { found = true; break; } } if (!found) { return false; } } return true; } private static boolean newExpressionsAreEquivalent(@NotNull GrNewExpression newExp1, @NotNull GrNewExpression newExp2) { final PsiMethod constructor1 = newExp1.resolveMethod(); final PsiMethod constructor2 = newExp2.resolveMethod(); if (constructor1 == null || constructor2 == null || !constructor1.equals(constructor2)) { return false; } return argumentListsAreEquivalent(newExp1.getArgumentList(), newExp2.getArgumentList()); } private static boolean prefixExpressionsAreEquivalent(@NotNull GrUnaryExpression prefixExp1, @NotNull GrUnaryExpression prefixExp2) { final IElementType sign1 = prefixExp1.getOperationTokenType(); final IElementType sign2 = prefixExp2.getOperationTokenType(); if (sign1 != sign2) { return false; } final GrExpression operand1 = prefixExp1.getOperand(); final GrExpression operand2 = prefixExp2.getOperand(); return expressionsAreEquivalent(operand1, operand2); } private static boolean postfixExpressionsAreEquivalent(@NotNull GrUnaryExpression postfixExp1, @NotNull GrUnaryExpression postfixExp2) { final IElementType sign1 = postfixExp1.getOperationTokenType(); final IElementType sign2 = postfixExp2.getOperationTokenType(); if (sign1 != sign2) { return false; } final GrExpression operand1 = postfixExp1.getOperand(); final GrExpression operand2 = postfixExp2.getOperand(); return expressionsAreEquivalent(operand1, operand2); } private static boolean binaryExpressionsAreEquivalent(@NotNull GrBinaryExpression binaryExp1, @NotNull GrBinaryExpression binaryExp2) { final IElementType sign1 = binaryExp1.getOperationTokenType(); final IElementType sign2 = binaryExp2.getOperationTokenType(); if (sign1 != sign2) { return false; } final GrExpression lhs1 = binaryExp1.getLeftOperand(); final GrExpression lhs2 = binaryExp2.getLeftOperand(); final GrExpression rhs1 = binaryExp1.getRightOperand(); final GrExpression rhs2 = binaryExp2.getRightOperand(); return expressionsAreEquivalent(lhs1, lhs2) && expressionsAreEquivalent(rhs1, rhs2); } private static boolean rangeExpressionsAreEquivalent(@NotNull GrRangeExpression rangeExp1, @NotNull GrRangeExpression rangeExp2) { return expressionsAreEquivalent(rangeExp1.getLeftOperand(), rangeExp2.getLeftOperand()) && expressionsAreEquivalent(rangeExp1.getRightOperand(), rangeExp2.getRightOperand()) && isInclusive(rangeExp1) == isInclusive(rangeExp2); } private static boolean isInclusive(GrRangeExpression range) { for (PsiElement child : range.getChildren()) { if ("..".equals(child.getText())) { return true; } } return false; } private static boolean assignmentExpressionsAreEquivalent(@NotNull GrAssignmentExpression assignExp1, @NotNull GrAssignmentExpression assignExp2) { final IElementType sign1 = assignExp1.getOperationTokenType(); final IElementType sign2 = assignExp2.getOperationTokenType(); if (sign1 != sign2) { return false; } final GrExpression lhs1 = assignExp1.getLValue(); final GrExpression lhs2 = assignExp2.getLValue(); final GrExpression rhs1 = assignExp1.getRValue(); final GrExpression rhs2 = assignExp2.getRValue(); return expressionsAreEquivalent(lhs1, lhs2) && expressionsAreEquivalent(rhs1, rhs2); } private static boolean conditionalExpressionsAreEquivalent(@NotNull GrConditionalExpression condExp1, @NotNull GrConditionalExpression condExp2) { final GrExpression condition1 = condExp1.getCondition(); final GrExpression condition2 = condExp2.getCondition(); final GrExpression thenExpression1 = condExp1.getThenBranch(); final GrExpression thenExpression2 = condExp2.getThenBranch(); final GrExpression elseExpression1 = condExp1.getElseBranch(); final GrExpression elseExpression2 = condExp2.getElseBranch(); return expressionsAreEquivalent(condition1, condition2) && expressionsAreEquivalent(thenExpression1, thenExpression2) && expressionsAreEquivalent(elseExpression1, elseExpression2); } private static boolean elvisExpressionsAreEquivalent(@NotNull GrElvisExpression condExp1, @NotNull GrElvisExpression condExp2) { final GrExpression condition1 = condExp1.getCondition(); final GrExpression condition2 = condExp2.getCondition(); final GrExpression elseExpression1 = condExp1.getElseBranch(); final GrExpression elseExpression2 = condExp2.getElseBranch(); return expressionsAreEquivalent(condition1, condition2) && expressionsAreEquivalent(elseExpression1, elseExpression2); } private static boolean expressionListsAreEquivalent(@Nullable GrExpression[] expressions1, @Nullable GrExpression[] expressions2) { if (expressions1 == null && expressions2 == null) { return true; } if (expressions1 == null || expressions2 == null) { return false; } if (expressions1.length != expressions2.length) { return false; } for (int i = 0; i < expressions1.length; i++) { if (!expressionsAreEquivalent(expressions1[i], expressions2[i])) { return false; } } return true; } private static int getExpressionType(@Nullable GrExpression exp) { if (exp instanceof GrArrayDeclaration) { return ARRAY_LITERAL_EXPRESSION; } if (exp instanceof GrLiteral) { return LITERAL_EXPRESSION; } if (exp instanceof GrReferenceExpression) { return REFERENCE_EXPRESSION; } if (exp instanceof GrTypeCastExpression) { return TYPE_CAST_EXPRESSION; } if (exp instanceof GrSafeCastExpression) { return SAFE_CAST_EXPRESSION; } if (exp instanceof GrInstanceOfExpression) { return INSTANCEOF_EXPRESSION; } if (exp instanceof GrNewExpression) { return NEW_EXPRESSION; } if (exp instanceof GrMethodCall) { return CALL_EXPRESSION; } if (exp instanceof GrUnaryExpression) { return ((GrUnaryExpression)exp).isPostfix() ? POSTFIX_EXPRESSION : PREFIX_EXPRESSION; } if (exp instanceof GrAssignmentExpression) { return ASSIGNMENT_EXPRESSION; } if (exp instanceof GrRangeExpression) { return RANGE_EXPRESSION; } if (exp instanceof GrBinaryExpression) { return BINARY_EXPRESSION; } if (exp instanceof GrElvisExpression) { return ELVIS_EXPRESSION; } if (exp instanceof GrConditionalExpression) { return CONDITIONAL_EXPRESSION; } if (exp instanceof GrIndexProperty) { return INDEX_EXPRESSION; } if (exp instanceof GrListOrMap) { return LIST_OR_MAP_EXPRESSION; } if (exp instanceof GrClosableBlock) { return CLOSABLE_BLOCK_EXPRESSION; } return -1; // Type of expression can be defined in third party plugins. See issue #IDEA-59846 } private static int getStatementType(@Nullable GrStatement statement) { if (statement instanceof GrBlockStatement) { return BLOCK_STATEMENT; } if (statement instanceof GrBreakStatement) { return BREAK_STATEMENT; } if (statement instanceof GrContinueStatement) { return CONTINUE_STATEMENT; } if (statement instanceof GrVariableDeclaration) { return VAR_STATEMENT; } if (statement instanceof GrApplicationStatement) { return APPLICATION_STATEMENT; } if (statement instanceof GrExpression) { return EXPRESSION_STATEMENT; } if (statement instanceof GrForStatement) { return FOR_STATEMENT; } if (statement instanceof GrIfStatement) { return IF_STATEMENT; } if (statement instanceof GrReturnStatement) { return RETURN_STATEMENT; } if (statement instanceof GrSwitchStatement) { return SWITCH_STATEMENT; } if (statement instanceof GrThrowStatement) { return THROW_STATEMENT; } if (statement instanceof GrTryCatchStatement) { return TRY_STATEMENT; } if (statement instanceof GrWhileStatement) { return WHILE_STATEMENT; } if (statement instanceof GrSynchronizedStatement) { return SYNCHRONIZED_STATEMENT; } if (statement instanceof GrAssertStatement) { return ASSERT_STATEMENT; } return -1; // Type of expression can be defined in third party plugins. See issue #IDEA-59846 } }