Skip to content

Constructor binding with a custom collection type does not work #37734

@wilkinsona

Description

@wilkinsona

The problem, originally reported on StackOverflow, can be reproduced with the following application:

package com.example;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;

import com.example.BindingToCustomCollectionApplication.CustomList;

@SpringBootConfiguration
@EnableConfigurationProperties(com.example.BindingToCustomCollectionApplication.CustomListProperties.class)
public class BindingToCustomCollectionApplication {

	public static void main(String[] args) {
		CustomListProperties properties = SpringApplication
				.run(BindingToCustomCollectionApplication.class, "--custom.values[0]=zero", "--custom.values[1]=one")
				.getBean(CustomListProperties.class);
		System.out.println(properties.getValues());
	}
	
	@Bean
	@ConfigurationPropertiesBinding
	static Converter<ArrayList<?>, CustomList<?>> arrayListToCustomList() {
		return new Converter<>() {

			@Override
			public CustomList<?> convert(ArrayList<?> source) {
				return new CustomList<>(source);
			}
			
		};
		
	}
	
	@ConstructorBinding
	@ConfigurationProperties("custom")
	static class CustomListProperties {
		
		private final CustomList<String> values;
		
		CustomListProperties(CustomList<String> values) {
			super();
			this.values = values;
		}

		public CustomList<String> getValues() {
			return values;
		}

	}
	
//	@ConfigurationProperties("custom")
//	static class CustomListProperties {
//		
//		private CustomList<String> values;
//
//		public CustomList<String> getValues() {
//			return values;
//		}
//
//		public void setValues(CustomList<String> values) {
//			this.values = values;
//		}
//
//	}
	
	static class CustomList<E> implements List<E> {
		
		private final List<E> delegate;
		
		private CustomList(List<E> delegate) {
			this.delegate = delegate;
		}

		public void forEach(Consumer<? super E> action) {
			delegate.forEach(action);
		}

		public int size() {
			return delegate.size();
		}

		public boolean isEmpty() {
			return delegate.isEmpty();
		}

		public boolean contains(Object o) {
			return delegate.contains(o);
		}

		public Iterator<E> iterator() {
			return delegate.iterator();
		}

		public Object[] toArray() {
			return delegate.toArray();
		}

		public <T> T[] toArray(T[] a) {
			return delegate.toArray(a);
		}

		public boolean add(E e) {
			return delegate.add(e);
		}

		public boolean remove(Object o) {
			return delegate.remove(o);
		}

		public boolean containsAll(Collection<?> c) {
			return delegate.containsAll(c);
		}

		public boolean addAll(Collection<? extends E> c) {
			return delegate.addAll(c);
		}

		public boolean addAll(int index, Collection<? extends E> c) {
			return delegate.addAll(index, c);
		}

		public boolean removeAll(Collection<?> c) {
			return delegate.removeAll(c);
		}

		public boolean retainAll(Collection<?> c) {
			return delegate.retainAll(c);
		}

		public void replaceAll(UnaryOperator<E> operator) {
			delegate.replaceAll(operator);
		}

		public <T> T[] toArray(IntFunction<T[]> generator) {
			return delegate.toArray(generator);
		}

		public void sort(Comparator<? super E> c) {
			delegate.sort(c);
		}

		public void clear() {
			delegate.clear();
		}

		public boolean equals(Object o) {
			return delegate.equals(o);
		}

		public int hashCode() {
			return delegate.hashCode();
		}

		public E get(int index) {
			return delegate.get(index);
		}

		public E set(int index, E element) {
			return delegate.set(index, element);
		}

		public void add(int index, E element) {
			delegate.add(index, element);
		}

		public boolean removeIf(Predicate<? super E> filter) {
			return delegate.removeIf(filter);
		}

		public E remove(int index) {
			return delegate.remove(index);
		}

		public int indexOf(Object o) {
			return delegate.indexOf(o);
		}

		public int lastIndexOf(Object o) {
			return delegate.lastIndexOf(o);
		}

		public ListIterator<E> listIterator() {
			return delegate.listIterator();
		}

		public ListIterator<E> listIterator(int index) {
			return delegate.listIterator(index);
		}

		public List<E> subList(int fromIndex, int toIndex) {
			return delegate.subList(fromIndex, toIndex);
		}

		public Spliterator<E> spliterator() {
			return delegate.spliterator();
		}

		public Stream<E> stream() {
			return delegate.stream();
		}

		public Stream<E> parallelStream() {
			return delegate.parallelStream();
		}
		
		public String toString() {
			return delegate.toString();
		}
		
	}

}

It works with JavaBean-based binding (as can be seen by uncommenting one CustomListProperties class and commenting out the other one).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions