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.beans.factory.config; 018 019import org.springframework.beans.BeanMetadataElement; 020import org.springframework.lang.Nullable; 021import org.springframework.util.Assert; 022import org.springframework.util.ClassUtils; 023import org.springframework.util.ObjectUtils; 024 025/** 026 * Holder for a typed String value. Can be added to bean definitions 027 * in order to explicitly specify a target type for a String value, 028 * for example for collection elements. 029 * 030 * <p>This holder will just store the String value and the target type. 031 * The actual conversion will be performed by the bean factory. 032 * 033 * @author Juergen Hoeller 034 * @since 1.2 035 * @see BeanDefinition#getPropertyValues 036 * @see org.springframework.beans.MutablePropertyValues#addPropertyValue 037 */ 038public class TypedStringValue implements BeanMetadataElement { 039 040 @Nullable 041 private String value; 042 043 @Nullable 044 private volatile Object targetType; 045 046 @Nullable 047 private Object source; 048 049 @Nullable 050 private String specifiedTypeName; 051 052 private volatile boolean dynamic; 053 054 055 /** 056 * Create a new {@link TypedStringValue} for the given String value. 057 * @param value the String value 058 */ 059 public TypedStringValue(@Nullable String value) { 060 setValue(value); 061 } 062 063 /** 064 * Create a new {@link TypedStringValue} for the given String value 065 * and target type. 066 * @param value the String value 067 * @param targetType the type to convert to 068 */ 069 public TypedStringValue(@Nullable String value, Class<?> targetType) { 070 setValue(value); 071 setTargetType(targetType); 072 } 073 074 /** 075 * Create a new {@link TypedStringValue} for the given String value 076 * and target type. 077 * @param value the String value 078 * @param targetTypeName the type to convert to 079 */ 080 public TypedStringValue(@Nullable String value, String targetTypeName) { 081 setValue(value); 082 setTargetTypeName(targetTypeName); 083 } 084 085 086 /** 087 * Set the String value. 088 * <p>Only necessary for manipulating a registered value, 089 * for example in BeanFactoryPostProcessors. 090 */ 091 public void setValue(@Nullable String value) { 092 this.value = value; 093 } 094 095 /** 096 * Return the String value. 097 */ 098 @Nullable 099 public String getValue() { 100 return this.value; 101 } 102 103 /** 104 * Set the type to convert to. 105 * <p>Only necessary for manipulating a registered value, 106 * for example in BeanFactoryPostProcessors. 107 */ 108 public void setTargetType(Class<?> targetType) { 109 Assert.notNull(targetType, "'targetType' must not be null"); 110 this.targetType = targetType; 111 } 112 113 /** 114 * Return the type to convert to. 115 */ 116 public Class<?> getTargetType() { 117 Object targetTypeValue = this.targetType; 118 if (!(targetTypeValue instanceof Class)) { 119 throw new IllegalStateException("Typed String value does not carry a resolved target type"); 120 } 121 return (Class<?>) targetTypeValue; 122 } 123 124 /** 125 * Specify the type to convert to. 126 */ 127 public void setTargetTypeName(@Nullable String targetTypeName) { 128 this.targetType = targetTypeName; 129 } 130 131 /** 132 * Return the type to convert to. 133 */ 134 @Nullable 135 public String getTargetTypeName() { 136 Object targetTypeValue = this.targetType; 137 if (targetTypeValue instanceof Class) { 138 return ((Class<?>) targetTypeValue).getName(); 139 } 140 else { 141 return (String) targetTypeValue; 142 } 143 } 144 145 /** 146 * Return whether this typed String value carries a target type . 147 */ 148 public boolean hasTargetType() { 149 return (this.targetType instanceof Class); 150 } 151 152 /** 153 * Determine the type to convert to, resolving it from a specified class name 154 * if necessary. Will also reload a specified Class from its name when called 155 * with the target type already resolved. 156 * @param classLoader the ClassLoader to use for resolving a (potential) class name 157 * @return the resolved type to convert to 158 * @throws ClassNotFoundException if the type cannot be resolved 159 */ 160 @Nullable 161 public Class<?> resolveTargetType(@Nullable ClassLoader classLoader) throws ClassNotFoundException { 162 String typeName = getTargetTypeName(); 163 if (typeName == null) { 164 return null; 165 } 166 Class<?> resolvedClass = ClassUtils.forName(typeName, classLoader); 167 this.targetType = resolvedClass; 168 return resolvedClass; 169 } 170 171 172 /** 173 * Set the configuration source {@code Object} for this metadata element. 174 * <p>The exact type of the object will depend on the configuration mechanism used. 175 */ 176 public void setSource(@Nullable Object source) { 177 this.source = source; 178 } 179 180 @Override 181 @Nullable 182 public Object getSource() { 183 return this.source; 184 } 185 186 /** 187 * Set the type name as actually specified for this particular value, if any. 188 */ 189 public void setSpecifiedTypeName(@Nullable String specifiedTypeName) { 190 this.specifiedTypeName = specifiedTypeName; 191 } 192 193 /** 194 * Return the type name as actually specified for this particular value, if any. 195 */ 196 @Nullable 197 public String getSpecifiedTypeName() { 198 return this.specifiedTypeName; 199 } 200 201 /** 202 * Mark this value as dynamic, i.e. as containing an expression 203 * and hence not being subject to caching. 204 */ 205 public void setDynamic() { 206 this.dynamic = true; 207 } 208 209 /** 210 * Return whether this value has been marked as dynamic. 211 */ 212 public boolean isDynamic() { 213 return this.dynamic; 214 } 215 216 217 @Override 218 public boolean equals(@Nullable Object other) { 219 if (this == other) { 220 return true; 221 } 222 if (!(other instanceof TypedStringValue)) { 223 return false; 224 } 225 TypedStringValue otherValue = (TypedStringValue) other; 226 return (ObjectUtils.nullSafeEquals(this.value, otherValue.value) && 227 ObjectUtils.nullSafeEquals(this.targetType, otherValue.targetType)); 228 } 229 230 @Override 231 public int hashCode() { 232 return ObjectUtils.nullSafeHashCode(this.value) * 29 + ObjectUtils.nullSafeHashCode(this.targetType); 233 } 234 235 @Override 236 public String toString() { 237 return "TypedStringValue: value [" + this.value + "], target type [" + this.targetType + "]"; 238 } 239 240}