Copy bisq.cli's console output fmt api, grpc utils

Use the existing Bisq API CLI utils for making calls to the daemon,
and formatting responses.

When/if the CLI is released on jitpack, this code can be removed
from this repo, and loaded from a gradle dependency.
This commit is contained in:
ghubstan 2022-06-24 14:00:41 -03:00
parent e7b33e9e71
commit 6422ac0cab
No known key found for this signature in database
GPG Key ID: E35592D6800A861E
35 changed files with 3506 additions and 0 deletions

View File

@ -0,0 +1,120 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots;
import bisq.proto.grpc.TxFeeRateInfo;
import com.google.common.annotations.VisibleForTesting;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import static java.lang.String.format;
import static java.math.RoundingMode.HALF_UP;
import static java.math.RoundingMode.UNNECESSARY;
/**
* Utility for formatting amounts, volumes and fees; there is no i18n support in the CLI.
*/
@VisibleForTesting
public class CurrencyFormat {
// Use the US locale as a base for all DecimalFormats, but commas should be omitted from number strings.
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US);
// Use the US locale as a base for all NumberFormats, but commas should be omitted from number strings.
private static final NumberFormat US_LOCALE_NUMBER_FORMAT = NumberFormat.getInstance(Locale.US);
// Formats numbers for internal use, i.e., grpc request parameters.
private static final DecimalFormat INTERNAL_FIAT_DECIMAL_FORMAT = new DecimalFormat("##############0.0000");
static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100_000_000);
static final DecimalFormat SATOSHI_FORMAT = new DecimalFormat("###,##0.00000000", DECIMAL_FORMAT_SYMBOLS);
static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.########", DECIMAL_FORMAT_SYMBOLS);
static final DecimalFormat BTC_TX_FEE_FORMAT = new DecimalFormat("###,###,##0", DECIMAL_FORMAT_SYMBOLS);
static final BigDecimal BSQ_SATOSHI_DIVISOR = new BigDecimal(100);
static final DecimalFormat BSQ_FORMAT = new DecimalFormat("###,###,###,##0.00", DECIMAL_FORMAT_SYMBOLS);
public static String formatSatoshis(String sats) {
//noinspection BigDecimalMethodWithoutRoundingCalled
return SATOSHI_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatSatoshis(long sats) {
return SATOSHI_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatBtc(long sats) {
return BTC_FORMAT.format(new BigDecimal(sats).divide(SATOSHI_DIVISOR));
}
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
public static String formatBsq(long sats) {
return BSQ_FORMAT.format(new BigDecimal(sats).divide(BSQ_SATOSHI_DIVISOR));
}
public static String formatTxFeeRateInfo(TxFeeRateInfo txFeeRateInfo) {
if (txFeeRateInfo.getUseCustomTxFeeRate())
return format("custom tx fee rate: %s sats/byte, network rate: %s sats/byte, min network rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getCustomTxFeeRate()),
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()),
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
else
return format("tx fee rate: %s sats/byte, min tx fee rate: %s sats/byte",
formatFeeSatoshis(txFeeRateInfo.getFeeServiceRate()),
formatFeeSatoshis(txFeeRateInfo.getMinFeeServiceRate()));
}
public static String formatPrice(long price) {
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(4);
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(4);
US_LOCALE_NUMBER_FORMAT.setRoundingMode(UNNECESSARY);
return US_LOCALE_NUMBER_FORMAT.format((double) price / 10_000);
}
public static String formatFiatVolume(long volume) {
US_LOCALE_NUMBER_FORMAT.setMinimumFractionDigits(0);
US_LOCALE_NUMBER_FORMAT.setMaximumFractionDigits(0);
US_LOCALE_NUMBER_FORMAT.setRoundingMode(HALF_UP);
return US_LOCALE_NUMBER_FORMAT.format((double) volume / 10_000);
}
public static long toSatoshis(BigDecimal btc) {
return btc.multiply(SATOSHI_DIVISOR).longValue();
}
public static long toSatoshis(String btc) {
if (btc.startsWith("-"))
throw new IllegalArgumentException(format("'%s' is not a positive number", btc));
try {
return new BigDecimal(btc).multiply(SATOSHI_DIVISOR).longValue();
} catch (NumberFormatException e) {
throw new IllegalArgumentException(format("'%s' is not a number", btc));
}
}
public static String formatFeeSatoshis(long sats) {
return BTC_TX_FEE_FORMAT.format(BigDecimal.valueOf(sats));
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots;
import bisq.proto.grpc.*;
import io.grpc.CallCredentials;
import io.grpc.ManagedChannelBuilder;
import lombok.extern.slf4j.Slf4j;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* gRPC Service Stubs -- all blocking.
*/
@Slf4j
final class GrpcStubs {
public final DisputeAgentsGrpc.DisputeAgentsBlockingStub disputeAgentsService;
public final HelpGrpc.HelpBlockingStub helpService;
public final GetVersionGrpc.GetVersionBlockingStub versionService;
public final OffersGrpc.OffersBlockingStub offersService;
public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService;
public final PriceGrpc.PriceBlockingStub priceService;
public final ShutdownServerGrpc.ShutdownServerBlockingStub shutdownService;
public final TradesGrpc.TradesBlockingStub tradesService;
public final WalletsGrpc.WalletsBlockingStub walletsService;
public GrpcStubs(String apiHost, int apiPort, String apiPassword) {
CallCredentials credentials = new PasswordCallCredentials(apiPassword);
var channel = ManagedChannelBuilder.forAddress(apiHost, apiPort).usePlaintext().build();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
log.info("Shutting down bot's grpc channel.");
channel.shutdown().awaitTermination(1, SECONDS);
log.info("Bot channel shutdown complete.");
} catch (InterruptedException ex) {
throw new IllegalStateException(ex);
}
}));
this.disputeAgentsService = DisputeAgentsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.helpService = HelpGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.priceService = PriceGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.shutdownService = ShutdownServerGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.tradesService = TradesGrpc.newBlockingStub(channel).withCallCredentials(credentials);
this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials);
}
}

View File

@ -0,0 +1,62 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots;
import io.grpc.CallCredentials;
import io.grpc.Metadata;
import io.grpc.Metadata.Key;
import java.util.concurrent.Executor;
import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
import static io.grpc.Status.UNAUTHENTICATED;
import static java.lang.String.format;
/**
* Sets the {@value PASSWORD_KEY} rpc call header to a given value.
*/
class PasswordCallCredentials extends CallCredentials {
public static final String PASSWORD_KEY = "password";
private final String passwordValue;
public PasswordCallCredentials(String passwordValue) {
if (passwordValue == null)
throw new IllegalArgumentException(format("'%s' value must not be null", PASSWORD_KEY));
this.passwordValue = passwordValue;
}
@Override
public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier metadataApplier) {
appExecutor.execute(() -> {
try {
var headers = new Metadata();
var passwordKey = Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER);
headers.put(passwordKey, passwordValue);
metadataApplier.apply(headers);
} catch (Throwable ex) {
metadataApplier.fail(UNAUTHENTICATED.withCause(ex));
}
});
}
@Override
public void thisUsesUnstableApi() {
}
}

View File

@ -0,0 +1,153 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table;
import bisq.bots.table.column.Column;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
import static com.google.common.base.Strings.padStart;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A simple table of formatted data for the CLI's output console. A table must be
* created with at least one populated column, and each column passed to the constructor
* must contain the same number of rows. Null checking is omitted because tables are
* populated by protobuf message fields which cannot be null.
* <p>
* All data in a column has the same type: long, string, etc., but a table
* may contain an arbitrary number of columns of any type. For output formatting
* purposes, numeric and date columns should be transformed to a StringColumn type with
* formatted and justified string values before being passed to the constructor.
* <p>
* This is not a relational, rdbms table.
*/
public class Table {
public final Column<?>[] columns;
public final int rowCount;
// Each printed column is delimited by two spaces.
private final int columnDelimiterLength = 2;
/**
* Default constructor. Takes populated Columns.
*
* @param columns containing the same number of rows
*/
public Table(Column<?>... columns) {
this.columns = columns;
this.rowCount = columns.length > 0 ? columns[0].rowCount() : 0;
validateStructure();
}
/**
* Print table data to a PrintStream.
*
* @param printStream the target output stream
*/
public void print(PrintStream printStream) {
printColumnNames(printStream);
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
printRow(printStream, rowIndex);
}
}
/**
* Print table column names to a PrintStream.
*
* @param printStream the target output stream
*/
private void printColumnNames(PrintStream printStream) {
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
var justifiedName = c.getJustification().equals(RIGHT)
? padStart(c.getName(), c.getWidth(), ' ')
: c.getName();
var paddedWidth = colIndex == columns.length - 1
? c.getName().length()
: c.getWidth() + columnDelimiterLength;
printStream.printf("%-" + paddedWidth + "s", justifiedName);
});
printStream.println();
}
/**
* Print a table row to a PrintStream.
*
* @param printStream the target output stream
*/
private void printRow(PrintStream printStream, int rowIndex) {
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
var paddedWidth = colIndex == columns.length - 1
? c.getWidth()
: c.getWidth() + columnDelimiterLength;
printStream.printf("%-" + paddedWidth + "s", c.getRow(rowIndex));
if (colIndex == columns.length - 1)
printStream.println();
});
}
/**
* Returns the table's formatted output as a String.
*
* @return String
*/
@Override
public String toString() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PrintStream ps = new PrintStream(baos, true, UTF_8)) {
print(ps);
}
return baos.toString();
}
/**
* Verifies the table has columns, and each column has the same number of rows.
*/
private void validateStructure() {
if (columns.length == 0)
throw new IllegalArgumentException("Table has no columns.");
if (columns[0].isEmpty())
throw new IllegalArgumentException(
format("Table's 1st column (%s) has no data.",
columns[0].getName()));
IntStream.range(1, columns.length).forEachOrdered(colIndex -> {
var c = columns[colIndex];
if (c.isEmpty())
throw new IllegalStateException(
format("Table column # %d (%s) does not have any data.",
colIndex + 1,
c.getName()));
if (this.rowCount != c.rowCount())
throw new IllegalStateException(
format("Table column # %d (%s) does not have same number of rows as 1st column.",
colIndex + 1,
c.getName()));
});
}
}

View File

@ -0,0 +1,44 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.proto.grpc.OfferInfo;
import java.util.List;
import java.util.function.Predicate;
/**
* Abstract superclass for TableBuilder implementations.
*/
abstract class AbstractTableBuilder {
protected final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
protected final TableType tableType;
protected final List<?> protos;
AbstractTableBuilder(TableType tableType, List<?> protos) {
this.tableType = tableType;
this.protos = protos;
if (protos.isEmpty())
throw new IllegalArgumentException("cannot build a table without rows");
}
public abstract Table build();
}

