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 java.io.IOException;
020    import java.io.ObjectInputStream;
021    import java.io.ObjectOutputStream;
022    import java.text.Format;
023    import java.text.MessageFormat;
024    import java.util.Arrays;
025    import 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     */
031    public 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, null);
207            }
208            return message.getThrowable();
209        }
210    }