InternalUtils: replace Guava Joiner and Splitter with native JDK equivalent

Also deprecate the old Joiner and Splitter related members of Utils.
This commit is contained in:
Sean Gilligan 2022-03-31 16:31:13 -07:00 committed by Andreas Schildbach
parent 56d89f37ac
commit 1219e0d7b0
22 changed files with 170 additions and 46 deletions

View file

@ -17,6 +17,8 @@
package org.bitcoinj.core;
import org.bitcoinj.core.internal.InternalUtils;
import java.util.ArrayList;
/**
@ -92,6 +94,6 @@ public class AddressV1Message extends AddressMessage {
@Override
public String toString() {
return "addr: " + Utils.SPACE_JOINER.join(addresses);
return "addr: " + InternalUtils.SPACE_JOINER.join(addresses);
}
}

View file

@ -16,6 +16,8 @@
package org.bitcoinj.core;
import org.bitcoinj.core.internal.InternalUtils;
import java.util.ArrayList;
/**
@ -89,6 +91,6 @@ public class AddressV2Message extends AddressMessage {
@Override
public String toString() {
return "addrv2: " + Utils.SPACE_JOINER.join(addresses);
return "addrv2: " + InternalUtils.SPACE_JOINER.join(addresses);
}
}

View file

@ -19,6 +19,7 @@ package org.bitcoinj.core;
import com.google.common.annotations.*;
import com.google.common.base.*;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.params.AbstractBitcoinNetParams;
import org.bitcoinj.script.*;
import org.slf4j.*;
@ -494,8 +495,7 @@ public class Block extends Message {
s.append(" block: \n");
s.append(" hash: ").append(getHashAsString()).append('\n');
s.append(" version: ").append(version);
String bips = Joiner.on(", ").skipNulls().join(isBIP34() ? "BIP34" : null, isBIP66() ? "BIP66" : null,
isBIP65() ? "BIP65" : null);
String bips = InternalUtils.commaJoin(isBIP34() ? "BIP34" : null, isBIP66() ? "BIP66" : null, isBIP65() ? "BIP65" : null);
if (!bips.isEmpty())
s.append(" (").append(bips).append(')');
s.append('\n');

View file

@ -16,6 +16,8 @@
package org.bitcoinj.core;
import org.bitcoinj.core.internal.InternalUtils;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -75,7 +77,7 @@ public final class BlockLocator {
@Override
public String toString() {
return "Block locator with " + size() + " blocks\n " + Utils.SPACE_JOINER.join(hashes);
return "Block locator with " + size() + " blocks\n " + InternalUtils.SPACE_JOINER.join(hashes);
}
@Override

View file

@ -17,8 +17,8 @@
package org.bitcoinj.core;
import com.google.common.annotations.*;
import com.google.common.base.*;
import com.google.common.util.concurrent.Uninterruptibles;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.utils.*;
import org.bitcoinj.wallet.Wallet;
import org.slf4j.*;
@ -156,7 +156,7 @@ public class TransactionBroadcast {
Collections.shuffle(peers, random);
peers = peers.subList(0, numToBroadcastTo);
log.info("broadcastTransaction: We have {} peers, adding {} to the memory pool", numConnected, tx.getTxId());
log.info("Sending to {} peers, will wait for {}, sending to: {}", numToBroadcastTo, numWaitingFor, Joiner.on(",").join(peers));
log.info("Sending to {} peers, will wait for {}, sending to: {}", numToBroadcastTo, numWaitingFor, InternalUtils.joiner(",").join(peers));
for (final Peer peer : peers) {
try {
CompletableFuture<Void> future = peer.sendMessage(tx);

View file

@ -17,14 +17,13 @@
package org.bitcoinj.core;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.wallet.DefaultRiskAnalysis;
import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
import com.google.common.base.Joiner;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
@ -539,7 +538,7 @@ public class TransactionInput extends ChildMessage {
s.append(": COINBASE");
} else {
s.append(" for [").append(outpoint).append("]: ").append(getScriptSig());
String flags = Joiner.on(", ").skipNulls().join(hasWitness() ? "witness" : null,
String flags = InternalUtils.commaJoin(hasWitness() ? "witness" : null,
hasSequence() ? "sequence: " + Long.toHexString(sequence) : null,
isOptInFullRBF() ? "opts into full RBF" : null);
if (!flags.isEmpty())

View file

@ -24,6 +24,7 @@ import java.util.List;
import javax.annotation.Nullable;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
@ -97,7 +98,7 @@ public class TransactionWitness {
stringPushes.add(Utils.HEX.encode(push));
}
}
return Utils.SPACE_JOINER.join(stringPushes);
return InternalUtils.SPACE_JOINER.join(stringPushes);
}
@Override

View file

@ -30,6 +30,7 @@ import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.bitcoinj.core.internal.InternalUtils;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,10 +48,20 @@ import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterrup
*/
public class Utils {
/** Joiner for concatenating words with a space inbetween. */
/**
* Joiner for concatenating words with a space inbetween.
* @deprecated Use @link java.util.StringJoiner} or a direct Guava dependency
*/
@Deprecated
public static final Joiner SPACE_JOINER = Joiner.on(" ");
/** Splitter for splitting words on whitespaces. */
/**
* Splitter for splitting words on whitespaces.
* @deprecated Use {@link java.lang.String#split(String)} or a direct Guava dependency
*/
@Deprecated
public static final Splitter WHITESPACE_SPLITTER = Splitter.on(Pattern.compile("\\s+"));
/** Hex encoding used throughout the framework. Use with HEX.encode(byte[]) or HEX.decode(CharSequence). */
public static final BaseEncoding HEX = BaseEncoding.base16().lowerCase();
@ -550,6 +561,6 @@ public class Utils {
List<String> parts = new ArrayList<>(stack.size());
for (byte[] push : stack)
parts.add('[' + HEX.encode(push) + ']');
return SPACE_JOINER.join(parts);
return InternalUtils.SPACE_JOINER.join(parts);
}
}

