    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017    package org.apache.logging.log4j.message;
    018
    019    import java.io.IOException;
    020    import java.io.ObjectInputStream;
    021    import java.io.ObjectOutputStream;
    022    import java.text.Format;
    023    import java.text.MessageFormat;
    024    import java.util.Arrays;
    025    import java.util.regex.Pattern;
    026
    027    /**
    028     * Handles messages that contain a format String. Dynamically determines if the format conforms to
    029     * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
    030     */
    031    public class FormattedMessage implements Message {
    032
    033        private static final long serialVersionUID = -665975803997290697L;
    034        private static final int HASHVAL = 31;
    035        private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
    036        private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
    037
    038        private String messagePattern;
    039        private transient Object[] argArray;
    040        private String[] stringArgs;
    041        private transient String formattedMessage;
    042        private final Throwable throwable;
    043        private Message message;
    044
    045        public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
    046            this.messagePattern = messagePattern;
    047            this.argArray = arguments;
    048            this.throwable = throwable;
    049        }
    050
    051        public FormattedMessage(final String messagePattern, final Object[] arguments) {
    052            this(messagePattern, arguments, null);
    053        }
    054
    055        /**
    056         * Constructor with a pattern and a single parameter.
    057         * @param messagePattern The message pattern.
    058         * @param arg The parameter.
    059         */
    060        public FormattedMessage(final String messagePattern, final Object arg) {
    061            this(messagePattern, new Object[] {arg}, null);
    062        }
    063
    064        /**
    065         * Constructor with a pattern and two parameters.
    066         * @param messagePattern The message pattern.
    067         * @param arg1 The first parameter.
    068         * @param arg2 The second parameter.
    069         */
    070        public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
    071            this(messagePattern, new Object[]{arg1, arg2});
    072        }
    073
    074
    075        /**
    076         * Returns the formatted message.
    077         * @return the formatted message.
    078         */
    079        @Override
    080        public String getFormattedMessage() {
    081            if (formattedMessage == null) {
    082                if (message == null) {
    083                    message = getMessage(messagePattern, argArray, throwable);
    084                }
    085                formattedMessage = message.getFormattedMessage();
    086            }
    087            return formattedMessage;
    088        }
    089
    090        /**
    091         * Returns the message pattern.
    092         * @return the message pattern.
    093         */
    094        @Override
    095        public String getFormat() {
    096            return messagePattern;
    097        }
    098
    099        /**
    100         * Returns the message parameters.
    101         * @return the message parameters.
    102         */
    103        @Override
    104        public Object[] getParameters() {
    105            if (argArray != null) {
    106                return argArray;
    107            }
    108            return stringArgs;
    109        }
    110
    111        protected Message getMessage(final String msgPattern, final Object[] args, final Throwable throwable) {
    112            try {
    113                final MessageFormat format = new MessageFormat(msgPattern);
    114                final Format[] formats = format.getFormats();
    115                if (formats != null && formats.length > 0) {
    116                    return new MessageFormatMessage(msgPattern, args);
    117                }
    118            } catch (final Exception ignored) {
    119                // Obviously, the message is not a proper pattern for MessageFormat.
    120            }
    121            try {
    122                if (MSG_PATTERN.matcher(msgPattern).find()) {
    123                    return new StringFormattedMessage(msgPattern, args);
    124                }
    125            } catch (final Exception ignored) {
    126                // Also not properly formatted.
    127            }
    128            return new ParameterizedMessage(msgPattern, args, throwable);
    129        }
    130
    131        @Override
    132        public boolean equals(final Object o) {
    133            if (this == o) {
    134                return true;
    135            }
    136            if (o == null || getClass() != o.getClass()) {
    137                return false;
    138            }
    139
    140            final FormattedMessage that = (FormattedMessage) o;
    141
    142            if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
    143                return false;
    144            }
    145            if (!Arrays.equals(stringArgs, that.stringArgs)) {
    146                return false;
    147            }
    148
    149            return true;
    150        }
    151
    152        @Override
    153        public int hashCode() {
    154            int result = messagePattern != null ? messagePattern.hashCode() : 0;
    155            result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
    156            return result;
    157        }
    158
    159
    160        @Override
    161        public String toString() {
    162            return "FormattedMessage[messagePattern=" + messagePattern + ", args=" +
    163                Arrays.toString(argArray) + ']';
    164        }
    165
    166        private void writeObject(final ObjectOutputStream out) throws IOException {
    167            out.defaultWriteObject();
    168            getFormattedMessage();
    169            out.writeUTF(formattedMessage);
    170            out.writeUTF(messagePattern);
    171            out.writeInt(argArray.length);
    172            stringArgs = new String[argArray.length];
    173            int i = 0;
    174            for (final Object obj : argArray) {
    175                stringArgs[i] = obj.toString();
    176                ++i;
    177            }
    178        }
    179
    180        private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
    181            in.defaultReadObject();
    182            formattedMessage = in.readUTF();
    183            messagePattern = in.readUTF();
    184            final int length = in.readInt();
    185            stringArgs = new String[length];
    186            for (int i = 0; i < length; ++i) {
    187                stringArgs[i] = in.readUTF();
    188            }
    189        }
    190
    191        /**
    192         * Always returns null.
    193         *
    194         * @return null
    195         */
    196        @Override
    197        public Throwable getThrowable() {
    198            if (throwable != null) {
    199                return throwable;
    200            }
    201            if (message == null) {
    202                message = getMessage(messagePattern, argArray, null);
    203            }
    204            return message.getThrowable();
    205        }
    206    }