You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2017/06/03 18:59:20 UTC

logging-log4j2 git commit: Branch for GenericMapMessage experiment as discussed on the ML with Ralph.

Repository: logging-log4j2
Updated Branches:
  refs/heads/GenericMapMessage [created] 5411ea1bb


Branch for GenericMapMessage experiment as discussed on the ML with
Ralph.

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

Branch: refs/heads/GenericMapMessage
Commit: 5411ea1bb52b081a2938f215e96645721dc8240e
Parents: 864b7a8
Author: Gary Gregory <gg...@apache.org>
Authored: Sat Jun 3 11:59:14 2017 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Sat Jun 3 11:59:14 2017 -0700

----------------------------------------------------------------------
 .../log4j/message/GenericMapMessage.java        | 460 +++++++++++++++++++
 .../logging/log4j/message/MapMessage.java       | 430 +----------------
 2 files changed, 466 insertions(+), 424 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/5411ea1b/log4j-api/src/main/java/org/apache/logging/log4j/message/GenericMapMessage.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/GenericMapMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/GenericMapMessage.java
new file mode 100644
index 0000000..0cbf0ff
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/GenericMapMessage.java
@@ -0,0 +1,460 @@
+/*
+ * 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.message;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.EnglishEnums;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.util.IndexedStringMap;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.StringBuilders;
+import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * Represents a Message that consists of a Map.
+ * <p>
+ * Thread-safety note: the contents of this message can be modified after construction.
+ * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
+ * logged, because it is undefined whether the logged message string will contain the old values or the modified
+ * values.
+ * </p>
+ */
+@PerformanceSensitive("allocation")
+@AsynchronouslyFormattable
+public class MapMessage implements MultiformatMessage, StringBuilderFormattable {
+
+    /**
+     * When set as the format specifier causes the Map to be formatted as XML.
+     */
+    public enum MapFormat {
+        
+        /** The map should be formatted as XML. */
+        XML,
+        
+        /** The map should be formatted as JSON. */
+        JSON,
+        
+        /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
+        JAVA;
+
+        /**
+         * Maps a format name to an {@link MapFormat} while ignoring case.
+         * 
+         * @param format a MapFormat name
+         * @return a MapFormat
+         */
+        public static MapFormat lookupIgnoreCase(final String format) {
+            return XML.name().equalsIgnoreCase(format) ? XML //
+                    : JSON.name().equalsIgnoreCase(format) ? JSON //
+                    : JAVA.name().equalsIgnoreCase(format) ? JAVA //
+                    : null;
+        }
+
+        /**
+         * All {@code MapFormat} names.
+         * 
+         * @return All {@code MapFormat} names.
+         */
+        public static String[] names() {
+            return new String[] {XML.name(), JSON.name(), JAVA.name()};
+        }
+    }
+
+    private static final long serialVersionUID = -5031471831131487120L;
+
+    private final IndexedStringMap data;
+
+    /**
+     * Constructs a new instance.
+     */
+    public MapMessage() {
+        data = new SortedArrayStringMap();
+    }
+
+    /**
+     * Constructs a new instance based on an existing Map.
+     * @param map The Map.
+     */
+    public MapMessage(final Map<String, String> map) {
+        this.data = new SortedArrayStringMap(map);
+    }
+
+    @Override
+    public String[] getFormats() {
+        return MapFormat.names();
+    }
+
+    /**
+     * Returns the data elements as if they were parameters on the logging event.
+     * @return the data elements.
+     */
+    @Override
+    public Object[] getParameters() {
+        final Object[] result = new Object[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            result[i] = data.getValueAt(i);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the message.
+     * @return the message.
+     */
+    @Override
+    public String getFormat() {
+        return Strings.EMPTY;
+    }
+
+    /**
+     * Returns the message data as an unmodifiable Map.
+     * @return the message data as an unmodifiable map.
+     */
+    public Map<String, String> getData() {
+        final TreeMap<String, String> result = new TreeMap<>(); // returned map must be sorted
+        for (int i = 0; i < data.size(); i++) {
+            result.put(data.getKeyAt(i), (String) data.getValueAt(i));
+        }
+        return Collections.unmodifiableMap(result);
+    }
+
+    /**
+     * Returns a read-only view of the message data.
+     * @return the read-only message data.
+     */
+    public IndexedReadOnlyStringMap getIndexedReadOnlyStringMap() {
+        return data;
+    }
+
+    /**
+     * Clear the data.
+     */
+    public void clear() {
+        data.clear();
+    }
+
+    /**
+     * Returns {@code true} if this data structure contains the specified key, {@code false} otherwise.
+     *
+     * @param key the key whose presence to check. May be {@code null}.
+     * @return {@code true} if this data structure contains the specified key, {@code false} otherwise
+     * @since 2.9
+     */
+    public boolean containsKey(final String key) {
+        return data.containsKey(key);
+    }
+
+    /**
+     * Adds an item to the data Map in fluent style.
+     * @param key The name of the data item.
+     * @param value The value of the data item.
+     * @return {@code this}
+     */
+    public MapMessage with(final String key, final String value) {
+        put(key, value);
+        return this;
+    }
+
+    /**
+     * Adds an item to the data Map.
+     * @param key The name of the data item.
+     * @param value The value of the data item.
+     */
+    public void put(final String key, final String value) {
+        if (value == null) {
+            throw new IllegalArgumentException("No value provided for key " + key);
+        }
+        validate(key, value);
+        data.putValue(key, value);
+    }
+
+    protected void validate(final String key, final String value) {
+
+    }
+
+    /**
+     * Adds all the elements from the specified Map.
+     * @param map The Map to add.
+     */
+    public void putAll(final Map<String, String> map) {
+        for (final Map.Entry<String, ?> entry : map.entrySet()) {
+            data.putValue(entry.getKey(), entry.getValue());
+        }
+    }
+
+    /**
+     * Retrieves the value of the element with the specified key or null if the key is not present.
+     * @param key The name of the element.
+     * @return The value of the element or null if the key is not present.
+     */
+    public String get(final String key) {
+        return data.getValue(key);
+    }
+
+    /**
+     * Removes the element with the specified name.
+     * @param key The name of the element.
+     * @return The previous value of the element.
+     */
+    public String remove(final String key) {
+        final String result = data.getValue(key);
+        data.remove(key);
+        return result;
+    }
+
+    /**
+     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
+     *
+     * @return The formatted String.
+     */
+    public String asString() {
+        return format((MapFormat) null, new StringBuilder()).toString();
+    }
+
+    /**
+     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
+     *
+     * @param format The format identifier.
+     * @return The formatted String.
+     */
+    public String asString(final String format) {
+        try {
+            return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString();
+        } catch (final IllegalArgumentException ex) {
+            return asString();
+        }
+    }
+    
+    /**
+     * Performs the given action for each key-value pair in this data structure
+     * until all entries have been processed or the action throws an exception.
+     * <p>
+     * Some implementations may not support structural modifications (adding new elements or removing elements) while
+     * iterating over the contents. In such implementations, attempts to add or remove elements from the
+     * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} method may cause a
+     * {@code ConcurrentModificationException} to be thrown.
+     * </p>
+     *
+     * @param action The action to be performed for each key-value pair in this collection
+     * @param <V> type of the value
+     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
+     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     * @see ReadOnlyStringMap#forEach(BiConsumer)
+     * @since 2.9
+     */
+    public <V> void forEach(final BiConsumer<String, ? super V> action) {
+        data.forEach(action);
+    }
+
+    /**
+     * Performs the given action for each key-value pair in this data structure
+     * until all entries have been processed or the action throws an exception.
+     * <p>
+     * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs,
+     * so the TriConsumer implementation itself can be stateless and potentially reusable.
+     * </p>
+     * <p>
+     * Some implementations may not support structural modifications (adding new elements or removing elements) while
+     * iterating over the contents. In such implementations, attempts to add or remove elements from the
+     * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) accept} method may cause a
+     * {@code ConcurrentModificationException} to be thrown.
+     * </p>
+     *
+     * @param action The action to be performed for each key-value pair in this collection
+     * @param state the object to be passed as the third parameter to each invocation on the specified
+     *          triconsumer
+     * @param <V> type of the value
+     * @param <S> type of the third parameter
+     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
+     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     * @see ReadOnlyStringMap#forEach(TriConsumer, Object)
+     * @since 2.9
+     */
+    public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
+        data.forEach(action, state);
+    }
+    
+    /**
+     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
+     *
+     * @param format The format identifier.
+     * @return The formatted String.
+     */
+    private StringBuilder format(final MapFormat format, final StringBuilder sb) {
+        if (format == null) {
+            appendMap(sb);
+        } else {
+            switch (format) {
+                case XML : {
+                    asXml(sb);
+                    break;
+                }
+                case JSON : {
+                    asJson(sb);
+                    break;
+                }
+                case JAVA : {
+                    asJava(sb);
+                    break;
+                }
+                default : {
+                    appendMap(sb);
+                }
+            }
+        }
+        return sb;
+    }
+
+    /**
+     * Formats this message as an XML fragment String into the given builder.
+     *
+     * @param sb format into this builder.
+     */
+    public void asXml(final StringBuilder sb) {
+        sb.append("<Map>\n");
+        for (int i = 0; i < data.size(); i++) {
+            sb.append("  <Entry key=\"").append(data.getKeyAt(i)).append("\">").append((String)data.getValueAt(i))
+                    .append("</Entry>\n");
+        }
+        sb.append("</Map>");
+    }
+
+    /**
+     * Formats the message and return it.
+     * @return the formatted message.
+     */
+    @Override
+    public String getFormattedMessage() {
+        return asString();
+    }
+
+    /**
+     *
+     * @param formats
+     *            An array of Strings that provide extra information about how to format the message. MapMessage uses
+     *            the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default
+     *            format is key1="value1" key2="value2" as required by <a href="https://tools.ietf.org/html/rfc5424">RFC
+     *            5424</a> messages.
+     *
+     * @return The formatted message.
+     */
+    @Override
+    public String getFormattedMessage(final String[] formats) {
+        if (formats == null || formats.length == 0) {
+            return asString();
+        }
+        for (int i = 0; i < formats.length; i++) {
+            final MapFormat mapFormat = MapFormat.lookupIgnoreCase(formats[i]);
+            if (mapFormat != null) {
+                return format(mapFormat, new StringBuilder()).toString();
+            }
+        }
+        return asString();
+
+    }
+
+    protected void appendMap(final StringBuilder sb) {
+        for (int i = 0; i < data.size(); i++) {
+            if (i > 0) {
+                sb.append(' ');
+            }
+            StringBuilders.appendKeyDqValue(sb, data.getKeyAt(i), data.getValueAt(i));
+        }
+    }
+
+    protected void asJson(final StringBuilder sb) {
+        sb.append('{');
+        for (int i = 0; i < data.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            StringBuilders.appendDqValue(sb, data.getKeyAt(i)).append(':');
+            StringBuilders.appendDqValue(sb, data.getValueAt(i));
+        }
+        sb.append('}');
+    }
+
+
+    protected void asJava(final StringBuilder sb) {
+        sb.append('{');
+        for (int i = 0; i < data.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            StringBuilders.appendKeyDqValue(sb, data.getKeyAt(i), data.getValueAt(i));
+        }
+        sb.append('}');
+    }
+
+    /**
+     * Constructs a new instance based on an existing Map.
+     * @param map The Map.
+     * @return A new MapMessage
+     */
+    public MapMessage newInstance(final Map<String, String> map) {
+        return new MapMessage(map);
+    }
+
+    @Override
+    public String toString() {
+        return asString();
+    }
+
+    @Override
+    public void formatTo(final StringBuilder buffer) {
+        format((MapFormat) null, buffer);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || this.getClass() != o.getClass()) {
+            return false;
+        }
+
+        final MapMessage that = (MapMessage) o;
+
+        return this.data.equals(that.data);
+    }
+
+    @Override
+    public int hashCode() {
+        return data.hashCode();
+    }
+
+    /**
+     * Always returns null.
+     *
+     * @return null
+     */
+    @Override
+    public Throwable getThrowable() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/5411ea1b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
----------------------------------------------------------------------
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
index 0cbf0ff..ab5c522 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
@@ -14,400 +14,21 @@
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+
 package org.apache.logging.log4j.message;
 
-import java.util.Collections;
 import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.logging.log4j.util.BiConsumer;
-import org.apache.logging.log4j.util.EnglishEnums;
-import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
-import org.apache.logging.log4j.util.IndexedStringMap;
-import org.apache.logging.log4j.util.PerformanceSensitive;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-import org.apache.logging.log4j.util.SortedArrayStringMap;
-import org.apache.logging.log4j.util.StringBuilderFormattable;
-import org.apache.logging.log4j.util.StringBuilders;
-import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util.TriConsumer;
-
-/**
- * Represents a Message that consists of a Map.
- * <p>
- * Thread-safety note: the contents of this message can be modified after construction.
- * When using asynchronous loggers and appenders it is not recommended to modify this message after the message is
- * logged, because it is undefined whether the logged message string will contain the old values or the modified
- * values.
- * </p>
- */
-@PerformanceSensitive("allocation")
-@AsynchronouslyFormattable
-public class MapMessage implements MultiformatMessage, StringBuilderFormattable {
 
-    /**
-     * When set as the format specifier causes the Map to be formatted as XML.
-     */
-    public enum MapFormat {
-        
-        /** The map should be formatted as XML. */
-        XML,
-        
-        /** The map should be formatted as JSON. */
-        JSON,
-        
-        /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
-        JAVA;
+public class MapMessage extends GenericMapMessage<MapMessage, String> {
 
-        /**
-         * Maps a format name to an {@link MapFormat} while ignoring case.
-         * 
-         * @param format a MapFormat name
-         * @return a MapFormat
-         */
-        public static MapFormat lookupIgnoreCase(final String format) {
-            return XML.name().equalsIgnoreCase(format) ? XML //
-                    : JSON.name().equalsIgnoreCase(format) ? JSON //
-                    : JAVA.name().equalsIgnoreCase(format) ? JAVA //
-                    : null;
-        }
-
-        /**
-         * All {@code MapFormat} names.
-         * 
-         * @return All {@code MapFormat} names.
-         */
-        public static String[] names() {
-            return new String[] {XML.name(), JSON.name(), JAVA.name()};
-        }
-    }
+    private static final long serialVersionUID = 1L;
 
-    private static final long serialVersionUID = -5031471831131487120L;
-
-    private final IndexedStringMap data;
-
-    /**
-     * Constructs a new instance.
-     */
     public MapMessage() {
-        data = new SortedArrayStringMap();
-    }
-
-    /**
-     * Constructs a new instance based on an existing Map.
-     * @param map The Map.
-     */
-    public MapMessage(final Map<String, String> map) {
-        this.data = new SortedArrayStringMap(map);
-    }
-
-    @Override
-    public String[] getFormats() {
-        return MapFormat.names();
+        super();
     }
 
-    /**
-     * Returns the data elements as if they were parameters on the logging event.
-     * @return the data elements.
-     */
-    @Override
-    public Object[] getParameters() {
-        final Object[] result = new Object[data.size()];
-        for (int i = 0; i < data.size(); i++) {
-            result[i] = data.getValueAt(i);
-        }
-        return result;
-    }
-
-    /**
-     * Returns the message.
-     * @return the message.
-     */
-    @Override
-    public String getFormat() {
-        return Strings.EMPTY;
-    }
-
-    /**
-     * Returns the message data as an unmodifiable Map.
-     * @return the message data as an unmodifiable map.
-     */
-    public Map<String, String> getData() {
-        final TreeMap<String, String> result = new TreeMap<>(); // returned map must be sorted
-        for (int i = 0; i < data.size(); i++) {
-            result.put(data.getKeyAt(i), (String) data.getValueAt(i));
-        }
-        return Collections.unmodifiableMap(result);
-    }
-
-    /**
-     * Returns a read-only view of the message data.
-     * @return the read-only message data.
-     */
-    public IndexedReadOnlyStringMap getIndexedReadOnlyStringMap() {
-        return data;
-    }
-
-    /**
-     * Clear the data.
-     */
-    public void clear() {
-        data.clear();
-    }
-
-    /**
-     * Returns {@code true} if this data structure contains the specified key, {@code false} otherwise.
-     *
-     * @param key the key whose presence to check. May be {@code null}.
-     * @return {@code true} if this data structure contains the specified key, {@code false} otherwise
-     * @since 2.9
-     */
-    public boolean containsKey(final String key) {
-        return data.containsKey(key);
-    }
-
-    /**
-     * Adds an item to the data Map in fluent style.
-     * @param key The name of the data item.
-     * @param value The value of the data item.
-     * @return {@code this}
-     */
-    public MapMessage with(final String key, final String value) {
-        put(key, value);
-        return this;
-    }
-
-    /**
-     * Adds an item to the data Map.
-     * @param key The name of the data item.
-     * @param value The value of the data item.
-     */
-    public void put(final String key, final String value) {
-        if (value == null) {
-            throw new IllegalArgumentException("No value provided for key " + key);
-        }
-        validate(key, value);
-        data.putValue(key, value);
-    }
-
-    protected void validate(final String key, final String value) {
-
-    }
-
-    /**
-     * Adds all the elements from the specified Map.
-     * @param map The Map to add.
-     */
-    public void putAll(final Map<String, String> map) {
-        for (final Map.Entry<String, ?> entry : map.entrySet()) {
-            data.putValue(entry.getKey(), entry.getValue());
-        }
-    }
-
-    /**
-     * Retrieves the value of the element with the specified key or null if the key is not present.
-     * @param key The name of the element.
-     * @return The value of the element or null if the key is not present.
-     */
-    public String get(final String key) {
-        return data.getValue(key);
-    }
-
-    /**
-     * Removes the element with the specified name.
-     * @param key The name of the element.
-     * @return The previous value of the element.
-     */
-    public String remove(final String key) {
-        final String result = data.getValue(key);
-        data.remove(key);
-        return result;
-    }
-
-    /**
-     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
-     *
-     * @return The formatted String.
-     */
-    public String asString() {
-        return format((MapFormat) null, new StringBuilder()).toString();
-    }
-
-    /**
-     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
-     *
-     * @param format The format identifier.
-     * @return The formatted String.
-     */
-    public String asString(final String format) {
-        try {
-            return format(EnglishEnums.valueOf(MapFormat.class, format), new StringBuilder()).toString();
-        } catch (final IllegalArgumentException ex) {
-            return asString();
-        }
-    }
-    
-    /**
-     * Performs the given action for each key-value pair in this data structure
-     * until all entries have been processed or the action throws an exception.
-     * <p>
-     * Some implementations may not support structural modifications (adding new elements or removing elements) while
-     * iterating over the contents. In such implementations, attempts to add or remove elements from the
-     * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} method may cause a
-     * {@code ConcurrentModificationException} to be thrown.
-     * </p>
-     *
-     * @param action The action to be performed for each key-value pair in this collection
-     * @param <V> type of the value
-     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
-     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
-     *          {@link #forEach(TriConsumer, Object)}.
-     * @see ReadOnlyStringMap#forEach(BiConsumer)
-     * @since 2.9
-     */
-    public <V> void forEach(final BiConsumer<String, ? super V> action) {
-        data.forEach(action);
-    }
-
-    /**
-     * Performs the given action for each key-value pair in this data structure
-     * until all entries have been processed or the action throws an exception.
-     * <p>
-     * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs,
-     * so the TriConsumer implementation itself can be stateless and potentially reusable.
-     * </p>
-     * <p>
-     * Some implementations may not support structural modifications (adding new elements or removing elements) while
-     * iterating over the contents. In such implementations, attempts to add or remove elements from the
-     * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) accept} method may cause a
-     * {@code ConcurrentModificationException} to be thrown.
-     * </p>
-     *
-     * @param action The action to be performed for each key-value pair in this collection
-     * @param state the object to be passed as the third parameter to each invocation on the specified
-     *          triconsumer
-     * @param <V> type of the value
-     * @param <S> type of the third parameter
-     * @throws java.util.ConcurrentModificationException some implementations may not support structural modifications
-     *          to this data structure while iterating over the contents with {@link #forEach(BiConsumer)} or
-     *          {@link #forEach(TriConsumer, Object)}.
-     * @see ReadOnlyStringMap#forEach(TriConsumer, Object)
-     * @since 2.9
-     */
-    public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
-        data.forEach(action, state);
-    }
-    
-    /**
-     * Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
-     *
-     * @param format The format identifier.
-     * @return The formatted String.
-     */
-    private StringBuilder format(final MapFormat format, final StringBuilder sb) {
-        if (format == null) {
-            appendMap(sb);
-        } else {
-            switch (format) {
-                case XML : {
-                    asXml(sb);
-                    break;
-                }
-                case JSON : {
-                    asJson(sb);
-                    break;
-                }
-                case JAVA : {
-                    asJava(sb);
-                    break;
-                }
-                default : {
-                    appendMap(sb);
-                }
-            }
-        }
-        return sb;
-    }
-
-    /**
-     * Formats this message as an XML fragment String into the given builder.
-     *
-     * @param sb format into this builder.
-     */
-    public void asXml(final StringBuilder sb) {
-        sb.append("<Map>\n");
-        for (int i = 0; i < data.size(); i++) {
-            sb.append("  <Entry key=\"").append(data.getKeyAt(i)).append("\">").append((String)data.getValueAt(i))
-                    .append("</Entry>\n");
-        }
-        sb.append("</Map>");
-    }
-
-    /**
-     * Formats the message and return it.
-     * @return the formatted message.
-     */
-    @Override
-    public String getFormattedMessage() {
-        return asString();
-    }
-
-    /**
-     *
-     * @param formats
-     *            An array of Strings that provide extra information about how to format the message. MapMessage uses
-     *            the first format specifier it recognizes. The supported formats are XML, JSON, and JAVA. The default
-     *            format is key1="value1" key2="value2" as required by <a href="https://tools.ietf.org/html/rfc5424">RFC
-     *            5424</a> messages.
-     *
-     * @return The formatted message.
-     */
-    @Override
-    public String getFormattedMessage(final String[] formats) {
-        if (formats == null || formats.length == 0) {
-            return asString();
-        }
-        for (int i = 0; i < formats.length; i++) {
-            final MapFormat mapFormat = MapFormat.lookupIgnoreCase(formats[i]);
-            if (mapFormat != null) {
-                return format(mapFormat, new StringBuilder()).toString();
-            }
-        }
-        return asString();
-
-    }
-
-    protected void appendMap(final StringBuilder sb) {
-        for (int i = 0; i < data.size(); i++) {
-            if (i > 0) {
-                sb.append(' ');
-            }
-            StringBuilders.appendKeyDqValue(sb, data.getKeyAt(i), data.getValueAt(i));
-        }
-    }
-
-    protected void asJson(final StringBuilder sb) {
-        sb.append('{');
-        for (int i = 0; i < data.size(); i++) {
-            if (i > 0) {
-                sb.append(", ");
-            }
-            StringBuilders.appendDqValue(sb, data.getKeyAt(i)).append(':');
-            StringBuilders.appendDqValue(sb, data.getValueAt(i));
-        }
-        sb.append('}');
-    }
-
-
-    protected void asJava(final StringBuilder sb) {
-        sb.append('{');
-        for (int i = 0; i < data.size(); i++) {
-            if (i > 0) {
-                sb.append(", ");
-            }
-            StringBuilders.appendKeyDqValue(sb, data.getKeyAt(i), data.getValueAt(i));
-        }
-        sb.append('}');
+    public MapMessage(Map<String, String> map) {
+        super(map);
     }
 
     /**
@@ -418,43 +39,4 @@ public class MapMessage implements MultiformatMessage, StringBuilderFormattable
     public MapMessage newInstance(final Map<String, String> map) {
         return new MapMessage(map);
     }
-
-    @Override
-    public String toString() {
-        return asString();
-    }
-
-    @Override
-    public void formatTo(final StringBuilder buffer) {
-        format((MapFormat) null, buffer);
-    }
-
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || this.getClass() != o.getClass()) {
-            return false;
-        }
-
-        final MapMessage that = (MapMessage) o;
-
-        return this.data.equals(that.data);
-    }
-
-    @Override
-    public int hashCode() {
-        return data.hashCode();
-    }
-
-    /**
-     * Always returns null.
-     *
-     * @return null
-     */
-    @Override
-    public Throwable getThrowable() {
-        return null;
-    }
 }