View file

@ -16,8 +16,8 @@
package org.bitcoinj.core;
import com.google.common.base.Joiner;
import com.google.common.net.InetAddresses;
import org.bitcoinj.core.internal.InternalUtils;
import javax.annotation.Nullable;
import java.io.IOException;
@ -323,6 +323,6 @@ public class VersionMessage extends Message {
}
if (services != 0)
strings.add("remaining: " + Long.toBinaryString(services));
return Joiner.on(", ").join(strings);
return InternalUtils.joiner(", ").join(strings);
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.core.internal;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Utilities for internal use only.
*/
public class InternalUtils {
/**
* A functional interface for joining {@link String}s or {@code Object}s via {@link Object#toString()} using
* a pre-configured delimiter.
* <p>
* In previous versions of <b>bitcoinj</b> this functionality was provided by Guava's {@code Joiner}.
*/
@FunctionalInterface
public interface Joiner {
/**
* @param objects A list of objects to join (after calling {@link Object#toString()})
* The components joined into a single {@code String} separated by the pre-configured delimiter.
*/
String join(List<?> objects);
}
/**
* A functional interface for splitting {@link String}s using a pre-configured regular expression.
* <p>
* In previous versions of <b>bitcoinj</b> this functionality was provided by Guava's {@code Splitter}.
*/
@FunctionalInterface
public interface Splitter {
/**
* @param string The {@code String} to split
* @return A list of split {@code String components}
*/
List<String> splitToList(String string);
}
/**
* Return a lambda for joining {@code String}s or {@code Object}s via {@link Object#toString()}.
* @param delimiter The delimiter used to join the {@code String} components
* @return A {@code Joiner} (lambda) instance
*/
public static Joiner joiner(String delimiter) {
return list -> list.stream()
.map(Object::toString)
.collect(Collectors.joining(delimiter));
}
/**
* Return a lambda for splitting a string into components
* @param regex regular expression used to split components
* @return A {@code Splitter} (lambda) instance
*/
public static Splitter splitter(String regex) {
return s -> Arrays.asList(s.split(regex));
}
/**
* A {@link Joiner} for joining strings into a single string delimited by a space character.
*/
public static final Joiner SPACE_JOINER = joiner(" ");
/**
* A {@link Splitter} for splitting a string into components by whitespace.
*/
public static final Splitter WHITESPACE_SPLITTER = splitter("\\s+");
/**
* Join strings with ", " skipping nulls
* @param strings varargs strings
* @return A joined string
*/
public static String commaJoin(String... strings) {
return Arrays.stream(strings).filter(Objects::nonNull).collect(Collectors.joining(", "));
}
}

View file

