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 org.apache.logging.log4j.Logger;
20  import org.apache.logging.log4j.status.StatusLogger;
21  
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.text.Format;
26  import java.text.MessageFormat;
27  import java.util.Arrays;
28  import java.util.regex.Pattern;
29  
30  /**
31   * Handles messages that contain a format String. Dynamically determines if the format conforms to
32   * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
33   */
34  public class FormattedMessage implements Message {
35  
36      private static final Logger LOGGER = StatusLogger.getLogger();
37  
38      private static final long serialVersionUID = -665975803997290697L;
39  
40      private static final int HASHVAL = 31;
41  
42      private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
43  
44      private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
45  
46      private String messagePattern;
47      private transient Object[] argArray;
48      private String[] stringArgs;
49      private transient String formattedMessage;
50      private final Throwable throwable;
51  
52      private Message message;
53  
54      public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
55          this.messagePattern = messagePattern;
56          this.argArray = arguments;
57          this.throwable = throwable;
58      }
59  
60      public FormattedMessage(final String messagePattern, final Object[] arguments) {
61          this.messagePattern = messagePattern;
62          this.argArray = arguments;
63          this.throwable = null;
64      }
65  
66      /**
67       * Constructor with a pattern and a single parameter.
68       * @param messagePattern The message pattern.
69       * @param arg The parameter.
70       */
71      public FormattedMessage(final String messagePattern, final Object arg) {
72          this.messagePattern = messagePattern;
73          this.argArray = new Object[] {arg};
74          this.throwable = null;
75      }
76  
77      /**
78       * Constructor with a pattern and two parameters.
79       * @param messagePattern The message pattern.
80       * @param arg1 The first parameter.
81       * @param arg2 The second parameter.
82       */
83      public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
84          this(messagePattern, new Object[]{arg1, arg2});
85      }
86  
87  
88      /**
89       * Returns the formatted message.
90       * @return the formatted message.
91       */
92      @Override
93      public String getFormattedMessage() {
94          if (formattedMessage == null) {
95              if (message == null) {
96                  message = getMessage(messagePattern, argArray, throwable);
97              }
98              formattedMessage = message.getFormattedMessage();
99          }
100         return formattedMessage;
101     }
102 
103     /**
104      * Returns the message pattern.
105      * @return the message pattern.
106      */
107     @Override
108     public String getFormat() {
109         return messagePattern;
110     }
111 
112     /**
113      * Returns the message parameters.
114      * @return the message parameters.
115      */
116     @Override
117     public Object[] getParameters() {
118         if (argArray != null) {
119             return argArray;
120         }
121         return stringArgs;
122     }
123 
124     protected Message getMessage(final String msgPattern, final Object[] args, final Throwable throwable) {
125         try {
126             final MessageFormat format = new MessageFormat(msgPattern);
127             final Format[] formats = format.getFormats();
128             if (formats != null && formats.length > 0) {
129                 return new MessageFormatMessage(msgPattern, args);
130             }
131         } catch (final Exception ex) {
132             // Obviously, the message is not a proper pattern for MessageFormat.
133         }
134         try {
135             if (MSG_PATTERN.matcher(msgPattern).find()) {
136                 return new StringFormattedMessage(msgPattern, args);
137             }
138         } catch (final Exception ex) {
139             // Also not properly formatted.
140         }
141         return new ParameterizedMessage(msgPattern, args, throwable);
142     }
143 
144     @Override
145     public boolean equals(final Object o) {
146         if (this == o) {
147             return true;
148         }
149         if (o == null || getClass() != o.getClass()) {
150             return false;
151         }
152 
153         final FormattedMessage that = (FormattedMessage) o;
154 
155         if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
156             return false;
157         }
158         if (!Arrays.equals(stringArgs, that.stringArgs)) {
159             return false;
160         }
161 
162         return true;
163     }
164 
165     @Override
166     public int hashCode() {
167         int result = messagePattern != null ? messagePattern.hashCode() : 0;
168         result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
169         return result;
170     }
171 
172 
173     @Override
174     public String toString() {
175         return "FormattedMessage[messagePattern=" + messagePattern + ", args=" +
176             Arrays.toString(argArray) +  "]";
177     }
178 
179     private void writeObject(final ObjectOutputStream out) throws IOException {
180         out.defaultWriteObject();
181         getFormattedMessage();
182         out.writeUTF(formattedMessage);
183         out.writeUTF(messagePattern);
184         out.writeInt(argArray.length);
185         stringArgs = new String[argArray.length];
186         int i = 0;
187         for (final Object obj : argArray) {
188             stringArgs[i] = obj.toString();
189             ++i;
190         }
191     }
192 
193     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
194         in.defaultReadObject();
195         formattedMessage = in.readUTF();
196         messagePattern = in.readUTF();
197         final int length = in.readInt();
198         stringArgs = new String[length];
199         for (int i = 0; i < length; ++i) {
200             stringArgs[i] = in.readUTF();
201         }
202     }
203 
204     /**
205      * Always returns null.
206      *
207      * @return null
208      */
209     @Override
210     public Throwable getThrowable() {
211         if (throwable != null) {
212             return throwable;
213         }
214         if (message == null) {
215             message = getMessage(messagePattern, argArray, throwable);
216         }
217         return message.getThrowable();
218     }
219 }