diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 38d9b31909..d05089e9e2 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -250,8 +250,21 @@ public enum Feature { * @deprecated Use {@link com.fasterxml.jackson.core.StreamWriteFeature#USE_FAST_DOUBLE_WRITER} instead */ @Deprecated - USE_FAST_DOUBLE_WRITER(false) - ; + USE_FAST_DOUBLE_WRITER(false), + + /** + * Feature that specifies that hex values are encoded with capital letters. + *

+ * Can be disabled to have a better possibility to compare between other Json + * writer libraries, such as JSON.stringify from Javascript. + *

+ * Feature is enabled by default. + * + * @since 2.14 + * @deprecated Use {@link com.fasterxml.jackson.core.json.JsonWriteFeature#WRITE_HEX_UPPER_CASE} instead + */ + @Deprecated + WRITE_HEX_UPPER_CASE(true); private final boolean _defaultState; private final int _mask; diff --git a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java index 11a9be21e6..b6854e66da 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonStreamContext.java @@ -303,7 +303,7 @@ public JsonLocation startLocation(ContentReference srcRef) { public JsonLocation getStartLocation(Object srcRef) { return JsonLocation.NA; } - + /** * Overridden to provide developer readable "JsonPath" representation * of the context. diff --git a/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java b/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java index fb42b62561..8d3315c964 100644 --- a/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java +++ b/src/main/java/com/fasterxml/jackson/core/io/CharTypes.java @@ -5,12 +5,16 @@ public final class CharTypes { protected final static char[] HC = "0123456789ABCDEF".toCharArray(); + protected final static char[] HClower = "0123456789abcdef".toCharArray(); protected final static byte[] HB; + protected final static byte[] HBlower; static { int len = HC.length; HB = new byte[len]; + HBlower = new byte[len]; for (int i = 0; i < len; ++i) { HB[i] = (byte) HC[i]; + HBlower[i] = (byte) HClower[i]; } } @@ -242,6 +246,7 @@ public static char hexToChar(int ch) return HC[ch]; } + /** * Helper method for appending JSON-escaped version of contents * into specific {@link StringBuilder}, using default JSON specification @@ -251,8 +256,7 @@ public static char hexToChar(int ch) * * @param content Unescaped String value to append with escaping applied */ - public static void appendQuoted(StringBuilder sb, String content) - { + public static void appendQuoted(StringBuilder sb, String content) { final int[] escCodes = sOutputEscapes128; int escLen = escCodes.length; for (int i = 0, len = content.length(); i < len; ++i) { @@ -285,11 +289,19 @@ public static void appendQuoted(StringBuilder sb, String content) } public static char[] copyHexChars() { - return (char[]) HC.clone(); + return copyHexChars(true); + } + + public static char[] copyHexChars(boolean uppercase) { + return (uppercase) ? HC.clone() : HClower.clone(); } public static byte[] copyHexBytes() { - return (byte[]) HB.clone(); + return copyHexBytes(true); + } + + public static byte[] copyHexBytes(boolean uppercase) { + return (uppercase) ? HB.clone() : HBlower.clone(); } /** diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java index c84e85551b..69e8a12df1 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonGeneratorImpl.java @@ -102,6 +102,14 @@ public abstract class JsonGeneratorImpl extends GeneratorBase */ protected boolean _cfgUnqNames; + + /** + * Write Hex values with uppercase letters + * + * @since 2.14 + */ + protected boolean _cfgWriteHexUppercase; + /* /********************************************************** /* Life-cycle @@ -117,6 +125,7 @@ public JsonGeneratorImpl(IOContext ctxt, int features, ObjectCodec codec) // inlined `setHighestNonEscapedChar()` _maximumNonEscapedChar = 127; } + _cfgWriteHexUppercase = Feature.WRITE_HEX_UPPER_CASE.enabledIn(features); _cfgUnqNames = !Feature.QUOTE_FIELD_NAMES.enabledIn(features); } @@ -143,6 +152,8 @@ public JsonGenerator enable(Feature f) { super.enable(f); if (f == Feature.QUOTE_FIELD_NAMES) { _cfgUnqNames = false; + } else if ( f == Feature.WRITE_HEX_UPPER_CASE) { + _cfgWriteHexUppercase = true; } return this; } @@ -153,6 +164,8 @@ public JsonGenerator disable(Feature f) { super.disable(f); if (f == Feature.QUOTE_FIELD_NAMES) { _cfgUnqNames = true; + } else if ( f == Feature.WRITE_HEX_UPPER_CASE) { + _cfgWriteHexUppercase = false; } return this; } @@ -162,6 +175,7 @@ public JsonGenerator disable(Feature f) { protected void _checkStdFeatureChanges(int newFeatureFlags, int changedFeatures) { super._checkStdFeatureChanges(newFeatureFlags, changedFeatures); _cfgUnqNames = !Feature.QUOTE_FIELD_NAMES.enabledIn(newFeatureFlags); + _cfgWriteHexUppercase = Feature.WRITE_HEX_UPPER_CASE.enabledIn(newFeatureFlags); } @Override diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java index b81adf6d9b..d09f92ca30 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteContext.java @@ -147,7 +147,7 @@ public Object getCurrentValue() { public void setCurrentValue(Object v) { _currentValue = v; } - + /* /********************************************************** /* Factory methods diff --git a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java index 41f6845ce5..a9bf70593e 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java +++ b/src/main/java/com/fasterxml/jackson/core/json/JsonWriteFeature.java @@ -72,6 +72,19 @@ public enum JsonWriteFeature @SuppressWarnings("deprecation") ESCAPE_NON_ASCII(false, JsonGenerator.Feature.ESCAPE_NON_ASCII), + /** + * Feature that specifies that hex values are encoded with capital letters. + *

+ * Can be disabled to have a better possibility to compare between other Json + * writer libraries, such as JSON.stringify from Javascript. + *

+ * Feature is enabled by default. + * + * @since 2.14 + */ + @SuppressWarnings("deprecation") + WRITE_HEX_UPPER_CASE(true, JsonGenerator.Feature.WRITE_HEX_UPPER_CASE), + //23-Nov-2015, tatu: for [core#223], if and when it gets implemented /* * Feature that specifies handling of UTF-8 content that contains diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java index 4d29076f15..ef948c8cce 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -29,12 +29,17 @@ public class UTF8JsonGenerator // intermediate copies only made up to certain length... private final static int MAX_BYTES_TO_BUFFER = 512; - private final static byte[] HEX_CHARS = CharTypes.copyHexBytes(); + private final static byte[] HEX_CHARS_UPPER = CharTypes.copyHexBytes(true); + private final static byte[] HEX_CHARS_LOWER = CharTypes.copyHexBytes(false); private final static byte[] NULL_BYTES = { 'n', 'u', 'l', 'l' }; private final static byte[] TRUE_BYTES = { 't', 'r', 'u', 'e' }; private final static byte[] FALSE_BYTES = { 'f', 'a', 'l', 's', 'e' }; + private byte[] getHexChars() { + return _cfgWriteHexUppercase ? HEX_CHARS_UPPER : HEX_CHARS_LOWER; + } + /* /********************************************************** /* Configuration @@ -2136,6 +2141,7 @@ protected final void _outputSurrogates(int surr1, int surr2) throws IOException */ private final int _outputMultiByteChar(int ch, int outputPtr) throws IOException { + byte[] HEX_CHARS = getHexChars(); byte[] bbuf = _outputBuffer; if (ch >= SURR1_FIRST && ch <= SURR2_LAST) { // yes, outside of BMP; add an escape // 23-Nov-2015, tatu: As per [core#223], may or may not want escapes; @@ -2175,6 +2181,7 @@ private final void _writeNull() throws IOException private int _writeGenericEscape(int charToEscape, int outputPtr) throws IOException { final byte[] bbuf = _outputBuffer; + byte[] HEX_CHARS = getHexChars(); bbuf[outputPtr++] = BYTE_BACKSLASH; bbuf[outputPtr++] = BYTE_u; if (charToEscape > 0xFF) { diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index 49be023971..d09842c7e2 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -19,7 +19,12 @@ public class WriterBasedJsonGenerator { protected final static int SHORT_WRITE = 32; - protected final static char[] HEX_CHARS = CharTypes.copyHexChars(); + protected final static char[] HEX_CHARS_UPPER = CharTypes.copyHexChars(true); + protected final static char[] HEX_CHARS_LOWER = CharTypes.copyHexChars(false); + + private char[] getHexChars() { + return _cfgWriteHexUppercase ? HEX_CHARS_UPPER : HEX_CHARS_LOWER; + } /* /********************************************************** @@ -1814,6 +1819,7 @@ private void _prependOrWriteCharacterEscape(char ch, int escCode) return; } if (escCode != CharacterEscapes.ESCAPE_CUSTOM) { // std, \\uXXXX + char[] HEX_CHARS = getHexChars(); if (_outputTail >= 6) { // fits, prepend to buffer char[] buf = _outputBuffer; int ptr = _outputTail - 6; @@ -1902,6 +1908,7 @@ private int _prependOrWriteCharacterEscape(char[] buffer, int ptr, int end, return ptr; } if (escCode != CharacterEscapes.ESCAPE_CUSTOM) { // std, \\uXXXX + char[] HEX_CHARS = getHexChars(); if (ptr > 5 && ptr < end) { // fits, prepend to buffer ptr -= 6; buffer[ptr++] = '\\'; @@ -1980,6 +1987,7 @@ private void _appendCharacterEscape(char ch, int escCode) } int ptr = _outputTail; char[] buf = _outputBuffer; + char[] HEX_CHARS = getHexChars(); buf[ptr++] = '\\'; buf[ptr++] = 'u'; // We know it's a control char, so only the last 2 chars are non-0 diff --git a/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java b/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java index c780e4f140..dd0aadea35 100644 --- a/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java +++ b/src/test/java/com/fasterxml/jackson/core/write/UTF8GeneratorTest.java @@ -11,6 +11,8 @@ import java.io.ByteArrayOutputStream; +import static com.fasterxml.jackson.core.json.JsonWriteFeature.WRITE_HEX_UPPER_CASE; + public class UTF8GeneratorTest extends BaseTest { private final JsonFactory JSON_F = new JsonFactory(); @@ -114,4 +116,33 @@ public void testFilteringWithEscapedChars() throws Exception assertNull(p.nextToken()); p.close(); } + + public void testHexLowercase() throws Exception + { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + JsonFactory factory = JsonFactory.builder().disable(WRITE_HEX_UPPER_CASE).build(); + JsonGenerator gen = factory.createGenerator(bytes); + String str = "\u001b"; + gen.writeString(str); + gen.flush(); + gen.close(); + + String result = bytes.toString(); + assertEquals("\"\\u001b\"", result); + } + + public void testHexUppercase() throws Exception + { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + JsonFactory factory = JsonFactory.builder().enable(WRITE_HEX_UPPER_CASE).build(); + JsonGenerator gen = factory.createGenerator(bytes); + String str = "\u001b"; + gen.writeString(str); + gen.flush(); + gen.close(); + + String result = bytes.toString(); + assertEquals("\"\\u001B\"", result); + } + }