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