| /* |
| * 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.logging.log4j.layout.json.template.util; |
| |
| import org.apache.logging.log4j.util.BiConsumer; |
| import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; |
| import org.apache.logging.log4j.util.StringBuilderFormattable; |
| import org.apache.logging.log4j.util.StringMap; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| /** |
| * A simple JSON writer with support for common Java data types. |
| * <p> |
| * The following types have specific handlers: |
| * <p> |
| * <ul> |
| * <li> <tt>null</tt> input |
| * <li>{@link Map}, {@link IndexedReadOnlyStringMap}, {@link StringMap} |
| * <li>{@link Collection} and {@link List} |
| * <li>{@link Number} ({@link BigDecimal}, {@link BigInteger}, {@link Float}, |
| * {@link Double}, {@link Byte}, {@link Short}, {@link Integer}, and |
| * {@link Long}) |
| * <li>{@link Boolean} |
| * <li>{@link StringBuilderFormattable} |
| * <li>arrays of primitve types |
| * <tt>char/boolean/byte/short/int/long/float/double</tt> and {@link Object} |
| * <li>{@link CharSequence} and <tt>char[]</tt> with necessary escaping |
| * </ul> |
| * <p> |
| * JSON standard quoting routines are borrowed from |
| * <a href="https://github.com/FasterXML/jackson-core">Jackson</a>. |
| */ |
| public final class JsonWriter implements AutoCloseable, Cloneable { |
| |
| private final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray(); |
| |
| /** |
| * Lookup table used for determining which output characters in 7-bit ASCII |
| * range (i.e., first 128 Unicode code points, single-byte UTF-8 characters) |
| * need to be quoted. |
| *<p> |
| * Value of 0 means "no escaping"; other positive values, that value is |
| * character to use after backslash; and negative values, that generic |
| * (backslash - u) escaping is to be used. |
| */ |
| private final static int[] ESC_CODES; |
| static { |
| int[] table = new int[128]; |
| // Control chars need generic escape sequence |
| for (int i = 0; i < 32; ++i) { |
| // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with constant |
| table[i] = -1; |
| } |
| // Others (and some within that range too) have explicit shorter sequences |
| table['"'] = '"'; |
| table['\\'] = '\\'; |
| // Escaping of slash is optional, so let's not add it |
| table[0x08] = 'b'; |
| table[0x09] = 't'; |
| table[0x0C] = 'f'; |
| table[0x0A] = 'n'; |
| table[0x0D] = 'r'; |
| ESC_CODES = table; |
| } |
| |
| private final char[] quoteBuffer; |
| |
| private final StringBuilder stringBuilder; |
| |
| private final StringBuilder formattableBuffer; |
| |
| private final int maxStringLength; |
| |
| private final String truncatedStringSuffix; |
| |
| private final String quotedTruncatedStringSuffix; |
| |
| private JsonWriter(final Builder builder) { |
| this.quoteBuffer = new char[]{'\\', '-', '0', '0', '-', '-'}; |
| this.stringBuilder = new StringBuilder(); |
| this.formattableBuffer = new StringBuilder(); |
| this.maxStringLength = builder.maxStringLength; |
| this.truncatedStringSuffix = builder.truncatedStringSuffix; |
| this.quotedTruncatedStringSuffix = quoteString(builder.truncatedStringSuffix); |
| } |
| |
| private String quoteString(final String string) { |
| final int startIndex = stringBuilder.length(); |
| quoteString(string, 0, string.length()); |
| final StringBuilder quotedStringBuilder = new StringBuilder(); |
| quotedStringBuilder.append(stringBuilder, startIndex, stringBuilder.length()); |
| final String quotedString = quotedStringBuilder.toString(); |
| stringBuilder.setLength(startIndex); |
| return quotedString; |
| } |
| |
| public String use(Runnable runnable) { |
| final int startIndex = stringBuilder.length(); |
| runnable.run(); |
| final StringBuilder sliceStringBuilder = new StringBuilder(); |
| sliceStringBuilder.append(stringBuilder, startIndex, stringBuilder.length()); |
| stringBuilder.setLength(startIndex); |
| return sliceStringBuilder.toString(); |
| } |
| |
| public StringBuilder getStringBuilder() { |
| return stringBuilder; |
| } |
| |
| public int getMaxStringLength() { |
| return maxStringLength; |
| } |
| |
| public String getTruncatedStringSuffix() { |
| return truncatedStringSuffix; |
| } |
| |
| public void writeValue(final Object value) { |
| |
| // null |
| if (value == null) { |
| writeNull(); |
| } |
| |
| // map |
| else if (value instanceof IndexedReadOnlyStringMap) { |
| final IndexedReadOnlyStringMap map = (IndexedReadOnlyStringMap) value; |
| writeObject(map); |
| } else if (value instanceof StringMap) { |
| final StringMap map = (StringMap) value; |
| writeObject(map); |
| } else if (value instanceof Map) { |
| @SuppressWarnings("unchecked") |
| final Map<String, Object> map = (Map<String, Object>) value; |
| writeObject(map); |
| } |
| |
| // list & collection |
| else if (value instanceof List) { |
| @SuppressWarnings("unchecked") |
| final List<Object> list = (List<Object>) value; |
| writeArray(list); |
| } else if (value instanceof Collection) { |
| @SuppressWarnings("unchecked") |
| final Collection<Object> collection = (Collection<Object>) value; |
| writeArray(collection); |
| } |
| |
| // number & boolean |
| else if (value instanceof Number) { |
| final Number number = (Number) value; |
| writeNumber(number); |
| } else if (value instanceof Boolean) { |
| final boolean booleanValue = (boolean) value; |
| writeBoolean(booleanValue); |
| } |
| |
| // formattable |
| else if (value instanceof StringBuilderFormattable) { |
| final StringBuilderFormattable formattable = (StringBuilderFormattable) value; |
| writeString(formattable); |
| } |
| |
| // arrays |
| else if (value instanceof char[]) { |
| final char[] charValues = (char[]) value; |
| writeArray(charValues); |
| } else if (value instanceof boolean[]) { |
| final boolean[] booleanValues = (boolean[]) value; |
| writeArray(booleanValues); |
| } else if (value instanceof byte[]) { |
| final byte[] byteValues = (byte[]) value; |
| writeArray(byteValues); |
| } else if (value instanceof short[]) { |
| final short[] shortValues = (short[]) value; |
| writeArray(shortValues); |
| } else if (value instanceof int[]) { |
| final int[] intValues = (int[]) value; |
| writeArray(intValues); |
| } else if (value instanceof long[]) { |
| final long[] longValues = (long[]) value; |
| writeArray(longValues); |
| } else if (value instanceof float[]) { |
| final float[] floatValues = (float[]) value; |
| writeArray(floatValues); |
| } else if (value instanceof double[]) { |
| final double[] doubleValues = (double[]) value; |
| writeArray(doubleValues); |
| } else if (value instanceof Object[]) { |
| final Object[] values = (Object[]) value; |
| writeArray(values); |
| } |
| |
| // string |
| else { |
| final String stringValue = value instanceof String |
| ? (String) value |
| : String.valueOf(value); |
| writeString(stringValue); |
| } |
| |
| } |
| |
| public void writeObject(final StringMap map) { |
| if (map == null) { |
| writeNull(); |
| } else { |
| writeObjectStart(); |
| final boolean[] firstEntry = {true}; |
| map.forEach((final String key, final Object value) -> { |
| if (key == null) { |
| throw new IllegalArgumentException("null keys are not allowed"); |
| } |
| if (firstEntry[0]) { |
| firstEntry[0] = false; |
| } else { |
| writeSeparator(); |
| } |
| writeObjectKey(key); |
| writeValue(value); |
| }); |
| writeObjectEnd(); |
| } |
| } |
| |
| public void writeObject(final IndexedReadOnlyStringMap map) { |
| if (map == null) { |
| writeNull(); |
| } else { |
| writeObjectStart(); |
| for (int entryIndex = 0; entryIndex < map.size(); entryIndex++) { |
| final String key = map.getKeyAt(entryIndex); |
| final Object value = map.getValueAt(entryIndex); |
| if (entryIndex > 0) { |
| writeSeparator(); |
| } |
| writeObjectKey(key); |
| writeValue(value); |
| } |
| writeObjectEnd(); |
| } |
| } |
| |
| public void writeObject(final Map<String, Object> map) { |
| if (map == null) { |
| writeNull(); |
| } else { |
| writeObjectStart(); |
| final boolean[] firstEntry = {true}; |
| map.forEach((final String key, final Object value) -> { |
| if (key == null) { |
| throw new IllegalArgumentException("null keys are not allowed"); |
| } |
| if (firstEntry[0]) { |
| firstEntry[0] = false; |
| } else { |
| writeSeparator(); |
| } |
| writeObjectKey(key); |
| writeValue(value); |
| }); |
| writeObjectEnd(); |
| } |
| } |
| |
| public void writeObjectStart() { |
| stringBuilder.append('{'); |
| } |
| |
| public void writeObjectEnd() { |
| stringBuilder.append('}'); |
| } |
| |
| public void writeObjectKey(final CharSequence key) { |
| writeString(key); |
| stringBuilder.append(':'); |
| } |
| |
| public void writeArray(final List<Object> items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final Object item = items.get(itemIndex); |
| writeValue(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final Collection<Object> items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| final boolean[] firstItem = {true}; |
| items.forEach((final Object item) -> { |
| if (firstItem[0]) { |
| firstItem[0] = false; |
| } else { |
| writeSeparator(); |
| } |
| writeValue(item); |
| }); |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final char[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| stringBuilder.append('"'); |
| quoteString(items, itemIndex, 1); |
| stringBuilder.append('"'); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final boolean[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final boolean item = items[itemIndex]; |
| writeBoolean(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final byte[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final byte item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final short[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final short item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final int[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final int item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final long[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final long item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final float[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final float item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final double[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final double item = items[itemIndex]; |
| writeNumber(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArray(final Object[] items) { |
| if (items == null) { |
| writeNull(); |
| } else { |
| writeArrayStart(); |
| for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { |
| if (itemIndex > 0) { |
| writeSeparator(); |
| } |
| final Object item = items[itemIndex]; |
| writeValue(item); |
| } |
| writeArrayEnd(); |
| } |
| } |
| |
| public void writeArrayStart() { |
| stringBuilder.append('['); |
| } |
| |
| public void writeArrayEnd() { |
| stringBuilder.append(']'); |
| } |
| |
| public void writeSeparator() { |
| stringBuilder.append(','); |
| } |
| |
| public <S> void writeString( |
| final BiConsumer<StringBuilder, S> emitter, |
| final S state) { |
| Objects.requireNonNull(emitter, "emitter"); |
| stringBuilder.append('"'); |
| formattableBuffer.setLength(0); |
| emitter.accept(formattableBuffer, state); |
| final int length = formattableBuffer.length(); |
| // Handle max. string length complying input. |
| if (length <= maxStringLength) { |
| quoteString(formattableBuffer, 0, length); |
| } |
| // Handle max. string length violating input. |
| else { |
| quoteString(formattableBuffer, 0, maxStringLength); |
| stringBuilder.append(quotedTruncatedStringSuffix); |
| } |
| stringBuilder.append('"'); |
| } |
| |
| public void writeString(final StringBuilderFormattable formattable) { |
| if (formattable == null) { |
| writeNull(); |
| } else { |
| stringBuilder.append('"'); |
| formattableBuffer.setLength(0); |
| formattable.formatTo(formattableBuffer); |
| final int length = formattableBuffer.length(); |
| // Handle max. string length complying input. |
| if (length <= maxStringLength) { |
| quoteString(formattableBuffer, 0, length); |
| } |
| // Handle max. string length violating input. |
| else { |
| quoteString(formattableBuffer, 0, maxStringLength); |
| stringBuilder.append(quotedTruncatedStringSuffix); |
| } |
| stringBuilder.append('"'); |
| } |
| } |
| |
| public void writeString(final CharSequence seq) { |
| if (seq == null) { |
| writeNull(); |
| } else { |
| writeString(seq, 0, seq.length()); |
| } |
| } |
| |
| public void writeString( |
| final CharSequence seq, |
| final int offset, |
| final int length) { |
| |
| // Handle null input. |
| if (seq == null) { |
| writeNull(); |
| return; |
| } |
| |
| // Check arguments. |
| if (offset < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive offset: " + offset); |
| } |
| if (length < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive length: " + length); |
| } |
| |
| stringBuilder.append('"'); |
| // Handle max. string length complying input. |
| if (length <= maxStringLength) { |
| quoteString(seq, offset, length); |
| } |
| // Handle max. string length violating input. |
| else { |
| quoteString(seq, offset, maxStringLength); |
| stringBuilder.append(quotedTruncatedStringSuffix); |
| } |
| stringBuilder.append('"'); |
| |
| } |
| |
| /** |
| * Quote text contents using JSON standard quoting. |
| */ |
| private void quoteString( |
| final CharSequence seq, |
| final int offset, |
| final int length) { |
| final int limit = offset + length; |
| int i = offset; |
| outer: |
| while (i < limit) { |
| while (true) { |
| final char c = seq.charAt(i); |
| if (c < ESC_CODES.length && ESC_CODES[c] != 0) { |
| break; |
| } |
| stringBuilder.append(c); |
| if (++i >= limit) { |
| break outer; |
| } |
| } |
| final char d = seq.charAt(i++); |
| final int escCode = ESC_CODES[d]; |
| final int quoteBufferLength = escCode < 0 |
| ? quoteNumeric(d) |
| : quoteNamed(escCode); |
| stringBuilder.append(quoteBuffer, 0, quoteBufferLength); |
| } |
| } |
| |
| public void writeString(final char[] buffer) { |
| if (buffer == null) { |
| writeNull(); |
| } else { |
| writeString(buffer, 0, buffer.length); |
| } |
| } |
| |
| public void writeString( |
| final char[] buffer, |
| final int offset, |
| final int length) { |
| |
| // Handle null input. |
| if (buffer == null) { |
| writeNull(); |
| return; |
| } |
| |
| // Check arguments. |
| if (offset < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive offset: " + offset); |
| } |
| if (length < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive length: " + length); |
| } |
| |
| stringBuilder.append('"'); |
| // Handle max. string length complying input. |
| if (length <= maxStringLength) { |
| quoteString(buffer, offset, length); |
| } |
| // Handle max. string length violating input. |
| else { |
| quoteString(buffer, offset, maxStringLength); |
| stringBuilder.append(quotedTruncatedStringSuffix); |
| } |
| stringBuilder.append('"'); |
| |
| } |
| |
| /** |
| * Quote text contents using JSON standard quoting. |
| */ |
| private void quoteString( |
| final char[] buffer, |
| final int offset, |
| final int length) { |
| final int limit = offset + length; |
| int i = offset; |
| outer: |
| while (i < limit) { |
| while (true) { |
| final char c = buffer[i]; |
| if (c < ESC_CODES.length && ESC_CODES[c] != 0) { |
| break; |
| } |
| stringBuilder.append(c); |
| if (++i >= limit) { |
| break outer; |
| } |
| } |
| final char d = buffer[i++]; |
| final int escCode = ESC_CODES[d]; |
| final int quoteBufferLength = escCode < 0 |
| ? quoteNumeric(d) |
| : quoteNamed(escCode); |
| stringBuilder.append(quoteBuffer, 0, quoteBufferLength); |
| } |
| } |
| |
| private int quoteNumeric(final int value) { |
| quoteBuffer[1] = 'u'; |
| // We know it's a control char, so only the last 2 chars are non-0 |
| quoteBuffer[4] = HEX_CHARS[value >> 4]; |
| quoteBuffer[5] = HEX_CHARS[value & 0xF]; |
| return 6; |
| } |
| |
| private int quoteNamed(final int esc) { |
| quoteBuffer[1] = (char) esc; |
| return 2; |
| } |
| |
| private void writeNumber(final Number number) { |
| if (number instanceof BigDecimal) { |
| final BigDecimal decimalNumber = (BigDecimal) number; |
| writeNumber(decimalNumber); |
| } else if (number instanceof BigInteger) { |
| final BigInteger integerNumber = (BigInteger) number; |
| writeNumber(integerNumber); |
| } else if (number instanceof Double) { |
| final double doubleNumber = (Double) number; |
| writeNumber(doubleNumber); |
| } else if (number instanceof Float) { |
| final float floatNumber = (float) number; |
| writeNumber(floatNumber); |
| } else if (number instanceof Byte || |
| number instanceof Short || |
| number instanceof Integer || |
| number instanceof Long) { |
| final long longNumber = number.longValue(); |
| writeNumber(longNumber); |
| } else { |
| final long longNumber = number.longValue(); |
| final double doubleValue = number.doubleValue(); |
| if (Double.compare(longNumber, doubleValue) == 0) { |
| writeNumber(longNumber); |
| } else { |
| writeNumber(doubleValue); |
| } |
| } |
| } |
| |
| public void writeNumber(final BigDecimal number) { |
| if (number == null) { |
| writeNull(); |
| } else { |
| stringBuilder.append(number); |
| } |
| } |
| |
| public void writeNumber(final BigInteger number) { |
| if (number == null) { |
| writeNull(); |
| } else { |
| stringBuilder.append(number); |
| } |
| } |
| |
| public void writeNumber(final float number) { |
| stringBuilder.append(number); |
| } |
| |
| public void writeNumber(final double number) { |
| stringBuilder.append(number); |
| } |
| |
| public void writeNumber(final short number) { |
| stringBuilder.append(number); |
| } |
| |
| public void writeNumber(final int number) { |
| stringBuilder.append(number); |
| } |
| |
| public void writeNumber(final long number) { |
| stringBuilder.append(number); |
| } |
| |
| public void writeNumber(final long integralPart, final long fractionalPart) { |
| if (fractionalPart < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive fraction: " + fractionalPart); |
| } |
| stringBuilder.append(integralPart); |
| if (fractionalPart != 0) { |
| stringBuilder.append('.'); |
| stringBuilder.append(fractionalPart); |
| } |
| } |
| |
| public void writeBoolean(final boolean value) { |
| writeRawString(value ? "true" : "false"); |
| } |
| |
| public void writeNull() { |
| writeRawString("null"); |
| } |
| |
| public void writeRawString(final CharSequence seq) { |
| Objects.requireNonNull(seq, "seq"); |
| writeRawString(seq, 0, seq.length()); |
| } |
| |
| public void writeRawString( |
| final CharSequence seq, |
| final int offset, |
| final int length) { |
| |
| // Check arguments. |
| Objects.requireNonNull(seq, "seq"); |
| if (offset < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive offset: " + offset); |
| } |
| if (length < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive length: " + length); |
| } |
| |
| // Write characters. |
| final int limit = offset + length; |
| stringBuilder.append(seq, offset, limit); |
| |
| } |
| |
| public void writeRawString(final char[] buffer) { |
| Objects.requireNonNull(buffer, "buffer"); |
| writeRawString(buffer, 0, buffer.length); |
| } |
| |
| public void writeRawString( |
| final char[] buffer, |
| final int offset, |
| final int length) { |
| |
| // Check arguments. |
| Objects.requireNonNull(buffer, "buffer"); |
| if (offset < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive offset: " + offset); |
| } |
| if (length < 0) { |
| throw new IllegalArgumentException( |
| "was expecting a positive length: " + length); |
| } |
| |
| // Write characters. |
| stringBuilder.append(buffer, offset, length); |
| |
| } |
| |
| @Override |
| public void close() { |
| stringBuilder.setLength(0); |
| } |
| |
| @Override |
| @SuppressWarnings("MethodDoesntCallSuperMethod") |
| public JsonWriter clone() { |
| final JsonWriter jsonWriter = newBuilder() |
| .setMaxStringLength(maxStringLength) |
| .setTruncatedStringSuffix(truncatedStringSuffix) |
| .build(); |
| jsonWriter.stringBuilder.append(stringBuilder); |
| return jsonWriter; |
| } |
| |
| public static Builder newBuilder() { |
| return new Builder(); |
| } |
| |
| public static final class Builder { |
| |
| private int maxStringLength; |
| |
| private String truncatedStringSuffix; |
| |
| public int getMaxStringLength() { |
| return maxStringLength; |
| } |
| |
| public Builder setMaxStringLength(final int maxStringLength) { |
| this.maxStringLength = maxStringLength; |
| return this; |
| } |
| |
| public String getTruncatedStringSuffix() { |
| return truncatedStringSuffix; |
| } |
| |
| public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) { |
| this.truncatedStringSuffix = truncatedStringSuffix; |
| return this; |
| } |
| |
| public JsonWriter build() { |
| validate(); |
| return new JsonWriter(this); |
| } |
| |
| private void validate() { |
| if (maxStringLength <= 0) { |
| throw new IllegalArgumentException( |
| "was expecting maxStringLength > 0: " + |
| maxStringLength); |
| } |
| Objects.requireNonNull(truncatedStringSuffix, "truncatedStringSuffix"); |
| } |
| |
| } |
| |
| } |