Skip to content

Commit c057da2

Browse files
committed
Extend supported types in ObjectUtils.nullSafeConciseToString()
This commit extends the list of explicitly supported types in ObjectUtils.nullSafeConciseToString() with the following. - Optional - File - Path - InetAddress - Charset - Currency - TimeZone - ZoneId - Pattern Closes gh-30806
1 parent a7f0732 commit c057da2

File tree

2 files changed

+163
-7
lines changed

2 files changed

+163
-7
lines changed

spring-core/src/main/java/org/springframework/util/ObjectUtils.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,26 @@
1616

1717
package org.springframework.util;
1818

19+
import java.io.File;
1920
import java.lang.reflect.Array;
21+
import java.net.InetAddress;
2022
import java.net.URI;
2123
import java.net.URL;
24+
import java.nio.charset.Charset;
25+
import java.nio.file.Path;
26+
import java.time.ZoneId;
2227
import java.time.temporal.Temporal;
2328
import java.util.Arrays;
2429
import java.util.Collection;
30+
import java.util.Currency;
2531
import java.util.Date;
2632
import java.util.Locale;
2733
import java.util.Map;
2834
import java.util.Optional;
2935
import java.util.StringJoiner;
36+
import java.util.TimeZone;
3037
import java.util.UUID;
38+
import java.util.regex.Pattern;
3139

3240
import org.springframework.lang.Nullable;
3341

