Skip to content

Commit

Permalink
Extend supported types in ObjectUtils.nullSafeConciseToString()
Browse files Browse the repository at this point in the history
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-30805
  • Loading branch information
sbrannen committed Jul 4, 2023
1 parent 08bce69 commit 3ef1b7d
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 7 deletions.
Expand Up @@ -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;

Expand Down Expand Up @@ -900,19 +908,27 @@ public static String nullSafeToString(@Nullable short[] array) {
* <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
Expand All @@ -923,9 +939,22 @@ public static String nullSafeConciseToString(@Nullable Object obj) {
if (obj == null) {
return "null";
}
if (obj instanceof Optional<?> optional) {
return (optional.isEmpty() ? "Optional.empty" :
"Optional[%s]".formatted(nullSafeConciseToString(optional.get())));
}
if (obj instanceof Class<?> clazz) {
return clazz.getName();
}
if (obj instanceof Charset charset) {
return charset.name();
}
if (obj instanceof TimeZone timeZone) {
return timeZone.getID();
}
if (obj instanceof ZoneId zoneId) {
return zoneId.getId();
}
if (obj instanceof CharSequence charSequence) {
return StringUtils.truncate(charSequence);
}
Expand All @@ -941,7 +970,10 @@ public static String nullSafeConciseToString(@Nullable Object obj) {

/**
* 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 &&
Expand All @@ -951,10 +983,18 @@ private static boolean isSimpleValueType(Class<?> type) {
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));
}

Expand Down
Expand Up @@ -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.Path;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
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.Map;
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;
Expand Down Expand Up @@ -826,6 +837,41 @@ void nullSafeConciseToStringForNull() {
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 = "X".repeat(100);
String repeat101 = "X".repeat(101);

Optional<String> optionalString = Optional.of(repeat100);
expected = "Optional[%s]".formatted(repeat100);
assertThat(ObjectUtils.nullSafeConciseToString(optionalString)).isEqualTo(expected);

optionalString = Optional.of(repeat101);
expected = "Optional[%s]".formatted(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 = "Optional[%s]".formatted(ObjectUtils.nullSafeConciseToString(customType));
assertThat(ObjectUtils.nullSafeConciseToString(optional)).isEqualTo(expected);
}

@Test
void nullSafeConciseToStringForClass() {
assertThat(ObjectUtils.nullSafeConciseToString(String.class)).isEqualTo("java.lang.String");
Expand Down Expand Up @@ -889,6 +935,30 @@ void nullSafeConciseToStringForUUID() {
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/" + "xyz".repeat(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(Path.of(path))).isEqualTo(path);

path = "/tmp/" + "xyz".repeat(32);
assertThat(ObjectUtils.nullSafeConciseToString(Path.of(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";
Expand All @@ -913,11 +983,56 @@ void nullSafeConciseToStringForURL() throws Exception {
.endsWith(truncated);
}

@Test
void nullSafeConciseToStringForInetAddress() {
InetAddress localhost = getLocalhost();
assertThat(ObjectUtils.nullSafeConciseToString(localhost)).isEqualTo(localhost.toString());
}

private static 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 = List.of("a", "b", "c");
Expand Down

0 comments on commit 3ef1b7d

Please sign in to comment.