View File

@ -0,0 +1,301 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.column.Column;
import bisq.bots.table.column.MixedTradeFeeColumn;
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.TradeInfo;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static bisq.bots.CurrencyFormat.formatSatoshis;
import static bisq.bots.table.builder.TableBuilderConstants.COL_HEADER_BUYER_DEPOSIT;
import static bisq.bots.table.builder.TableBuilderConstants.COL_HEADER_SELLER_DEPOSIT;
import static bisq.bots.table.builder.TableType.TRADE_DETAIL_TBL;
import static java.lang.String.format;
import static protobuf.OfferDirection.SELL;
abstract class AbstractTradeListBuilder extends AbstractTableBuilder {
protected final List<TradeInfo> trades;
protected final TradeTableColumnSupplier colSupplier;
protected final Column<String> colTradeId;
@Nullable
protected final Column<Long> colCreateDate;
@Nullable
protected final Column<String> colMarket;
protected final Column<String> colPrice;
@Nullable
protected final Column<String> colPriceDeviation;
@Nullable
protected final Column<String> colCurrency;
@Nullable
protected final Column<Long> colAmount;
@Nullable
protected final Column<String> colMixedAmount;
@Nullable
protected final Column<Long> colMinerTxFee;
@Nullable
protected final MixedTradeFeeColumn colMixedTradeFee;
@Nullable
protected final Column<Long> colBuyerDeposit;
@Nullable
protected final Column<Long> colSellerDeposit;
@Nullable
protected final Column<String> colPaymentMethod;
@Nullable
protected final Column<String> colRole;
@Nullable
protected final Column<String> colOfferType;
@Nullable
protected final Column<String> colClosingStatus;
// Trade detail tbl specific columns
@Nullable
protected final Column<Boolean> colIsDepositPublished;
@Nullable
protected final Column<Boolean> colIsDepositConfirmed;
@Nullable
protected final Column<Boolean> colIsPayoutPublished;
@Nullable
protected final Column<Boolean> colIsCompleted;
@Nullable
protected final Column<Long> colBisqTradeFee;
@Nullable
protected final Column<String> colTradeCost;
@Nullable
protected final Column<Boolean> colIsPaymentStartedMessageSent;
@Nullable
protected final Column<Boolean> colIsPaymentReceivedMessageSent;
@Nullable
protected final Column<String> colAltcoinReceiveAddressColumn;
// BSQ swap trade detail specific columns
@Nullable
protected final Column<String> status;
@Nullable
protected final Column<String> colTxId;
@Nullable
protected final Column<Long> colNumConfirmations;
AbstractTradeListBuilder(TableType tableType, List<?> protos) {
super(tableType, protos);
validate();
this.trades = protos.stream().map(p -> (TradeInfo) p).collect(Collectors.toList());
this.colSupplier = new TradeTableColumnSupplier(tableType, trades);
this.colTradeId = colSupplier.tradeIdColumn.get();
this.colCreateDate = colSupplier.createDateColumn.get();
this.colMarket = colSupplier.marketColumn.get();
this.colPrice = colSupplier.priceColumn.get();
this.colPriceDeviation = colSupplier.priceDeviationColumn.get();
this.colCurrency = colSupplier.currencyColumn.get();
this.colAmount = colSupplier.amountColumn.get();
this.colMixedAmount = colSupplier.mixedAmountColumn.get();
this.colMinerTxFee = colSupplier.minerTxFeeColumn.get();
this.colMixedTradeFee = colSupplier.mixedTradeFeeColumn.get();
this.colBuyerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_BUYER_DEPOSIT);
this.colSellerDeposit = colSupplier.toSecurityDepositColumn.apply(COL_HEADER_SELLER_DEPOSIT);
this.colPaymentMethod = colSupplier.paymentMethodColumn.get();
this.colRole = colSupplier.roleColumn.get();
this.colOfferType = colSupplier.offerTypeColumn.get();
this.colClosingStatus = colSupplier.statusDescriptionColumn.get();
// Trade detail specific columns, some in common with BSQ swap trades detail.
this.colIsDepositPublished = colSupplier.depositPublishedColumn.get();
this.colIsDepositConfirmed = colSupplier.depositConfirmedColumn.get();
this.colIsPayoutPublished = colSupplier.payoutPublishedColumn.get();
this.colIsCompleted = colSupplier.fundsWithdrawnColumn.get();
this.colBisqTradeFee = colSupplier.bisqTradeDetailFeeColumn.get();
this.colTradeCost = colSupplier.tradeCostColumn.get();
this.colIsPaymentStartedMessageSent = colSupplier.paymentStartedMessageSentColumn.get();
this.colIsPaymentReceivedMessageSent = colSupplier.paymentReceivedMessageSentColumn.get();
//noinspection ConstantConditions
this.colAltcoinReceiveAddressColumn = colSupplier.altcoinReceiveAddressColumn.get();
// BSQ swap trade detail specific columns
this.status = colSupplier.bsqSwapStatusColumn.get();
this.colTxId = colSupplier.bsqSwapTxIdColumn.get();
this.colNumConfirmations = colSupplier.numConfirmationsColumn.get();
}
protected void validate() {
if (isTradeDetailTblBuilder.get()) {
if (protos.size() != 1)
throw new IllegalArgumentException("trade detail tbl can have only one row");
} else if (protos.isEmpty()) {
throw new IllegalArgumentException("trade tbl has no rows");
}
}
// Helper Functions
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> tableType.equals(TRADE_DETAIL_TBL);
protected final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
protected final Predicate<TradeInfo> isBsqTrade = (t) -> !isFiatOffer.test(t.getOffer()) && t.getOffer().getBaseCurrencyCode().equals("BSQ");
protected final Predicate<TradeInfo> isBsqSwapTrade = (t) -> t.getOffer().getIsBsqSwapOffer();
protected final Predicate<TradeInfo> isMyOffer = (t) -> t.getOffer().getIsMyOffer();
protected final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
protected final Predicate<TradeInfo> isSellOffer = (t) -> t.getOffer().getDirection().equals(SELL.name());
protected final Predicate<TradeInfo> isBtcSeller = (t) -> (isMyOffer.test(t) && isSellOffer.test(t))
|| (!isMyOffer.test(t) && !isSellOffer.test(t));
protected final Predicate<TradeInfo> isTradeFeeBtc = (t) -> isMyOffer.test(t)
? t.getOffer().getIsCurrencyForMakerFeeBtc()
: t.getIsCurrencyForTakerFeeBtc();
// Column Value Functions
// Altcoin volumes from server are string representations of decimals.
// Converting them to longs ("sats") requires shifting the decimal points
// to left: 2 for BSQ, 8 for other altcoins.
protected final Function<TradeInfo, Long> toAltcoinTradeVolumeAsLong = (t) ->
isBsqTrade.test(t)
? new BigDecimal(t.getTradeVolume()).movePointRight(2).longValue()
: new BigDecimal(t.getTradeVolume()).movePointRight(8).longValue();
protected final Function<TradeInfo, String> toTradeVolumeAsString = (t) ->
isFiatTrade.test(t)
? t.getTradeVolume()
: formatSatoshis(t.getTradeAmountAsLong());
protected final Function<TradeInfo, Long> toTradeVolumeAsLong = (t) ->
isFiatTrade.test(t)
? Long.parseLong(t.getTradeVolume())
: toAltcoinTradeVolumeAsLong.apply(t);
protected final Function<TradeInfo, Long> toTradeAmount = (t) ->
isFiatTrade.test(t)
? t.getTradeAmountAsLong()
: toTradeVolumeAsLong.apply(t);
protected final Function<TradeInfo, String> toMarket = (t) ->
t.getOffer().getBaseCurrencyCode() + "/"
+ t.getOffer().getCounterCurrencyCode();
protected final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
isFiatTrade.test(t)
? t.getOffer().getCounterCurrencyCode()
: t.getOffer().getBaseCurrencyCode();
protected final Function<TradeInfo, String> toPriceDeviation = (t) ->
t.getOffer().getUseMarketBasedPrice()
? format("%.2f%s", t.getOffer().getMarketPriceMarginPct(), "%")
: "N/A";
protected final Function<TradeInfo, Long> toMyMinerTxFee = (t) -> {
if (isBsqSwapTrade.test(t)) {
// The BTC seller pays the miner fee for both sides.
return isBtcSeller.test(t) ? t.getTxFeeAsLong() : 0L;
} else {
return isTaker.test(t)
? t.getTxFeeAsLong()
: t.getOffer().getTxFee();
}
};
protected final Function<TradeInfo, Long> toTradeFeeBsq = (t) -> {
var isMyOffer = t.getOffer().getIsMyOffer();
if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
? 0L // Maker paid BTC fee, return 0.
: t.getOffer().getMakerFee();
} else {
return t.getIsCurrencyForTakerFeeBtc()
? 0L // Taker paid BTC fee, return 0.
: t.getTakerFeeAsLong();
}
};
protected final Function<TradeInfo, Long> toTradeFeeBtc = (t) -> {
var isMyOffer = t.getOffer().getIsMyOffer();
if (isMyOffer) {
return t.getOffer().getIsCurrencyForMakerFeeBtc()
? t.getOffer().getMakerFee()
: 0L; // Maker paid BSQ fee, return 0.
} else {
return t.getIsCurrencyForTakerFeeBtc()
? t.getTakerFeeAsLong()
: 0L; // Taker paid BSQ fee, return 0.
}
};
protected final Function<TradeInfo, Long> toMyMakerOrTakerFee = (t) -> {
if (isBsqSwapTrade.test(t)) {
return isTaker.test(t)
? t.getBsqSwapTradeInfo().getBsqTakerTradeFee()
: t.getBsqSwapTradeInfo().getBsqMakerTradeFee();
} else {
return isTaker.test(t)
? t.getTakerFeeAsLong()
: t.getOffer().getMakerFee();
}
};
protected final Function<TradeInfo, String> toOfferType = (t) -> {
if (isFiatTrade.test(t)) {
return t.getOffer().getDirection() + " " + t.getOffer().getBaseCurrencyCode();
} else {
if (t.getOffer().getDirection().equals("BUY")) {
return "SELL " + t.getOffer().getBaseCurrencyCode();
} else {
return "BUY " + t.getOffer().getBaseCurrencyCode();
}
}
};
protected final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
if (isFiatTrade.test(t)) {
return false;
} else {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
if (isTaker.test(t)) {
return !isBuyerMakerAndSellerTaker;
} else {
return isBuyerMakerAndSellerTaker;
}
}
};
protected final Function<TradeInfo, String> toAltcoinReceiveAddress = (t) -> {
if (showAltCoinBuyerAddress.test(t)) {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
return isBuyerMakerAndSellerTaker // (is BTC buyer / maker)
? contract.getTakerPaymentAccountPayload().getAddress()
: contract.getMakerPaymentAccountPayload().getAddress();
} else {
return "";
}
};
}

