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