diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CharRange.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CharRange.java index e50bb9d3..69904e6e 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CharRange.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CharRange.java @@ -4,8 +4,9 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.CtRange; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import org.apache.commons.lang3.Range; @@ -40,35 +41,35 @@ default CtExpression suggest(CtExpression ctExpression) { private static final Map, Suggester> MAPPING = Map.of( Range.of('a', 'z'), (factory, ctExpression, targetType) -> factory.createBinaryOperator( - SpoonUtil.createStaticInvocation( + FactoryUtil.createStaticInvocation( targetType, "isAlphabetic", - SpoonUtil.castExpression(int.class, ctExpression) + ExpressionUtil.castExpression(int.class, ctExpression) ), - SpoonUtil.createStaticInvocation( + FactoryUtil.createStaticInvocation( targetType, "isLowerCase", - SpoonUtil.castExpression(char.class, ctExpression) + ExpressionUtil.castExpression(char.class, ctExpression) ), BinaryOperatorKind.AND ), Range.of('A', 'Z'), (factory, ctExpression, targetType) -> factory.createBinaryOperator( - SpoonUtil.createStaticInvocation( + FactoryUtil.createStaticInvocation( targetType, "isAlphabetic", - SpoonUtil.castExpression(int.class, ctExpression) + ExpressionUtil.castExpression(int.class, ctExpression) ), - SpoonUtil.createStaticInvocation( + FactoryUtil.createStaticInvocation( targetType, "isUpperCase", - SpoonUtil.castExpression(char.class, ctExpression) + ExpressionUtil.castExpression(char.class, ctExpression) ), BinaryOperatorKind.AND ), - Range.of('0', '9'), (factory, ctExpression, targetType) -> SpoonUtil.createStaticInvocation( + Range.of('0', '9'), (factory, ctExpression, targetType) -> FactoryUtil.createStaticInvocation( targetType, "isDigit", - SpoonUtil.castExpression(char.class, ctExpression) + ExpressionUtil.castExpression(char.class, ctExpression) ) ); @@ -96,7 +97,7 @@ public void process(CtBinaryOperator ctBinaryOperator) { CtBinaryOperator operator = ctBinaryOperator; if (ctBinaryOperator.getKind() == BinaryOperatorKind.OR) { isNegated = true; - operator = (CtBinaryOperator) SpoonUtil.negate(ctBinaryOperator); + operator = (CtBinaryOperator) ExpressionUtil.negate(ctBinaryOperator); } else { isNegated = false; if (ctBinaryOperator.getKind() != BinaryOperatorKind.AND) { @@ -133,7 +134,7 @@ public void process(CtBinaryOperator ctBinaryOperator) { makeSuggestion(leftRange.ctExpression(), intersection).ifPresent(suggestion -> { if (isNegated) { - suggestion = SpoonUtil.negate(suggestion); + suggestion = ExpressionUtil.negate(suggestion); } addLocalProblem( diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CheckIterableDuplicates.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CheckIterableDuplicates.java index 4e7d572a..66044d7c 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CheckIterableDuplicates.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CheckIterableDuplicates.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -54,7 +54,7 @@ public void process(CtForEach ctForEach) { return; } - List statements = SpoonUtil.getEffectiveStatements(ctForEach.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctForEach.getBody()); if (statements.size() != 1 || !(statements.get(0) instanceof CtIf ctIf)) { return; } @@ -64,7 +64,7 @@ public void process(CtForEach ctForEach) { return; } - List ifStatements = SpoonUtil.getEffectiveStatements(ctIf.getThenStatement()); + List ifStatements = StatementUtil.getEffectiveStatements(ctIf.getThenStatement()); if (ifStatements.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionAddAll.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionAddAll.java index 3fc51f98..d8aa1283 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionAddAll.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionAddAll.java @@ -5,7 +5,7 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.check.api.UseEnumValues.CtEnumFieldRead; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -138,7 +138,7 @@ private void reportProblem(CtVariable ctVariable, List ctBlock) { - List statements = SpoonUtil.getEffectiveStatements(ctBlock); + List statements = StatementUtil.getEffectiveStatements(ctBlock); CtVariableReference collection = null; List> addedValues = new ArrayList<>(); @@ -179,7 +179,7 @@ private void checkAddAll(CtBlock ctBlock) { } private void checkAddAll(CtForEach ctFor) { - List statements = SpoonUtil.getEffectiveStatements(ctFor.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctFor.getBody()); if (statements.size() != 1) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionsNCopies.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionsNCopies.java index 8d9b82e8..58ccf03a 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionsNCopies.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/CollectionsNCopies.java @@ -5,7 +5,7 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -32,7 +32,7 @@ public void process(CtFor ctFor) { ForLoopRange forLoopRange = ForLoopRange.fromCtFor(ctFor).orElse(null); - List statements = SpoonUtil.getEffectiveStatements(ctFor.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctFor.getBody()); if (statements.size() != 1 || forLoopRange == null @@ -50,7 +50,7 @@ public void process(CtFor ctFor) { } CtExpression rhs = ctInvocation.getArguments().get(0); - if (!SpoonUtil.isImmutable(rhs.getType())) { + if (!TypeUtil.isImmutable(rhs.getType())) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/IsEmptyReimplementationCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/IsEmptyReimplementationCheck.java index dd471cb1..abd61107 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/IsEmptyReimplementationCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/IsEmptyReimplementationCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -71,7 +71,7 @@ private static CtExpression buildIsEmptySuggestion(CtExpression target) { private void checkIsEmptyReimplementation(CtExpression target, CtBinaryOperator ctBinaryOperator, ProblemType problemType) { Predicate> isLiteral = expr -> expr instanceof CtLiteral; - if (!SpoonUtil.isBoolean(ctBinaryOperator)) { + if (!ExpressionUtil.isBoolean(ctBinaryOperator)) { return; } @@ -117,12 +117,12 @@ private void checkIsEmptyReimplementation(CtExpression target, CtBinaryOperat // f() == 0 : isEmpty - CtBinaryOperator result = SpoonUtil.normalizeBy( - (left, right) -> isLiteral.test(SpoonUtil.resolveConstant(left)) && !isLiteral.test(SpoonUtil.resolveConstant(right)), + CtBinaryOperator result = ExpressionUtil.normalizeBy( + (left, right) -> isLiteral.test(ExpressionUtil.resolveConstant(left)) && !isLiteral.test(ExpressionUtil.resolveConstant(right)), ctBinaryOperator ); - if (!(result.getRightHandOperand() instanceof CtLiteral ctLiteral) || !(ctLiteral.getValue() instanceof Number number) || SpoonUtil.isNullLiteral(ctLiteral)) { + if (!(result.getRightHandOperand() instanceof CtLiteral ctLiteral) || !(ctLiteral.getValue() instanceof Number number) || ExpressionUtil.isNullLiteral(ctLiteral)) { return; } @@ -142,13 +142,13 @@ private void checkIsEmptyReimplementation(CtExpression target, CtBinaryOperat // f() != 0 : !isEmpty case NE -> { if (isZero) { - this.reportProblem(ctBinaryOperator, ctBinaryOperator.toString(), SpoonUtil.negate(suggestion).toString(), problemType); + this.reportProblem(ctBinaryOperator, ctBinaryOperator.toString(), ExpressionUtil.negate(suggestion).toString(), problemType); } } // f() >= 1 : !isEmpty case GE -> { if (isOne) { - this.reportProblem(ctBinaryOperator, ctBinaryOperator.toString(), SpoonUtil.negate(suggestion).toString(), problemType); + this.reportProblem(ctBinaryOperator, ctBinaryOperator.toString(), ExpressionUtil.negate(suggestion).toString(), problemType); } } } @@ -157,17 +157,17 @@ private void checkIsEmptyReimplementation(CtExpression target, CtBinaryOperat private void checkEqualsCall(CtExpression target, CtInvocation ctInvocation, ProblemType problemType) { CtExpression argument = ctInvocation.getArguments().get(0); - if (SpoonUtil.isNullLiteral(argument)) { + if (ExpressionUtil.isNullLiteral(argument)) { return; } - if (SpoonUtil.isStringLiteral(SpoonUtil.resolveConstant(argument), "")) { + if (ExpressionUtil.isStringLiteral(ExpressionUtil.resolveConstant(argument), "")) { this.reportProblem(ctInvocation, ctInvocation.toString(), buildIsEmptySuggestion(target).toString(), problemType); return; } // detect "".equals(s) - if (SpoonUtil.isStringLiteral(SpoonUtil.resolveConstant(target), "")) { + if (ExpressionUtil.isStringLiteral(ExpressionUtil.resolveConstant(target), "")) { this.reportProblem(ctInvocation, ctInvocation.toString(), buildIsEmptySuggestion(argument).toString(), problemType); } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/MathReimplementation.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/MathReimplementation.java index bcbe746c..4c4b8e6b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/MathReimplementation.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/MathReimplementation.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -50,7 +51,7 @@ private static boolean isMathSqrt(CtInvocation ctInvocation) { private static boolean isPowSqrt(CtInvocation ctInvocation) { return isMathPow(ctInvocation) && ctInvocation.getArguments().size() == 2 - && SpoonUtil.resolveConstant(ctInvocation.getArguments().get(1)) instanceof CtLiteral ctLiteral + && ExpressionUtil.resolveConstant(ctInvocation.getArguments().get(1)) instanceof CtLiteral ctLiteral && ctLiteral.getValue() instanceof Double doubleValue && doubleValue == 0.5; } @@ -124,7 +125,7 @@ private void checkMaxMin(CtIf ctIf) { // ensure that in the if block there is only one assignment to a variable // and the condition is a binary operator with <, <=, > or >= - List thenBlock = SpoonUtil.getEffectiveStatements(ctIf.getThenStatement()); + List thenBlock = StatementUtil.getEffectiveStatements(ctIf.getThenStatement()); if (thenBlock.size() != 1 || !(thenBlock.get(0) instanceof CtAssignment thenAssignment) || !(thenAssignment.getAssigned() instanceof CtVariableWrite ctVariableWrite) @@ -143,7 +144,7 @@ private void checkMaxMin(CtIf ctIf) { assignedVariable.getModifiers().contains(ModifierKind.STATIC) ); if (ctIf.getElseStatement() != null) { - List elseBlock = SpoonUtil.getEffectiveStatements(ctIf.getElseStatement()); + List elseBlock = StatementUtil.getEffectiveStatements(ctIf.getElseStatement()); if (elseBlock.size() != 1 || !(elseBlock.get(0) instanceof CtAssignment elseAssignment) || !(elseAssignment.getAssigned() instanceof CtVariableAccess elseAccess) @@ -158,7 +159,7 @@ private void checkMaxMin(CtIf ctIf) { CtBinaryOperator condition = ctBinaryOperator; // ensure that the else value is on the left side of the condition if (ctBinaryOperator.getRightHandOperand().equals(elseValue)) { - condition = SpoonUtil.swapCtBinaryOperator(condition); + condition = ExpressionUtil.swapCtBinaryOperator(condition); } // if it is not on either side of the condition, return diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/SimplifyArraysFill.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/SimplifyArraysFill.java index b36b97e4..862f0267 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/SimplifyArraysFill.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/SimplifyArraysFill.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -37,7 +37,7 @@ public void process(CtInvocation ctInvocation) { } List> args = ctInvocation.getArguments(); - if (SpoonUtil.resolveConstant(args.get(1)) instanceof CtLiteral ctLiteral + if (ExpressionUtil.resolveConstant(args.get(1)) instanceof CtLiteral ctLiteral && ctLiteral.getValue() instanceof Integer number && number == 0 && args.get(2) instanceof CtFieldAccess ctFieldAccess diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/StringRepeat.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/StringRepeat.java index 559a3286..8fc3f603 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/StringRepeat.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/StringRepeat.java @@ -4,9 +4,10 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -26,7 +27,7 @@ public class StringRepeat extends IntegratedCheck { private void checkStringRepeat(CtFor ctFor) { ForLoopRange forLoopRange = ForLoopRange.fromCtFor(ctFor).orElse(null); - List statements = SpoonUtil.getEffectiveStatements(ctFor.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctFor.getBody()); if (statements.size() != 1 || forLoopRange == null) { return; } @@ -39,7 +40,7 @@ private void checkStringRepeat(CtFor ctFor) { return; } - CtExpression rhs = SpoonUtil.resolveCtExpression(ctAssignment.getAssignment()); + CtExpression rhs = ExpressionUtil.resolveCtExpression(ctAssignment.getAssignment()); // return if the for loop uses the loop variable (would not be a simple repetition) if (!ctAssignment.getElements(new VariableAccessFilter<>(forLoopRange.loopVariable())).isEmpty()) { return; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArrayCopy.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArrayCopy.java index ba294d68..ed9e772b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArrayCopy.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArrayCopy.java @@ -6,7 +6,7 @@ import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtArrayAccess; @@ -25,7 +25,7 @@ public class UseArrayCopy extends IntegratedCheck { private void checkArrayCopy(CtFor ctFor) { ForLoopRange forLoopRange = ForLoopRange.fromCtFor(ctFor).orElse(null); - List statements = SpoonUtil.getEffectiveStatements(ctFor.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctFor.getBody()); if (statements.size() != 1 || forLoopRange == null) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArraysFill.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArraysFill.java index 0566a57f..bddeb1c2 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArraysFill.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseArraysFill.java @@ -5,8 +5,10 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; +import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtArrayWrite; @@ -30,7 +32,7 @@ public class UseArraysFill extends IntegratedCheck { private void checkArraysFill(CtFor ctFor) { ForLoopRange forLoopRange = ForLoopRange.fromCtFor(ctFor).orElse(null); - List statements = SpoonUtil.getEffectiveStatements(ctFor.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctFor.getBody()); if (statements.size() != 1 || forLoopRange == null @@ -41,7 +43,7 @@ private void checkArraysFill(CtFor ctFor) { return; } - CtVariable loopVariable = (CtVariable) SpoonUtil.getReferenceDeclaration(forLoopRange.loopVariable()); + CtVariable loopVariable = (CtVariable) VariableUtil.getReferenceDeclaration(forLoopRange.loopVariable()); // return if the for loop uses the loop variable (would not be a simple repetition) if (UsesFinder.variableUses(loopVariable).nestedIn(ctAssignment.getAssignment()).hasAny()) { return; @@ -53,7 +55,7 @@ private void checkArraysFill(CtFor ctFor) { } CtExpression rhs = ctAssignment.getAssignment(); - if (!SpoonUtil.isImmutable(rhs.getType())) { + if (!TypeUtil.isImmutable(rhs.getType())) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEntrySet.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEntrySet.java index 7de9c4b4..5ca20226 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEntrySet.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEntrySet.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -34,7 +34,7 @@ protected void check(StaticAnalysis staticAnalysis) { public void process(CtForEach ctForEach) { if (ctForEach.isImplicit() || !ctForEach.getPosition().isValidPosition() - || !(SpoonUtil.resolveCtExpression(ctForEach.getExpression()) instanceof CtInvocation ctInvocation) + || !(ExpressionUtil.resolveCtExpression(ctForEach.getExpression()) instanceof CtInvocation ctInvocation) || !hasInvokedKeySet(ctInvocation) || !ctForEach.getExpression().getPosition().isValidPosition()) { return; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEnumValues.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEnumValues.java index beb3a2cb..d79197fe 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEnumValues.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseEnumValues.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; @@ -125,7 +126,7 @@ protected void check(StaticAnalysis staticAnalysis) { staticAnalysis.processWith(new AbstractProcessor>() { @Override public void process(CtField ctField) { - if (!SpoonUtil.isEffectivelyFinal(ctField)) { + if (!VariableUtil.isEffectivelyFinal(ctField)) { return; } @@ -144,7 +145,7 @@ public void process(CtField ctField) { } else { checkListingEnumValues( isOrderedCollection(ctExpression.getType()), - SpoonUtil.getElementsOfExpression(ctExpression), + ExpressionUtil.getElementsOfExpression(ctExpression), suggestion -> "%s.of(%s)".formatted(ctExpression.getType().getSimpleName(), suggestion), ctExpression ); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseFormatString.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseFormatString.java index eeb53779..feb5f02b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseFormatString.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseFormatString.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -85,7 +86,7 @@ private String buildFormattedString(Iterable> ctExpres // true if the formatString has a %n placeholder boolean hasInlineNewline = false; for (CtExpression ctExpression : ctExpressions) { - if (SpoonUtil.resolveConstant(ctExpression) instanceof CtLiteral literal + if (ExpressionUtil.resolveConstant(ctExpression) instanceof CtLiteral literal && literal.getValue() != null && TypeUtil.isTypeEqualTo(literal.getType(), java.lang.String.class)) { ctExpression = literal; @@ -146,12 +147,12 @@ private CtExpression resolveExpression(CtExpression ctExpression) { typeFactory.stringType(), "lineSeparator" )) { - return SpoonUtil.makeLiteral(typeFactory.stringType(), "\n"); + return FactoryUtil.makeLiteral(typeFactory.stringType(), "\n"); } if (ctExpression instanceof CtLiteral ctLiteral - && SpoonUtil.areLiteralsEqual(ctLiteral, SpoonUtil.makeLiteral(typeFactory.characterPrimitiveType(), '\n'))) { - return SpoonUtil.makeLiteral(typeFactory.stringType(), "\n"); + && ExpressionUtil.areLiteralsEqual(ctLiteral, FactoryUtil.makeLiteral(typeFactory.characterPrimitiveType(), '\n'))) { + return FactoryUtil.makeLiteral(typeFactory.stringType(), "\n"); } return ctExpression; @@ -198,13 +199,13 @@ private void checkCtBinaryOperator(CtBinaryOperator ctBinaryOperator) { // only visit binary operators that evaluate to a String // (should be guaranteed by the visitor) -> seems to not be guaranteed; replacing the throw by a return for now - if (!SpoonUtil.isString(ctBinaryOperator.getType())) { + if (!TypeUtil.isString(ctBinaryOperator.getType())) { return; } List> formatArgs = this.getFormatArgs(ctBinaryOperator); - int numberOfLiterals = (int) formatArgs.stream().filter(ctExpression -> SpoonUtil.resolveConstant(ctExpression) instanceof CtLiteral literal && literal.getValue() != null).count(); + int numberOfLiterals = (int) formatArgs.stream().filter(ctExpression -> ExpressionUtil.resolveConstant(ctExpression) instanceof CtLiteral literal && literal.getValue() != null).count(); if (numberOfLiterals < MIN_NUMBER_LITERALS) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseModuloOperator.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseModuloOperator.java index 6c185820..8c61b893 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseModuloOperator.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseModuloOperator.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.BinaryOperatorKind; @@ -33,7 +34,7 @@ public class UseModuloOperator extends IntegratedCheck { ); private void checkModulo(CtIf ctIf) { - List thenBlock = SpoonUtil.getEffectiveStatements(ctIf.getThenStatement()); + List thenBlock = StatementUtil.getEffectiveStatements(ctIf.getThenStatement()); if (ctIf.getElseStatement() != null || thenBlock.size() != 1 || !(thenBlock.get(0) instanceof CtAssignment thenAssignment) @@ -44,7 +45,7 @@ private void checkModulo(CtIf ctIf) { } // must assign a value of 0 - if (!(SpoonUtil.resolveCtExpression(thenAssignment.getAssignment()) instanceof CtLiteral ctLiteral) + if (!(ExpressionUtil.resolveCtExpression(thenAssignment.getAssignment()) instanceof CtLiteral ctLiteral) || !(ctLiteral.getValue() instanceof Integer integer) || integer != 0) { return; @@ -52,7 +53,7 @@ private void checkModulo(CtIf ctIf) { CtVariableReference assignedVariable = ctVariableWrite.getVariable(); - CtBinaryOperator condition = SpoonUtil.normalizeBy( + CtBinaryOperator condition = ExpressionUtil.normalizeBy( (left, right) -> right instanceof CtVariableAccess ctVariableAccess && ctVariableAccess.getVariable().equals(assignedVariable), ctBinaryOperator ); @@ -74,7 +75,7 @@ private void checkModulo(CtIf ctIf) { // for boxed types, one could check if the value is null, // for which the suggestion `a %= null` would not make sense - if (SpoonUtil.isNullLiteral(checkedValue)) { + if (ExpressionUtil.isNullLiteral(checkedValue)) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseStringFormatted.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseStringFormatted.java index 5535447b..c11e8742 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseStringFormatted.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseStringFormatted.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -39,7 +39,7 @@ private void checkCtInvocation(CtInvocation ctInvocation) { CtExpression format = args.remove(0); // skip if the format string is not a string literal (e.g. a complex concatenation) - if (SpoonUtil.tryGetStringLiteral(SpoonUtil.resolveConstant(format)).isEmpty()) { + if (ExpressionUtil.tryGetStringLiteral(ExpressionUtil.resolveConstant(format)).isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseSubList.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseSubList.java index 972a9e76..cedb82c0 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseSubList.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/api/UseSubList.java @@ -5,9 +5,9 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.check.general.ForToForEachLoop; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtFor; @@ -45,7 +45,7 @@ private void checkSubList(CtFor ctFor) { } // check if the loop iterates over the whole list (then it is covered by the foreach loop check) - if (SpoonUtil.resolveConstant(forLoopRange.start()) instanceof CtLiteral ctLiteral + if (ExpressionUtil.resolveConstant(forLoopRange.start()) instanceof CtLiteral ctLiteral && ctLiteral.getValue() == 0 && ForToForEachLoop.findIterable(forLoopRange).isPresent()) { return; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/FieldJavadocCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/FieldJavadocCheck.java index 9af70eb8..f8f9b6e0 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/FieldJavadocCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/FieldJavadocCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtJavaDoc; @@ -32,7 +32,7 @@ public void process(CtField field) { return; } - Optional javadoc = SpoonUtil.getJavadoc(field); + Optional javadoc = ElementUtil.getJavadoc(field); if (javadoc.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/JavadocStubCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/JavadocStubCheck.java index 10c3ddca..873e61df 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/JavadocStubCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/JavadocStubCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtJavaDoc; @@ -34,7 +34,7 @@ protected void check(StaticAnalysis staticAnalysis) { public void process(CtJavaDoc javadoc) { if (allowGettersSettersWithEmptyDescription && javadoc.getParent() instanceof CtMethod method - && (SpoonUtil.isGetter(method) || SpoonUtil.isSetter(method))) { + && (MethodUtil.isGetter(method) || MethodUtil.isSetter(method))) { // Setters and Getters are okay } else if (isDefaultValueDescription(javadoc.getContent())) { addLocalProblem(javadoc, new LocalizedMessage("javadoc-stub-exp-desc"), diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/MethodJavadocCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/MethodJavadocCheck.java index 5f7a8fd6..b128354c 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/MethodJavadocCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/MethodJavadocCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtJavaDoc; @@ -41,7 +41,7 @@ public void process(CtMethod method) { return; } - Optional javadoc = SpoonUtil.getJavadoc(method); + Optional javadoc = ElementUtil.getJavadoc(method); if (javadoc.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/ThrowsJavadocCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/ThrowsJavadocCheck.java index 0d885560..42715b67 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/ThrowsJavadocCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/ThrowsJavadocCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.reflect.code.CtCase; import spoon.reflect.code.CtConstructorCall; @@ -25,7 +25,7 @@ @ExecutableCheck(reportedProblems = {ProblemType.JAVADOC_UNDOCUMENTED_THROWS}) public class ThrowsJavadocCheck extends IntegratedCheck { private void checkCtExecutable(CtExecutable ctExecutable) { - Optional doc = SpoonUtil.getJavadoc(ctExecutable); + Optional doc = ElementUtil.getJavadoc(ctExecutable); if (doc.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/TypeJavadocCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/TypeJavadocCheck.java index 6f6ab3c0..c6705f60 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/TypeJavadocCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/TypeJavadocCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtJavaDoc; @@ -51,7 +51,7 @@ protected void check(StaticAnalysis staticAnalysis) { staticAnalysis.processWith(new AbstractProcessor>() { @Override public void process(CtType type) { - Optional javadoc = SpoonUtil.getJavadoc(type); + Optional javadoc = ElementUtil.getJavadoc(type); if (javadoc.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/UnnecessaryComment.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/UnnecessaryComment.java index 997d82ce..27e74d7c 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/UnnecessaryComment.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/comment/UnnecessaryComment.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtComment; @@ -69,7 +69,7 @@ public void process(CtElement element) { // // Here the comments without an attached CtElement are processed: if (element instanceof CtComment ctComment && isStandaloneComment(ctComment) && !visitedComments.contains(ctComment)) { - List followingComments = SpoonUtil.getNextStatements(ctComment) + List followingComments = StatementUtil.getNextStatements(ctComment) .stream() .takeWhile(CtComment.class::isInstance) .map(CtComment.class::cast) diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/ChainedIfCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/ChainedIfCheck.java index 490c041a..75fdb9b0 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/ChainedIfCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/ChainedIfCheck.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtBlock; @@ -27,17 +28,17 @@ public void process(CtIf ctIf) { } // check if the if-statement has a nested if: - List thenStatements = SpoonUtil.getEffectiveStatements(ctIf.getThenStatement()); + List thenStatements = StatementUtil.getEffectiveStatements(ctIf.getThenStatement()); if (thenStatements.size() == 1 && thenStatements.get(0) instanceof CtIf nestedIf && (nestedIf.getElseStatement() == null - || SpoonUtil.getEffectiveStatements(nestedIf.getElseStatement()).isEmpty())) { + || StatementUtil.getEffectiveStatements(nestedIf.getElseStatement()).isEmpty())) { addLocalProblem( ctIf.getCondition(), new LocalizedMessage( "merge-nested-if", Map.of( - "suggestion", SpoonUtil.createBinaryOperator( + "suggestion", FactoryUtil.createBinaryOperator( ctIf.getCondition(), nestedIf.getCondition(), spoon.reflect.code.BinaryOperatorKind.AND @@ -53,7 +54,7 @@ public void process(CtIf ctIf) { return; } - List statements = SpoonUtil.getEffectiveStatements(ctBlock); + List statements = StatementUtil.getEffectiveStatements(ctBlock); if (statements.size() != 1) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantAssignment.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantAssignment.java index 0bdb8ead..ec74dae1 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantAssignment.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantAssignment.java @@ -5,7 +5,7 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.check.unnecessary.UnusedCodeElementCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.processing.AbstractProcessor; @@ -42,7 +42,7 @@ public void process(CtAssignment ctAssignment) { return; } - List followingStatements = SpoonUtil.getNextStatements(ctAssignment); + List followingStatements = StatementUtil.getNextStatements(ctAssignment); CtLocalVariable ctLocalVariable = ctLocalVariableReference.getDeclaration(); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantBooleanEqual.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantBooleanEqual.java index cd125f76..ac09ffdf 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantBooleanEqual.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantBooleanEqual.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.BinaryOperatorKind; @@ -29,7 +29,7 @@ private void reportProblem(CtBinaryOperator ctBinaryOperator, boolean literal CtExpression suggestion = otherSide; if (!realLiteral) { - suggestion = SpoonUtil.negate(otherSide); + suggestion = ExpressionUtil.negate(otherSide); } addLocalProblem( @@ -53,8 +53,8 @@ public void process(CtBinaryOperator ctBinaryOperator) { if (ctBinaryOperator.isImplicit() || !ctBinaryOperator.getPosition().isValidPosition() || !OPERATORS.contains(ctBinaryOperator.getKind()) - || !SpoonUtil.isBoolean(ctBinaryOperator.getLeftHandOperand()) - || !SpoonUtil.isBoolean(ctBinaryOperator.getRightHandOperand())) { + || !ExpressionUtil.isBoolean(ctBinaryOperator.getLeftHandOperand()) + || !ExpressionUtil.isBoolean(ctBinaryOperator.getRightHandOperand())) { return; } @@ -62,11 +62,11 @@ public void process(CtBinaryOperator ctBinaryOperator) { CtExpression right = ctBinaryOperator.getRightHandOperand(); // the lhs resolves to a literal boolean - SpoonUtil.tryGetBooleanLiteral(left) + ExpressionUtil.tryGetBooleanLiteral(left) .ifPresentOrElse( literal -> reportProblem(ctBinaryOperator, literal, right), // if the lhs is not a literal boolean, check if the rhs is - () -> SpoonUtil.tryGetBooleanLiteral(right) + () -> ExpressionUtil.tryGetBooleanLiteral(right) .ifPresent(literal -> reportProblem(ctBinaryOperator, literal, left)) ); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantCatch.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantCatch.java index ae55a869..0eefb46b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantCatch.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantCatch.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.Effect; import de.firemage.autograder.core.integrated.effects.TerminalEffect; @@ -26,8 +26,8 @@ protected void check(StaticAnalysis staticAnalysis) { public void process(CtCatch ctCatch) { if (ctCatch.isImplicit() || !ctCatch.getPosition().isValidPosition()) return; - List statements = SpoonUtil.getEffectiveStatements(ctCatch.getBody()); - Optional singleEffect = SpoonUtil.getSingleEffect(statements); + List statements = StatementUtil.getEffectiveStatements(ctCatch.getBody()); + Optional singleEffect = StatementUtil.getSingleEffect(statements); CtVariableReference caughtVariable = ctCatch.getParameter().getReference(); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantElse.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantElse.java index 7d41af17..a8b13a75 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantElse.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantElse.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.Effect; import de.firemage.autograder.core.integrated.effects.TerminalEffect; @@ -21,11 +21,11 @@ public class RedundantElse extends IntegratedCheck { private static final int MINIMUM_STATEMENTS = 5; private Optional getTerminalEffect(CtStatement ctStatement) { - List ctStatements = SpoonUtil.getEffectiveStatements(ctStatement); + List ctStatements = StatementUtil.getEffectiveStatements(ctStatement); if (ctStatements.isEmpty()) return Optional.empty(); - Optional optionalEffect = SpoonUtil.tryMakeEffect(ctStatements.get(ctStatements.size() - 1)); + Optional optionalEffect = StatementUtil.tryMakeEffect(ctStatements.get(ctStatements.size() - 1)); return optionalEffect.flatMap(effect -> { if (effect instanceof TerminalEffect terminalEffect) { @@ -49,7 +49,7 @@ private void checkCtIf(CtIf ctIf) { } String elseIf = ""; - List elseStatements = SpoonUtil.getEffectiveStatements(ctIf.getElseStatement()); + List elseStatements = StatementUtil.getEffectiveStatements(ctIf.getElseStatement()); int numberOfElseStatements = elseStatements.size(); if (elseStatements.size() == 1 && elseStatements.get(0) instanceof CtIf ctElseIf) { // skip else { if ... } @@ -67,7 +67,7 @@ private void checkCtIf(CtIf ctIf) { return; } - numberOfElseStatements = SpoonUtil.getEffectiveStatements(ctElseIf.getThenStatement()).size(); + numberOfElseStatements = StatementUtil.getEffectiveStatements(ctElseIf.getThenStatement()).size(); elseIf = " else if (b) { ... }"; } else if (ctIf.getElseStatement().isImplicit()) { @@ -105,7 +105,7 @@ public void process(CtIf ctIf) { if (parentIf != null && parentIf.getElseStatement() != null) { // if so, then check if the else statement is this if // (to avoid conflicts with else { if } can be else if {}) - List ctStatements = SpoonUtil.getEffectiveStatements(parentIf.getElseStatement()); + List ctStatements = StatementUtil.getEffectiveStatements(parentIf.getElseStatement()); if (ctStatements.size() == 1 && ctStatements.get(0).equals(ctIf)) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantIfForBooleanCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantIfForBooleanCheck.java index 479eda7a..9cb14253 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantIfForBooleanCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantIfForBooleanCheck.java @@ -3,8 +3,10 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.AssignmentEffect; import de.firemage.autograder.core.integrated.effects.Effect; @@ -39,8 +41,8 @@ private String makeSuggestion(CtExpression ctExpression, Effect thenEffect, E } private void checkIfElse(CtExpression condition, CtStatement thenStmt, CtStatement elseStmt) { - Effect thenEffect = SpoonUtil.tryMakeEffect(thenStmt).orElse(null); - Effect elseEffect = SpoonUtil.tryMakeEffect(elseStmt).orElse(null); + Effect thenEffect = StatementUtil.tryMakeEffect(thenStmt).orElse(null); + Effect elseEffect = StatementUtil.tryMakeEffect(elseStmt).orElse(null); // skip if they are not both return statements or both assignments to the same variable if (thenEffect == null @@ -56,12 +58,12 @@ private void checkIfElse(CtExpression condition, CtStatement thenStmt, CtStat CtExpression elseValue = elseEffect.value().get(); // skip if they do not assign or return a boolean expression - if (!SpoonUtil.isBoolean(thenValue) || !SpoonUtil.isBoolean(elseValue)) { + if (!ExpressionUtil.isBoolean(thenValue) || !ExpressionUtil.isBoolean(elseValue)) { return; } - Boolean thenLiteral = SpoonUtil.tryGetBooleanLiteral(thenValue).orElse(null); - Boolean elseLiteral = SpoonUtil.tryGetBooleanLiteral(elseValue).orElse(null); + Boolean thenLiteral = ExpressionUtil.tryGetBooleanLiteral(thenValue).orElse(null); + Boolean elseLiteral = ExpressionUtil.tryGetBooleanLiteral(elseValue).orElse(null); // skip non-sense like if (a) return true else return true if (thenLiteral != null && thenLiteral.equals(elseLiteral)) { @@ -72,7 +74,7 @@ private void checkIfElse(CtExpression condition, CtStatement thenStmt, CtStat if (thenLiteral == null) { // if it does not return a literal, both the condition and the return value must be true - thenCondition = SpoonUtil.createBinaryOperator( + thenCondition = FactoryUtil.createBinaryOperator( condition, thenValue, BinaryOperatorKind.AND @@ -90,13 +92,13 @@ private void checkIfElse(CtExpression condition, CtStatement thenStmt, CtStat // if (a) { return b; } else { return c; } -> return a && b || c; if (thenCondition == null) { - combinedCondition = SpoonUtil.createBinaryOperator( - SpoonUtil.negate(condition), + combinedCondition = FactoryUtil.createBinaryOperator( + ExpressionUtil.negate(condition), elseValue, BinaryOperatorKind.AND ); } else { - combinedCondition = SpoonUtil.createBinaryOperator( + combinedCondition = FactoryUtil.createBinaryOperator( combinedCondition, elseValue, BinaryOperatorKind.OR @@ -105,11 +107,11 @@ private void checkIfElse(CtExpression condition, CtStatement thenStmt, CtStat } else if (elseLiteral) { // if it does return true, then the if condition must be false if (thenCondition == null) { - combinedCondition = SpoonUtil.negate(condition); + combinedCondition = ExpressionUtil.negate(condition); } else { - combinedCondition = SpoonUtil.createBinaryOperator( + combinedCondition = FactoryUtil.createBinaryOperator( combinedCondition, - SpoonUtil.negate(condition), + ExpressionUtil.negate(condition), BinaryOperatorKind.OR ); } @@ -137,7 +139,7 @@ public void process(CtBlock block) { return; } - List statements = SpoonUtil.getEffectiveStatements(block); + List statements = StatementUtil.getEffectiveStatements(block); // TODO: write test @@ -148,7 +150,7 @@ public void process(CtBlock block) { continue; } - CtStatement thenStatement = SpoonUtil.unwrapStatement(ifStmt.getThenStatement()); + CtStatement thenStatement = StatementUtil.unwrapStatement(ifStmt.getThenStatement()); CtStatement elseStatement = ifStmt.getElseStatement(); // if(...) { return true } return false @@ -163,7 +165,7 @@ public void process(CtBlock block) { checkIfElse( ifStmt.getCondition(), thenStatement, - SpoonUtil.unwrapStatement(elseStatement) + StatementUtil.unwrapStatement(elseStatement) ); } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantNegationCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantNegationCheck.java index 761f1306..a5a51d11 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantNegationCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantNegationCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.evaluator.Evaluator; @@ -30,7 +30,7 @@ public CtExpression foldCtUnaryOperator(CtUnaryOperator ctUnaryOperato CtExpression operand = ctUnaryOperator.getOperand(); // this negates the operand and optimizes it if possible - return (CtExpression) SpoonUtil.negate(operand); + return (CtExpression) ExpressionUtil.negate(operand); } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantUninitializedVariable.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantUninitializedVariable.java index 02f2d813..ea46f1b1 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantUninitializedVariable.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantUninitializedVariable.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtAssignment; @@ -51,7 +51,7 @@ public void process(CtLocalVariable ctLocalVariable) { if (ctAssignments.isEmpty()) return; - boolean isConditional = !SpoonUtil.getEffectiveStatements(ctLocalVariable.getParent(CtBlock.class)) + boolean isConditional = !StatementUtil.getEffectiveStatements(ctLocalVariable.getParent(CtBlock.class)) .contains(ctAssignments.get(0)); if (isConditional) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantVariable.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantVariable.java index 6e98f2ea..a3654380 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantVariable.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RedundantVariable.java @@ -5,7 +5,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.reflect.code.CtComment; @@ -53,10 +53,10 @@ private void checkVariableRead(CtStatement ctStatement, CtVariableRead ctVari return; } - CtStatement previousStatement = SpoonUtil.getPreviousStatement(ctStatement).orElse(null); + CtStatement previousStatement = StatementUtil.getPreviousStatement(ctStatement).orElse(null); while (!ctLocalVariable.equals(previousStatement) && this.isAllowedStatement(previousStatement)) { - previousStatement = SpoonUtil.getPreviousStatement(previousStatement).orElse(null); + previousStatement = StatementUtil.getPreviousStatement(previousStatement).orElse(null); } if (previousStatement == null) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RepeatedMathOperationCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RepeatedMathOperationCheck.java index 15425d75..7febddc3 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RepeatedMathOperationCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/RepeatedMathOperationCheck.java @@ -3,9 +3,10 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; +import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.core.integrated.evaluator.Evaluator; import de.firemage.autograder.core.integrated.evaluator.fold.Fold; import spoon.processing.AbstractProcessor; @@ -90,7 +91,7 @@ public CtElement exit(CtElement ctElement) { public CtExpression foldCtBinaryOperator(CtBinaryOperator ctBinaryOperator) { // skip if the operator is not supported if (!OCCURRENCE_THRESHOLDS.containsKey(ctBinaryOperator.getKind()) || - !SpoonUtil.isPrimitiveNumeric(ctBinaryOperator.getType())) { + !TypeUtil.isPrimitiveNumeric(ctBinaryOperator.getType())) { return ctBinaryOperator; } @@ -112,7 +113,7 @@ public CtExpression foldCtBinaryOperator(CtBinaryOperator ctBinaryOper return this.function.apply(expression, count); } }) - .reduce((left, right) -> SpoonUtil.createBinaryOperator(left, right, this.kind)) + .reduce((left, right) -> FactoryUtil.createBinaryOperator(left, right, this.kind)) .orElseThrow(); } } @@ -125,7 +126,7 @@ public static CtExpression repeatExpression(BinaryOperatorKind kind, CtExpres } public static CtExpression joinExpressions(BinaryOperatorKind kind, CtExpression first, CtExpression... others) { - return Arrays.stream(others).reduce(first, (left, right) -> SpoonUtil.createBinaryOperator(left, right, kind)); + return Arrays.stream(others).reduce(first, (left, right) -> FactoryUtil.createBinaryOperator(left, right, kind)); } @Override @@ -148,9 +149,9 @@ public void process(CtExpression ctExpression) { OCCURRENCE_THRESHOLDS.get(BinaryOperatorKind.PLUS), (expression, count) -> { plusOptimizations.addAndGet(1); - return SpoonUtil.createBinaryOperator( + return FactoryUtil.createBinaryOperator( expression, - SpoonUtil.makeLiteralNumber(expression.getType(), count), + FactoryUtil.makeLiteralNumber(expression.getType(), count), BinaryOperatorKind.MUL ); } @@ -162,11 +163,11 @@ public void process(CtExpression ctExpression) { (expression, count) -> { TypeFactory typeFactory = expression.getFactory().Type(); mulOptimizations.addAndGet(1); - return SpoonUtil.createStaticInvocation( + return FactoryUtil.createStaticInvocation( typeFactory.get(java.lang.Math.class).getReference(), "pow", expression, - SpoonUtil.makeLiteralNumber(typeFactory.integerPrimitiveType(), count) + FactoryUtil.makeLiteralNumber(typeFactory.integerPrimitiveType(), count) ); } ); @@ -197,8 +198,8 @@ private Map countOccurrences(CtExpression expression, Bina if (expression instanceof CtBinaryOperator operator && operator.getKind() == kind) { // '+' can also be used on Strings, but String operations are not associative - if (SpoonUtil.isString(operator.getLeftHandOperand().getType()) || - SpoonUtil.isString(operator.getRightHandOperand().getType())) { + if (TypeUtil.isString(operator.getLeftHandOperand().getType()) || + TypeUtil.isString(operator.getRightHandOperand().getType())) { return Map.of(); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnnecessaryBoxing.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnnecessaryBoxing.java index 485eaec6..1febaaac 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnnecessaryBoxing.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnnecessaryBoxing.java @@ -4,8 +4,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.processing.AbstractProcessor; @@ -26,7 +26,7 @@ private static boolean isBoxedType(CtTypeReference ctTypeReference) { private static boolean isLikelyNull(CtExpression ctExpression) { return ctExpression == null - || SpoonUtil.isNullLiteral(ctExpression) + || ExpressionUtil.isNullLiteral(ctExpression) || isBoxedType(ctExpression.getType()); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnusedImport.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnusedImport.java index 5ec3d552..3fad6441 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnusedImport.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/UnusedImport.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -95,7 +96,7 @@ public List visitReference(JavadocReference reference) { } private static boolean isReferencingTheSameElement(CtReference left, CtReference right) { - return left.equals(right) || Objects.equals(SpoonUtil.getReferenceDeclaration(left), SpoonUtil.getReferenceDeclaration(right)); + return left.equals(right) || Objects.equals(VariableUtil.getReferenceDeclaration(left), VariableUtil.getReferenceDeclaration(right)); } @SuppressWarnings("unchecked") @@ -136,7 +137,7 @@ private void checkImport(CtImport ctImport, CtCompilationUnit ctCompilationUnit, return; } - CtNamedElement element = (CtNamedElement) SpoonUtil.getReferenceDeclaration(ctImport.getReference()); + CtNamedElement element = (CtNamedElement) VariableUtil.getReferenceDeclaration(ctImport.getReference()); // types from the same package are imported implicitly // @@ -213,7 +214,7 @@ private void checkImport(CtImport ctImport, CtCompilationUnit ctCompilationUnit, @Override protected void check(StaticAnalysis staticAnalysis) { - SpoonUtil.visitCtCompilationUnit(staticAnalysis.getModel(), ctCompilationUnit -> { + CoreUtil.visitCtCompilationUnit(staticAnalysis.getModel(), ctCompilationUnit -> { Collection importedElements = new HashSet<>(); for (CtImport ctImport : ctCompilationUnit.getImports()) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/WrapperInstantiationCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/WrapperInstantiationCheck.java index 43810917..6284246b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/WrapperInstantiationCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/complexity/WrapperInstantiationCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -44,7 +44,7 @@ public void process(CtConstructorCall ctConstructorCall) { // check if the argument is not the unboxed type // for example Integer(String) - if (!boxedType.unbox().equals(SpoonUtil.getExpressionType(value))) { + if (!boxedType.unbox().equals(ExpressionUtil.getExpressionType(value))) { suggestion = "%s.valueOf(%s)".formatted(boxedType, suggestion); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/ExceptionMessageCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/ExceptionMessageCheck.java index 66b06335..05388fdf 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/ExceptionMessageCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/ExceptionMessageCheck.java @@ -4,8 +4,8 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -49,7 +49,7 @@ private static boolean hasMessage(Iterable> arguments) return true; } - String literal = SpoonUtil.tryGetStringLiteral(ctExpression).orElse(null); + String literal = ExpressionUtil.tryGetStringLiteral(ctExpression).orElse(null); return literal == null || !literal.isBlank(); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/RuntimeExceptionCatchCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/RuntimeExceptionCatchCheck.java index d869bfa2..ec20e0c4 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/RuntimeExceptionCatchCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/exceptions/RuntimeExceptionCatchCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtCatch; @@ -28,7 +28,7 @@ public void process(CtCatch ctCatch) { CtTypeReference varType = ctCatch.getParameter().getType(); - List statements = SpoonUtil.getEffectiveStatements(ctCatch.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctCatch.getBody()); // catching an exception to throw another is okay if (statements.size() == 1 && statements.get(0) instanceof CtThrow) { return; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidRecompilingRegex.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidRecompilingRegex.java index e76c25bd..de215539 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidRecompilingRegex.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidRecompilingRegex.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.core.integrated.UsesFinder; @@ -38,7 +38,7 @@ public void process(CtField ctField) { return; } - CtExpression ctExpression = SpoonUtil.resolveCtExpression(ctField.getDefaultExpression()); + CtExpression ctExpression = ExpressionUtil.resolveCtExpression(ctField.getDefaultExpression()); // skip all non-literals to improve performance if (!(ctExpression instanceof CtLiteral)) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidShadowing.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidShadowing.java index 28a3c5a3..0fe7e598 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidShadowing.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/AvoidShadowing.java @@ -4,7 +4,6 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.UsesFinder; @@ -59,7 +58,7 @@ public void process(CtVariable ctVariable) { } // skip fields inside overridden methods - if (MethodUtil.isInOverridingMethod(ctVariable) || SpoonUtil.isInSetter(ctVariable)) { + if (MethodUtil.isInOverridingMethod(ctVariable) || MethodUtil.isInSetter(ctVariable)) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareCharValue.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareCharValue.java index 3a8d4fe9..698a74c3 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareCharValue.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareCharValue.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -34,7 +34,7 @@ public class CompareCharValue extends IntegratedCheck { private static Optional getComparedIntegerValue(CtExpression left, CtExpression right) { if (!TypeUtil.isTypeEqualTo(left.getType(), char.class) - || !(SpoonUtil.resolveConstant(right) instanceof CtLiteral literal && literal.getValue() instanceof Integer value)) { + || !(ExpressionUtil.resolveConstant(right) instanceof CtLiteral literal && literal.getValue() instanceof Integer value)) { return Optional.empty(); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareObjectsNotStringsCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareObjectsNotStringsCheck.java index b6611bf1..3d6949fe 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareObjectsNotStringsCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/CompareObjectsNotStringsCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtInvocation; @@ -28,12 +28,12 @@ public void process(CtInvocation invocation) { CtExecutableReference executable = invocation.getExecutable(); if (executable.getSignature().equals("equals(java.lang.Object)") && executable.getParameters().size() == 1) { - Optional> lhsType = SpoonUtil.isToStringCall(invocation.getTarget()); + Optional> lhsType = ExpressionUtil.isToStringCall(invocation.getTarget()); if (lhsType.isEmpty()) { return; } - Optional> rhsType = SpoonUtil.isToStringCall(invocation.getArguments().get(0)); + Optional> rhsType = ExpressionUtil.isToStringCall(invocation.getArguments().get(0)); if (rhsType.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/FieldShouldBeFinal.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/FieldShouldBeFinal.java index 3c26bf07..f194c73f 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/FieldShouldBeFinal.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/FieldShouldBeFinal.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.core.integrated.UsesFinder; @@ -94,7 +94,7 @@ private static boolean canBeFinal(CtField ctField) { int mainPathWrites = 0; int otherPathWrites = 0; - for (CtStatement ctStatement : SpoonUtil.getEffectiveStatementsOf(ctConstructor)) { + for (CtStatement ctStatement : StatementUtil.getEffectiveStatementsOf(ctConstructor)) { if (ctStatement instanceof CtAssignment ctAssignment && UsesFinder.variableWrites(ctField).nestedIn(ctAssignment).hasAny()) { mainPathWrites += 1; } else if (UsesFinder.variableWrites(ctField).nestedIn(ctStatement).hasAny()) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ForToForEachLoop.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ForToForEachLoop.java index adc66880..519013b2 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ForToForEachLoop.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ForToForEachLoop.java @@ -6,7 +6,6 @@ import de.firemage.autograder.core.integrated.ForLoopRange; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -175,7 +174,7 @@ public void process(CtFor ctFor) { String iterableExpression = iterable.getSimpleName(); Function, Optional>> getPotentialLoopVariableAccess; - if (SpoonUtil.isString(iterable.getType())) { + if (TypeUtil.isString(iterable.getType())) { getPotentialLoopVariableAccess = LOOP_VARIABLE_ACCESS_STRING; iterableExpression = "%s.toCharArray()".formatted(iterableExpression); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeDoWhile.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeDoWhile.java index 0288a866..00b11b25 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeDoWhile.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeDoWhile.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtStatement; @@ -59,8 +60,8 @@ public void process(CtWhile ctWhile) { // } // ``` - Deque loopStatements = new ArrayDeque<>(SpoonUtil.getEffectiveStatements(ctWhile.getBody())); - CtStatement currentStatementBeforeLoop = SpoonUtil.getPreviousStatement(ctWhile).orElse(null); + Deque loopStatements = new ArrayDeque<>(StatementUtil.getEffectiveStatements(ctWhile.getBody())); + CtStatement currentStatementBeforeLoop = StatementUtil.getPreviousStatement(ctWhile).orElse(null); if (currentStatementBeforeLoop == null || loopStatements.isEmpty()) { return; } @@ -72,7 +73,7 @@ public void process(CtWhile ctWhile) { return; } - currentStatementBeforeLoop = SpoonUtil.getPreviousStatement(currentStatementBeforeLoop).orElse(null); + currentStatementBeforeLoop = StatementUtil.getPreviousStatement(currentStatementBeforeLoop).orElse(null); } if (loopStatements.isEmpty()) { @@ -81,7 +82,7 @@ public void process(CtWhile ctWhile) { new LocalizedMessage( "loop-should-be-do-while", Map.of("suggestion", """ - %ndo %s while (%s)""".formatted(SpoonUtil.truncatedSuggestion(ctWhile.getBody()), ctWhile.getLoopingExpression())) + %ndo %s while (%s)""".formatted(CoreUtil.truncatedSuggestion(ctWhile.getBody()), ctWhile.getLoopingExpression())) ), ProblemType.LOOP_SHOULD_BE_DO_WHILE ); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeFor.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeFor.java index c9a8065c..b5db18e3 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeFor.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/LoopShouldBeFor.java @@ -4,9 +4,11 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; +import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.processing.AbstractProcessor; import spoon.reflect.code.BinaryOperatorKind; @@ -62,15 +64,15 @@ public String toString() { } private static LoopSuggestion getCounter(CtLoop ctLoop, CodeModel model) { - List statements = SpoonUtil.getEffectiveStatements(ctLoop.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctLoop.getBody()); if (statements.isEmpty()) { return null; } - CtStatement previous = SpoonUtil.getPreviousStatement(ctLoop).orElse(null); - if (!(previous instanceof CtLocalVariable ctLocalVariable) || !SpoonUtil.isPrimitiveNumeric(ctLocalVariable.getType())) { + CtStatement previous = StatementUtil.getPreviousStatement(ctLoop).orElse(null); + if (!(previous instanceof CtLocalVariable ctLocalVariable) || !TypeUtil.isPrimitiveNumeric(ctLocalVariable.getType())) { return null; } @@ -129,7 +131,7 @@ private static LoopSuggestion getCounter(CtLoop ctLoop, CodeModel model) { newBody = ctLoop.getFactory().createBlock(); } - boolean isUsedAfterLoop = SpoonUtil.getNextStatements(ctLoop) + boolean isUsedAfterLoop = StatementUtil.getNextStatements(ctLoop) .stream() .anyMatch(statement -> UsesFinder.variableUses(ctLocalVariable).nestedIn(statement).hasAny()); @@ -169,7 +171,7 @@ private static LoopSuggestion getCounter(CtLoop ctLoop, CodeModel model) { upperBound = factory.createInvocation(ctForEach.getExpression().clone(), methods.get(0).getReference(), List.of()); } - condition = SpoonUtil.createBinaryOperator( + condition = FactoryUtil.createBinaryOperator( factory.createVariableRead(ctLocalVariable.getReference(), false), upperBound, BinaryOperatorKind.LT diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ReassignedParameterCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ReassignedParameterCheck.java index 10d2dc85..4e734ed4 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ReassignedParameterCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/ReassignedParameterCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.declaration.CtConstructor; @@ -26,7 +26,7 @@ public void process(CtParameter ctParameter) { return; } - if (!SpoonUtil.isEffectivelyFinal(ctParameter)) { + if (!VariableUtil.isEffectivelyFinal(ctParameter)) { addLocalProblem( ctParameter, new LocalizedMessage( diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/StringCompareCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/StringCompareCheck.java index 9797a971..56f1ee98 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/StringCompareCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/StringCompareCheck.java @@ -3,9 +3,10 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; +import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtBinaryOperator; @@ -16,8 +17,8 @@ @ExecutableCheck(reportedProblems = { ProblemType.STRING_COMPARE_BY_REFERENCE }) public class StringCompareCheck extends IntegratedCheck { private static boolean isStringComparison(CtExpression lhs, CtExpression rhs) { - return SpoonUtil.isString(lhs.getType()) && !SpoonUtil.isNullLiteral(rhs) - || SpoonUtil.isString(rhs.getType()) && !SpoonUtil.isNullLiteral(lhs); + return TypeUtil.isString(lhs.getType()) && !ExpressionUtil.isNullLiteral(rhs) + || TypeUtil.isString(rhs.getType()) && !ExpressionUtil.isNullLiteral(lhs); } @Override diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/UseGuardClauses.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/UseGuardClauses.java index fad1850a..10b737d5 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/general/UseGuardClauses.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/general/UseGuardClauses.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.Effect; import de.firemage.autograder.core.integrated.effects.TerminalEffect; @@ -29,12 +30,12 @@ private void reportProblem(CtStatement ctStatement, CtExpression condition) { } private boolean isTerminal(CtStatement ctStatement) { - List ctStatements = SpoonUtil.getEffectiveStatements(ctStatement); + List ctStatements = StatementUtil.getEffectiveStatements(ctStatement); if (ctStatements.isEmpty()) return false; - Optional optionalEffect = SpoonUtil.tryMakeEffect(ctStatements.get(ctStatements.size() - 1)); + Optional optionalEffect = StatementUtil.tryMakeEffect(ctStatements.get(ctStatements.size() - 1)); return optionalEffect.map(TerminalEffect.class::isInstance).orElse(false); } @@ -42,7 +43,7 @@ private boolean isTerminal(CtStatement ctStatement) { private void checkCtIf(CtIf ctIf, CtExpression condition) { // if the condition != null, then the ctIf is an else if if (condition != null) { - CtExpression ifCondition = SpoonUtil.createBinaryOperator( + CtExpression ifCondition = FactoryUtil.createBinaryOperator( condition, ctIf.getCondition(), BinaryOperatorKind.AND @@ -54,7 +55,7 @@ private void checkCtIf(CtIf ctIf, CtExpression condition) { } // the condition to reach the else statement - CtExpression elseCondition = SpoonUtil.createUnaryOperator( + CtExpression elseCondition = FactoryUtil.createUnaryOperator( UnaryOperatorKind.NOT, ctIf.getCondition() ); @@ -64,10 +65,10 @@ private void checkCtIf(CtIf ctIf, CtExpression condition) { // if there is no else, return if (ctStatement == null) return; - List ctStatements = SpoonUtil.getEffectiveStatements(ctStatement); + List ctStatements = StatementUtil.getEffectiveStatements(ctStatement); if (condition != null) { - elseCondition = SpoonUtil.createBinaryOperator( + elseCondition = FactoryUtil.createBinaryOperator( condition, elseCondition, BinaryOperatorKind.AND @@ -75,7 +76,7 @@ private void checkCtIf(CtIf ctIf, CtExpression condition) { } if (ctStatements.size() == 1 && ctStatements.get(0) instanceof CtIf ctElseIf) { - CtExpression elseIfCondition = SpoonUtil.createBinaryOperator( + CtExpression elseIfCondition = FactoryUtil.createBinaryOperator( elseCondition, ctElseIf.getCondition(), BinaryOperatorKind.AND @@ -97,7 +98,7 @@ public void process(CtIf ctIf) { CtIf parentIf = ctIf.getParent(CtIf.class); if (parentIf != null && parentIf.getElseStatement() != null) { - List ctStatements = SpoonUtil.getEffectiveStatements(parentIf.getElseStatement()); + List ctStatements = StatementUtil.getEffectiveStatements(parentIf.getElseStatement()); if (ctStatements.size() == 1 && ctStatements.get(0).equals(ctIf)) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/ConstantsHaveDescriptiveNamesCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/ConstantsHaveDescriptiveNamesCheck.java index 3c92b084..8d749d2c 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/ConstantsHaveDescriptiveNamesCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/ConstantsHaveDescriptiveNamesCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.FactoryUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -201,7 +201,7 @@ public void process(CtField field) { && ctInvocation.getTarget() instanceof CtTypeAccess ctTypeAccess && TypeUtil.isTypeEqualTo(ctTypeAccess.getAccessedType(), java.lang.System.class) && MethodUtil.isSignatureEqualTo(ctInvocation.getExecutable(), String.class, "lineSeparator")) { - literal = SpoonUtil.makeLiteral(field.getFactory().Type().stringType(), "\n"); + literal = FactoryUtil.makeLiteral(field.getFactory().Type().stringType(), "\n"); } else { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/PackageNamingConvention.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/PackageNamingConvention.java index 86da3ab7..180cbc7b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/PackageNamingConvention.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/naming/PackageNamingConvention.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtCompilationUnit; @@ -88,7 +88,7 @@ protected void check(StaticAnalysis staticAnalysis) { String positions = declarations.stream() .map(CtPackageDeclaration::getPosition) - .map(SpoonUtil::formatSourcePosition) + .map(CoreUtil::formatSourcePosition) .collect(Collectors.joining(", ")); this.addLocalProblem( diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ClosedSetOfValues.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ClosedSetOfValues.java index 7db604c9..006dbf13 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ClosedSetOfValues.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ClosedSetOfValues.java @@ -3,8 +3,10 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.AssignmentEffect; import de.firemage.autograder.core.integrated.effects.TerminalEffect; @@ -53,7 +55,7 @@ private boolean shouldSwitchOverEnum(CtAbstractSwitch ctSwitch) { continue; } - var effect = SpoonUtil.getSingleEffect(ctCase.getStatements()).orElse(null); + var effect = StatementUtil.getSingleEffect(ctCase.getStatements()).orElse(null); if (!(effect instanceof TerminalEffect || effect instanceof AssignmentEffect)) { return true; @@ -68,7 +70,7 @@ private static Set> getDistinctElementsFromSwitch(CtAbstractSwitch< List> elements = ctSwitch.getCases() .stream() .flatMap((CtCase e) -> e.getCaseExpressions().stream()) - .map(SpoonUtil::resolveCtExpression) + .map(ExpressionUtil::resolveCtExpression) .toList(); if (elements.stream().anyMatch(e -> !(e instanceof CtLiteral))) { @@ -122,7 +124,7 @@ private static List> getFiniteSet(Iterable> result = new ArrayList<>(); for (CtExpression ctExpression : elements) { - CtExpression resolved = SpoonUtil.resolveCtExpression(ctExpression); + CtExpression resolved = ExpressionUtil.resolveCtExpression(ctExpression); if (!isSupportedType(resolved.getType()) || !(resolved instanceof CtLiteral ctLiteral)) { return List.of(); @@ -216,14 +218,14 @@ public void visitCtField(CtField ctField) { return; } - if (!SpoonUtil.isEffectivelyFinal(ctField)) { + if (!VariableUtil.isEffectivelyFinal(ctField)) { return; } if (ctField.getType().isArray() && ctExpression instanceof CtNewArray ctNewArray) { checkFiniteListing(ctExpression, ctNewArray.getElements()); } else { - checkFiniteListing(ctExpression, SpoonUtil.getElementsOfExpression(ctExpression)); + checkFiniteListing(ctExpression, ExpressionUtil.getElementsOfExpression(ctExpression)); } super.visitCtField(ctField); @@ -240,14 +242,14 @@ public void visitCtLocalVariable(CtLocalVariable ctLocalVariable) { return; } - if (!SpoonUtil.isEffectivelyFinal(ctLocalVariable)) { + if (!VariableUtil.isEffectivelyFinal(ctLocalVariable)) { return; } if (ctLocalVariable.getType().isArray() && ctExpression instanceof CtNewArray ctNewArray) { checkFiniteListing(ctExpression, ctNewArray.getElements()); } else { - checkFiniteListing(ctExpression, SpoonUtil.getElementsOfExpression(ctExpression)); + checkFiniteListing(ctExpression, ExpressionUtil.getElementsOfExpression(ctExpression)); } super.visitCtLocalVariable(ctLocalVariable); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/DoNotMakeConstantsClasses.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/DoNotMakeConstantsClasses.java index 4081d767..3cbe9949 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/DoNotMakeConstantsClasses.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/DoNotMakeConstantsClasses.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -28,7 +28,7 @@ private boolean isConstantsClassLike(CtType ctType) { && !ctType.getFields().isEmpty() // all fields should be static and effectively final (no assignments) && ctType.getFields().stream().allMatch( - ctField -> ctField.isStatic() && SpoonUtil.isEffectivelyFinal(ctField) + ctField -> ctField.isStatic() && VariableUtil.isEffectivelyFinal(ctField) ) // the class should not be abstract && !ctType.isAbstract() diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/IOUISeparation.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/IOUISeparation.java index f3dd2105..24087b01 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/IOUISeparation.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/IOUISeparation.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.MethodUtil; @@ -169,7 +169,7 @@ protected void check(StaticAnalysis staticAnalysis) { "ui-input-separation", Map.of( "first", - SpoonUtil.formatSourcePosition(scannerUses.get(0).getPosition()) + CoreUtil.formatSourcePosition(scannerUses.get(0).getPosition()) ) ), ProblemType.UI_INPUT_SEPARATION @@ -183,7 +183,7 @@ protected void check(StaticAnalysis staticAnalysis) { "ui-output-separation", Map.of( "first", - SpoonUtil.formatSourcePosition(printUses.get(0).getPosition()) + CoreUtil.formatSourcePosition(printUses.get(0).getPosition()) ) ), ProblemType.UI_OUTPUT_SEPARATION diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/LeakedCollectionCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/LeakedCollectionCheck.java index 3c5ba2c6..47cc450c 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/LeakedCollectionCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/LeakedCollectionCheck.java @@ -5,7 +5,8 @@ import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.check.utils.Option; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -158,7 +159,7 @@ private static boolean isMutableExpression(CtExpression ctExpression) { // In both cases the assigned expression is mutable, and we have to detect this. // To do this, we check if the assigned variable is mutable. if (ctExpression instanceof CtVariableRead ctVariableRead && ctExecutable != null) { - CtVariable ctVariable = SpoonUtil.getVariableDeclaration(ctVariableRead.getVariable()); + CtVariable ctVariable = VariableUtil.getVariableDeclaration(ctVariableRead.getVariable()); // this is a special case for enums, where the constructor is private and mutability can only be introduced // through the enum constructor calls @@ -213,7 +214,7 @@ private static List> findPreviousAssignee(CtVariableRead ctVa boolean foundPreviousAssignment = false; CtStatement currentStatement = ctVariableRead.getParent(CtStatement.class); - var reversedStatements = new ArrayList<>(SpoonUtil.getEffectiveStatements(ctExecutable.getBody())); + var reversedStatements = new ArrayList<>(StatementUtil.getEffectiveStatements(ctExecutable.getBody())); Collections.reverse(reversedStatements); for (CtStatement ctStatement : reversedStatements) { if (!foundPreviousAssignment) { @@ -240,7 +241,7 @@ private static Option> findParameterReference(CtExpression ctE return Option.none(); } - CtVariable ctVariableDeclaration = SpoonUtil.getVariableDeclaration(ctVariableRead.getVariable()); + CtVariable ctVariableDeclaration = VariableUtil.getVariableDeclaration(ctVariableRead.getVariable()); if (ctVariableDeclaration != null && isParameterOf(ctVariableDeclaration, ctExecutable)) { // There is a special-case: one can reassign the parameter to itself with a different value: @@ -268,7 +269,7 @@ private static boolean hasAssignedParameterReference(CtExpression ctExpressio } private void checkCtExecutableReturn(CtExecutable ctExecutable) { - List statements = SpoonUtil.getEffectiveStatements(ctExecutable.getBody()); + List statements = StatementUtil.getEffectiveStatements(ctExecutable.getBody()); // a lambda like () -> true does not have a body, but an expression which is a return statement // this case is handled here @@ -340,7 +341,7 @@ private void checkCtExecutableAssign(CtExecutable ctExecutable) { return; } - for (CtStatement ctStatement : SpoonUtil.getEffectiveStatements(ctExecutable.getBody())) { + for (CtStatement ctStatement : StatementUtil.getEffectiveStatements(ctExecutable.getBody())) { if (!(ctStatement instanceof CtAssignment ctAssignment) || !(ctAssignment.getAssigned() instanceof CtFieldWrite ctFieldWrite)) { continue; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MethodShouldBeAbstractCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MethodShouldBeAbstractCheck.java index c28aa230..d8be7d4d 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MethodShouldBeAbstractCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MethodShouldBeAbstractCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtConstructorCall; @@ -46,7 +46,7 @@ public void process(CtClass clazz) { continue; } - List statements = SpoonUtil.getEffectiveStatements(method.getBody()); + List statements = StatementUtil.getEffectiveStatements(method.getBody()); if (statements.isEmpty()) { addLocalProblem(method, formatExplanation(method), ProblemType.METHOD_USES_PLACEHOLDER_IMPLEMENTATION); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MutableEnum.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MutableEnum.java index 4babbb93..283c1298 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MutableEnum.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/MutableEnum.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.declaration.CtEnum; @@ -22,7 +22,7 @@ public class MutableEnum extends IntegratedCheck { */ private static boolean isMutable(CtType ctType) { for (CtField ctField : ctType.getFields()) { - if (!SpoonUtil.isEffectivelyFinal(ctField)) { + if (!VariableUtil.isEffectivelyFinal(ctField)) { return true; } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ShouldBeEnumAttribute.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ShouldBeEnumAttribute.java index 5b9ace9f..cb4d8d76 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ShouldBeEnumAttribute.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/ShouldBeEnumAttribute.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.effects.Effect; import spoon.processing.AbstractProcessor; @@ -27,7 +27,7 @@ public void process(CtAbstractSwitch ctSwitch) { return; } - List effects = SpoonUtil.getCasesEffects(ctSwitch.getCases()); + List effects = StatementUtil.getCasesEffects(ctSwitch.getCases()); if (effects.isEmpty()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/StaticFieldShouldBeInstanceCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/StaticFieldShouldBeInstanceCheck.java index bc2ec9d4..00738c9b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/StaticFieldShouldBeInstanceCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/oop/StaticFieldShouldBeInstanceCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -35,7 +35,7 @@ public void process(CtField ctField) { // the field is not marked as final, so values can be assigned to it. // if the field is assigned multiple times, it should not be static - if (!SpoonUtil.isEffectivelyFinal(ctField)) { + if (!VariableUtil.isEffectivelyFinal(ctField)) { addLocalProblem( ctField, new LocalizedMessage( diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DefaultPackageCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DefaultPackageCheck.java index 271d632a..89a3b658 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DefaultPackageCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DefaultPackageCheck.java @@ -3,8 +3,8 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; @@ -34,7 +34,7 @@ protected void check(StaticAnalysis staticAnalysis) { String positions = typesInDefaultPackage.stream() .map(CtType::getPosition) - .map(SpoonUtil::formatSourcePosition) + .map(CoreUtil::formatSourcePosition) .collect(Collectors.joining(", ")); this.addLocalProblem( diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DuplicateCode.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DuplicateCode.java index 0891b8d4..e0a322f7 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DuplicateCode.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/structure/DuplicateCode.java @@ -3,8 +3,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.UsesFinder; import de.firemage.autograder.core.integrated.structure.StructuralElement; @@ -48,7 +49,7 @@ private static String formatSourceRange(CtElement start, CtElement end) { return String.format( "%s:%d-%d", - SpoonUtil.getBaseName(startPosition.getFile().getName()), + CoreUtil.getBaseName(startPosition.getFile().getName()), startPosition.getLine(), endPosition.getEndLine() ); @@ -131,7 +132,7 @@ public int countExposedVariables() { } int count = 0; - for (CtStatement ctStatement : SpoonUtil.getNextStatements(this.getLast())) { + for (CtStatement ctStatement : StatementUtil.getNextStatements(this.getLast())) { for (CtVariable declaredVariable : declaredVariables) { if (UsesFinder.variableUses(declaredVariable).nestedIn(ctStatement).hasAny()) { count += 1; @@ -206,7 +207,7 @@ private void checkCtStatement(CtStatement ctStatement) { CodeSegment leftCode = CodeSegment.of(ctStatement); CodeSegment rightCode = CodeSegment.of(duplicate); - for (var entry : zip(SpoonUtil.getNextStatements(ctStatement), SpoonUtil.getNextStatements(duplicate))) { + for (var entry : zip(StatementUtil.getNextStatements(ctStatement), StatementUtil.getNextStatements(duplicate))) { if (!StructuralEqualsVisitor.equals(entry.getKey(), entry.getValue())) { break; } @@ -271,7 +272,7 @@ public void visitCtMethod(CtMethod ctMethod) { return; } - for (CtStatement ctStatement : SpoonUtil.getEffectiveStatements(ctMethod.getBody())) { + for (CtStatement ctStatement : StatementUtil.getEffectiveStatements(ctMethod.getBody())) { this.checkCtStatement(ctStatement); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/EmptyBlockCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/EmptyBlockCheck.java index 4d93d3d0..b58b39f3 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/EmptyBlockCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/EmptyBlockCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import spoon.reflect.code.CtBlock; @@ -17,7 +17,7 @@ @ExecutableCheck(reportedProblems = {ProblemType.EMPTY_BLOCK, ProblemType.EMPTY_CATCH}) public class EmptyBlockCheck extends IntegratedCheck { private static boolean isEmptyBlock(CtBlock ctBlock) { - return SpoonUtil.getEffectiveStatements(ctBlock).isEmpty() + return StatementUtil.getEffectiveStatements(ctBlock).isEmpty() // allow empty blocks that only contain comments && (ctBlock.getStatements().isEmpty() || !ctBlock.getStatements().stream().allMatch(CtComment.class::isInstance)); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/UnusedCodeElementCheck.java b/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/UnusedCodeElementCheck.java index eeaa98c1..e928b801 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/UnusedCodeElementCheck.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/check/unnecessary/UnusedCodeElementCheck.java @@ -4,9 +4,9 @@ import de.firemage.autograder.core.LocalizedMessage; import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ElementUtil; import de.firemage.autograder.core.integrated.IntegratedCheck; import de.firemage.autograder.core.integrated.MethodHierarchy; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -42,7 +42,7 @@ public class UnusedCodeElementCheck extends IntegratedCheck { */ public static boolean isConsideredUnused(CtNamedElement element, CodeModel model) { // ignore exception constructors and params in those constructors - var parentConstructor = SpoonUtil.getParentOrSelf(element, CtConstructor.class); + var parentConstructor = ElementUtil.getParentOrSelf(element, CtConstructor.class); if (parentConstructor != null && TypeUtil.isSubtypeOf(parentConstructor.getType(), java.lang.Throwable.class)) { return false; } @@ -65,7 +65,7 @@ public static boolean isConsideredUnused(CtNamedElement element, CodeModel model return false; } else if (variable instanceof CtParameter parameter && parameter.getParent() instanceof CtMethod method) { // For method parameters, also look in overriding methods - int parameterIndex = SpoonUtil.getParameterIndex(parameter, method); + int parameterIndex = ElementUtil.getParameterIndex(parameter, method); return MethodHierarchy .streamAllOverridingMethods(method) .allMatch(m -> isConsideredUnused(m.getExecutable().getParameters().get(parameterIndex), model)); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CoreUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CoreUtil.java index 35e81cc8..7b31143b 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CoreUtil.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CoreUtil.java @@ -1,7 +1,19 @@ package de.firemage.autograder.core.integrated; +import org.apache.commons.io.FilenameUtils; +import spoon.reflect.CtModel; +import spoon.reflect.cu.SourcePosition; +import spoon.reflect.cu.position.CompoundSourcePosition; +import spoon.reflect.declaration.CtCompilationUnit; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtNamedElement; +import spoon.reflect.declaration.CtType; + +import java.io.File; import java.util.Arrays; import java.util.Optional; +import java.util.StringJoiner; +import java.util.function.Consumer; /** * Utility class for functionality that does not fit in any other utility class. @@ -45,4 +57,82 @@ public static void setDebugMode() { public static boolean isInDebugMode() { return IS_IN_JUNIT_TEST || AUTOGRADER_DEBUG_ENVIRONMENT.orElse(false); } + + public static void visitCtCompilationUnit(CtModel ctModel, Consumer lambda) { + // it is not possible to visit CtCompilationUnit through the processor API. + // + // in https://github.com/INRIA/spoon/issues/5168 the below code is mentioned as a workaround: + ctModel + .getAllTypes() + .stream() + .map(CtType::getPosition) + .filter(SourcePosition::isValidPosition) + .map(SourcePosition::getCompilationUnit) + // visit each compilation unit only once + .distinct() + .forEach(lambda); + } + + /** + * Converts the provided source position into a human-readable string. + * + * @param sourcePosition the source position as given by spoon + * @return a human-readable string representation of the source position + */ + public static String formatSourcePosition(SourcePosition sourcePosition) { + return String.format("%s:L%d", getBaseName(sourcePosition.getFile().getName()), sourcePosition.getLine()); + } + + public static String getBaseName(String fileName) { + if (fileName == null) { + return null; + } + return FilenameUtils.removeExtension(new File(fileName).getName()); + } + + public static String truncatedSuggestion(CtElement ctElement) { + StringJoiner result = new StringJoiner(System.lineSeparator()); + + for (String line : ctElement.toString().split("\\r?\\n")) { + int newLineLength = 0; + + // this ensures that the truncation is the same on linux and windows + if (!result.toString().contains("\r\n")) { + newLineLength += (int) result.toString().chars().filter(ch -> ch == '\n').count(); + } + + if (result.length() + newLineLength > 150) { + if (line.startsWith(" ")) { + result.add("...".indent(line.length() - line.stripIndent().length()).stripTrailing()); + } else { + result.add("..."); + } + + if (result.toString().startsWith("{")) { + result.add("}"); + } + + break; + } + + result.add(line); + } + + return result.toString(); + } + + public static SourcePosition getNamePosition(CtNamedElement ctNamedElement) { + SourcePosition position = ctNamedElement.getPosition(); + + if (position instanceof CompoundSourcePosition compoundSourcePosition) { + return ctNamedElement.getFactory().createSourcePosition( + position.getCompilationUnit(), + compoundSourcePosition.getNameStart(), + compoundSourcePosition.getNameEnd(), + position.getCompilationUnit().getLineSeparatorPositions() + ); + } + + return position; + } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CtRange.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CtRange.java index 02bb33ff..92fb45f4 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CtRange.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/CtRange.java @@ -24,7 +24,7 @@ private static > Optional> of(CtBinaryOperato // swap operator if necessary, so that the literal is on the right side: // - CtBinaryOperator result = SpoonUtil.normalizeBy( + CtBinaryOperator result = ExpressionUtil.normalizeBy( (left, right) -> isLiteral.test(left) && !isLiteral.test(right), ctBinaryOperator ); @@ -44,17 +44,17 @@ private static > Optional> of(CtBinaryOperato public Range toRange() { // if (this.operator == BinaryOperatorKind.LE) { - T lowerBound = SpoonUtil.minimumValue(this.ctLiteral).getValue(); + T lowerBound = ExpressionUtil.minimumValue(this.ctLiteral).getValue(); - if (SpoonUtil.resolveCtExpression(this.ctExpression) instanceof CtLiteral exprLiteral) { + if (ExpressionUtil.resolveCtExpression(this.ctExpression) instanceof CtLiteral exprLiteral) { lowerBound = exprLiteral.getValue(); } return Range.of(lowerBound, this.ctLiteral.getValue()); } else if (this.operator == BinaryOperatorKind.GE) { // >= - T upperBound = SpoonUtil.maximumValue(this.ctLiteral).getValue(); - if (SpoonUtil.resolveCtExpression(this.ctExpression) instanceof CtLiteral exprLiteral) { + T upperBound = ExpressionUtil.maximumValue(this.ctLiteral).getValue(); + if (ExpressionUtil.resolveCtExpression(this.ctExpression) instanceof CtLiteral exprLiteral) { upperBound = exprLiteral.getValue(); } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ElementUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ElementUtil.java index 29db3eec..b1760983 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ElementUtil.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ElementUtil.java @@ -1,9 +1,12 @@ package de.firemage.autograder.core.integrated; import spoon.processing.FactoryAccessor; +import spoon.reflect.code.CtJavaDoc; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtParameter; import java.util.Collections; import java.util.HashSet; @@ -11,6 +14,8 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -75,6 +80,32 @@ public CtElement next() throws NoSuchElementException { }; } + public static

P getParentOrSelf(CtElement element, Class

parentType) { + Objects.requireNonNull(element); + if (parentType.isAssignableFrom(element.getClass())) { + return (P) element; + } + return element.getParent(parentType); + } + + public static int getParameterIndex(CtParameter parameter, CtExecutable executable) { + for (int i = 0; i < executable.getParameters().size(); i++) { + if (executable.getParameters().get(i) == parameter) { + return i; + } + } + throw new IllegalArgumentException("Parameter not found in executable"); + } + + public static Optional getJavadoc(CtElement element) { + if (element.getComments().isEmpty() || !(element.getComments().get(0) instanceof CtJavaDoc)) { + // TODO lookup inherited javadoc + return Optional.empty(); + } else { + return Optional.of(element.getComments().get(0).asJavaDoc()); + } + } + private record IdentityKey(T value) { public static IdentityKey of(T value) { return new IdentityKey<>(value); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ExpressionUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ExpressionUtil.java new file mode 100644 index 00000000..60c3d7a1 --- /dev/null +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ExpressionUtil.java @@ -0,0 +1,556 @@ +package de.firemage.autograder.core.integrated; + +import de.firemage.autograder.core.integrated.evaluator.Evaluator; +import de.firemage.autograder.core.integrated.evaluator.fold.FoldUtils; +import de.firemage.autograder.core.integrated.evaluator.fold.InlineVariableRead; +import de.firemage.autograder.core.integrated.evaluator.fold.RemoveRedundantCasts; +import spoon.reflect.CtModel; +import spoon.reflect.code.BinaryOperatorKind; +import spoon.reflect.code.CtBinaryOperator; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtTypeAccess; +import spoon.reflect.code.CtUnaryOperator; +import spoon.reflect.code.LiteralBase; +import spoon.reflect.code.UnaryOperatorKind; +import spoon.reflect.declaration.CtTypedElement; +import spoon.reflect.eval.PartialEvaluator; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtTypeReference; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class ExpressionUtil { + private ExpressionUtil() { + } + + public static Optional> isToStringCall(CtExpression expression) { + if (!TypeUtil.isString(expression.getType())) { + return Optional.empty(); + } + + if (expression instanceof CtInvocation invocation && + MethodUtil.isSignatureEqualTo(invocation.getExecutable(), String.class, "toString")) { + return Optional.of(invocation.getTarget().getType()); + } else { + return Optional.empty(); + } + } + + public static boolean isStringLiteral(CtExpression expression, String value) { + return expression instanceof CtLiteral literal && literal.getValue() != null && + literal.getValue().equals(value); + } + + public static boolean isNullLiteral(CtExpression expression) { + return resolveConstant(expression) instanceof CtLiteral literal && literal.getValue() == null; + } + + public static boolean isIntegerLiteral(CtExpression expression, int value) { + return expression instanceof CtLiteral literal && literal.getValue().equals(value); + } + + public static boolean isBoolean(CtTypedElement ctTypedElement) { + CtTypeReference ctTypeReference = ctTypedElement.getType(); + return ctTypeReference != null && TypeUtil.isTypeEqualTo(ctTypeReference, boolean.class, Boolean.class); + } + + public static Optional tryGetBooleanLiteral(CtExpression expression) { + if (resolveConstant(expression) instanceof CtLiteral literal + && literal.getValue() != null + && isBoolean(literal)) { + + return Optional.of((Boolean) literal.getValue()); + } else { + return Optional.empty(); + } + } + + public static Optional tryGetStringLiteral(CtExpression expression) { + if (resolveConstant(expression) instanceof CtLiteral literal + && literal.getValue() != null + && TypeUtil.isTypeEqualTo(literal.getType(), String.class)) { + + return Optional.of((String) literal.getValue()); + } else { + return Optional.empty(); + } + } + + // equals impl of CtLiteral seems to be broken + public static boolean areLiteralsEqual( + CtLiteral left, + CtLiteral right + ) { + if (left == null && right == null) { + return true; + } else if (left == null || right == null) { + return false; + } + + if (left.getValue() == null) { + return right.getValue() == null; + } else if (right.getValue() == null) { + return false; + } + + if (left.getValue() instanceof Character l && right.getValue() instanceof Character r) { + return l.equals(r); + } else if (left.getValue() instanceof Number l && right.getValue() instanceof Character r) { + return l.intValue() == (int) r; + } else if (left.getValue() instanceof Character l && right.getValue() instanceof Number r) { + return (int) l == r.intValue(); + } + + if (!(left.getValue() instanceof Number valLeft) + || !(right.getValue() instanceof Number valRight)) { + return left.getValue() == right.getValue() || left.getValue().equals(right.getValue()); + } + + if (valLeft instanceof Float || valLeft instanceof Double || valRight instanceof Float + || valRight instanceof Double) { + return valLeft.doubleValue() == valRight.doubleValue(); + } + + return valLeft.longValue() == valRight.longValue(); + } + + public static List> getElementsOfExpression(CtExpression ctExpression) { + var supportedCollections = Stream.of( + List.class, + java.util.Set.class, + java.util.Collection.class + ).map((Class e) -> ctExpression.getFactory().Type().createReference(e)); + + List> result = new ArrayList<>(); + + CtTypeReference expressionType = ctExpression.getType(); + if (supportedCollections.noneMatch(ty -> ty.equals(expressionType) || expressionType.isSubtypeOf(ty))) { + return result; + } + + if (ctExpression instanceof CtInvocation ctInvocation + && ctInvocation.getTarget() instanceof CtTypeAccess) { + CtExecutableReference ctExecutableReference = ctInvocation.getExecutable(); + if (ctExecutableReference.getSimpleName().equals("of")) { + result.addAll(ctInvocation.getArguments()); + } + } + + return result; + } + + public static CtLiteral minimumValue(CtLiteral ctLiteral) { + CtLiteral result = ctLiteral.getFactory().createLiteral(); + result.setBase(LiteralBase.DECIMAL); + result.setType(ctLiteral.getType().clone()); + + // - byte + // - short + // - int + // - long + // - float + // - double + // - boolean + // - char + + Object value = ctLiteral.getValue(); + Map, Object> minimumValueMapping = Map.ofEntries( + Map.entry(byte.class, Byte.MIN_VALUE), + Map.entry(Byte.class, Byte.MIN_VALUE), + Map.entry(short.class, Short.MIN_VALUE), + Map.entry(Short.class, Short.MIN_VALUE), + Map.entry(int.class, Integer.MIN_VALUE), + Map.entry(Integer.class, Integer.MIN_VALUE), + Map.entry(long.class, Long.MIN_VALUE), + Map.entry(Long.class, Long.MIN_VALUE), + Map.entry(float.class, Float.MIN_VALUE), + Map.entry(Float.class, Float.MIN_VALUE), + Map.entry(double.class, Double.MIN_VALUE), + Map.entry(Double.class, Double.MIN_VALUE), + Map.entry(boolean.class, false), + Map.entry(Boolean.class, false), + Map.entry(char.class, Character.MIN_VALUE), + Map.entry(Character.class, Character.MIN_VALUE) + ); + + result.setValue(minimumValueMapping.get(value.getClass())); + + return result; + } + + public static CtLiteral maximumValue(CtLiteral ctLiteral) { + CtLiteral result = ctLiteral.getFactory().createLiteral(); + result.setBase(LiteralBase.DECIMAL); + result.setType(ctLiteral.getType().clone()); + + // - byte + // - short + // - int + // - long + // - float + // - double + // - boolean + // - char + + Object value = ctLiteral.getValue(); + Map, Object> maximumValueMapping = Map.ofEntries( + Map.entry(byte.class, Byte.MAX_VALUE), + Map.entry(Byte.class, Byte.MAX_VALUE), + Map.entry(short.class, Short.MAX_VALUE), + Map.entry(Short.class, Short.MAX_VALUE), + Map.entry(int.class, Integer.MAX_VALUE), + Map.entry(Integer.class, Integer.MAX_VALUE), + Map.entry(long.class, Long.MAX_VALUE), + Map.entry(Long.class, Long.MAX_VALUE), + Map.entry(float.class, Float.MAX_VALUE), + Map.entry(Float.class, Float.MAX_VALUE), + Map.entry(double.class, Double.MAX_VALUE), + Map.entry(Double.class, Double.MAX_VALUE), + Map.entry(boolean.class, true), + Map.entry(Boolean.class, true), + Map.entry(char.class, Character.MAX_VALUE), + Map.entry(Character.class, Character.MAX_VALUE) + ); + + result.setValue(maximumValueMapping.get(value.getClass())); + + return result; + } + + /** + * Swaps the operands of a binary operator. + * + * @param ctBinaryOperator the operator to swap, can be of any kind + * @return the cloned version with the operands swapped or the given operator if it is not supported + * @param the type the operator evaluates to + */ + @SuppressWarnings({"unchecked","rawtypes"}) + public static CtBinaryOperator swapCtBinaryOperator(CtBinaryOperator ctBinaryOperator) { + CtBinaryOperator result = ctBinaryOperator.clone(); + + CtExpression left = result.getLeftHandOperand(); + CtExpression right = result.getRightHandOperand(); + + // NOTE: this only implements a few cases, for other non-commutative operators, this will break code + result.setKind(switch (ctBinaryOperator.getKind()) { + // a < b => b > a + case LT -> BinaryOperatorKind.GT; + // a <= b => b >= a + case LE -> BinaryOperatorKind.GE; + // a >= b => b <= a + case GE -> BinaryOperatorKind.LE; + // a > b => b < a + case GT -> BinaryOperatorKind.LT; + default -> ctBinaryOperator.getKind(); + }); + + // swap the left and right + result.setLeftHandOperand(right); + result.setRightHandOperand(left); + + return result; + } + + /** + * Replaces {@link spoon.reflect.code.CtVariableRead} in the provided expression if they are effectively final + * and their value is known. + * + * @param ctExpression the expression to resolve. If it is {@code null}, then {@code null} is returned + * @return the resolved expression. It will be cloned and detached from the {@link CtModel} + * @param the type of the expression + */ + public static CtExpression resolveConstant(CtExpression ctExpression) { + if (ctExpression == null) return null; + + Evaluator evaluator = new Evaluator(InlineVariableRead.create(true)); + + return evaluator.evaluate(ctExpression); + } + + /** + * Converts a binary operator like < to <= or > to >= and adjusts the operands accordingly + * to make finding patterns on them easier by not having to special-case them. Additionally, + * one can specify a predicate to swap the operands if necessary. For example, to ensure that + * a literal is always on the right-hand side. + * + * @param shouldSwap the left and right hands are passed to it, and it should return true if they should be swapped and false if nothing should be changed + * @param ctBinaryOperator the operator to normalize, can be of any kind + * @return the normalized operator or the given operator if it is not supported + * @param the type the operator evaluates to + */ + public static CtBinaryOperator normalizeBy( + BiPredicate, ? super CtExpression> shouldSwap, + CtBinaryOperator ctBinaryOperator + ) { + CtExpression left = ctBinaryOperator.getLeftHandOperand(); + CtExpression right = ctBinaryOperator.getRightHandOperand(); + + BinaryOperatorKind operator = ctBinaryOperator.getKind(); + + CtBinaryOperator result = ctBinaryOperator.clone(); + result.setKind(operator); + result.setLeftHandOperand(left.clone()); + result.setRightHandOperand(right.clone()); + + // check if the left and right have to be swapped. To do that, the operator must be inverted: + // a <= b => b >= a + // a < b => b > a + // a >= b => b <= a + // a > b => b < a + // + // ^ in this example it is expected that the b should be on the left + if (shouldSwap.test(left, right)) { + result = swapCtBinaryOperator(result); + } + + // in this step < and > are adjusted to <= and >= : + // a < b => a <= b - 1 + // a > b => a >= b + 1 + + return ExpressionUtil.normalize(result); + } + + /** + * Converts a binary operator like 'a < b' to 'a <= b - 1' or 'a > b' to 'a >= b + 1'. + * + * @param ctBinaryOperator the operator to normalize, can be of any kind + * @return the normalized operator or the given operator if it is not supported + * @param the type the operator evaluates to + */ + private static CtBinaryOperator normalize(CtBinaryOperator ctBinaryOperator) { + // the following primitive types exist: + // - byte + // - short + // - int + // - long + // - float + // - double + // - boolean + // - char + // + // of those the following are not `Number`: + // - boolean + // - char + + if (!Set.of(BinaryOperatorKind.LT, BinaryOperatorKind.GT).contains(ctBinaryOperator.getKind()) + || !ctBinaryOperator.getRightHandOperand().getType().isPrimitive()) { + return ctBinaryOperator; + } + + // the literal to add/subtract. Simply setting it to 1 is not enough, because + // 1 is of type int and the other side might for example be a double or float + CtLiteral step = ctBinaryOperator.getFactory().Core().createLiteral(); + + Predicate> isCharacter = ty -> TypeUtil.isTypeEqualTo(ty, char.class, java.lang.Character.class); + if (isCharacter.test(ctBinaryOperator.getRightHandOperand().getType())) { + // for character use an integer literal + step.setValue((char) 1); + step.setType(ctBinaryOperator.getFactory().Type().characterPrimitiveType()); + } else { + // this assumes that < and > are only used with numbers + step.setValue(FoldUtils.convert(ctBinaryOperator.getRightHandOperand().getType(), ((Number) 1).doubleValue())); + step.setType(ctBinaryOperator.getRightHandOperand().getType()); + } + + CtBinaryOperator result = ctBinaryOperator.clone(); + if (ctBinaryOperator.getKind() == BinaryOperatorKind.LT) { + // < => <= - 1 + result.setKind(BinaryOperatorKind.LE); + result.setRightHandOperand(FactoryUtil.createBinaryOperator( + ctBinaryOperator.getRightHandOperand(), + step, + BinaryOperatorKind.MINUS + )); + } else if (ctBinaryOperator.getKind() == BinaryOperatorKind.GT) { + // > => >= + 1 + result.setKind(BinaryOperatorKind.GE); + result.setRightHandOperand(FactoryUtil.createBinaryOperator( + ctBinaryOperator.getRightHandOperand(), + step, + BinaryOperatorKind.PLUS + )); + } + + // simplify the resulting operator + result.setLeftHandOperand(ExpressionUtil.resolveCtExpression(result.getLeftHandOperand())); + // if the operand was a literal, it might have been promoted + if (result.getLeftHandOperand() instanceof CtLiteral ctLiteral) { + result.setLeftHandOperand(ExpressionUtil.castLiteral( + ExpressionUtil.getExpressionType(ctBinaryOperator.getLeftHandOperand()), + ctLiteral + )); + } + + result.setRightHandOperand(ExpressionUtil.resolveCtExpression(result.getRightHandOperand())); + if (result.getRightHandOperand() instanceof CtLiteral ctLiteral) { + result.setRightHandOperand(ExpressionUtil.castLiteral( + ExpressionUtil.getExpressionType(ctBinaryOperator.getRightHandOperand()), + ctLiteral + )); + } + + return result; + } + + @SuppressWarnings("unchecked") + public static CtExpression negate(CtExpression ctExpression) { + // !(!(a)) => a + if (ctExpression instanceof CtUnaryOperator ctUnaryOperator && ctUnaryOperator.getKind() == UnaryOperatorKind.NOT) { + return (CtExpression) ctUnaryOperator.getOperand(); + } + + if (ctExpression instanceof CtBinaryOperator ctBinaryOperator) { + CtBinaryOperator result = ctBinaryOperator.clone(); + switch (ctBinaryOperator.getKind()) { + // !(a == b) -> a != b + case EQ -> { + result.setKind(BinaryOperatorKind.NE); + return result; + } + // !(a != b) -> a == b + // + // a | b | a ^ b + // 0 | 0 | 0 + // 0 | 1 | 1 + // 1 | 0 | 1 + // 1 | 1 | 0 + // => !(a ^ b) -> a == b + case NE, BITXOR -> { + result.setKind(BinaryOperatorKind.EQ); + return result; + } + // !(a && b) -> !a || !b + case AND -> { + result.setKind(BinaryOperatorKind.OR); + result.setLeftHandOperand(negate(result.getLeftHandOperand())); + result.setRightHandOperand(negate(result.getRightHandOperand())); + return result; + } + // !(a || b) -> !a && !b + case OR -> { + result.setKind(BinaryOperatorKind.AND); + result.setLeftHandOperand(negate(result.getLeftHandOperand())); + result.setRightHandOperand(negate(result.getRightHandOperand())); + return result; + } + // !(a >= b) -> a < b + case GE -> { + result.setKind(BinaryOperatorKind.LT); + return result; + } + // !(a > b) -> a <= b + case GT -> { + result.setKind(BinaryOperatorKind.LE); + return result; + } + // !(a <= b) -> a > b + case LE -> { + result.setKind(BinaryOperatorKind.GT); + return result; + } + // !(a < b) -> a >= b + case LT -> { + result.setKind(BinaryOperatorKind.GE); + return result; + } + } + } + + return FactoryUtil.createUnaryOperator(UnaryOperatorKind.NOT, ctExpression.clone()); + } + + public static CtExpression resolveCtExpression(CtExpression ctExpression) { + if (ctExpression == null) return null; + + // Spoon's partiallyEvaluate is broken, not configurable, and fixing it would be too much work. + // Therefore, we use our own implementation. + PartialEvaluator evaluator = new Evaluator(); + + return evaluator.evaluate(ctExpression); + } + + public static CtExpression castExpression(Class targetType, CtExpression ctExpression) { + return castExpression(ctExpression.getFactory().Type().createReference(targetType), ctExpression); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static CtLiteral castLiteral(CtTypeReference type, CtLiteral literal) { + CtLiteral result = literal.clone(); + result.setType(type.clone()); + + // casting a primitive to a string: + if (TypeUtil.isTypeEqualTo(type, String.class) && literal.getType().isPrimitive()) { + result.setValue(literal.getValue().toString()); + return result; + } + + // It is not possible to cast an Integer to a Double directly, which is a problem. + CtTypeReference targetType = type.unbox(); + if (targetType.isPrimitive()) { + // the FoldUtils.convert method only works for Number -> Number conversions + if (TypeUtil.isSubtypeOf(targetType.box(), Number.class)) { + // for instances of Number, one can use the convert method: + if (literal.getValue() instanceof Number number) { + result.setValue(FoldUtils.convert(type, number)); + } else { + // primitive types that do not implement Number are: + // boolean, char + + // NOTE: it does not make sense to cast a boolean to any other primitive type + if (literal.getValue() instanceof Character character) { + result.setValue(FoldUtils.convert(type, (int) character)); + } + } + } + + if (TypeUtil.isTypeEqualTo(targetType, char.class)) { + if (literal.getValue() instanceof Number number) { + result.setValue((char) number.intValue()); + } else { + result.setValue((char) literal.getValue()); + } + } else if (TypeUtil.isTypeEqualTo(targetType, boolean.class)) { + result.setValue((boolean) literal.getValue()); + } + } else { + result.setValue(type.getActualClass().cast(literal.getValue())); + } + + return result; + } + + // returns the type of the expression after applying the type casts + public static CtTypeReference getExpressionType(CtExpression ctExpression) { + CtTypeReference result = ctExpression.getType(); + + List> typeCasts = ctExpression.getTypeCasts(); + if (!typeCasts.isEmpty()) { + result = typeCasts.get(0); + } + + return result; + } + + @SuppressWarnings("unchecked") + public static > E castExpression(CtTypeReference type, CtExpression ctExpression) { + // no need to cast if the type is the same + if (getExpressionType(ctExpression).equals(type)) { + return (E) ctExpression; + } + + List> typeCasts = new ArrayList<>(ctExpression.getTypeCasts()); + typeCasts.add(0, type.clone()); + ctExpression.setTypeCasts(typeCasts); + + return (E) RemoveRedundantCasts.removeRedundantCasts(ctExpression); + } +} diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/FactoryUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/FactoryUtil.java new file mode 100644 index 00000000..09f6aa80 --- /dev/null +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/FactoryUtil.java @@ -0,0 +1,115 @@ +package de.firemage.autograder.core.integrated; + +import de.firemage.autograder.core.integrated.evaluator.fold.FoldUtils; +import spoon.reflect.code.BinaryOperatorKind; +import spoon.reflect.code.CtBinaryOperator; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtUnaryOperator; +import spoon.reflect.code.UnaryOperatorKind; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; + +import java.util.Arrays; +import java.util.List; + +public class FactoryUtil { + private FactoryUtil() { + } + + @SuppressWarnings("unchecked") + public static CtLiteral makeLiteralNumber(CtTypeReference ctTypeReference, Number number) { + Object value = FoldUtils.convert(ctTypeReference, number); + + return makeLiteral(ctTypeReference, (T) value); + } + + /** + * Makes a new literal with the given value and type. + * + * @param ctTypeReference a reference to the type of the literal + * @param value the value of the literal + * @param the type of the value + * + * @return a new literal with the given value, note that the base is not set + */ + public static CtLiteral makeLiteral(CtTypeReference ctTypeReference, T value) { + CtLiteral literal = ctTypeReference.getFactory().createLiteral(); + literal.setType(ctTypeReference.clone()); + literal.setValue(value); + return literal; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static CtBinaryOperator createBinaryOperator( + CtExpression leftHandOperand, + CtExpression rightHandOperand, + BinaryOperatorKind operatorKind + ) { + Factory factory = leftHandOperand.getFactory(); + if (factory == null) { + factory = rightHandOperand.getFactory(); + } + + CtBinaryOperator ctBinaryOperator = factory.createBinaryOperator( + leftHandOperand.clone(), + rightHandOperand.clone(), + operatorKind + ); + + if (ctBinaryOperator.getType() == null) { + ctBinaryOperator.setType(FoldUtils.inferType(ctBinaryOperator)); + } + + return ctBinaryOperator; + } + + @SuppressWarnings({"unchecked","rawtypes"}) + public static CtUnaryOperator createUnaryOperator(UnaryOperatorKind operatorKind, CtExpression ctExpression) { + CtUnaryOperator ctUnaryOperator = ctExpression.getFactory().createUnaryOperator(); + ctUnaryOperator.setOperand(ctExpression.clone()); + ctUnaryOperator.setKind(operatorKind); + + if (ctUnaryOperator.getType() == null) { + ctUnaryOperator.setType(FoldUtils.inferType(ctUnaryOperator)); + } + + return ctUnaryOperator; + } + + /** + * Creates a static invocation of the given method on the given target type. + * + * @param targetType the type on which the method is defined + * @param methodName the name of the method + * @param parameters the parameters to pass to the method + * @return the invocation + * @param the result type of the invocation + */ + public static CtInvocation createStaticInvocation( + CtTypeReference targetType, + String methodName, + CtExpression... parameters + ) { + Factory factory = targetType.getFactory(); + + CtMethod methodHandle = null; + List> potentialMethods = targetType.getTypeDeclaration().getMethodsByName(methodName); + if (potentialMethods.size() == 1) { + methodHandle = (CtMethod) potentialMethods.get(0); + } else { + methodHandle = targetType.getTypeDeclaration().getMethod( + methodName, + Arrays.stream(parameters).map(ExpressionUtil::getExpressionType).toArray(CtTypeReference[]::new) + ); + } + + return factory.createInvocation( + factory.createTypeAccess(methodHandle.getDeclaringType().getReference()), + methodHandle.getReference(), + parameters + ); + } +} diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ForLoopRange.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ForLoopRange.java index 6842ef7d..8b23c0d9 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ForLoopRange.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/ForLoopRange.java @@ -33,12 +33,12 @@ public static Optional fromCtFor(CtFor ctFor) { // check that this variable is a local variable && ctVariableAccess.getVariable().getDeclaration() instanceof CtLocalVariable localVariable // which is declared before the loop - && SpoonUtil.getPreviousStatement(ctFor) + && StatementUtil.getPreviousStatement(ctFor) .map(statement -> statement instanceof CtVariable ctVariable && ctVariable.getReference().equals(ctVariableAccess.getVariable())) .orElse(false) // the loop variable must not be used after the loop - && SpoonUtil.getNextStatements(ctFor).stream().noneMatch(statement -> UsesFinder.variableUses(localVariable).nestedIn(statement).hasAny()) + && StatementUtil.getNextStatements(ctFor).stream().noneMatch(statement -> UsesFinder.variableUses(localVariable).nestedIn(statement).hasAny()) ) { potentialLoopVariable = localVariable; } else if (ctFor.getForInit().size() == 1 && ctFor.getForInit().get(0) instanceof CtLocalVariable ctLocalVariable) { @@ -54,7 +54,7 @@ public static Optional fromCtFor(CtFor ctFor) { CtLocalVariable ctLocalVariable = (CtLocalVariable) potentialLoopVariable; // ensure that the loop has exactly one variable initialized with a literal value - CtExpression start = SpoonUtil.resolveCtExpression(ctLocalVariable.getDefaultExpression()); + CtExpression start = ExpressionUtil.resolveCtExpression(ctLocalVariable.getDefaultExpression()); // ensure that it is initialized with some integer if (!(TypeUtil.isTypeEqualTo(start.getType(), int.class, Integer.class)) @@ -83,12 +83,12 @@ public static Optional fromCtFor(CtFor ctFor) { // check for i <= n - 1, so it is not converted to (n - 1) + 1 if (end instanceof CtBinaryOperator ctBinaryOperator && ctBinaryOperator.getKind() == BinaryOperatorKind.MINUS - && SpoonUtil.isIntegerLiteral(SpoonUtil.resolveCtExpression(ctBinaryOperator.getRightHandOperand()), 1)) { + && ExpressionUtil.isIntegerLiteral(ExpressionUtil.resolveCtExpression(ctBinaryOperator.getRightHandOperand()), 1)) { end = (CtExpression) ctBinaryOperator.getLeftHandOperand(); } else { - end = SpoonUtil.createBinaryOperator( + end = FactoryUtil.createBinaryOperator( end, - SpoonUtil.makeLiteral(end.getType(), 1), + FactoryUtil.makeLiteral(end.getType(), 1), BinaryOperatorKind.PLUS ); } @@ -104,8 +104,8 @@ public static Optional fromCtFor(CtFor ctFor) { public CtExpression length() { CtExpression length = this.end; // special case init with 0, because end - 0 = end - if (!SpoonUtil.isIntegerLiteral(this.start, 0)) { - length = SpoonUtil.createBinaryOperator( + if (!ExpressionUtil.isIntegerLiteral(this.start, 0)) { + length = FactoryUtil.createBinaryOperator( this.end, this.start, BinaryOperatorKind.MINUS diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/MethodUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/MethodUtil.java index 6d7a7145..51fa44ee 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/MethodUtil.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/MethodUtil.java @@ -4,6 +4,7 @@ import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLambda; import spoon.reflect.code.CtStatement; +import spoon.reflect.code.CtTypeAccess; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtMethod; import spoon.reflect.factory.TypeFactory; @@ -104,4 +105,30 @@ public static boolean isInMainMethod(CtElement ctElement) { return MethodUtil.isMainMethod(ctMethod); } + + public static boolean isGetter(CtMethod method) { + return method.getSimpleName().startsWith("get") + && method.getParameters().isEmpty() + && !method.getType().getSimpleName().equals("void") + && (method.isAbstract() || StatementUtil.getEffectiveStatements(method.getBody()).size() == 1); + } + + public static boolean isSetter(CtMethod method) { + return method.getSimpleName().startsWith("set") + && method.getParameters().size() == 1 + && method.getType().getSimpleName().equals("void") + && (method.isAbstract() || StatementUtil.getEffectiveStatements(method.getBody()).size() == 1); + } + + public static boolean isStaticCallTo(CtInvocation invocation, String typeName, String methodName) { + return invocation.getExecutable().isStatic() + && invocation.getTarget() instanceof CtTypeAccess access + && access.getAccessedType().getQualifiedName().equals(typeName) + && invocation.getExecutable().getSimpleName().equals(methodName); + } + + public static boolean isInSetter(CtElement ctElement) { + CtMethod parent = ctElement.getParent(CtMethod.class); + return parent != null && isSetter(parent); + } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/SpoonUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/SpoonUtil.java deleted file mode 100644 index 9583396c..00000000 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/SpoonUtil.java +++ /dev/null @@ -1,1076 +0,0 @@ -package de.firemage.autograder.core.integrated; - -import de.firemage.autograder.core.integrated.effects.AssignmentStatement; -import de.firemage.autograder.core.integrated.effects.Effect; -import de.firemage.autograder.core.integrated.effects.TerminalEffect; -import de.firemage.autograder.core.integrated.effects.TerminalStatement; -import de.firemage.autograder.core.integrated.evaluator.Evaluator; -import de.firemage.autograder.core.integrated.evaluator.fold.FoldUtils; -import de.firemage.autograder.core.integrated.evaluator.fold.InlineVariableRead; -import de.firemage.autograder.core.integrated.evaluator.fold.RemoveRedundantCasts; -import spoon.reflect.CtModel; -import spoon.reflect.code.BinaryOperatorKind; -import spoon.reflect.code.CtBinaryOperator; -import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtBodyHolder; -import spoon.reflect.code.CtBreak; -import spoon.reflect.code.CtCase; -import spoon.reflect.code.CtComment; -import spoon.reflect.code.CtExpression; -import spoon.reflect.code.CtInvocation; -import spoon.reflect.code.CtJavaDoc; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.code.CtStatement; -import spoon.reflect.code.CtStatementList; -import spoon.reflect.code.CtTypeAccess; -import spoon.reflect.code.CtUnaryOperator; -import spoon.reflect.code.CtVariableWrite; -import spoon.reflect.code.LiteralBase; -import spoon.reflect.code.UnaryOperatorKind; -import spoon.reflect.cu.SourcePosition; -import spoon.reflect.cu.position.CompoundSourcePosition; -import spoon.reflect.declaration.CtCompilationUnit; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtNamedElement; -import spoon.reflect.declaration.CtParameter; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypedElement; -import spoon.reflect.declaration.CtVariable; -import spoon.reflect.declaration.ModifierKind; -import spoon.reflect.eval.PartialEvaluator; -import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtExecutableReference; -import spoon.reflect.reference.CtFieldReference; -import spoon.reflect.reference.CtReference; -import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.reference.CtVariableReference; - -import org.apache.commons.io.FilenameUtils; - -import java.io.File; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; -import java.util.function.BiPredicate; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -public final class SpoonUtil { - private SpoonUtil() { - } - - public static boolean isString(CtTypeReference type) { - return TypeUtil.isTypeEqualTo(type, java.lang.String.class); - } - - public static Optional> isToStringCall(CtExpression expression) { - if (!SpoonUtil.isString(expression.getType())) { - return Optional.empty(); - } - - if (expression instanceof CtInvocation invocation && - MethodUtil.isSignatureEqualTo(invocation.getExecutable(), java.lang.String.class, "toString")) { - return Optional.of(invocation.getTarget().getType()); - } else { - return Optional.empty(); - } - } - - public static boolean isStringLiteral(CtExpression expression, String value) { - return expression instanceof CtLiteral literal && literal.getValue() != null && - literal.getValue().equals(value); - } - - public static boolean isNullLiteral(CtExpression expression) { - return SpoonUtil.resolveConstant(expression) instanceof CtLiteral literal && literal.getValue() == null; - } - - public static boolean isIntegerLiteral(CtExpression expression, int value) { - return expression instanceof CtLiteral literal && literal.getValue().equals(value); - } - - public static boolean isBoolean(CtTypedElement ctTypedElement) { - CtTypeReference ctTypeReference = ctTypedElement.getType(); - return ctTypeReference != null && TypeUtil.isTypeEqualTo(ctTypeReference, boolean.class, Boolean.class); - } - - public static Optional tryGetBooleanLiteral(CtExpression expression) { - if (SpoonUtil.resolveConstant(expression) instanceof CtLiteral literal - && literal.getValue() != null - && isBoolean(literal)) { - - return Optional.of((Boolean) literal.getValue()); - } else { - return Optional.empty(); - } - } - - public static Optional tryGetStringLiteral(CtExpression expression) { - if (SpoonUtil.resolveConstant(expression) instanceof CtLiteral literal - && literal.getValue() != null - && TypeUtil.isTypeEqualTo(literal.getType(), java.lang.String.class)) { - - return Optional.of((String) literal.getValue()); - } else { - return Optional.empty(); - } - } - - // equals impl of CtLiteral seems to be broken - public static boolean areLiteralsEqual( - CtLiteral left, - CtLiteral right - ) { - if (left == null && right == null) { - return true; - } else if (left == null || right == null) { - return false; - } - - if (left.getValue() == null) { - return right.getValue() == null; - } else if (right.getValue() == null) { - return false; - } - - if (left.getValue() instanceof Character l && right.getValue() instanceof Character r) { - return l.equals(r); - } else if (left.getValue() instanceof Number l && right.getValue() instanceof Character r) { - return l.intValue() == (int) r; - } else if (left.getValue() instanceof Character l && right.getValue() instanceof Number r) { - return (int) l == r.intValue(); - } - - if (!(left.getValue() instanceof Number valLeft) - || !(right.getValue() instanceof Number valRight)) { - return left.getValue() == right.getValue() || left.getValue().equals(right.getValue()); - } - - if (valLeft instanceof Float || valLeft instanceof Double || valRight instanceof Float - || valRight instanceof Double) { - return valLeft.doubleValue() == valRight.doubleValue(); - } - - return valLeft.longValue() == valRight.longValue(); - } - - @SuppressWarnings("unchecked") - public static CtLiteral makeLiteralNumber(CtTypeReference ctTypeReference, Number number) { - Object value = FoldUtils.convert(ctTypeReference, number); - - return SpoonUtil.makeLiteral(ctTypeReference, (T) value); - } - - /** - * Makes a new literal with the given value and type. - * - * @param ctTypeReference a reference to the type of the literal - * @param value the value of the literal - * @param the type of the value - * - * @return a new literal with the given value, note that the base is not set - */ - public static CtLiteral makeLiteral(CtTypeReference ctTypeReference, T value) { - CtLiteral literal = ctTypeReference.getFactory().createLiteral(); - literal.setType(ctTypeReference.clone()); - literal.setValue(value); - return literal; - } - - public static List> getElementsOfExpression(CtExpression ctExpression) { - var supportedCollections = Stream.of( - java.util.List.class, - java.util.Set.class, - java.util.Collection.class - ).map((Class e) -> ctExpression.getFactory().Type().createReference(e)); - - List> result = new ArrayList<>(); - - CtTypeReference expressionType = ctExpression.getType(); - if (supportedCollections.noneMatch(ty -> ty.equals(expressionType) || expressionType.isSubtypeOf(ty))) { - return result; - } - - if (ctExpression instanceof CtInvocation ctInvocation - && ctInvocation.getTarget() instanceof CtTypeAccess) { - CtExecutableReference ctExecutableReference = ctInvocation.getExecutable(); - if (ctExecutableReference.getSimpleName().equals("of")) { - result.addAll(ctInvocation.getArguments()); - } - } - - return result; - } - - private static List getEffectiveStatements(Collection statements) { - return statements.stream().flatMap(ctStatement -> { - // flatten blocks - if (ctStatement instanceof CtStatementList ctStatementList) { - return getEffectiveStatements(ctStatementList.getStatements()).stream(); - } else { - return Stream.of(ctStatement); - } - }).filter(statement -> !(statement instanceof CtComment)).toList(); - } - - - public static CtLiteral minimumValue(CtLiteral ctLiteral) { - CtLiteral result = ctLiteral.getFactory().createLiteral(); - result.setBase(LiteralBase.DECIMAL); - result.setType(ctLiteral.getType().clone()); - - // - byte - // - short - // - int - // - long - // - float - // - double - // - boolean - // - char - - Object value = ctLiteral.getValue(); - Map, Object> minimumValueMapping = Map.ofEntries( - Map.entry(byte.class, Byte.MIN_VALUE), - Map.entry(Byte.class, Byte.MIN_VALUE), - Map.entry(short.class, Short.MIN_VALUE), - Map.entry(Short.class, Short.MIN_VALUE), - Map.entry(int.class, Integer.MIN_VALUE), - Map.entry(Integer.class, Integer.MIN_VALUE), - Map.entry(long.class, Long.MIN_VALUE), - Map.entry(Long.class, Long.MIN_VALUE), - Map.entry(float.class, Float.MIN_VALUE), - Map.entry(Float.class, Float.MIN_VALUE), - Map.entry(double.class, Double.MIN_VALUE), - Map.entry(Double.class, Double.MIN_VALUE), - Map.entry(boolean.class, false), - Map.entry(Boolean.class, false), - Map.entry(char.class, Character.MIN_VALUE), - Map.entry(Character.class, Character.MIN_VALUE) - ); - - result.setValue(minimumValueMapping.get(value.getClass())); - - return result; - } - - public static CtLiteral maximumValue(CtLiteral ctLiteral) { - CtLiteral result = ctLiteral.getFactory().createLiteral(); - result.setBase(LiteralBase.DECIMAL); - result.setType(ctLiteral.getType().clone()); - - // - byte - // - short - // - int - // - long - // - float - // - double - // - boolean - // - char - - Object value = ctLiteral.getValue(); - Map, Object> maximumValueMapping = Map.ofEntries( - Map.entry(byte.class, Byte.MAX_VALUE), - Map.entry(Byte.class, Byte.MAX_VALUE), - Map.entry(short.class, Short.MAX_VALUE), - Map.entry(Short.class, Short.MAX_VALUE), - Map.entry(int.class, Integer.MAX_VALUE), - Map.entry(Integer.class, Integer.MAX_VALUE), - Map.entry(long.class, Long.MAX_VALUE), - Map.entry(Long.class, Long.MAX_VALUE), - Map.entry(float.class, Float.MAX_VALUE), - Map.entry(Float.class, Float.MAX_VALUE), - Map.entry(double.class, Double.MAX_VALUE), - Map.entry(Double.class, Double.MAX_VALUE), - Map.entry(boolean.class, true), - Map.entry(Boolean.class, true), - Map.entry(char.class, Character.MAX_VALUE), - Map.entry(Character.class, Character.MAX_VALUE) - ); - - result.setValue(maximumValueMapping.get(value.getClass())); - - return result; - } - - /** - * Converts a binary operator like 'a < b' to 'a <= b - 1' or 'a > b' to 'a >= b + 1'. - * - * @param ctBinaryOperator the operator to normalize, can be of any kind - * @return the normalized operator or the given operator if it is not supported - * @param the type the operator evaluates to - */ - private static CtBinaryOperator normalize(CtBinaryOperator ctBinaryOperator) { - // the following primitive types exist: - // - byte - // - short - // - int - // - long - // - float - // - double - // - boolean - // - char - // - // of those the following are not `Number`: - // - boolean - // - char - - if (!Set.of(BinaryOperatorKind.LT, BinaryOperatorKind.GT).contains(ctBinaryOperator.getKind()) - || !ctBinaryOperator.getRightHandOperand().getType().isPrimitive()) { - return ctBinaryOperator; - } - - // the literal to add/subtract. Simply setting it to 1 is not enough, because - // 1 is of type int and the other side might for example be a double or float - CtLiteral step = ctBinaryOperator.getFactory().Core().createLiteral(); - - Predicate> isCharacter = ty -> TypeUtil.isTypeEqualTo(ty, char.class, java.lang.Character.class); - if (isCharacter.test(ctBinaryOperator.getRightHandOperand().getType())) { - // for character use an integer literal - step.setValue((char) 1); - step.setType(ctBinaryOperator.getFactory().Type().characterPrimitiveType()); - } else { - // this assumes that < and > are only used with numbers - step.setValue(FoldUtils.convert(ctBinaryOperator.getRightHandOperand().getType(), ((Number) 1).doubleValue())); - step.setType(ctBinaryOperator.getRightHandOperand().getType()); - } - - CtBinaryOperator result = ctBinaryOperator.clone(); - if (ctBinaryOperator.getKind() == BinaryOperatorKind.LT) { - // < => <= - 1 - result.setKind(BinaryOperatorKind.LE); - result.setRightHandOperand(SpoonUtil.createBinaryOperator( - ctBinaryOperator.getRightHandOperand(), - step, - BinaryOperatorKind.MINUS - )); - } else if (ctBinaryOperator.getKind() == BinaryOperatorKind.GT) { - // > => >= + 1 - result.setKind(BinaryOperatorKind.GE); - result.setRightHandOperand(SpoonUtil.createBinaryOperator( - ctBinaryOperator.getRightHandOperand(), - step, - BinaryOperatorKind.PLUS - )); - } - - // simplify the resulting operator - result.setLeftHandOperand(SpoonUtil.resolveCtExpression(result.getLeftHandOperand())); - // if the operand was a literal, it might have been promoted - if (result.getLeftHandOperand() instanceof CtLiteral ctLiteral) { - result.setLeftHandOperand(SpoonUtil.castLiteral( - SpoonUtil.getExpressionType(ctBinaryOperator.getLeftHandOperand()), - ctLiteral - )); - } - - result.setRightHandOperand(SpoonUtil.resolveCtExpression(result.getRightHandOperand())); - if (result.getRightHandOperand() instanceof CtLiteral ctLiteral) { - result.setRightHandOperand(SpoonUtil.castLiteral( - SpoonUtil.getExpressionType(ctBinaryOperator.getRightHandOperand()), - ctLiteral - )); - } - - return result; - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public static CtBinaryOperator createBinaryOperator( - CtExpression leftHandOperand, - CtExpression rightHandOperand, - BinaryOperatorKind operatorKind - ) { - Factory factory = leftHandOperand.getFactory(); - if (factory == null) { - factory = rightHandOperand.getFactory(); - } - - CtBinaryOperator ctBinaryOperator = factory.createBinaryOperator( - leftHandOperand.clone(), - rightHandOperand.clone(), - operatorKind - ); - - if (ctBinaryOperator.getType() == null) { - ctBinaryOperator.setType(FoldUtils.inferType(ctBinaryOperator)); - } - - return ctBinaryOperator; - } - - @SuppressWarnings({"unchecked","rawtypes"}) - public static CtUnaryOperator createUnaryOperator(UnaryOperatorKind operatorKind, CtExpression ctExpression) { - CtUnaryOperator ctUnaryOperator = ctExpression.getFactory().createUnaryOperator(); - ctUnaryOperator.setOperand(ctExpression.clone()); - ctUnaryOperator.setKind(operatorKind); - - if (ctUnaryOperator.getType() == null) { - ctUnaryOperator.setType(FoldUtils.inferType(ctUnaryOperator)); - } - - return ctUnaryOperator; - } - - /** - * Swaps the operands of a binary operator. - * - * @param ctBinaryOperator the operator to swap, can be of any kind - * @return the cloned version with the operands swapped or the given operator if it is not supported - * @param the type the operator evaluates to - */ - @SuppressWarnings({"unchecked","rawtypes"}) - public static CtBinaryOperator swapCtBinaryOperator(CtBinaryOperator ctBinaryOperator) { - CtBinaryOperator result = ctBinaryOperator.clone(); - - CtExpression left = result.getLeftHandOperand(); - CtExpression right = result.getRightHandOperand(); - - // NOTE: this only implements a few cases, for other non-commutative operators, this will break code - result.setKind(switch (ctBinaryOperator.getKind()) { - // a < b => b > a - case LT -> BinaryOperatorKind.GT; - // a <= b => b >= a - case LE -> BinaryOperatorKind.GE; - // a >= b => b <= a - case GE -> BinaryOperatorKind.LE; - // a > b => b < a - case GT -> BinaryOperatorKind.LT; - default -> ctBinaryOperator.getKind(); - }); - - // swap the left and right - result.setLeftHandOperand(right); - result.setRightHandOperand(left); - - return result; - } - - /** - * Replaces {@link spoon.reflect.code.CtVariableRead} in the provided expression if they are effectively final - * and their value is known. - * - * @param ctExpression the expression to resolve. If it is {@code null}, then {@code null} is returned - * @return the resolved expression. It will be cloned and detached from the {@link CtModel} - * @param the type of the expression - */ - public static CtExpression resolveConstant(CtExpression ctExpression) { - if (ctExpression == null) return null; - - Evaluator evaluator = new Evaluator(InlineVariableRead.create(true)); - - return evaluator.evaluate(ctExpression); - } - - /** - * Converts a binary operator like < to <= or > to >= and adjusts the operands accordingly - * to make finding patterns on them easier by not having to special-case them. Additionally, - * one can specify a predicate to swap the operands if necessary. For example, to ensure that - * a literal is always on the right-hand side. - * - * @param shouldSwap the left and right hands are passed to it, and it should return true if they should be swapped and false if nothing should be changed - * @param ctBinaryOperator the operator to normalize, can be of any kind - * @return the normalized operator or the given operator if it is not supported - * @param the type the operator evaluates to - */ - public static CtBinaryOperator normalizeBy( - BiPredicate, ? super CtExpression> shouldSwap, - CtBinaryOperator ctBinaryOperator - ) { - CtExpression left = ctBinaryOperator.getLeftHandOperand(); - CtExpression right = ctBinaryOperator.getRightHandOperand(); - - BinaryOperatorKind operator = ctBinaryOperator.getKind(); - - CtBinaryOperator result = ctBinaryOperator.clone(); - result.setKind(operator); - result.setLeftHandOperand(left.clone()); - result.setRightHandOperand(right.clone()); - - // check if the left and right have to be swapped. To do that, the operator must be inverted: - // a <= b => b >= a - // a < b => b > a - // a >= b => b <= a - // a > b => b < a - // - // ^ in this example it is expected that the b should be on the left - if (shouldSwap.test(left, right)) { - result = swapCtBinaryOperator(result); - } - - // in this step < and > are adjusted to <= and >= : - // a < b => a <= b - 1 - // a > b => a >= b + 1 - - return normalize(result); - } - - @SuppressWarnings("unchecked") - public static CtExpression negate(CtExpression ctExpression) { - // !(!(a)) => a - if (ctExpression instanceof CtUnaryOperator ctUnaryOperator && ctUnaryOperator.getKind() == UnaryOperatorKind.NOT) { - return (CtExpression) ctUnaryOperator.getOperand(); - } - - if (ctExpression instanceof CtBinaryOperator ctBinaryOperator) { - CtBinaryOperator result = ctBinaryOperator.clone(); - switch (ctBinaryOperator.getKind()) { - // !(a == b) -> a != b - case EQ -> { - result.setKind(BinaryOperatorKind.NE); - return result; - } - // !(a != b) -> a == b - // - // a | b | a ^ b - // 0 | 0 | 0 - // 0 | 1 | 1 - // 1 | 0 | 1 - // 1 | 1 | 0 - // => !(a ^ b) -> a == b - case NE, BITXOR -> { - result.setKind(BinaryOperatorKind.EQ); - return result; - } - // !(a && b) -> !a || !b - case AND -> { - result.setKind(BinaryOperatorKind.OR); - result.setLeftHandOperand(negate(result.getLeftHandOperand())); - result.setRightHandOperand(negate(result.getRightHandOperand())); - return result; - } - // !(a || b) -> !a && !b - case OR -> { - result.setKind(BinaryOperatorKind.AND); - result.setLeftHandOperand(negate(result.getLeftHandOperand())); - result.setRightHandOperand(negate(result.getRightHandOperand())); - return result; - } - // !(a >= b) -> a < b - case GE -> { - result.setKind(BinaryOperatorKind.LT); - return result; - } - // !(a > b) -> a <= b - case GT -> { - result.setKind(BinaryOperatorKind.LE); - return result; - } - // !(a <= b) -> a > b - case LE -> { - result.setKind(BinaryOperatorKind.GT); - return result; - } - // !(a < b) -> a >= b - case LT -> { - result.setKind(BinaryOperatorKind.GE); - return result; - } - } - } - - return createUnaryOperator(UnaryOperatorKind.NOT, ctExpression.clone()); - } - - public static List getEffectiveStatements(CtStatement ctStatement) { - if (ctStatement == null) { - return List.of(); - } - - if (ctStatement instanceof CtStatementList ctStatementList) { - return getEffectiveStatements(ctStatementList.getStatements()); - } - - return getEffectiveStatements(List.of(ctStatement)); - } - - public static List getEffectiveStatementsOf(CtBodyHolder ctBodyHolder) { - if (ctBodyHolder == null) { - return List.of(); - } - - CtStatement body = ctBodyHolder.getBody(); - if (body == null) { - return List.of(); - } - - return getEffectiveStatements(body); - } - - public static CtExpression resolveCtExpression(CtExpression ctExpression) { - if (ctExpression == null) return null; - - // Spoon's partiallyEvaluate is broken, not configurable, and fixing it would be too much work. - // Therefore, we use our own implementation. - PartialEvaluator evaluator = new Evaluator(); - - return evaluator.evaluate(ctExpression); - } - - /** - * Extracts a nested statement from a block if possible. - *

- * A statement might be in a block {@code { statement }}. - * This method will extract the statement from the block and return it. - * - * @param statement the statement to unwrap - * @return the given statement or an unwrapped version if possible - */ - public static CtStatement unwrapStatement(CtStatement statement) { - if (statement instanceof CtBlock block) { - List statements = SpoonUtil.getEffectiveStatements(block); - if (statements.size() == 1) { - return statements.get(0); - } - } - return statement; - } - - public static boolean isGetter(CtMethod method) { - return method.getSimpleName().startsWith("get") - && method.getParameters().isEmpty() - && !method.getType().getSimpleName().equals("void") - && (method.isAbstract() || getEffectiveStatements(method.getBody()).size() == 1); - } - - public static boolean isSetter(CtMethod method) { - return method.getSimpleName().startsWith("set") - && method.getParameters().size() == 1 - && method.getType().getSimpleName().equals("void") - && (method.isAbstract() || getEffectiveStatements(method.getBody()).size() == 1); - } - - public static boolean isInSetter(CtElement ctElement) { - CtMethod parent = ctElement.getParent(CtMethod.class); - return parent != null && SpoonUtil.isSetter(parent); - } - - public static boolean isPrimitiveNumeric(CtTypeReference type) { - return type.isPrimitive() - && !type.getQualifiedName().equals("boolean") - && !type.getQualifiedName().equals("char"); - } - - /** - * Creates a static invocation of the given method on the given target type. - * - * @param targetType the type on which the method is defined - * @param methodName the name of the method - * @param parameters the parameters to pass to the method - * @return the invocation - * @param the result type of the invocation - */ - public static CtInvocation createStaticInvocation( - CtTypeReference targetType, - String methodName, - CtExpression... parameters - ) { - Factory factory = targetType.getFactory(); - - CtMethod methodHandle = null; - List> potentialMethods = targetType.getTypeDeclaration().getMethodsByName(methodName); - if (potentialMethods.size() == 1) { - methodHandle = (CtMethod) potentialMethods.get(0); - } else { - methodHandle = targetType.getTypeDeclaration().getMethod( - methodName, - Arrays.stream(parameters).map(SpoonUtil::getExpressionType).toArray(CtTypeReference[]::new) - ); - } - - return factory.createInvocation( - factory.createTypeAccess(methodHandle.getDeclaringType().getReference()), - methodHandle.getReference(), - parameters - ); - } - - public static CtExpression castExpression(Class targetType, CtExpression ctExpression) { - return SpoonUtil.castExpression(ctExpression.getFactory().Type().createReference(targetType), ctExpression); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - public static CtLiteral castLiteral(CtTypeReference type, CtLiteral literal) { - CtLiteral result = literal.clone(); - result.setType(type.clone()); - - // casting a primitive to a string: - if (TypeUtil.isTypeEqualTo(type, String.class) && literal.getType().isPrimitive()) { - result.setValue(literal.getValue().toString()); - return result; - } - - // It is not possible to cast an Integer to a Double directly, which is a problem. - CtTypeReference targetType = type.unbox(); - if (targetType.isPrimitive()) { - // the FoldUtils.convert method only works for Number -> Number conversions - if (TypeUtil.isSubtypeOf(targetType.box(), Number.class)) { - // for instances of Number, one can use the convert method: - if (literal.getValue() instanceof Number number) { - result.setValue(FoldUtils.convert(type, number)); - } else { - // primitive types that do not implement Number are: - // boolean, char - - // NOTE: it does not make sense to cast a boolean to any other primitive type - if (literal.getValue() instanceof Character character) { - result.setValue(FoldUtils.convert(type, (int) character)); - } - } - } - - if (TypeUtil.isTypeEqualTo(targetType, char.class)) { - if (literal.getValue() instanceof Number number) { - result.setValue((char) number.intValue()); - } else { - result.setValue((char) literal.getValue()); - } - } else if (TypeUtil.isTypeEqualTo(targetType, boolean.class)) { - result.setValue((boolean) literal.getValue()); - } - } else { - result.setValue(type.getActualClass().cast(literal.getValue())); - } - - return result; - } - - // returns the type of the expression after applying the type casts - public static CtTypeReference getExpressionType(CtExpression ctExpression) { - CtTypeReference result = ctExpression.getType(); - - List> typeCasts = ctExpression.getTypeCasts(); - if (!typeCasts.isEmpty()) { - result = typeCasts.get(0); - } - - return result; - } - - @SuppressWarnings("unchecked") - public static > E castExpression(CtTypeReference type, CtExpression ctExpression) { - // no need to cast if the type is the same - if (SpoonUtil.getExpressionType(ctExpression).equals(type)) { - return (E) ctExpression; - } - - List> typeCasts = new ArrayList<>(ctExpression.getTypeCasts()); - typeCasts.add(0, type.clone()); - ctExpression.setTypeCasts(typeCasts); - - return (E) RemoveRedundantCasts.removeRedundantCasts(ctExpression); - } - - public static Optional getJavadoc(CtElement element) { - if (element.getComments().isEmpty() || !(element.getComments().get(0) instanceof CtJavaDoc)) { - // TODO lookup inherited javadoc - return Optional.empty(); - } else { - return Optional.of(element.getComments().get(0).asJavaDoc()); - } - } - - public static boolean isStaticCallTo(CtInvocation invocation, String typeName, String methodName) { - return invocation.getExecutable().isStatic() - && invocation.getTarget() instanceof CtTypeAccess access - && access.getAccessedType().getQualifiedName().equals(typeName) - && invocation.getExecutable().getSimpleName().equals(methodName); - } - - public static boolean isEffectivelyFinal(CtVariable ctVariable) { - if (ctVariable.getModifiers().contains(ModifierKind.FINAL)) { - return true; - } - - return UsesFinder.variableUses(ctVariable).ofType(CtVariableWrite.class).hasNone(); - } - - public static Optional> getEffectivelyFinalExpression(CtVariable ctVariable) { - if (!isEffectivelyFinal(ctVariable)) { - return Optional.empty(); - } - - return Optional.ofNullable(ctVariable.getDefaultExpression()); - } - - /** - * Checks if the given element is guaranteed to be immutable. - *

- * Note that when this method returns {@code false}, the type might still be immutable. - * - * @param ctTypeReference the type to check - * @return true if the given element is guaranteed to be immutable, false otherwise - * @param the type of the element - */ - public static boolean isImmutable(CtTypeReference ctTypeReference) { - Deque> queue = new ArrayDeque<>(Collections.singletonList(ctTypeReference)); - Collection> visited = new HashSet<>(); - - while (!queue.isEmpty()) { - CtType ctType = queue.removeFirst().getTypeDeclaration(); - - // if the type is not in the classpath, null is returned - // in those cases, assume that the type is not immutable - if (ctType == null) { - return false; - } - - // skip types that have been checked (those are guaranteed to be immutable) - if (visited.contains(ctType)) { - continue; - } - - // primitive types and strings are immutable as well: - if (ctType.getReference().unbox().isPrimitive() - || TypeUtil.isTypeEqualTo(ctType.getReference(), java.lang.String.class)) { - continue; - } - - // types that are not in the classpath like java.util.ArrayList are shadow types. - // the source code for those is missing, so it is impossible to check if they are immutable. - // => assume they are not immutable - if (ctType.isShadow()) { - return false; - } - - // for a type to be immutable, all of its fields must be final and immutable as well: - for (CtFieldReference ctFieldReference : ctType.getAllFields()) { - if (!SpoonUtil.isEffectivelyFinal(ctFieldReference.getFieldDeclaration())) { - return false; - } - - queue.add(ctFieldReference.getType()); - } - - visited.add(ctType); - } - - return true; - } - - public static void visitCtCompilationUnit(CtModel ctModel, Consumer lambda) { - // it is not possible to visit CtCompilationUnit through the processor API. - // - // in https://github.com/INRIA/spoon/issues/5168 the below code is mentioned as a workaround: - ctModel - .getAllTypes() - .stream() - .map(CtType::getPosition) - .filter(SourcePosition::isValidPosition) - .map(SourcePosition::getCompilationUnit) - // visit each compilation unit only once - .distinct() - .forEach(lambda); - } - - public static CtElement getReferenceDeclaration(CtReference ctReference) { - // this might be null if the reference is not in the source path - // for example, when the reference points to a java.lang type - CtElement target = ctReference.getDeclaration(); - - if (target == null && ctReference instanceof CtTypeReference ctTypeReference) { - target = ctTypeReference.getTypeDeclaration(); - } - - if (target == null && ctReference instanceof CtExecutableReference ctExecutableReference) { - target = ctExecutableReference.getExecutableDeclaration(); - } - - if (target == null && ctReference instanceof CtVariableReference ctVariableReference) { - target = getVariableDeclaration(ctVariableReference); - } - - return target; - } - - public static CtVariable getVariableDeclaration(CtVariableReference ctVariableReference) { - // this might be null if the reference is not in the source path - // for example, when the reference points to a java.lang type - CtVariable target = ctVariableReference.getDeclaration(); - - if (target == null && ctVariableReference instanceof CtFieldReference ctFieldReference) { - target = ctFieldReference.getFieldDeclaration(); - } - - return target; - } - - private static int referenceIndexOf(List list, T element) { - for (int i = 0; i < list.size(); i++) { - if (list.get(i) == element) { - return i; - } - } - - return -1; - } - - /** - * Finds the statement that is before the given statement if possible. - * - * @param ctStatement the statement to find the previous statement of, must not be null - * @return the previous statement or an empty optional if there is no previous statement - */ - public static Optional getPreviousStatement(CtStatement ctStatement) { - List previousStatements = getPreviousStatements(ctStatement); - return previousStatements.isEmpty() ? Optional.empty() : Optional.of(previousStatements.get(previousStatements.size() - 1)); - } - - public static List getPreviousStatements(CtStatement ctStatement) { - List result = new ArrayList<>(); - if (ctStatement.getParent() instanceof CtStatementList ctStatementList) { - List statements = ctStatementList.getStatements(); - int index = referenceIndexOf(statements, ctStatement); - - if (index >= 0) { - result.addAll(statements.subList(0, index)); - } - } - - return result; - } - - public static List getNextStatements(CtStatement ctStatement) { - List result = new ArrayList<>(); - if (ctStatement.getParent() instanceof CtStatementList ctStatementList) { - List statements = ctStatementList.getStatements(); - int index = referenceIndexOf(statements, ctStatement); - - if (index >= 0) { - result.addAll(statements.subList(index + 1, statements.size())); - } - } - - return result; - } - - public static String truncatedSuggestion(CtElement ctElement) { - StringJoiner result = new StringJoiner(System.lineSeparator()); - - for (String line : ctElement.toString().split("\\r?\\n")) { - int newLineLength = 0; - - // this ensures that the truncation is the same on linux and windows - if (!result.toString().contains("\r\n")) { - newLineLength += (int) result.toString().chars().filter(ch -> ch == '\n').count(); - } - - if (result.length() + newLineLength > 150) { - if (line.startsWith(" ")) { - result.add("...".indent(line.length() - line.stripIndent().length()).stripTrailing()); - } else { - result.add("..."); - } - - if (result.toString().startsWith("{")) { - result.add("}"); - } - - break; - } - - result.add(line); - } - - return result.toString(); - } - - public static Optional tryMakeEffect(CtStatement ctStatement) { - return TerminalStatement.of(ctStatement).or(() -> AssignmentStatement.of(ctStatement)); - } - - public static Optional getSingleEffect(Collection ctStatements) { - List statements = getEffectiveStatements(ctStatements); - - if (statements.size() != 1 && (statements.size() != 2 || !(statements.get(1) instanceof CtBreak))) { - return Optional.empty(); - } - - return tryMakeEffect(statements.get(0)); - } - - public static List getCasesEffects(Iterable> ctCases) { - List effects = new ArrayList<>(); - for (CtCase ctCase : ctCases) { - Optional effect = SpoonUtil.getSingleEffect(ctCase.getStatements()); - if (effect.isEmpty()) { - return new ArrayList<>(); - } - - Effect resolvedEffect = effect.get(); - - - // check for default case, which is allowed to be a terminal effect, even if the other cases are not: - if (ctCase.getCaseExpressions().isEmpty() && resolvedEffect instanceof TerminalEffect) { - continue; - } - - effects.add(resolvedEffect); - } - - if (effects.isEmpty()) return new ArrayList<>(); - - return effects; - } - - /** - * Converts the provided source position into a human-readable string. - * - * @param sourcePosition the source position as given by spoon - * @return a human-readable string representation of the source position - */ - public static String formatSourcePosition(SourcePosition sourcePosition) { - return String.format("%s:L%d", getBaseName(sourcePosition.getFile().getName()), sourcePosition.getLine()); - } - - public static String getBaseName(String fileName) { - if (fileName == null) { - return null; - } - return FilenameUtils.removeExtension(new File(fileName).getName()); - } - - public static SourcePosition getNamePosition(CtNamedElement ctNamedElement) { - SourcePosition position = ctNamedElement.getPosition(); - - if (position instanceof CompoundSourcePosition compoundSourcePosition) { - return ctNamedElement.getFactory().createSourcePosition( - position.getCompilationUnit(), - compoundSourcePosition.getNameStart(), - compoundSourcePosition.getNameEnd(), - position.getCompilationUnit().getLineSeparatorPositions() - ); - } - - return position; - } - - public static

P getParentOrSelf(CtElement element, Class

parentType) { - Objects.requireNonNull(element); - if (parentType.isAssignableFrom(element.getClass())) { - return (P) element; - } - return element.getParent(parentType); - } - - public static int getParameterIndex(CtParameter parameter, CtExecutable executable) { - for (int i = 0; i < executable.getParameters().size(); i++) { - if (executable.getParameters().get(i) == parameter) { - return i; - } - } - throw new IllegalArgumentException("Parameter not found in executable"); - } -} diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StatementUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StatementUtil.java new file mode 100644 index 00000000..bf34db7a --- /dev/null +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StatementUtil.java @@ -0,0 +1,166 @@ +package de.firemage.autograder.core.integrated; + +import de.firemage.autograder.core.integrated.effects.AssignmentStatement; +import de.firemage.autograder.core.integrated.effects.Effect; +import de.firemage.autograder.core.integrated.effects.TerminalEffect; +import de.firemage.autograder.core.integrated.effects.TerminalStatement; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtBodyHolder; +import spoon.reflect.code.CtBreak; +import spoon.reflect.code.CtCase; +import spoon.reflect.code.CtComment; +import spoon.reflect.code.CtStatement; +import spoon.reflect.code.CtStatementList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class StatementUtil { + private StatementUtil() { + } + + private static List getEffectiveStatements(Collection statements) { + return statements.stream().flatMap(ctStatement -> { + // flatten blocks + if (ctStatement instanceof CtStatementList ctStatementList) { + return getEffectiveStatements(ctStatementList.getStatements()).stream(); + } else { + return Stream.of(ctStatement); + } + }).filter(statement -> !(statement instanceof CtComment)).toList(); + } + + public static List getEffectiveStatements(CtStatement ctStatement) { + if (ctStatement == null) { + return List.of(); + } + + if (ctStatement instanceof CtStatementList ctStatementList) { + return getEffectiveStatements(ctStatementList.getStatements()); + } + + return getEffectiveStatements(List.of(ctStatement)); + } + + public static List getEffectiveStatementsOf(CtBodyHolder ctBodyHolder) { + if (ctBodyHolder == null) { + return List.of(); + } + + CtStatement body = ctBodyHolder.getBody(); + if (body == null) { + return List.of(); + } + + return getEffectiveStatements(body); + } + + /** + * Extracts a nested statement from a block if possible. + *

+ * A statement might be in a block {@code { statement }}. + * This method will extract the statement from the block and return it. + * + * @param statement the statement to unwrap + * @return the given statement or an unwrapped version if possible + */ + public static CtStatement unwrapStatement(CtStatement statement) { + if (statement instanceof CtBlock block) { + List statements = getEffectiveStatements(block); + if (statements.size() == 1) { + return statements.get(0); + } + } + return statement; + } + + private static int referenceIndexOf(List list, T element) { + for (int i = 0; i < list.size(); i++) { + if (list.get(i) == element) { + return i; + } + } + + return -1; + } + + /** + * Finds the statement that is before the given statement if possible. + * + * @param ctStatement the statement to find the previous statement of, must not be null + * @return the previous statement or an empty optional if there is no previous statement + */ + public static Optional getPreviousStatement(CtStatement ctStatement) { + List previousStatements = getPreviousStatements(ctStatement); + return previousStatements.isEmpty() ? Optional.empty() : Optional.of(previousStatements.get(previousStatements.size() - 1)); + } + + public static List getPreviousStatements(CtStatement ctStatement) { + List result = new ArrayList<>(); + if (ctStatement.getParent() instanceof CtStatementList ctStatementList) { + List statements = ctStatementList.getStatements(); + int index = referenceIndexOf(statements, ctStatement); + + if (index >= 0) { + result.addAll(statements.subList(0, index)); + } + } + + return result; + } + + public static List getNextStatements(CtStatement ctStatement) { + List result = new ArrayList<>(); + if (ctStatement.getParent() instanceof CtStatementList ctStatementList) { + List statements = ctStatementList.getStatements(); + int index = referenceIndexOf(statements, ctStatement); + + if (index >= 0) { + result.addAll(statements.subList(index + 1, statements.size())); + } + } + + return result; + } + + public static Optional tryMakeEffect(CtStatement ctStatement) { + return TerminalStatement.of(ctStatement).or(() -> AssignmentStatement.of(ctStatement)); + } + + public static Optional getSingleEffect(Collection ctStatements) { + List statements = getEffectiveStatements(ctStatements); + + if (statements.size() != 1 && (statements.size() != 2 || !(statements.get(1) instanceof CtBreak))) { + return Optional.empty(); + } + + return tryMakeEffect(statements.get(0)); + } + + public static List getCasesEffects(Iterable> ctCases) { + List effects = new ArrayList<>(); + for (CtCase ctCase : ctCases) { + Optional effect = getSingleEffect(ctCase.getStatements()); + if (effect.isEmpty()) { + return new ArrayList<>(); + } + + Effect resolvedEffect = effect.get(); + + + // check for default case, which is allowed to be a terminal effect, even if the other cases are not: + if (ctCase.getCaseExpressions().isEmpty() && resolvedEffect instanceof TerminalEffect) { + continue; + } + + effects.add(resolvedEffect); + } + + if (effects.isEmpty()) return new ArrayList<>(); + + return effects; + } +} diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StaticAnalysis.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StaticAnalysis.java index a79d68f0..c991eb82 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StaticAnalysis.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/StaticAnalysis.java @@ -54,7 +54,7 @@ private static boolean isJavaUtilImport(CtImport ctImport) { public boolean hasJavaUtilImport() { AtomicBoolean hasImport = new AtomicBoolean(false); - SpoonUtil.visitCtCompilationUnit(this.getModel(), ctCompilationUnit -> { + CoreUtil.visitCtCompilationUnit(this.getModel(), ctCompilationUnit -> { if (hasImport.get()) { return; } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/TypeUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/TypeUtil.java index 4432a084..24c0cac3 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/TypeUtil.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/TypeUtil.java @@ -4,12 +4,14 @@ import spoon.reflect.declaration.CtTypeInformation; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.TypeFactory; +import spoon.reflect.reference.CtFieldReference; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; @@ -153,4 +155,69 @@ public static boolean isSubtypeOf(CtTypeReference ctTypeReference, Class e public static boolean isInnerClass(CtTypeMember type) { return type.getDeclaringType() != null; } + + public static boolean isString(CtTypeReference type) { + return isTypeEqualTo(type, String.class); + } + + public static boolean isPrimitiveNumeric(CtTypeReference type) { + return type.isPrimitive() + && !type.getQualifiedName().equals("boolean") + && !type.getQualifiedName().equals("char"); + } + + /** + * Checks if the given element is guaranteed to be immutable. + *

+ * Note that when this method returns {@code false}, the type might still be immutable. + * + * @param ctTypeReference the type to check + * @return true if the given element is guaranteed to be immutable, false otherwise + * @param the type of the element + */ + public static boolean isImmutable(CtTypeReference ctTypeReference) { + Deque> queue = new ArrayDeque<>(Collections.singletonList(ctTypeReference)); + Collection> visited = new HashSet<>(); + + while (!queue.isEmpty()) { + CtType ctType = queue.removeFirst().getTypeDeclaration(); + + // if the type is not in the classpath, null is returned + // in those cases, assume that the type is not immutable + if (ctType == null) { + return false; + } + + // skip types that have been checked (those are guaranteed to be immutable) + if (visited.contains(ctType)) { + continue; + } + + // primitive types and strings are immutable as well: + if (ctType.getReference().unbox().isPrimitive() + || isTypeEqualTo(ctType.getReference(), String.class)) { + continue; + } + + // types that are not in the classpath like java.util.ArrayList are shadow types. + // the source code for those is missing, so it is impossible to check if they are immutable. + // => assume they are not immutable + if (ctType.isShadow()) { + return false; + } + + // for a type to be immutable, all of its fields must be final and immutable as well: + for (CtFieldReference ctFieldReference : ctType.getAllFields()) { + if (!VariableUtil.isEffectivelyFinal(ctFieldReference.getFieldDeclaration())) { + return false; + } + + queue.add(ctFieldReference.getType()); + } + + visited.add(ctType); + } + + return true; + } } diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/VariableUtil.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/VariableUtil.java new file mode 100644 index 00000000..69f74f9d --- /dev/null +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/VariableUtil.java @@ -0,0 +1,67 @@ +package de.firemage.autograder.core.integrated; + +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtVariableWrite; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtVariable; +import spoon.reflect.declaration.ModifierKind; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtFieldReference; +import spoon.reflect.reference.CtReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.reference.CtVariableReference; + +import java.util.Optional; + +public final class VariableUtil { + private VariableUtil() { + } + + public static boolean isEffectivelyFinal(CtVariable ctVariable) { + if (ctVariable.getModifiers().contains(ModifierKind.FINAL)) { + return true; + } + + return UsesFinder.variableUses(ctVariable).ofType(CtVariableWrite.class).hasNone(); + } + + public static Optional> getEffectivelyFinalExpression(CtVariable ctVariable) { + if (!isEffectivelyFinal(ctVariable)) { + return Optional.empty(); + } + + return Optional.ofNullable(ctVariable.getDefaultExpression()); + } + + public static CtElement getReferenceDeclaration(CtReference ctReference) { + // this might be null if the reference is not in the source path + // for example, when the reference points to a java.lang type + CtElement target = ctReference.getDeclaration(); + + if (target == null && ctReference instanceof CtTypeReference ctTypeReference) { + target = ctTypeReference.getTypeDeclaration(); + } + + if (target == null && ctReference instanceof CtExecutableReference ctExecutableReference) { + target = ctExecutableReference.getExecutableDeclaration(); + } + + if (target == null && ctReference instanceof CtVariableReference ctVariableReference) { + target = getVariableDeclaration(ctVariableReference); + } + + return target; + } + + public static CtVariable getVariableDeclaration(CtVariableReference ctVariableReference) { + // this might be null if the reference is not in the source path + // for example, when the reference points to a java.lang type + CtVariable target = ctVariableReference.getDeclaration(); + + if (target == null && ctVariableReference instanceof CtFieldReference ctFieldReference) { + target = ctFieldReference.getFieldDeclaration(); + } + + return target; + } +} diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/OperatorHelper.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/OperatorHelper.java index 30f24d97..eee8d371 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/OperatorHelper.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/OperatorHelper.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtVariableRead; @@ -175,8 +175,8 @@ public static Optional> getPromotedType( CtExpression right ) { TypeFactory typeFactory = left.getFactory().Type(); - CtTypeReference leftType = SpoonUtil.getExpressionType(left); - CtTypeReference rightType = SpoonUtil.getExpressionType(right); + CtTypeReference leftType = ExpressionUtil.getExpressionType(left); + CtTypeReference rightType = ExpressionUtil.getExpressionType(right); switch (operator) { // logical operators @@ -322,7 +322,7 @@ public static Optional> getPromotedType( CtExpression operand ) { TypeFactory typeFactory = operand.getFactory().Type(); - CtTypeReference operandType = SpoonUtil.getExpressionType(operand); + CtTypeReference operandType = ExpressionUtil.getExpressionType(operand); // The type of the expression is the type of the variable. return switch (operator) { diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyCasts.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyCasts.java index a8020c30..a370e3fa 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyCasts.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyCasts.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtLiteral; import spoon.reflect.reference.CtTypeReference; @@ -38,7 +38,7 @@ public CtLiteral foldCtLiteral(CtLiteral ctLiteral) { ctLiteral.setTypeCasts(new ArrayList<>()); for (CtTypeReference cast : casts) { - result = SpoonUtil.castLiteral(cast, result); + result = ExpressionUtil.castLiteral(cast, result); } return result; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyOperatorPromotion.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyOperatorPromotion.java index cbef14bd..9b04efcd 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyOperatorPromotion.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/ApplyOperatorPromotion.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.evaluator.OperatorHelper; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtBinaryOperator; @@ -69,11 +69,11 @@ public CtExpression foldCtBinaryOperator(CtBinaryOperator ctBinaryOper // only promote if the predicate allows it if (this.shouldApplyOnBinaryOperator.shouldApplyOn(ctBinaryOperator, ctBinaryOperator.getLeftHandOperand())) { - ctBinaryOperator.setLeftHandOperand(SpoonUtil.castExpression(promotedType, ctBinaryOperator.getLeftHandOperand())); + ctBinaryOperator.setLeftHandOperand(ExpressionUtil.castExpression(promotedType, ctBinaryOperator.getLeftHandOperand())); } if (this.shouldApplyOnBinaryOperator.shouldApplyOn(ctBinaryOperator, ctBinaryOperator.getRightHandOperand())) { - ctBinaryOperator.setRightHandOperand(SpoonUtil.castExpression(promotedType, ctBinaryOperator.getRightHandOperand())); + ctBinaryOperator.setRightHandOperand(ExpressionUtil.castExpression(promotedType, ctBinaryOperator.getRightHandOperand())); } return ctBinaryOperator; @@ -92,7 +92,7 @@ public CtExpression foldCtUnaryOperator(CtUnaryOperator ctUnaryOperato } if (this.shouldApplyOnUnaryOperator.shouldApplyOn(ctUnaryOperator, ctUnaryOperator.getOperand())) { - ctUnaryOperator.setOperand((CtExpression) SpoonUtil.castExpression(promotedType, ctUnaryOperator.getOperand())); + ctUnaryOperator.setOperand((CtExpression) ExpressionUtil.castExpression(promotedType, ctUnaryOperator.getOperand())); } return ctUnaryOperator; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluateLiteralOperations.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluateLiteralOperations.java index 7d267e37..afb3b054 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluateLiteralOperations.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluateLiteralOperations.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.evaluator.Evaluator; import de.firemage.autograder.core.integrated.evaluator.OperatorHelper; import spoon.SpoonException; @@ -48,7 +48,7 @@ public CtExpression foldCtBinaryOperator(CtBinaryOperator ctBinaryOper Object leftObject = leftLiteral.getValue(); Object rightObject = rightLiteral.getValue(); - CtTypeReference operatorType = (CtTypeReference) SpoonUtil.getExpressionType(ctBinaryOperator); + CtTypeReference operatorType = (CtTypeReference) ExpressionUtil.getExpressionType(ctBinaryOperator); Object value = switch (ctBinaryOperator.getKind()) { case AND -> (Boolean) leftObject && (Boolean) rightObject; @@ -185,7 +185,7 @@ public CtExpression foldCtUnaryOperator(CtUnaryOperator ctUnaryOperato return ctUnaryOperator; } - CtTypeReference operatorType = (CtTypeReference) SpoonUtil.getExpressionType(promotedOperator); + CtTypeReference operatorType = (CtTypeReference) ExpressionUtil.getExpressionType(promotedOperator); Object literalValue = literal.getValue(); Object value = switch (promotedOperator.getKind()) { case NOT -> !(Boolean) literalValue; diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluatePartialLiteralOperations.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluatePartialLiteralOperations.java index 73016831..6697c556 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluatePartialLiteralOperations.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/EvaluatePartialLiteralOperations.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.evaluator.Evaluator; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtExpression; @@ -30,7 +30,7 @@ public CtExpression foldCtBinaryOperator(CtBinaryOperator ctBinaryOper CtExpression rightExpression = promotedOperator.getRightHandOperand(); if (!(promotedOperator.getLeftHandOperand() instanceof CtLiteral) && promotedOperator.getRightHandOperand() instanceof CtLiteral) { - promotedOperator = SpoonUtil.swapCtBinaryOperator(promotedOperator); + promotedOperator = ExpressionUtil.swapCtBinaryOperator(promotedOperator); } // ignore if both operands are not literals diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/InlineVariableRead.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/InlineVariableRead.java index f21de6da..769e99ac 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/InlineVariableRead.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/InlineVariableRead.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.UsesFinder; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtLiteral; @@ -37,7 +37,7 @@ public CtExpression foldCtVariableRead(CtVariableRead ctVariableRead) return ctVariableRead; } - Optional> ctExpressionOptional = SpoonUtil.getEffectivelyFinalExpression(ctVariable); + Optional> ctExpressionOptional = VariableUtil.getEffectivelyFinalExpression(ctVariable); return ctExpressionOptional.flatMap(ctExpression -> { // only inline literals: diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/RemoveRedundantCasts.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/RemoveRedundantCasts.java index e195daa1..a7435ffe 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/RemoveRedundantCasts.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/evaluator/fold/RemoveRedundantCasts.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator.fold; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.reflect.code.CtExpression; import spoon.reflect.reference.CtTypeParameterReference; @@ -123,7 +123,7 @@ public static CtExpression removeRedundantCasts(CtExpression ctExpress currentType = newType; } - CtTypeReference originalExpressionType = SpoonUtil.getExpressionType(ctExpression); + CtTypeReference originalExpressionType = ExpressionUtil.getExpressionType(ctExpression); CtTypeReference newExpressionType = ctExpression.getType(); if (!newCasts.isEmpty()) { newExpressionType = newCasts.get(newCasts.size() - 1); diff --git a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/structure/StructuralEqualsVisitor.java b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/structure/StructuralEqualsVisitor.java index 7ed94161..711831d4 100644 --- a/autograder-core/src/main/java/de/firemage/autograder/core/integrated/structure/StructuralEqualsVisitor.java +++ b/autograder-core/src/main/java/de/firemage/autograder/core/integrated/structure/StructuralEqualsVisitor.java @@ -1,6 +1,7 @@ package de.firemage.autograder.core.integrated.structure; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.CoreUtil; import spoon.reflect.code.CtBinaryOperator; import spoon.reflect.code.CtConditional; @@ -132,7 +133,7 @@ protected void exit(CtElement ctElement) { return; } - if (SpoonUtil.resolveConstant(expression) instanceof CtLiteral || isAllowedExpression.test(expression)) { + if (ExpressionUtil.resolveConstant(expression) instanceof CtLiteral || isAllowedExpression.test(expression)) { this.isConstant = true; } else { this.isConstant = false; @@ -155,8 +156,8 @@ private static boolean isRefactorable(Object element) { return isConstantExpressionOr(ctExpression, e -> { // in addition to constant expressions, it is also okay to access parameters (if they have not been modified in the method) if (e instanceof CtVariableRead ctVariableRead) { - CtVariable ctVariable = SpoonUtil.getVariableDeclaration(ctVariableRead.getVariable()); - return ctVariable instanceof CtParameter ctParameter && SpoonUtil.isEffectivelyFinal(ctParameter); + CtVariable ctVariable = VariableUtil.getVariableDeclaration(ctVariableRead.getVariable()); + return ctVariable instanceof CtParameter ctParameter && VariableUtil.isEffectivelyFinal(ctParameter); } return false; diff --git a/autograder-core/src/test/java/de/firemage/autograder/core/integrated/evaluator/TestEvaluator.java b/autograder-core/src/test/java/de/firemage/autograder/core/integrated/evaluator/TestEvaluator.java index 79dece00..b7fe3c19 100644 --- a/autograder-core/src/test/java/de/firemage/autograder/core/integrated/evaluator/TestEvaluator.java +++ b/autograder-core/src/test/java/de/firemage/autograder/core/integrated/evaluator/TestEvaluator.java @@ -1,6 +1,6 @@ package de.firemage.autograder.core.integrated.evaluator; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.core.integrated.UsesFinder; import de.firemage.autograder.core.integrated.evaluator.fold.ApplyCasts; import de.firemage.autograder.core.integrated.evaluator.fold.ChainedFold; @@ -85,11 +85,11 @@ private static void runExpressionTest(String expression, String arguments, Strin CtExpression ctExpression = createExpression(expression, arguments); // the type should not change throughout the evaluation - CtTypeReference currentType = SpoonUtil.getExpressionType(ctExpression).clone(); + CtTypeReference currentType = ExpressionUtil.getExpressionType(ctExpression).clone(); CtExpression result = evaluator.evaluate(ctExpression); assertEquals(expected, result.toString()); - assertEquals(currentType, SpoonUtil.getExpressionType(result)); + assertEquals(currentType, ExpressionUtil.getExpressionType(result)); } /** diff --git a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/complexity/RegexCheck.java b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/complexity/RegexCheck.java index 394b5c64..97f23642 100644 --- a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/complexity/RegexCheck.java +++ b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/complexity/RegexCheck.java @@ -4,7 +4,7 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import de.firemage.autograder.core.integrated.TypeUtil; @@ -77,7 +77,7 @@ private static boolean isRegexInvocation(CtInvocation ctInvocation) { private static boolean isInAllowedContext(CtLiteral ctLiteral) { CtElement parent = ctLiteral.getParent(); if (parent instanceof CtVariable ctVariable - && SpoonUtil.isEffectivelyFinal(ctVariable)) { + && VariableUtil.isEffectivelyFinal(ctVariable)) { // Check if the variable is only used in a regex invocation (e.g. Pattern.compile) return UsesFinder.variableUses(ctVariable) .hasAnyAndAllMatch(ctVariableAccess -> ctVariableAccess.getParent() instanceof CtInvocation ctInvocation @@ -92,7 +92,7 @@ protected void check(StaticAnalysis staticAnalysis) { staticAnalysis.processWith(new AbstractProcessor>() { @Override public void process(CtLiteral literal) { - if (!SpoonUtil.isString(literal.getType()) || !isInAllowedContext(literal)) { + if (!TypeUtil.isString(literal.getType()) || !isInAllowedContext(literal)) { return; } diff --git a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/general/ConstantNamingAndQualifierCheck.java b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/general/ConstantNamingAndQualifierCheck.java index 34430a48..d796336b 100644 --- a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/general/ConstantNamingAndQualifierCheck.java +++ b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/general/ConstantNamingAndQualifierCheck.java @@ -4,9 +4,11 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.extra.integrated.IdentifierNameUtils; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.VariableUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtLiteral; @@ -59,7 +61,7 @@ public void process(CtVariable ctVariable) { // skip non-constant variables (and those that should be ignored) if (ctVariable.isImplicit() || !ctVariable.getPosition().isValidPosition() - || !SpoonUtil.isEffectivelyFinal(ctVariable) + || !VariableUtil.isEffectivelyFinal(ctVariable) || ctVariable.getDefaultExpression() == null || IGNORE_FIELDS.contains(ctVariable.getSimpleName())) { return; @@ -67,12 +69,12 @@ public void process(CtVariable ctVariable) { // only check primitive types and strings, because other types may be mutable like list // and should therefore not be static, even if they are final - if (!ctVariable.getType().unbox().isPrimitive() && !SpoonUtil.isString(ctVariable.getType())) { + if (!ctVariable.getType().unbox().isPrimitive() && !TypeUtil.isString(ctVariable.getType())) { return; } if (ctVariable instanceof CtLocalVariable ctLocalVariable - && SpoonUtil.resolveCtExpression(ctLocalVariable.getDefaultExpression()) instanceof CtLiteral) { + && ExpressionUtil.resolveCtExpression(ctLocalVariable.getDefaultExpression()) instanceof CtLiteral) { // by the check above, ctLocalVariable has a default expression and is effectively final // // this code catches the case where one tries to bypass the checkstyle by doing: diff --git a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/LinguisticNamingCheck.java b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/LinguisticNamingCheck.java index 1525d889..4391a229 100644 --- a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/LinguisticNamingCheck.java +++ b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/LinguisticNamingCheck.java @@ -6,10 +6,12 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.CoreUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; +import de.firemage.autograder.core.integrated.StatementUtil; import de.firemage.autograder.extra.integrated.IdentifierNameUtils; import de.firemage.autograder.core.integrated.IntegratedCheck; import de.firemage.autograder.core.integrated.MethodHierarchy; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.TypeUtil; import spoon.processing.AbstractProcessor; @@ -52,7 +54,7 @@ private void reportProblem(String key, CtNamedElement ctNamedElement, Map void checkCtVariable(CtVariable ctVariable) { return; } - if (hasBooleanPrefix(ctVariable) && !SpoonUtil.isBoolean(ctVariable)) { + if (hasBooleanPrefix(ctVariable) && !ExpressionUtil.isBoolean(ctVariable)) { this.reportProblem( "linguistic-naming-boolean", ctVariable, @@ -78,7 +80,7 @@ private void checkCtMethod(CtMethod ctMethod, CodeModel model) { return; } - if (hasBooleanPrefix(ctMethod) && !SpoonUtil.isBoolean(ctMethod)) { + if (hasBooleanPrefix(ctMethod) && !ExpressionUtil.isBoolean(ctMethod)) { this.reportProblem( "linguistic-naming-boolean", ctMethod, @@ -106,7 +108,7 @@ private void checkCtMethod(CtMethod ctMethod, CodeModel model) { return; } - if (prefix.equals("set") && isInvalidSetterReturnType(ctMethod) && SpoonUtil.getEffectiveStatements(ctMethod.getBody()).size() <= 3) { + if (prefix.equals("set") && isInvalidSetterReturnType(ctMethod) && StatementUtil.getEffectiveStatements(ctMethod.getBody()).size() <= 3) { // it is expected that a setter returns nothing (void) this.reportProblem( "linguistic-naming-setter", @@ -134,7 +136,7 @@ private static boolean isInvalidSetterReturnType(CtMethod ctMethod) { } // returning a boolean is allowed as well (for indicating success) - return !SpoonUtil.isBoolean(ctMethod); + return !ExpressionUtil.isBoolean(ctMethod); } @Override diff --git a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/VariablesHaveDescriptiveNamesCheck.java b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/VariablesHaveDescriptiveNamesCheck.java index 84b46d9e..026c37f0 100644 --- a/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/VariablesHaveDescriptiveNamesCheck.java +++ b/autograder-extra/src/main/java/de/firemage/autograder/extra/check/naming/VariablesHaveDescriptiveNamesCheck.java @@ -4,9 +4,9 @@ import de.firemage.autograder.core.ProblemType; import de.firemage.autograder.core.check.ExecutableCheck; +import de.firemage.autograder.core.integrated.TypeUtil; import de.firemage.autograder.extra.integrated.IdentifierNameUtils; import de.firemage.autograder.core.integrated.IntegratedCheck; -import de.firemage.autograder.core.integrated.SpoonUtil; import de.firemage.autograder.core.integrated.StaticAnalysis; import de.firemage.autograder.core.integrated.MethodUtil; import org.apache.commons.lang3.StringUtils; @@ -128,7 +128,7 @@ private static boolean isAllowedLoopCounter(CtVariable variable) { } // for (int i = 0; i < 10; i++) - return SpoonUtil.isPrimitiveNumeric(variable.getType()); + return TypeUtil.isPrimitiveNumeric(variable.getType()); } private static boolean isAbbreviation(CtVariable variable) { diff --git a/autograder-extra/src/test/java/de/firemage/autograder/extra/check/TestLocalizedStrings.java b/autograder-extra/src/test/java/de/firemage/autograder/extra/check/TestLocalizedStrings.java index b7ba6036..e9e2b63a 100644 --- a/autograder-extra/src/test/java/de/firemage/autograder/extra/check/TestLocalizedStrings.java +++ b/autograder-extra/src/test/java/de/firemage/autograder/extra/check/TestLocalizedStrings.java @@ -2,7 +2,7 @@ import de.firemage.autograder.core.Linter; import de.firemage.autograder.api.JavaVersion; -import de.firemage.autograder.core.integrated.SpoonUtil; +import de.firemage.autograder.core.integrated.ExpressionUtil; import de.firemage.autograder.extra.check.naming.LinguisticNamingCheck; import de.firemage.autograder.extra.check.naming.VariablesHaveDescriptiveNamesCheck; import de.firemage.autograder.core.integrated.TypeUtil; @@ -80,7 +80,7 @@ class TestLocalizedStrings { private static FluentBundle germanBundle; private static String resolveKey(CtModel ctModel, List> args) { - if (args.isEmpty() || !(SpoonUtil.resolveCtExpression(args.get(0)) instanceof CtLiteral ctLiteral + if (args.isEmpty() || !(ExpressionUtil.resolveCtExpression(args.get(0)) instanceof CtLiteral ctLiteral && ctLiteral.getValue() instanceof String key)) { throw new IllegalArgumentException("The first argument must be a string literal: " + args); }