View File

@ -0,0 +1,71 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.*;
import bisq.proto.grpc.AddressBalanceInfo;
import java.util.List;
import java.util.stream.Collectors;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.ADDRESS_BALANCE_TBL;
import static java.lang.String.format;
/**
* Builds a {@code bisq.bots.table.Table} from a List of
* {@code bisq.proto.grpc.AddressBalanceInfo} objects.
*/
class AddressBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with address info.
private final Column<String> colAddress;
private final Column<Long> colAvailableBalance;
private final Column<Long> colConfirmations;
private final Column<Boolean> colIsUsed;
AddressBalanceTableBuilder(List<?> protos) {
super(ADDRESS_BALANCE_TBL, protos);
colAddress = new StringColumn(format(COL_HEADER_ADDRESS, "BTC"));
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
this.colConfirmations = new LongColumn(COL_HEADER_CONFIRMATIONS);
this.colIsUsed = new BooleanColumn(COL_HEADER_IS_USED_ADDRESS);
}
public Table build() {
List<AddressBalanceInfo> addresses = protos.stream()
.map(a -> (AddressBalanceInfo) a)
.collect(Collectors.toList());
// Populate columns with address info.
//noinspection SimplifyStreamApiCallChains
addresses.stream().forEachOrdered(a -> {
colAddress.addRow(a.getAddress());
colAvailableBalance.addRow(a.getBalance());
colConfirmations.addRow(a.getNumConfirmations());
colIsUsed.addRow(!a.getIsAddressUnused());
});
// Define and return the table instance with populated columns.
return new Table(colAddress,
colAvailableBalance.asStringColumn(),
colConfirmations.asStringColumn(),
colIsUsed.asStringColumn());
}
}

View File

@ -0,0 +1,75 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.Column;
import bisq.bots.table.column.SatoshiColumn;
import bisq.proto.grpc.BsqBalanceInfo;
import java.util.List;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.BSQ_BALANCE_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a
* {@code bisq.proto.grpc.BsqBalanceInfo} object.
*/
class BsqBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with bsq balance info.
private final Column<Long> colAvailableConfirmedBalance;
private final Column<Long> colUnverifiedBalance;
private final Column<Long> colUnconfirmedChangeBalance;
private final Column<Long> colLockedForVotingBalance;
private final Column<Long> colLockupBondsBalance;
private final Column<Long> colUnlockingBondsBalance;
BsqBalanceTableBuilder(List<?> protos) {
super(BSQ_BALANCE_TBL, protos);
this.colAvailableConfirmedBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_CONFIRMED_BALANCE, true);
this.colUnverifiedBalance = new SatoshiColumn(COL_HEADER_UNVERIFIED_BALANCE, true);
this.colUnconfirmedChangeBalance = new SatoshiColumn(COL_HEADER_UNCONFIRMED_CHANGE_BALANCE, true);
this.colLockedForVotingBalance = new SatoshiColumn(COL_HEADER_LOCKED_FOR_VOTING_BALANCE, true);
this.colLockupBondsBalance = new SatoshiColumn(COL_HEADER_LOCKUP_BONDS_BALANCE, true);
this.colUnlockingBondsBalance = new SatoshiColumn(COL_HEADER_UNLOCKING_BONDS_BALANCE, true);
}
public Table build() {
BsqBalanceInfo balance = (BsqBalanceInfo) protos.get(0);
// Populate columns with bsq balance info.
colAvailableConfirmedBalance.addRow(balance.getAvailableConfirmedBalance());
colUnverifiedBalance.addRow(balance.getUnverifiedBalance());
colUnconfirmedChangeBalance.addRow(balance.getUnconfirmedChangeBalance());
colLockedForVotingBalance.addRow(balance.getLockedForVotingBalance());
colLockupBondsBalance.addRow(balance.getLockupBondsBalance());
colUnlockingBondsBalance.addRow(balance.getUnlockingBondsBalance());
// Define and return the table instance with populated columns.
return new Table(colAvailableConfirmedBalance.asStringColumn(),
colUnverifiedBalance.asStringColumn(),
colUnconfirmedChangeBalance.asStringColumn(),
colLockedForVotingBalance.asStringColumn(),
colLockupBondsBalance.asStringColumn(),
colUnlockingBondsBalance.asStringColumn());
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.Column;
import bisq.bots.table.column.SatoshiColumn;
import bisq.proto.grpc.BtcBalanceInfo;
import java.util.List;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.BTC_BALANCE_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a
* {@code bisq.proto.grpc.BtcBalanceInfo} object.
*/
class BtcBalanceTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with btc balance info.
private final Column<Long> colAvailableBalance;
private final Column<Long> colReservedBalance;
private final Column<Long> colTotalAvailableBalance;
private final Column<Long> colLockedBalance;
BtcBalanceTableBuilder(List<?> protos) {
super(BTC_BALANCE_TBL, protos);
this.colAvailableBalance = new SatoshiColumn(COL_HEADER_AVAILABLE_BALANCE);
this.colReservedBalance = new SatoshiColumn(COL_HEADER_RESERVED_BALANCE);
this.colTotalAvailableBalance = new SatoshiColumn(COL_HEADER_TOTAL_AVAILABLE_BALANCE);
this.colLockedBalance = new SatoshiColumn(COL_HEADER_LOCKED_BALANCE);
}
public Table build() {
BtcBalanceInfo balance = (BtcBalanceInfo) protos.get(0);
// Populate columns with btc balance info.
colAvailableBalance.addRow(balance.getAvailableBalance());
colReservedBalance.addRow(balance.getReservedBalance());
colTotalAvailableBalance.addRow(balance.getTotalAvailableBalance());
colLockedBalance.addRow(balance.getLockedBalance());
// Define and return the table instance with populated columns.
return new Table(colAvailableBalance.asStringColumn(),
colReservedBalance.asStringColumn(),
colTotalAvailableBalance.asStringColumn(),
colLockedBalance.asStringColumn());
}
}

View File

@ -0,0 +1,80 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import java.util.List;
import static bisq.bots.table.builder.TableType.CLOSED_TRADES_TBL;
@SuppressWarnings("ConstantConditions")
class ClosedTradeTableBuilder extends AbstractTradeListBuilder {
ClosedTradeTableBuilder(List<?> protos) {
super(CLOSED_TRADES_TBL, protos);
}
public Table build() {
populateColumns();
return new Table(colTradeId,
colCreateDate.asStringColumn(),
colMarket,
colPrice.justify(),
colPriceDeviation.justify(),
colAmount.asStringColumn(),
colMixedAmount.justify(),
colCurrency,
colMinerTxFee.asStringColumn(),
colMixedTradeFee.asStringColumn(),
colBuyerDeposit.asStringColumn(),
colSellerDeposit.asStringColumn(),
colOfferType,
colClosingStatus);
}
private void populateColumns() {
trades.forEach(t -> {
colTradeId.addRow(t.getTradeId());
colCreateDate.addRow(t.getDate());
colMarket.addRow(toMarket.apply(t));
colPrice.addRow(t.getTradePrice());
colPriceDeviation.addRow(toPriceDeviation.apply(t));
colAmount.addRow(t.getTradeAmountAsLong());
colMixedAmount.addRow(t.getTradeVolume());
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
colMinerTxFee.addRow(toMyMinerTxFee.apply(t));
if (t.getOffer().getIsBsqSwapOffer()) {
// For BSQ Swaps, BTC buyer pays the BSQ trade fee for both sides (BTC seller pays no fee).
var optionalTradeFeeBsq = isBtcSeller.test(t) ? 0L : toTradeFeeBsq.apply(t);
colMixedTradeFee.addRow(optionalTradeFeeBsq, true);
} else if (isTradeFeeBtc.test(t)) {
colMixedTradeFee.addRow(toTradeFeeBtc.apply(t), false);
} else {
// V1 trade fee paid in BSQ.
colMixedTradeFee.addRow(toTradeFeeBsq.apply(t), true);
}
colBuyerDeposit.addRow(t.getOffer().getBuyerSecurityDeposit());
colSellerDeposit.addRow(t.getOffer().getSellerSecurityDeposit());
colOfferType.addRow(toOfferType.apply(t));
colClosingStatus.addRow(t.getClosingStatus());
});
}
}

View File

@ -0,0 +1,64 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import java.util.List;
import static bisq.bots.table.builder.TableType.FAILED_TRADES_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
*/
@SuppressWarnings("ConstantConditions")
class FailedTradeTableBuilder extends AbstractTradeListBuilder {
FailedTradeTableBuilder(List<?> protos) {
super(FAILED_TRADES_TBL, protos);
}
public Table build() {
populateColumns();
return new Table(colTradeId,
colCreateDate.asStringColumn(),
colMarket,
colPrice.justify(),
colAmount.asStringColumn(),
colMixedAmount.justify(),
colCurrency,
colOfferType,
colRole,
colClosingStatus);
}
private void populateColumns() {
trades.forEach(t -> {
colTradeId.addRow(t.getTradeId());
colCreateDate.addRow(t.getDate());
colMarket.addRow(toMarket.apply(t));
colPrice.addRow(t.getTradePrice());
colAmount.addRow(t.getTradeAmountAsLong());
colMixedAmount.addRow(t.getTradeVolume());
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
colOfferType.addRow(toOfferType.apply(t));
colRole.addRow(t.getRole());
colClosingStatus.addRow("Failed");
});
}
}

View File

