View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.message;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.text.Format;
23  import java.text.MessageFormat;
24  import java.util.Arrays;
25  import java.util.Locale;
26  import java.util.regex.Pattern;
27  
28  /**
29   * Handles messages that contain a format String. Dynamically determines if the format conforms to
30   * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
31   */
32  public class FormattedMessage implements Message {
33  
34      private static final long serialVersionUID = -665975803997290697L;
35      private static final int HASHVAL = 31;
36      private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
37      private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
38  
39      private String messagePattern;
40      private transient Object[] argArray;
41      private String[] stringArgs;
42      private transient String formattedMessage;
43      private final Throwable throwable;
44      private Message message;
45      private final Locale locale;
46      
47      /**
48       * Constructs with a locale, a pattern and a single parameter.
49       * @param locale The locale
50       * @param messagePattern The message pattern.
51       * @param arg The parameter.
52       * @since 2.6
53       */
54      public FormattedMessage(final Locale locale, final String messagePattern, final Object arg) {
55          this(locale, messagePattern, new Object[] { arg }, null);
56      }
57  
58      /**
59       * Constructs with a locale, a pattern and two parameters.
60       * @param locale The locale
61       * @param messagePattern The message pattern.
62       * @param arg1 The first parameter.
63       * @param arg2 The second parameter.
64       * @since 2.6
65       */
66      public FormattedMessage(final Locale locale, final String messagePattern, final Object arg1, final Object arg2) {
67          this(locale, messagePattern, new Object[] { arg1, arg2 });
68      }
69  
70      /**
71       * Constructs with a locale, a pattern and a parameter array.
72       * @param locale The locale
73       * @param messagePattern The message pattern.
74       * @param arguments The parameter.
75       * @since 2.6
76       */
77      public FormattedMessage(final Locale locale, final String messagePattern, final Object... arguments) {
78          this(locale, messagePattern, arguments, null);
79      }
80  
81      /**
82       * Constructs with a locale, a pattern, a parameter array, and a throwable.
83       * @param locale The Locale
84       * @param messagePattern The message pattern.
85       * @param arguments The parameter.
86       * @param throwable The throwable
87       * @since 2.6
88       */
89      public FormattedMessage(final Locale locale, final String messagePattern, final Object[] arguments, final Throwable throwable) {
90          this.locale = locale;
91          this.messagePattern = messagePattern;
92          this.argArray = arguments;
93          this.throwable = throwable;
94      }
95  
96      /**
97       * Constructs with a pattern and a single parameter.
98       * @param messagePattern The message pattern.
99       * @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 }