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 */
017 package org.apache.logging.log4j.message;
018
019 import java.io.IOException;
020 import java.io.ObjectInputStream;
021 import java.io.ObjectOutputStream;
022 import java.text.Format;
023 import java.text.MessageFormat;
024 import java.util.Arrays;
025 import java.util.regex.Pattern;
026
027 /**
028 * Handles messages that contain a format String. Dynamically determines if the format conforms to
029 * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
030 */
031 public class FormattedMessage implements Message {
032
033 private static final long serialVersionUID = -665975803997290697L;
034 private static final int HASHVAL = 31;
035 private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
036 private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
037
038 private String messagePattern;
039 private transient Object[] argArray;
040 private String[] stringArgs;
041 private transient String formattedMessage;
042 private final Throwable throwable;
043 private Message message;
044
045 public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
046 this.messagePattern = messagePattern;
047 this.argArray = arguments;
048 this.throwable = throwable;
049 }
050
051 public FormattedMessage(final String messagePattern, final Object[] arguments) {
052 this(messagePattern, arguments, null);
053 }
054
055 /**
056 * Constructor with a pattern and a single parameter.
057 * @param messagePattern The message pattern.
058 * @param arg The parameter.
059 */
060 public FormattedMessage(final String messagePattern, final Object arg) {
061 this(messagePattern, new Object[] {arg}, null);
062 }
063
064 /**
065 * Constructor with a pattern and two parameters.
066 * @param messagePattern The message pattern.
067 * @param arg1 The first parameter.
068 * @param arg2 The second parameter.
069 */
070 public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
071 this(messagePattern, new Object[]{arg1, arg2});
072 }
073
074
075 /**
076 * Returns the formatted message.
077 * @return the formatted message.
078 */
079 @Override
080 public String getFormattedMessage() {
081 if (formattedMessage == null) {
082 if (message == null) {
083 message = getMessage(messagePattern, argArray, throwable);
084 }
085 formattedMessage = message.getFormattedMessage();
086 }
087 return formattedMessage;
088 }
089
090 /**
091 * Returns the message pattern.
092 * @return the message pattern.
093 */
094 @Override
095 public String getFormat() {
096 return messagePattern;
097 }
098
099 /**
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 }