@ -0,0 +1,259 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.*;
import bisq.proto.grpc.OfferInfo;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.OFFER_TBL;
import static bisq.bots.table.column.Column.JUSTIFICATION.*;
import static bisq.bots.table.column.ZippedStringColumns.DUPLICATION_MODE.EXCLUDE_DUPLICATES;
import static java.lang.String.format;
import static protobuf.OfferDirection.BUY;
import static protobuf.OfferDirection.SELL;
/**
* Builds a {@code bisq.bots.table.Table} from a List of
* {@code bisq.proto.grpc.OfferInfo} objects.
*/
class OfferTableBuilder extends AbstractTableBuilder {
// Columns common to both fiat and cryptocurrency offers.
private final Column<String> colOfferId = new StringColumn(COL_HEADER_UUID, LEFT);
private final Column<String> colDirection = new StringColumn(COL_HEADER_DIRECTION, LEFT);
private final Column<Long> colAmount = new SatoshiColumn("Temp Amount", NONE);
private final Column<Long> colMinAmount = new SatoshiColumn("Temp Min Amount", NONE);
private final Column<String> colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
private final Column<Long> colCreateDate = new Iso8601DateTimeColumn(COL_HEADER_CREATION_DATE);
OfferTableBuilder(List<?> protos) {
super(OFFER_TBL, protos);
}
public Table build() {
List<OfferInfo> offers = protos.stream().map(p -> (OfferInfo) p).collect(Collectors.toList());
return isShowingFiatOffers.get()
? buildFiatOfferTable(offers)
: buildCryptoCurrencyOfferTable(offers);
}
@SuppressWarnings("ConstantConditions")
public Table buildFiatOfferTable(List<OfferInfo> offers) {
@Nullable
Column<String> colEnabled = enabledColumn.get(); // Not boolean: "YES", "NO", or "PENDING"
Column<String> colFiatPrice = new StringColumn(format(COL_HEADER_DETAILED_PRICE, fiatTradeCurrency.get()), RIGHT);
Column<String> colVolume = new StringColumn(format("Temp Volume (%s)", fiatTradeCurrency.get()), NONE);
Column<String> colMinVolume = new StringColumn(format("Temp Min Volume (%s)", fiatTradeCurrency.get()), NONE);
@Nullable
Column<String> colTriggerPrice = fiatTriggerPriceColumn.get();
// Populate columns with offer info.
offers.forEach(o -> {
if (colEnabled != null)
colEnabled.addRow(toEnabled.apply(o));
colDirection.addRow(o.getDirection());
colFiatPrice.addRow(o.getPrice());
colMinAmount.addRow(o.getMinAmount());
colAmount.addRow(o.getAmount());
colVolume.addRow(o.getVolume());
colMinVolume.addRow(o.getMinVolume());
if (colTriggerPrice != null)
colTriggerPrice.addRow(toBlankOrNonZeroValue.apply(o.getTriggerPrice()));
colPaymentMethod.addRow(o.getPaymentMethodShortName());
colCreateDate.addRow(o.getDate());
colOfferId.addRow(o.getId());
});
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
ZippedStringColumns volumeRange =
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, fiatTradeCurrency.get()),
RIGHT,
" - ",
colMinVolume.asStringColumn(),
colVolume.asStringColumn());
// Define and return the table instance with populated columns.
if (isShowingMyOffers.get()) {
return new Table(colEnabled.asStringColumn(),
colDirection,
colFiatPrice.justify(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colTriggerPrice.justify(),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
} else {
return new Table(colDirection,
colFiatPrice.justify(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
}
@SuppressWarnings("ConstantConditions")
public Table buildCryptoCurrencyOfferTable(List<OfferInfo> offers) {
@Nullable
Column<String> colEnabled = enabledColumn.get(); // Not boolean: YES, NO, or PENDING
Column<String> colBtcPrice = new StringColumn(format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, altcoinTradeCurrency.get()), RIGHT);
Column<String> colVolume = new StringColumn(format("Temp Volume (%s)", altcoinTradeCurrency.get()), NONE);
Column<String> colMinVolume = new StringColumn(format("Temp Min Volume (%s)", altcoinTradeCurrency.get()), NONE);
@Nullable
Column<String> colTriggerPrice = altcoinTriggerPriceColumn.get();
// Populate columns with offer info.
offers.forEach(o -> {
if (colEnabled != null)
colEnabled.addRow(toEnabled.apply(o));
colDirection.addRow(directionFormat.apply(o));
colBtcPrice.addRow(o.getPrice());
colAmount.addRow(o.getAmount());
colMinAmount.addRow(o.getMinAmount());
colVolume.addRow(o.getVolume());
colMinVolume.addRow(o.getMinVolume());
if (colTriggerPrice != null)
colTriggerPrice.addRow(toBlankOrNonZeroValue.apply(o.getTriggerPrice()));
colPaymentMethod.addRow(o.getPaymentMethodShortName());
colCreateDate.addRow(o.getDate());
colOfferId.addRow(o.getId());
});
ZippedStringColumns amountRange = zippedAmountRangeColumns.get();
ZippedStringColumns volumeRange =
new ZippedStringColumns(format(COL_HEADER_VOLUME_RANGE, altcoinTradeCurrency.get()),
RIGHT,
" - ",
colMinVolume.asStringColumn(),
colVolume.asStringColumn());
// Define and return the table instance with populated columns.
if (isShowingMyOffers.get()) {
if (isShowingBsqOffers.get()) {
return new Table(colEnabled.asStringColumn(),
colDirection,
colBtcPrice.justify(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
} else {
return new Table(colEnabled.asStringColumn(),
colDirection,
colBtcPrice.justify(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colTriggerPrice.justify(),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
} else {
return new Table(colDirection,
colBtcPrice.justify(),
amountRange.asStringColumn(EXCLUDE_DUPLICATES),
volumeRange.asStringColumn(EXCLUDE_DUPLICATES),
colPaymentMethod,
colCreateDate.asStringColumn(),
colOfferId);
}
}
private final Function<String, String> toBlankOrNonZeroValue = (s) -> s.trim().equals("0") ? "" : s;
private final Supplier<OfferInfo> firstOfferInList = () -> (OfferInfo) protos.get(0);
private final Supplier<Boolean> isShowingMyOffers = () -> firstOfferInList.get().getIsMyOffer();
private final Supplier<Boolean> isShowingFiatOffers = () -> isFiatOffer.test(firstOfferInList.get());
private final Supplier<String> fiatTradeCurrency = () -> firstOfferInList.get().getCounterCurrencyCode();
private final Supplier<String> altcoinTradeCurrency = () -> firstOfferInList.get().getBaseCurrencyCode();
private final Supplier<Boolean> isShowingBsqOffers = () ->
!isFiatOffer.test(firstOfferInList.get()) && altcoinTradeCurrency.get().equals("BSQ");
@Nullable // Not a boolean column: YES, NO, or PENDING.
private final Supplier<StringColumn> enabledColumn = () ->
isShowingMyOffers.get()
? new StringColumn(COL_HEADER_ENABLED, LEFT)
: null;
@Nullable
private final Supplier<StringColumn> fiatTriggerPriceColumn = () ->
isShowingMyOffers.get()
? new StringColumn(format(COL_HEADER_TRIGGER_PRICE, fiatTradeCurrency.get()), RIGHT)
: null;
@Nullable
private final Supplier<StringColumn> altcoinTriggerPriceColumn = () ->
isShowingMyOffers.get() && !isShowingBsqOffers.get()
? new StringColumn(format(COL_HEADER_TRIGGER_PRICE, altcoinTradeCurrency.get()), RIGHT)
: null;
private final Function<OfferInfo, String> toEnabled = (o) -> {
if (o.getIsMyOffer() && o.getIsMyPendingOffer())
return "PENDING";
else
return o.getIsActivated() ? "YES" : "NO";
};
private final Function<String, String> toMirroredDirection = (d) ->
d.equalsIgnoreCase(BUY.name()) ? SELL.name() : BUY.name();
private final Function<OfferInfo, String> directionFormat = (o) -> {
if (isFiatOffer.test(o)) {
return o.getBaseCurrencyCode();
} else {
// Return "Sell BSQ (Buy BTC)", or "Buy BSQ (Sell BTC)".
String direction = o.getDirection();
String mirroredDirection = toMirroredDirection.apply(direction);
Function<String, String> mixedCase = (word) -> word.charAt(0) + word.substring(1).toLowerCase();
return format("%s %s (%s %s)",
mixedCase.apply(mirroredDirection),
o.getBaseCurrencyCode(),
mixedCase.apply(direction),
o.getCounterCurrencyCode());
}
};
private final Supplier<ZippedStringColumns> zippedAmountRangeColumns = () -> {
if (colMinAmount.isEmpty() || colAmount.isEmpty())
throw new IllegalStateException("amount columns must have data");
return new ZippedStringColumns(COL_HEADER_AMOUNT_RANGE,
RIGHT,
" - ",
colMinAmount.asStringColumn(),
colAmount.asStringColumn());
};
}

View File

@ -0,0 +1,62 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import java.util.List;
import static bisq.bots.table.builder.TableType.OPEN_TRADES_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a list of {@code bisq.proto.grpc.TradeInfo} objects.
*/
@SuppressWarnings("ConstantConditions")
class OpenTradeTableBuilder extends AbstractTradeListBuilder {
OpenTradeTableBuilder(List<?> protos) {
super(OPEN_TRADES_TBL, protos);
}
public Table build() {
populateColumns();
return new Table(colTradeId,
colCreateDate.asStringColumn(),
colMarket,
colPrice.justify(),
colAmount.asStringColumn(),
colMixedAmount.justify(),
colCurrency,
colPaymentMethod,
colRole);
}
private void populateColumns() {
trades.forEach(t -> {
colTradeId.addRow(t.getTradeId());
colCreateDate.addRow(t.getDate());
colMarket.addRow(toMarket.apply(t));
colPrice.addRow(t.getTradePrice());
colAmount.addRow(t.getTradeAmountAsLong());
colMixedAmount.addRow(t.getTradeVolume());
colCurrency.addRow(toPaymentCurrencyCode.apply(t));
colPaymentMethod.addRow(t.getOffer().getPaymentMethodShortName());
colRole.addRow(t.getRole());
});
}
}

View File

@ -0,0 +1,68 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.Column;
import bisq.bots.table.column.StringColumn;
import protobuf.PaymentAccount;
import java.util.List;
import java.util.stream.Collectors;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.PAYMENT_ACCOUNT_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a List of
* {@code protobuf.PaymentAccount} objects.
*/
class PaymentAccountTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with payment account info.
private final Column<String> colName;
private final Column<String> colCurrency;
private final Column<String> colPaymentMethod;
private final Column<String> colId;
PaymentAccountTableBuilder(List<?> protos) {
super(PAYMENT_ACCOUNT_TBL, protos);
this.colName = new StringColumn(COL_HEADER_NAME);
this.colCurrency = new StringColumn(COL_HEADER_CURRENCY);
this.colPaymentMethod = new StringColumn(COL_HEADER_PAYMENT_METHOD);
this.colId = new StringColumn(COL_HEADER_UUID);
}
public Table build() {
List<PaymentAccount> paymentAccounts = protos.stream()
.map(a -> (PaymentAccount) a)
.collect(Collectors.toList());
// Populate columns with payment account info.
//noinspection SimplifyStreamApiCallChains
paymentAccounts.stream().forEachOrdered(a -> {
colName.addRow(a.getAccountName());
colCurrency.addRow(a.getSelectedTradeCurrency().getCode());
colPaymentMethod.addRow(a.getPaymentMethod().getId());
colId.addRow(a.getId());
});
// Define and return the table instance with populated columns.
return new Table(colName, colCurrency, colPaymentMethod, colId);
}
}

View File

@ -0,0 +1,68 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import java.util.List;
import static java.util.Collections.singletonList;
/**
* Table builder factory. It is not conventionally named TableBuilderFactory because
* it has no static factory methods. The number of static fields and methods in the
* {@code bisq.bots.table} are kept to a minimum in an effort o reduce class load time
* in the session-less CLI.
*/
public class TableBuilder extends AbstractTableBuilder {
public TableBuilder(TableType tableType, Object proto) {
this(tableType, singletonList(proto));
}
public TableBuilder(TableType tableType, List<?> protos) {
super(tableType, protos);
}
public Table build() {
switch (tableType) {
case ADDRESS_BALANCE_TBL:
return new AddressBalanceTableBuilder(protos).build();
case BSQ_BALANCE_TBL:
return new BsqBalanceTableBuilder(protos).build();
case BTC_BALANCE_TBL:
return new BtcBalanceTableBuilder(protos).build();
case CLOSED_TRADES_TBL:
return new ClosedTradeTableBuilder(protos).build();
case FAILED_TRADES_TBL:
return new FailedTradeTableBuilder(protos).build();
case OFFER_TBL:
return new OfferTableBuilder(protos).build();
case OPEN_TRADES_TBL:
return new OpenTradeTableBuilder(protos).build();
case PAYMENT_ACCOUNT_TBL:
return new PaymentAccountTableBuilder(protos).build();
case TRADE_DETAIL_TBL:
return new TradeDetailTableBuilder(protos).build();
case TRANSACTION_TBL:
return new TransactionTableBuilder(protos).build();
default:
throw new IllegalArgumentException("invalid cli table type " + tableType.name());
}
}
}

View File

@ -0,0 +1,82 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
/**
* Table column name constants.
*/
class TableBuilderConstants {
static final String COL_HEADER_ADDRESS = "%-3s Address";
static final String COL_HEADER_AMOUNT = "Amount";
static final String COL_HEADER_AMOUNT_IN_BTC = "Amount in BTC";
static final String COL_HEADER_AMOUNT_RANGE = "BTC(min - max)";
static final String COL_HEADER_AVAILABLE_BALANCE = "Available Balance";
static final String COL_HEADER_AVAILABLE_CONFIRMED_BALANCE = "Available Confirmed Balance";
static final String COL_HEADER_UNCONFIRMED_CHANGE_BALANCE = "Unconfirmed Change Balance";
static final String COL_HEADER_RESERVED_BALANCE = "Reserved Balance";
static final String COL_HEADER_TOTAL_AVAILABLE_BALANCE = "Total Available Balance";
static final String COL_HEADER_LOCKED_BALANCE = "Locked Balance";
static final String COL_HEADER_LOCKED_FOR_VOTING_BALANCE = "Locked For Voting Balance";
static final String COL_HEADER_LOCKUP_BONDS_BALANCE = "Lockup Bonds Balance";
static final String COL_HEADER_UNLOCKING_BONDS_BALANCE = "Unlocking Bonds Balance";
static final String COL_HEADER_UNVERIFIED_BALANCE = "Unverified Balance";
static final String COL_HEADER_BSQ_SWAP_TRADE_ROLE = "My BSQ Swap Role";
static final String COL_HEADER_BUYER_DEPOSIT = "Buyer Deposit (BTC)";
static final String COL_HEADER_SELLER_DEPOSIT = "Seller Deposit (BTC)";
static final String COL_HEADER_CONFIRMATIONS = "Confirmations";
static final String COL_HEADER_DEVIATION = "Deviation";
static final String COL_HEADER_IS_USED_ADDRESS = "Is Used";
static final String COL_HEADER_CREATION_DATE = "Creation Date (UTC)";
static final String COL_HEADER_CURRENCY = "Currency";
static final String COL_HEADER_DATE_TIME = "Date/Time (UTC)";
static final String COL_HEADER_DETAILED_AMOUNT = "Amount(%-3s)";
static final String COL_HEADER_DETAILED_PRICE = "Price in %-3s for 1 BTC";
static final String COL_HEADER_DETAILED_PRICE_OF_ALTCOIN = "Price in BTC for 1 %-3s";
static final String COL_HEADER_DIRECTION = "Buy/Sell";
static final String COL_HEADER_ENABLED = "Enabled";
static final String COL_HEADER_MARKET = "Market";
static final String COL_HEADER_NAME = "Name";
static final String COL_HEADER_OFFER_TYPE = "Offer Type";
static final String COL_HEADER_PAYMENT_METHOD = "Payment Method";
static final String COL_HEADER_PRICE = "Price";
static final String COL_HEADER_STATUS = "Status";
static final String COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS = "%-3s Buyer Address";
static final String COL_HEADER_TRADE_BUYER_COST = "Buyer Cost(%-3s)";
static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed";
static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published";
static final String COL_HEADER_TRADE_PAYMENT_SENT = "%-3s Sent";
static final String COL_HEADER_TRADE_PAYMENT_RECEIVED = "%-3s Received";
static final String COL_HEADER_TRADE_PAYOUT_PUBLISHED = "Payout Published";
static final String COL_HEADER_TRADE_WITHDRAWN = "Withdrawn";
static final String COL_HEADER_TRADE_ID = "Trade ID";
static final String COL_HEADER_TRADE_ROLE = "My Role";
static final String COL_HEADER_TRADE_SHORT_ID = "ID";
static final String COL_HEADER_TRADE_MAKER_FEE = "Maker Fee(%-3s)";
static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)";
static final String COL_HEADER_TRADE_FEE = "Trade Fee";
static final String COL_HEADER_TRIGGER_PRICE = "Trigger Price(%-3s)";
static final String COL_HEADER_TX_ID = "Tx ID";
static final String COL_HEADER_TX_INPUT_SUM = "Tx Inputs (BTC)";
static final String COL_HEADER_TX_OUTPUT_SUM = "Tx Outputs (BTC)";
static final String COL_HEADER_TX_FEE = "Tx Fee (BTC)";
static final String COL_HEADER_TX_SIZE = "Tx Size (Bytes)";
static final String COL_HEADER_TX_IS_CONFIRMED = "Is Confirmed";
static final String COL_HEADER_TX_MEMO = "Memo";
static final String COL_HEADER_VOLUME_RANGE = "%-3s(min - max)";
static final String COL_HEADER_UUID = "ID";
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
/**
* Used as param in TableBuilder constructor instead of inspecting
* protos to find out what kind of CLI output table should be built.
*/
public enum TableType {
ADDRESS_BALANCE_TBL,
BSQ_BALANCE_TBL,
BTC_BALANCE_TBL,
CLOSED_TRADES_TBL,
FAILED_TRADES_TBL,
OFFER_TBL,
OPEN_TRADES_TBL,
PAYMENT_ACCOUNT_TBL,
TRADE_DETAIL_TBL,
TRANSACTION_TBL
}

View File

@ -0,0 +1,159 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.Column;
import bisq.proto.grpc.TradeInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import static bisq.bots.table.builder.TableType.TRADE_DETAIL_TBL;
import static java.lang.String.format;
import static protobuf.BsqSwapTrade.State.COMPLETED;
import static protobuf.BsqSwapTrade.State.PREPARATION;
/**
* Builds a {@code bisq.bots.table.Table} from a {@code bisq.proto.grpc.TradeInfo} object.
*/
@SuppressWarnings("ConstantConditions")
class TradeDetailTableBuilder extends AbstractTradeListBuilder {
private final Predicate<TradeInfo> isPendingBsqSwap = (t) -> t.getState().equals(PREPARATION.name());
private final Predicate<TradeInfo> isCompletedBsqSwap = (t) -> t.getState().equals(COMPLETED.name());
TradeDetailTableBuilder(List<?> protos) {
super(TRADE_DETAIL_TBL, protos);
}
/**
* Build a single row trade detail table.
*
* @return Table containing one row
*/
public Table build() {
// A trade detail table only has one row.
var trade = trades.get(0);
populateColumns(trade);
List<Column<?>> columns = defineColumnList(trade);
return new Table(columns.toArray(new Column<?>[0]));
}
private void populateColumns(TradeInfo trade) {
if (isBsqSwapTrade.test(trade)) {
var isPending = isPendingBsqSwap.test(trade);
var isCompleted = isCompletedBsqSwap.test(trade);
if (isPending == isCompleted)
throw new IllegalStateException(
format("programmer error: trade must be either pending or completed, is pending=%s and completed=%s",
isPending,
isCompleted));
populateBsqSwapTradeColumns(trade);
} else {
populateBisqV1TradeColumns(trade);
}
}
private void populateBisqV1TradeColumns(TradeInfo trade) {
colTradeId.addRow(trade.getShortId());
colRole.addRow(trade.getRole());
colPrice.addRow(trade.getTradePrice());
colAmount.addRow(toTradeAmount.apply(trade));
colMinerTxFee.addRow(toMyMinerTxFee.apply(trade));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(trade));
colIsDepositPublished.addRow(trade.getIsDepositPublished());
colIsDepositConfirmed.addRow(trade.getIsDepositConfirmed());
colTradeCost.addRow(toTradeVolumeAsString.apply(trade));
colIsPaymentStartedMessageSent.addRow(trade.getIsPaymentStartedMessageSent());
colIsPaymentReceivedMessageSent.addRow(trade.getIsPaymentReceivedMessageSent());
colIsPayoutPublished.addRow(trade.getIsPayoutPublished());
colIsCompleted.addRow(trade.getIsCompleted());
if (colAltcoinReceiveAddressColumn != null)
colAltcoinReceiveAddressColumn.addRow(toAltcoinReceiveAddress.apply(trade));
}
private void populateBsqSwapTradeColumns(TradeInfo trade) {
colTradeId.addRow(trade.getShortId());
colRole.addRow(trade.getRole());
colPrice.addRow(trade.getTradePrice());
colAmount.addRow(toTradeAmount.apply(trade));
colMinerTxFee.addRow(toMyMinerTxFee.apply(trade));
colBisqTradeFee.addRow(toMyMakerOrTakerFee.apply(trade));
colTradeCost.addRow(toTradeVolumeAsString.apply(trade));
var isCompleted = isCompletedBsqSwap.test(trade);
status.addRow(isCompleted ? "COMPLETED" : "PENDING");
if (isCompleted) {
colTxId.addRow(trade.getBsqSwapTradeInfo().getTxId());
colNumConfirmations.addRow(trade.getBsqSwapTradeInfo().getNumConfirmations());
}
}
private List<Column<?>> defineColumnList(TradeInfo trade) {
return isBsqSwapTrade.test(trade)
? getBsqSwapTradeColumnList(isCompletedBsqSwap.test(trade))
: getBisqV1TradeColumnList();
}
private List<Column<?>> getBisqV1TradeColumnList() {
List<Column<?>> columns = new ArrayList<>() {{
add(colTradeId);
add(colRole);
add(colPrice.justify());
add(colAmount.asStringColumn());
add(colMinerTxFee.asStringColumn());
add(colBisqTradeFee.asStringColumn());
add(colIsDepositPublished.asStringColumn());
add(colIsDepositConfirmed.asStringColumn());
add(colTradeCost.justify());
add(colIsPaymentStartedMessageSent.asStringColumn());
add(colIsPaymentReceivedMessageSent.asStringColumn());
add(colIsPayoutPublished.asStringColumn());
add(colIsCompleted.asStringColumn());
}};
if (colAltcoinReceiveAddressColumn != null)
columns.add(colAltcoinReceiveAddressColumn);
return columns;
}
private List<Column<?>> getBsqSwapTradeColumnList(boolean isCompleted) {
List<Column<?>> columns = new ArrayList<>() {{
add(colTradeId);
add(colRole);
add(colPrice.justify());
add(colAmount.asStringColumn());
add(colMinerTxFee.asStringColumn());
add(colBisqTradeFee.asStringColumn());
add(colTradeCost.justify());
add(status);
}};
if (isCompleted)
columns.add(colTxId);
if (!colNumConfirmations.isEmpty())
columns.add(colNumConfirmations.asStringColumn());
return columns;
}
}

View File

@ -0,0 +1,285 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.column.*;
import bisq.proto.grpc.ContractInfo;
import bisq.proto.grpc.OfferInfo;
import bisq.proto.grpc.TradeInfo;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.*;
import static bisq.bots.table.column.AltcoinVolumeColumn.DISPLAY_MODE.ALTCOIN_VOLUME;
import static bisq.bots.table.column.AltcoinVolumeColumn.DISPLAY_MODE.BSQ_VOLUME;
import static bisq.bots.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
import static java.lang.String.format;
/**
* Convenience for supplying column definitions to
* open/closed/failed/detail trade table builders.
*/
@Slf4j
class TradeTableColumnSupplier {
@Getter
private final TableType tableType;
@Getter
private final List<TradeInfo> trades;
public TradeTableColumnSupplier(TableType tableType, List<TradeInfo> trades) {
this.tableType = tableType;
this.trades = trades;
}
private final Supplier<Boolean> isTradeDetailTblBuilder = () -> getTableType().equals(TRADE_DETAIL_TBL);
private final Supplier<Boolean> isOpenTradeTblBuilder = () -> getTableType().equals(OPEN_TRADES_TBL);
private final Supplier<Boolean> isClosedTradeTblBuilder = () -> getTableType().equals(CLOSED_TRADES_TBL);
private final Supplier<Boolean> isFailedTradeTblBuilder = () -> getTableType().equals(FAILED_TRADES_TBL);
private final Supplier<TradeInfo> firstRow = () -> getTrades().get(0);
private final Predicate<OfferInfo> isFiatOffer = (o) -> o.getBaseCurrencyCode().equals("BTC");
private final Predicate<TradeInfo> isFiatTrade = (t) -> isFiatOffer.test(t.getOffer());
private final Predicate<TradeInfo> isBsqSwapTrade = (t) -> t.getOffer().getIsBsqSwapOffer();
private final Predicate<TradeInfo> isTaker = (t) -> t.getRole().toLowerCase().contains("taker");
private final Supplier<Boolean> isSwapTradeDetail = () ->
isTradeDetailTblBuilder.get() && isBsqSwapTrade.test(firstRow.get());
final Supplier<StringColumn> tradeIdColumn = () -> isTradeDetailTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_SHORT_ID)
: new StringColumn(COL_HEADER_TRADE_ID);
final Supplier<Iso8601DateTimeColumn> createDateColumn = () -> isTradeDetailTblBuilder.get()
? null
: new Iso8601DateTimeColumn(COL_HEADER_DATE_TIME);
final Supplier<StringColumn> marketColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_MARKET);
private final Function<TradeInfo, Column<String>> toDetailedPriceColumn = (t) -> {
String colHeader = isFiatTrade.test(t)
? format(COL_HEADER_DETAILED_PRICE, t.getOffer().getCounterCurrencyCode())
: format(COL_HEADER_DETAILED_PRICE_OF_ALTCOIN, t.getOffer().getBaseCurrencyCode());
return new StringColumn(colHeader, RIGHT);
};
final Supplier<Column<String>> priceColumn = () -> isTradeDetailTblBuilder.get()
? toDetailedPriceColumn.apply(firstRow.get())
: new StringColumn(COL_HEADER_PRICE, RIGHT);
final Supplier<Column<String>> priceDeviationColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_DEVIATION, RIGHT);
final Supplier<StringColumn> currencyColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_CURRENCY);
private final Function<TradeInfo, Column<Long>> toDetailedAmountColumn = (t) -> {
String headerCurrencyCode = t.getOffer().getBaseCurrencyCode();
String colHeader = format(COL_HEADER_DETAILED_AMOUNT, headerCurrencyCode);
AltcoinVolumeColumn.DISPLAY_MODE displayMode = headerCurrencyCode.equals("BSQ") ? BSQ_VOLUME : ALTCOIN_VOLUME;
return isFiatTrade.test(t)
? new SatoshiColumn(colHeader)
: new AltcoinVolumeColumn(colHeader, displayMode);
};
// Can be fiat, btc or altcoin amount represented as longs. Placing the decimal
// in the displayed string representation is done in the Column implementation.
final Supplier<Column<Long>> amountColumn = () -> isTradeDetailTblBuilder.get()
? toDetailedAmountColumn.apply(firstRow.get())
: new BtcColumn(COL_HEADER_AMOUNT_IN_BTC);
final Supplier<StringColumn> mixedAmountColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_AMOUNT, RIGHT);
final Supplier<Column<Long>> minerTxFeeColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
? new SatoshiColumn(COL_HEADER_TX_FEE)
: null;
final Supplier<MixedTradeFeeColumn> mixedTradeFeeColumn = () -> isTradeDetailTblBuilder.get()
? null
: new MixedTradeFeeColumn(COL_HEADER_TRADE_FEE);
final Supplier<StringColumn> paymentMethodColumn = () -> isTradeDetailTblBuilder.get() || isClosedTradeTblBuilder.get()
? null
: new StringColumn(COL_HEADER_PAYMENT_METHOD, LEFT);
final Supplier<StringColumn> roleColumn = () -> {
if (isSwapTradeDetail.get())
return new StringColumn(COL_HEADER_BSQ_SWAP_TRADE_ROLE);
else
return isTradeDetailTblBuilder.get() || isOpenTradeTblBuilder.get() || isFailedTradeTblBuilder.get()
? new StringColumn(COL_HEADER_TRADE_ROLE)
: null;
};
final Function<String, Column<Long>> toSecurityDepositColumn = (name) -> isClosedTradeTblBuilder.get()
? new SatoshiColumn(name)
: null;
final Supplier<StringColumn> offerTypeColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_OFFER_TYPE);
final Supplier<StringColumn> statusDescriptionColumn = () -> isTradeDetailTblBuilder.get()
? null
: new StringColumn(COL_HEADER_STATUS);
private final Function<String, Column<Boolean>> toBooleanColumn = BooleanColumn::new;
final Supplier<Column<Boolean>> depositPublishedColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_PUBLISHED)
: null;
};
final Supplier<Column<Boolean>> depositConfirmedColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_DEPOSIT_CONFIRMED)
: null;
};
final Supplier<Column<Boolean>> payoutPublishedColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_PAYOUT_PUBLISHED)
: null;
};
final Supplier<Column<Boolean>> fundsWithdrawnColumn = () -> {
if (isSwapTradeDetail.get())
return null;
else
return isTradeDetailTblBuilder.get()
? toBooleanColumn.apply(COL_HEADER_TRADE_WITHDRAWN)
: null;
};
final Supplier<Column<Long>> bisqTradeDetailFeeColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
String headerCurrencyCode = isTaker.test(t)
? t.getIsCurrencyForTakerFeeBtc() ? "BTC" : "BSQ"
: t.getOffer().getIsCurrencyForMakerFeeBtc() ? "BTC" : "BSQ";
String colHeader = isTaker.test(t)
? format(COL_HEADER_TRADE_TAKER_FEE, headerCurrencyCode)
: format(COL_HEADER_TRADE_MAKER_FEE, headerCurrencyCode);
boolean isBsqSatoshis = headerCurrencyCode.equals("BSQ");
return new SatoshiColumn(colHeader, isBsqSatoshis);
} else {
return null;
}
};
final Function<TradeInfo, String> toPaymentCurrencyCode = (t) ->
isFiatTrade.test(t)
? t.getOffer().getCounterCurrencyCode()
: t.getOffer().getBaseCurrencyCode();
final Supplier<Column<Boolean>> paymentStartedMessageSentColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = format(COL_HEADER_TRADE_PAYMENT_SENT, headerCurrencyCode);
return new BooleanColumn(colHeader);
} else {
return null;
}
};
final Supplier<Column<Boolean>> paymentReceivedMessageSentColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(firstRow.get());
String colHeader = format(COL_HEADER_TRADE_PAYMENT_RECEIVED, headerCurrencyCode);
return new BooleanColumn(colHeader);
} else {
return null;
}
};
final Supplier<Column<String>> tradeCostColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
String headerCurrencyCode = t.getOffer().getCounterCurrencyCode();
String colHeader = format(COL_HEADER_TRADE_BUYER_COST, headerCurrencyCode);
return new StringColumn(colHeader, RIGHT);
} else {
return null;
}
};
final Supplier<Column<String>> bsqSwapTxIdColumn = () -> isSwapTradeDetail.get()
? new StringColumn(COL_HEADER_TX_ID)
: null;
final Supplier<Column<String>> bsqSwapStatusColumn = () -> isSwapTradeDetail.get()
? new StringColumn(COL_HEADER_STATUS)
: null;
final Supplier<Column<Long>> numConfirmationsColumn = () -> isSwapTradeDetail.get()
? new LongColumn(COL_HEADER_CONFIRMATIONS)
: null;
final Predicate<TradeInfo> showAltCoinBuyerAddress = (t) -> {
if (isFiatTrade.test(t)) {
return false;
} else {
ContractInfo contract = t.getContract();
boolean isBuyerMakerAndSellerTaker = contract.getIsBuyerMakerAndSellerTaker();
if (isTaker.test(t)) {
return !isBuyerMakerAndSellerTaker;
} else {
return isBuyerMakerAndSellerTaker;
}
}
};
@Nullable
final Supplier<Column<String>> altcoinReceiveAddressColumn = () -> {
if (isTradeDetailTblBuilder.get()) {
TradeInfo t = firstRow.get();
if (showAltCoinBuyerAddress.test(t)) {
String headerCurrencyCode = toPaymentCurrencyCode.apply(t);
String colHeader = format(COL_HEADER_TRADE_ALTCOIN_BUYER_ADDRESS, headerCurrencyCode);
return new StringColumn(colHeader);
} else {
return null;
}
} else {
return null;
}
};
}

