void (PsiElement element, PsiMethod method, @Nullable String thisVariableName, boolean tailCallIsContainedInLoop, boolean isReturnAtTheEndOfWhileLoop, @NonNls StringBuilder out) { PsiMethodCallExpression tailCall; if (isImplicitCallOnThis(element, method)) { if (thisVariableName != null) { out.append(thisVariableName).append('.'); } out.append(element.getText()); } else if (element instanceof PsiQualifiedExpression) { out.append(thisVariableName == null ? element.getText() : thisVariableName); } else if ((tailCall = getTailCall(element, method)) != null) { assert element instanceof PsiStatement; final PsiExpression[] arguments = tailCall.getArgumentList().getExpressions(); final PsiParameter[] parameters = method.getParameterList().getParameters(); final boolean isInBlock = element.getParent() instanceof PsiCodeBlock; if (!isInBlock) { out.append('{'); } else { // remove tabs and spaces at the end int index = out.length() - 1; char c = out.charAt(index); while (c == ' ' || c == '\t') c = out.charAt(--index); out.delete(index + 1, out.length()); } for (PsiComment comment : PsiTreeUtil.findChildrenOfType(element, PsiComment.class)) { if (!isAtStartOfLine(comment)) out.append(' '); out.append(comment.getText()).append('\n'); } PsiExpression current = tailCall; List<String> conditions = new ArrayList<>(); while (true) { PsiPolyadicExpression parent = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprUp(current.getParent()), PsiPolyadicExpression.class); if (parent == null) { break; } PsiExpression[] operands = parent.getOperands(); if (operands.length < 2) break; String condition = parent.getText().substring(0, operands[operands.length - 2].getTextRangeInParent().getEndOffset()); boolean returnTrue = parent.getOperationTokenType() == JavaTokenType.OROR; if (!returnTrue) { PsiExpression cond = JavaPsiFacade.getElementFactory(method.getProject()).createExpressionFromText(condition, parent); condition = BoolUtils.getNegatedExpressionText(cond); } String ifStatement = "if(" + condition + ") return " + returnTrue + ";\n"; conditions.add(0, ifStatement); current = parent; } conditions.forEach(out::append); final Graph<Integer> graph = buildGraph(parameters, arguments); // When replacing recursion with iteration, new values are assigned to the parameters, // instead of calling the method with the new values. Care needs to be taken to not clobber // the value of a parameter which is used later (in some expression assigned to a different // parameter). To achieve this a simple graph of the dependencies between the parameters is // built and analysed/ordered. If the graph is a directed acyclic graph, the assignments // are ordered in such a way that making a defensive copy is unnecessary (topological // ordering). If the graph has a cycle, a copy of the value of at least one parameter needs // to be made before assigning a new value. final DFSTBuilder<Integer> builder = new DFSTBuilder<>(graph); final List<Integer> sortedNodes = builder.getSortedNodes(); final Set<Integer> seen = new HashSet<>(); final Map<PsiElement, String> replacements = new HashMap<>(); for (Integer index : sortedNodes) { final PsiParameter parameter = parameters[index]; final String parameterName = parameter.getName(); final PsiExpression argument = PsiUtil.skipParenthesizedExprDown(arguments[index]); assert argument != null; if (argument instanceof PsiReferenceExpression referenceExpression) { if (parameter.equals(referenceExpression.resolve())) { // parameter keeps same value continue; } } final Iterator<Integer> dependants = graph.getIn(index); // parameters which depend on parameter 'index' boolean copy = false; while (dependants.hasNext()) { if (!seen.contains(dependants.next())) { // current parameter which depends on value of parameter 'index' has not yet received its value (cycle) // if 'dependants' was some collection instead of an iterator this would have been a nice containsAll expression copy = true; break; } } if (copy) { final String variableName = JavaCodeStyleManager.getInstance(method.getProject()).suggestUniqueVariableName(parameterName, element, false); out.append(parameter.getType().getCanonicalText()).append(' ').append(variableName).append('='); out.append(parameterName).append(';'); replacements.put(parameter, variableName); } out.append(parameterName).append('='); buildText(argument, replacements, out); out.append(';'); seen.add(index); } if (thisVariableName != null) { final PsiReferenceExpression methodExpression = tailCall.getMethodExpression(); final PsiExpression qualifier = methodExpression.getQualifierExpression(); if (qualifier != null) { out.append(thisVariableName).append('='); replaceTailCalls(qualifier, method, thisVariableName, tailCallIsContainedInLoop, isReturnAtTheEndOfWhileLoop, out); out.append(';'); } } final PsiCodeBlock body = method.getBody(); assert body != null; if ((element instanceof PsiReturnStatement && ControlFlowUtils.blockCompletesWithStatement(body, (PsiStatement)element)) || (element instanceof PsiExpressionStatement && (!isReturnAtTheEndOfWhileLoop || isBeforeVoidReturn(element, method)))) { //don't do anything, as the continue statement is unnecessary } else if (tailCallIsContainedInLoop) { out.append("continue ").append(method.getName()).append(';'); } else { out.append("continue;"); } if (!isInBlock) { out.append('}'); } } else { final PsiCodeBlock body = method.getBody(); assert body != null; if (isVoidReturn(element)) { final PsiElement prevElement = PsiTreeUtil.skipWhitespacesAndCommentsBackward(element); final PsiExpressionStatement prevExpressionStatement = ObjectUtils.tryCast(prevElement, PsiExpressionStatement.class); tailCall = getTailCall(prevExpressionStatement, method); if (tailCall != null) { out.append("continue;"); return; } } final PsiElement[] children = element.getChildren(); if (children.length == 0) { out.append(element.getText()); } else { for (final PsiElement child : children) { replaceTailCalls(child, method, thisVariableName, tailCallIsContainedInLoop, isReturnAtTheEndOfWhileLoop, out); } } } }