All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.util.lang.ToStringBuilder Maven / Gradle / Ivy

package org.opentripplanner.util.lang;

import static java.lang.Boolean.TRUE;
import static org.opentripplanner.util.time.DurationUtils.durationToStr;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opentripplanner.util.time.DurationUtils;
import org.opentripplanner.util.time.TimeUtils;

/**
 * This toString builder which add elements to a compact string of the form:
 * 

* {@code ClassName{field1:value, field2:value, ...}} *

* Fields equals to 'ignoreValue' is NOT added to the result string. This produces a short and easy * to read result. You should use {@code null} as 'ignoreValue' if the field is nullable. *

* The naming of the 'add' methods should give a hint to which type the value have, this make it * easier to choose the right method and less error prune as compared with relaying on pure * override, which often result in a wrong method call. *

* The builder should be independent of locale, the value should always be formatted the same way, * this allows us to use the toString in unit tests. */ public class ToStringBuilder { /** A random in value, not expected to exist in data */ private static final int RANDOM_IGNORE_VALUE = -9_371_207; private static final String FIELD_SEPARATOR = ", "; private static final String FIELD_VALUE_SEP = ": "; private static final String NULL_VALUE = "null"; private final StringBuilder sb = new StringBuilder(); private final OtpNumberFormat numFormat = new OtpNumberFormat(); boolean first = true; private ToStringBuilder(String name) { sb.append(name).append("{"); } /** * Create a ToStringBuilder for a regular POJO type. This builder will include metadata(class and * field names) when building the to string. */ public static ToStringBuilder of(Class clazz) { return new ToStringBuilder(clazz.getSimpleName()); } /** * Create a ToStringBuilder for a regular POJO type without including the type in the name. Some * classes are always embedded in other classes and the type is given, for these cases this * builder make the toString a bit easier to read. */ public static ToStringBuilder of() { return new ToStringBuilder(""); } /* General purpose formatters */ public ToStringBuilder addNum(String name, Number num) { return addIfNotNull(name, num, numFormat::formatNumber); } public ToStringBuilder addNum(String name, Number value, Number ignoreValue) { return addIfNotIgnored(name, value, ignoreValue, numFormat::formatNumber); } public ToStringBuilder addNum(String name, Number num, String unit) { return addIfNotNull(name, num, n -> numFormat.formatNumber(n, unit)); } public ToStringBuilder addCost(String name, Integer cost, Integer ignoreValue) { return addIfNotIgnored(name, cost, ignoreValue, OtpNumberFormat::formatCost); } public ToStringBuilder addCostCenti(String name, Integer cost, Integer ignoreValue) { return addIfNotIgnored(name, cost, ignoreValue, OtpNumberFormat::formatCostCenti); } public ToStringBuilder addBool(String name, Boolean value) { return addIfNotNull(name, value); } public ToStringBuilder addBoolIfTrue(String name, Boolean value) { if (TRUE.equals(value)) { addLabel(name); } return this; } public ToStringBuilder addStr(String name, String value, String ignoreValue) { return addIfNotIgnored(name, value, ignoreValue, v -> "'" + v + "'"); } public ToStringBuilder addStr(String name, String value) { return addStr(name, value, null); } public ToStringBuilder addEnum(String name, Enum value) { return addEnum(name, value, null); } public ToStringBuilder addEnum(String name, Enum value, Enum ignoreValue) { return addIfNotIgnored(name, value, ignoreValue, Enum::name); } public ToStringBuilder addObj(String name, Object obj) { return addObj(name, obj, null); } public ToStringBuilder addObj(String name, Object value, @Nullable Object ignoreValue) { return addIfNotIgnored(name, value, ignoreValue, Object::toString); } /** * Use this if you would like a custom toString function to convert the value. If the given value * is null, then the value is not printed. *

* Implementation note! The "Op" (Operation) suffix is necessary to separate this from * {@link #addObj(String, Object, Object)}, when the last argument is null. */ public ToStringBuilder addObjOp( String name, @Nullable T value, Function toObjectOp ) { return addObjOp(name, value, null, toObjectOp); } /** * Use this if you would like a custom toString function to convert the value. If the given value * equals the given {@code ignoreValue}, then the value is not printed. */ public ToStringBuilder addObjOp( String name, @Nullable T value, @Nullable T ignoreValue, Function toObjectOp ) { return addIfNotIgnored(name, value, ignoreValue, v -> nullSafeToString(toObjectOp.apply(v))); } public ToStringBuilder addInts(String name, int[] intArray) { return addIfNotNull(name, intArray, Arrays::toString); } public ToStringBuilder addDoubles(String name, double[] value, double ignoreValue) { if (value == null) { return addIt(name, "null"); } if (Arrays.stream(value).allMatch(it -> Objects.equals(it, ignoreValue))) { return this; } return addIt(name, Arrays.toString(value)); } /** Add collection if not null or not empty, all elements are added */ public ToStringBuilder addCol(String name, Collection c) { return addIfNotNull(name, c == null || c.isEmpty() ? null : c); } public ToStringBuilder addCol(String name, Collection c, Collection ignoreValue) { return addIfNotIgnored(name, c, ignoreValue, Objects::toString); } /** * Add collection if not null or not empty, all elements are added using the given custom * {@code toStingOp}. */ public ToStringBuilder addCol(String name, Collection c, Function toStingOp) { return addIfNotNull(name, c == null || c.isEmpty() ? null : c.stream().map(toStingOp).toList()); } /** Add the collection, truncate the number of elements at given maxLimit. */ public ToStringBuilder addCollection(String name, Collection c, int maxLimit) { if (c == null) { return this; } if (c.size() > maxLimit + 1) { String value = c .stream() .limit(maxLimit) .map(Object::toString) .collect(Collectors.joining(", ")); return addIt(name + "(" + maxLimit + "/" + c.size() + ")", "[" + value + ", ..]"); } return addIfNotNull(name, c); } public ToStringBuilder addColSize(String name, Collection c) { return addIfNotNull(name, c, x -> String.format("%d items", x.size())); } /** Add the collection, truncate the number of elements at given maxLimit. */ public ToStringBuilder addIntArraySize(String name, int[] array, int notSet) { if (array == null) { return this; } return addIt(name, Arrays.stream(array).filter(t -> t != notSet).count() + "/" + array.length); } /** Add the BitSet: name : {cardinality}/{logical size}/{size} */ public ToStringBuilder addBitSetSize(String name, BitSet bitSet) { if (bitSet == null) { return this; } return addIt(name, bitSet.cardinality() + "/" + bitSet.length()); } /* Special purpose formatters */ /** Add a Coordinate location, longitude or latitude */ public ToStringBuilder addCoordinate(String name, Number num) { return addIfNotNull(name, num, numFormat::formatCoordinate); } public ToStringBuilder addDateTime(String name, Instant time) { return addIfNotNull(name, time, Instant::toString); } /** * Add the TIME part in the local system timezone using 24 hours. Format: HH:mm:ss. Note! The * DATE is not printed. {@code null} value is ignored. */ public ToStringBuilder addTime(String name, ZonedDateTime time) { return addIfNotNull(name, time, DateTimeFormatter.ISO_LOCAL_TIME::format); } /** * Add time in seconds since midnight. Format: hh:mm:ss. Ignore default values. */ public ToStringBuilder addServiceTime(String name, int timeSecondsPastMidnight, int ignoreValue) { return addIfNotIgnored(name, timeSecondsPastMidnight, ignoreValue, TimeUtils::timeToStrCompact); } /** * Add time in seconds since midnight. Format: hh:mm:ss. */ public ToStringBuilder addServiceTime(String name, int timeSecondsPastMidnight) { return addIfNotIgnored( name, timeSecondsPastMidnight, RANDOM_IGNORE_VALUE, TimeUtils::timeToStrCompact ); } /** * Add times in seconds since midnight. Format: hh:mm. {@code null} value is ignored. */ public ToStringBuilder addServiceTimeSchedule(String name, int[] value) { return addIfNotNull( name, value, a -> Arrays .stream(a) .mapToObj(TimeUtils::timeToStrCompact) .collect(Collectors.joining(" ", "[", "]")) ); } /** * Add the TIME part in the local system timezone using 24 hours. Format: HH:mm:ss. Note! The * DATE is not printed. {@code null} value is ignored. */ public ToStringBuilder addDate(String name, LocalDate time) { return addIfNotNull(name, time, DateTimeFormatter.ISO_LOCAL_DATE::format); } /** * Add a duration to the string in format like '3h4m35s'. Each component (hours, minutes, and or * seconds) is only added if they are not zero {@code 0}. This is the same format as the {@link * Duration#toString()}, but without the 'PT' prefix. {@code null} value is ignored. */ public ToStringBuilder addDurationSec(String name, Integer durationSeconds) { return addDurationSec(name, durationSeconds, null); } /** * Add a duration to the string in format like '3h4m35s'. Each component (hours, minutes, and or * seconds) is only added if they are not zero {@code 0}. This is the same format as the {@link * Duration#toString()}, but without the 'PT' prefix. {@code null} value is ignored. */ public ToStringBuilder addDurationSec(String name, Integer durationSeconds, Integer ignoreValue) { return addIfNotIgnored(name, durationSeconds, ignoreValue, DurationUtils::durationToStr); } /** * Same as {@link #addDuration(String, Duration, Duration)} with ignore-value {@code null}. */ public ToStringBuilder addDuration(String name, Duration duration) { return addDuration(name, duration, null); } /** * Add a duration to the string in format like '3h4m35s'. Each component (hours, minutes, and or * seconds) is only added if they are not zero {@code 0}. This is the same format as the {@link * Duration#toString()}, but without the 'PT' prefix. */ public ToStringBuilder addDuration(String name, Duration duration, Duration ignoreValue) { return addIfNotIgnored(name, duration, ignoreValue, d -> durationToStr((int) d.toSeconds())); } @Override public String toString() { return sb.append("}").toString(); } /** private methods */ private ToStringBuilder addIfNotNull(String name, T value) { return addIfNotIgnored(name, value, null, Object::toString); } private ToStringBuilder addIfNotNull(String name, T value, Function vToString) { return addIfNotIgnored(name, value, null, vToString); } private ToStringBuilder addIfNotIgnored( String name, T value, T ignoreValue, Function mapToString ) { // 'ignoreValue' should be the first argument here to avoid calling equals when // 'ignoreValue=null' and the type do not support equals(..). if (Objects.equals(ignoreValue, value)) { return this; } if (value == null) { return addIt(name, NULL_VALUE); } return addIt(name, mapToString.apply(value)); } private ToStringBuilder addIt(String name, @Nonnull String value) { addLabel(name); addValue(value); return this; } private void addLabel(String name) { if (first) { first = false; } else { sb.append(FIELD_SEPARATOR); } sb.append(name); } private void addValue(@Nonnull String value) { sb.append(FIELD_VALUE_SEP); sb.append(value); } /** * Map the given object to a String. If the input object is {@code null} the string * {@code "null"} is returned if not the {@link Object#toString()} method is called. */ public static String nullSafeToString(@Nullable Object object) { if (object == null) { return NULL_VALUE; } return object.toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy