Browse Source

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
pull/30971/head
Sam Brannen 3 years ago
parent
commit
c057da23ec
  1. 55
      spring-core/src/main/java/org/springframework/util/ObjectUtils.java
  2. 115
      spring-core/src/test/java/org/springframework/util/ObjectUtilsTests.java

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

@ -16,18 +16,26 @@ @@ -16,18 +16,26 @@
package org.springframework.util;
import java.io.File;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Currency;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Pattern;
import org.springframework.lang.Nullable;
@ -923,19 +931,27 @@ public abstract class ObjectUtils { @@ -923,19 +931,27 @@ public abstract class ObjectUtils {
* <p>Returns:
* <ul>
* <li>{@code "null"} if {@code obj} is {@code null}</li>
* <li>{@code"Optional.empty"} if {@code obj} is an empty {@link Optional}</li>
* <li>{@code"Optional[<concise-string>]"} if {@code obj} is a non-empty {@code Optional},
* where {@code <concise-string>} is the result of invoking {@link #nullSafeConciseToString}
* on the object contained in the {@code Optional}</li>
* <li>{@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}</li>
* <li>{@linkplain Charset#name() Charset name} if {@code obj} is a {@link Charset}</li>
* <li>{@linkplain TimeZone#getID() TimeZone ID} if {@code obj} is a {@link TimeZone}</li>
* <li>{@linkplain ZoneId#getId() Zone ID} if {@code obj} is a {@link ZoneId}</li>
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
* if {@code obj} is a {@link String} or {@link CharSequence}</li>
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
* if {@code obj} is a <em>simple value type</em> whose {@code toString()} method
* returns a non-null value.</li>
* returns a non-null value</li>
* <li>Otherwise, a string representation of the object's type name concatenated
* with {@code @} and a hex string form of the object's identity hash code</li>
* with {@code "@"} and a hex string form of the object's identity hash code</li>
* </ul>
* <p>In the context of this method, a <em>simple value type</em> is any of the following:
* a primitive wrapper (excluding {@code Void}), an {@code Enum}, a {@code Number},
* a {@code Date}, a {@code Temporal}, a {@code UUID}, a {@code URI}, a {@code URL},
* or a {@code Locale}.
* primitive wrapper (excluding {@link Void}), {@link Enum}, {@link Number},
* {@link Date}, {@link Temporal}, {@link File}, {@link Path}, {@link URI},
* {@link URL}, {@link InetAddress}, {@link Currency}, {@link Locale},
* {@link UUID}, {@link Pattern}.
* @param obj the object to build a string representation for
* @return a concise string representation of the supplied object
* @since 5.3.27
@ -946,9 +962,23 @@ public abstract class ObjectUtils { @@ -946,9 +962,23 @@ public abstract class ObjectUtils {
if (obj == null) {
return "null";
}
if (obj instanceof Optional<?>) {
Optional<?> optional = (Optional<?>) obj;
return (!optional.isPresent() ? "Optional.empty" :
String.format("Optional[%s]", nullSafeConciseToString(optional.get())));
}
if (obj instanceof Class<?>) {
return ((Class<?>) obj).getName();
}
if (obj instanceof Charset) {
return ((Charset) obj).name();
}
if (obj instanceof TimeZone) {
return ((TimeZone) obj).getID();
}
if (obj instanceof ZoneId) {
return ((ZoneId) obj).getId();
}
if (obj instanceof CharSequence) {
return StringUtils.truncate((CharSequence) obj);
}
@ -964,7 +994,10 @@ public abstract class ObjectUtils { @@ -964,7 +994,10 @@ public abstract class ObjectUtils {
/**
* Derived from {@link org.springframework.beans.BeanUtils#isSimpleValueType}.
* As of 5.3.28, considering {@code UUID} in addition to the bean-level check.
* <p>As of 5.3.28, considering {@link UUID} in addition to the bean-level check.
* <p>As of 5.3.29, additionally considering {@link File}, {@link Path},
* {@link InetAddress}, {@link Charset}, {@link Currency}, {@link TimeZone},
* {@link ZoneId}, {@link Pattern}.
*/
private static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
@ -974,10 +1007,18 @@ public abstract class ObjectUtils { @@ -974,10 +1007,18 @@ public abstract class ObjectUtils {
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
UUID.class == type ||
ZoneId.class.isAssignableFrom(type) ||
TimeZone.class.isAssignableFrom(type) ||
File.class.isAssignableFrom(type) ||
Path.class.isAssignableFrom(type) ||
Charset.class.isAssignableFrom(type) ||
Currency.class.isAssignableFrom(type) ||
InetAddress.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
UUID.class == type ||
Locale.class == type ||
Pattern.class == type ||
Class.class == type));
}

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

@ -16,23 +16,34 @@ @@ -16,23 +16,34 @@
package org.springframework.util;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@ -851,6 +862,41 @@ class ObjectUtilsTests { @@ -851,6 +862,41 @@ class ObjectUtilsTests {
assertThat(ObjectUtils.nullSafeConciseToString(null)).isEqualTo("null");
}
@Test
void nullSafeConciseToStringForEmptyOptional() {
Optional<String> optional = Optional.empty();
assertThat(ObjectUtils.nullSafeConciseToString(optional)).isEqualTo("Optional.empty");
}
@Test
void nullSafeConciseToStringForNonEmptyOptionals() {
Optional<Tropes> optionalEnum = Optional.of(Tropes.BAR);
String expected = "Optional[BAR]";
assertThat(ObjectUtils.nullSafeConciseToString(optionalEnum)).isEqualTo(expected);
String repeat100 = repeat("X", 100);
String repeat101 = repeat("X", 101);
Optional<String> optionalString = Optional.of(repeat100);
expected = String.format("Optional[%s]", repeat100);
assertThat(ObjectUtils.nullSafeConciseToString(optionalString)).isEqualTo(expected);
optionalString = Optional.of(repeat101);
expected = String.format("Optional[%s]", repeat100 + truncated);
assertThat(ObjectUtils.nullSafeConciseToString(optionalString)).isEqualTo(expected);
}
@Test
void nullSafeConciseToStringForNonEmptyOptionalCustomType() {
class CustomType {
}
CustomType customType = new CustomType();
Optional<CustomType> optional = Optional.of(customType);
String expected = String.format("Optional[%s]", ObjectUtils.nullSafeConciseToString(customType));
assertThat(ObjectUtils.nullSafeConciseToString(optional)).isEqualTo(expected);
}
@Test
void nullSafeConciseToStringForClass() {
assertThat(ObjectUtils.nullSafeConciseToString(String.class)).isEqualTo("java.lang.String");
@ -914,6 +960,30 @@ class ObjectUtilsTests { @@ -914,6 +960,30 @@ class ObjectUtilsTests {
assertThat(ObjectUtils.nullSafeConciseToString(id)).isEqualTo(id.toString());
}
@Test
void nullSafeConciseToStringForFile() {
String path = "/tmp/file.txt";
assertThat(ObjectUtils.nullSafeConciseToString(new File(path))).isEqualTo(path);
path = "/tmp/" + repeat("xyz", 32);
assertThat(ObjectUtils.nullSafeConciseToString(new File(path)))
.hasSize(truncatedLength)
.startsWith(path.subSequence(0, 100))
.endsWith(truncated);
}
@Test
void nullSafeConciseToStringForPath() {
String path = "/tmp/file.txt";
assertThat(ObjectUtils.nullSafeConciseToString(Paths.get(path))).isEqualTo(path);
path = "/tmp/" + repeat("xyz", 32);
assertThat(ObjectUtils.nullSafeConciseToString(Paths.get(path)))
.hasSize(truncatedLength)
.startsWith(path.subSequence(0, 100))
.endsWith(truncated);
}
@Test
void nullSafeConciseToStringForURI() {
String uri = "https://www.example.com/?foo=1&bar=2&baz=3";
@ -938,11 +1008,56 @@ class ObjectUtilsTests { @@ -938,11 +1008,56 @@ class ObjectUtilsTests {
.endsWith(truncated);
}
@Test
void nullSafeConciseToStringForInetAddress() {
InetAddress localhost = getLocalhost();
assertThat(ObjectUtils.nullSafeConciseToString(localhost)).isEqualTo(localhost.toString());
}
private InetAddress getLocalhost() {
try {
return InetAddress.getLocalHost();
}
catch (UnknownHostException ex) {
return InetAddress.getLoopbackAddress();
}
}
@Test
void nullSafeConciseToStringForCharset() {
Charset charset = StandardCharsets.UTF_8;
assertThat(ObjectUtils.nullSafeConciseToString(charset)).isEqualTo(charset.name());
}
@Test
void nullSafeConciseToStringForCurrency() {
Currency currency = Currency.getInstance(Locale.US);
assertThat(ObjectUtils.nullSafeConciseToString(currency)).isEqualTo(currency.toString());
}
@Test
void nullSafeConciseToStringForLocale() {
assertThat(ObjectUtils.nullSafeConciseToString(Locale.GERMANY)).isEqualTo("de_DE");
}
@Test
void nullSafeConciseToStringForRegExPattern() {
Pattern pattern = Pattern.compile("^(foo|bar)$");
assertThat(ObjectUtils.nullSafeConciseToString(pattern)).isEqualTo(pattern.toString());
}
@Test
void nullSafeConciseToStringForTimeZone() {
TimeZone timeZone = TimeZone.getDefault();
assertThat(ObjectUtils.nullSafeConciseToString(timeZone)).isEqualTo(timeZone.getID());
}
@Test
void nullSafeConciseToStringForZoneId() {
ZoneId zoneId = ZoneId.systemDefault();
assertThat(ObjectUtils.nullSafeConciseToString(zoneId)).isEqualTo(zoneId.getId());
}
@Test
void nullSafeConciseToStringForArraysAndCollections() {
List<String> list = Arrays.asList("a", "b", "c");

Loading…
Cancel
Save