View File

@ -0,0 +1,95 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.builder;
import bisq.bots.table.Table;
import bisq.bots.table.column.*;
import bisq.proto.grpc.TxInfo;
import javax.annotation.Nullable;
import java.util.List;
import static bisq.bots.table.builder.TableBuilderConstants.*;
import static bisq.bots.table.builder.TableType.TRANSACTION_TBL;
/**
* Builds a {@code bisq.bots.table.Table} from a {@code bisq.proto.grpc.TxInfo} object.
*/
class TransactionTableBuilder extends AbstractTableBuilder {
// Default columns not dynamically generated with tx info.
private final Column<String> colTxId;
private final Column<Boolean> colIsConfirmed;
private final Column<Long> colInputSum;
private final Column<Long> colOutputSum;
private final Column<Long> colTxFee;
private final Column<Long> colTxSize;
TransactionTableBuilder(List<?> protos) {
super(TRANSACTION_TBL, protos);
this.colTxId = new StringColumn(COL_HEADER_TX_ID);
this.colIsConfirmed = new BooleanColumn(COL_HEADER_TX_IS_CONFIRMED);
this.colInputSum = new SatoshiColumn(COL_HEADER_TX_INPUT_SUM);
this.colOutputSum = new SatoshiColumn(COL_HEADER_TX_OUTPUT_SUM);
this.colTxFee = new SatoshiColumn(COL_HEADER_TX_FEE);
this.colTxSize = new LongColumn(COL_HEADER_TX_SIZE);
}
public Table build() {
// TODO Add 'gettransactions' api method & show multiple tx in the console.
// For now, a tx tbl is only one row.
TxInfo tx = (TxInfo) protos.get(0);
// Declare the columns derived from tx info.
@Nullable
Column<String> colMemo = tx.getMemo().isEmpty()
? null
: new StringColumn(COL_HEADER_TX_MEMO);
// Populate columns with tx info.
colTxId.addRow(tx.getTxId());
colIsConfirmed.addRow(!tx.getIsPending());
colInputSum.addRow(tx.getInputSum());
colOutputSum.addRow(tx.getOutputSum());
colTxFee.addRow(tx.getFee());
colTxSize.addRow((long) tx.getSize());
if (colMemo != null)
colMemo.addRow(tx.getMemo());
// Define and return the table instance with populated columns.
if (colMemo != null) {
return new Table(colTxId,
colIsConfirmed.asStringColumn(),
colInputSum.asStringColumn(),
colOutputSum.asStringColumn(),
colTxFee.asStringColumn(),
colTxSize.asStringColumn(),
colMemo);
} else {
return new Table(colTxId,
colIsConfirmed.asStringColumn(),
colInputSum.asStringColumn(),
colOutputSum.asStringColumn(),
colTxFee.asStringColumn(),
colTxSize.asStringColumn());
}
}
}

