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, 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}