From 773c77862f1ac85f8970fa27b8b67976f3b52b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Tue, 11 Oct 2016 15:57:35 +0200 Subject: [PATCH 1/2] Add new feature LITERAL_BLOCK_STYLE If enabled will use a literal block style for string containing newlines. All other quotes are unaffected. This make sense in addition to MINIMIZE_QUOTES and ALWAYS_QUOTE_NUMBER_AS_STRINGS as e.g this is safer for string values like "true" (which when quotes are stripped mutate from a string type to a boolean type). It is false by default. --- .../dataformat/yaml/YAMLGenerator.java | 81 +++++++++++-------- .../dataformat/yaml/SimpleGenerationTest.java | 51 +++++++++--- 2 files changed, 84 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index a5c61b4..3a47fef 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -28,7 +28,7 @@ public enum Feature // implements FormatFeature // for 2.7 /** * Whether we are to write an explicit document start marker ("---") * or not. - * + * * @since 2.3 */ WRITE_DOC_START_MARKER(true), @@ -38,17 +38,17 @@ public enum Feature // implements FormatFeature // for 2.7 * or "generic" Object Id mechanism (false). Former works better for systems that * are YAML-centric; latter may be better choice for interoperability, when * converting between formats or accepting other formats. - * + * * @since 2.5 */ USE_NATIVE_OBJECT_ID(true), - + /** * Whether to use YAML native Type Id construct for indicating type (true); * or "generic" type property (false). Former works better for systems that * are YAML-centric; latter may be better choice for interoperability, when * converting between formats or accepting other formats. - * + * * @since 2.5 */ USE_NATIVE_TYPE_ID(true), @@ -80,7 +80,7 @@ public enum Feature // implements FormatFeature // for 2.7 * @since 2.7 */ MINIMIZE_QUOTES(false), - + /** * Whether numbers stored as strings will be rendered with quotes (true) or * without quotes (false, default) when MINIMIZE_QUOTES is enabled. @@ -91,12 +91,21 @@ public enum Feature // implements FormatFeature // for 2.7 * * @since 2.8.2 */ - ALWAYS_QUOTE_NUMBERS_AS_STRINGS(false) + ALWAYS_QUOTE_NUMBERS_AS_STRINGS(false), + + /** + * Whether for string containing newlines a literal block style + * should be used. This automatically enabled when {@link #MINIMIZE_QUOTES} is set. + *

