001/*
002 * Copyright 2002-2017 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.core.env;
018
019import java.util.Iterator;
020import java.util.List;
021import java.util.concurrent.CopyOnWriteArrayList;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026/**
027 * Default implementation of the {@link PropertySources} interface.
028 * Allows manipulation of contained property sources and provides a constructor
029 * for copying an existing {@code PropertySources} instance.
030 *
031 * <p>Where <em>precedence</em> is mentioned in methods such as {@link #addFirst}
032 * and {@link #addLast}, this is with regard to the order in which property sources
033 * will be searched when resolving a given property with a {@link PropertyResolver}.
034 *
035 * @author Chris Beams
036 * @author Juergen Hoeller
037 * @since 3.1
038 * @see PropertySourcesPropertyResolver
039 */
040public class MutablePropertySources implements PropertySources {
041
042        private final Log logger;
043
044        private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
045
046
047        /**
048         * Create a new {@link MutablePropertySources} object.
049         */
050        public MutablePropertySources() {
051                this.logger = LogFactory.getLog(getClass());
052        }
053
054        /**
055         * Create a new {@code MutablePropertySources} from the given propertySources
056         * object, preserving the original order of contained {@code PropertySource} objects.
057         */
058        public MutablePropertySources(PropertySources propertySources) {
059                this();
060                for (PropertySource<?> propertySource : propertySources) {
061                        addLast(propertySource);
062                }
063        }
064
065        /**
066         * Create a new {@link MutablePropertySources} object and inherit the given logger,
067         * usually from an enclosing {@link Environment}.
068         */
069        MutablePropertySources(Log logger) {
070                this.logger = logger;
071        }
072
073
074        @Override
075        public boolean contains(String name) {
076                return this.propertySourceList.contains(PropertySource.named(name));
077        }
078
079        @Override
080        public PropertySource<?> get(String name) {
081                int index = this.propertySourceList.indexOf(PropertySource.named(name));
082                return (index != -1 ? this.propertySourceList.get(index) : null);
083        }
084
085        @Override
086        public Iterator<PropertySource<?>> iterator() {
087                return this.propertySourceList.iterator();
088        }
089
090        /**
091         * Add the given property source object with highest precedence.
092         */
093        public void addFirst(PropertySource<?> propertySource) {
094                if (logger.isDebugEnabled()) {
095                        logger.debug("Adding PropertySource '" + propertySource.getName() + "' with highest search precedence");
096                }
097                removeIfPresent(propertySource);
098                this.propertySourceList.add(0, propertySource);
099        }
100
101        /**
102         * Add the given property source object with lowest precedence.
103         */
104        public void addLast(PropertySource<?> propertySource) {
105                if (logger.isDebugEnabled()) {
106                        logger.debug("Adding PropertySource '" + propertySource.getName() + "' with lowest search precedence");
107                }
108                removeIfPresent(propertySource);
109                this.propertySourceList.add(propertySource);
110        }
111
112        /**
113         * Add the given property source object with precedence immediately higher
114         * than the named relative property source.
115         */
116        public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
117                if (logger.isDebugEnabled()) {
118                        logger.debug("Adding PropertySource '" + propertySource.getName() +
119                                        "' with search precedence immediately higher than '" + relativePropertySourceName + "'");
120                }
121                assertLegalRelativeAddition(relativePropertySourceName, propertySource);
122                removeIfPresent(propertySource);
123                int index = assertPresentAndGetIndex(relativePropertySourceName);
124                addAtIndex(index, propertySource);
125        }
126
127        /**
128         * Add the given property source object with precedence immediately lower
129         * than the named relative property source.
130         */
131        public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
132                if (logger.isDebugEnabled()) {
133                        logger.debug("Adding PropertySource '" + propertySource.getName() +
134                                        "' with search precedence immediately lower than '" + relativePropertySourceName + "'");
135                }
136                assertLegalRelativeAddition(relativePropertySourceName, propertySource);
137                removeIfPresent(propertySource);
138                int index = assertPresentAndGetIndex(relativePropertySourceName);
139                addAtIndex(index + 1, propertySource);
140        }
141
142        /**
143         * Return the precedence of the given property source, {@code -1} if not found.
144         */
145        public int precedenceOf(PropertySource<?> propertySource) {
146                return this.propertySourceList.indexOf(propertySource);
147        }
148
149        /**
150         * Remove and return the property source with the given name, {@code null} if not found.
151         * @param name the name of the property source to find and remove
152         */
153        public PropertySource<?> remove(String name) {
154                if (logger.isDebugEnabled()) {
155                        logger.debug("Removing PropertySource '" + name + "'");
156                }
157                int index = this.propertySourceList.indexOf(PropertySource.named(name));
158                return (index != -1 ? this.propertySourceList.remove(index) : null);
159        }
160
161        /**
162         * Replace the property source with the given name with the given property source object.
163         * @param name the name of the property source to find and replace
164         * @param propertySource the replacement property source
165         * @throws IllegalArgumentException if no property source with the given name is present
166         * @see #contains
167         */
168        public void replace(String name, PropertySource<?> propertySource) {
169                if (logger.isDebugEnabled()) {
170                        logger.debug("Replacing PropertySource '" + name + "' with '" + propertySource.getName() + "'");
171                }
172                int index = assertPresentAndGetIndex(name);
173                this.propertySourceList.set(index, propertySource);
174        }
175
176        /**
177         * Return the number of {@link PropertySource} objects contained.
178         */
179        public int size() {
180                return this.propertySourceList.size();
181        }
182
183        @Override
184        public String toString() {
185                return this.propertySourceList.toString();
186        }
187
188        /**
189         * Ensure that the given property source is not being added relative to itself.
190         */
191        protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
192                String newPropertySourceName = propertySource.getName();
193                if (relativePropertySourceName.equals(newPropertySourceName)) {
194                        throw new IllegalArgumentException(
195                                        "PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself");
196                }
197        }
198
199        /**
200         * Remove the given property source if it is present.
201         */
202        protected void removeIfPresent(PropertySource<?> propertySource) {
203                this.propertySourceList.remove(propertySource);
204        }
205
206        /**
207         * Add the given property source at a particular index in the list.
208         */
209        private void addAtIndex(int index, PropertySource<?> propertySource) {
210                removeIfPresent(propertySource);
211                this.propertySourceList.add(index, propertySource);
212        }
213
214        /**
215         * Assert that the named property source is present and return its index.
216         * @param name {@linkplain PropertySource#getName() name of the property source} to find
217         * @throws IllegalArgumentException if the named property source is not present
218         */
219        private int assertPresentAndGetIndex(String name) {
220                int index = this.propertySourceList.indexOf(PropertySource.named(name));
221                if (index == -1) {
222                        throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
223                }
224                return index;
225        }
226
227}