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.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 */ 031public 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 = messagePattern; 053 this.argArray = arguments; 054 this.throwable = null; 055 } 056 057 /** 058 * Constructor with a pattern and a single parameter. 059 * @param messagePattern The message pattern. 060 * @param arg The parameter. 061 */ 062 public FormattedMessage(final String messagePattern, final Object arg) { 063 this.messagePattern = messagePattern; 064 this.argArray = new Object[] {arg}; 065 this.throwable = null; 066 } 067 068 /** 069 * Constructor with a pattern and two parameters. 070 * @param messagePattern The message pattern. 071 * @param arg1 The first parameter. 072 * @param arg2 The second parameter. 073 */ 074 public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) { 075 this(messagePattern, new Object[]{arg1, arg2}); 076 } 077 078 079 /** 080 * Returns the formatted message. 081 * @return the formatted message. 082 */ 083 @Override 084 public String getFormattedMessage() { 085 if (formattedMessage == null) { 086 if (message == null) { 087 message = getMessage(messagePattern, argArray, throwable); 088 } 089 formattedMessage = message.getFormattedMessage(); 090 } 091 return formattedMessage; 092 } 093 094 /** 095 * Returns the message pattern. 096 * @return the message pattern. 097 */ 098 @Override 099 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, throwable); 207 } 208 return message.getThrowable(); 209 } 210}