Repository: assertj/assertj

Problem statement:
Unquoted string produced by BasicErrorMessageFactory is quoted by StandardRepresentation in 3.27.x
**Describe the bug**

https://github.com/assertj/assertj/pull/3617 has introduced a regression in `StandardRepresentation` when handling an `UnquotedString`. `UnquotedString` implements `CharSequence` so it's now handled as such. This results in it being wrapped in quotes.

* assertj core version: 3.27.x
* java version: Any
* test framework version: N/A
* os (if relevant): N/A

**Test case reproducing the bug**

```java
@Test
void hasNotFailedWhenFailedShouldFail() {
	assertThat(new TestErrorMessageFactory().create()).isEqualTo("alpha \"bravo\"");
}

static class TestErrorMessageFactory extends BasicErrorMessageFactory {

	public TestErrorMessageFactory() {
		super("%s %s", unquotedString("alpha"), "bravo");
	}

}
```

A possible workaround is to implement a custom `QuotedString` class that doesn't implement `CharSequence` so that `StandardRepresentation` then falls back to calling `toString()`:

```java
static class TestErrorMessageFactory extends BasicErrorMessageFactory {

public TestErrorMessageFactory() {
	super("%s %s", new UnquotedString("alpha"), "bravo");
}

private static class UnquotedString {

	private final String string;

	private UnquotedString(String string) {
		this.string = string;
	}

	@Override
	public String toString() {
		return this.string;
	}

}
```

Is this a reasonable approach? I'm wondering if the loss of the various `CharSequence` methods or the lack of hashCode and equals may cause problems.




Hints:
Good catch thanks @wilkinsona 

Patch:
diff --git a/assertj-core/src/main/java/org/assertj/core/error/BasicErrorMessageFactory.java b/assertj-core/src/main/java/org/assertj/core/error/BasicErrorMessageFactory.java
--- a/assertj-core/src/main/java/org/assertj/core/error/BasicErrorMessageFactory.java
+++ b/assertj-core/src/main/java/org/assertj/core/error/BasicErrorMessageFactory.java
@@ -23,6 +23,7 @@
 
 import java.util.Arrays;
 import java.util.Objects;
+
 import org.assertj.core.description.Description;
 import org.assertj.core.presentation.Representation;
 import org.assertj.core.util.VisibleForTesting;
diff --git a/assertj-core/src/main/java/org/assertj/core/presentation/StandardRepresentation.java b/assertj-core/src/main/java/org/assertj/core/presentation/StandardRepresentation.java
--- a/assertj-core/src/main/java/org/assertj/core/presentation/StandardRepresentation.java
+++ b/assertj-core/src/main/java/org/assertj/core/presentation/StandardRepresentation.java
@@ -12,16 +12,18 @@
  */
 package org.assertj.core.presentation;
 
-import org.assertj.core.configuration.Configuration;
-import org.assertj.core.configuration.ConfigurationProvider;
-import org.assertj.core.data.MapEntry;
-import org.assertj.core.groups.Tuple;
-import org.assertj.core.internal.ComparatorBasedComparisonStrategy;
-import org.assertj.core.util.Closeables;
-import org.assertj.core.util.VisibleForTesting;
-import org.assertj.core.util.diff.ChangeDelta;
-import org.assertj.core.util.diff.DeleteDelta;
-import org.assertj.core.util.diff.InsertDelta;
+import static java.lang.Integer.toHexString;
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.util.Arrays.isArray;
+import static org.assertj.core.util.Arrays.isArrayTypePrimitive;
+import static org.assertj.core.util.Arrays.isObjectArray;
+import static org.assertj.core.util.Arrays.notAnArrayOfPrimitives;
+import static org.assertj.core.util.DateUtil.formatAsDatetime;
+import static org.assertj.core.util.DateUtil.formatAsDatetimeWithMs;
+import static org.assertj.core.util.Preconditions.checkArgument;
+import static org.assertj.core.util.Strings.concat;
+import static org.assertj.core.util.Strings.quote;
+import static org.assertj.core.util.Throwables.getStackTrace;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -61,18 +63,16 @@
 import java.util.concurrent.atomic.LongAdder;
 import java.util.function.Function;
 
