/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jdbi.v3.core.collector;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collector;
import java.util.stream.Stream;
import org.jdbi.v3.core.generic.GenericType;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import static org.jdbi.v3.core.generic.GenericTypes.getErasedType;
import static org.jdbi.v3.core.generic.GenericTypes.resolveMapEntryType;

public class BuiltInCollectorFactoryTest {
    private BuiltInCollectorFactory factory = new BuiltInCollectorFactory();

    @Test
    public void optional() {
        Type optionalString = new GenericType<Optional<String>>() {}.getType();
        assertThat(factory.accepts(optionalString)).isTrue();
        assertThat(factory.accepts(Optional.class)).isFalse();
        assertThat(factory.elementType(optionalString)).contains(String.class);

        Collector<String, ?, Optional<String>> collector = (Collector<String, ?, Optional<String>>) factory.build(optionalString);
        assertThat(Stream.<String>empty().collect(collector)).isEmpty();
        assertThat(Stream.of("foo").collect(collector)).contains("foo");
        assertThatThrownBy(() -> Stream.of("foo", "bar").collect(collector))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("Multiple values");
    }

    @Test
    public void collections() {
        testCollectionType(new GenericType<Collection<String>>(){});
        testCollectionType(new GenericType<List<String>>(){});
        testCollectionType(new GenericType<ArrayList<String>>(){});
        testCollectionType(new GenericType<LinkedList<String>>(){});
        testCollectionType(new GenericType<Set<String>>(){});
        testCollectionType(new GenericType<HashSet<String>>(){});
        testCollectionType(new GenericType<SortedSet<String>>(){});
        testCollectionType(new GenericType<TreeSet<String>>(){});
    }

    private <C extends Collection<String>> void testCollectionType(GenericType<C> genericType) {
        Type containerType = genericType.getType();
        Class<?> erasedType = getErasedType(containerType);
        assertThat(factory.accepts(containerType)).isTrue();
        assertThat(factory.accepts(erasedType)).isFalse();
        assertThat(factory.elementType(containerType)).contains(String.class);

        Collector<String, ?, C> collector = (Collector<String, ?, C>) factory.build(containerType);
        assertThat(Stream.of("foo", "bar", "baz").collect(collector))
                .isInstanceOf(erasedType)
                .containsOnly("foo", "bar", "baz");
    }

    @Test
    public void maps() {
        testMapType(new GenericType<Map<Long, String>>() {});
        testMapType(new GenericType<HashMap<Long, String>>() {});
        testMapType(new GenericType<LinkedHashMap<Long, String>>() {});
        testMapType(new GenericType<SortedMap<Long, String>>() {});
        testMapType(new GenericType<TreeMap<Long, String>>() {});
        testMapType(new GenericType<ConcurrentMap<Long, String>>() {});
        testMapType(new GenericType<ConcurrentHashMap<Long, String>>() {});
        testMapType(new GenericType<WeakHashMap<Long, String>>() {});
    }

    private <M extends Map<Long,String>> void testMapType(GenericType<M> genericType) {
        Type containerType = genericType.getType();
        Class<?> erasedType = getErasedType(containerType);
        assertThat(factory.accepts(containerType)).isTrue();
        assertThat(factory.accepts(erasedType)).isFalse();
        assertThat(factory.elementType(containerType)).contains(resolveMapEntryType(Long.class, String.class));

        Collector<Map.Entry<Long, String>, ?, M> collector = (Collector<Map.Entry<Long, String>, ?, M>) factory.build(containerType);
        assertThat(Stream.of(entry(1L, "foo"), entry(2L, "bar"), entry(3L, "baz")).collect(collector))
                .isInstanceOf(erasedType)
                .containsOnly(entry(1L, "foo"), entry(2L, "bar"), entry(3L, "baz"));
    }
}

@@ -5140,16 +5140,232 @@
 baz%22));%0A
+        assertThatThrownBy(() -%3E Stream.of(entry(1L, %22foo%22), entry(1L, %22bar%22)).collect(collector))%0A                .isInstanceOf(IllegalStateException.class)%0A                .hasMessageContaining(%22Multiple values%22);%0A
     %7D%0A%7D%0A

Add test validating exception thrown for map collectors when duplicate keys are encountered