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 org.apache.logging.log4j.Logger;
020    import org.apache.logging.log4j.status.StatusLogger;
021    
022    import java.io.IOException;
023    import java.io.ObjectInputStream;
024    import java.io.ObjectOutputStream;
025    import java.text.Format;
026    import java.text.MessageFormat;
027    import java.util.Arrays;
028    import java.util.regex.Pattern;
029    
030    /**
031     * Handles messages that contain a format String. Dynamically determines if the format conforms to
032     * MessageFormat or String.format and if not then uses ParameterizedMessage to format.
033     */
034    public class FormattedMessage implements Message {
035    
036        private static final Logger LOGGER = StatusLogger.getLogger();
037    
038        private static final long serialVersionUID = -665975803997290697L;
039    
040        private static final int HASHVAL = 31;
041    
042        private static final String FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
043    
044        private static final Pattern MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
045    
046        private String messagePattern;
047        private transient Object[] argArray;
048        private String[] stringArgs;
049        private transient String formattedMessage;
050        private final Throwable throwable;
051    
052        private Message message;
053    
054        public FormattedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
055            this.messagePattern = messagePattern;
056            this.argArray = arguments;
057            this.throwable = throwable;
058        }
059    
060        public FormattedMessage(final String messagePattern, final Object[] arguments) {
061            this.messagePattern = messagePattern;
062            this.argArray = arguments;
063            this.throwable = null;
064        }
065    
066        /**
067         * Constructor with a pattern and a single parameter.
068         * @param messagePattern The message pattern.
069         * @param arg The parameter.
070         */
071        public FormattedMessage(final String messagePattern, final Object arg) {
072            this.messagePattern = messagePattern;
073            this.argArray = new Object[] {arg};
074            this.throwable = null;
075        }
076    
077        /**
078         * Constructor with a pattern and two parameters.
079         * @param messagePattern The message pattern.
080         * @param arg1 The first parameter.
081         * @param arg2 The second parameter.
082         */
083        public FormattedMessage(final String messagePattern, final Object arg1, final Object arg2) {
084            this(messagePattern, new Object[]{arg1, arg2});
085        }
086    
087    
088        /**
089         * Returns the formatted message.
090         * @return the formatted message.
091         */
092        @Override
093        public String getFormattedMessage() {
094            if (formattedMessage == null) {
095                if (message == null) {
096                    message = getMessage(messagePattern, argArray, throwable);
097                }
098                formattedMessage = message.getFormattedMessage();
099            }
100            return formattedMessage;
101        }
102    
103        /**
104         * Returns the message pattern.
105         * @return the message pattern.
106         */
107        @Override
108        public String getFormat() {
109            return messagePattern;
110        }
111    
112        /**
113         * Returns the message parameters.
114         * @return the message parameters.
115         */
116        @Override
117        public Object[] getParameters() {
118            if (argArray != null) {
119                return argArray;
120            }
121            return stringArgs;
122        }
123    
124        protected Message getMessage(final String msgPattern, final Object[] args, final Throwable throwable) {
125            try {
126                final MessageFormat format = new MessageFormat(msgPattern);
127                final Format[] formats = format.getFormats();
128                if (formats != null && formats.length > 0) {
129                    return new MessageFormatMessage(msgPattern, args);
130                }
131            } catch (final Exception ex) {
132                // Obviously, the message is not a proper pattern for MessageFormat.
133            }
134            try {
135                if (MSG_PATTERN.matcher(msgPattern).find()) {
136                    return new StringFormattedMessage(msgPattern, args);
137                }
138            } catch (final Exception ex) {
139                // Also not properly formatted.
140            }
141            return new ParameterizedMessage(msgPattern, args, throwable);
142        }
143    
144        @Override
145        public boolean equals(final Object o) {
146            if (this == o) {
147                return true;
148            }
149            if (o == null || getClass() != o.getClass()) {
150                return false;
151            }
152    
153            final FormattedMessage that = (FormattedMessage) o;
154    
155            if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) {
156                return false;
157            }
158            if (!Arrays.equals(stringArgs, that.stringArgs)) {
159                return false;
160            }
161    
162            return true;
163        }
164    
165        @Override
166        public int hashCode() {
167            int result = messagePattern != null ? messagePattern.hashCode() : 0;
168            result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0);
169            return result;
170        }
171    
172    
173        @Override
174        public String toString() {
175            return "FormattedMessage[messagePattern=" + messagePattern + ", args=" +
176                Arrays.toString(argArray) +  "]";
177        }
178    
179        private void writeObject(final ObjectOutputStream out) throws IOException {
180            out.defaultWriteObject();
181            getFormattedMessage();
182            out.writeUTF(formattedMessage);
183            out.writeUTF(messagePattern);
184            out.writeInt(argArray.length);
185            stringArgs = new String[argArray.length];
186            int i = 0;
187            for (final Object obj : argArray) {
188                stringArgs[i] = obj.toString();
189                ++i;
190            }
191        }
192    
193        private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
194            in.defaultReadObject();
195            formattedMessage = in.readUTF();
196            messagePattern = in.readUTF();
197            final int length = in.readInt();
198            stringArgs = new String[length];
199            for (int i = 0; i < length; ++i) {
200                stringArgs[i] = in.readUTF();
201            }
202        }
203    
204        /**
205         * Always returns null.
206         *
207         * @return null
208         */
209        @Override
210        public Throwable getThrowable() {
211            if (throwable != null) {
212                return throwable;
213            }
214            if (message == null) {
215                message = getMessage(messagePattern, argArray, throwable);
216            }
217            return message.getThrowable();
218        }
219    }