@@ -923,19 +931,27 @@ public static String nullSafeToString(@Nullable short[] array) {
923931
* <p>Returns:
924932
* <ul>
925933
* <li>{@code "null"} if {@code obj} is {@code null}</li>
934+
* <li>{@code"Optional.empty"} if {@code obj} is an empty {@link Optional}</li>
935+
* <li>{@code"Optional[<concise-string>]"} if {@code obj} is a non-empty {@code Optional},
936+
* where {@code <concise-string>} is the result of invoking {@link #nullSafeConciseToString}
937+
* on the object contained in the {@code Optional}</li>
926938
* <li>{@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}</li>
939+
* <li>{@linkplain Charset#name() Charset name} if {@code obj} is a {@link Charset}</li>
940+
* <li>{@linkplain TimeZone#getID() TimeZone ID} if {@code obj} is a {@link TimeZone}</li>
941+
* <li>{@linkplain ZoneId#getId() Zone ID} if {@code obj} is a {@link ZoneId}</li>
927942
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
928943
* if {@code obj} is a {@link String} or {@link CharSequence}</li>
929944
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
930945
* if {@code obj} is a <em>simple value type</em> whose {@code toString()} method
931-
* returns a non-null value.</li>
946+
* returns a non-null value</li>
932947
* <li>Otherwise, a string representation of the object's type name concatenated
933-
* with {@code @} and a hex string form of the object's identity hash code</li>
948+
* with {@code "@"} and a hex string form of the object's identity hash code</li>
934949
* </ul>
935950
* <p>In the context of this method, a <em>simple value type</em> is any of the following:
936-
* a primitive wrapper (excluding {@code Void}), an {@code Enum}, a {@code Number},
937-
* a {@code Date}, a {@code Temporal}, a {@code UUID}, a {@code URI}, a {@code URL},
938-
* or a {@code Locale}.
951+
* primitive wrapper (excluding {@link Void}), {@link Enum}, {@link Number},
952+
* {@link Date}, {@link Temporal}, {@link File}, {@link Path}, {@link URI},
953+
* {@link URL}, {@link InetAddress}, {@link Currency}, {@link Locale},
954+
* {@link UUID}, {@link Pattern}.
939955
* @param obj the object to build a string representation for
940956
* @return a concise string representation of the supplied object
941957
* @since 5.3.27
@@ -946,9 +962,23 @@ public static String nullSafeConciseToString(@Nullable Object obj) {
946962
if (obj == null) {
947963
return "null";
948964
}
965+
if (obj instanceof Optional<?>) {
966+
Optional<?> optional = (Optional<?>) obj;
967+
return (!optional.isPresent() ? "Optional.empty" :
968+
String.format("Optional[%s]", nullSafeConciseToString(optional.get())));
969+
}
949970
if (obj instanceof Class<?>) {
950971
return ((Class<?>) obj).getName();
951972
}
973+
if (obj instanceof Charset) {
974+
return ((Charset) obj).name();
975+
}
976+
if (obj instanceof TimeZone) {
977+
return ((TimeZone) obj).getID();
978+
}
979+
if (obj instanceof ZoneId) {
980+
return ((ZoneId) obj).getId();
981+
}
952982
if (obj instanceof CharSequence) {
953983
return StringUtils.truncate((CharSequence) obj);
954984
}
@@ -964,7 +994,10 @@ public static String nullSafeConciseToString(@Nullable Object obj) {
964994

965995
/**
966996
* Derived from {@link org.springframework.beans.BeanUtils#isSimpleValueType}.
967-
* As of 5.3.28, considering {@code UUID} in addition to the bean-level check.
997+
* <p>As of 5.3.28, considering {@link UUID} in addition to the bean-level check.
998+
* <p>As of 5.3.29, additionally considering {@link File}, {@link Path},
999+
* {@link InetAddress}, {@link Charset}, {@link Currency}, {@link TimeZone},
1000+
* {@link ZoneId}, {@link Pattern}.
9681001
*/
9691002
private static boolean isSimpleValueType(Class<?> type) {
9701003
return (Void.class != type && void.class != type &&
@@ -974,10 +1007,18 @@ private static boolean isSimpleValueType(Class<?> type) {
9741007
Number.class.isAssignableFrom(type) ||
9751008
Date.class.isAssignableFrom(type) ||
9761009
Temporal.class.isAssignableFrom(type) ||
977-
UUID.class == type ||
1010+
ZoneId.class.isAssignableFrom(type) ||
1011+
TimeZone.class.isAssignableFrom(type) ||
1012+
File.class.isAssignableFrom(type) ||
1013+
Path.class.isAssignableFrom(type) ||
1014+
Charset.class.isAssignableFrom(type) ||
1015+
Currency.class.isAssignableFrom(type) ||
1016+
InetAddress.class.isAssignableFrom(type) ||
9781017
URI.class == type ||
9791018
URL.class == type ||
1019+
UUID.class == type ||
9801020
Locale.class == type ||
1021+
Pattern.class == type ||
9811022
Class.class == type));
9821023
}
9831024

spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,34 @@
1616

1717
package org.springframework.util;
1818

19+
import java.io.File;
1920
import java.io.IOException;
2021
import java.math.BigDecimal;
2122
import java.math.BigInteger;
23+
import java.net.InetAddress;
2224
import java.net.URI;
2325
import java.net.URL;
26+
import java.net.UnknownHostException;
27+
import java.nio.charset.Charset;
28+
import java.nio.charset.StandardCharsets;
29+
import java.nio.file.Paths;
2430
import java.sql.SQLException;
2531
import java.time.LocalDate;
32+
import java.time.ZoneId;
2633
import java.util.ArrayList;
2734
import java.util.Arrays;
2835
import java.util.Collections;
36+
import java.util.Currency;
2937
import java.util.Date;
3038
import java.util.HashMap;
3139
import java.util.HashSet;
3240
import java.util.List;
3341
import java.util.Locale;
42+
import java.util.Optional;
3443
import java.util.Set;
44+
import java.util.TimeZone;
3545
import java.util.UUID;
46+
import java.util.regex.Pattern;
3647

3748
import org.junit.jupiter.api.Nested;
3849
import org.junit.jupiter.api.Test;
@@ -851,6 +862,41 @@ void nullSafeConciseToStringForNull() {
851862
assertThat(ObjectUtils.nullSafeConciseToString(null)).isEqualTo("null");
852863
}
853864

865+
@Test
866+
void nullSafeConciseToStringForEmptyOptional() {
867+
Optional<String> optional = Optional.empty();
868+
assertThat(ObjectUtils.nullSafeConciseToString(optional)).isEqualTo("Optional.empty");
869+
}
870+
871+
@Test
872+
void nullSafeConciseToStringForNonEmptyOptionals() {
873+
Optional<Tropes> optionalEnum = Optional.of(Tropes.BAR);
874+
String expected = "Optional[BAR]";
875+
assertThat(ObjectUtils.nullSafeConciseToString(optionalEnum)).isEqualTo(expected);
876+
877+
String repeat100 = repeat("X", 100);
878+
String repeat101 = repeat("X", 101);
879+
880+
Optional<String> optionalString = Optional.of(repeat100);
881+
expected = String.format("Optional[%s]", repeat100);
882+
assertThat(ObjectUtils.nullSafeConciseToString(optionalString)).isEqualTo(expected);
883+
884+
optionalString = Optional.of(repeat101);
885+
expected = String.format("Optional[%s]", repeat100 + truncated);
886+
assertThat(ObjectUtils.nullSafeConciseToString(optionalString)).isEqualTo(expected);
887+
}
888+
889+
@Test
890+
void nullSafeConciseToStringForNonEmptyOptionalCustomType() {
891+
class CustomType {
892+
}
893+
894+
CustomType customType = new CustomType();
895+
Optional<CustomType> optional = Optional.of(customType);
896+
String expected = String.format("Optional[%s]", ObjectUtils.nullSafeConciseToString(customType));
897+
assertThat(ObjectUtils.nullSafeConciseToString(optional)).isEqualTo(expected);
898+
}
899+
854900
@Test
855901
void nullSafeConciseToStringForClass() {
856902
assertThat(ObjectUtils.nullSafeConciseToString(String.class)).isEqualTo("java.lang.String");
@@ -914,6 +960,30 @@ void nullSafeConciseToStringForUUID() {
914960
assertThat(ObjectUtils.nullSafeConciseToString(id)).isEqualTo(id.toString());
915961
}
916962

963+
@Test
964+
void nullSafeConciseToStringForFile() {
965+
String path = "/tmp/file.txt";
966+
assertThat(ObjectUtils.nullSafeConciseToString(new File(path))).isEqualTo(path);
967+
968+
path = "/tmp/" + repeat("xyz", 32);
969+
assertThat(ObjectUtils.nullSafeConciseToString(new File(path)))
970+
.hasSize(truncatedLength)
971+
.startsWith(path.subSequence(0, 100))
972+
.endsWith(truncated);
973+
}
974+
975+
@Test
976+
void nullSafeConciseToStringForPath() {
977+
String path = "/tmp/file.txt";
978+
assertThat(ObjectUtils.nullSafeConciseToString(Paths.get(path))).isEqualTo(path);
979+
980+
path = "/tmp/" + repeat("xyz", 32);
981+
assertThat(ObjectUtils.nullSafeConciseToString(Paths.get(path)))
982+
.hasSize(truncatedLength)
983+
.startsWith(path.subSequence(0, 100))
984+
.endsWith(truncated);
985+
}
986+
917987
@Test
918988
void nullSafeConciseToStringForURI() {
919989
String uri = "https://www.example.com/?foo=1&bar=2&baz=3";
@@ -938,11 +1008,56 @@ void nullSafeConciseToStringForURL() throws Exception {
9381008
.endsWith(truncated);
9391009
}
9401010

1011+
@Test
1012+
void nullSafeConciseToStringForInetAddress() {
1013+
InetAddress localhost = getLocalhost();
1014+
assertThat(ObjectUtils.nullSafeConciseToString(localhost)).isEqualTo(localhost.toString());
1015+
}
1016+
1017+
private InetAddress getLocalhost() {
1018+
try {
1019+
return InetAddress.getLocalHost();
1020+
}
1021+
catch (UnknownHostException ex) {
1022+
return InetAddress.getLoopbackAddress();
1023+
}
1024+
}
1025+
1026+
@Test
1027+
void nullSafeConciseToStringForCharset() {
1028+
Charset charset = StandardCharsets.UTF_8;
1029+
assertThat(ObjectUtils.nullSafeConciseToString(charset)).isEqualTo(charset.name());
1030+
}
1031+
1032+
@Test
1033+
void nullSafeConciseToStringForCurrency() {
1034+
Currency currency = Currency.getInstance(Locale.US);
1035+
assertThat(ObjectUtils.nullSafeConciseToString(currency)).isEqualTo(currency.toString());
1036+
}
1037+
9411038
@Test
9421039
void nullSafeConciseToStringForLocale() {
9431040
assertThat(ObjectUtils.nullSafeConciseToString(Locale.GERMANY)).isEqualTo("de_DE");
9441041
}
9451042

1043+
@Test
1044+
void nullSafeConciseToStringForRegExPattern() {
1045+
Pattern pattern = Pattern.compile("^(foo|bar)$");
1046+
assertThat(ObjectUtils.nullSafeConciseToString(pattern)).isEqualTo(pattern.toString());
1047+
}
1048+
1049+
@Test
1050+
void nullSafeConciseToStringForTimeZone() {
1051+
TimeZone timeZone = TimeZone.getDefault();
1052+
assertThat(ObjectUtils.nullSafeConciseToString(timeZone)).isEqualTo(timeZone.getID());
1053+
}
1054+
1055+
@Test
1056+
void nullSafeConciseToStringForZoneId() {
1057+
ZoneId zoneId = ZoneId.systemDefault();
1058+
assertThat(ObjectUtils.nullSafeConciseToString(zoneId)).isEqualTo(zoneId.getId());
1059+
}
1060+
9461061
@Test
9471062
void nullSafeConciseToStringForArraysAndCollections() {
9481063
List<String> list = Arrays.asList("a", "b", "c");

0 commit comments

Comments
 (0)