This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new bd76060e0e Unit tests
bd76060e0e is described below
commit bd76060e0ef63163121c3499ad648dc9eda8ee32
Author: James Bognar <[email protected]>
AuthorDate: Tue Dec 2 05:44:21 2025 -0800
Unit tests
---
.../java/org/apache/juneau/bean/atom/Category.java | 2 +-
.../java/org/apache/juneau/bean/atom/Common.java | 2 +-
.../java/org/apache/juneau/bean/atom/Content.java | 2 +-
.../org/apache/juneau/bean/atom/Generator.java | 2 +-
.../java/org/apache/juneau/bean/atom/Icon.java | 2 +-
.../java/org/apache/juneau/bean/atom/Logo.java | 2 +-
.../java/org/apache/juneau/bean/atom/Person.java | 2 +-
.../org/apache/juneau/bean/html5/HtmlElement.java | 6 +-
.../apache/juneau/bean/jsonschema/JsonSchema.java | 8 +-
.../juneau/bean/jsonschema/JsonSchemaMap.java | 2 +-
.../org/apache/juneau/bean/openapi3/Contact.java | 2 +-
.../bean/openapi3/ExternalDocumentation.java | 2 +-
.../org/apache/juneau/bean/openapi3/License.java | 2 +-
.../juneau/bean/openapi3/OpenApiBuilder.java | 6 +-
.../org/apache/juneau/bean/openapi3/Server.java | 2 +-
.../org/apache/juneau/bean/swagger/Contact.java | 2 +-
.../juneau/bean/swagger/ExternalDocumentation.java | 2 +-
.../org/apache/juneau/bean/swagger/License.java | 2 +-
.../apache/juneau/bean/swagger/SwaggerBuilder.java | 6 +-
.../juneau/commons/reflect/ParameterInfo.java | 16 +-
.../apache/juneau/commons/utils/StringUtils.java | 330 ++++------
.../main/java/org/apache/juneau/BeanSession.java | 2 +-
.../org/apache/juneau/collections/JsonList.java | 2 +-
.../main/java/org/apache/juneau/cp/Messages.java | 2 +-
.../org/apache/juneau/httppart/HttpPartSchema.java | 2 +-
.../org/apache/juneau/jsonschema/SchemaUtils.java | 4 +-
.../juneau/objecttools/StringMatcherFactory.java | 4 +-
.../org/apache/juneau/svl/VarResolverSession.java | 8 +-
.../org/apache/juneau/rest/client/RestClient.java | 6 +-
.../org/apache/juneau/rest/client/RestRequest.java | 2 +-
.../rest/swagger/BasicSwaggerProviderSession.java | 8 +-
.../org/apache/juneau/rest/util/RestUtils.java | 2 +-
.../juneau/commons/utils/StringUtils_Test.java | 712 +++++++++++++++------
33 files changed, 701 insertions(+), 455 deletions(-)
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Category.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Category.java
index 6ca9ceca9f..aedf3162c4 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Category.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Category.java
@@ -173,7 +173,7 @@ public class Category extends Common {
* @return This object
*/
public Category setScheme(Object value) {
- this.scheme = toURI(value);
+ this.scheme = toUri(value);
return this;
}
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Common.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Common.java
index b684706104..bd582e9ebf 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Common.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Common.java
@@ -132,7 +132,7 @@ public abstract class Common {
* @return This object.
*/
public Common setBase(Object value) {
- this.base = toURI(value);
+ this.base = toUri(value);
return this;
}
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Content.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Content.java
index 54728eb0b9..f7c464dcbe 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Content.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Content.java
@@ -182,7 +182,7 @@ public class Content extends Text {
* @return This object.
*/
public Content setSrc(Object value) {
- this.src = toURI(value);
+ this.src = toUri(value);
return this;
}
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Generator.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Generator.java
index d19a36d4cf..1ca8b62516 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Generator.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Generator.java
@@ -165,7 +165,7 @@ public class Generator extends Common {
* @return This object
*/
public Generator setUri(Object value) {
- this.uri = toURI(value);
+ this.uri = toUri(value);
return this;
}
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Icon.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Icon.java
index 4ecfcbe2b4..2e59b980c9 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Icon.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Icon.java
@@ -130,7 +130,7 @@ public class Icon extends Common {
* @return This object
*/
public Icon setUri(Object value) {
- this.uri = toURI(value);
+ this.uri = toUri(value);
return this;
}
}
\ No newline at end of file
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Logo.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Logo.java
index 3ab0bd42be..80b941733b 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Logo.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Logo.java
@@ -133,7 +133,7 @@ public class Logo extends Common {
* @return This object
*/
public Logo setUri(Object value) {
- this.uri = toURI(value);
+ this.uri = toUri(value);
return this;
}
}
\ No newline at end of file
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Person.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Person.java
index 334e5c7af5..27059440ac 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Person.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Person.java
@@ -195,7 +195,7 @@ public class Person extends Common {
* @return This object.
*/
public Person setUri(Object value) {
- this.uri = toURI(value);
+ this.uri = toUri(value);
return this;
}
}
\ No newline at end of file
diff --git
a/juneau-bean/juneau-bean-html5/src/main/java/org/apache/juneau/bean/html5/HtmlElement.java
b/juneau-bean/juneau-bean-html5/src/main/java/org/apache/juneau/bean/html5/HtmlElement.java
index 305d55fd83..ec4ef07189 100644
---
a/juneau-bean/juneau-bean-html5/src/main/java/org/apache/juneau/bean/html5/HtmlElement.java
+++
b/juneau-bean/juneau-bean-html5/src/main/java/org/apache/juneau/bean/html5/HtmlElement.java
@@ -92,7 +92,7 @@ public abstract class HtmlElement {
attrs.remove(key);
else {
if ("url".equals(key) || "href".equals(key) ||
key.endsWith("action"))
- val = toURI(val);
+ val = toUri(val);
attrs.put(key, val);
}
return this;
@@ -119,7 +119,7 @@ public abstract class HtmlElement {
public HtmlElement attrUri(String key, Object val) {
if (attrs == null)
attrs = map();
- attrs.put(key, toURI(val));
+ attrs.put(key, toUri(val));
return this;
}
@@ -1048,7 +1048,7 @@ public abstract class HtmlElement {
value.entrySet().forEach(x -> {
var key = x.getKey();
if ("url".equals(key) || "href".equals(key) ||
key.endsWith("action"))
- x.setValue(toURI(x.getValue()));
+ x.setValue(toUri(x.getValue()));
});
}
this.attrs = value;
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
index af8e41d061..f242b36efd 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchema.java
@@ -1569,7 +1569,7 @@ public class JsonSchema {
*/
@Deprecated
public JsonSchema setId(Object id) {
- this.id = toURI(id);
+ this.id = toUri(id);
return this;
}
@@ -1591,7 +1591,7 @@ public class JsonSchema {
*/
@Beanp("$id")
public JsonSchema setIdUri(Object idUri) {
- this.idUri = toURI(idUri);
+ this.idUri = toUri(idUri);
return this;
}
@@ -1866,7 +1866,7 @@ public class JsonSchema {
*/
@Beanp("$ref")
public JsonSchema setRef(Object ref) {
- this.ref = toURI(ref);
+ this.ref = toUri(ref);
return this;
}
@@ -1909,7 +1909,7 @@ public class JsonSchema {
*/
@Beanp("$schema")
public JsonSchema setSchemaVersionUri(Object schemaVersion) {
- this.schemaVersion = toURI(schemaVersion);
+ this.schemaVersion = toUri(schemaVersion);
return this;
}
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaMap.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaMap.java
index af62d83980..0284a8be26 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaMap.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaMap.java
@@ -84,7 +84,7 @@ public abstract class JsonSchemaMap extends
ConcurrentHashMap<URI,JsonSchema> {
*/
@Override /* Overridden from Map */
public JsonSchema get(Object uri) {
- var u = toURI(uri);
+ var u = toUri(uri);
var s = super.get(u);
if (nn(s))
return s;
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Contact.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Contact.java
index 8d87e6b737..1d4d6c8bcc 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Contact.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Contact.java
@@ -162,7 +162,7 @@ public class Contact extends OpenApiElement {
return switch (property) {
case "email" -> setEmail(s(value));
case "name" -> setName(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/ExternalDocumentation.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/ExternalDocumentation.java
index b001ea9742..ce03fb1c1a 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/ExternalDocumentation.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/ExternalDocumentation.java
@@ -135,7 +135,7 @@ public class ExternalDocumentation extends OpenApiElement {
assertArgNotNull("property", property);
return switch (property) {
case "description" -> setDescription(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/License.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/License.java
index 8a9be7b9e6..e7ef27d42c 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/License.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/License.java
@@ -145,7 +145,7 @@ public class License extends OpenApiElement {
assertArgNotNull("property", property);
return switch (property) {
case "name" -> setName(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
index a7d1bc319d..db708e692a 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
@@ -82,7 +82,7 @@ public class OpenApiBuilder {
* @return The new element.
*/
public static final Contact contact(String name, Object url, String
email) {
- return
contact().setName(name).setUrl(toURI(url)).setEmail(email);
+ return
contact().setName(name).setUrl(toUri(url)).setEmail(email);
}
/**
@@ -153,7 +153,7 @@ public class OpenApiBuilder {
* @return The new element.
*/
public static final ExternalDocumentation externalDocumentation(Object
url) {
- return externalDocumentation().setUrl(toURI(url));
+ return externalDocumentation().setUrl(toUri(url));
}
/**
@@ -169,7 +169,7 @@ public class OpenApiBuilder {
* @return The new element.
*/
public static final ExternalDocumentation externalDocumentation(Object
url, String description) {
- return
externalDocumentation().setUrl(toURI(url)).setDescription(description);
+ return
externalDocumentation().setUrl(toUri(url)).setDescription(description);
}
/**
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Server.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Server.java
index d5e9a46a51..c6cc3dad36 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Server.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Server.java
@@ -172,7 +172,7 @@ public class Server extends OpenApiElement {
assertArgNotNull("property", property);
return switch (property) {
case "description" -> setDescription(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
case "variables" -> setVariables(toMapBuilder(value,
String.class, ServerVariable.class).sparse().build());
default -> {
super.set(property, value);
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Contact.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Contact.java
index 372f0d73d0..d102f0e2ce 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Contact.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/Contact.java
@@ -161,7 +161,7 @@ public class Contact extends SwaggerElement {
return switch (property) {
case "email" -> setEmail(s(value));
case "name" -> setName(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ExternalDocumentation.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ExternalDocumentation.java
index 7c2dab5139..e4ca1a75a2 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ExternalDocumentation.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/ExternalDocumentation.java
@@ -146,7 +146,7 @@ public class ExternalDocumentation extends SwaggerElement {
assertArgNotNull("property", property);
return switch (property) {
case "description" -> setDescription(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/License.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/License.java
index 61707b457f..a8d3fbb324 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/License.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/License.java
@@ -145,7 +145,7 @@ public class License extends SwaggerElement {
assertArgNotNull("property", property);
return switch (property) {
case "name" -> setName(s(value));
- case "url" -> setUrl(toURI(value));
+ case "url" -> setUrl(toUri(value));
default -> {
super.set(property, value);
yield this;
diff --git
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SwaggerBuilder.java
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SwaggerBuilder.java
index efe1585ae2..19cd2078ed 100644
---
a/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SwaggerBuilder.java
+++
b/juneau-bean/juneau-bean-swagger-v2/src/main/java/org/apache/juneau/bean/swagger/SwaggerBuilder.java
@@ -64,7 +64,7 @@ public class SwaggerBuilder {
* @return The new element.
*/
public static final Contact contact(String name, Object url, String
email) {
- return
contact().setName(name).setUrl(toURI(url)).setEmail(email);
+ return
contact().setName(name).setUrl(toUri(url)).setEmail(email);
}
/**
@@ -88,7 +88,7 @@ public class SwaggerBuilder {
* @return The new element.
*/
public static final ExternalDocumentation externalDocumentation(Object
url) {
- return externalDocumentation().setUrl(toURI(url));
+ return externalDocumentation().setUrl(toUri(url));
}
/**
@@ -104,7 +104,7 @@ public class SwaggerBuilder {
* @return The new element.
*/
public static final ExternalDocumentation externalDocumentation(Object
url, String description) {
- return
externalDocumentation().setUrl(toURI(url)).setDescription(description);
+ return
externalDocumentation().setUrl(toUri(url)).setDescription(description);
}
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
index fbb68482ba..57578ef985 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ParameterInfo.java
@@ -126,12 +126,18 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
public static ParameterInfo of(Parameter inner) {
assertArgNotNull("inner", inner);
var exec = inner.getDeclaringExecutable();
- ExecutableInfo execInfo = exec instanceof Constructor ?
ConstructorInfo.of((Constructor<?>)exec) : MethodInfo.of((Method)exec);
- var params = execInfo.getParameters();
- for (var param : params) {
- if (param.inner() == inner) {
+ ExecutableInfo execInfo;
+ if (exec instanceof Constructor<?> c)
+ execInfo = ConstructorInfo.of(c);
+ else if (exec instanceof Method m)
+ execInfo = MethodInfo.of(m);
+ else
+ throw new IllegalArgumentException("Unsupported
executable type: " + exec.getClass());
+
+ for (var param : execInfo.getParameters()) {
+ var wrapped = param.inner();
+ if (wrapped == inner || wrapped.equals(inner))
return param;
- }
}
throw new IllegalArgumentException("Parameter not found in
declaring executable: " + inner);
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
index 51bfc71ed2..075b7defdd 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
@@ -59,6 +59,9 @@ public class StringUtils {
/** Digits 0-9 represented as an {@link AsciiSet}. */
public static final AsciiSet DECIMAL_CHARS = AsciiSet.of("0123456789");
+ /** Digits 0-9 represented as an {@link AsciiSet}. */
+ public static final AsciiSet DIGIT = AsciiSet.of("0123456789");
+
/** Zero-length string constant. */
public static final String EMPTY = "";
@@ -71,6 +74,15 @@ public class StringUtils {
/** Characters allowed in HTTP headers (including quoted strings and
comments). */
public static final AsciiSet HTTP_HEADER_CHARS =
AsciiSet.create().chars("\t -").ranges("!-[","]-}").build();
+ /** Letters a-z and A-Z represented as an {@link AsciiSet}. */
+ public static final AsciiSet LETTER = AsciiSet.create().ranges("a-z",
"A-Z").build();
+
+ /** Lowercase letters a-z represented as an {@link AsciiSet}. */
+ public static final AsciiSet LETTER_LC = AsciiSet.create().range('a',
'z').build();
+
+ /** Uppercase letters A-Z represented as an {@link AsciiSet}. */
+ public static final AsciiSet LETTER_UC = AsciiSet.create().range('A',
'Z').build();
+
/** Characters escaped when parsing key/value pairs. */
public static final AsciiSet MAP_ESCAPE_SET = AsciiSet.of(",=\\");
@@ -107,6 +119,9 @@ public class StringUtils {
/** Extended set of characters that are typically safe to leave
unencoded. */
public static final AsciiSet URL_UNENCODED_LAX_CHARS =
URL_UNENCODED_CHARS.copy().chars(":@$,").chars("{}|\\^[]`").build();
+ /** Vowel characters a, e, i, o, u (both uppercase and lowercase)
represented as an {@link AsciiSet}. */
+ public static final AsciiSet VOWEL = AsciiSet.of("aeiouAEIOU");
+
/**
* All standard whitespace characters (space, tab, newline, carriage
return, form feed, vertical tab).
*/
@@ -137,8 +152,6 @@ public class StringUtils {
static final Map<Character,AsciiSet> ESCAPE_SETS = new
ConcurrentHashMap<>();
- private static final char[] HEX_ARRAY =
"0123456789ABCDEF".toCharArray();
-
private static final List<Readifier> READIFIERS = loadReadifiers();
private static final Cache<Class<?>,Function<Object,String>>
READIFIER_CACHE =
Cache.<Class<?>,Function<Object,String>>create().weak().build();
@@ -304,8 +317,7 @@ public class StringUtils {
* @return The word with 'a' or 'an' prepended.
*/
public static String articlized(String subject) {
- var vowels = AsciiSet.of("AEIOUaeiou");
- return (vowels.contains(subject.charAt(0)) ? "an " : "a ") +
subject;
+ return (VOWEL.contains(subject.charAt(0)) ? "an " : "a ") +
subject;
}
/**
@@ -1140,7 +1152,7 @@ public class StringUtils {
return i;
i++;
}
- if (i == len && s1.length() == s2.length())
+ if (eq(s1.length(), s2.length()))
return -1;
return i;
}
@@ -1176,7 +1188,7 @@ public class StringUtils {
return i;
i++;
}
- if (i == len && s1.length() == s2.length())
+ if (eq(s1.length(), s2.length()))
return -1;
return i;
}
@@ -1227,13 +1239,11 @@ public class StringUtils {
// For simplicity, return the same code for both primary and
alternate
// A full Double Metaphone implementation would be much more
complex
var primary = metaphone(str);
- if (primary == null)
- return null;
// Generate alternate code (simplified - full implementation
would have different rules)
var alternate = primary;
- return new String[] { primary, alternate };
+ return a(primary, alternate);
}
/**
@@ -1417,8 +1427,6 @@ public class StringUtils {
return 0.0;
var length = str.length();
- if (length == 0)
- return 0.0;
// Count character frequencies
var charCounts = new int[Character.MAX_VALUE + 1];
@@ -2028,7 +2036,7 @@ public class StringUtils {
for (var i = 0; i < in.length(); i++) {
var c = in.charAt(i);
- if (c <= 127 && ! URI_CHARS.contains(c)) {
+ if (! URI_CHARS.contains(c)) {
sb = append(sb, in.substring(m, i));
if (c == ' ')
sb.append("+");
@@ -2337,14 +2345,14 @@ public class StringUtils {
for (var i = 0; i < s.length(); i++) {
var c = s.charAt(i);
if (state == S1) {
- if (c >= 'a' && c <= 'z')
+ if (isLowerCaseLetter(c))
state = S2;
else
return s;
} else if (state == S2) {
if (c == ':')
state = S3;
- else if (c < 'a' || c > 'z')
+ else if (! isLowerCaseLetter(c))
return s;
} else if (state == S3) { // NOSONAR - False positive.
if (c == '/')
@@ -2361,7 +2369,7 @@ public class StringUtils {
state = S6;
else
return s;
- } else if (state == S6) {
+ } else /* state == S6 */ {
if (c == '/') // NOSONAR - Intentional.
return s.substring(0, i);
}
@@ -2423,8 +2431,6 @@ public class StringUtils {
// Skip whitespace
while (i < len && Character.isWhitespace(s.charAt(i)))
i++;
- if (i >= len)
- break;
// Parse number (including decimal)
var numStart = i;
@@ -2455,7 +2461,7 @@ public class StringUtils {
// Parse unit (read all letters until we hit a digit or
whitespace)
var unitStart = i;
- while (i < len && Character.isLetter(s.charAt(i)))
+ while (i < len && LETTER.contains(s.charAt(i)))
i++;
var unit = s.substring(unitStart, i).trim().toLowerCase();
@@ -2838,14 +2844,14 @@ public class StringUtils {
for (var i = 0; i < s.length(); i++) {
var c = s.charAt(i);
if (state == S1) {
- if (c >= 'a' && c <= 'z')
+ if (isLowerCaseLetter(c))
state = S2;
else
return false;
} else if (state == S2) {
if (c == ':')
state = S3;
- else if (c < 'a' || c > 'z')
+ else if (! isLowerCaseLetter(c))
return false;
} else if (state == S3) { // NOSONAR - False positive.
if (c == '/')
@@ -2857,7 +2863,7 @@ public class StringUtils {
state = S5;
else
return false;
- } else if (state == S5) {
+ } else /* state == S5 */ {
return true;
}
}
@@ -2948,7 +2954,7 @@ public class StringUtils {
if (isEmpty(str))
return false;
for (var i = 0; i < str.length(); i++) {
- if (! Character.isLetter(str.charAt(i)))
+ if (! LETTER.contains(str.charAt(i)))
return false;
}
return true;
@@ -2974,7 +2980,7 @@ public class StringUtils {
if (isEmpty(str))
return false;
for (var i = 0; i < str.length(); i++) {
- if (! Character.isLetterOrDigit(str.charAt(i)))
+ if (! (LETTER.contains(str.charAt(i)) ||
DIGIT.contains(str.charAt(i))))
return false;
}
return true;
@@ -3169,7 +3175,7 @@ public class StringUtils {
if (isEmpty(str))
return false;
for (var i = 0; i < str.length(); i++) {
- if (! Character.isDigit(str.charAt(i)))
+ if (! DIGIT.contains(str.charAt(i)))
return false;
}
return true;
@@ -3283,16 +3289,17 @@ public class StringUtils {
}
/**
- * Returns <jk>true</jk> if the specified string is valid JSON.
+ * Returns <jk>true</jk> if the specified string appears to be valid
JSON.
*
* <p>
+ * This method performs a simple heuristic check and does not strictly
validate JSON syntax.
* Leading and trailing spaces are ignored.
* <br>Leading and trailing comments are not allowed.
*
* @param s The string to test.
- * @return <jk>true</jk> if the specified string is valid JSON.
+ * @return <jk>true</jk> if the specified string appears to be valid
JSON.
*/
- public static boolean isJson(String s) {
+ public static boolean isProbablyJson(String s) {
if (s == null)
return false;
var c1 = firstNonWhitespaceChar(s);
@@ -3303,13 +3310,16 @@ public class StringUtils {
}
/**
- * Returns <jk>true</jk> if the specified string appears to be an JSON
array.
+ * Returns <jk>true</jk> if the specified string appears to be a JSON
array.
+ *
+ * <p>
+ * This method performs a simple heuristic check and does not strictly
validate JSON syntax.
*
* @param o The object to test.
* @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and
trailing whitespace and comments will be ignored.
* @return <jk>true</jk> if the specified string appears to be a JSON
array.
*/
- public static boolean isJsonArray(Object o, boolean
ignoreWhitespaceAndComments) {
+ public static boolean isProbablyJsonArray(Object o, boolean
ignoreWhitespaceAndComments) {
if (o instanceof CharSequence o2) {
var s = o2.toString();
if (! ignoreWhitespaceAndComments)
@@ -3328,11 +3338,14 @@ public class StringUtils {
/**
* Returns <jk>true</jk> if the specified string appears to be a JSON
object.
*
+ * <p>
+ * This method performs a simple heuristic check and does not strictly
validate JSON syntax.
+ *
* @param o The object to test.
* @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and
trailing whitespace and comments will be ignored.
* @return <jk>true</jk> if the specified string appears to be a JSON
object.
*/
- public static boolean isJsonObject(Object o, boolean
ignoreWhitespaceAndComments) {
+ public static boolean isProbablyJsonObject(Object o, boolean
ignoreWhitespaceAndComments) {
if (o instanceof CharSequence o2) {
var s = o2.toString();
if (! ignoreWhitespaceAndComments)
@@ -3494,27 +3507,35 @@ public class StringUtils {
for (var i = 0; i < s.length(); i++) {
var c = s.charAt(i);
if (state == S1) {
- if (c >= 'a' && c <= 'z')
+ if (isLowerCaseLetter(c))
state = S2;
else
return false;
} else if (state == S2) {
- if (c >= 'a' && c <= 'z')
+ if (isLowerCaseLetter(c))
state = S3;
else
return false;
} else if (state == S3) { // NOSONAR - False positive.
if (c == ':')
state = S4;
- else if (c < 'a' || c > 'z')
+ else if (! isLowerCaseLetter(c))
return false;
- } else if (state == S4) {
+ } else /* state == S4 */ {
return c == '/';
}
}
return false;
}
+ private static boolean isLowerCaseLetter(char c) {
+ if (c < 'a')
+ return false;
+ if (c > 'z')
+ return false;
+ return true;
+ }
+
/**
* Validates if a date string matches the specified date format.
*
@@ -3585,10 +3606,6 @@ public class StringUtils {
// Split by dots (use -1 to preserve trailing empty strings)
var labels = hostname.split("\\.", -1);
- // Must have at least one label
- if (labels.length == 0)
- return false;
-
// Check each label
for (var label : labels) {
// Label cannot be empty
@@ -4460,10 +4477,8 @@ public class StringUtils {
result.append(c);
break;
case 'X':
- if (i == 0)
- result.append('S');
- else
- result.append("KS");
+ // X at start is handled in initial
section (line 4346-4348), so i is never 0 here
+ result.append("KS");
break;
case 'Z':
result.append('S');
@@ -4577,7 +4592,7 @@ public class StringUtils {
var c2 = str2.charAt(i2);
// If both are digits, compare numerically
- if (Character.isDigit(c1) && Character.isDigit(c2)) {
+ if (DIGIT.contains(c1) && DIGIT.contains(c2)) {
// Skip leading zeros
while (i1 < len1 && str1.charAt(i1) == '0')
i1++;
@@ -4587,9 +4602,9 @@ public class StringUtils {
// Find end of number sequences
var end1 = i1;
var end2 = i2;
- while (end1 < len1 &&
Character.isDigit(str1.charAt(end1)))
+ while (end1 < len1 &&
DIGIT.contains(str1.charAt(end1)))
end1++;
- while (end2 < len2 &&
Character.isDigit(str2.charAt(end2)))
+ while (end2 < len2 &&
DIGIT.contains(str2.charAt(end2)))
end2++;
// Compare lengths first (longer number is
larger)
@@ -4925,64 +4940,6 @@ public class StringUtils {
return s.substring(0, 1) + s.substring(1).replaceAll(".", "*");
// NOSONAR
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String validation methods
-
//-----------------------------------------------------------------------------------------------------------------
-
- /**
- * Provides optimization suggestions for a string based on its
characteristics.
- *
- * <p>
- * Returns <jk>null</jk> if the input string is <jk>null</jk> or if no
optimizations are suggested.
- * Returns a string containing optimization suggestions separated by
newlines.
- *
- * <h5 class='section'>Optimization Suggestions:</h5>
- * <ul>
- * <li><b>Large strings:</b> Suggests using StringBuilder for
concatenation</li>
- * <li><b>Frequently used strings:</b> Suggests interning</li>
- * <li><b>Character manipulation:</b> Suggests using char[] for
intensive operations</li>
- * </ul>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- * optimizeString(<jk>null</jk>); <jc>//
Returns: null</jc>
- * optimizeString(<js>"short"</js>); <jc>//
Returns: null (no suggestions)</jc>
- * optimizeString(<js>"very long string..."</js>); <jc>//
Returns: suggestions for large strings</jc>
- * </p>
- *
- * @param str The string to analyze. Can be <jk>null</jk>.
- * @return A string with optimization suggestions, or <jk>null</jk> if
no suggestions or input was <jk>null</jk>.
- */
- public static String optimizeString(String str) {
- if (str == null)
- return null;
-
- var suggestions = new ArrayList<String>();
- var length = str.length();
-
- // Suggest StringBuilder for large strings or frequent
concatenation scenarios
- if (length > 1000) {
- suggestions.add("Consider using StringBuilder for
concatenation operations");
- }
-
- // Suggest interning for medium-length strings that might be
repeated
- if (length > 10 && length < 100 && ! isInterned(str)) {
- suggestions.add("Consider interning if this string is
used frequently");
- }
-
- // Suggest char[] for intensive character manipulation
- if (length > 100) {
- suggestions.add("For intensive character manipulation,
consider using char[]");
- }
-
- // Suggest compression for very large strings
- if (length > 10000) {
- suggestions.add("For very large strings, consider
compression if storage is a concern");
- }
-
- return suggestions.isEmpty() ? null : String.join(NEWLINE,
suggestions);
- }
-
/**
* Converts a number to its ordinal form (1st, 2nd, 3rd, 4th, etc.).
*
@@ -5168,10 +5125,6 @@ public class StringUtils {
return Integer.decode(s.substring(0, s.length() - 1).trim()) *
m; // NOSONAR - NPE not possible here.
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String manipulation methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Parses an ISO8601 string into a calendar.
*
@@ -5685,10 +5638,6 @@ public class StringUtils {
return sb.toString();
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String joining and splitting methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Calculates a simple readability score for a string.
*
@@ -5747,10 +5696,6 @@ public class StringUtils {
return Math.max(0.0, Math.min(100.0, score));
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String cleaning and sanitization methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Converts an arbitrary object to a readable string format suitable
for debugging and testing.
*
@@ -6193,8 +6138,6 @@ public class StringUtils {
return 1.0;
var maxLen = Math.max(str1.length(), str2.length());
- if (maxLen == 0)
- return 1.0;
var distance = levenshteinDistance(str1, str2);
return 1.0 - ((double)distance / maxLen);
@@ -6411,7 +6354,7 @@ public class StringUtils {
escapeCount++;
else if (s.charAt(i) == c && escapeCount % 2 == 0) {
var s2 = s.substring(x1, i);
- var s3 = unEscapeChars(s2, escapeChars);
+ var s3 = unescapeChars(s2, escapeChars);
consumer.accept(s3.trim()); // NOSONAR - NPE
not possible.
x1 = i + 1;
}
@@ -6419,7 +6362,7 @@ public class StringUtils {
escapeCount = 0;
}
var s2 = s.substring(x1);
- var s3 = unEscapeChars(s2, escapeChars);
+ var s3 = unescapeChars(s2, escapeChars);
consumer.accept(s3.trim()); // NOSONAR - NPE not possible.
}
@@ -6452,7 +6395,7 @@ public class StringUtils {
escapeCount++;
else if (sArray[i] == c && escapeCount % 2 == 0) {
var s2 = new String(sArray, x1, i - x1);
- var s3 = unEscapeChars(s2, escapeChars);
+ var s3 = unescapeChars(s2, escapeChars);
l.add(s3.trim());
limit--;
x1 = i + 1;
@@ -6461,7 +6404,7 @@ public class StringUtils {
escapeCount = 0;
}
var s2 = new String(sArray, x1, sArray.length - x1);
- var s3 = unEscapeChars(s2, escapeChars);
+ var s3 = unescapeChars(s2, escapeChars);
l.add(s3.trim());
return l;
@@ -6589,24 +6532,24 @@ public class StringUtils {
key = s.substring(x1, i);
if (trim)
key = trim(key);
- key = unEscapeChars(key,
MAP_ESCAPE_SET);
+ key = unescapeChars(key,
MAP_ESCAPE_SET);
state = S2;
x1 = i + 1;
} else if (c == ',') {
key = s.substring(x1, i);
if (trim)
key = trim(key);
- key = unEscapeChars(key,
MAP_ESCAPE_SET);
+ key = unescapeChars(key,
MAP_ESCAPE_SET);
m.put(key, "");
state = S1;
x1 = i + 1;
}
- } else if (state == S2) {
+ } else /* state == S2 */ {
if (c == ',') { // NOSONAR -
Intentional.
var val = s.substring(x1, i);
if (trim)
val = trim(val);
- val = unEscapeChars(val,
MAP_ESCAPE_SET);
+ val = unescapeChars(val,
MAP_ESCAPE_SET);
m.put(key, val);
key = null;
x1 = i + 1;
@@ -6705,12 +6648,12 @@ public class StringUtils {
} else if (c == '}') {
depthCount--;
} else if (c == ',' && depthCount == 0) {
-
l.add(trim(unEscapeChars(s.substring(x1, i), escapeChars)));
+
l.add(trim(unescapeChars(s.substring(x1, i), escapeChars)));
x1 = i + 1;
}
}
}
- l.add(trim(unEscapeChars(s.substring(x1, s.length()),
escapeChars)));
+ l.add(trim(unescapeChars(s.substring(x1, s.length()),
escapeChars)));
return l;
}
@@ -6857,7 +6800,7 @@ public class StringUtils {
if (c == (state == S2 ? '\'' : '"')) {
var s2 = s.substring(mark,
keepQuotes ? i + 1 : i);
if (needsUnescape) // NOSONAR
- False positive check.
- s2 = unEscapeChars(s2,
QUOTE_ESCAPE_SET);
+ s2 = unescapeChars(s2,
QUOTE_ESCAPE_SET);
l.add(s2);
state = S1;
isInEscape = needsUnescape =
false;
@@ -7063,9 +7006,9 @@ public class StringUtils {
var sb = new StringBuilder(str.length());
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
- if (Character.isUpperCase(c))
+ if (LETTER_UC.contains(c))
sb.append(Character.toLowerCase(c));
- else if (Character.isLowerCase(c))
+ else if (LETTER_LC.contains(c))
sb.append(Character.toUpperCase(c));
else
sb.append(c);
@@ -7150,8 +7093,8 @@ public class StringUtils {
public static String toHex(byte b) {
var c = new char[2];
var v = b & 0xFF;
- c[0] = HEX_ARRAY[v >>> 4];
- c[1] = HEX_ARRAY[v & 0x0F];
+ c[0] = HEX[v >>> 4];
+ c[1] = HEX[v & 0x0F];
return new String(c);
}
@@ -7314,10 +7257,6 @@ public class StringUtils {
return obj == null ? null : obj.toString();
}
-
//------------------------------------------------------------------------------------------------------------------
- // Additional utility methods
-
//------------------------------------------------------------------------------------------------------------------
-
/**
* Safely converts an object to a string, returning the default string
if the object is <jk>null</jk>.
*
@@ -7351,7 +7290,7 @@ public class StringUtils {
* @param o The object to convert to a URI.
* @return A new URI, or the same object if the object was already a
URI, or
*/
- public static URI toURI(Object o) {
+ public static URI toUri(Object o) {
if (o == null || o instanceof URI)
return (URI)o;
try {
@@ -7551,7 +7490,7 @@ public class StringUtils {
* @param escaped The characters escaped.
* @return A new string if characters were removed, or the same string
if not or if the input was <jk>null</jk>.
*/
- public static String unEscapeChars(String s, AsciiSet escaped) {
+ public static String unescapeChars(String s, AsciiSet escaped) {
if (s == null || s.isEmpty())
return s;
var count = 0;
@@ -7684,9 +7623,7 @@ public class StringUtils {
}
if (needsDecode) {
- try {
- return URLDecoder.decode(s, "UTF-8");
- } catch (@SuppressWarnings("unused")
UnsupportedEncodingException e) {/* Won't happen */}
+ return safe(()->URLDecoder.decode(s, "UTF-8"));
}
return s;
}
@@ -7708,9 +7645,7 @@ public class StringUtils {
needsEncode |= (!
URL_UNENCODED_CHARS.contains(s.charAt(i)));
if (needsEncode) {
- try {
- return URLEncoder.encode(s, "UTF-8");
- } catch (@SuppressWarnings("unused")
UnsupportedEncodingException e) {/* Won't happen */}
+ return safe(()->URLEncoder.encode(s, "UTF-8"));
}
return s;
@@ -7739,11 +7674,7 @@ public class StringUtils {
else if (c <= 127)
sb.append('%').append(toHex2(c));
else
- try {
- sb.append(URLEncoder.encode(""
+ c, "UTF-8")); // Yuck.
- } catch (@SuppressWarnings("unused")
UnsupportedEncodingException e) {
- // Not possible.
- }
+ safe(()->sb.append(URLEncoder.encode(""
+ c, "UTF-8"))); // Yuck.
}
s = sb.toString();
}
@@ -7846,7 +7777,7 @@ public class StringUtils {
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
- if (Character.isLetterOrDigit(c) || c == '_') {
+ if ((LETTER.contains(c) || DIGIT.contains(c)) || c ==
'_') {
if (! inWord) {
count++;
inWord = true;
@@ -7859,10 +7790,6 @@ public class StringUtils {
return count;
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String Builder Utilities
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Wraps text to a specified line length.
*
@@ -8009,8 +7936,6 @@ public class StringUtils {
* Helper method to estimate the number of syllables in a word.
*/
private static int estimateSyllables(String word) {
- if (word == null || word.isEmpty())
- return 1;
var lower = word.toLowerCase();
var count = 0;
@@ -8018,7 +7943,7 @@ public class StringUtils {
for (var i = 0; i < lower.length(); i++) {
var c = lower.charAt(i);
- var isVowel = (c == 'a' || c == 'e' || c == 'i' || c ==
'o' || c == 'u' || c == 'y');
+ var isVowel = (VOWEL.contains(c) || c == 'y');
if (isVowel && ! prevWasVowel) {
count++;
@@ -8059,10 +7984,6 @@ public class StringUtils {
}
}
-
//-----------------------------------------------------------------------------------------------------------------
- // Performance and Memory Utilities
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Helper method to get Soundex code for a character.
*/
@@ -8094,7 +8015,10 @@ public class StringUtils {
* @param ip The IPv6 address string to validate.
* @return <jk>true</jk> if the string is a valid IPv6 address format,
<jk>false</jk> otherwise.
*/
- private static boolean isValidIPv6Address(String ip) {
+ public static boolean isValidIPv6Address(String ip) {
+ if (ip == null || ip.isEmpty())
+ return false;
+
// IPv6 addresses can be:
// 1. Full format: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 (8
groups of 4 hex digits)
// 2. Compressed format: 2001:db8::1 (uses :: to represent
consecutive zeros)
@@ -8130,7 +8054,8 @@ public class StringUtils {
}
// Validate IPv6 part before the IPv4
var ipv6Part = ip.substring(0, lastColon);
- if (ipv6Part.isEmpty() || ipv6Part.equals("::ffff") ||
ipv6Part.equals("::FFFF"))
+ // Accept empty, ::, ::ffff, ::FFFF, or : (when string
starts with ::)
+ if (ipv6Part.isEmpty() || ipv6Part.equals("::") ||
ipv6Part.equals("::ffff") || ipv6Part.equals("::FFFF") || (ipv6Part.equals(":")
&& ip.startsWith("::")))
return true;
// More complex validation would be needed for other
IPv4-mapped formats
// For now, accept common formats
@@ -8148,8 +8073,6 @@ public class StringUtils {
// Split by ::
var parts = ip.split("::", -1);
- if (parts.length > 2)
- return false; // Only one :: allowed
if (parts.length == 2) {
// Compressed format
@@ -8158,8 +8081,6 @@ public class StringUtils {
var totalParts = leftParts.length + rightParts.length;
if (totalParts > 7)
return false; // Too many groups (max 8, but ::
counts as one or more)
- if (totalParts == 0 && !ip.equals("::"))
- return false; // Empty on both sides of :: is
invalid (except :: itself)
} else {
// Full format (no compression)
var groups = ip.split(":");
@@ -8174,14 +8095,12 @@ public class StringUtils {
continue; // Skip empty section from ::
var groupParts = groupSection.split(":");
for (var group : groupParts) {
- if (group.isEmpty())
- return false;
if (group.length() > 4)
return false; // Each group is max 4
hex digits
// Validate hex digits
for (var i = 0; i < group.length(); i++) {
var c = group.charAt(i);
- if (!((c >= '0' && c <= '9') || (c >=
'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
+ if (! HEXADECIMAL_CHARS.contains(c))
return false;
}
}
@@ -8194,7 +8113,7 @@ public class StringUtils {
* Helper method to check if a character is a vowel.
*/
private static boolean isVowel(char c) {
- return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
+ return VOWEL.contains(c);
}
private static class Readifier {
@@ -8241,30 +8160,7 @@ public class StringUtils {
list.add(readifier(Constructor.class, x ->
ConstructorInfo.of(x).getFullName()));
list.add(readifier(Method.class, x ->
MethodInfo.of(x).getFullName()));
list.add(readifier(Field.class, x ->
FieldInfo.of(x).toString()));
- list.add(readifier(Parameter.class, x -> {
- try {
- return ParameterInfo.of(x).toString();
- } catch (IllegalArgumentException ex) {
- var exec = x.getDeclaringExecutable();
- String execName;
- if (exec instanceof Constructor<?> c)
- execName =
ConstructorInfo.of(c).getFullName();
- else if (exec instanceof Method m)
- execName =
MethodInfo.of(m).getFullName();
- else
- execName = exec.toString();
-
- var idx = -1;
- var params = exec.getParameters();
- for (var i = 0; i < params.length; i++) {
- if (params[i] == x) {
- idx = i;
- break;
- }
- }
- return idx >= 0 ? execName + "[" + idx + "]" :
execName;
- }
- }));
+ list.add(readifier(Parameter.class, x ->
ParameterInfo.of(x).toString()));
list.add(readifier(ClassInfo.class, ClassInfo::toString));
list.add(readifier(MethodInfo.class, MethodInfo::toString));
list.add(readifier(ConstructorInfo.class,
ConstructorInfo::toString));
@@ -8388,24 +8284,38 @@ public class StringUtils {
}
/**
- * Skips over comment sequences in a StringReader.
+ * Skips over a single comment sequence in a StringReader.
*
- * @param r The StringReader positioned at the start of a comment.
+ * <p>
+ * The reader must be positioned at the first <js>'/'</js> character of
a comment.
+ * This method will skip only the comment it's currently positioned on,
not all comments in the reader.
+ *
+ * <p>
+ * Supports both <js>"/* * /"</js> style block comments and
<js>"//"</js> style line comments.
+ *
+ * @param r The StringReader positioned at the start of a comment (at
the first <js>'/'</js>).
* @throws IOException If an I/O error occurs.
*/
- private static void skipComments(StringReader r) throws IOException {
+ public static void skipComments(StringReader r) throws IOException {
var c = r.read();
// "/* */" style comments
if (c == '*') {
- while (c != -1)
- if ((c = r.read()) == '*')
- if ((c = r.read()) == '/') // NOSONAR
- Intentional.
+ c = r.read();
+ while (c != -1) {
+ if (c == '*') {
+ c = r.read();
+ if (c == '/')
return;
+ // If not '/', continue checking from
this character
+ // Don't read again, just continue the
loop
+ } else {
+ c = r.read();
+ }
+ }
// "//" style comments
} else if (c == '/') {
- while (c != -1) {
- c = r.read();
- if (c == -1 || c == '\n')
+ while ((c = r.read()) != -1) {
+ if (c == '\n')
return;
}
}
@@ -8417,7 +8327,7 @@ public class StringUtils {
* @param c The character to create an escape set for.
* @return An AsciiSet containing the character and backslash.
*/
- static AsciiSet getEscapeSet(char c) {
+ private static AsciiSet getEscapeSet(char c) {
return ESCAPE_SETS.computeIfAbsent(c, key ->
AsciiSet.create().chars(key, '\\').build());
}
@@ -8428,7 +8338,7 @@ public class StringUtils {
* @param str The string to split.
* @return A list of words, or empty list if input is null or empty.
*/
- static List<String> splitWords(String str) {
+ private static List<String> splitWords(String str) {
if (str == null || isEmpty(str))
return Collections.emptyList();
@@ -8441,9 +8351,9 @@ public class StringUtils {
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
var isSeparator = (c == ' ' || c == '_' || c == '-' ||
c == '\t');
- var isUpperCase = Character.isUpperCase(c);
- var isLowerCase = Character.isLowerCase(c);
- var isLetter = Character.isLetter(c);
+ var isUpperCase = LETTER_UC.contains(c);
+ var isLowerCase = LETTER_LC.contains(c);
+ var isLetter = LETTER.contains(c);
if (isSeparator) {
if (sb.length() > 0) {
@@ -8471,7 +8381,7 @@ public class StringUtils {
// We need at least 2
consecutive uppercase letters before this one to split
if (i + 1 < str.length()) {
var nextChar =
str.charAt(i + 1);
- if
(Character.isLowerCase(nextChar)) {
+ if
(LETTER_LC.contains(nextChar)) {
// This
uppercase starts a new word, split before it
words.add(sb.toString());
sb.setLength(0);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index a181ce5028..8055479d86 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -1438,7 +1438,7 @@ public class BeanSession extends ContextSession {
return null;
else if (from.isString()) {
var s = value.toString();
- if (isJsonArray(s, false)) {
+ if (isProbablyJsonArray(s,
false)) {
var l2 =
JsonList.ofJson(s);
l2.setBeanSession(this);
l2.forEach(x ->
l.add(elementType.isObject() ? x : convertToMemberType(l, x, elementType)));
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
index 5bcd211670..306503122f 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
@@ -270,7 +270,7 @@ public class JsonList extends LinkedList<Object> {
public static JsonList ofJsonOrCdl(String s) throws ParseException {
if (Utils.isEmpty(s)) // NOAI
return null;
- if (! isJsonArray(s, true))
+ if (! isProbablyJsonArray(s, true))
return new JsonList((Object[])splita(s.trim(), ','));
return new JsonList(s);
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
index 866680a6ff..fa9efe00d3 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cp/Messages.java
@@ -259,7 +259,7 @@ public class Messages extends ResourceBundle {
for (var i = mbl.length - 1; i >= 0; i--) {
var c = firstNonNull(mbl[i].getA(),
forClass);
var value = mbl[i].getB();
- if (isJsonObject(value, true)) {
+ if (isProbablyJsonObject(value, true)) {
MessagesString ms;
try {
ms =
Json5.DEFAULT.read(value, MessagesString.class);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index b7b94fd6d2..925bd2bb68 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -3540,7 +3540,7 @@ public class HttpPartSchema {
String s = StringUtils.joinnl(ss);
if (s.isEmpty())
return null;
- if (! isJsonObject(s, true))
+ if (! isProbablyJsonObject(s, true))
s = "{" + s + "}";
try {
return JsonMap.ofJson(s);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
index 08ea41fbd1..d8a9baeb4e 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
@@ -67,7 +67,7 @@ public class SchemaUtils {
return null;
if ("IGNORE".equalsIgnoreCase(s))
return JsonMap.of("ignore", true);
- if (! isJsonObject(s, true))
+ if (! isProbablyJsonObject(s, true))
s = "{" + s + "}";
return JsonMap.ofJson(s);
}
@@ -89,7 +89,7 @@ public class SchemaUtils {
String s = joinnl(ss);
if (s.isEmpty())
return null;
- if (! isJsonObject(s, true))
+ if (! isProbablyJsonObject(s, true))
s = "{" + s + "}";
return JsonMap.ofJson(s);
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
index 6b2046241c..0c42685adb 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
@@ -84,10 +84,10 @@ public class StringMatcherFactory extends MatcherFactory {
}
if (c0 == '\'') {
- s = unEscapeChars(strip(s),
SQ_CHAR);
+ s = unescapeChars(strip(s),
SQ_CHAR);
ignoreCase = true;
} else if (c0 == '"') {
- s = unEscapeChars(strip(s),
DQ_CHAR);
+ s = unescapeChars(strip(s),
DQ_CHAR);
}
if (REGEX_CHARS.contains(s) ||
META_CHARS.contains(s)) {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/svl/VarResolverSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/svl/VarResolverSession.java
index 3b236f60c3..959a74239e 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/svl/VarResolverSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/svl/VarResolverSession.java
@@ -366,7 +366,7 @@ public class VarResolverSession {
state = S3;
} else if (c < 'A' || c > 'z' || (c > 'Z' && c
< 'a')) { // False trigger "$X "
if (hasInnerEscapes)
-
out.append(unEscapeChars(s.substring(x, i + 1), AS1));
+
out.append(unescapeChars(s.substring(x, i + 1), AS1));
else
out.append(s, x, i + 1);
x = i + 1;
@@ -390,7 +390,7 @@ public class VarResolverSession {
Var r = getVar(varType);
if (r == null) {
if (hasInnerEscapes)
-
out.append(unEscapeChars(s.substring(x2, i + 1), AS2));
+
out.append(unescapeChars(s.substring(x2, i + 1), AS2));
else
out.append(s,
x2, i + 1);
x = i + 1;
@@ -424,9 +424,9 @@ public class VarResolverSession {
if (isInEscape)
out.append('\\');
else if (state == S2)
- out.append('$').append(unEscapeChars(s.substring(x +
1), AS1));
+ out.append('$').append(unescapeChars(s.substring(x +
1), AS1));
else if (state == S3)
-
out.append('$').append(varType).append('{').append(unEscapeChars(s.substring(x
+ 1), AS2));
+
out.append('$').append(varType).append('{').append(unescapeChars(s.substring(x
+ 1), AS2));
return out;
}
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 20bd0269c6..45aaf04716 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -7825,7 +7825,7 @@ public class RestClient extends BeanContextable
implements HttpClient, Closeable
"RestClient.close() has already been called.
This client cannot be reused. Closed location stack trace can be displayed by
setting the system property
'org.apache.juneau.rest.client2.RestClient.trackCreation' to true.");
}
- var req = createRequest(toURI(op.getUri(), rootUrl),
op.getMethod(), op.hasContent());
+ var req = createRequest(toUri(op.getUri(), rootUrl),
op.getMethod(), op.hasContent());
onCallInit(req);
@@ -7975,12 +7975,12 @@ public class RestClient extends BeanContextable
implements HttpClient, Closeable
return ! serializers.getSerializers().isEmpty();
}
- URI toURI(Object x, String rootUrl) throws RestCallException {
+ URI toUri(Object x, String rootUrl) throws RestCallException {
try {
if (x instanceof URI x2)
return x2;
if (x instanceof URL x3)
- x3.toURI();
+ return x3.toURI();
if (x instanceof URIBuilder x4)
return x4.build();
var s = x == null ? "" : x.toString();
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index f4015162d6..0109d34b61 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -2185,7 +2185,7 @@ public class RestRequest extends BeanSession implements
HttpUriRequest, Configur
* @throws RestCallException Invalid URI syntax detected.
*/
public RestRequest uri(Object uri) throws RestCallException {
- var x = client.toURI(uri, null);
+ var x = client.toUri(uri, null);
if (nn(x.getScheme()))
uriBuilder.setScheme(x.getScheme());
if (nn(x.getHost()))
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
index 7bf8f1b2de..32dc1c30b2 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
@@ -613,7 +613,7 @@ public class BasicSwaggerProviderSession {
return;
var example = (Object)null;
- if (isJson(sex)) {
+ if (isProbablyJson(sex)) {
example = jp.parse(sex, type);
} else {
var cm = js.getClassMeta(type);
@@ -937,7 +937,7 @@ public class BasicSwaggerProviderSession {
if (s.isEmpty())
return null;
s = resolve(s);
- if (! isJsonArray(s, true))
+ if (! isProbablyJsonArray(s, true))
s = "[" + s + "]";
return JsonList.ofJson(s);
} catch (ParseException e) {
@@ -970,7 +970,7 @@ public class BasicSwaggerProviderSession {
o2 = resolve(o2);
if ("IGNORE".equalsIgnoreCase(o2))
return JsonMap.of("ignore", true);
- if (! isJsonObject(o2, true))
+ if (! isProbablyJsonObject(o2, true))
o2 = "{" + o2 + "}";
return JsonMap.ofJson(o2);
}
@@ -1104,7 +1104,7 @@ public class BasicSwaggerProviderSession {
var s = joinnl(ss);
if (s.isEmpty())
return null;
- if (! isJsonObject(s, true))
+ if (! isProbablyJsonObject(s, true))
s = "{" + s + "}";
s = resolve(s);
return JsonMap.ofJson(s);
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
index 2386fcfd57..53b77ea460 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/RestUtils.java
@@ -194,7 +194,7 @@ public class RestUtils {
* @throws ParseException Invalid JSON in string.
*/
public static Object parseAnything(String s) throws ParseException {
- if (isJson(s))
+ if (isProbablyJson(s))
return JsonParser.DEFAULT.parse(s, Object.class);
return s;
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java
index 182158b737..644abb57e4 100755
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/StringUtils_Test.java
@@ -756,12 +756,15 @@ class StringUtils_Test extends TestBase {
void a030_diffPosition() {
assertEquals(-1, diffPosition("a", "a"));
assertEquals(-1, diffPosition(null, null));
+ assertEquals(-1, diffPosition("identical", "identical")); //
Equal length returns -1 (line 1143 true branch)
assertEquals(0, diffPosition("a", "b"));
assertEquals(1, diffPosition("aa", "ab"));
assertEquals(1, diffPosition("aaa", "ab"));
assertEquals(1, diffPosition("aa", "abb"));
assertEquals(0, diffPosition("a", null));
assertEquals(0, diffPosition(null, "b"));
+ assertEquals(3, diffPosition("abc", "abcdef")); // Equal
prefix but different lengths (line 1143 false branch)
+ assertEquals(2, diffPosition("abcd", "ab")); // Opposite
direction length difference
// Equal strings of same length - triggers line 1158
assertEquals(-1, diffPosition("hello", "hello"));
assertEquals(-1, diffPosition("test", "test"));
@@ -1396,6 +1399,7 @@ class StringUtils_Test extends TestBase {
assertEquals("++x++x++", fixUrl(" x x "));
assertEquals("foo%7Bbar%7Dbaz", fixUrl("foo{bar}baz"));
assertEquals("%7Dfoo%7Bbar%7Dbaz%7B", fixUrl("}foo{bar}baz{"));
+ assertEquals("%E9", fixUrl("é")); // Non-ASCII character
should be percent-encoded
}
//====================================================================================================
@@ -1752,6 +1756,7 @@ class StringUtils_Test extends TestBase {
assertEquals(1 * h + 30 * m, getDuration("1h 30m"));
assertEquals(2 * d + 3 * h + 15 * m, getDuration("2d3h15m"));
assertEquals(1 * w + 2 * d + 3 * h, getDuration("1w2d3h"));
+ assertEquals(-1, getDuration("d10")); // Non-number before
unit - covers invalid number branch (line 2438)
// Months
assertEquals(1 * mo, getDuration("1mo"));
@@ -1886,6 +1891,10 @@ class StringUtils_Test extends TestBase {
var pattern = getMatchPattern("TEST*",
java.util.regex.Pattern.CASE_INSENSITIVE);
assertTrue(pattern.matcher("test123").matches());
assertTrue(pattern.matcher("TEST123").matches());
+
+ // Null input should return null (line 2549)
+ assertNull(getMatchPattern(null));
+ assertNull(getMatchPattern(null,
java.util.regex.Pattern.CASE_INSENSITIVE));
}
//====================================================================================================
@@ -2090,6 +2099,7 @@ class StringUtils_Test extends TestBase {
assertFalse(isAbsoluteUri("http1://foo")); // Number after
'http' (not a letter, not ':')
assertFalse(isAbsoluteUri("http@://foo")); // '@' after 'http'
(not a letter, not ':')
assertFalse(isAbsoluteUri("http /://foo")); // Space after
'http' (not a letter, not ':')
+ assertFalse(isAbsoluteUri("http{://foo")); // '{' after 'http'
(greater than 'z')
// State S3: non-slash - triggers line 2863 (else branch)
assertFalse(isAbsoluteUri("http:x://foo")); // 'x' instead of
'/'
@@ -2395,98 +2405,135 @@ class StringUtils_Test extends TestBase {
}
//====================================================================================================
- // isJson(String)
+ // isProbablyJson(String)
//====================================================================================================
@Test
- void a093_isJson() {
+ void a093_isProbablyJson() {
// Valid JSON
- assertTrue(isJson("{}"));
- assertTrue(isJson("[]"));
- assertTrue(isJson("'test'"));
- assertTrue(isJson("true"));
- assertTrue(isJson("false"));
- assertTrue(isJson("null"));
- assertTrue(isJson("123"));
- assertTrue(isJson("123.45"));
- assertTrue(isJson(" {} ")); // With whitespace
- assertTrue(isJson(" [] ")); // With whitespace
+ assertTrue(isProbablyJson("{}"));
+ assertTrue(isProbablyJson("[]"));
+ assertTrue(isProbablyJson("'test'"));
+ assertTrue(isProbablyJson("true"));
+ assertTrue(isProbablyJson("false"));
+ assertTrue(isProbablyJson("null"));
+ assertTrue(isProbablyJson("123"));
+ assertTrue(isProbablyJson("123.45"));
+ assertTrue(isProbablyJson(" {} ")); // With whitespace
+ assertTrue(isProbablyJson(" [] ")); // With whitespace
+ // Note: isProbablyJson doesn't support comments (uses
firstNonWhitespaceChar, not firstRealCharacter)
+ // Comments are only supported in isProbablyJsonArray and
isProbablyJsonObject when ignoreWhitespaceAndComments=true
// Invalid JSON
- assertFalse(isJson(null));
- assertFalse(isJson(""));
- assertFalse(isJson("abc"));
- assertFalse(isJson("{"));
- assertFalse(isJson("}"));
- assertFalse(isJson("["));
- assertFalse(isJson("]"));
+ assertFalse(isProbablyJson(null));
+ assertFalse(isProbablyJson(""));
+ assertFalse(isProbablyJson("abc"));
+ assertFalse(isProbablyJson("{"));
+ assertFalse(isProbablyJson("}"));
+ assertFalse(isProbablyJson("["));
+ assertFalse(isProbablyJson("]"));
+ assertFalse(isProbablyJson("'abc")); // Starts with quote,
missing closing quote
+ assertFalse(isProbablyJson("abc'")); // Ends with quote,
missing opening quote
}
//====================================================================================================
- // isJsonArray(Object,boolean)
+ // isProbablyJsonArray(Object,boolean)
//====================================================================================================
@Test
- void a094_isJsonArray() {
+ void a094_isProbablyJsonArray() {
// Valid JSON arrays
- assertTrue(isJsonArray("[]", false));
- assertTrue(isJsonArray("[1,2,3]", false));
- assertTrue(isJsonArray(" [1,2,3] ", true)); // With whitespace
- assertTrue(isJsonArray("/*comment*/ [1,2,3] /*comment*/",
true)); // With comments
+ assertTrue(isProbablyJsonArray("[]", false));
+ assertTrue(isProbablyJsonArray("[1,2,3]", false));
+ assertTrue(isProbablyJsonArray(" [1,2,3] ", true)); // With
whitespace
+ assertTrue(isProbablyJsonArray("/*comment*/ [1,2,3]
/*comment*/", true)); // With /* */ comments
+ // Test lines 8282-8287 - // style comments in skipComments
+ // Note: // comments extend to newline or EOF. If no newline,
they consume everything to EOF.
+ assertTrue(isProbablyJsonArray("//comment\n [1,2,3]", true));
// With // comment ending in newline
+ // When // comment has no newline, it consumes to EOF, so
nothing remains - this is invalid
+ assertFalse(isProbablyJsonArray("//comment [1,2,3]", true)); //
With // comment, no newline - consumes everything
+ assertTrue(isProbablyJsonArray(" //comment\n [1,2,3] ",
true)); // With // comment and whitespace
// Invalid JSON arrays
- assertFalse(isJsonArray(null, false));
- assertFalse(isJsonArray("", false));
- assertFalse(isJsonArray("{}", false));
- assertFalse(isJsonArray("123", false));
- assertFalse(isJsonArray("[", false));
- assertFalse(isJsonArray("]", false));
+ assertFalse(isProbablyJsonArray(null, false));
+ assertFalse(isProbablyJsonArray("", false));
+ assertFalse(isProbablyJsonArray("{}", false));
+ assertFalse(isProbablyJsonArray("123", false));
+ assertFalse(isProbablyJsonArray("[", false));
+ assertFalse(isProbablyJsonArray("]", false));
// Test with ignoreWhitespaceAndComments=true - triggers lines
3333, 3336, 3338
// Line 3333: firstRealCharacter(s) != '['
- assertFalse(isJsonArray(" {1,2,3} ", true)); // Starts with
'{', not '['
- assertFalse(isJsonArray(" /*comment*/ {1,2,3} ", true)); //
Starts with '{', not '['
+ assertFalse(isProbablyJsonArray(" {1,2,3} ", true)); //
Starts with '{', not '['
+ assertFalse(isProbablyJsonArray(" /*comment*/ {1,2,3} ",
true)); // Starts with '{', not '['
// Line 3336: lastIndexOf(']') == -1
- assertFalse(isJsonArray(" [1,2,3 ", true)); // No closing ']'
- assertFalse(isJsonArray(" /*comment*/ [1,2,3 ", true)); // No
closing ']'
+ assertFalse(isProbablyJsonArray(" [1,2,3 ", true)); // No
closing ']'
+ assertFalse(isProbablyJsonArray(" /*comment*/ [1,2,3 ",
true)); // No closing ']'
// Line 3338: firstRealCharacter(s) == -1 (after closing
bracket)
- assertTrue(isJsonArray(" [1,2,3] ", true)); // Valid, no
characters after ']'
- assertTrue(isJsonArray(" /*comment*/ [1,2,3] /*comment*/ ",
true)); // Valid, only comments/whitespace after ']'
- assertFalse(isJsonArray(" [1,2,3] extra ", true)); //
Invalid, has characters after ']'
- assertFalse(isJsonArray(" /*comment*/ [1,2,3] extra
/*comment*/ ", true)); // Invalid, has characters after ']'
+ assertTrue(isProbablyJsonArray(" [1,2,3] ", true)); // Valid,
no characters after ']'
+ assertTrue(isProbablyJsonArray(" /*comment*/ [1,2,3]
/*comment*/ ", true)); // Valid, only comments/whitespace after ']'
+ // Test lines 8282-8287 - // style comments after JSON structure
+ // Test with newline to ensure the code path is covered (lines
8283-8287)
+ // Line 8285: c == '\n' branch
+ assertTrue(isProbablyJsonArray(" [1,2,3] //comment\n ",
true)); // Valid, // comment with newline
+ // Line 8285: c == -1 branch (EOF)
+ // When // comment ends at EOF, it consumes everything,
firstRealCharacter returns -1 (no more content)
+ assertTrue(isProbablyJsonArray(" [1,2,3] //comment", true));
// Valid, // comment ending at EOF
+ assertFalse(isProbablyJsonArray(" [1,2,3] extra ", true)); //
Invalid, has characters after ']'
+ assertFalse(isProbablyJsonArray(" /*comment*/ [1,2,3] extra
/*comment*/ ", true)); // Invalid, has characters after ']'
}
//====================================================================================================
- // isJsonObject(Object,boolean)
+ // isProbablyJsonObject(Object,boolean)
//====================================================================================================
@Test
- void a095_isJsonObject() {
+ void a095_isProbablyJsonObject() {
// Valid JSON objects
- assertTrue(isJsonObject("{foo:'bar'}", true));
- assertTrue(isJsonObject(" { foo:'bar' } ", true));
- assertTrue(isJsonObject("/*foo*/ { foo:'bar' } /*foo*/", true));
- assertTrue(isJsonObject("{}", false));
- assertTrue(isJsonObject("{'key':'value'}", false));
+ assertTrue(isProbablyJsonObject("{foo:'bar'}", true));
+ assertTrue(isProbablyJsonObject(" { foo:'bar' } ", true));
+ assertTrue(isProbablyJsonObject("/*foo*/ { foo:'bar' }
/*foo*/", true));
+ // Test lines 8282-8287 - // style comments in skipComments
(via firstRealCharacter)
+ // Note: // comments extend to newline or EOF. If no newline,
they consume everything to EOF.
+ assertTrue(isProbablyJsonObject("//comment\n { foo:'bar' }",
true)); // With // comment ending in newline
+ // When // comment has no newline, it consumes to EOF, so
nothing remains - this is invalid
+ assertFalse(isProbablyJsonObject("//comment { foo:'bar' }",
true)); // With // comment, no newline - consumes everything
+ assertTrue(isProbablyJsonObject(" //comment\n { foo:'bar' }
", true)); // With // comment and whitespace
+ assertTrue(isProbablyJsonObject("{}", false));
+ assertTrue(isProbablyJsonObject("{'key':'value'}", false));
// Invalid JSON objects
- assertFalse(isJsonObject(null, false));
- assertFalse(isJsonObject("", false));
- assertFalse(isJsonObject(" { foo:'bar' ", true));
- assertFalse(isJsonObject(" foo:'bar' } ", true));
- assertFalse(isJsonObject("[]", false));
- assertFalse(isJsonObject("123", false));
-
- // Test with ignoreWhitespaceAndComments=false - triggers line
3354
- assertTrue(isJsonObject("{}", false)); // Simple case
- assertTrue(isJsonObject("{key:value}", false)); // With content
- assertFalse(isJsonObject(" {} ", false)); // Whitespace not
ignored
- assertFalse(isJsonObject("[]", false)); // Not an object
+ assertFalse(isProbablyJsonObject(null, false));
+ assertFalse(isProbablyJsonObject("", false));
+ assertFalse(isProbablyJsonObject(" { foo:'bar' ", true));
+ assertFalse(isProbablyJsonObject(" foo:'bar' } ", true));
+ assertFalse(isProbablyJsonObject("[]", false));
+ assertFalse(isProbablyJsonObject("123", false));
+
+ // Test with ignoreWhitespaceAndComments=false - triggers line
3333 straight check
+ assertTrue(isProbablyJsonObject("{}", false)); // Simple case
+ assertTrue(isProbablyJsonObject("{key:value}", false)); // With
content
+ assertFalse(isProbablyJsonObject(" {} ", false)); //
Whitespace not ignored
+ assertFalse(isProbablyJsonObject("[]", false)); // Not an object
+ assertFalse(isProbablyJsonObject("{", false)); // Missing
closing brace
+ assertFalse(isProbablyJsonObject("}", false)); // Missing
opening brace
+ assertFalse(isProbablyJsonObject("x", false)); // Does not
start/end with braces
// Test with ignoreWhitespaceAndComments=true - triggers line
3361
- assertTrue(isJsonObject(" {} ", true)); // Valid, no
characters after '}'
- assertTrue(isJsonObject(" /*comment*/ {key:value} /*comment*/
", true)); // Valid, only comments/whitespace after '}'
- assertFalse(isJsonObject(" {key:value} extra ", true)); //
Invalid, has characters after '}'
- assertFalse(isJsonObject(" /*comment*/ {key:value} extra
/*comment*/ ", true)); // Invalid, has characters after '}'
+ assertTrue(isProbablyJsonObject(" {} ", true)); // Valid, no
characters after '}'
+ assertTrue(isProbablyJsonObject(" /*comment*/ {key:value}
/*comment*/ ", true)); // Valid, only comments/whitespace after '}'
+ // Test lines 8282-8287 - // style comments after JSON structure
+ // Test with newline to ensure the code path is covered (lines
8283-8287)
+ // Line 8285: c == '\n' branch
+ assertTrue(isProbablyJsonObject(" {key:value} //comment\n ",
true)); // Valid, // comment with newline
+ // Line 8285: c == -1 branch (EOF)
+ // When // comment ends at EOF, it consumes everything,
firstRealCharacter returns -1 (no more content)
+ assertTrue(isProbablyJsonObject(" {key:value} //comment",
true)); // Valid, // comment ending at EOF
+ assertFalse(isProbablyJsonObject(" {key:value} extra ",
true)); // Invalid, has characters after '}'
+ assertFalse(isProbablyJsonObject(" /*comment*/ {key:value}
extra /*comment*/ ", true)); // Invalid, has characters after '}'
+
+ // Non-CharSequence input should return false (covers final
branch)
+ assertFalse(isProbablyJsonObject(123, true));
+ assertFalse(isProbablyJsonObject(new Object(), false));
}
//====================================================================================================
@@ -3018,6 +3065,7 @@ class StringUtils_Test extends TestBase {
// Invalid: empty label
assertFalse(isValidHostname("example..com"));
assertFalse(isValidHostname(".."));
+ assertFalse(isValidHostname(".")); // Single dot splits into
labels but should be invalid (labels array with empty strings)
// Invalid: label starts with hyphen
assertFalse(isValidHostname("-example.com"));
@@ -3100,6 +3148,8 @@ class StringUtils_Test extends TestBase {
assertFalse(isValidIpAddress("192.168.1")); // Invalid IPv4
(too few parts)
assertFalse(isValidIpAddress("192.168.1.1.1")); // Invalid IPv4
(too many parts)
assertFalse(isValidIpAddress("256.1.1.1")); // Invalid IPv4
(out of range)
+ assertFalse(isValidIpAddress("abc."));
+ assertFalse(isValidIpAddress("192.168.1.1::invalid")); //
Contains both '.' and ':' -> skipped IPv4 branch, goes to IPv6 which fails
// Line 3663: IPv6 check (contains ":")
assertTrue(isValidIpAddress("2001:0db8:85a3::8a2e:0370:7334"));
// Valid IPv6
@@ -3114,6 +3164,127 @@ class StringUtils_Test extends TestBase {
assertFalse(isValidIpAddress("192.168.1.abc")); // Invalid
number format
}
+
//====================================================================================================
+ // isValidIPv6Address(String)
+
//====================================================================================================
+ @Test
+ void a107_isValidIPv6Address() {
+ // Null/empty input
+ assertFalse(isValidIPv6Address(null));
+ assertFalse(isValidIPv6Address(""));
+
+ // Valid IPv6 addresses - full format
+
assertTrue(isValidIPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
+
assertTrue(isValidIPv6Address("2001:0DB8:85A3:0000:0000:8A2E:0370:7334")); //
Uppercase
+
assertTrue(isValidIPv6Address("2001:db8:85a3:0:0:8a2e:370:7334")); // Leading
zeros omitted
+
+ // Valid IPv6 addresses - compressed format
+ assertTrue(isValidIPv6Address("2001:db8::1")); // Compressed
zeros
+ assertTrue(isValidIPv6Address("::1")); // Loopback
+ assertTrue(isValidIPv6Address("::")); // Unspecified
+
assertTrue(isValidIPv6Address("2001:db8:85a3::8a2e:0370:7334")); // Compressed
in middle
+ assertTrue(isValidIPv6Address("2001:db8::8a2e:0370:7334")); //
Compressed at start
+
assertTrue(isValidIPv6Address("2001:db8:85a3:0000:0000:8a2e::")); // Compressed
at end
+
+ // Valid IPv6 addresses - IPv4-mapped
+ assertTrue(isValidIPv6Address("::ffff:192.168.1.1"));
+ assertTrue(isValidIPv6Address("::FFFF:192.168.1.1")); //
Uppercase
+ assertTrue(isValidIPv6Address("::192.168.1.1")); // Empty IPv6
part
+
+ // Test line 8008 - starts with single colon (not ::)
+ assertFalse(isValidIPv6Address(":2001:db8::1")); // Starts with
single colon
+ assertFalse(isValidIPv6Address(":1")); // Starts with single
colon
+
+ // Test line 8010 - ends with single colon (not ::)
+ assertFalse(isValidIPv6Address("2001:db8::1:")); // Ends with
single colon
+ assertFalse(isValidIPv6Address("1:")); // Ends with single colon
+
+ // Test line 8014-8039 - IPv4-mapped format
+ // Test line 8017 - lastColon < 0 (no colon before IPv4)
+ assertFalse(isValidIPv6Address("192.168.1.1")); // No colon,
just IPv4
+
+ // Test line 8022 - ipv4Parts.length != 4
+ assertFalse(isValidIPv6Address("::ffff:192.168.1")); // Too few
IPv4 parts
+ assertFalse(isValidIPv6Address("::ffff:192.168.1.1.1")); // Too
many IPv4 parts
+
+ // Test line 8027 - num < 0 or num > 255
+ assertFalse(isValidIPv6Address("::ffff:256.168.1.1")); // IPv4
part > 255
+ assertFalse(isValidIPv6Address("::ffff:192.256.1.1")); // IPv4
part > 255
+ assertFalse(isValidIPv6Address("::ffff:-1.168.1.1")); // IPv4
part < 0 (will fail parse)
+
+ // Test line 8029 - NumberFormatException
+ assertFalse(isValidIPv6Address("::ffff:abc.168.1.1")); //
Invalid number format
+ assertFalse(isValidIPv6Address("::ffff:192.abc.1.1")); //
Invalid number format
+
+ // Test line 8035 - ipv6Part validation (empty or ::ffff/::FFFF)
+ assertTrue(isValidIPv6Address("::ffff:192.168.1.1")); // Valid
::ffff
+ assertTrue(isValidIPv6Address("::FFFF:192.168.1.1")); // Valid
::FFFF
+ assertTrue(isValidIPv6Address("::192.168.1.1")); // Valid empty
IPv6 part
+
+ // Test line 8044-8048 - double colon count > 1
+ assertFalse(isValidIPv6Address("2001::db8::1")); // Multiple ::
+ assertFalse(isValidIPv6Address("::1::")); // Multiple ::
+
+ // Test line 8058 - parts.length > 2 (multiple ::)
+ // Note: This might be caught earlier by doubleColonCount
check, but we test it anyway
+ assertFalse(isValidIPv6Address("2001::db8::1")); // Multiple ::
+
+ // Test line 8056-8064 - compressed format validation
+ // Test line 8066 - totalParts > 7 in compressed format
+ // Need a compressed format (with ::) that has more than 7
total parts
+ // Example: 1:2:3:4:5:6:7:8::9 has 8 parts before :: and 1
after = 9 total > 7
+ assertFalse(isValidIPv6Address("1:2:3:4:5:6:7:8::9")); // Too
many parts in compressed format (8+1=9 > 7)
+ // Another case: 1:2:3:4:5:6:7::8:9 has 7 parts before :: and 2
after = 9 total > 7
+ assertFalse(isValidIPv6Address("1:2:3:4:5:6:7::8:9")); // Too
many parts in compressed format (7+2=9 > 7)
+ // Test line 8068 - totalParts == 0 && !ip.equals("::")
+ // Need both sides of :: to be empty but not exactly "::"
+ // This is tricky - ":::" would have parts = ["", "", ""] which
is length 3, not 2
+ // Actually, if we have ":::" and split by "::", we get ["",
":", ""] which is 3 parts, caught at 8058
+ // Let me think... if we have something like "::" with
something that makes it not equal "::"
+ // Actually, the check is for when parts.length == 2, so we
need exactly 2 parts from split("::")
+ // If both parts are empty, that means the string is "::" which
is valid
+ // So this line might be unreachable, but let's test edge cases
+ // Actually wait - if we have ":::" and split by "::", we get
["", ":", ""] = 3 parts
+ // But what if we have something that creates 2 empty parts?
That would be "::" itself
+ // So line 8068 might be defensive code that's hard to trigger
+ // Let's test with a case that might trigger it - but it's
likely unreachable
+
+ // Test line 8068 - groups.length != 8 (full format, no
compression)
+
assertFalse(isValidIPv6Address("2001:db8:85a3:0000:0000:8a2e:0370")); // Too
few groups (7)
+
assertFalse(isValidIPv6Address("2001:db8:85a3:0000:0000:8a2e:0370:7334:9999"));
// Too many groups (9)
+
+ // Test line 8084 - group.isEmpty() in validation loop
+ // This occurs when a group is empty after splitting by ":"
within a section
+ // Note: "2001::db8:85a3" is valid (compressed format) - the
empty section from :: is skipped
+ // Empty groups can occur with malformed syntax, but many cases
are caught earlier
+ // The validation loop splits by "::" first, then splits each
section by ":"
+ // An empty group would occur if we have consecutive single
colons within a section
+ // However, such cases are often caught by earlier checks
+ // This line is defensive code that's hard to trigger, but we
test edge cases
+ // Note: Triple colons are caught by doubleColonCount check, so
they won't reach here
+
+ // Test line 8081 - group.length() > 4
+ assertFalse(isValidIPv6Address("2001:12345:db8::1")); // Group
too long (5 hex digits)
+ assertFalse(isValidIPv6Address("2001:abcdef:db8::1")); // Group
too long (6 hex digits)
+
+ // Test line 8086 - invalid hex characters
+ assertFalse(isValidIPv6Address("2001:db8g:85a3::1")); //
Invalid hex 'g'
+ assertFalse(isValidIPv6Address("2001:db8h:85a3::1")); //
Invalid hex 'h'
+ assertFalse(isValidIPv6Address("2001:db8z:85a3::1")); //
Invalid hex 'z'
+ assertFalse(isValidIPv6Address("2001:db8G:85a3::1")); //
Invalid hex 'G' (should be lowercase or valid)
+ // Actually, uppercase A-F is valid, so G-Z are invalid
+ assertFalse(isValidIPv6Address("2001:db8G:85a3::1")); //
Invalid hex 'G'
+ assertFalse(isValidIPv6Address("2001:db8@:85a3::1")); //
Invalid character '@'
+ assertFalse(isValidIPv6Address("2001:db8#:85a3::1")); //
Invalid character '#'
+
+ // Edge cases
+ assertTrue(isValidIPv6Address("1:2:3:4:5:6:7:8")); // Minimal
valid
+
assertTrue(isValidIPv6Address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); //
Max values
+ assertTrue(isValidIPv6Address("0:0:0:0:0:0:0:0")); // All zeros
+ assertTrue(isValidIPv6Address("::ffff:0.0.0.0")); //
IPv4-mapped with zeros
+ assertTrue(isValidIPv6Address("::ffff:255.255.255.255")); //
IPv4-mapped with max values
+ }
+
//====================================================================================================
// isValidMacAddress(String)
//====================================================================================================
@@ -3242,9 +3413,10 @@ class StringUtils_Test extends TestBase {
collection.add("a");
collection.add("b");
collection.add("c");
- assertNull(join((Collection<?>)null, ',')); // Line 3825: null
check
- assertEquals("a,b,c", join(collection, ',')); // Lines
3827-3833: iteration
- assertEquals("a", join(Collections.singletonList("a"), ','));
// Single element
+ assertNull(join((Collection<?>)null, ',')); // Line 3803: null
check
+ assertEquals("", join(Collections.emptyList(), ',')); // Empty
collection - loop never executes
+ assertEquals("a", join(Collections.singletonList("a"), ','));
// Single element - iter.hasNext() false after first iteration
+ assertEquals("a,b,c", join(collection, ',')); // Lines
3806-3809: iteration with multiple elements
// join(Collection<?>, String, StringBuilder) - triggers line
3859
var sb1 = new StringBuilder("prefix-");
@@ -3780,6 +3952,33 @@ class StringUtils_Test extends TestBase {
assertNotNull(codeGGE);
// Second G should become K, not J
+ // Line 4402: GH followed by vowels (A, E, I, O, U) - silent GH
+ var codeGHA = metaphone("GHAST"); // GH followed by A
+ assertNotNull(codeGHA);
+ var codeGHE = metaphone("GHETTO"); // GH followed by E
+ assertNotNull(codeGHE);
+ var codeGHI = metaphone("GHILLIE"); // GH followed by I
+ assertNotNull(codeGHI);
+ var codeGHO = metaphone("GHOST"); // GH followed by O
+ assertNotNull(codeGHO);
+ var codeGHU = metaphone("GHOUL"); // GH followed by U
+ assertNotNull(codeGHU);
+
+ // Line 4404: GN followed by E or D - silent GN
+ var codeGNE = metaphone("SIGNE"); // GN followed by E
+ assertNotNull(codeGNE);
+ var codeGND = metaphone("SIGND"); // GN followed by D (test
case)
+ assertNotNull(codeGND);
+
+ // Line 4406: G followed by E/I/Y with prev != 'G' - becomes J
+ // Already tested: GE (AGE), GI (GIRAFFE)
+ var codeGY = metaphone("GYM"); // GY with prev != 'G'
+ assertNotNull(codeGY);
+ assertTrue(codeGY.contains("J")); // GY becomes J
+ var codeGY2 = metaphone("AGY"); // GY with prev != 'G' (prev is
A)
+ assertNotNull(codeGY2);
+ assertTrue(codeGY2.contains("J")); // GY becomes J
+
// Test H handling - triggers line 4437
// H between vowels (silent) - both prev and next are vowels
(!isVowel(prev) || !isVowel(next) is false)
var codeH3 = metaphone("AHOY"); // A-H-O, H between vowels
@@ -3871,6 +4070,12 @@ class StringUtils_Test extends TestBase {
assertEquals(0, naturalCompare("file12.txt", "file12.txt")); //
12 == 12 (same length, all digits equal)
assertTrue(naturalCompare("file19.txt", "file20.txt") < 0); //
19 < 20 (same length, digit by digit)
+ // Test equal numbers followed by more content - triggers lines
4605-4606
+ // When numbers are equal, i1 and i2 are set to end1 and end2,
then loop continues
+ assertTrue(naturalCompare("file12abc", "file12def") < 0); // 12
== 12, then compare "abc" < "def"
+ assertTrue(naturalCompare("file12def", "file12abc") > 0); // 12
== 12, then compare "def" > "abc"
+ assertEquals(0, naturalCompare("file12abc", "file12abc")); //
12 == 12, then "abc" == "abc"
+
// Test when one string is longer - triggers line 4643
assertTrue(naturalCompare("file1", "file10.txt") < 0); //
"file1" is shorter
assertTrue(naturalCompare("file10.txt", "file1") > 0); //
"file10.txt" is longer
@@ -4058,63 +4263,6 @@ class StringUtils_Test extends TestBase {
assertEquals("1*****", obfuscate("123456"));
}
-
//====================================================================================================
- // optimizeString(String)
-
//====================================================================================================
- @Test
- void a139_optimizeString() {
- // Null/empty input
- assertNull(optimizeString(null));
- assertNull(optimizeString("short")); // No suggestions for
short strings
-
- // Large strings should suggest StringBuilder
- var largeString = "x".repeat(1001);
- var suggestions = optimizeString(largeString);
- assertNotNull(suggestions);
- assertTrue(suggestions.contains("StringBuilder"));
-
- // Very large strings should suggest compression
- var veryLargeString = "x".repeat(10001);
- var suggestions2 = optimizeString(veryLargeString);
- assertNotNull(suggestions2);
- assertTrue(suggestions2.contains("compression"));
-
- // Medium-length strings (10 < length < 100) that are not
interned - triggers line 4992
- // Create a string that is not interned and meets the criteria
- // Use a unique string that won't be in the string pool
- var mediumString = new String(new char[50]).replace('\0', 'x');
// Length 50, explicitly not interned
- var suggestions3 = optimizeString(mediumString);
- // optimizeString returns null if no suggestions, or a string
with suggestions
- // For medium-length strings that are not interned, it should
suggest interning
- // Note: The string might be interned by the JVM, so we check
if suggestions exist
- if (suggestions3 != null) {
- assertTrue(suggestions3.contains("interning"));
- }
- // If null, the string was already interned, which is fine -
the line is still covered by the check
-
- // Medium-length string that IS interned (should not suggest
interning)
- var internedString = "x".repeat(50).intern();
- var suggestions4 = optimizeString(internedString);
- // Should not contain interning suggestion if already interned
- if (suggestions4 != null) {
- assertFalse(suggestions4.contains("interning"));
- }
-
- // String exactly 10 chars (should not suggest interning)
- var exactly10 = "x".repeat(10);
- var suggestions5 = optimizeString(exactly10);
- if (suggestions5 != null) {
- assertFalse(suggestions5.contains("interning"));
- }
-
- // String exactly 100 chars (should not suggest interning)
- var exactly100 = "x".repeat(100);
- var suggestions6 = optimizeString(exactly100);
- if (suggestions6 != null) {
- assertFalse(suggestions6.contains("interning"));
- }
- }
-
//====================================================================================================
// ordinal(int)
//====================================================================================================
@@ -4734,10 +4882,16 @@ class StringUtils_Test extends TestBase {
assertEquals(0.0, readabilityScore("..."), 0.0001); // No words
extracted
assertEquals(0.0, readabilityScore(" "), 0.0001); // Only
whitespace
- // Test line 8191 - estimateSyllables with null or empty word
(returns 1)
- // This is tested indirectly through readabilityScore with
single-letter words
+ // Test line 7921 - estimateSyllables with null or empty word
(returns 1)
+ // Note: This is defensive code. extractWords uses pattern \\w+
which requires at least
+ // one character, so it won't return null or empty strings.
However, estimateSyllables
+ // has this check as defensive programming. We test it
indirectly by ensuring
+ // readabilityScore handles various inputs without crashing.
var scoreSingle = readabilityScore("a");
assertTrue(scoreSingle >= 0); // Should handle single letter
word
+ // Test with various word patterns to ensure estimateSyllables
handles edge cases
+ var scoreMixed = readabilityScore("a b c d e");
+ assertTrue(scoreMixed >= 0 && scoreMixed <= 100);
// Test sentence endings - triggers line 5743
var score1 = readabilityScore("First sentence. Second
sentence!");
@@ -5154,11 +5308,57 @@ class StringUtils_Test extends TestBase {
// splita(...) - Already covered in a175_split
//====================================================================================================
+
//====================================================================================================
+ // skipComments(StringReader)
+
//====================================================================================================
+ @Test
+ void a178_skipCommentsStringReader() throws IOException {
+ // Test /* */ style comments
+ var r1 = new StringReader("/*comment*/rest");
+ r1.read(); // Read the '/'
+ skipComments(r1);
+ var sb1 = new StringBuilder();
+ int c;
+ while ((c = r1.read()) != -1)
+ sb1.append((char)c);
+ assertEquals("rest", sb1.toString());
+
+ // Test // style comment with newline
+ var r2 = new StringReader("//comment\nrest");
+ r2.read(); // Read the '/'
+ skipComments(r2);
+ var sb2 = new StringBuilder();
+ while ((c = r2.read()) != -1)
+ sb2.append((char)c);
+ assertEquals("rest", sb2.toString());
+
+ // Test // style comment ending at EOF
+ var r3 = new StringReader("//comment");
+ r3.read(); // Read the '/'
+ skipComments(r3);
+ assertEquals(-1, r3.read()); // Should be at EOF
+
+ // Test /* */ comment with no closing (should consume to EOF)
+ var r5 = new StringReader("/*unclosed");
+ r5.read(); // Read the '/'
+ skipComments(r5);
+ assertEquals(-1, r5.read()); // Should be at EOF
+
+ // Test // comment with content after newline
+ var r6 = new StringReader("//comment\nmore//another");
+ r6.read(); // Read the '/'
+ skipComments(r6);
+ var sb6 = new StringBuilder();
+ while ((c = r6.read()) != -1)
+ sb6.append((char)c);
+ assertEquals("more//another", sb6.toString());
+ }
+
//====================================================================================================
// snakeCase(String)
//====================================================================================================
@Test
- void a178_snakeCase() {
+ void a180_snakeCase() {
assertNull(snakeCase(null));
assertEquals("", snakeCase(""));
assertEquals("hello_world", snakeCase("hello world"));
@@ -5184,7 +5384,7 @@ class StringUtils_Test extends TestBase {
// sort(String[])
//====================================================================================================
@Test
- void a179_sort() {
+ void a181_sort() {
assertNull(sort(null));
assertList(sort(a()));
assertList(sort(a("c", "a", "b")), "a", "b", "c");
@@ -5199,7 +5399,7 @@ class StringUtils_Test extends TestBase {
// sortIgnoreCase(String[])
//====================================================================================================
@Test
- void a180_sortIgnoreCase() {
+ void a182_sortIgnoreCase() {
assertNull(sortIgnoreCase(null));
assertList(sortIgnoreCase(a()));
assertList(sortIgnoreCase(a("c", "a", "b")), "a", "b", "c");
@@ -5214,7 +5414,7 @@ class StringUtils_Test extends TestBase {
// soundex(String)
//====================================================================================================
@Test
- void a181_soundex() {
+ void a183_soundex() {
// Basic soundex examples
var code1 = soundex("Smith");
assertNotNull(code1);
@@ -5305,13 +5505,28 @@ class StringUtils_Test extends TestBase {
// String with same consecutive codes (code == lastCode, should
skip)
var code16 = soundex("ABBC"); // A + B(1) + B(1, same) + C(2) =
A12 (B skipped)
assertEquals("A120", code16);
+
+ // Test line 6253 - loop exits early when result.length() >= 4
(before reaching end of string)
+ // Long string that produces 4 codes early, loop should exit
due to result.length() >= 4
+ var code17 = soundex("ABCDEFGHIJKLMNOP"); // A + B(1) + C(2) +
D(3) = A123, loop exits early
+ assertEquals("A123", code17);
+ assertEquals(4, code17.length()); // Should be exactly 4, not
longer
+
+ // Test line 6253 - loop exits when i >= upper.length()
(reached end of string)
+ // Short string that doesn't produce 4 codes, loop exits when
reaching end
+ // Test line 6269 - padding loop when result.length() < 4
+ // String that produces 2 codes (needs 2 zeros)
+ var code18 = soundex("AB"); // A + B(1) = A1, needs 2 zeros,
loop exits at end of string
+ assertEquals("A100", code18);
+ // String that produces 1 code (needs 3 zeros) - already
covered by code12
+ // String that produces 0 codes (needs 4 zeros) - already
covered by code13
}
//====================================================================================================
// split(...) - All variants
//====================================================================================================
@Test
- void a182_split() {
+ void a184_split() {
// split(String) - splits on comma
assertEquals(Collections.emptyList(), split(null));
assertTrue(split("").isEmpty());
@@ -5394,7 +5609,7 @@ class StringUtils_Test extends TestBase {
// splita(String[], char)
//====================================================================================================
@Test
- void a183_splitaStringArray() {
+ void a185_splitaStringArray() {
// Null array - explicitly cast to String[] to disambiguate
String[] nullArray = null;
assertNull(splita(nullArray, ','));
@@ -5441,7 +5656,7 @@ class StringUtils_Test extends TestBase {
// splitMap(String,boolean)
//====================================================================================================
@Test
- void a184_splitMap() {
+ void a186_splitMap() {
assertString("{a=1}", splitMap("a=1", true));
assertString("{a=1,b=2}", splitMap("a=1,b=2", true));
assertString("{a=1,b=2}", splitMap(" a = 1 , b = 2 ", true));
@@ -5472,7 +5687,7 @@ class StringUtils_Test extends TestBase {
// splitMethodArgs(String)
//====================================================================================================
@Test
- void a185_splitMethodArgs() {
+ void a187_splitMethodArgs() {
// Basic method argument splitting
var args1 = splitMethodArgs("a,b,c");
assertEquals(3, args1.length);
@@ -5508,7 +5723,7 @@ class StringUtils_Test extends TestBase {
// splitNested(String)
//====================================================================================================
@Test
- void a186_splitNested() {
+ void a188_splitNested() {
// Basic nested splitting (uses curly braces)
var result1 = splitNested("a,b,c");
assertEquals(3, result1.size());
@@ -5562,7 +5777,7 @@ class StringUtils_Test extends TestBase {
// splitNestedInner(String)
//====================================================================================================
@Test
- void a187_splitNestedInner() {
+ void a189_splitNestedInner() {
// Basic nested inner splitting (extracts inner content)
var result1 = splitNestedInner("a{b}");
assertEquals(1, result1.size());
@@ -5625,7 +5840,7 @@ class StringUtils_Test extends TestBase {
// splitQuoted(String)
//====================================================================================================
@Test
- void a188_splitQuoted() {
+ void a190_splitQuoted() {
assertNull(splitQuoted(null));
assertEmpty(splitQuoted(""));
assertEmpty(splitQuoted(" \t "));
@@ -5669,12 +5884,30 @@ class StringUtils_Test extends TestBase {
assertEquals(1, result4.length);
assertEquals("'foo\\'bar'", result4[0]); // Quotes kept, escape
preserved
- // Test line 7039 - state S4 (non-whitespace token ending with
whitespace)
+ // Test line 6772 - state S1: character that is not space, tab,
single quote, or double quote
+ // This transitions to state S4
+ // Test starting from whitespace to ensure we're in state S1
+ var result5a = splitQuoted(" abc");
+ assertEquals(1, result5a.length);
+ assertEquals("abc", result5a[0]);
+ // Also test without leading whitespace
+ var result5a2 = splitQuoted("abc");
+ assertEquals(1, result5a2.length);
+ assertEquals("abc", result5a2[0]);
+
+ // Test line 6793 - state S4: encountering space or tab
+ // This adds the token and returns to state S1
var result5 = splitQuoted("foo bar");
assertEquals(2, result5.length);
assertEquals("foo", result5[0]);
assertEquals("bar", result5[1]);
+ // Test line 6793 - state S4: encountering tab character
+ var result5b = splitQuoted("foo\tbar");
+ assertEquals(2, result5b.length);
+ assertEquals("foo", result5b[0]);
+ assertEquals("bar", result5b[1]);
+
// Test line 7048 - unmatched quotes error
assertThrows(IllegalArgumentException.class, () ->
splitQuoted("'unmatched quote"));
assertThrows(IllegalArgumentException.class, () ->
splitQuoted("\"unmatched quote"));
@@ -5686,7 +5919,7 @@ class StringUtils_Test extends TestBase {
// startsWith(String,char)
//====================================================================================================
@Test
- void a189_startsWith() {
+ void a191_startsWith() {
assertFalse(startsWith(null, 'a'));
assertFalse(startsWith("", 'a'));
assertTrue(startsWith("a", 'a'));
@@ -5700,7 +5933,7 @@ class StringUtils_Test extends TestBase {
// startsWithIgnoreCase(String,String)
//====================================================================================================
@Test
- void a190_startsWithIgnoreCase() {
+ void a192_startsWithIgnoreCase() {
assertTrue(startsWithIgnoreCase("Hello World", "hello"));
assertTrue(startsWithIgnoreCase("Hello World", "HELLO"));
assertTrue(startsWithIgnoreCase("Hello World", "Hello"));
@@ -5717,7 +5950,7 @@ class StringUtils_Test extends TestBase {
// stringSupplier(Supplier<?>)
//====================================================================================================
@Test
- void a191_stringSupplier() {
+ void a193_stringSupplier() {
var supplier1 = stringSupplier(() -> "test");
assertEquals("test", supplier1.get());
@@ -5736,7 +5969,7 @@ class StringUtils_Test extends TestBase {
// strip(String)
//====================================================================================================
@Test
- void a192_strip() {
+ void a194_strip() {
assertNull(strip(null));
assertEquals("", strip(""));
// strip returns the same string if length <= 1
@@ -5753,7 +5986,7 @@ class StringUtils_Test extends TestBase {
// stripInvalidHttpHeaderChars(String)
//====================================================================================================
@Test
- void a193_stripInvalidHttpHeaderChars() {
+ void a195_stripInvalidHttpHeaderChars() {
assertNull(stripInvalidHttpHeaderChars(null));
assertEquals("", stripInvalidHttpHeaderChars(""));
// Test actual behavior - spaces appear to be removed
@@ -5771,7 +6004,7 @@ class StringUtils_Test extends TestBase {
// substringAfter(String,String)
//====================================================================================================
@Test
- void a194_substringAfter() {
+ void a196_substringAfter() {
assertNull(substringAfter(null, "."));
assertEquals("", substringAfter("hello.world", null));
assertEquals("world", substringAfter("hello.world", "."));
@@ -5784,7 +6017,7 @@ class StringUtils_Test extends TestBase {
// substringBefore(String,String)
//====================================================================================================
@Test
- void a195_substringBefore() {
+ void a197_substringBefore() {
assertNull(substringBefore(null, "."));
assertEquals("hello.world", substringBefore("hello.world",
null));
assertEquals("hello", substringBefore("hello.world", "."));
@@ -5797,7 +6030,7 @@ class StringUtils_Test extends TestBase {
// substringBetween(String,String,String)
//====================================================================================================
@Test
- void a196_substringBetween() {
+ void a198_substringBetween() {
assertNull(substringBetween(null, "<", ">"));
assertNull(substringBetween("<hello>", null, ">"));
assertNull(substringBetween("<hello>", "<", null));
@@ -5817,7 +6050,7 @@ class StringUtils_Test extends TestBase {
// swapCase(String)
//====================================================================================================
@Test
- void a197_swapCase() {
+ void a199_swapCase() {
assertNull(swapCase(null));
assertEquals("", swapCase(""));
assertEquals("hELLO wORLD", swapCase("Hello World"));
@@ -5830,7 +6063,7 @@ class StringUtils_Test extends TestBase {
// titleCase(String)
//====================================================================================================
@Test
- void a198_titleCase() {
+ void a200_titleCase() {
assertNull(titleCase(null));
assertEquals("", titleCase(""));
assertEquals("Hello World", titleCase("hello world"));
@@ -5843,13 +6076,25 @@ class StringUtils_Test extends TestBase {
assertEquals("Test", titleCase("test"));
assertEquals("Test", titleCase("TEST"));
assertEquals("Hello 123 World", titleCase("hello 123 world"));
+
+ // Test line 7033 - splitWords returns empty list (only
separators, no actual words)
+ // Note: digits and punctuation are treated as part of words by
splitWords
+ assertEquals("", titleCase(" ")); // Only spaces
+ assertEquals("", titleCase("___")); // Only underscores
+ assertEquals("", titleCase("---")); // Only hyphens
+ assertEquals("", titleCase("\t\t")); // Only tabs
+ assertEquals("", titleCase(" _- ")); // Only separators
+ // Digits are treated as words, so "123" becomes a word
+ assertEquals("123", titleCase("123")); // Only digits - treated
as a word
+ // Punctuation is treated as part of words
+ assertEquals("!@#", titleCase("!@#")); // Only punctuation -
treated as a word
}
//====================================================================================================
// toCdl(Object)
//====================================================================================================
@Test
- void a199_toCdl() {
+ void a201_toCdl() {
// Null input
assertNull(toCdl(null));
@@ -5872,7 +6117,7 @@ class StringUtils_Test extends TestBase {
// toHex(byte) and toHex(byte[])
//====================================================================================================
@Test
- void a200_toHex() {
+ void a202_toHex() {
// toHex(byte)
assertEquals("00", toHex((byte)0));
assertEquals("FF", toHex((byte)-1));
@@ -5891,7 +6136,7 @@ class StringUtils_Test extends TestBase {
// toHex2(int)
//====================================================================================================
@Test
- void a201_toHex2() {
+ void a203_toHex2() {
// Test zero
assertString("00", toHex2(0));
@@ -5916,7 +6161,7 @@ class StringUtils_Test extends TestBase {
// toHex4(int)
//====================================================================================================
@Test
- void a202_toHex4() {
+ void a204_toHex4() {
// Test zero
assertString("0000", toHex4(0));
@@ -5942,7 +6187,7 @@ class StringUtils_Test extends TestBase {
// toHex8(long)
//====================================================================================================
@Test
- void a203_toHex8() {
+ void a205_toHex8() {
// Test zero
assertString("00000000", toHex8(0));
@@ -5963,7 +6208,7 @@ class StringUtils_Test extends TestBase {
// toHex(InputStream)
//====================================================================================================
@Test
- void a204_toHexInputStream() throws Exception {
+ void a206_toHexInputStream() throws Exception {
// Null input
assertNull(toHex((java.io.InputStream)null));
@@ -5997,7 +6242,7 @@ class StringUtils_Test extends TestBase {
// toIsoDate(Calendar)
//====================================================================================================
@Test
- void a205_toIsoDate() {
+ void a207_toIsoDate() {
assertNull(toIsoDate(null));
// Create a calendar for a specific date
@@ -6014,7 +6259,7 @@ class StringUtils_Test extends TestBase {
// toIsoDateTime(Calendar)
//====================================================================================================
@Test
- void a206_toIsoDateTime() {
+ void a208_toIsoDateTime() {
assertNull(toIsoDateTime(null));
// Create a calendar for a specific date-time
@@ -6032,7 +6277,7 @@ class StringUtils_Test extends TestBase {
// toReadableBytes(byte[])
//====================================================================================================
@Test
- void a207_toReadableBytes() {
+ void a209_toReadableBytes() {
// Test with printable characters
var bytes1 = "Hello".getBytes();
var result1 = toReadableBytes(bytes1);
@@ -6111,24 +6356,24 @@ class StringUtils_Test extends TestBase {
}
//====================================================================================================
- // toURI(Object)
+ // toUri(Object)
//====================================================================================================
@Test
- void a211_toURI() {
+ void a211_toUri() {
// Null input
- assertNull(toURI(null));
+ assertNull(toUri(null));
// URI input - returns same object
var uri1 = java.net.URI.create("http://example.com");
- assertSame(uri1, toURI(uri1));
+ assertSame(uri1, toUri(uri1));
// String input
- var uri2 = toURI("http://example.com");
+ var uri2 = toUri("http://example.com");
assertNotNull(uri2);
assertEquals("http://example.com", uri2.toString());
// Invalid URI - should throw exception
- assertThrows(RuntimeException.class, () -> toURI("not a valid
uri"));
+ assertThrows(RuntimeException.class, () -> toUri("not a valid
uri"));
}
//====================================================================================================
@@ -6291,28 +6536,36 @@ class StringUtils_Test extends TestBase {
}
//====================================================================================================
- // unEscapeChars(String,AsciiSet)
+ // unescapeChars(String,AsciiSet)
//====================================================================================================
@Test
- void a222_unEscapeChars() {
+ void a222_unescapeChars() {
var escape = AsciiSet.of("\\,|");
- assertNull(unEscapeChars(null, escape));
- assertEquals("xxx", unEscapeChars("xxx", escape));
- assertEquals("x,xx", unEscapeChars("x\\,xx", escape));
- assertEquals("x\\xx", unEscapeChars("x\\xx", escape));
- assertEquals("x\\,xx", unEscapeChars("x\\\\,xx", escape));
- assertEquals("x\\,xx", unEscapeChars("x\\\\\\,xx", escape));
- assertEquals("\\", unEscapeChars("\\", escape));
- assertEquals(",", unEscapeChars("\\,", escape));
+ assertNull(unescapeChars(null, escape));
+ assertEquals("xxx", unescapeChars("xxx", escape));
+ assertEquals("x,xx", unescapeChars("x\\,xx", escape));
+ assertEquals("x\\xx", unescapeChars("x\\xx", escape));
+ assertEquals("x\\,xx", unescapeChars("x\\\\,xx", escape));
+ assertEquals("x\\,xx", unescapeChars("x\\\\\\,xx", escape));
+ assertEquals("\\", unescapeChars("\\", escape));
+ assertEquals(",", unescapeChars("\\,", escape));
// Test line 7743 - double backslash (c2 == '\\')
- assertEquals("x\\y", unEscapeChars("x\\\\y", escape)); //
Double backslash becomes single
- assertEquals("x\\", unEscapeChars("x\\\\", escape)); // Double
backslash at end
- assertEquals("|", unEscapeChars("\\|", escape));
+ assertEquals("x\\y", unescapeChars("x\\\\y", escape)); //
Double backslash becomes single
+ assertEquals("x\\", unescapeChars("x\\\\", escape)); // Double
backslash at end
+ assertEquals("|", unescapeChars("\\|", escape));
- escape = AsciiSet.of(",|");
- assertEquals("x\\\\xx", unEscapeChars("x\\\\xx", escape));
+ // Test line 7493 - double backslash where second backslash is
NOT in escaped set
+ // When escape set doesn't include '\', double backslash
handling
+ escape = AsciiSet.of(",|"); // Backslash not in escaped set
+ assertEquals("x\\\\xx", unescapeChars("x\\\\xx", escape));
+ // More explicit test: double backslash with a character that's
not escaped
+ // When we have "a\\\\b" and '\' is not in escaped set:
+ // - First '\' sees second '\', appends '\' and skips second
'\', then appends the second '\' at line 7498
+ // - Second '\' sees 'b', doesn't match, appends the second '\'
at line 7498
+ // Result: "a\\\\b" (both backslashes preserved)
+ assertEquals("a\\\\b", unescapeChars("a\\\\b",
AsciiSet.of(","))); // '\\' not in escaped set, so line 7493 executes
}
//====================================================================================================
@@ -6432,6 +6685,30 @@ class StringUtils_Test extends TestBase {
// No encoding needed - returns as-is
assertEquals("Hello", urlEncodeLax("Hello"));
assertEquals("test123", urlEncodeLax("test123"));
+
+ // Test line 7656 - ASCII characters (c <= 127) that need
encoding
+ // Characters not in URL_UNENCODED_LAX_CHARS and not space
+ var result2 = urlEncodeLax("test#value");
+ assertNotNull(result2);
+ assertTrue(result2.contains("%23")); // # is encoded as %23
+ var result3 = urlEncodeLax("test%value");
+ assertNotNull(result3);
+ assertTrue(result3.contains("%25")); // % is encoded as %25
+ var result4 = urlEncodeLax("test&value");
+ assertNotNull(result4);
+ assertTrue(result4.contains("%26")); // & is encoded as %26
+
+ // Test line 7659 - Non-ASCII characters (c > 127) that need
encoding
+ // Unicode characters are encoded using URLEncoder.encode
+ var result5 = urlEncodeLax("testévalue");
+ assertNotNull(result5);
+ assertTrue(result5.contains("%")); // é should be encoded
+ var result6 = urlEncodeLax("test中文");
+ assertNotNull(result6);
+ assertTrue(result6.contains("%")); // Chinese characters should
be encoded
+ var result7 = urlEncodeLax("test🎉");
+ assertNotNull(result7);
+ assertTrue(result7.contains("%")); // Emoji should be encoded
}
//====================================================================================================
@@ -6461,19 +6738,28 @@ class StringUtils_Test extends TestBase {
var result4 = urlEncodePath("simplepath");
assertEquals("simplepath", result4); // No encoding needed,
returns as-is
- // Test lines 7967-7971 - UTF-8 surrogate pairs (high surrogate
0xD800-0xDBFF, low surrogate 0xDC00-0xDFFF)
- // Create a string with surrogate pairs (emoji or other high
Unicode characters)
- // Note: The emoji might be in the valid character set, so
let's use a character that definitely needs encoding
- var highSurrogate = (char)0xD800;
- var lowSurrogate = (char)0xDC00;
- var surrogatePair = new String(new char[]{highSurrogate,
lowSurrogate});
- var result5 = urlEncodePath(surrogatePair);
+ // Test lines 7701-7705 - UTF-8 surrogate pairs (high surrogate
0xD800-0xDBFF, low surrogate 0xDC00-0xDFFF)
+ // The surrogate pair handling code is executed when encoding
characters that contain surrogate pairs
+ // Use a string that contains a character needing encoding
along with surrogate pairs
+ // to ensure the encoding path is taken and surrogate pair
handling is executed
+ var emoji = "🎉"; // This contains surrogate pairs
+ var result5 = urlEncodePath(emoji);
assertNotNull(result5);
- // If the surrogate pair is not in the valid character set, it
should be encoded
- // Otherwise, it might be returned as-is
+ // The surrogate pair code (lines 7701-7705) is executed during
encoding
+ // Check that the result is different from input (encoding
occurred) or contains encoded characters
assertTrue(result5.length() > 0);
-
- // Test lines 7985, 7990 - uppercase hex digits (caseDiff
applied)
+ // Also test with a string that combines regular characters
with surrogate pairs
+ var result5b = urlEncodePath("test🎉file");
+ assertNotNull(result5b);
+ assertTrue(result5b.length() > 0);
+
+ // Test lines 7719, 7724 - uppercase hex digits (caseDiff
applied)
+ // forDigit returns lowercase 'a'-'f' for hex digits 10-15,
which need to be converted to uppercase
+ // We need characters that produce bytes with hex values
containing a-f
+ // For example, byte 0x0A produces hex "a", byte 0x0B produces
"b", etc.
+ // Use characters that when UTF-8 encoded produce bytes with
values 0x0A-0x0F
+ // Actually, any non-ASCII character will produce multi-byte
UTF-8 encoding
+ // Let's use a character that produces bytes with hex digits a-f
var result6 = urlEncodePath("test@file");
assertNotNull(result6);
// Check that hex digits are uppercase (A-F, not a-f)
@@ -6489,6 +6775,20 @@ class StringUtils_Test extends TestBase {
// Verify it's uppercase (caseDiff converts lowercase
to uppercase)
assertEquals(hex.toUpperCase(), hex);
}
+ // Test with a character that produces bytes with hex digits a-f
+ // Use a character that when UTF-8 encoded produces bytes with
values that result in lowercase hex
+ // For example, characters that produce bytes 0x0A-0x0F,
0x1A-0x1F, etc.
+ // Chinese character or other multi-byte UTF-8 character should
work
+ var result7 = urlEncodePath("test中文");
+ assertNotNull(result7);
+ assertTrue(result7.contains("%"));
+ // Verify hex digits are uppercase
+ var matcher2 = hexPattern.matcher(result7);
+ while (matcher2.find()) {
+ var hex = matcher2.group(1);
+ // Verify it's uppercase (lines 7719 and 7724 convert
lowercase to uppercase)
+ assertEquals(hex.toUpperCase(), hex);
+ }
// If we found hex sequences, verify they're uppercase
if (foundHex) {
// All hex should be uppercase
@@ -6552,18 +6852,48 @@ class StringUtils_Test extends TestBase {
var result1 = wrap("line1\n\nline2", 10, "\n");
assertTrue(result1.contains("\n\n")); // Empty line preserved
- // Test line 8108 - empty word skipping (word.isEmpty())
- var result2 = wrap("word1 word2", 10, "\n"); // Multiple
spaces create empty words
+ // Test line 7842 - empty word skipping (word.isEmpty())
+ // Multiple spaces create empty words that should be skipped
+ var result2 = wrap("word1 word2", 10, "\n");
assertTrue(result2.contains("word1"));
assertTrue(result2.contains("word2"));
-
- // Test lines 8117-8131 - word breaking when wordLength >
wrapLength && words.length > 1
+ // Test with multiple consecutive spaces
+ var result2b = wrap("a b c", 10, "\n");
+ assertTrue(result2b.contains("a"));
+ assertTrue(result2b.contains("b"));
+ assertTrue(result2b.contains("c"));
+
+ // Test lines 7853-7865 - word breaking when wordLength >
wrapLength && words.length > 1
+ // This tests breaking a long word when it's the first word on
a line
+ // Line 7853: result.length() > 0 (result already has content)
+ // Lines 7856-7865: while loop that breaks word into chunks
+ // Line 7857: wordPos > 0 (not first iteration)
+ // Line 7860: remaining <= wrapLength (remaining fits)
+ // Line 7861: append remaining
+ // Line 7862: break
+ // Line 7864: append chunk
+ // Line 7865: advance position
var result3 = wrap("short verylongword here", 5, "\n");
assertFalse(result3.contains("verylongword")); // Word should
be split into chunks
for (var line : result3.split("\n")) {
if (! line.isEmpty())
assertTrue(line.length() <= 5);
}
+ // Test with result already having content (line 7853:
result.length() > 0)
+ // After a previous line, a long word that needs breaking as
first word on new line
+ // This happens when a previous line was completed and we start
a new line
+ var result3b = wrap("first\nverylongword here", 5, "\n");
+ // After "first\n", "verylongword" is the first word on the new
line and needs breaking
+ assertTrue(result3b.contains("first"));
+ assertFalse(result3b.contains("verylongword")); // Long word
should be broken
+ // Test word that breaks into multiple chunks (covers lines
7857, 7864, 7865)
+ // Word length 15, wrapLength 5 -> 3 chunks of 5, 5, 5
+ // This tests: wordPos > 0 (line 7857), append chunk (line
7864), advance position (line 7865)
+ // And: remaining <= wrapLength (line 7860), append remaining
(line 7861), break (line 7862)
+ var result3c = wrap("abcdefghijklmno here", 5, "\n");
+ assertTrue(result3c.contains("abcde")); // First chunk (wordPos
== 0, no newline before)
+ assertTrue(result3c.contains("fghij")); // Second chunk
(wordPos > 0, newline before, line 7857)
+ assertTrue(result3c.contains("klmno")); // Third chunk
(remaining <= wrapLength, line 7860-7862)
// Test lines 8149-8162 - word breaking in else branch (when
current line doesn't fit)
var result4 = wrap("short word verylongword here", 10, "\n");