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 */
017package org.apache.logging.log4j.message;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.ObjectOutputStream;
022import java.text.Format;
023import java.text.MessageFormat;
024import java.util.Arrays;
025import 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 */
031public 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}