+ * The content of such strinfs is limited to printable characters according to the rules of + * literal block style. + */ + LITERAL_BLOCK_STYLE(false) ; protected final boolean _defaultState; protected final int _mask; - + /** * Method that calculates bit set (flags) of all features that * are enabled by default. @@ -111,14 +120,14 @@ public static int collectDefaults() } return flags; } - + private Feature(boolean defaultState) { _defaultState = defaultState; _mask = (1 << ordinal()); } - + public boolean enabledByDefault() { return _defaultState; } - public boolean enabledIn(int flags) { return (flags & _mask) != 0; } + public boolean enabledIn(int flags) { return (flags & _mask) != 0; } public int getMask() { return _mask; } } @@ -127,7 +136,7 @@ private Feature(boolean defaultState) { /* Internal constants /********************************************************** */ - + protected final static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE; protected final static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE; protected final static Pattern PLAIN_NUMBER_P = Pattern.compile("[0-9]*(\\.[0-9]*)?"); @@ -153,7 +162,7 @@ private Feature(boolean defaultState) { // for field names, leave out quotes private final static Character STYLE_NAME = null; - + // numbers, booleans, should use implicit private final static Character STYLE_SCALAR = null; // Strings quoted for fun @@ -185,7 +194,7 @@ private Feature(boolean defaultState) { * need to output one. */ protected String _typeId; - + /* /********************************************************** /* Life-cycle @@ -203,14 +212,14 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures, _writer = out; _outputOptions = buildDumperOptions(jsonFeatures, yamlFeatures, version); - + _emitter = new Emitter(_writer, _outputOptions); // should we start output now, or try to defer? _emitter.emit(new StreamStartEvent(null, null)); Map noTags = Collections.emptyMap(); - + boolean startMarker = Feature.WRITE_DOC_START_MARKER.enabledIn(yamlFeatures); - + _emitter.emit(new DocumentStartEvent(null, null, startMarker, version, // for 1.10 was: ((version == null) ? null : version.getArray()), noTags)); @@ -232,10 +241,10 @@ protected DumperOptions buildDumperOptions(int jsonFeatures, int yamlFeatures, o return opt; } - /* - /********************************************************** - /* Versioned - /********************************************************** + /* + /********************************************************** + /* Versioned + /********************************************************** */ @Override @@ -291,7 +300,7 @@ public JsonGenerator overrideFormatFeatures(int values, int mask) { _formatFeatures = (_formatFeatures & ~mask) | (values & mask); return this; } - + @Override public boolean canUseSchema(FormatSchema schema) { return false; @@ -330,13 +339,13 @@ public YAMLGenerator configure(Feature f, boolean state) { } return this; } - + /* /********************************************************************** /* Overridden methods; writing field names /********************************************************************** */ - + /* And then methods overridden to make final, streamline some * aspects... */ @@ -389,7 +398,7 @@ public final void flush() throws IOException { _writer.flush(); } - + @Override public void close() throws IOException { @@ -406,7 +415,7 @@ public void close() throws IOException /* Public API: structural output /********************************************************** */ - + @Override public final void writeStartArray() throws IOException { @@ -422,7 +431,7 @@ public final void writeStartArray() throws IOException _emitter.emit(new SequenceStartEvent(anchor, yamlTag, implicit, null, null, style)); } - + @Override public final void writeEndArray() throws IOException { @@ -430,7 +439,7 @@ public final void writeEndArray() throws IOException _reportError("Current context not Array but "+_writeContext.typeDesc()); } // just to make sure we don't "leak" type ids - _typeId = null; + _typeId = null; _writeContext = _writeContext.getParent(); _emitter.emit(new SequenceEndEvent(null, null)); } @@ -458,7 +467,7 @@ public final void writeEndObject() throws IOException _reportError("Current context not Object but "+_writeContext.typeDesc()); } // just to make sure we don't "leak" type ids - _typeId = null; + _typeId = null; _writeContext = _writeContext.getParent(); _emitter.emit(new MappingEndEvent(null, null)); } @@ -488,6 +497,8 @@ public void writeString(String text) throws IOException,JsonGenerationException } else { style = STYLE_PLAIN; } + } else if (Feature.LITERAL_BLOCK_STYLE.enabledIn(_formatFeatures) && text.indexOf('\n') >= 0) { + style = STYLE_LITERAL; } _writeScalar(text, "string", style); } @@ -565,7 +576,7 @@ public void writeRawValue(char[] text, int offset, int len) throws IOException { /* Output method implementations, base64-encoded binary /********************************************************** */ - + @Override public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException { @@ -624,13 +635,13 @@ public void writeNumber(BigInteger v) throws IOException _verifyValueWrite("write number"); _writeScalar(String.valueOf(v.toString()), "java.math.BigInteger", STYLE_SCALAR); } - + @Override public void writeNumber(double d) throws IOException { _verifyValueWrite("write number"); _writeScalar(String.valueOf(d), "double", STYLE_SCALAR); - } + } @Override public void writeNumber(float f) throws IOException @@ -681,14 +692,14 @@ public boolean canWriteObjectId() { // yes, YAML does support Native Type Ids! // 10-Sep-2014, tatu: Except as per [#23] might not want to... return Feature.USE_NATIVE_OBJECT_ID.enabledIn(_formatFeatures); - } + } @Override public boolean canWriteTypeId() { // yes, YAML does support Native Type Ids! // 10-Sep-2014, tatu: Except as per [#22] might not want to... return Feature.USE_NATIVE_TYPE_ID.enabledIn(_formatFeatures); - } + } @Override public void writeTypeId(Object id) @@ -706,7 +717,7 @@ public void writeObjectRef(Object id) AliasEvent evt = new AliasEvent(String.valueOf(id), null, null); _emitter.emit(evt); } - + @Override public void writeObjectId(Object id) throws IOException @@ -749,7 +760,7 @@ protected void _writeScalar(String value, String type, Character style) throws I { _emitter.emit(_scalarEvent(value, style)); } - + protected ScalarEvent _scalarEvent(String value, Character style) { String yamlTag = _typeId; diff --git a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java index 1571fa3..f6c6240 100644 --- a/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java +++ b/src/test/java/com/fasterxml/jackson/dataformat/yaml/SimpleGenerationTest.java @@ -40,7 +40,7 @@ public void testStreamingObject() throws Exception assertEquals("name: \"Brad\"\nage: 39", yaml); gen.close(); } - + public void testStreamingNested() throws Exception { YAMLFactory f = new YAMLFactory(); @@ -54,9 +54,9 @@ public void testStreamingNested() throws Exception gen.writeString("b"); gen.writeEndArray(); gen.writeEndObject(); - + gen.close(); - + String yaml = w.toString(); // note: 1.12 uses more compact notation; 1.10 has prefix @@ -139,7 +139,7 @@ public void testWithFile2() throws Exception assertEquals(1, result.size()); assertEquals("Foobar", result.get("name")); } - + @SuppressWarnings("resource") public void testStartMarker() throws Exception { @@ -190,7 +190,7 @@ public void testSplitLines() throws Exception assertEquals("---\n" + "- \"1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890\"", yaml); - } + } public void testLiteralStringsSingleLine() throws Exception { @@ -237,7 +237,7 @@ public void testQuoteNumberStoredAsString() throws Exception f.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); f.configure(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS, true); - + YAMLMapper mapper = new YAMLMapper(f); Map content = new HashMap(); @@ -246,14 +246,14 @@ public void testQuoteNumberStoredAsString() throws Exception assertEquals("---\n" + "key: \"20\"", yaml); - + content.clear(); content.put("key", "2.0"); yaml = mapper.writeValueAsString(content).trim(); assertEquals("---\n" + "key: \"2.0\"", yaml); - + content.clear(); content.put("key", "2.0.1.2.3"); yaml = mapper.writeValueAsString(content).trim(); @@ -270,7 +270,7 @@ public void testNonQuoteNumberStoredAsString() throws Exception assertFalse(f.isEnabled(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS)); f.configure(YAMLGenerator.Feature.MINIMIZE_QUOTES, true); - + YAMLMapper mapper = new YAMLMapper(f); Map content = new HashMap(); @@ -279,14 +279,14 @@ public void testNonQuoteNumberStoredAsString() throws Exception assertEquals("---\n" + "key: 20", yaml); - + content.clear(); content.put("key", "2.0"); yaml = mapper.writeValueAsString(content).trim(); assertEquals("---\n" + "key: 2.0", yaml); - + content.clear(); content.put("key", "2.0.1.2.3"); yaml = mapper.writeValueAsString(content).trim(); @@ -295,13 +295,38 @@ public void testNonQuoteNumberStoredAsString() throws Exception "key: 2.0.1.2.3", yaml); } + public void testLiteralBlockStyle() throws Exception + { + YAMLFactory f = new YAMLFactory(); + // verify default settings + assertFalse(f.isEnabled(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE)); + + f.configure(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE, true); + + YAMLMapper mapper = new YAMLMapper(f); + + Map content = new HashMap(); + content.put("text", "Hello\nWorld"); + String yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "text: |-\n Hello\n World", yaml); + + content.clear(); + content.put("text", "Hello World"); + yaml = mapper.writeValueAsString(content).trim(); + + assertEquals("---\n" + + "text: \"Hello World\"", yaml); + } + /* /********************************************************************** /* Helper methods /********************************************************************** */ - - + + protected void _writeBradDoc(JsonGenerator gen) throws IOException { gen.writeStartObject(); From 156bc36c913f45a18c701adcd09678ff9cd6b4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roland=20Hu=C3=9F?= Date: Tue, 11 Oct 2016 16:13:09 +0200 Subject: [PATCH 2/2] Fixed typo --- .../com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java index 3a47fef..b73dc68 100644 --- a/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java +++ b/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java @@ -97,7 +97,7 @@ public enum Feature // implements FormatFeature // for 2.7 * Whether for string containing newlines a literal block style * should be used. This automatically enabled when {@link #MINIMIZE_QUOTES} is set. *

- * The content of such strinfs is limited to printable characters according to the rules of + * The content of such strings is limited to printable characters according to the rules of * literal block style. */ LITERAL_BLOCK_STYLE(false)