replace StringBuilder based API with Buffer-based API (providing
separate TextBuffer and BinaryBuffer implementations where appropriate)

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/08386d0e
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/08386d0e
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/08386d0e

Branch: refs/heads/LOG4J2-930
Commit: 08386d0e0dd578fc1d020f24975da54da9f15651
Parents: 8b748ca
Author: rpopma <[email protected]>
Authored: Mon Jan 12 17:42:30 2015 +0900
Committer: rpopma <[email protected]>
Committed: Mon Jan 12 17:42:30 2015 +0900

----------------------------------------------------------------------
 .../log4j/core/layout/PatternLayout.java        |  154 +-
 .../log4j/core/layout/Rfc5424Layout.java        | 1438 +++++++++---------
 .../pattern/AbstractStyleNameConverter.java     |  129 +-
 .../core/pattern/ArrayPatternConverter.java     |    4 +-
 .../core/pattern/ClassNamePatternConverter.java |   36 +-
 .../core/pattern/DatePatternConverter.java      |  187 ++-
 .../core/pattern/EncodingPatternConverter.java  |   95 +-
 .../ExtendedThrowablePatternConverter.java      |   44 +-
 .../core/pattern/FileDatePatternConverter.java  |   14 +-
 .../pattern/FileLocationPatternConverter.java   |   30 +-
 .../log4j/core/pattern/FormattingInfo.java      |   86 +-
 .../pattern/FullLocationPatternConverter.java   |   29 +-
 .../log4j/core/pattern/HighlightConverter.java  |  472 +++---
 .../core/pattern/IntegerPatternConverter.java   |   19 +-
 .../core/pattern/LevelPatternConverter.java     |  102 +-
 .../pattern/LineLocationPatternConverter.java   |   43 +-
 .../pattern/LineSeparatorPatternConverter.java  |   32 +-
 .../core/pattern/LiteralPatternConverter.java   |   38 +-
 .../core/pattern/LogEventPatternConverter.java  |  151 +-
 .../core/pattern/LoggerPatternConverter.java    |   32 +-
 .../log4j/core/pattern/MapPatternConverter.java |   63 +-
 .../core/pattern/MarkerPatternConverter.java    |   26 +-
 .../log4j/core/pattern/MdcPatternConverter.java |   66 +-
 .../core/pattern/MessagePatternConverter.java   |   59 +-
 .../pattern/MethodLocationPatternConverter.java |   29 +-
 .../core/pattern/NamePatternConverter.java      |   39 +-
 .../log4j/core/pattern/NdcPatternConverter.java |   59 +-
 .../core/pattern/RegexReplacementConverter.java |   40 +-
 .../pattern/RelativeTimePatternConverter.java   |   38 +-
 .../pattern/RootThrowablePatternConverter.java  |   42 +-
 .../pattern/SequenceNumberPatternConverter.java |   37 +-
 .../log4j/core/pattern/StyleConverter.java      |   80 +-
 .../core/pattern/ThreadPatternConverter.java    |   42 +-
 .../core/pattern/ThrowablePatternConverter.java |   80 +-
 .../core/pattern/UuidPatternConverter.java      |   28 +-
 .../core/pattern/DatePatternConverterTest.java  |   41 +-
 .../pattern/EncodingPatternConverterTest.java   |    4 +-
 .../ExtendedThrowablePatternConverterTest.java  |   32 +-
 .../log4j/core/pattern/FormattingInfoTest.java  |  142 ++
 .../core/pattern/LevelPatternConverterTest.java |   30 +-
 .../core/pattern/MapPatternConverterTest.java   |    8 +-
 .../pattern/MarkerPatternConverterTest.java     |    4 +-
 .../pattern/MessagePatternConverterTest.java    |   16 +-
 .../log4j/core/pattern/PatternParserTest.java   |    6 +-
 .../log4j/core/pattern/PatternParserTest2.java  |   16 +-
 .../pattern/RegexReplacementConverterTest.java  |    4 +-
 .../RootThrowablePatternConverterTest.java      |    8 +-
 .../pattern/ThrowablePatternConverterTest.java  |   37 +-
 48 files changed, 2479 insertions(+), 1732 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/08386d0e/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index 45079fa..75ce6a4 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -33,10 +33,13 @@ import 
org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.pattern.BinaryBuffer;
 import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
 import org.apache.logging.log4j.core.pattern.PatternFormatter;
 import org.apache.logging.log4j.core.pattern.PatternParser;
 import org.apache.logging.log4j.core.pattern.RegexReplacement;
+import org.apache.logging.log4j.core.pattern.TextBuffer;
+import org.apache.logging.log4j.util.PropertiesUtil;
 
 /**
  * A flexible layout configurable with pattern string.
@@ -58,25 +61,20 @@ public final class PatternLayout extends 
AbstractStringLayout {
     private static final long serialVersionUID = 1L;
 
     /**
-     * Default pattern string for log output. Currently set to the
-     * string <b>"%m%n"</b> which just prints the application supplied
-     * message.
+     * Default pattern string for log output. Currently set to the string 
<b>"%m%n"</b> which just prints the
+     * application supplied message.
      */
     public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
 
     /**
-     * A conversion pattern equivalent to the TTCCCLayout.
-     * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
+     * A conversion pattern equivalent to the TTCCCLayout. Current value is 
<b>%r [%t] %p %c %x - %m%n</b>.
      */
-    public static final String TTCC_CONVERSION_PATTERN =
-        "%r [%t] %p %c %x - %m%n";
+    public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - 
%m%n";
 
     /**
-     * A simple pattern.
-     * Current value is <b>%d [%t] %p %c - %m%n</b>.
+     * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>.
      */
-    public static final String SIMPLE_CONVERSION_PATTERN =
-        "%d [%t] %p %c - %m%n";
+    public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - 
%m%n";
 
     /** Key to identify pattern converters. */
     public static final String KEY = "Converter";
