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.Locale;
026import java.util.regex.Pattern;
027
028/**
029 * Handles messages that contain a format String. Dynamically determines if the format conforms to
030 * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
031 */
032public class FormattedMessage implements Message {
033
034    private static final long serialVersionUID = -665975803997290697L;
035    private static final int HASHVAL = 31;
036    private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
037    private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
038
039    private String messagePattern;
040    private transient Object[] argArray;
041    private String[] stringArgs;
042    private transient String formattedMessage;
043    private final Throwable throwable;
044    private Message message;
045    private final Locale locale;
046    
047    /**
048     * Constructs with a locale, a pattern and a single parameter.
049     * @param locale The locale
050     * @param messagePattern The message pattern.
051     * @param arg The parameter.
052     * @since 2.6
053     */
054    public FormattedMessage(final Locale locale, final String messagePattern, final Object arg) {
055        this(locale, messagePattern, new Object[] { arg }, null);
056    }
057
058    /**
059     * Constructs with a locale, a pattern and two parameters.
060     * @param locale The locale
061     * @param messagePattern The message pattern.
062     * @param arg1 The first parameter.
063     * @param arg2 The second parameter.
064     * @since 2.6
065     */
066    public FormattedMessage(final Locale locale, final String messagePattern, final Object arg1, final Object arg2) {
067        this(locale, messagePattern, new Object[] { arg1, arg2 });
068    }
069
070    /**
071     * Constructs with a locale, a pattern and a parameter array.
072     * @param locale The locale
073     * @param messagePattern The message pattern.
074     * @param arguments The parameter.
075     * @since 2.6
076     */
077    public FormattedMessage(final Locale locale, final String messagePattern, final Object... arguments) {
078        this(locale, messagePattern, arguments, null);
079    }
080
081    /**
082     * Constructs with a locale, a pattern, a parameter array, and a throwable.
083     * @param locale The Locale
084     * @param messagePattern The message pattern.
085     * @param arguments The parameter.
086     * @param throwable The throwable
087     * @since 2.6
088     */
089    public FormattedMessage(final Locale locale, final String messagePattern, final Object[] arguments, final Throwable throwable) {
090        this.locale = locale;
091        this.messagePattern = messagePattern;
092        this.argArray = arguments;
093        this.throwable = throwable;
094    }
095
096    /**
097     * Constructs with a pattern and a single parameter.
098     * @param messagePattern The message pattern.
099     * @param arg The parameter.
100     */
101    public FormattedMessage(final String messagePattern, final Object arg) {
102        this(messagePattern, new Object[] { arg }, null);
103    }
104
105    /**
106     * Constructs with a pattern and two parameters.
107     * @param messagePattern The message pattern.
108     * @param arg1 The first parameter.
109     * @param arg2 The second parameter.
110     */
111    public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
112        this(messagePattern, new Object[] { arg1, arg2 });
113    }
114
115    /**
116     * Constructs with a pattern and a parameter array.
117     * @param messagePattern The message pattern.
118     * @param arguments The parameter.
119     */
120    public FormattedMessage(final String messagePattern, final Object... arguments) {
121        this(messagePattern, arguments, null);
122    }
123
124    /**
125     * Constructs with a pattern, a parameter array, and a throwable.
126     * @param messagePattern The message pattern.
127     * @param arguments The parameter.
128     * @param throwable The throwable
129     */
130    public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
131        this.locale = Locale.getDefault(Locale.Category.FORMAT);
132        this.messagePattern = messagePattern;
133        this.argArray = arguments;
134        this.throwable = throwable;
135    }
136
137
138    @Override
139    public boolean equals(final Object o) {
140        if (this == o) {
141            return true;
142        }
143        if (o == null || getClass() != o.getClass()) {
144            return false;
145        }
146
147        final FormattedMessage that = (FormattedMessage) o;
148
149        if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
150            return false;
151        }
152        if (!Arrays.equals(stringArgs, that.stringArgs)) {
153            return false;
154        }
155
156        return true;
157    }
158
159    /**
160     * Gets the message pattern.
161     * @return the message pattern.
162     */
163    @Override
164    public String getFormat() {
165        return messagePattern;
166    }
167
168    /**
169     * Gets the formatted message.
170     * @return the formatted message.
171     */
172    @Override
173    public String getFormattedMessage() {
174        if (formattedMessage == null) {
175            if (message == null) {
176                message = getMessage(messagePattern, argArray, throwable);
177            }
178            formattedMessage = message.getFormattedMessage();
179        }
180        return formattedMessage;
181    }
182
183    protected Message getMessage(final String msgPattern, final Object[] args, final Throwable aThrowable) {
184        try {
185            final MessageFormat format = new MessageFormat(msgPattern);
186            final Format[] formats = format.getFormats();
187            if (formats != null && formats.length > 0) {
188                return new MessageFormatMessage(locale, msgPattern, args);
189            }
190        } catch (final Exception ignored) {
191            // Obviously, the message is not a proper pattern for MessageFormat.
192        }
193        try {
194            if (MSG_PATTERN.matcher(msgPattern).find()) {
195                return new StringFormattedMessage(locale, msgPattern, args);
196            }
197        } catch (final Exception ignored) {
198            // Also not properly formatted.
199        }
200        return new ParameterizedMessage(msgPattern, args, aThrowable);
201    }
202
203    /**
204     * Gets the message parameters.
205     * @return the message parameters.
206     */
207    @Override
208    public Object[] getParameters() {
209        if (argArray != null) {
210            return argArray;
211        }
212        return stringArgs;
213    }
214
215    @Override
216    public Throwable getThrowable() {
217        if (throwable != null) {
218            return throwable;
219        }
220        if (message == null) {
221            message = getMessage(messagePattern, argArray, null);
222        }
223        return message.getThrowable();
224    }
225
226
227    @Override
228    public int hashCode() {
229        int result = messagePattern != null ? messagePattern.hashCode() : 0;
230        result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
231        return result;
232    }
233
234    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
235        in.defaultReadObject();
236        formattedMessage = in.readUTF();
237        messagePattern = in.readUTF();
238        final int length = in.readInt();
239        stringArgs = new String[length];
240        for (int i = 0; i < length; ++i) {
241            stringArgs[i] = in.readUTF();
242        }
243    }
244
245    @Override
246    public String toString() {
247        return getFormattedMessage();
248    }
249
250    private void writeObject(final ObjectOutputStream out) throws IOException {
251        out.defaultWriteObject();
252        getFormattedMessage();
253        out.writeUTF(formattedMessage);
254        out.writeUTF(messagePattern);
255        out.writeInt(argArray.length);
256        stringArgs = new String[argArray.length];
257        int i = 0;
258        for (final Object obj : argArray) {
259            final String string = String.valueOf(obj);
260            stringArgs[i] = string;
261            out.writeUTF(string);
262            ++i;
263        }
264    }
265}