-import static java.lang.Integer.toHexString;
-import static java.util.stream.Collectors.toList;
-import static org.assertj.core.util.Arrays.isArray;
-import static org.assertj.core.util.Arrays.isArrayTypePrimitive;
-import static org.assertj.core.util.Arrays.isObjectArray;
-import static org.assertj.core.util.Arrays.notAnArrayOfPrimitives;
-import static org.assertj.core.util.DateUtil.formatAsDatetime;
-import static org.assertj.core.util.DateUtil.formatAsDatetimeWithMs;
-import static org.assertj.core.util.Preconditions.checkArgument;
-import static org.assertj.core.util.Strings.concat;
-import static org.assertj.core.util.Strings.quote;
-import static org.assertj.core.util.Throwables.getStackTrace;
+import org.assertj.core.configuration.Configuration;
+import org.assertj.core.configuration.ConfigurationProvider;
+import org.assertj.core.data.MapEntry;
+import org.assertj.core.groups.Tuple;
+import org.assertj.core.internal.ComparatorBasedComparisonStrategy;
+import org.assertj.core.util.Closeables;
+import org.assertj.core.util.VisibleForTesting;
+import org.assertj.core.util.diff.ChangeDelta;
+import org.assertj.core.util.diff.DeleteDelta;
+import org.assertj.core.util.diff.InsertDelta;
 
 /**
  * Standard java object representation.
@@ -234,6 +234,7 @@ public String toStringOf(Object object) {
     if (object instanceof AtomicReferenceFieldUpdater) return AtomicReferenceFieldUpdater.class.getSimpleName();
     if (object instanceof File) return toStringOf((File) object);
     if (object instanceof Path) return fallbackToStringOf(object);
+    if (isUnquotedString(object)) return toUnquotedStringOf(object);
     if (object instanceof String) return toStringOf((String) object);
     if (object instanceof CharSequence) return toStringOf((CharSequence) object);
     if (object instanceof Character) return toStringOf((Character) object);
@@ -262,6 +263,11 @@ public String toStringOf(Object object) {
     return fallbackToStringOf(object);
   }
 
+  private static boolean isUnquotedString(Object object) {
+    String className = object.getClass().getName();
+    return className.contains("org.assertj.core") && className.contains("UnquotedString");
+  }
+
   private static boolean isInstanceOfNotOverridingToString(Object object, Class<?> type) {
     return type.isInstance(object) && !hasOverriddenToStringInSubclassOf(object.getClass(), type);
   }
@@ -404,6 +410,10 @@ protected String toStringOf(String s) {
     return concatWithDoubleQuotes(s);
   }
 
+  protected String toUnquotedStringOf(Object s) {
+    return s.toString();
+  }
+
   protected String toStringOf(CharSequence s) {
     return concatWithDoubleQuotes(s);
   }


Test patch:
diff --git a/assertj-core/src/test/java/org/assertj/core/error/BasicErrorMessageFactory_unquotedString_Test.java b/assertj-core/src/test/java/org/assertj/core/error/BasicErrorMessageFactory_unquotedString_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/error/BasicErrorMessageFactory_unquotedString_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/error/BasicErrorMessageFactory_unquotedString_Test.java
@@ -14,6 +14,7 @@
 
 import static org.assertj.core.api.BDDAssertions.then;
 import static org.assertj.core.error.BasicErrorMessageFactory.unquotedString;
+import static org.assertj.core.presentation.StandardRepresentation.STANDARD_REPRESENTATION;
 
 import org.junit.jupiter.api.Test;
 
@@ -26,4 +27,15 @@ class BasicErrorMessageFactory_unquotedString_Test {
   void should_implement_toString() {
     then(unquotedString("some value")).hasToString("some value");
   }
+
+  @Test
+  void StandardRepresentation_should_not_quote_unquoted_String() {
+    // GIVEN
+    CharSequence unquotedString = unquotedString("Hello");
+    // WHEN
+    String unquotedStringStandardRepresentation = STANDARD_REPRESENTATION.toStringOf(unquotedString);
+    // THEN
+    then(unquotedStringStandardRepresentation).isEqualTo("Hello");
+  }
+
 }
diff --git a/assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/presentation/StandardRepresentation_toStringOf_Test.java b/assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/presentation/StandardRepresentation_toStringOf_Test.java
--- a/assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/presentation/StandardRepresentation_toStringOf_Test.java
+++ b/assertj-tests/assertj-integration-tests/assertj-core-tests/src/test/java/org/assertj/tests/core/presentation/StandardRepresentation_toStringOf_Test.java
@@ -109,9 +109,9 @@ void should_quote_CharSequence_implementation() {
     // GIVEN
     CharSequence charSequence = new StringBuilder("Hello");
     // WHEN
-    String emptyStringStandardRepresentation = STANDARD_REPRESENTATION.toStringOf(charSequence);
+    String charSequenceStandardRepresentation = STANDARD_REPRESENTATION.toStringOf(charSequence);
     // THEN
-    then(emptyStringStandardRepresentation).isEqualTo("\"Hello\"");
+    then(charSequenceStandardRepresentation).isEqualTo("\"Hello\"");
   }
 
   @Test