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