Avoid "NullAway.Init" suppression in favor of explicit field handling

Closes gh-36961
This commit is contained in:
Juergen Hoeller
2026-06-23 11:55:58 +02:00
parent 9130ded96f
commit bd405756ab
18 changed files with 140 additions and 131 deletions
@@ -100,8 +100,7 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
private @Nullable PropertyEditorRegistrar defaultEditorRegistrar;
@SuppressWarnings("NullAway.Init")
private Map<Class<?>, PropertyEditor> defaultEditors;
private @Nullable Map<Class<?>, PropertyEditor> defaultEditors;
private @Nullable Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
@@ -201,7 +200,7 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
}
}
if (this.defaultEditors == null) {
createDefaultEditors();
this.defaultEditors = createDefaultEditors();
}
return this.defaultEditors.get(requiredType);
}
@@ -209,75 +208,77 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
/**
* Actually register the default editors for this registry instance.
*/
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
private Map<Class<?>, PropertyEditor> createDefaultEditors() {
Map<Class<?>, PropertyEditor> defaultEditors = new HashMap<>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Path.class, new PathEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());
defaultEditors.put(Charset.class, new CharsetEditor());
defaultEditors.put(Class.class, new ClassEditor());
defaultEditors.put(Class[].class, new ClassArrayEditor());
defaultEditors.put(Currency.class, new CurrencyEditor());
defaultEditors.put(File.class, new FileEditor());
defaultEditors.put(InputStream.class, new InputStreamEditor());
defaultEditors.put(InputSource.class, new InputSourceEditor());
defaultEditors.put(Locale.class, new LocaleEditor());
defaultEditors.put(Path.class, new PathEditor());
defaultEditors.put(Pattern.class, new PatternEditor());
defaultEditors.put(Properties.class, new PropertiesEditor());
defaultEditors.put(Reader.class, new ReaderEditor());
defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
defaultEditors.put(TimeZone.class, new TimeZoneEditor());
defaultEditors.put(URI.class, new URIEditor());
defaultEditors.put(URL.class, new URLEditor());
defaultEditors.put(UUID.class, new UUIDEditor());
defaultEditors.put(ZoneId.class, new ZoneIdEditor());
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
defaultEditors.put(char[].class, new CharArrayPropertyEditor());
// The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
defaultEditors.put(char.class, new CharacterEditor(false));
defaultEditors.put(Character.class, new CharacterEditor(true));
// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
// The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
// Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
defaultEditors.put(String[].class, sae);
defaultEditors.put(short[].class, sae);
defaultEditors.put(int[].class, sae);
defaultEditors.put(long[].class, sae);
}
return defaultEditors;
}
/**
@@ -74,8 +74,7 @@ public abstract class AbstractFactoryBean<T>
private boolean initialized = false;
@SuppressWarnings("NullAway.Init")
private T singletonInstance;
private @Nullable T singletonInstance;
private @Nullable T earlySingletonInstance;
@@ -147,7 +146,7 @@ public abstract class AbstractFactoryBean<T>
* @see #getEarlySingletonInterfaces()
*/
@Override
public final T getObject() throws Exception {
public final @Nullable T getObject() throws Exception {
if (isSingleton()) {
return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
}
@@ -89,15 +89,13 @@ public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAwa
private @Nullable BeanWrapper targetBeanWrapper;
@SuppressWarnings("NullAway.Init")
private String targetBeanName;
private @Nullable String targetBeanName;
private @Nullable String propertyPath;
private @Nullable Class<?> resultType;
@SuppressWarnings("NullAway.Init")
private String beanName;
private @Nullable String beanName;
private @Nullable BeanFactory beanFactory;
@@ -160,25 +158,27 @@ public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAwa
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
String targetBeanName = this.targetBeanName;
if (this.targetBeanWrapper != null && this.targetBeanName != null) {
if (this.targetBeanWrapper != null && targetBeanName != null) {
throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both");
}
if (this.targetBeanWrapper == null && this.targetBeanName == null) {
if (this.targetBeanWrapper == null && targetBeanName == null) {
if (this.propertyPath != null) {
throw new IllegalArgumentException(
"Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'");
}
// No other properties specified: check bean name.
int dotIndex = (this.beanName != null ? this.beanName.indexOf('.') : -1);
if (dotIndex == -1) {
int dotIndex;
if (this.beanName == null || (dotIndex = this.beanName.indexOf('.')) <= 0) {
throw new IllegalArgumentException(
"Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean " +
"bean name '" + this.beanName + "' does not follow 'beanName.property' syntax");
}
this.targetBeanName = this.beanName.substring(0, dotIndex);
targetBeanName = this.beanName.substring(0, dotIndex);
this.targetBeanName = targetBeanName;
this.propertyPath = this.beanName.substring(dotIndex + 1);
}
@@ -187,9 +187,10 @@ public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAwa
throw new IllegalArgumentException("'propertyPath' is required");
}
if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) {
if (this.targetBeanWrapper == null && StringUtils.hasLength(targetBeanName) &&
this.beanFactory.isSingleton(targetBeanName)) {
// Eagerly fetch singleton target bean, and determine result type.
Object bean = this.beanFactory.getBean(this.targetBeanName);
Object bean = this.beanFactory.getBean(targetBeanName);
this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath);
}