org.openrewrite.staticanalysis.EmptyBlockVisitor Maven / Gradle / Ivy
/*
* Copyright 2020 the original author or authors.
*
* 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
*
* https://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.openrewrite.staticanalysis;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.DeleteStatement;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.format.ShiftFormat;
import org.openrewrite.java.style.EmptyBlockStyle;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.Statement;
import org.openrewrite.marker.Markers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.Collections.singletonList;
import static org.openrewrite.Tree.randomId;
@Value
@EqualsAndHashCode(callSuper = false)
public class EmptyBlockVisitor
extends JavaIsoVisitor
{
EmptyBlockStyle emptyBlockStyle;
JavaTemplate continueStatement = JavaTemplate.builder("continue;").build();
@Override
public J.WhileLoop visitWhileLoop(J.WhileLoop whileLoop, P p) {
J.WhileLoop w = super.visitWhileLoop(whileLoop, p);
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralWhile()) && isEmptyBlock(w.getBody())) {
J.Block body = (J.Block) w.getBody();
w = continueStatement.apply(updateCursor(w), body.getCoordinates().lastStatement());
}
return w;
}
@Override
public J.DoWhileLoop visitDoWhileLoop(J.DoWhileLoop doWhileLoop, P p) {
J.DoWhileLoop w = super.visitDoWhileLoop(doWhileLoop, p);
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralWhile()) && isEmptyBlock(w.getBody())) {
J.Block body = (J.Block) w.getBody();
w = continueStatement.apply(updateCursor(w), body.getCoordinates().lastStatement());
}
return w;
}
@Override
public J.Block visitBlock(J.Block block, P p) {
J.Block b = super.visitBlock(block, p);
AtomicBoolean filtered = new AtomicBoolean(false);
List statements = ListUtils.map(b.getStatements(), s -> {
if (!(s instanceof J.Block)) {
return s;
}
J.Block nestedBlock = (J.Block) s;
if (isEmptyBlock(nestedBlock) && ((Boolean.TRUE.equals(emptyBlockStyle.getStaticInit()) && nestedBlock.isStatic()) ||
(Boolean.TRUE.equals(emptyBlockStyle.getInstanceInit()) && !nestedBlock.isStatic()))) {
filtered.set(true);
return null;
}
return nestedBlock;
});
return filtered.get() ? b.withStatements(statements) : b;
}
@Override
public J.Try visitTry(J.Try tryable, P p) {
J.Try t = super.visitTry(tryable, p);
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralTry()) &&
isEmptyBlock(t.getBody()) &&
isEmptyResources(t.getResources())) {
doAfterVisit(new DeleteStatement<>(tryable));
} else if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralFinally()) && t.getFinally() != null &&
!t.getCatches().isEmpty() && isEmptyBlock(t.getFinally())) {
t = t.withFinally(null);
}
return t;
}
@Override
public J.If visitIf(J.If iff, P p) {
J.If i = super.visitIf(iff, p);
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralElse()) &&
i.getElsePart() != null &&
isEmptyBlock(i.getElsePart().getBody())) {
i = i.withElsePart(null);
}
if (Boolean.FALSE.equals(emptyBlockStyle.getLiteralIf()) || !isEmptyBlock(i.getThenPart())) {
return i;
}
if (i.getElsePart() == null) {
// extract side effects from condition (if there are any).
J.Block enclosingBlock = getCursor().firstEnclosing(J.Block.class);
if (enclosingBlock != null) {
doAfterVisit(new ExtractSideEffectsOfIfCondition<>(
enclosingBlock, i));
}
return i;
}
// invert top-level if
// Ideally should also add support for other types of expression
J.ControlParentheses cond = i.getIfCondition();
if (!(cond.getTree() instanceof J.Binary)) {
return i;
}
J.Binary binary = (J.Binary) cond.getTree();
// only boolean operators are valid for if conditions
switch (binary.getOperator()) {
case Equal:
cond = cond.withTree(binary.withOperator(J.Binary.Type.NotEqual));
break;
case NotEqual:
cond = cond.withTree(binary.withOperator(J.Binary.Type.Equal));
break;
case LessThan:
cond = cond.withTree(binary.withOperator(J.Binary.Type.GreaterThanOrEqual));
break;
case LessThanOrEqual:
cond = cond.withTree(binary.withOperator(J.Binary.Type.GreaterThan));
break;
case GreaterThan:
cond = cond.withTree(binary.withOperator(J.Binary.Type.LessThanOrEqual));
break;
case GreaterThanOrEqual:
cond = cond.withTree(binary.withOperator(J.Binary.Type.LessThan));
break;
default:
break;
}
i = i.withIfCondition(cond);
if (i.getElsePart() == null) {
return i.withThenPart(new J.Empty(randomId(), Space.EMPTY, Markers.EMPTY))
.withElsePart(null);
}
// NOTE: then part MUST be a J.Block, because otherwise impossible to have an empty if condition followed by else-if/else chain
J.Block thenPart = (J.Block) i.getThenPart();
Statement elseStatement = i.getElsePart().getBody();
List elseStatementBody;
if (elseStatement instanceof J.Block) {
// any else statements should already be at the correct indentation level
elseStatementBody = ((J.Block) elseStatement).getStatements();
} else if (elseStatement instanceof J.If) {
// J.If will typically just have a format of one space (the space between "else" and "if" in "else if")
// we want this to be on its own line now inside its containing if block
elseStatementBody = singletonList(ShiftFormat.indent(
elseStatement.withPrefix(Space.format("\n" + i.getPrefix().getIndent())),
getCursor(), 1));
} else {
elseStatementBody = singletonList(ShiftFormat.indent(elseStatement, getCursor(), 1));
}
return i.withThenPart(thenPart.withStatements(elseStatementBody))
.withElsePart(null);
}
@Override
public J.Synchronized visitSynchronized(J.Synchronized synch, P p) {
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralSynchronized()) && isEmptyBlock(synch.getBody())) {
doAfterVisit(new DeleteStatement<>(synch));
}
return super.visitSynchronized(synch, p);
}
@Override
public J.Switch visitSwitch(J.Switch switch_, P p) {
if (Boolean.TRUE.equals(emptyBlockStyle.getLiteralSwitch()) && isEmptyBlock(switch_.getCases())) {
doAfterVisit(new DeleteStatement<>(switch_));
}
return super.visitSwitch(switch_, p);
}
private boolean isEmptyBlock(Statement blockNode) {
if (blockNode instanceof J.Block) {
J.Block block = (J.Block) blockNode;
if (EmptyBlockStyle.BlockPolicy.STATEMENT.equals(emptyBlockStyle.getBlockPolicy())) {
return block.getStatements().isEmpty();
} else if (EmptyBlockStyle.BlockPolicy.TEXT.equals(emptyBlockStyle.getBlockPolicy())) {
return block.getStatements().isEmpty() && block.getEnd().getComments().isEmpty();
}
}
return false;
}
private boolean isEmptyResources(@Nullable List resources) {
return resources == null || resources.isEmpty();
}
private static class ExtractSideEffectsOfIfCondition extends JavaVisitor
{
private final J.Block enclosingBlock;
private final J.If toExtract;
public ExtractSideEffectsOfIfCondition(J.Block enclosingBlock, J.If toExtract) {
this.enclosingBlock = enclosingBlock;
this.toExtract = toExtract;
}
@Override
public J visitBlock(J.Block block, P p) {
J.Block b = (J.Block) super.visitBlock(block, p);
if (enclosingBlock.isScope(block)) {
List statements = new ArrayList<>(b.getStatements().size());
for (Statement statement : b.getStatements()) {
if (statement == toExtract) {
for (J sideEffect : toExtract.getIfCondition().getTree().getSideEffects()) {
sideEffect = autoFormat(sideEffect, p, getCursor());
statements.add((Statement) sideEffect);
}
} else {
statements.add(statement);
}
}
b = b.withStatements(statements);
}
return b;
}
}
}