View File

@ -0,0 +1,86 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
/**
* Partial implementation of the {@link Column} interface.
*/
abstract class AbstractColumn<C extends Column<T>, T> implements Column<T> {
// We create an encapsulated StringColumn up front to populate with formatted
// strings in each this.addRow(Long value) call. But we will not know how
// to justify the cached, formatted string until the column is fully populated.
protected final StringColumn stringColumn;
// The name field is not final, so it can be re-set for column alignment.
protected String name;
protected final JUSTIFICATION justification;
// The max width is not known until after column is fully populated.
protected int maxWidth;
public AbstractColumn(String name, JUSTIFICATION justification) {
this.name = name;
this.justification = justification;
this.stringColumn = this instanceof StringColumn ? null : new StringColumn(name, justification);
}
@Override
public String getName() {
return this.name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public int getWidth() {
return maxWidth;
}
@Override
public JUSTIFICATION getJustification() {
return this.justification;
}
@Override
public Column<T> justify() {
if (this instanceof StringColumn && this.justification.equals(RIGHT))
return this.justify();
else
return this; // no-op
}
protected final String toJustifiedString(String s) {
switch (justification) {
case LEFT:
return padEnd(s, maxWidth, ' ');
case RIGHT:
return padStart(s, maxWidth, ' ');
case NONE:
default:
return s;
}
}
}

View File

@ -0,0 +1,88 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.math.BigDecimal;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying altcoin volume with appropriate precision.
*/
public class AltcoinVolumeColumn extends LongColumn {
public enum DISPLAY_MODE {
ALTCOIN_VOLUME,
BSQ_VOLUME,
}
private final DISPLAY_MODE displayMode;
// The default AltcoinVolumeColumn JUSTIFICATION is RIGHT.
public AltcoinVolumeColumn(String name, DISPLAY_MODE displayMode) {
this(name, RIGHT, displayMode);
}
public AltcoinVolumeColumn(String name,
JUSTIFICATION justification,
DISPLAY_MODE displayMode) {
super(name, justification);
this.displayMode = displayMode;
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = toFormattedString.apply(value, displayMode);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return toFormattedString.apply(getRow(rowIndex), displayMode);
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted altcoin value strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return this.stringColumn;
}
private final BiFunction<Long, DISPLAY_MODE, String> toFormattedString = (value, displayMode) -> {
switch (displayMode) {
case ALTCOIN_VOLUME:
return value > 0 ? new BigDecimal(value).movePointLeft(8).toString() : "";
case BSQ_VOLUME:
return value > 0 ? new BigDecimal(value).movePointLeft(2).toString() : "";
default:
throw new IllegalStateException("invalid display mode: " + displayMode);
}
};
}

View File

@ -0,0 +1,131 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.LEFT;
/**
* For displaying boolean values as YES, NO, or user's choice for 'true' and 'false'.
*/
public class BooleanColumn extends AbstractColumn<BooleanColumn, Boolean> {
private static final String DEFAULT_TRUE_AS_STRING = "YES";
private static final String DEFAULT_FALSE_AS_STRING = "NO";
private final List<Boolean> rows = new ArrayList<>();
private final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
private final String trueAsString;
private final String falseAsString;
// The default BooleanColumn JUSTIFICATION is LEFT.
// The default BooleanColumn True AsString value is YES.
// The default BooleanColumn False AsString value is NO.
public BooleanColumn(String name) {
this(name, LEFT, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
}
// Use this constructor to override default LEFT justification.
@SuppressWarnings("unused")
public BooleanColumn(String name, JUSTIFICATION justification) {
this(name, justification, DEFAULT_TRUE_AS_STRING, DEFAULT_FALSE_AS_STRING);
}
// Use this constructor to override default true/false as string defaults.
public BooleanColumn(String name, String trueAsString, String falseAsString) {
this(name, LEFT, trueAsString, falseAsString);
}
// Use this constructor to override default LEFT justification.
public BooleanColumn(String name,
JUSTIFICATION justification,
String trueAsString,
String falseAsString) {
super(name, justification);
this.trueAsString = trueAsString;
this.falseAsString = falseAsString;
this.maxWidth = name.length();
}
@Override
public void addRow(Boolean value) {
rows.add(value);
// We do not know how much padding each StringColumn value needs until it has all the values.
String s = asString(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Boolean> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Boolean getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Boolean newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex)
? trueAsString
: falseAsString;
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted satoshi strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return stringColumn;
}
private String asString(boolean value) {
return value ? trueAsString : falseAsString;
}
}

View File

@ -0,0 +1,48 @@
package bisq.bots.table.column;
import java.util.stream.IntStream;
import static bisq.bots.CurrencyFormat.formatBtc;
import static com.google.common.base.Strings.padEnd;
import static java.util.Comparator.comparingInt;
public class BtcColumn extends SatoshiColumn {
public BtcColumn(String name) {
super(name);
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = formatBtc(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return formatBtc(getRow(rowIndex));
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted satoshi strings, but we did
// not know how much zero padding each string needed until now.
int maxColumnValueWidth = stringColumn.getRows().stream()
.max(comparingInt(String::length))
.get()
.length();
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String btcString = stringColumn.getRow(rowIndex);
if (btcString.length() < maxColumnValueWidth) {
String paddedBtcString = padEnd(btcString, maxColumnValueWidth, '0');
stringColumn.updateRow(rowIndex, paddedBtcString);
}
});
return stringColumn.justify();
}
}

View File

@ -0,0 +1,122 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.List;
public interface Column<T> {
enum JUSTIFICATION {
LEFT,
RIGHT,
NONE
}
/**
* Returns the column's name.
*
* @return name as String
*/
String getName();
/**
* Sets the column name.
*
* @param name of the column
*/
void setName(String name);
/**
* Add column value.
*
* @param value added to column's data (row)
*/
void addRow(T value);
/**
* Returns the column data.
*
* @return rows as List<T>
*/
List<T> getRows();
/**
* Returns the maximum width of the column name, or longest,
* formatted string value -- whichever is greater.
*
* @return width of the populated column as int
*/
int getWidth();
/**
* Returns the number of rows in the column.
*
* @return number of rows in the column as int.
*/
int rowCount();
/**
* Returns true if the column has no data.
*
* @return true if empty, false if not
*/
boolean isEmpty();
/**
* Returns the column value (data) at given row index.
*
* @return value object
*/
T getRow(int rowIndex);
/**
* Update an existing value at the given row index to a new value.
*
* @param rowIndex row index of value to be updated
* @param newValue new value
*/
void updateRow(int rowIndex, T newValue);
/**
* Returns the row value as a formatted String.
*
* @return a row value as formatted String
*/
String getRowAsFormattedString(int rowIndex);
/**
* Return the column with all of its data as a StringColumn with all of its
* formatted string data.
*
* @return StringColumn
*/
StringColumn asStringColumn();
/**
* Convenience for justifying populated StringColumns before being displayed.
* Is only useful for StringColumn instances.
*/
Column<T> justify();
/**
* Returns JUSTIFICATION value (RIGHT|LEFT|NONE) for the column.
*
* @return column JUSTIFICATION
*/
JUSTIFICATION getJustification();
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Double values.
*/
public class DoubleColumn extends NumberColumn<DoubleColumn, Double> {
protected final List<Double> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default DoubleColumn JUSTIFICATION is RIGHT.
public DoubleColumn(String name) {
this(name, RIGHT);
}
public DoubleColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Double value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Double> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Double getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Double newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,83 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.stream.IntStream;
import static bisq.bots.CurrencyFormat.formatFiatVolume;
import static bisq.bots.CurrencyFormat.formatPrice;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying fiat volume or price with appropriate precision.
*/
public class FiatColumn extends LongColumn {
public enum DISPLAY_MODE {
FIAT_PRICE,
FIAT_VOLUME
}
private final DISPLAY_MODE displayMode;
// The default FiatColumn JUSTIFICATION is RIGHT.
// The default FiatColumn DISPLAY_MODE is PRICE.
public FiatColumn(String name) {
this(name, RIGHT, DISPLAY_MODE.FIAT_PRICE);
}
public FiatColumn(String name, DISPLAY_MODE displayMode) {
this(name, RIGHT, displayMode);
}
public FiatColumn(String name,
JUSTIFICATION justification,
DISPLAY_MODE displayMode) {
super(name, justification);
this.displayMode = displayMode;
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = displayMode.equals(DISPLAY_MODE.FIAT_PRICE) ? formatPrice(value) : formatFiatVolume(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
// We cached the formatted fiat price strings, but we did
// not know how much padding each string needed until now.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
return this.stringColumn;
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Integer values.
*/
public class IntegerColumn extends NumberColumn<IntegerColumn, Integer> {
protected final List<Integer> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default IntegerColumn JUSTIFICATION is RIGHT.
public IntegerColumn(String name) {
this(name, RIGHT);
}
public IntegerColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Integer value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Integer> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Integer getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Integer newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,63 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.LEFT;
import static com.google.common.base.Strings.padEnd;
import static com.google.common.base.Strings.padStart;
import static java.lang.System.currentTimeMillis;
import static java.util.TimeZone.getTimeZone;
/**
* For displaying (long) timestamp values as ISO-8601 dates in UTC time zone.
*/
public class Iso8601DateTimeColumn extends LongColumn {
protected final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
// The default Iso8601DateTimeColumn JUSTIFICATION is LEFT.
public Iso8601DateTimeColumn(String name) {
this(name, LEFT);
}
public Iso8601DateTimeColumn(String name, JUSTIFICATION justification) {
super(name, justification);
iso8601DateFormat.setTimeZone(getTimeZone("UTC"));
this.maxWidth = Math.max(name.length(), String.valueOf(currentTimeMillis()).length());
}
@Override
public String getRowAsFormattedString(int rowIndex) {
long time = getRow(rowIndex);
return justification.equals(LEFT)
? padEnd(iso8601DateFormat.format(new Date(time)), maxWidth, ' ')
: padStart(iso8601DateFormat.format(new Date(time)), maxWidth, ' ');
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,93 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying Long values.
*/
public class LongColumn extends NumberColumn<LongColumn, Long> {
protected final List<Long> rows = new ArrayList<>();
protected final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default LongColumn JUSTIFICATION is RIGHT.
public LongColumn(String name) {
this(name, RIGHT);
}
public LongColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(Long value) {
rows.add(value);
String s = String.valueOf(value);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public List<Long> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public Long getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, Long newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
String s = String.valueOf(getRow(rowIndex));
return toJustifiedString(s);
}
@Override
public StringColumn asStringColumn() {
IntStream.range(0, rows.size()).forEachOrdered(rowIndex ->
stringColumn.addRow(getRowAsFormattedString(rowIndex)));
return stringColumn;
}
}

View File

@ -0,0 +1,59 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import static bisq.bots.CurrencyFormat.formatBsq;
import static bisq.bots.CurrencyFormat.formatSatoshis;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying a mix of BSQ and BTC trade fees with appropriate precision.
*/
public class MixedTradeFeeColumn extends LongColumn {
public MixedTradeFeeColumn(String name) {
super(name, RIGHT);
}
@Override
public void addRow(Long value) {
throw new UnsupportedOperationException("use public void addRow(Long value, boolean isBsq) instead");
}
public void addRow(Long value, boolean isBsq) {
rows.add(value);
String s = isBsq
? formatBsq(value) + " BSQ"
: formatSatoshis(value) + " BTC";
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex).toString();
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
/**
* Abstract superclass for numeric Columns.
*
* @param <C> the subclass column's type (LongColumn, IntegerColumn, ...)
* @param <T> the subclass column's numeric Java type (Long, Integer, ...)
*/
abstract class NumberColumn<C extends NumberColumn<C, T>,
T extends Number> extends AbstractColumn<C, T> implements Column<T> {
public NumberColumn(String name, JUSTIFICATION justification) {
super(name, justification);
}
}

View File

@ -0,0 +1,72 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import static bisq.bots.CurrencyFormat.formatBsq;
import static bisq.bots.CurrencyFormat.formatSatoshis;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying BTC or BSQ satoshi values with appropriate precision.
*/
public class SatoshiColumn extends LongColumn {
protected final boolean isBsqSatoshis;
// The default SatoshiColumn JUSTIFICATION is RIGHT.
public SatoshiColumn(String name) {
this(name, RIGHT, false);
}
public SatoshiColumn(String name, boolean isBsqSatoshis) {
this(name, RIGHT, isBsqSatoshis);
}
public SatoshiColumn(String name, JUSTIFICATION justification) {
this(name, justification, false);
}
public SatoshiColumn(String name, JUSTIFICATION justification, boolean isBsqSatoshis) {
super(name, justification);
this.isBsqSatoshis = isBsqSatoshis;
}
@Override
public void addRow(Long value) {
rows.add(value);
// We do not know how much padding each StringColumn value needs until it has all the values.
String s = isBsqSatoshis ? formatBsq(value) : formatSatoshis(value);
stringColumn.addRow(s);
if (isNewMaxWidth.test(s))
maxWidth = s.length();
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return isBsqSatoshis
? formatBsq(getRow(rowIndex))
: formatSatoshis(getRow(rowIndex));
}
@Override
public StringColumn asStringColumn() {
return stringColumn.justify();
}
}

View File

@ -0,0 +1,102 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import static bisq.bots.table.column.Column.JUSTIFICATION.LEFT;
import static bisq.bots.table.column.Column.JUSTIFICATION.RIGHT;
/**
* For displaying justified string values.
*/
public class StringColumn extends AbstractColumn<StringColumn, String> {
private final List<String> rows = new ArrayList<>();
private final Predicate<String> isNewMaxWidth = (s) -> s != null && !s.isEmpty() && s.length() > maxWidth;
// The default StringColumn JUSTIFICATION is LEFT.
public StringColumn(String name) {
this(name, LEFT);
}
// Use this constructor to override default LEFT justification.
public StringColumn(String name, JUSTIFICATION justification) {
super(name, justification);
this.maxWidth = name.length();
}
@Override
public void addRow(String value) {
rows.add(value);
if (isNewMaxWidth.test(value))
maxWidth = value.length();
}
@Override
public List<String> getRows() {
return rows;
}
@Override
public int rowCount() {
return rows.size();
}
@Override
public boolean isEmpty() {
return rows.isEmpty();
}
@Override
public String getRow(int rowIndex) {
return rows.get(rowIndex);
}
@Override
public void updateRow(int rowIndex, String newValue) {
rows.set(rowIndex, newValue);
}
@Override
public String getRowAsFormattedString(int rowIndex) {
return getRow(rowIndex);
}
@Override
public StringColumn asStringColumn() {
return this;
}
@Override
public StringColumn justify() {
if (justification.equals(RIGHT)) {
IntStream.range(0, getRows().size()).forEach(rowIndex -> {
String unjustified = getRow(rowIndex);
String justified = toJustifiedString(unjustified);
updateRow(rowIndex, justified);
});
}
return this;
}
}

View File

@ -0,0 +1,124 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.bots.table.column;
import bisq.bots.table.column.Column.JUSTIFICATION;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
/**
* For zipping multiple StringColumns into a single StringColumn.
* Useful for displaying amount and volume range values.
*/
public class ZippedStringColumns {
public enum DUPLICATION_MODE {
EXCLUDE_DUPLICATES,
INCLUDE_DUPLICATES
}
private final String name;
private final JUSTIFICATION justification;
private final String delimiter;
private final StringColumn[] columns;
public ZippedStringColumns(String name,
JUSTIFICATION justification,
String delimiter,
StringColumn... columns) {
this.name = name;
this.justification = justification;
this.delimiter = delimiter;
this.columns = columns;
validateColumnData();
}
public StringColumn asStringColumn(DUPLICATION_MODE duplicationMode) {
StringColumn stringColumn = new StringColumn(name, justification);
buildRows(stringColumn, duplicationMode);
// Re-set the column name field to its justified value, in case any of the column
// values are longer than the name passed to this constructor.
stringColumn.setName(stringColumn.toJustifiedString(name));
return stringColumn;
}
private void buildRows(StringColumn stringColumn, DUPLICATION_MODE duplicationMode) {
// Populate the StringColumn with unjustified zipped values; we cannot justify
// the zipped values until stringColumn knows its final maxWidth.
IntStream.range(0, columns[0].getRows().size()).forEach(rowIndex -> {
String row = buildRow(rowIndex, duplicationMode);
stringColumn.addRow(row);
});
formatRows(stringColumn);
}
private String buildRow(int rowIndex, DUPLICATION_MODE duplicationMode) {
StringBuilder rowBuilder = new StringBuilder();
@Nullable
List<String> processedValues = duplicationMode.equals(DUPLICATION_MODE.EXCLUDE_DUPLICATES)
? new ArrayList<>()
: null;
IntStream.range(0, columns.length).forEachOrdered(colIndex -> {
// For each column @ rowIndex ...
var value = columns[colIndex].getRows().get(rowIndex);
if (duplicationMode.equals(DUPLICATION_MODE.INCLUDE_DUPLICATES)) {
if (rowBuilder.length() > 0)
rowBuilder.append(delimiter);
rowBuilder.append(value);
} else if (!processedValues.contains(value)) {
if (rowBuilder.length() > 0)
rowBuilder.append(delimiter);
rowBuilder.append(value);
processedValues.add(value);
}
});
return rowBuilder.toString();
}
private void formatRows(StringColumn stringColumn) {
// Now we can justify the zipped string values in the new StringColumn.
IntStream.range(0, stringColumn.getRows().size()).forEach(rowIndex -> {
String unjustified = stringColumn.getRow(rowIndex);
String justified = stringColumn.toJustifiedString(unjustified);
stringColumn.updateRow(rowIndex, justified);
});
}
private void validateColumnData() {
if (columns.length == 0)
throw new IllegalStateException("cannot zip columns because they do not have any data");
StringColumn firstColumn = columns[0];
if (firstColumn.getRows().isEmpty())
throw new IllegalStateException("1st column has no data");
IntStream.range(1, columns.length).forEach(colIndex -> {
if (columns[colIndex].getRows().size() != firstColumn.getRows().size())
throw new IllegalStateException("columns do not have same number of rows");
});
}
}