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 = messagePattern;
53          this.argArray = arguments;
54          this.throwable = null;
55      }
56  
57      /**
58       * Constructor with a pattern and a single parameter.
59       * @param messagePattern The message pattern.
60       * @param arg The parameter.
61       */
62      public FormattedMessage(final String messagePattern, final Object arg) {
63          this.messagePattern = messagePattern;
64          this.argArray = new Object[] {arg};
65          this.throwable = null;
66      }
67  
68      /**
69       * Constructor with a pattern and two parameters.
70       * @param messagePattern The message pattern.
71       * @param arg1 The first parameter.
72       * @param arg2 The second parameter.
73       */
74      public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
75          this(messagePattern, new Object[]{arg1, arg2});
76      }
77  
78  
79      /**
80       * Returns the formatted message.
81       * @return the formatted message.
82       */
83      @Override
84      public String getFormattedMessage() {
85          if (formattedMessage == null) {
86              if (message == null) {
87                  message = getMessage(messagePattern, argArray, throwable);
88              }
89              formattedMessage = message.getFormattedMessage();
90          }
91          return formattedMessage;
92      }
93  
94      /**
95       * Returns the message pattern.
96       * @return the message pattern.
97       */
98      @Override
99      public String getFormat() {
100         return messagePattern;
101     }
102 
103     /**
104      * Returns the message parameters.
105      * @return the message parameters.
106      */
107     @Override
108     public Object[] getParameters() {
109         if (argArray != null) {
110             return argArray;
111         }
112         return stringArgs;
113     }
114 
115     protected Message getMessage(final String msgPattern, final Object[] args, final Throwable throwable) {
116         try {
117             final MessageFormat format = new MessageFormat(msgPattern);
118             final Format[] formats = format.getFormats();
119             if (formats != null && formats.length > 0) {
120                 return new MessageFormatMessage(msgPattern, args);
121             }
122         } catch (final Exception ex) {
123             // Obviously, the message is not a proper pattern for MessageFormat.
124         }
125         try {
126             if (MSG_PATTERN.matcher(msgPattern).find()) {
127                 return new StringFormattedMessage(msgPattern, args);
128             }
129         } catch (final Exception ex) {
130             // Also not properly formatted.
131         }
132         return new ParameterizedMessage(msgPattern, args, throwable);
133     }
134 
135     @Override
136     public boolean equals(final Object o) {
137         if (this == o) {
138             return true;
139         }
140         if (o == null || getClass() != o.getClass()) {
141             return false;
142         }
143 
144         final FormattedMessage that = (FormattedMessage) o;
145 
146         if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
147             return false;
148         }
149         if (!Arrays.equals(stringArgs, that.stringArgs)) {
150             return false;
151         }
152 
153         return true;
154     }
155 
156     @Override
157     public int hashCode() {
158         int result = messagePattern != null ? messagePattern.hashCode() : 0;
159         result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
160         return result;
161     }
162 
163 
164     @Override
165     public String toString() {
166         return "FormattedMessage[messagePattern=" + messagePattern + ", args=" +
167             Arrays.toString(argArray) + ']';
168     }
169 
170     private void writeObject(final ObjectOutputStream out) throws IOException {
171         out.defaultWriteObject();
172         getFormattedMessage();
173         out.writeUTF(formattedMessage);
174         out.writeUTF(messagePattern);
175         out.writeInt(argArray.length);
176         stringArgs = new String[argArray.length];
177         int i = 0;
178         for (final Object obj : argArray) {
179             stringArgs[i] = obj.toString();
180             ++i;
181         }
182     }
183 
184     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
185         in.defaultReadObject();
186         formattedMessage = in.readUTF();
187         messagePattern = in.readUTF();
188         final int length = in.readInt();
189         stringArgs = new String[length];
190         for (int i = 0; i < length; ++i) {
191             stringArgs[i] = in.readUTF();
192         }
193     }
194 
195     /**
196      * Always returns null.
197      *
198      * @return null
199      */
200     @Override
201     public Throwable getThrowable() {
202         if (throwable != null) {
203             return throwable;
204         }
205         if (message == null) {
206             message = getMessage(messagePattern, argArray, null);
207         }
208         return message.getThrowable();
209     }
210 }