@@ -84,14 +82,14 @@ public final class PatternLayout extends 
AbstractStringLayout {
     /**
      * Initial converter for pattern.
      */
-    private final List<PatternFormatter> formatters;
+    // store in raw array instead of a collection for performance reasons
+    private final PatternFormatter[] formatters;
 
     /**
      * Conversion pattern.
      */
     private final String conversionPattern;
 
-
     /**
      * The current Configuration.
      */
@@ -103,22 +101,24 @@ public final class PatternLayout extends 
AbstractStringLayout {
 
     private final boolean noConsoleNoAnsi;
 
+    private final boolean supportsBinaryOutput;
+
     /**
-     * Constructs a EnhancedPatternLayout using the supplied conversion 
pattern.
+     * Constructs a PatternLayout using the supplied conversion pattern.
      *
      * @param config The Configuration.
      * @param replace The regular expression to match.
      * @param pattern conversion pattern.
      * @param charset The character set.
      * @param alwaysWriteExceptions Whether or not exceptions should always be 
handled in this pattern (if {@code true},
-     *                         exceptions will be written even if the pattern 
does not specify so).
-     * @param noConsoleNoAnsi
-     *            If {@code "true"} (default) and {@link System#console()} is 
null, do not output ANSI escape codes
+     *            exceptions will be written even if the pattern does not 
specify so).
+     * @param noConsoleNoAnsi If {@code "true"} (default) and {@link 
System#console()} is null, do not output ANSI
+     *            escape codes
      * @param header
      */
     private PatternLayout(final Configuration config, final RegexReplacement 
replace, final String pattern,
-                          final Charset charset, final boolean 
alwaysWriteExceptions, final boolean noConsoleNoAnsi,
-                          final String header, final String footer) {
+            final Charset charset, final boolean alwaysWriteExceptions, final 
boolean noConsoleNoAnsi,
+            final String header, final String footer) {
         super(charset, toBytes(header, charset), toBytes(footer, charset));
         this.replace = replace;
         this.conversionPattern = pattern;
@@ -126,7 +126,17 @@ public final class PatternLayout extends 
AbstractStringLayout {
         this.alwaysWriteExceptions = alwaysWriteExceptions;
         this.noConsoleNoAnsi = noConsoleNoAnsi;
         final PatternParser parser = createPatternParser(config);
-        this.formatters = parser.parse(pattern == null ? 
DEFAULT_CONVERSION_PATTERN : pattern, this.alwaysWriteExceptions, 
this.noConsoleNoAnsi);
+        final List<PatternFormatter> list = parser.parse(pattern == null ? 
DEFAULT_CONVERSION_PATTERN : pattern,
+                this.alwaysWriteExceptions, this.noConsoleNoAnsi);
+        this.formatters = (PatternFormatter[]) list.toArray(new 
PatternFormatter[list.size()]);
+        this.supportsBinaryOutput = replace == null && 
supportBinaryOutput(formatters);
+        for (PatternFormatter patternFormatter : list) {
+            patternFormatter.getConverter().setCharset(charset);
+        }
+    }
+
+    private static boolean supportBinaryOutput(final PatternFormatter[] 
formatters) {
+        return 
PropertiesUtil.getProperties().getBooleanProperty("log4j.use.BinaryBuffers", 
true);
     }
 
     private static byte[] toBytes(final String str, final Charset charset) {
@@ -136,7 +146,7 @@ public final class PatternLayout extends 
AbstractStringLayout {
         return null;
     }
 
-    private byte[] strSubstitutorReplace(final byte... b) {
+    private byte[] strSubstitutorReplace(final byte[] b) {
         if (b != null && config != null) {
             return getBytes(config.getStrSubstitutor().replace(new String(b, 
getCharset())));
         }
@@ -173,14 +183,20 @@ public final class PatternLayout extends 
AbstractStringLayout {
      * @return Map of content format keys supporting PatternLayout
      */
     @Override
-    public Map<String, String> getContentFormat()
-    {
+    public Map<String, String> getContentFormat() {
         final Map<String, String> result = new HashMap<String, String>();
         result.put("structured", "false");
         result.put("formatType", "conversion");
         result.put("format", conversionPattern);
         return result;
     }
+    
+    private ThreadLocal<TextBuffer> textBuffer = new ThreadLocal<TextBuffer>() 
{
+        @Override
+        protected TextBuffer initialValue() {
+            return new TextBuffer();
+        }
+    };
 
     /**
      * Formats a logging event to a writer.
@@ -191,9 +207,12 @@ public final class PatternLayout extends 
AbstractStringLayout {
      */
     @Override
     public String toSerializable(final LogEvent event) {
-        final StringBuilder buf = new StringBuilder();
-        for (final PatternFormatter formatter : formatters) {
-            formatter.format(event, buf);
+        final TextBuffer buf = textBuffer.get();
+        buf.setLength(0);
+        
+        // PERFORMANCE-SENSITIVE CODE: do not refactor to for-each loop
+        for (int i = 0, len = formatters.length; i < len; i++) {
+            formatters[i].format(event, buf);
         }
         String str = buf.toString();
         if (replace != null) {
@@ -201,9 +220,39 @@ public final class PatternLayout extends 
AbstractStringLayout {
         }
         return str;
     }
+    
+    private ThreadLocal<BinaryBuffer> binaryBuffer = new 
ThreadLocal<BinaryBuffer>() {
+        @Override
+        protected BinaryBuffer initialValue() {
+            return new BinaryBuffer(getCharset());
+        }
+    };
+
+    /**
+     * Formats the Log Event as a byte array.
+     *
+     * @param event The Log Event.
+     * @return The formatted event as a byte array.
+     */
+    @Override
+    public byte[] toByteArray(final LogEvent event) {
+        if (!supportsBinaryOutput) {
+            return getBytes(toSerializable(event));
+        }
+        final Charset charset = getCharset();
+        final BinaryBuffer buf = binaryBuffer.get();
+        buf.setLength(0);
+
+        // PERFORMANCE-SENSITIVE CODE: do not refactor to for-each loop
+        for (int i = 0, len = formatters.length; i < len; i++) {
+            formatters[i].format(event, buf, charset);
+        }
+        return buf.toByteArray();
+    }
 
     /**
      * Create a PatternParser.
+     * 
      * @param config The Configuration.
      * @return The PatternParser.
      */
@@ -228,22 +277,16 @@ public final class PatternLayout extends 
AbstractStringLayout {
     /**
      * Create a pattern layout.
      *
-     * @param pattern
-     *        The pattern. If not specified, defaults to 
DEFAULT_CONVERSION_PATTERN.
-     * @param config
-     *        The Configuration. Some Converters require access to the 
Interpolator.
-     * @param replace
-     *        A Regex replacement String.
-     * @param charset
-     *        The character set.
-     * @param alwaysWriteExceptions
-     *        If {@code "true"} (default) exceptions are always written even 
if the pattern contains no exception tokens.
-     * @param noConsoleNoAnsi
-     *        If {@code "true"} (default is false) and {@link 
System#console()} is null, do not output ANSI escape codes
-     * @param header
-     *        The footer to place at the top of the document, once.
-     * @param footer
-     *        The footer to place at the bottom of the document, once.
+     * @param pattern The pattern. If not specified, defaults to 
DEFAULT_CONVERSION_PATTERN.
+     * @param config The Configuration. Some Converters require access to the 
Interpolator.
+     * @param replace A Regex replacement String.
+     * @param charset The character set.
+     * @param alwaysWriteExceptions If {@code "true"} (default) exceptions are 
always written even if the pattern
+     *            contains no exception tokens.
+     * @param noConsoleNoAnsi If {@code "true"} (default is false) and {@link 
System#console()} is null, do not output
+     *            ANSI escape codes
+     * @param header The footer to place at the top of the document, once.
+     * @param footer The footer to place at the bottom of the document, once.
      * @return The PatternLayout.
      */
     @PluginFactory
@@ -251,21 +294,14 @@ public final class PatternLayout extends 
AbstractStringLayout {
             @PluginAttribute(value = "pattern", defaultString = 
DEFAULT_CONVERSION_PATTERN) final String pattern,
             @PluginConfiguration final Configuration config,
             @PluginElement("Replace") final RegexReplacement replace,
-            @PluginAttribute(value = "charset", defaultString = "UTF-8") final 
Charset charset,
+            /* Cannot do defaultString = Charset.defaultCharset().name() for 
charset... */
+            @PluginAttribute(value = "charset") final Charset charset,
             @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = 
true) final boolean alwaysWriteExceptions,
             @PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = 
false) final boolean noConsoleNoAnsi,
-            @PluginAttribute("header") final String header,
-            @PluginAttribute("footer") final String footer) {
-        return newBuilder()
-            .withPattern(pattern)
-            .withConfiguration(config)
-            .withRegexReplacement(replace)
-            .withCharset(charset)
-            .withAlwaysWriteExceptions(alwaysWriteExceptions)
-            .withNoConsoleNoAnsi(noConsoleNoAnsi)
-            .withHeader(header)
-            .withFooter(footer)
-            .build();
+            @PluginAttribute("header") final String header, 
@PluginAttribute("footer") final String footer) {
+        return 
newBuilder().withPattern(pattern).withConfiguration(config).withRegexReplacement(replace)
+                
.withCharset(charset).withAlwaysWriteExceptions(alwaysWriteExceptions)
+                
.withNoConsoleNoAnsi(noConsoleNoAnsi).withHeader(header).withFooter(footer).build();
     }
 
     /**
@@ -281,6 +317,7 @@ public final class PatternLayout extends 
AbstractStringLayout {
 
     /**
      * Creates a builder for a custom PatternLayout.
+     * 
      * @return a PatternLayout builder.
      */
     @PluginBuilderFactory
@@ -331,7 +368,6 @@ public final class PatternLayout extends 
AbstractStringLayout {
             return this;
         }
 
-
         public Builder withConfiguration(final Configuration configuration) {
             this.configuration = configuration;
             return this;
@@ -343,7 +379,9 @@ public final class PatternLayout extends 
AbstractStringLayout {
         }
 
         public Builder withCharset(final Charset charset) {
-            this.charset = charset;
+            if (charset != null) {
+                this.charset = charset; // else use platform default (NOT 
utf-8!)
+            }
             return this;
         }
 
@@ -374,7 +412,7 @@ public final class PatternLayout extends 
AbstractStringLayout {
                 configuration = new DefaultConfiguration();
             }
             return new PatternLayout(configuration, regexReplacement, pattern, 
charset, alwaysWriteExceptions,
-                noConsoleNoAnsi, header, footer);
+                    noConsoleNoAnsi, header, footer);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/08386d0e/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
index 340c5b3..8aa7ab1 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
@@ -1,718 +1,720 @@
-/*
- * 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.core.layout;
-
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LoggingException;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.net.Facility;
-import org.apache.logging.log4j.core.net.Priority;
-import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternFormatter;
-import org.apache.logging.log4j.core.pattern.PatternParser;
-import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
-import org.apache.logging.log4j.core.util.Charsets;
-import org.apache.logging.log4j.core.util.NetUtils;
-import org.apache.logging.log4j.core.util.Patterns;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.StructuredDataId;
-import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.apache.logging.log4j.util.Strings;
-
-
-/**
- * Formats a log event in accordance with RFC 5424.
- *
- * @see <a href="https://tools.ietf.org/html/rfc5424";>RFC 5424</a>
- */
-@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = 
Layout.ELEMENT_TYPE, printObject = true)
-public final class Rfc5424Layout extends AbstractStringLayout {
-
-    private static final long serialVersionUID = 1L;
-
-    private static final String LF = "\n";
-
-    /**
-     * Not a very good default - it is the Apache Software Foundation's 
enterprise number.
-     */
-    public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
-    /**
-     * The default event id.
-     */
-    public static final String DEFAULT_ID = "Audit";
-    /**
-     * Match newlines in a platform-independent manner.
-     */
-    public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
-    /**
-     * Match characters which require escaping
-     */
-    public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = 
Pattern.compile("[\\\"\\]\\\\]");
-
-    public static final String DEFAULT_MDCID = "mdc";
-    private static final int TWO_DIGITS = 10;
-    private static final int THREE_DIGITS = 100;
-    private static final int MILLIS_PER_MINUTE = 60000;
-    private static final int MINUTES_PER_HOUR = 60;
-
-    private static final String COMPONENT_KEY = "RFC5424-Converter";
-
-    private final Facility facility;
-    private final String defaultId;
-    private final int enterpriseNumber;
-    private final boolean includeMdc;
-    private final String mdcId;
-    private final StructuredDataId mdcSdId;
-    private final String localHostName;
-    private final String appName;
-    private final String messageId;
-    private final String configName;
-    private final String mdcPrefix;
-    private final String eventPrefix;
-    private final List<String> mdcExcludes;
-    private final List<String> mdcIncludes;
-    private final List<String> mdcRequired;
-    private final ListChecker checker;
-    private final ListChecker noopChecker = new NoopChecker();
-    private final boolean includeNewLine;
-    private final String escapeNewLine;
-    private final boolean useTlsMessageFormat;
-
-    private long lastTimestamp = -1;
-    private String timestamppStr;
-
-    private final List<PatternFormatter> exceptionFormatters;
-    private final Map<String,  FieldFormatter> fieldFormatters;
-
-    private Rfc5424Layout(final Configuration config, final Facility facility, 
final String id, final int ein,
-                          final boolean includeMDC, final boolean includeNL, 
final String escapeNL, final String mdcId,
-                          final String mdcPrefix, final String eventPrefix,
-                          final String appName, final String messageId, final 
String excludes, final String includes,
-                          final String required, final Charset charset, final 
String exceptionPattern,
-                          final boolean useTLSMessageFormat, final 
LoggerFields[] loggerFields) {
-        super(charset);
-        final PatternParser exceptionParser = createPatternParser(config, 
ThrowablePatternConverter.class);
-        exceptionFormatters = exceptionPattern == null ? null : 
exceptionParser.parse(exceptionPattern, false, false);
-        this.facility = facility;
-        this.defaultId = id == null ? DEFAULT_ID : id;
-        this.enterpriseNumber = ein;
-        this.includeMdc = includeMDC;
-        this.includeNewLine = includeNL;
-        this.escapeNewLine = escapeNL == null ? null : 
Matcher.quoteReplacement(escapeNL);
-        this.mdcId = mdcId;
-        this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, 
null);
-        this.mdcPrefix = mdcPrefix;
-        this.eventPrefix = eventPrefix;
-        this.appName = appName;
-        this.messageId = messageId;
-        this.useTlsMessageFormat = useTLSMessageFormat;
-        this.localHostName = NetUtils.getLocalHostname();
-        ListChecker c = null;
-        if (excludes != null) {
-            final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
-            if (array.length > 0) {
-                c = new ExcludeChecker();
-                mdcExcludes = new ArrayList<String>(array.length);
-                for (final String str : array) {
-                    mdcExcludes.add(str.trim());
-                }
-            } else {
-                mdcExcludes = null;
-            }
-        } else {
-            mdcExcludes = null;
-        }
-        if (includes != null) {
-            final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
-            if (array.length > 0) {
-                c = new IncludeChecker();
-                mdcIncludes = new ArrayList<String>(array.length);
-                for (final String str : array) {
-                    mdcIncludes.add(str.trim());
-                }
-            } else {
-                mdcIncludes = null;
-            }
-        } else {
-            mdcIncludes = null;
-        }
-        if (required != null) {
-            final String[] array = required.split(Patterns.COMMA_SEPARATOR);
-            if (array.length > 0) {
-                mdcRequired = new ArrayList<String>(array.length);
-                for (final String str : array) {
-                    mdcRequired.add(str.trim());
-                }
-            } else {
-                mdcRequired = null;
-            }
-
-        } else {
-            mdcRequired = null;
-        }
-        this.checker = c != null ? c : noopChecker;
-        final String name = config == null ? null : config.getName();
-        configName = name != null && name.length() > 0 ? name : null;
-        this.fieldFormatters = createFieldFormatters(loggerFields, config);
-    }
-
-    private Map<String, FieldFormatter> createFieldFormatters(final 
LoggerFields[] loggerFields,
-            final Configuration config) {
-        final Map<String, FieldFormatter> sdIdMap = new HashMap<String, 
FieldFormatter>();
-
-        if (loggerFields != null) {
-            for (final LoggerFields lField : loggerFields) {
-                final StructuredDataId key = lField.getSdId() == null ? 
mdcSdId : lField.getSdId();
-                final Map<String, List<PatternFormatter>> sdParams = new 
HashMap<String, List<PatternFormatter>>();
-                final Map<String, String> fields = lField.getMap();
-                if (!fields.isEmpty()) {
-                    final PatternParser fieldParser = 
createPatternParser(config, null);
-
-                    for (final Map.Entry<String, String> entry : 
fields.entrySet()) {
-                        final List<PatternFormatter> formatters = 
fieldParser.parse(entry.getValue(), false, false);
-                        sdParams.put(entry.getKey(), formatters);
-                    }
-                    final FieldFormatter fieldFormatter = new 
FieldFormatter(sdParams,
-                            lField.getDiscardIfAllFieldsAreEmpty());
-                    sdIdMap.put(key.toString(), fieldFormatter);
-                }
-            }
-        }
-        return sdIdMap.size() > 0 ? sdIdMap : null;
-    }
-
-    /**
-     * Create a PatternParser.
-     *
-     * @param config The Configuration.
-     * @param filterClass Filter the returned plugins after calling the plugin 
manager.
-     * @return The PatternParser.
-     */
-    private static PatternParser createPatternParser(final Configuration 
config,
-            final Class<? extends PatternConverter> filterClass) {
-        if (config == null) {
-            return new PatternParser(config, PatternLayout.KEY, 
LogEventPatternConverter.class, filterClass);
-        }
-        PatternParser parser = config.getComponent(COMPONENT_KEY);
-        if (parser == null) {
-            parser = new PatternParser(config, PatternLayout.KEY, 
ThrowablePatternConverter.class);
-            config.addComponent(COMPONENT_KEY, parser);
-            parser = (PatternParser) config.getComponent(COMPONENT_KEY);
-        }
-        return parser;
-    }
-
-    /**
-     * Gets this Rfc5424Layout's content format. Specified by:
-     * <ul>
-     * <li>Key: "structured" Value: "true"</li>
-     * <li>Key: "format" Value: "RFC5424"</li>
-     * </ul>
-     * 
-     * @return Map of content format keys supporting Rfc5424Layout
-     */
-    @Override
-    public Map<String, String> getContentFormat() {
-        final Map<String, String> result = new HashMap<String, String>();
-        result.put("structured", "true");
-        result.put("formatType", "RFC5424");
-        return result;
-    }
-
-    /**
-     * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance 
with the RFC 5424 Syslog specification.
-     *
-     * @param event The LogEvent.
-     * @return The RFC 5424 String representation of the LogEvent.
-     */
-    @Override
-    public String toSerializable(final LogEvent event) {
-        final StringBuilder buf = new StringBuilder();
-        appendPriority(buf, event.getLevel());
-        appendTimestamp(buf, event.getTimeMillis());
-        appendSpace(buf);
-        appendHostName(buf);
-        appendSpace(buf);
-        appendAppName(buf);
-        appendSpace(buf);
-        appendProcessId(buf);
-        appendSpace(buf);
-        appendMessageId(buf, event.getMessage());
-        appendSpace(buf);
-        appendStructuredElements(buf, event);
-        appendMessage(buf, event);
-        if (useTlsMessageFormat) {
-            return new TlsSyslogFrame(buf.toString()).toString();
-        }
-        return buf.toString();
-    }
-
-    private void appendPriority(final StringBuilder buffer, final Level 
logLevel) {
-        buffer.append('<');
-        buffer.append(Priority.getPriority(facility, logLevel));
-        buffer.append(">1 ");
-    }
-
-    private void appendTimestamp(final StringBuilder buffer, final long 
milliseconds)  {
-        buffer.append(computeTimeStampString(milliseconds));
-    }
-
-    private void appendSpace(final StringBuilder buffer) {
-        buffer.append(' ');
-    }
-
-    private void appendHostName(final StringBuilder buffer) {
-        buffer.append(localHostName);
-    }
-
-    private void appendAppName(final StringBuilder buffer) {
-        if (appName != null) {
-            buffer.append(appName);
-        } else if (configName != null) {
-            buffer.append(configName);
-        } else {
-            buffer.append('-');
-        }
-    }
-
-    private void appendProcessId(final StringBuilder buffer) {
-        buffer.append(getProcId());
-    }
-
-    private void appendMessageId(final StringBuilder buffer, final Message 
message) {
-        final boolean isStructured = message instanceof StructuredDataMessage;
-        final String type = isStructured ? ((StructuredDataMessage) 
message).getType() : null;
-        if (type != null) {
-            buffer.append(type);
-        } else if (messageId != null) {
-            buffer.append(messageId);
-        } else {
-            buffer.append('-');
-        }
-    }
-
-    private void appendMessage(final StringBuilder buffer, final LogEvent 
event) {
-        final Message message = event.getMessage();
-        // This layout formats StructuredDataMessages instead of delegating to 
the Message itself.
-        final String text = (message instanceof StructuredDataMessage) ? 
message.getFormat() : message.getFormattedMessage();
-
-        if (text != null && text.length() > 0) {
-            buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
-        }
-
-        if (exceptionFormatters != null && event.getThrown() != null) {
-            final StringBuilder exception = new StringBuilder(LF);
-            for (final PatternFormatter formatter : exceptionFormatters) {
-                formatter.format(event, exception);
-            }
-            buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
-        }
-        if (includeNewLine) {
-            buffer.append(LF);
-        }
-    }
-
-    private void appendStructuredElements(final StringBuilder buffer, final 
LogEvent event) {
-        final Message message = event.getMessage();
-        final boolean isStructured = message instanceof StructuredDataMessage;
-
-        if (!isStructured && (fieldFormatters!= null && 
fieldFormatters.isEmpty()) && !includeMdc) {
-            buffer.append('-');
-            return;
-        }
-
-        final Map<String, StructuredDataElement> sdElements = new 
HashMap<String, StructuredDataElement>();
-        final Map<String, String> contextMap = event.getContextMap();
-
-        if (mdcRequired != null) {
-            checkRequired(contextMap);
-        }
-
-        if (fieldFormatters != null) {
-            for (final Map.Entry<String, FieldFormatter> sdElement: 
fieldFormatters.entrySet()) {
-                final String sdId = sdElement.getKey();
-                final StructuredDataElement elem = 
sdElement.getValue().format(event);
-                sdElements.put(sdId, elem);
-            }
-        }
-
-        if (includeMdc && contextMap.size() > 0) {
-            if (sdElements.containsKey(mdcSdId.toString())) {
-                final StructuredDataElement union = 
sdElements.get(mdcSdId.toString());
-                union.union(contextMap);
-                sdElements.put(mdcSdId.toString(), union);
-            } else {
-                final StructuredDataElement formattedContextMap = new 
StructuredDataElement(contextMap, false);
-                sdElements.put(mdcSdId.toString(), formattedContextMap);
-            }
-        }
-
-        if (isStructured) {
-            final StructuredDataMessage data = (StructuredDataMessage) message;
-            final Map<String, String> map = data.getData();
-            final StructuredDataId id = data.getId();
-            final String sdId = getId(id);
-
-            if (sdElements.containsKey(sdId)) {
-                final StructuredDataElement union = 
sdElements.get(id.toString());
-                union.union(map);
-                sdElements.put(sdId, union);
-            } else {
-                final StructuredDataElement formattedData = new 
StructuredDataElement(map, false);
-                sdElements.put(sdId, formattedData);
-            }
-        }
-
-        if (sdElements.isEmpty()) {
-            buffer.append('-');
-            return;
-        }
-
-        for (final Map.Entry<String, StructuredDataElement> entry: 
sdElements.entrySet()) {
-            formatStructuredElement(entry.getKey(), mdcPrefix, 
entry.getValue(), buffer, checker);
-        }
-    }
-
-    private String escapeNewlines(final String text, final String 
escapeNewLine) {
-        if (null == escapeNewLine) {
-            return text;
-        }
-        return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
-    }
-
-    protected String getProcId() {
-        return "-";
-    }
-
-    protected List<String> getMdcExcludes() {
-        return mdcExcludes;
-    }
-
-    protected List<String> getMdcIncludes() {
-        return mdcIncludes;
-    }
-
-    private String computeTimeStampString(final long now) {
-        long last;
-        synchronized (this) {
-            last = lastTimestamp;
-            if (now == lastTimestamp) {
-                return timestamppStr;
-            }
-        }
-
-        final StringBuilder buffer = new StringBuilder();
-        final Calendar cal = new GregorianCalendar();
-        cal.setTimeInMillis(now);
-        buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
-        buffer.append('-');
-        pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
-        buffer.append('-');
-        pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
-        buffer.append('T');
-        pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
-        buffer.append(':');
-        pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
-        buffer.append(':');
-        pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
-
-        final int millis = cal.get(Calendar.MILLISECOND);
-        if (millis != 0) {
-            buffer.append('.');
-            pad(millis, THREE_DIGITS, buffer);
-        }
-
-        int tzmin = (cal.get(Calendar.ZONE_OFFSET) + 
cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
-        if (tzmin == 0) {
-            buffer.append('Z');
-        } else {
-            if (tzmin < 0) {
-                tzmin = -tzmin;
-                buffer.append('-');
-            } else {
-                buffer.append('+');
-            }
-            final int tzhour = tzmin / MINUTES_PER_HOUR;
-            tzmin -= tzhour * MINUTES_PER_HOUR;
-            pad(tzhour, TWO_DIGITS, buffer);
-            buffer.append(':');
-            pad(tzmin, TWO_DIGITS, buffer);
-        }
-        synchronized (this) {
-            if (last == lastTimestamp) {
-                lastTimestamp = now;
-                timestamppStr = buffer.toString();
-            }
-        }
-        return buffer.toString();
-    }
-
-    private void pad(final int val, int max, final StringBuilder buf) {
-        while (max > 1) {
-            if (val < max) {
-                buf.append('0');
-            }
-            max = max / TWO_DIGITS;
-        }
-        buf.append(Integer.toString(val));
-    }
-
-    private void formatStructuredElement(final String id, final String prefix, 
final StructuredDataElement data,
-                                         final StringBuilder sb, final 
ListChecker checker) {
-        if ((id == null && defaultId == null) || data.discard()) {
-            return;
-        }
-
-        sb.append('[');
-        sb.append(id);
-        if (!mdcSdId.toString().equals(id)) {
-            appendMap(prefix, data.getFields(), sb, noopChecker);
-        } else {
-            appendMap(prefix, data.getFields(), sb, checker);
-        }
-        sb.append(']');
-    }
-
-    private String getId(final StructuredDataId id) {
-        final StringBuilder sb = new StringBuilder();
-        if (id == null || id.getName() == null) {
-            sb.append(defaultId);
-        } else {
-            sb.append(id.getName());
-        }
-        int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
-        if (ein < 0) {
-            ein = enterpriseNumber;
-        }
-        if (ein >= 0) {
-            sb.append('@').append(ein);
-        }
-        return sb.toString();
-    }
-
-    private void checkRequired(final Map<String, String> map) {
-        for (final String key : mdcRequired) {
-            final String value = map.get(key);
-            if (value == null) {
-                throw new LoggingException("Required key " + key + " is 
missing from the " + mdcId);
-            }
-        }
-    }
-
-    private void appendMap(final String prefix, final Map<String, String> map, 
final StringBuilder sb,
-                           final ListChecker checker) {
-        final SortedMap<String, String> sorted = new TreeMap<String, 
String>(map);
-        for (final Map.Entry<String, String> entry : sorted.entrySet()) {
-            if (checker.check(entry.getKey()) && entry.getValue() != null) {
-                sb.append(' ');
-                if (prefix != null) {
-                    sb.append(prefix);
-                }
-                sb.append(escapeNewlines(escapeSDParams(entry.getKey()), 
escapeNewLine)).append("=\"")
-                    .append(escapeNewlines(escapeSDParams(entry.getValue()), 
escapeNewLine)).append('"');
-            }
-        }
-    }
-
-    private String escapeSDParams(final String value) {
-        return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
-    }
-
-    /**
-     * Interface used to check keys in a Map.
-     */
-    private interface ListChecker {
-        boolean check(String key);
-    }
-
-    /**
-     * Includes only the listed keys.
-     */
-    private class IncludeChecker implements ListChecker {
-        @Override
-        public boolean check(final String key) {
-            return mdcIncludes.contains(key);
-        }
-    }
-
-    /**
-     * Excludes the listed keys.
-     */
-    private class ExcludeChecker implements ListChecker {
-        @Override
-        public boolean check(final String key) {
-            return !mdcExcludes.contains(key);
-        }
-    }
-
-    /**
-     * Does nothing.
-     */
-    private class NoopChecker implements ListChecker {
-        @Override
-        public boolean check(final String key) {
-            return true;
-        }
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("facility=").append(facility.name());
-        sb.append(" appName=").append(appName);
-        sb.append(" defaultId=").append(defaultId);
-        sb.append(" enterpriseNumber=").append(enterpriseNumber);
-        sb.append(" newLine=").append(includeNewLine);
-        sb.append(" includeMDC=").append(includeMdc);
-        sb.append(" messageId=").append(messageId);
-        return sb.toString();
-    }
-
-    /**
-     * Create the RFC 5424 Layout.
-     *
-     * @param facility         The Facility is used to try to classify the 
message.
-     * @param id               The default structured data id to use when 
formatting according to RFC 5424.
-     * @param enterpriseNumber The IANA enterprise number.
-     * @param includeMDC       Indicates whether data from the 
ThreadContextMap will be included in the RFC 5424 Syslog
-     *                         record. Defaults to "true:.
-     * @param mdcId            The id to use for the MDC Structured Data 
Element.
-     * @param mdcPrefix        The prefix to add to MDC key names.
-     * @param eventPrefix      The prefix to add to event key names.
-     * @param newLine          If true, a newline will be appended to the end 
of the syslog record. The default is false.
-     * @param escapeNL         String that should be used to replace newlines 
within the message text.
-     * @param appName          The value to use as the APP-NAME in the RFC 
5424 syslog record.
-     * @param msgId            The default value to be used in the MSGID field 
of RFC 5424 syslog records.
-     * @param excludes         A comma separated list of MDC keys that should 
be excluded from the LogEvent.
-     * @param includes         A comma separated list of MDC keys that should 
be included in the FlumeEvent.
-     * @param required         A comma separated list of MDC keys that must be 
present in the MDC.
-     * @param exceptionPattern The pattern for formatting exceptions.
-     * @param useTlsMessageFormat If true the message will be formatted 
according to RFC 5425.
-     * @param loggerFields     Container for the KeyValuePairs containing the 
patterns
-     * @param config           The Configuration. Some Converters require 
access to the Interpolator.
-     * @return An Rfc5424Layout.
-     */
-    @PluginFactory
-    public static Rfc5424Layout createLayout(
-            @PluginAttribute(value = "facility", defaultString = "LOCAL0") 
final Facility facility,
-            @PluginAttribute("id") final String id,
-            @PluginAttribute(value = "enterpriseNumber", defaultInt = 
DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
-            @PluginAttribute(value = "includeMDC", defaultBoolean = true) 
final boolean includeMDC,
-            @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) 
final String mdcId,
-            @PluginAttribute("mdcPrefix") final String mdcPrefix,
-            @PluginAttribute("eventPrefix") final String eventPrefix,
-            @PluginAttribute(value = "newLine", defaultBoolean = false) final 
boolean newLine,
-            @PluginAttribute("newLineEscape") final String escapeNL,
-            @PluginAttribute("appName") final String appName,
-            @PluginAttribute("messageId") final String msgId,
-            @PluginAttribute("mdcExcludes") final String excludes,
-            @PluginAttribute("mdcIncludes") String includes,
-            @PluginAttribute("mdcRequired") final String required,
-            @PluginAttribute("exceptionPattern") final String exceptionPattern,
-            @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = 
false) final boolean useTlsMessageFormat, // RFC 5425
-            @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
-            @PluginConfiguration final Configuration config) {
-        final Charset charset = Charsets.UTF_8;
-        if (includes != null && excludes != null) {
-            LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. 
Includes wil be ignored");
-            includes = null;
-        }
-
-        return new Rfc5424Layout(config, facility, id, enterpriseNumber, 
includeMDC, newLine, escapeNL, mdcId, mdcPrefix,
-                eventPrefix, appName, msgId, excludes, includes, required, 
charset, exceptionPattern,
-                useTlsMessageFormat, loggerFields);
-    }
-
-    private class FieldFormatter {
-
-        private final Map<String, List<PatternFormatter>> delegateMap;
-        private final boolean discardIfEmpty;
-
-        public FieldFormatter(final Map<String, List<PatternFormatter>> 
fieldMap, final boolean discardIfEmpty) {
-            this.discardIfEmpty = discardIfEmpty;
-            this.delegateMap = fieldMap;
-        }
-
-        public StructuredDataElement format(final LogEvent event) {
-            final Map<String, String> map = new HashMap<String, String>();
-
-            for (final Map.Entry<String, List<PatternFormatter>> entry : 
delegateMap.entrySet()) {
-                final StringBuilder buffer = new StringBuilder();
-                for (final PatternFormatter formatter : entry.getValue()) {
-                    formatter.format(event, buffer);
-                }
-                map.put(entry.getKey(), buffer.toString());
-            }
-            return new StructuredDataElement(map, discardIfEmpty);
-        }
-    }
-
-    private class StructuredDataElement {
-
-        private final Map<String, String> fields;
-        private final boolean discardIfEmpty;
-
-        public StructuredDataElement(final Map<String, String> fields, final 
boolean discardIfEmpty) {
-            this.discardIfEmpty = discardIfEmpty;
-            this.fields = fields;
-        }
-
-        boolean discard() {
-            if (discardIfEmpty == false) {
-                return false;
-            }
-            boolean foundNotEmptyValue = false;
-            for (final Map.Entry<String, String> entry : fields.entrySet()) {
-                if (Strings.isNotEmpty(entry.getValue())) {
-                    foundNotEmptyValue = true;
-                    break;
-                }
-            }
-            return !foundNotEmptyValue;
-        }
-
-        void union(final Map<String, String> fields) {
-            this.fields.putAll(fields);
-        }
-
-        Map<String, String> getFields() {
-            return this.fields;
-        }
-    }
-}
+/*
+ * 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.core.layout;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Priority;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.core.pattern.TextBuffer;
+import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
+import org.apache.logging.log4j.core.util.Charsets;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.StructuredDataId;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.util.Strings;
+
+
+/**
+ * Formats a log event in accordance with RFC 5424.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc5424";>RFC 5424</a>
+ */
+@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = 
Layout.ELEMENT_TYPE, printObject = true)
+public final class Rfc5424Layout extends AbstractStringLayout {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String LF = "\n";
+
+    /**
+     * Not a very good default - it is the Apache Software Foundation's 
enterprise number.
+     */
+    public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
+    /**
+     * The default event id.
+     */
+    public static final String DEFAULT_ID = "Audit";
+    /**
+     * Match newlines in a platform-independent manner.
+     */
+    public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
+    /**
+     * Match characters which require escaping
+     */
+    public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = 
Pattern.compile("[\\\"\\]\\\\]");
+
+    public static final String DEFAULT_MDCID = "mdc";
+    private static final int TWO_DIGITS = 10;
+    private static final int THREE_DIGITS = 100;
+    private static final int MILLIS_PER_MINUTE = 60000;
+    private static final int MINUTES_PER_HOUR = 60;
+
+    private static final String COMPONENT_KEY = "RFC5424-Converter";
+
+    private final Facility facility;
+    private final String defaultId;
+    private final int enterpriseNumber;
+    private final boolean includeMdc;
+    private final String mdcId;
+    private final StructuredDataId mdcSdId;
+    private final String localHostName;
+    private final String appName;
+    private final String messageId;
+    private final String configName;
+    private final String mdcPrefix;
+    private final String eventPrefix;
+    private final List<String> mdcExcludes;
+    private final List<String> mdcIncludes;
+    private final List<String> mdcRequired;
+    private final ListChecker checker;
+    private final ListChecker noopChecker = new NoopChecker();
+    private final boolean includeNewLine;
+    private final String escapeNewLine;
+    private final boolean useTlsMessageFormat;
+
+    private long lastTimestamp = -1;
+    private String timestamppStr;
+
+    private final List<PatternFormatter> exceptionFormatters;
+    private final Map<String,  FieldFormatter> fieldFormatters;
+
+    private Rfc5424Layout(final Configuration config, final Facility facility, 
final String id, final int ein,
+                          final boolean includeMDC, final boolean includeNL, 
final String escapeNL, final String mdcId,
+                          final String mdcPrefix, final String eventPrefix,
+                          final String appName, final String messageId, final 
String excludes, final String includes,
+                          final String required, final Charset charset, final 
String exceptionPattern,
+                          final boolean useTLSMessageFormat, final 
LoggerFields[] loggerFields) {
+        super(charset);
+        final PatternParser exceptionParser = createPatternParser(config, 
ThrowablePatternConverter.class);
+        exceptionFormatters = exceptionPattern == null ? null : 
exceptionParser.parse(exceptionPattern, false, false);
+        this.facility = facility;
+        this.defaultId = id == null ? DEFAULT_ID : id;
+        this.enterpriseNumber = ein;
+        this.includeMdc = includeMDC;
+        this.includeNewLine = includeNL;
+        this.escapeNewLine = escapeNL == null ? null : 
Matcher.quoteReplacement(escapeNL);
+        this.mdcId = mdcId;
+        this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, 
null);
+        this.mdcPrefix = mdcPrefix;
+        this.eventPrefix = eventPrefix;
+        this.appName = appName;
+        this.messageId = messageId;
+        this.useTlsMessageFormat = useTLSMessageFormat;
+        this.localHostName = NetUtils.getLocalHostname();
+        ListChecker c = null;
+        if (excludes != null) {
+            final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
+            if (array.length > 0) {
+                c = new ExcludeChecker();
+                mdcExcludes = new ArrayList<String>(array.length);
+                for (final String str : array) {
+                    mdcExcludes.add(str.trim());
+                }
+            } else {
+                mdcExcludes = null;
+            }
+        } else {
+            mdcExcludes = null;
+        }
+        if (includes != null) {
+            final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
+            if (array.length > 0) {
+                c = new IncludeChecker();
+                mdcIncludes = new ArrayList<String>(array.length);
+                for (final String str : array) {
+                    mdcIncludes.add(str.trim());
+                }
+            } else {
+                mdcIncludes = null;
+            }
+        } else {
+            mdcIncludes = null;
+        }
+        if (required != null) {
+            final String[] array = required.split(Patterns.COMMA_SEPARATOR);
+            if (array.length > 0) {
+                mdcRequired = new ArrayList<String>(array.length);
+                for (final String str : array) {
+                    mdcRequired.add(str.trim());
+                }
+            } else {
+                mdcRequired = null;
+            }
+
+        } else {
+            mdcRequired = null;
+        }
+        this.checker = c != null ? c : noopChecker;
+        final String name = config == null ? null : config.getName();
+        configName = name != null && name.length() > 0 ? name : null;
+        this.fieldFormatters = createFieldFormatters(loggerFields, config);
+    }
+
+    private Map<String, FieldFormatter> createFieldFormatters(final 
LoggerFields[] loggerFields,
+            final Configuration config) {
+        final Map<String, FieldFormatter> sdIdMap = new HashMap<String, 
FieldFormatter>();
+
+        if (loggerFields != null) {
+            for (final LoggerFields lField : loggerFields) {
+                final StructuredDataId key = lField.getSdId() == null ? 
mdcSdId : lField.getSdId();
+                final Map<String, List<PatternFormatter>> sdParams = new 
HashMap<String, List<PatternFormatter>>();
+                final Map<String, String> fields = lField.getMap();
+                if (!fields.isEmpty()) {
+                    final PatternParser fieldParser = 
createPatternParser(config, null);
+
+                    for (final Map.Entry<String, String> entry : 
fields.entrySet()) {
+                        final List<PatternFormatter> formatters = 
fieldParser.parse(entry.getValue(), false, false);
+                        sdParams.put(entry.getKey(), formatters);
+                    }
+                    final FieldFormatter fieldFormatter = new 
FieldFormatter(sdParams,
+                            lField.getDiscardIfAllFieldsAreEmpty());
+                    sdIdMap.put(key.toString(), fieldFormatter);
+                }
+            }
+        }
+        return sdIdMap.size() > 0 ? sdIdMap : null;
+    }
+
+    /**
+     * Create a PatternParser.
+     *
+     * @param config The Configuration.
+     * @param filterClass Filter the returned plugins after calling the plugin 
manager.
+     * @return The PatternParser.
+     */
+    private static PatternParser createPatternParser(final Configuration 
config,
+            final Class<? extends PatternConverter> filterClass) {
+        if (config == null) {
+            return new PatternParser(config, PatternLayout.KEY, 
LogEventPatternConverter.class, filterClass);
+        }
+        PatternParser parser = config.getComponent(COMPONENT_KEY);
+        if (parser == null) {
+            parser = new PatternParser(config, PatternLayout.KEY, 
ThrowablePatternConverter.class);
+            config.addComponent(COMPONENT_KEY, parser);
+            parser = (PatternParser) config.getComponent(COMPONENT_KEY);
+        }
+        return parser;
+    }
+
+    /**
+     * Gets this Rfc5424Layout's content format. Specified by:
+     * <ul>
+     * <li>Key: "structured" Value: "true"</li>
+     * <li>Key: "format" Value: "RFC5424"</li>
+     * </ul>
+     * 
+     * @return Map of content format keys supporting Rfc5424Layout
+     */
+    @Override
+    public Map<String, String> getContentFormat() {
+        final Map<String, String> result = new HashMap<String, String>();
+        result.put("structured", "true");
+        result.put("formatType", "RFC5424");
+        return result;
+    }
+
+    /**
+     * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance 
with the RFC 5424 Syslog specification.
+     *
+     * @param event The LogEvent.
+     * @return The RFC 5424 String representation of the LogEvent.
+     */
+    @Override
+    public String toSerializable(final LogEvent event) {
+        final StringBuilder buf = new StringBuilder();
+        appendPriority(buf, event.getLevel());
+        appendTimestamp(buf, event.getTimeMillis());
+        appendSpace(buf);
+        appendHostName(buf);
+        appendSpace(buf);
+        appendAppName(buf);
+        appendSpace(buf);
+        appendProcessId(buf);
+        appendSpace(buf);
+        appendMessageId(buf, event.getMessage());
+        appendSpace(buf);
+        appendStructuredElements(buf, event);
+        appendMessage(buf, event);
+        if (useTlsMessageFormat) {
+            return new TlsSyslogFrame(buf.toString()).toString();
+        }
+        return buf.toString();
+    }
+
+    private void appendPriority(final StringBuilder buffer, final Level 
logLevel) {
+        buffer.append('<');
+        buffer.append(Priority.getPriority(facility, logLevel));
+        buffer.append(">1 ");
+    }
+
+    private void appendTimestamp(final StringBuilder buffer, final long 
milliseconds)  {
+        buffer.append(computeTimeStampString(milliseconds));
+    }
+
+    private void appendSpace(final StringBuilder buffer) {
+        buffer.append(' ');
+    }
+
+    private void appendHostName(final StringBuilder buffer) {
+        buffer.append(localHostName);
+    }
+
+    private void appendAppName(final StringBuilder buffer) {
+        if (appName != null) {
+            buffer.append(appName);
+        } else if (configName != null) {
+            buffer.append(configName);
+        } else {
+            buffer.append('-');
+        }
+    }
+
+    private void appendProcessId(final StringBuilder buffer) {
+        buffer.append(getProcId());
+    }
+
+    private void appendMessageId(final StringBuilder buffer, final Message 
message) {
+        final boolean isStructured = message instanceof StructuredDataMessage;
+        final String type = isStructured ? ((StructuredDataMessage) 
message).getType() : null;
+        if (type != null) {
+            buffer.append(type);
+        } else if (messageId != null) {
+            buffer.append(messageId);
+        } else {
+            buffer.append('-');
+        }
+    }
+
+    private void appendMessage(final StringBuilder buffer, final LogEvent 
event) {
+        final Message message = event.getMessage();
+        // This layout formats StructuredDataMessages instead of delegating to 
the Message itself.
+        final String text = (message instanceof StructuredDataMessage) ? 
message.getFormat() : message.getFormattedMessage();
+
+        if (text != null && text.length() > 0) {
+            buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
+        }
+
+        if (exceptionFormatters != null && event.getThrown() != null) {
+            final TextBuffer exception = new TextBuffer();
+            exception.append(LF);
+            for (final PatternFormatter formatter : exceptionFormatters) {
+                formatter.format(event, exception);
+            }
+            buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
+        }
+        if (includeNewLine) {
+            buffer.append(LF);
+        }
+    }
+
+    private void appendStructuredElements(final StringBuilder buffer, final 
LogEvent event) {
+        final Message message = event.getMessage();
+        final boolean isStructured = message instanceof StructuredDataMessage;
+
+        if (!isStructured && (fieldFormatters!= null && 
fieldFormatters.isEmpty()) && !includeMdc) {
+            buffer.append('-');
+            return;
+        }
+
+        final Map<String, StructuredDataElement> sdElements = new 
HashMap<String, StructuredDataElement>();
+        final Map<String, String> contextMap = event.getContextMap();
+
+        if (mdcRequired != null) {
+            checkRequired(contextMap);
+        }
+
+        if (fieldFormatters != null) {
+            for (final Map.Entry<String, FieldFormatter> sdElement: 
fieldFormatters.entrySet()) {
+                final String sdId = sdElement.getKey();
+                final StructuredDataElement elem = 
sdElement.getValue().format(event);
+                sdElements.put(sdId, elem);
+            }
+        }
+
+        if (includeMdc && contextMap.size() > 0) {
+            if (sdElements.containsKey(mdcSdId.toString())) {
+                final StructuredDataElement union = 
sdElements.get(mdcSdId.toString());
+                union.union(contextMap);
+                sdElements.put(mdcSdId.toString(), union);
+            } else {
+                final StructuredDataElement formattedContextMap = new 
StructuredDataElement(contextMap, false);
+                sdElements.put(mdcSdId.toString(), formattedContextMap);
+            }
+        }
+
+        if (isStructured) {
+            final StructuredDataMessage data = (StructuredDataMessage) message;
+            final Map<String, String> map = data.getData();
+            final StructuredDataId id = data.getId();
+            final String sdId = getId(id);
+
+            if (sdElements.containsKey(sdId)) {
+                final StructuredDataElement union = 
sdElements.get(id.toString());
+                union.union(map);
+                sdElements.put(sdId, union);
+            } else {
+                final StructuredDataElement formattedData = new 
StructuredDataElement(map, false);
+                sdElements.put(sdId, formattedData);
+            }
+        }
+
+        if (sdElements.isEmpty()) {
+            buffer.append('-');
+            return;
+        }
+
+        for (final Map.Entry<String, StructuredDataElement> entry: 
sdElements.entrySet()) {
+            formatStructuredElement(entry.getKey(), mdcPrefix, 
entry.getValue(), buffer, checker);
+        }
+    }
+
+    private String escapeNewlines(final String text, final String 
escapeNewLine) {
+        if (null == escapeNewLine) {
+            return text;
+        }
+        return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
+    }
+
+    protected String getProcId() {
+        return "-";
+    }
+
+    protected List<String> getMdcExcludes() {
+        return mdcExcludes;
+    }
+
+    protected List<String> getMdcIncludes() {
+        return mdcIncludes;
+    }
+
+    private String computeTimeStampString(final long now) {
+        long last;
+        synchronized (this) {
+            last = lastTimestamp;
+            if (now == lastTimestamp) {
+                return timestamppStr;
+            }
+        }
+
+        final StringBuilder buffer = new StringBuilder();
+        final Calendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(now);
+        buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
+        buffer.append('-');
+        pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
+        buffer.append('-');
+        pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
+        buffer.append('T');
+        pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
+        buffer.append(':');
+        pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
+        buffer.append(':');
+        pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
+
+        final int millis = cal.get(Calendar.MILLISECOND);
+        if (millis != 0) {
+            buffer.append('.');
+            pad(millis, THREE_DIGITS, buffer);
+        }
+
+        int tzmin = (cal.get(Calendar.ZONE_OFFSET) + 
cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
+        if (tzmin == 0) {
+            buffer.append('Z');
+        } else {
+            if (tzmin < 0) {
+                tzmin = -tzmin;
+                buffer.append('-');
+            } else {
+                buffer.append('+');
+            }
+            final int tzhour = tzmin / MINUTES_PER_HOUR;
+            tzmin -= tzhour * MINUTES_PER_HOUR;
+            pad(tzhour, TWO_DIGITS, buffer);
+            buffer.append(':');
+            pad(tzmin, TWO_DIGITS, buffer);
+        }
+        synchronized (this) {
+            if (last == lastTimestamp) {
+                lastTimestamp = now;
+                timestamppStr = buffer.toString();
+            }
+        }
+        return buffer.toString();
+    }
+
+    private void pad(final int val, int max, final StringBuilder buf) {
+        while (max > 1) {
+            if (val < max) {
+                buf.append('0');
+            }
+            max = max / TWO_DIGITS;
+        }
+        buf.append(Integer.toString(val));
+    }
+
+    private void formatStructuredElement(final String id, final String prefix, 
final StructuredDataElement data,
+                                         final StringBuilder sb, final 
ListChecker checker) {
+        if ((id == null && defaultId == null) || data.discard()) {
+            return;
+        }
+
+        sb.append('[');
+        sb.append(id);
+        if (!mdcSdId.toString().equals(id)) {
+            appendMap(prefix, data.getFields(), sb, noopChecker);
+        } else {
+            appendMap(prefix, data.getFields(), sb, checker);
+        }
+        sb.append(']');
+    }
+
+    private String getId(final StructuredDataId id) {
+        final StringBuilder sb = new StringBuilder();
+        if (id == null || id.getName() == null) {
+            sb.append(defaultId);
+        } else {
+            sb.append(id.getName());
+        }
+        int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
+        if (ein < 0) {
+            ein = enterpriseNumber;
+        }
+        if (ein >= 0) {
+            sb.append('@').append(ein);
+        }
+        return sb.toString();
+    }
+
+    private void checkRequired(final Map<String, String> map) {
+        for (final String key : mdcRequired) {
+            final String value = map.get(key);
+            if (value == null) {
+                throw new LoggingException("Required key " + key + " is 
missing from the " + mdcId);
+            }
+        }
+    }
+
+    private void appendMap(final String prefix, final Map<String, String> map, 
final StringBuilder sb,
+                           final ListChecker checker) {
+        final SortedMap<String, String> sorted = new TreeMap<String, 
String>(map);
+        for (final Map.Entry<String, String> entry : sorted.entrySet()) {
+            if (checker.check(entry.getKey()) && entry.getValue() != null) {
+                sb.append(' ');
+                if (prefix != null) {
+                    sb.append(prefix);
+                }
+                sb.append(escapeNewlines(escapeSDParams(entry.getKey()), 
escapeNewLine)).append("=\"")
+                    .append(escapeNewlines(escapeSDParams(entry.getValue()), 
escapeNewLine)).append('"');
+            }
+        }
+    }
+
+    private String escapeSDParams(final String value) {
+        return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
+    }
+
+    /**
+     * Interface used to check keys in a Map.
+     */
+    private interface ListChecker {
+        boolean check(String key);
+    }
+
+    /**
+     * Includes only the listed keys.
+     */
+    private class IncludeChecker implements ListChecker {
+        @Override
+        public boolean check(final String key) {
+            return mdcIncludes.contains(key);
+        }
+    }
+
+    /**
+     * Excludes the listed keys.
+     */
+    private class ExcludeChecker implements ListChecker {
+        @Override
+        public boolean check(final String key) {
+            return !mdcExcludes.contains(key);
+        }
+    }
+
+    /**
+     * Does nothing.
+     */
+    private class NoopChecker implements ListChecker {
+        @Override
+        public boolean check(final String key) {
+            return true;
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("facility=").append(facility.name());
+        sb.append(" appName=").append(appName);
+        sb.append(" defaultId=").append(defaultId);
+        sb.append(" enterpriseNumber=").append(enterpriseNumber);
+        sb.append(" newLine=").append(includeNewLine);
+        sb.append(" includeMDC=").append(includeMdc);
+        sb.append(" messageId=").append(messageId);
+        return sb.toString();
+    }
+
+    /**
+     * Create the RFC 5424 Layout.
+     *
+     * @param facility         The Facility is used to try to classify the 
message.
+     * @param id               The default structured data id to use when 
formatting according to RFC 5424.
+     * @param enterpriseNumber The IANA enterprise number.
+     * @param includeMDC       Indicates whether data from the 
ThreadContextMap will be included in the RFC 5424 Syslog
+     *                         record. Defaults to "true:.
+     * @param mdcId            The id to use for the MDC Structured Data 
Element.
+     * @param mdcPrefix        The prefix to add to MDC key names.
+     * @param eventPrefix      The prefix to add to event key names.
+     * @param newLine          If true, a newline will be appended to the end 
of the syslog record. The default is false.
+     * @param escapeNL         String that should be used to replace newlines 
within the message text.
+     * @param appName          The value to use as the APP-NAME in the RFC 
5424 syslog record.
+     * @param msgId            The default value to be used in the MSGID field 
of RFC 5424 syslog records.
+     * @param excludes         A comma separated list of MDC keys that should 
be excluded from the LogEvent.
+     * @param includes         A comma separated list of MDC keys that should 
be included in the FlumeEvent.
+     * @param required         A comma separated list of MDC keys that must be 
present in the MDC.
+     * @param exceptionPattern The pattern for formatting exceptions.
+     * @param useTlsMessageFormat If true the message will be formatted 
according to RFC 5425.
+     * @param loggerFields     Container for the KeyValuePairs containing the 
patterns
+     * @param config           The Configuration. Some Converters require 
access to the Interpolator.
+     * @return An Rfc5424Layout.
+     */
+    @PluginFactory
+    public static Rfc5424Layout createLayout(
+            @PluginAttribute(value = "facility", defaultString = "LOCAL0") 
final Facility facility,
+            @PluginAttribute("id") final String id,
+            @PluginAttribute(value = "enterpriseNumber", defaultInt = 
DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
+            @PluginAttribute(value = "includeMDC", defaultBoolean = true) 
final boolean includeMDC,
+            @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) 
final String mdcId,
+            @PluginAttribute("mdcPrefix") final String mdcPrefix,
+            @PluginAttribute("eventPrefix") final String eventPrefix,
+            @PluginAttribute(value = "newLine", defaultBoolean = false) final 
boolean newLine,
+            @PluginAttribute("newLineEscape") final String escapeNL,
+            @PluginAttribute("appName") final String appName,
+            @PluginAttribute("messageId") final String msgId,
+            @PluginAttribute("mdcExcludes") final String excludes,
+            @PluginAttribute("mdcIncludes") String includes,
+            @PluginAttribute("mdcRequired") final String required,
+            @PluginAttribute("exceptionPattern") final String exceptionPattern,
+            @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = 
false) final boolean useTlsMessageFormat, // RFC 5425
+            @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
+            @PluginConfiguration final Configuration config) {
+        final Charset charset = Charsets.UTF_8;
+        if (includes != null && excludes != null) {
+            LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. 
Includes wil be ignored");
+            includes = null;
+        }
+
+        return new Rfc5424Layout(config, facility, id, enterpriseNumber, 
includeMDC, newLine, escapeNL, mdcId, mdcPrefix,
+                eventPrefix, appName, msgId, excludes, includes, required, 
charset, exceptionPattern,
+                useTlsMessageFormat, loggerFields);
+    }
+
+    private class FieldFormatter {
+
+        private final Map<String, List<PatternFormatter>> delegateMap;
+        private final boolean discardIfEmpty;
+
+        public FieldFormatter(final Map<String, List<PatternFormatter>> 
fieldMap, final boolean discardIfEmpty) {
+            this.discardIfEmpty = discardIfEmpty;
+            this.delegateMap = fieldMap;
+        }
+
+        public StructuredDataElement format(final LogEvent event) {
+            final Map<String, String> map = new HashMap<String, String>();
+
+            for (final Map.Entry<String, List<PatternFormatter>> entry : 
delegateMap.entrySet()) {
+                final TextBuffer buffer = new TextBuffer();
+                for (final PatternFormatter formatter : entry.getValue()) {
+                    formatter.format(event, buffer);
+                }
+                map.put(entry.getKey(), buffer.toString());
+            }
+            return new StructuredDataElement(map, discardIfEmpty);
+        }
+    }
+
+    private class StructuredDataElement {
+
+        private final Map<String, String> fields;
+        private final boolean discardIfEmpty;
+
+        public StructuredDataElement(final Map<String, String> fields, final 
boolean discardIfEmpty) {
+            this.discardIfEmpty = discardIfEmpty;
+            this.fields = fields;
+        }
+
+        boolean discard() {
+            if (discardIfEmpty == false) {
+                return false;
+            }
+            boolean foundNotEmptyValue = false;
+            for (final Map.Entry<String, String> entry : fields.entrySet()) {
+                if (Strings.isNotEmpty(entry.getValue())) {
+                    foundNotEmptyValue = true;
+                    break;
+                }
+            }
+            return !foundNotEmptyValue;
+        }
+
+        void union(final Map<String, String> fields) {
+            this.fields.putAll(fields);
+        }
+
+        Map<String, String> getFields() {
+            return this.fields;
+        }
+    }
+}

Reply via email to