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}