001/*
002 * Copyright 2002-2018 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;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.util.Assert;
026import org.springframework.util.StringUtils;
027import org.springframework.util.StringValueResolver;
028
029/**
030 * Simple implementation of the {@link AliasRegistry} interface.
031 * Serves as base class for
032 * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
033 * implementations.
034 *
035 * @author Juergen Hoeller
036 * @since 2.5.2
037 */
038public class SimpleAliasRegistry implements AliasRegistry {
039
040        /** Map from alias to canonical name */
041        private final Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16);
042
043
044        @Override
045        public void registerAlias(String name, String alias) {
046                Assert.hasText(name, "'name' must not be empty");
047                Assert.hasText(alias, "'alias' must not be empty");
048                synchronized (this.aliasMap) {
049                        if (alias.equals(name)) {
050                                this.aliasMap.remove(alias);
051                        }
052                        else {
053                                String registeredName = this.aliasMap.get(alias);
054                                if (registeredName != null) {
055                                        if (registeredName.equals(name)) {
056                                                // An existing alias - no need to re-register
057                                                return;
058                                        }
059                                        if (!allowAliasOverriding()) {
060                                                throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
061                                                                name + "': It is already registered for name '" + registeredName + "'.");
062                                        }
063                                }
064                                checkForAliasCircle(name, alias);
065                                this.aliasMap.put(alias, name);
066                        }
067                }
068        }
069
070        /**
071         * Return whether alias overriding is allowed.
072         * Default is {@code true}.
073         */
074        protected boolean allowAliasOverriding() {
075                return true;
076        }
077
078        /**
079         * Determine whether the given name has the given alias registered.
080         * @param name the name to check
081         * @param alias the alias to look for
082         * @since 4.2.1
083         */
084        public boolean hasAlias(String name, String alias) {
085                for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
086                        String registeredName = entry.getValue();
087                        if (registeredName.equals(name)) {
088                                String registeredAlias = entry.getKey();
089                                if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
090                                        return true;
091                                }
092                        }
093                }
094                return false;
095        }
096
097        @Override
098        public void removeAlias(String alias) {
099                synchronized (this.aliasMap) {
100                        String name = this.aliasMap.remove(alias);
101                        if (name == null) {
102                                throw new IllegalStateException("No alias '" + alias + "' registered");
103                        }
104                }
105        }
106
107        @Override
108        public boolean isAlias(String name) {
109                return this.aliasMap.containsKey(name);
110        }
111
112        @Override
113        public String[] getAliases(String name) {
114                List<String> result = new ArrayList<String>();
115                synchronized (this.aliasMap) {
116                        retrieveAliases(name, result);
117                }
118                return StringUtils.toStringArray(result);
119        }
120
121        /**
122         * Transitively retrieve all aliases for the given name.
123         * @param name the target name to find aliases for
124         * @param result the resulting aliases list
125         */
126        private void retrieveAliases(String name, List<String> result) {
127                for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
128                        String registeredName = entry.getValue();
129                        if (registeredName.equals(name)) {
130                                String alias = entry.getKey();
131                                result.add(alias);
132                                retrieveAliases(alias, result);
133                        }
134                }
135        }
136
137        /**
138         * Resolve all alias target names and aliases registered in this
139         * factory, applying the given StringValueResolver to them.
140         * <p>The value resolver may for example resolve placeholders
141         * in target bean names and even in alias names.
142         * @param valueResolver the StringValueResolver to apply
143         */
144        public void resolveAliases(StringValueResolver valueResolver) {
145                Assert.notNull(valueResolver, "StringValueResolver must not be null");
146                synchronized (this.aliasMap) {
147                        Map<String, String> aliasCopy = new HashMap<String, String>(this.aliasMap);
148                        for (String alias : aliasCopy.keySet()) {
149                                String registeredName = aliasCopy.get(alias);
150                                String resolvedAlias = valueResolver.resolveStringValue(alias);
151                                String resolvedName = valueResolver.resolveStringValue(registeredName);
152                                if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {
153                                        this.aliasMap.remove(alias);
154                                }
155                                else if (!resolvedAlias.equals(alias)) {
156                                        String existingName = this.aliasMap.get(resolvedAlias);
157                                        if (existingName != null) {
158                                                if (existingName.equals(resolvedName)) {
159                                                        // Pointing to existing alias - just remove placeholder
160                                                        this.aliasMap.remove(alias);
161                                                        break;
162                                                }
163                                                throw new IllegalStateException(
164                                                                "Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias +
165                                                                "') for name '" + resolvedName + "': It is already registered for name '" +
166                                                                registeredName + "'.");
167                                        }
168                                        checkForAliasCircle(resolvedName, resolvedAlias);
169                                        this.aliasMap.remove(alias);
170                                        this.aliasMap.put(resolvedAlias, resolvedName);
171                                }
172                                else if (!registeredName.equals(resolvedName)) {
173                                        this.aliasMap.put(alias, resolvedName);
174                                }
175                        }
176                }
177        }
178
179        /**
180         * Check whether the given name points back to the given alias as an alias
181         * in the other direction already, catching a circular reference upfront
182         * and throwing a corresponding IllegalStateException.
183         * @param name the candidate name
184         * @param alias the candidate alias
185         * @see #registerAlias
186         * @see #hasAlias
187         */
188        protected void checkForAliasCircle(String name, String alias) {
189                if (hasAlias(alias, name)) {
190                        throw new IllegalStateException("Cannot register alias '" + alias +
191                                        "' for name '" + name + "': Circular reference - '" +
192                                        name + "' is a direct or indirect alias for '" + alias + "' already");
193                }
194        }
195
196        /**
197         * Determine the raw name, resolving aliases to canonical names.
198         * @param name the user-specified name
199         * @return the transformed name
200         */
201        public String canonicalName(String name) {
202                String canonicalName = name;
203                // Handle aliasing...
204                String resolvedName;
205                do {
206                        resolvedName = this.aliasMap.get(canonicalName);
207                        if (resolvedName != null) {
208                                canonicalName = resolvedName;
209                        }
210                }
211                while (resolvedName != null);
212                return canonicalName;
213        }
214
215}