diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ca79774a6..58eeca071 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@ + Add JsonToStringStyle implementation to ToStringStyle Add NoClassNameToStringStyle implementation of ToStringStyle Fix wrong examples in JavaDoc of StringUtils.replaceEachRepeatedly(...), StringUtils.replaceEach(...) Add StringUtils.containsAny(CharSequence, CharSequence...) method diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java index 5bb7d35d2..24f295aae 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java @@ -139,6 +139,29 @@ public abstract class ToStringStyle implements Serializable { */ public static final ToStringStyle NO_CLASS_NAME_STYLE = new NoClassNameToStringStyle(); + /** + * The JSON toString style. Using the Person example from + * {@link ToStringBuilder}, the output would look like this: + * + *
+     * {
+     *   "name": "John Doe",
+     *   "age": 33,
+     *   "smoker": true
+     * }
+     * 
+ * + * Note: Since field names are mandatory in JSON, this + * ToStringStyle will throw an {@link UnsupportedOperationException} if no + * field name is passed in while appending. Furthermore This ToStringStyle + * will only generate valid JSON if referenced objects also produce JSON + * when calling {@code toString()} on them. + * + * @since 3.4 + * @see json.org + */ + public static final ToStringStyle JSON_STYLE = new JsonToStringStyle(); + /** *

* A registry of objects used by reflectionToString methods @@ -2326,4 +2349,265 @@ public abstract class ToStringStyle implements Serializable { } + // ---------------------------------------------------------------------------- + + /** + *

+ * ToStringStyle that outputs with JSON format. + *

+ * + *

+ * This is an inner class rather than using + * StandardToStringStyle to ensure its immutability. + *

+ */ + private static final class JsonToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * The summary size text start '>'. + */ + private String FIELD_NAME_PREFIX = "\""; + + /** + *

+ * Constructor. + *

+ * + *

+ * Use the static constant rather than instantiating. + *

+ */ + JsonToStringStyle() { + super(); + + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + + this.setContentStart("{"); + this.setContentEnd("}"); + + this.setArrayStart("["); + this.setArrayEnd("]"); + + this.setFieldSeparator(","); + this.setFieldNameValueSeparator(":"); + + this.setNullText("null"); + + this.setSummaryObjectStartText("\"<"); + this.setSummaryObjectEndText(">\""); + + this.setSizeStartText("\"\""); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + Object[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, long[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, int[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + short[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, byte[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, char[] array, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + double[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + float[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, + boolean[] array, Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(StringBuffer buffer, String fieldName, Object value, + Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)){ + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, value, fullDetail); + } + + @Override + protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { + + if (value == null) { + + appendNullText(buffer, fieldName); + return; + } + + if (value.getClass() == String.class) { + + appendValueAsString(buffer, (String)value); + return; + } + + buffer.append(value); + } + + private void appendValueAsString(StringBuffer buffer, String value) { + + buffer.append("\"" + value + "\""); + } + + @Override + protected void appendFieldStart(StringBuffer buffer, String fieldName) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + + super.appendFieldStart(buffer, FIELD_NAME_PREFIX + fieldName + + FIELD_NAME_PREFIX); + } + + /** + *

+ * Ensure Singleton after serialization. + *

+ * + * @return the singleton + */ + private Object readResolve() { + return ToStringStyle.JSON_STYLE; + } + + } } diff --git a/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java new file mode 100644 index 000000000..0041d9d25 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/JsonToStringStyleTest.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.apache.commons.lang3.SystemUtils; +import org.apache.commons.lang3.builder.ToStringStyleTest.Person; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests {@link org.apache.commons.lang3.builder.JsonToStringStyleTest}. + * + * @version $Id$ + */ +public class JsonToStringStyleTest { + + private final Integer base = Integer.valueOf(5); + + @Before + public void setUp() throws Exception { + ToStringBuilder.setDefaultStyle(ToStringStyle.JSON_STYLE); + } + + @After + public void tearDown() throws Exception { + ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE); + } + + // ---------------------------------------------------------------- + + @Test + public void testNull() { + + assertEquals(String.valueOf((Object) null), + new ToStringBuilder(null).toString()); + } + + @Test + public void testBlank() { + + assertEquals("{}", new ToStringBuilder(base).toString()); + } + + @Test + public void testAppendSuper() { + + assertEquals( + "{}", + new ToStringBuilder(base).appendSuper( + "Integer@8888[" + SystemUtils.LINE_SEPARATOR + "]") + .toString()); + assertEquals( + "{}", + new ToStringBuilder(base).appendSuper( + "Integer@8888[" + SystemUtils.LINE_SEPARATOR + " null" + + SystemUtils.LINE_SEPARATOR + "]").toString()); + + assertEquals( + "{\"a\":\"hello\"}", + new ToStringBuilder(base) + .appendSuper( + "Integer@8888[" + SystemUtils.LINE_SEPARATOR + + "]").append("a", "hello").toString()); + + assertEquals( + "{\"a\":\"hello\"}", + new ToStringBuilder(base) + .appendSuper( + "Integer@8888[" + SystemUtils.LINE_SEPARATOR + + " null" + SystemUtils.LINE_SEPARATOR + + "]").append("a", "hello").toString()); + + assertEquals("{\"a\":\"hello\"}", new ToStringBuilder(base) + .appendSuper(null).append("a", "hello").toString()); + } + + @Test + public void testObject() { + + final Integer i3 = Integer.valueOf(3); + final Integer i4 = Integer.valueOf(4); + + try { + + assertEquals("{\"null\":null}", + new ToStringBuilder(base).append((Object) null).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":3}", new ToStringBuilder(base).append(i3) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + assertEquals("{\"a\":null}", + new ToStringBuilder(base).append("a", (Object) null).toString()); + + assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", i3) + .toString()); + + assertEquals("{\"a\":3,\"b\":4}", + new ToStringBuilder(base).append("a", i3).append("b", i4) + .toString()); + + try { + assertEquals("{\"a\":\"\"}", + new ToStringBuilder(base).append("a", i3, false).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals( + "{\"a\":\"\"}", + new ToStringBuilder(base).append("a", new ArrayList(), + false).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + assertEquals( + "{\"a\":[]}", + new ToStringBuilder(base).append("a", new ArrayList(), + true).toString()); + + try { + + assertEquals( + "{\"a\":\"\"}", + new ToStringBuilder(base).append("a", + new HashMap(), false).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + assertEquals( + "{\"a\":{}}", + new ToStringBuilder(base).append("a", + new HashMap(), true).toString()); + + try { + + assertEquals( + "{\"a\":\"\"}", + new ToStringBuilder(base).append("a", (Object) new String[0], + false).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + assertEquals( + "{\"a\":[]}", + new ToStringBuilder(base).append("a", (Object) new String[0], + true).toString()); + + try { + + assertEquals( + "{\"a\":\"\"}", + new ToStringBuilder(base).append("a", + (Object) new int[] { 1, 2, 3 }, false).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + + assertEquals( + "{\"a\":[1,2,3]}", + new ToStringBuilder(base).append("a", + (Object) new int[] { 1, 2, 3 }, true).toString()); + + try { + + assertEquals( + "{\"a\":\"\"}", + new ToStringBuilder(base).append("a", + (Object) new String[] { "v", "x", "y", "z" }, false) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + + assertEquals( + "{\"a\":[\"v\",\"x\",\"y\",\"z\"]}", + new ToStringBuilder(base).append("a", + (Object) new String[] { "v", "x", "y", "z" }, true) + .toString()); + } + + @Test + public void testPerson() { + + final Person p = new Person(); + + p.name = "Jane Doe"; + p.age = 25; + p.smoker = true; + + assertEquals( + "{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true}", + new ToStringBuilder(p).append("name", p.name) + .append("age", p.age).append("smoker", p.smoker) + .toString()); + } + + @Test + public void testLong() { + + try { + + assertEquals("{\"null\":3}", new ToStringBuilder(base).append(3L) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", 3L) + .toString()); + + assertEquals("{\"a\":3,\"b\":4}", + new ToStringBuilder(base).append("a", 3L).append("b", 4L) + .toString()); + } + + @Test + public void testObjectArray() { + + Object[] array = new Object[] { null, base, new int[] { 3, 6 } }; + + try { + + assertEquals("{\"null\":[null,5,[3,6]]}", new ToStringBuilder(base) + .append(array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":[null,5,[3,6]]}", new ToStringBuilder(base) + .append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + array = null; + + try { + + assertEquals("{\"null\":null}", new ToStringBuilder(base).append(array) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":null}", + new ToStringBuilder(base).append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + } + + @Test + public void testLongArray() { + + long[] array = new long[] { 1, 2, -3, 4 }; + + try { + + assertEquals("{\"null\":[1,2,-3,4]}", + new ToStringBuilder(base).append(array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":[1,2,-3,4]}", + new ToStringBuilder(base).append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + array = null; + + try { + + assertEquals("{\"null\":null}", new ToStringBuilder(base).append(array) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":null}", + new ToStringBuilder(base).append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + } + + @Test + public void testLongArrayArray() { + + long[][] array = new long[][] { { 1, 2 }, null, { 5 } }; + + try { + + assertEquals("{\"null\":[[1,2],null,[5]]}", new ToStringBuilder(base) + .append(array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":[[1,2],null,[5]]}", new ToStringBuilder(base) + .append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + array = null; + + try { + + assertEquals("{\"null\":null}", new ToStringBuilder(base).append(array) + .toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + + try { + + assertEquals("{\"null\":null}", + new ToStringBuilder(base).append((Object) array).toString()); + + fail("Should have generated UnsupportedOperationException"); + + } catch (UnsupportedOperationException e) { + } + } + +}