001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.support;
018
019import java.text.MessageFormat;
020import java.util.HashMap;
021import java.util.Locale;
022import java.util.Map;
023
024import org.springframework.lang.Nullable;
025import org.springframework.util.Assert;
026
027/**
028 * Simple implementation of {@link org.springframework.context.MessageSource}
029 * which allows messages to be registered programmatically.
030 * This MessageSource supports basic internationalization.
031 *
032 * <p>Intended for testing rather than for use in production systems.
033 *
034 * @author Rod Johnson
035 * @author Juergen Hoeller
036 */
037public class StaticMessageSource extends AbstractMessageSource {
038
039        private final Map<String, Map<Locale, MessageHolder>> messageMap = new HashMap<>();
040
041
042        @Override
043        @Nullable
044        protected String resolveCodeWithoutArguments(String code, Locale locale) {
045                Map<Locale, MessageHolder> localeMap = this.messageMap.get(code);
046                if (localeMap == null) {
047                        return null;
048                }
049                MessageHolder holder = localeMap.get(locale);
050                if (holder == null) {
051                        return null;
052                }
053                return holder.getMessage();
054        }
055
056        @Override
057        @Nullable
058        protected MessageFormat resolveCode(String code, Locale locale) {
059                Map<Locale, MessageHolder> localeMap = this.messageMap.get(code);
060                if (localeMap == null) {
061                        return null;
062                }
063                MessageHolder holder = localeMap.get(locale);
064                if (holder == null) {
065                        return null;
066                }
067                return holder.getMessageFormat();
068        }
069
070        /**
071         * Associate the given message with the given code.
072         * @param code the lookup code
073         * @param locale the locale that the message should be found within
074         * @param msg the message associated with this lookup code
075         */
076        public void addMessage(String code, Locale locale, String msg) {
077                Assert.notNull(code, "Code must not be null");
078                Assert.notNull(locale, "Locale must not be null");
079                Assert.notNull(msg, "Message must not be null");
080                this.messageMap.computeIfAbsent(code, key -> new HashMap<>(4)).put(locale, new MessageHolder(msg, locale));
081                if (logger.isDebugEnabled()) {
082                        logger.debug("Added message [" + msg + "] for code [" + code + "] and Locale [" + locale + "]");
083                }
084        }
085
086        /**
087         * Associate the given message values with the given keys as codes.
088         * @param messages the messages to register, with messages codes
089         * as keys and message texts as values
090         * @param locale the locale that the messages should be found within
091         */
092        public void addMessages(Map<String, String> messages, Locale locale) {
093                Assert.notNull(messages, "Messages Map must not be null");
094                messages.forEach((code, msg) -> addMessage(code, locale, msg));
095        }
096
097
098        @Override
099        public String toString() {
100                return getClass().getName() + ": " + this.messageMap;
101        }
102
103
104        private class MessageHolder {
105
106                private final String message;
107
108                private final Locale locale;
109
110                @Nullable
111                private volatile MessageFormat cachedFormat;
112
113                public MessageHolder(String message, Locale locale) {
114                        this.message = message;
115                        this.locale = locale;
116                }
117
118                public String getMessage() {
119                        return this.message;
120                }
121
122                public MessageFormat getMessageFormat() {
123                        MessageFormat messageFormat = this.cachedFormat;
124                        if (messageFormat == null) {
125                                messageFormat = createMessageFormat(this.message, this.locale);
126                                this.cachedFormat = messageFormat;
127                        }
128                        return messageFormat;
129                }
130
131                @Override
132                public String toString() {
133                        return this.message;
134                }
135        }
136
137}