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.regex.Pattern;
26  
27  /**
28   * Handles messages that contain a format String. Dynamically determines if the format conforms to
29   * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
30   */
31  public class FormattedMessage implements Message {
32  
33      private static final long serialVersionUID = -665975803997290697L;
34      private static final int HASHVAL = 31;
35      private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
36      private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
37  
38      private String messagePattern;
39      private transient Object[] argArray;
40      private String[] stringArgs;
41      private transient String formattedMessage;
42      private final Throwable throwable;
43      private Message message;
44  
45      public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
46          this.messagePattern = messagePattern;
47          this.argArray = arguments;
48          this.throwable = throwable;
49      }
50  
51      public FormattedMessage(final String messagePattern, final Object[] arguments) {
52          this(messagePattern, arguments, null);
53      }
54  
55      /**
56       * Constructor with a pattern and a single parameter.
57       * @param messagePattern The message pattern.
58       * @param arg The parameter.
59       */
60      public FormattedMessage(final String messagePattern, final Object arg) {
61          this(messagePattern, new Object[] {arg}, null);
62      }
63  
64      /**
65       * Constructor with a pattern and two parameters.
66       * @param messagePattern The message pattern.
67       * @param arg1 The first parameter.
68       * @param arg2 The second parameter.
69       */
70      public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
71          this(messagePattern, new Object[]{arg1, arg2});
72      }
73  
74  
75      /**
76       * Returns the formatted message.
77       * @return the formatted message.
78       */
79      @Override
80      public String getFormattedMessage() {
81          if (formattedMessage == null) {
82              if (message == null) {
83                  message = getMessage(messagePattern, argArray, throwable);
84              }
85              formattedMessage = message.getFormattedMessage();
86          }
87          return formattedMessage;
88      }
89  
90      /**
91       * Returns the message pattern.
92       * @return the message pattern.
93       */
94      @Override
95      public String getFormat() {
96          return messagePattern;
97      }
98  
99      /**
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 }