Preconditions: replace Guava checkArgument() and checkState() in base with own implementation

This has the added benefit of exception messages only being evaluated on demand.
This commit is contained in:
Andreas Schildbach 2023-03-16 10:10:34 +01:00
parent 9ab5653fcf
commit dca54e0621
7 changed files with 118 additions and 38 deletions

View File

@ -22,7 +22,7 @@ import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Locale;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* <p>Implementation of the Bech32 encoding.</p>
@ -127,16 +127,16 @@ public class Bech32 {
}
/** Encode a Bech32 string. */
public static String encode(Encoding encoding, String hrp, final byte[] values) {
checkArgument(hrp.length() >= 1, "Human-readable part is too short");
checkArgument(hrp.length() <= 83, "Human-readable part is too long");
hrp = hrp.toLowerCase(Locale.ROOT);
byte[] checksum = createChecksum(encoding, hrp, values);
public static String encode(Encoding encoding, final String hrp, final byte[] values) {
checkArgument(hrp.length() >= 1, () -> "human-readable part is too short: " + hrp.length());
checkArgument(hrp.length() <= 83, () -> "human-readable part is too long: " + hrp.length());
String lcHrp = hrp.toLowerCase(Locale.ROOT);
byte[] checksum = createChecksum(encoding, lcHrp, values);
byte[] combined = new byte[values.length + checksum.length];
System.arraycopy(values, 0, combined, 0, values.length);
System.arraycopy(checksum, 0, combined, values.length, checksum.length);
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
sb.append(hrp);
StringBuilder sb = new StringBuilder(lcHrp.length() + 1 + combined.length);
sb.append(lcHrp);
sb.append('1');
for (byte b : combined) {
sb.append(CHARSET.charAt(b));

View File

@ -20,7 +20,7 @@ import org.bitcoinj.base.utils.MonetaryFormat;
import java.math.BigDecimal;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* Represents a monetary Bitcoin value. This class is immutable and should be treated as a Java <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html#Value-basedClasses">Value-based class</a>.
@ -129,9 +129,9 @@ public final class Coin implements Monetary, Comparable<Coin> {
* @return {@code Coin} object containing value in satoshis
*/
public static Coin valueOf(final int coins, final int cents) {
checkArgument(cents < 100);
checkArgument(cents >= 0);
checkArgument(coins >= 0);
checkArgument(cents < 100, () -> "cents nust be below 100: " + cents);
checkArgument(cents >= 0, () -> "cents cannot be negative: " + cents);
checkArgument(coins >= 0, () -> "coins cannot be negative: " + cents);
final Coin coin = COIN.multiply(coins).add(CENT.multiply(cents));
return coin;
}

View File

@ -27,7 +27,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* A {@code Sha256Hash} wraps a {@code byte[]} so that {@link #equals} and {@link #hashCode} work correctly, allowing it to be used as a key in a
@ -42,7 +42,8 @@ public class Sha256Hash implements Comparable<Sha256Hash> {
private final byte[] bytes;
private Sha256Hash(byte[] rawHashBytes) {
checkArgument(rawHashBytes.length == LENGTH);
checkArgument(rawHashBytes.length == LENGTH, () ->
"length must be " + LENGTH + ": " + rawHashBytes.length);
this.bytes = rawHashBytes;
}

View File

@ -24,7 +24,7 @@ import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Comparator;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* Utility methods for bit, byte, and integer manipulation and conversion. Most of these were moved here
@ -71,13 +71,13 @@ public class ByteUtils {
* @return numBytes byte long array.
*/
public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) {
checkArgument(b.signum() >= 0, "b must be positive or zero");
checkArgument(numBytes > 0, "numBytes must be positive");
checkArgument(b.signum() >= 0, () -> "b must be positive or zero: " + b);
checkArgument(numBytes > 0, () -> "numBytes must be positive: " + numBytes);
byte[] src = b.toByteArray();
byte[] dest = new byte[numBytes];
boolean isFirstByteOnlyForSign = src[0] == 0;
int length = isFirstByteOnlyForSign ? src.length - 1 : src.length;
checkArgument(length <= numBytes, "The given number does not fit in " + numBytes);
checkArgument(length <= numBytes, () -> "The given number does not fit in " + numBytes);
int srcPos = isFirstByteOnlyForSign ? 1 : 0;
int destPos = numBytes - length;
System.arraycopy(src, srcPos, dest, destPos, length);

View File

@ -0,0 +1,65 @@
/*
* Copyright by the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.base.internal;
import java.util.function.Supplier;
public class Preconditions {
/**
* Ensures the truth of an expression involving one or more parameters to the calling method.
* @param expression a boolean expression
* @throws IllegalArgumentException if {@code expression} is false
*/
public static void checkArgument(boolean expression) {
if (!expression)
throw new IllegalArgumentException();
}
/**
* Ensures the truth of an expression involving one or more parameters to the calling method.
* @param expression a boolean expression
* @param messageSupplier supplier of the detail message to be used in the event that a IllegalArgumentException is thrown
* @throws IllegalArgumentException if {@code expression} is false
*/
public static void checkArgument(boolean expression, Supplier<String> messageSupplier) {
if (!expression)
throw new IllegalArgumentException(messageSupplier.get());
}
/**
* Ensures the truth of an expression involving the state of the calling instance, but not
* involving any parameters to the calling method.
* @param expression a boolean expression
* @throws IllegalStateException if {@code expression} is false
*/
public static void checkState(boolean expression) {
if (!expression)
throw new IllegalStateException();
}
/**
* Ensures the truth of an expression involving the state of the calling instance, but not
* involving any parameters to the calling method.
* @param expression a boolean expression
* @param messageSupplier supplier of the detail message to be used in the event that a IllegalStateException is thrown
* @throws IllegalStateException if {@code expression} is false
*/
public static void checkState(boolean expression, Supplier<String> messageSupplier) {
if (!expression)
throw new IllegalStateException(messageSupplier.get());
}
}

View File

@ -22,7 +22,7 @@ import org.bitcoinj.base.Monetary;
import java.math.BigDecimal;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* Represents a monetary fiat value. It was decided to not fold this into {@link Coin} because of type
@ -105,12 +105,14 @@ public final class Fiat implements Monetary, Comparable<Fiat> {
}
public Fiat add(final Fiat value) {
checkArgument(value.currencyCode.equals(currencyCode));
checkArgument(value.currencyCode.equals(currencyCode), () ->
"values to add must be of same currency: " + value.currencyCode + " vs " + currencyCode);
return new Fiat(currencyCode, Math.addExact(this.value, value.value));
}
public Fiat subtract(final Fiat value) {
checkArgument(value.currencyCode.equals(currencyCode));
checkArgument(value.currencyCode.equals(currencyCode), () ->
"values to substract must be of same currency: " + value.currencyCode + " vs " + currencyCode);
return new Fiat(currencyCode, Math.subtractExact(this.value, value.value));
}
@ -127,7 +129,8 @@ public final class Fiat implements Monetary, Comparable<Fiat> {
}
public long divide(final Fiat divisor) {
checkArgument(divisor.currencyCode.equals(currencyCode));
checkArgument(divisor.currencyCode.equals(currencyCode), () ->
"values to divide must be of same currency: " + divisor.currencyCode + " vs " + currencyCode);
return this.value / divisor.value;
}

View File

@ -27,10 +27,10 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.math.LongMath.checkedPow;
import static com.google.common.math.LongMath.divide;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkState;
/**
* <p>
@ -92,8 +92,10 @@ public final class MonetaryFormat {
* Set character to prefix negative values.
*/
public MonetaryFormat negativeSign(char negativeSign) {
checkArgument(!Character.isDigit(negativeSign));
checkArgument(negativeSign > 0);
checkArgument(!Character.isDigit(negativeSign), () ->
"negativeSign can't be digit: " + negativeSign);
checkArgument(negativeSign > 0, () ->
"negativeSign must be positive: " + negativeSign);
if (negativeSign == this.negativeSign)
return this;
else
@ -106,7 +108,8 @@ public final class MonetaryFormat {
* sign will always be interpreted as if the positive sign was used.
*/
public MonetaryFormat positiveSign(char positiveSign) {
checkArgument(!Character.isDigit(positiveSign));
checkArgument(!Character.isDigit(positiveSign), () ->
"positiveSign can't be digit: " + positiveSign);
if (positiveSign == this.positiveSign)
return this;
else
@ -130,8 +133,10 @@ public final class MonetaryFormat {
* used either.
*/
public MonetaryFormat decimalMark(char decimalMark) {
checkArgument(!Character.isDigit(decimalMark));
checkArgument(decimalMark > 0);
checkArgument(!Character.isDigit(decimalMark), () ->
"decimalMark can't be digit: " + decimalMark);
checkArgument(decimalMark > 0, () ->
"decimalMark must be positive: " + decimalMark);
if (decimalMark == this.decimalMark)
return this;
else
@ -194,7 +199,8 @@ public final class MonetaryFormat {
* number of repetitions
*/
public MonetaryFormat repeatOptionalDecimals(int decimals, int repetitions) {
checkArgument(repetitions >= 0);
checkArgument(repetitions >= 0, () ->
"repetitions cannot be negative: " + repetitions);
List<Integer> decimalGroups = new ArrayList<>(repetitions);
for (int i = 0; i < repetitions; i++)
decimalGroups.add(decimals);
@ -245,7 +251,8 @@ public final class MonetaryFormat {
* currency code
*/
public MonetaryFormat code(int codeShift, String code) {
checkArgument(codeShift >= 0);
checkArgument(codeShift >= 0, () ->
"codeShift cannot be negative: " + codeShift);
final String[] codes = null == this.codes
? new String[MAX_DECIMALS]
: Arrays.copyOf(this.codes, this.codes.length);
@ -259,8 +266,10 @@ public final class MonetaryFormat {
* Separator between currency code and formatted value. This configuration is not relevant for parsing.
*/
public MonetaryFormat codeSeparator(char codeSeparator) {
checkArgument(!Character.isDigit(codeSeparator));
checkArgument(codeSeparator > 0);
checkArgument(!Character.isDigit(codeSeparator), () ->
"codeSeparator can't be digit: " + codeSeparator);
checkArgument(codeSeparator > 0, () ->
"codeSeparator must be positive: " + codeSeparator);
if (codeSeparator == this.codeSeparator)
return this;
else
@ -354,13 +363,14 @@ public final class MonetaryFormat {
*/
public CharSequence format(Monetary monetary) {
// preparation
int maxDecimals = minDecimals;
int max = minDecimals;
if (decimalGroups != null)
for (int group : decimalGroups)
maxDecimals += group;
max += group;
int smallestUnitExponent = monetary.smallestUnitExponent();
checkState(maxDecimals <= smallestUnitExponent,
"The maximum possible number of decimals (%s) cannot exceed %s.", maxDecimals, smallestUnitExponent);
int maxDecimals = max;
checkState(maxDecimals <= smallestUnitExponent, () ->
"maxDecimals cannot exceed " + smallestUnitExponent + ": " + maxDecimals);
// rounding
long satoshis = Math.abs(monetary.getValue());
@ -440,7 +450,8 @@ public final class MonetaryFormat {
}
private long parseValue(String str, int smallestUnitExponent) {
checkState(DECIMALS_PADDING.length() >= smallestUnitExponent);
checkState(DECIMALS_PADDING.length() >= smallestUnitExponent, () ->
"smallestUnitExponent can't be higher than " + DECIMALS_PADDING.length() + ": " + smallestUnitExponent);
if (str.isEmpty())
throw new NumberFormatException("empty string");
char first = str.charAt(0);