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}