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.util.Arrays;
023import java.util.IllegalFormatException;
024import java.util.Locale;
025
026import org.apache.logging.log4j.Logger;
027import org.apache.logging.log4j.status.StatusLogger;
028
029/**
030 * Handles messages that consist of a format string conforming to {@link java.util.Formatter}.
031 * 
032 * <h4>Note to implementors</h4>
033 * <p>
034 * This class implements the unrolled args API even though StringFormattedMessage does not. This leaves the room for
035 * StringFormattedMessage to unroll itself later.
036 * </p>
037 */
038public class StringFormattedMessage implements Message {
039
040    private static final Logger LOGGER = StatusLogger.getLogger();
041
042    private static final long serialVersionUID = -665975803997290697L;
043
044    private static final int HASHVAL = 31;
045
046    private String messagePattern;
047    private transient Object[] argArray;
048    private String[] stringArgs;
049    private transient String formattedMessage;
050    private transient Throwable throwable;
051    private final Locale locale;
052    
053   /**
054    * Constructs a message.
055    * 
056    * @param locale the locale for this message format
057    * @param messagePattern the pattern for this message format
058    * @param arguments The objects to format
059    * @since 2.6
060    */
061    public StringFormattedMessage(final Locale locale, final String messagePattern, final Object... arguments) {
062        this.locale = locale;
063        this.messagePattern = messagePattern;
064        this.argArray = arguments;
065        if (arguments != null && arguments.length > 0 && arguments[arguments.length - 1] instanceof Throwable) {
066            this.throwable = (Throwable) arguments[arguments.length - 1];
067        }
068    }
069
070    /**
071     * Constructs a message.
072     * 
073     * @param messagePattern the pattern for this message format
074     * @param arguments The objects to format
075     * @since 2.6
076     */
077    public StringFormattedMessage(final String messagePattern, final Object... arguments) {
078        this(Locale.getDefault(Locale.Category.FORMAT), messagePattern, arguments);
079    }
080
081    /**
082     * Returns the formatted message.
083     * @return the formatted message.
084     */
085    @Override
086    public String getFormattedMessage() {
087        if (formattedMessage == null) {
088            formattedMessage = formatMessage(messagePattern, argArray);
089        }
090        return formattedMessage;
091    }
092
093    /**
094     * Returns the message pattern.
095     * @return the message pattern.
096     */
097    @Override
098    public String getFormat() {
099        return messagePattern;
100    }
101
102    /**
103     * Returns the message parameters.
104     * @return the message parameters.
105     */
106    @Override
107    public Object[] getParameters() {
108        if (argArray != null) {
109            return argArray;
110        }
111        return stringArgs;
112    }
113
114    protected String formatMessage(final String msgPattern, final Object... args) {
115        try {
116            return String.format(locale, msgPattern, args);
117        } catch (final IllegalFormatException ife) {
118            LOGGER.error("Unable to format msg: " + msgPattern, ife);
119            return msgPattern;
120        }
121    }
122
123    @Override
124    public boolean equals(final Object o) {
125        if (this == o) {
126            return true;
127        }
128        if (o == null || getClass() != o.getClass()) {
129            return false;
130        }
131
132        final StringFormattedMessage that = (StringFormattedMessage) o;
133
134        if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
135            return false;
136        }
137
138        return Arrays.equals(stringArgs, that.stringArgs);
139    }
140
141    @Override
142    public int hashCode() {
143        int result = messagePattern != null ? messagePattern.hashCode() : 0;
144        result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
145        return result;
146    }
147
148
149    @Override
150    public String toString() {
151        return getFormattedMessage();
152    }
153
154    private void writeObject(final ObjectOutputStream out) throws IOException {
155        out.defaultWriteObject();
156        getFormattedMessage();
157        out.writeUTF(formattedMessage);
158        out.writeUTF(messagePattern);
159        out.writeInt(argArray.length);
160        stringArgs = new String[argArray.length];
161        int i = 0;
162        for (final Object obj : argArray) {
163            final String string = String.valueOf(obj);
164            stringArgs[i] = string;
165            out.writeUTF(string);
166            ++i;
167        }
168    }
169
170    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
171        in.defaultReadObject();
172        formattedMessage = in.readUTF();
173        messagePattern = in.readUTF();
174        final int length = in.readInt();
175        stringArgs = new String[length];
176        for (int i = 0; i < length; ++i) {
177            stringArgs[i] = in.readUTF();
178        }
179    }
180
181    /**
182     * Return the throwable passed to the Message.
183     *
184     * @return the Throwable.
185     */
186    @Override
187    public Throwable getThrowable() {
188        return throwable;
189    }
190}