@ -16,15 +16,16 @@
package org.bitcoinj.crypto;
import com.google.common.base.Splitter;
import org.bitcoinj.core.internal.InternalUtils;
import javax.annotation.Nonnull;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* HD Key derivation path. {@code HDPath} can be used to represent a full path or a relative path.
@ -45,7 +46,9 @@ public class HDPath extends AbstractList<ChildNumber> {
private static final char PREFIX_PRIVATE = 'm';
private static final char PREFIX_PUBLIC = 'M';
private static final char SEPARATOR = '/';
private static final Splitter SEPARATOR_SPLITTER = Splitter.on(SEPARATOR).trimResults();
private static final InternalUtils.Splitter SEPARATOR_SPLITTER = s -> Stream.of(s.split("/"))
.map(String::trim)
.collect(Collectors.toList());
protected final boolean hasPrivateKey;
protected final List<ChildNumber> unmodifiableList;
@ -155,7 +158,7 @@ public class HDPath extends AbstractList<ChildNumber> {
* Where a letter "H" means hardened key. Spaces are ignored.
*/
public static HDPath parsePath(@Nonnull String path) {
List<String> parsedNodes = new LinkedList<>(SEPARATOR_SPLITTER.splitToList(path));
List<String> parsedNodes = SEPARATOR_SPLITTER.splitToList(path);
boolean hasPrivateKey = false;
if (!parsedNodes.isEmpty()) {
final String firstNode = parsedNodes.get(0);

View file

@ -19,6 +19,7 @@ package org.bitcoinj.crypto;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.internal.InternalUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -130,7 +131,7 @@ public class MnemonicCode {
// used as a pseudo-random function. Desired length of the
// derived key is 512 bits (= 64 bytes).
//
String pass = Utils.SPACE_JOINER.join(words);
String pass = InternalUtils.SPACE_JOINER.join(words);
String salt = "mnemonic" + passphrase;
final Stopwatch watch = Stopwatch.createStarted();

View file

@ -16,7 +16,6 @@
package org.bitcoinj.crypto;
import com.google.common.base.Joiner;
import org.bitcoinj.protocols.payments.PaymentSession;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1String;
@ -36,6 +35,9 @@ import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* X509Utils provides tools for working with X.509 certificates and keystores, as used in the BIP 70 payment protocol.
@ -78,7 +80,7 @@ public class X509Utils {
}
if (org != null) {
return withLocation ? Joiner.on(", ").skipNulls().join(org, location, country) : org;
return withLocation ? Stream.of(org, location, country).filter(Objects::nonNull).collect(Collectors.joining()) : org;
} else if (commonName != null) {
return commonName;
} else {

View file

@ -20,6 +20,7 @@
package org.bitcoinj.script;
import org.bitcoinj.core.*;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.crypto.TransactionSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -148,7 +149,7 @@ public class Script {
@Override
public String toString() {
if (!chunks.isEmpty())
return Utils.SPACE_JOINER.join(chunks);
return InternalUtils.SPACE_JOINER.join(chunks);
else
return "<empty>";
}

View file

@ -20,6 +20,7 @@ import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.crypto.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.utils.ListenerRegistration;
@ -1390,7 +1391,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
? seed.decrypt(getKeyCrypter(), DEFAULT_PASSPHRASE_FOR_MNEMONIC, aesKey)
: seed;
final List<String> words = decryptedSeed.getMnemonicCode();
builder.append("Seed as words: ").append(Utils.SPACE_JOINER.join(words)).append('\n');
builder.append("Seed as words: ").append(InternalUtils.SPACE_JOINER.join(words)).append('\n');
builder.append("Seed as hex: ").append(decryptedSeed.toHexString()).append('\n');
} else {
if (seed.isEncrypted())

View file

@ -18,11 +18,11 @@
package org.bitcoinj.wallet;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.crypto.*;
import com.google.common.base.MoreObjects;
import java.util.Objects;
import com.google.common.base.Splitter;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
@ -250,7 +250,7 @@ public class DeterministicSeed implements EncryptableItem {
/** Get the mnemonic code as string, or null if unknown. */
@Nullable
public String getMnemonicString() {
return mnemonicCode != null ? Utils.SPACE_JOINER.join(mnemonicCode) : null;
return mnemonicCode != null ? InternalUtils.SPACE_JOINER.join(mnemonicCode) : null;
}
private static List<String> decodeMnemonicCode(byte[] mnemonicCode) {
@ -258,6 +258,6 @@ public class DeterministicSeed implements EncryptableItem {
}
private static List<String> decodeMnemonicCode(String mnemonicCode) {
return Splitter.on(" ").splitToList(mnemonicCode);
return InternalUtils.WHITESPACE_SPLITTER.splitToList(mnemonicCode);
}
}

View file

@ -18,10 +18,8 @@
package org.bitcoinj.crypto;
import org.bitcoinj.core.Base58;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.params.MainNetParams;
import org.junit.Test;
import org.slf4j.Logger;
@ -219,7 +217,7 @@ public class BIP32Test {
}
String getPathDescription() {
return "m/" + Joiner.on("/").join(Iterables.transform(Arrays.asList(path), Functions.toStringFunction()));
return "m/" + InternalUtils.joiner("/").join(Arrays.asList(path));
}
}
}

View file

@ -25,7 +25,7 @@ import org.junit.Before;
import org.junit.Test;
import static org.bitcoinj.core.Utils.HEX;
import static org.bitcoinj.core.Utils.WHITESPACE_SPLITTER;
import static org.bitcoinj.core.internal.InternalUtils.WHITESPACE_SPLITTER;
/**
* Test the various guard clauses of {@link MnemonicCode}.

View file

@ -28,8 +28,8 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.bitcoinj.core.Utils.HEX;
import static org.bitcoinj.core.Utils.SPACE_JOINER;
import static org.bitcoinj.core.Utils.WHITESPACE_SPLITTER;
import static org.bitcoinj.core.internal.InternalUtils.SPACE_JOINER;
import static org.bitcoinj.core.internal.InternalUtils.WHITESPACE_SPLITTER;
import static org.junit.Assert.assertEquals;
/**

View file

@ -17,7 +17,7 @@
package org.bitcoinj.examples;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.Script;
import org.bitcoinj.wallet.DeterministicSeed;
@ -42,6 +42,6 @@ public class BackupToMnemonicSeed {
System.out.println("seed: " + seed.toString());
System.out.println("creation time: " + seed.getCreationTimeSeconds());
System.out.println("mnemonicCode: " + Utils.SPACE_JOINER.join(seed.getMnemonicCode()));
System.out.println("mnemonicCode: " + InternalUtils.SPACE_JOINER.join(seed.getMnemonicCode()));
}
}

View file

@ -16,10 +16,9 @@
package wallettemplate;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.internal.InternalUtils;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.wallet.DeterministicSeed;
import com.google.common.base.Splitter;
import com.google.common.util.concurrent.Service;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
@ -96,13 +95,13 @@ public class WalletSettingsController implements OverlayController<WalletSetting
// Set the mnemonic seed words.
final List<String> mnemonicCode = seed.getMnemonicCode();
checkNotNull(mnemonicCode); // Already checked for encryption.
String origWords = Utils.SPACE_JOINER.join(mnemonicCode);
String origWords = InternalUtils.SPACE_JOINER.join(mnemonicCode);
wordsArea.setText(origWords);
// Validate words as they are being typed.
MnemonicCode codec = unchecked(MnemonicCode::new);
TextFieldValidator validator = new TextFieldValidator(wordsArea, text ->
!didThrow(() -> codec.check(Splitter.on(' ').splitToList(text)))
!didThrow(() -> codec.check(InternalUtils.splitter(" ").splitToList(text)))
);
// Clear the date picker if the user starts editing the words, if it contained the current wallets date.
@ -181,7 +180,7 @@ public class WalletSettingsController implements OverlayController<WalletSetting
app.mainWindowController().restoreFromSeedAnimation();
long birthday = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), null, "", birthday);
DeterministicSeed seed = new DeterministicSeed(InternalUtils.splitter(" ").splitToList(wordsArea.getText()), null, "", birthday);
// Shut down bitcoinj and restart it with the new seed.
app.walletAppKit().addListener(new Service.Listener() {
@Override

View file

@ -37,8 +37,6 @@ import org.bitcoinj.wallet.CoinSelector;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.io.BaseEncoding;
import com.google.common.io.Resources;
import com.google.protobuf.ByteString;
@ -105,6 +103,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.bitcoinj.core.Coin.parseCoin;
import static com.google.common.base.Preconditions.checkNotNull;
@ -1107,8 +1108,7 @@ public class WalletTool implements Callable<Integer> {
if (seedStr != null) {
DeterministicSeed seed;
// Parse as mnemonic code.
final List<String> split = Collections
.unmodifiableList(Splitter.on(CharMatcher.anyOf(" :;,")).omitEmptyStrings().splitToList(seedStr));
final List<String> split = splitMnemonic(seedStr);
String passphrase = ""; // TODO allow user to specify a passphrase
seed = new DeterministicSeed(split, null, passphrase, creationTimeSecs);
try {
@ -1137,6 +1137,12 @@ public class WalletTool implements Callable<Integer> {
wallet.saveToFile(walletFile);
}
private List<String> splitMnemonic(String seedStr) {
return Stream.of(seedStr.split("[ :;,]")) // anyOf(" :;,")
.filter(s -> !s.isEmpty())
.collect(Collectors.toUnmodifiableList());
}
private void saveWallet(File walletFile) {
try {
// This will save the new state of the wallet to a temp file then rename, in case anything goes wrong.