diff --git a/MUTATORS.generated.MD b/MUTATORS.generated.MD index 566e72375..2c8fdbaf7 100644 --- a/MUTATORS.generated.MD +++ b/MUTATORS.generated.MD @@ -244,6 +244,16 @@ isDraft languageLevel: jdk1 +### [BigIntegerInstantiation](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java) + +PMD: [BigIntegerInstantiation](https://pmd.github.io/pmd/pmd_rules_java_performance.html#bigintegerinstantiation) + +Cleanthat own ID: BigIntegerInstantiation + +isDraft + +languageLevel: jdk1.5 + ### [CastMathOperandsBeforeAssignement](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/CastMathOperandsBeforeAssignement.java) Sonar: [RSPEC-2184](https://rules.sonarsource.com/java/RSPEC-2184) diff --git a/MUTATORS_BY_TAG.generated.MD b/MUTATORS_BY_TAG.generated.MD index 27d20692b..ab0917a73 100644 --- a/MUTATORS_BY_TAG.generated.MD +++ b/MUTATORS_BY_TAG.generated.MD @@ -25,6 +25,7 @@ - [ArithmeticOverFloats](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/ArithmeticOverFloats.java) - [AvoidFileStream](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/AvoidFileStream.java) - [AvoidUncheckedExceptionsInSignatures](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/AvoidUncheckedExceptionsInSignatures.java) +- [BigIntegerInstantiation](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java) - [CastMathOperandsBeforeAssignement](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/CastMathOperandsBeforeAssignement.java) - [CollectionToOptional](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/CollectionToOptional.java) - [CreateTempFilesUsingNio](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/CreateTempFilesUsingNio.java) @@ -157,6 +158,7 @@ - [AvoidFileStream](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/AvoidFileStream.java) - [AvoidMultipleUnaryOperators](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/AvoidMultipleUnaryOperators.java) - [AvoidUncheckedExceptionsInSignatures](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/AvoidUncheckedExceptionsInSignatures.java) +- [BigIntegerInstantiation](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java) - [ComparisonWithNaN](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/ComparisonWithNaN.java) - [LiteralsFirstInComparisons](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/LiteralsFirstInComparisons.java) - [PrimitiveWrapperInstantiation](java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/PrimitiveWrapperInstantiation.java) diff --git a/java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java b/java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java new file mode 100644 index 000000000..aad5f8d4e --- /dev/null +++ b/java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/BigIntegerInstantiation.java @@ -0,0 +1,177 @@ +/* + * Copyright 2025 Benoit Lacelle - SOLVEN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.solven.cleanthat.engine.java.refactorer.mutators; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.nodeTypes.NodeWithArguments; +import com.google.common.collect.ImmutableSet; + +import eu.solven.cleanthat.engine.java.IJdkVersionConstants; +import eu.solven.cleanthat.engine.java.refactorer.AJavaparserExprMutator; +import eu.solven.cleanthat.engine.java.refactorer.NodeAndSymbolSolver; +import eu.solven.cleanthat.engine.java.refactorer.helpers.MethodCallExprHelpers; +import eu.solven.cleanthat.engine.java.refactorer.meta.IMutatorDescriber; + +/** + * Turns `new BigInteger("1")` into `BigInteger.ONE` + * + * @author Balazs Glatz + */ +public class BigIntegerInstantiation extends AJavaparserExprMutator implements IMutatorDescriber { + + private static final Map NUMBER_TO_CONSTANT = Map.of("0", "ZERO", "1", "ONE", "10", "TEN"); + + @Override + public String minimalJavaVersion() { + return IJdkVersionConstants.JDK_5; + } + + @Override + public boolean isPerformanceImprovment() { + return true; + } + + @Override + public Set getTags() { + return ImmutableSet.of("Performance"); + } + + @Override + public Optional getPmdId() { + return Optional.of("BigIntegerInstantiation"); + } + + @Override + public String pmdUrl() { + return "https://pmd.github.io/pmd/pmd_rules_java_performance.html#bigintegerinstantiation"; + } + + @Override + protected boolean processExpression(NodeAndSymbolSolver expression) { + var node = expression.getNode(); + + var isObjectCreation = node instanceof ObjectCreationExpr; + var isMethodCall = node instanceof MethodCallExpr; + + if (!(isObjectCreation || isMethodCall)) { + return false; + } + + var arguments = ((NodeWithArguments) node).getArguments(); + + if (arguments.size() != 1) { + return false; + } + + String typeName; + if (isObjectCreation) { + var objectCreation = node.asObjectCreationExpr(); + typeName = objectCreation.getType().getNameAsString(); + } else { + var methodCall = node.asMethodCallExpr(); + if (!"valueOf".equals(methodCall.getNameAsString())) { + return false; + } + Optional scope = methodCall.getScope(); + var isBigInteger = MethodCallExprHelpers.scopeHasRequiredType(expression.editNode(scope), BigInteger.class); + var isBigDecimal = MethodCallExprHelpers.scopeHasRequiredType(expression.editNode(scope), BigDecimal.class); + if (isBigInteger) { + typeName = "BigInteger"; + } else if (isBigDecimal) { + typeName = "BigDecimal"; + } else { + return false; + } + } + + var argument = arguments.get(0); + + String number; + if (argument.isStringLiteralExpr()) { + number = getValueAsString(typeName, argument.asStringLiteralExpr().getValue()); + } else if (argument.isDoubleLiteralExpr()) { + number = getValueAsString(argument.asDoubleLiteralExpr().asDouble()); + } else { + number = getValueAsString(typeName, getArgumentAsNumber(argument)); + } + + if (number == null) { + return false; + } + var constant = NUMBER_TO_CONSTANT.get(number); + if (constant == null) { + return false; + } + + var replacement = new FieldAccessExpr(new NameExpr(typeName), constant); + + return tryReplace(expression, replacement); + } + + private Long getArgumentAsNumber(Expression argument) { + if (argument.isIntegerLiteralExpr()) { + return argument.asIntegerLiteralExpr().asNumber().longValue(); + } else if (argument.isLongLiteralExpr()) { + return argument.asLongLiteralExpr().asNumber().longValue(); + } else { + return null; + } + } + + public static String getValueAsString(String klass, String value) { + try { + if ("BigDecimal".equals(klass)) { + return new BigDecimal(value).toString(); + } + return new BigInteger(value).toString(); + } catch (NumberFormatException exception) { + return null; + } + } + + public static String getValueAsString(double value) { + try { + return BigDecimal.valueOf(value).toString(); + } catch (ClassCastException | NumberFormatException ignored) { + return null; + } + } + + public static String getValueAsString(String klass, Long value) { + if (value == null) { + return null; + } + try { + if ("BigDecimal".equals(klass)) { + return BigDecimal.valueOf(value).toString(); + } + return BigInteger.valueOf(value).toString(); + } catch (ClassCastException | NumberFormatException exception) { + return null; + } + } + +} diff --git a/java/src/test/java/eu/solven/cleanthat/engine/java/refactorer/cases/do_not_format_me/TestBigIntegerInstantiationCases.java b/java/src/test/java/eu/solven/cleanthat/engine/java/refactorer/cases/do_not_format_me/TestBigIntegerInstantiationCases.java new file mode 100644 index 000000000..5283900c1 --- /dev/null +++ b/java/src/test/java/eu/solven/cleanthat/engine/java/refactorer/cases/do_not_format_me/TestBigIntegerInstantiationCases.java @@ -0,0 +1,350 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import eu.solven.cleanthat.engine.java.refactorer.annotations.CompareMethods; +import eu.solven.cleanthat.engine.java.refactorer.annotations.UnmodifiedMethod; +import eu.solven.cleanthat.engine.java.refactorer.meta.IJavaparserAstMutator; +import eu.solven.cleanthat.engine.java.refactorer.mutators.BigIntegerInstantiation; +import eu.solven.cleanthat.engine.java.refactorer.test.AJavaparserRefactorerCases; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class TestBigIntegerInstantiationCases extends AJavaparserRefactorerCases { + + @Override + public IJavaparserAstMutator getTransformer() { + return new BigIntegerInstantiation(); + } + + @CompareMethods + public static class BigIntegerOfZero { + public Object pre() { + return BigInteger.valueOf(0); + } + + public Object post() { + return BigInteger.ZERO; + } + } + + @CompareMethods + public static class BigIntegerZero { + public Object pre() { + return new BigInteger("0"); + } + + public Object post() { + return BigInteger.ZERO; + } + } + + @CompareMethods + public static class BigIntegerOne { + public Object pre() { + return new BigInteger("1"); + } + + public Object post() { + return BigInteger.ONE; + } + } + + @CompareMethods + public static class BigIntegerTen { + public Object pre() { + return new BigInteger("10"); + } + + public Object post() { + return BigInteger.TEN; + } + } + + @CompareMethods + public static class BigDecimalZero { + public Object pre() { + return new BigDecimal("0"); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalOne { + public Object pre() { + return new BigDecimal("1"); + } + + public Object post() { + return BigDecimal.ONE; + } + } + + @CompareMethods + public static class BigDecimalTen { + public Object pre() { + return new BigDecimal("10"); + } + + public Object post() { + return BigDecimal.TEN; + } + } + + @CompareMethods + public static class BigDecimalZeroInt { + public Object pre() { + return new BigDecimal(0); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalZeroLong { + public Object pre() { + return new BigDecimal(0L); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalZeroDouble { + public Object pre() { + return new BigDecimal(0d); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalZeroFloat { + public Object pre() { + return new BigDecimal(0f); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalZeros { + public Object pre() { + return new BigDecimal(00); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalStringZeros { + public Object pre() { + return new BigDecimal("00"); + } + + public Object post() { + return BigDecimal.ZERO; + } + } + + @CompareMethods + public static class BigDecimalLeadingZero { + public Object pre() { + return new BigDecimal(01); + } + + public Object post() { + return BigDecimal.ONE; + } + } + + @CompareMethods + public static class BigDecimalLeadingZeroString { + public Object pre() { + return new BigDecimal("01"); + } + + public Object post() { + return BigDecimal.ONE; + } + } + + @CompareMethods + public static class BigDecimalOneDouble { + public Object pre() { + return new BigDecimal(1.); + } + + public Object post() { + return BigDecimal.ONE; + } + } + + @CompareMethods + public static class BigDecimalTenDouble { + public Object pre() { + return new BigDecimal(10.d); + } + + public Object post() { + return BigDecimal.TEN; + } + } + + @UnmodifiedMethod + public static class BigIntegerInvalidTen { + public Object pre() { + return new BigInteger("1_0"); + } + } + + @UnmodifiedMethod + public static class BigIntegerNegativeOne { + public Object pre() { + return new BigInteger("-1"); + } + } + + @UnmodifiedMethod + public static class BigDecimalNegativeOne { + public Object pre() { + return new BigDecimal(-1); + } + } + + @UnmodifiedMethod + public static class BigIntegerThree { + public Object pre() { + return new BigInteger("3"); + } + } + + @UnmodifiedMethod + public static class BigDecimalThreeDouble { + public Object pre() { + return new BigDecimal(3d); + } + } + + @UnmodifiedMethod + public static class BigDecimalDecimal { + public Object pre() { + return new BigDecimal("1.2"); + } + } + + @UnmodifiedMethod + public static class BigIntegerDot { + public Object pre() { + return new BigInteger("."); + } + } + + @UnmodifiedMethod + public static class BigDecimalDots { + public Object pre() { + return new BigDecimal("0.."); + } + } + + @UnmodifiedMethod + public static class BigDecimalZeroDotZero { + public Object pre() { + return new BigDecimal("0.0"); + } + } + + @UnmodifiedMethod + public static class BigDecimalDotZeroDot { + public Object pre() { + return new BigDecimal(".0."); + } + } + + @UnmodifiedMethod + public static class BigIntegerEmpty { + public Object pre() { + return new BigInteger(""); + } + } + + @UnmodifiedMethod + public static class BigDecimalEmpty { + public Object pre() { + return new BigDecimal(""); + } + } + + @UnmodifiedMethod + public static class BigDecimalTrailingZeros { + public Object pre() { + return new BigDecimal("0.0000"); + } + } + + @UnmodifiedMethod + public static class BigDecimalRandomDouble { + public Object pre() { + return new BigDecimal(10.00000001); + } + } + + @UnmodifiedMethod + public static class BigDecimalRandomString { + public Object pre() { + return new BigDecimal("10.00000001"); + } + } + + @UnmodifiedMethod + public static class BigIntegerInvalidOne { + public Object pre() { + return new BigInteger("1."); + } + } + + @UnmodifiedMethod + public static class BigIntegerLongMaxValue { + public Object pre() { + return new BigInteger(Long.MAX_VALUE + ""); + } + } + + @UnmodifiedMethod + public static class BigDecimalLongMaxValue { + public Object pre() { + return new BigDecimal(Long.MAX_VALUE); + } + } + + @UnmodifiedMethod + public static class BigDecimalOfLongMaxValue { + public Object pre() { + return BigDecimal.valueOf(Long.MAX_VALUE); + } + } + + @CompareMethods + public static class BigDecimalTenUnderscore { + public Object pre() { + return new BigDecimal(1_0); + } + + public Object post() { + return BigDecimal.TEN; + } + } + +} diff --git a/refactorer/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/scanner/MutatorsScanner.java b/refactorer/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/scanner/MutatorsScanner.java index f0eeb2454..5c834c420 100644 --- a/refactorer/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/scanner/MutatorsScanner.java +++ b/refactorer/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/scanner/MutatorsScanner.java @@ -61,6 +61,7 @@ public final class MutatorsScanner { (Class) Class.forName(PACKAGE_MUTATORS + "AvoidMultipleUnaryOperators"), (Class) Class .forName(PACKAGE_MUTATORS + "AvoidUncheckedExceptionsInSignatures"), + (Class) Class.forName(PACKAGE_MUTATORS + "BigIntegerInstantiation"), (Class) Class.forName(PACKAGE_MUTATORS + "CastMathOperandsBeforeAssignement"), (Class) Class.forName(PACKAGE_MUTATORS + "CollectionIndexOfToContains"), (Class) Class.forName(PACKAGE_MUTATORS + "CollectionToOptional"),