/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.jboss.solder.util.collections;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import static org.jboss.solder.util.collections.Preconditions.checkNotNull;

public class Multimaps {

    private Multimaps() {
    }

    private static class CustomSetMultimap<K, V> extends AbstractSetMultimap<K, V> {
        transient Supplier<? extends Set<V>> factory;

        CustomSetMultimap(Map<K, Collection<V>> map, Supplier<? extends Set<V>> factory) {
            super(map);
            this.factory = checkNotNull(factory);
        }

        @Override
        protected Set<V> createCollection() {
            return factory.get();
        }

        /**
         * @serialData the factory and the backing map
         */
        private void writeObject(ObjectOutputStream stream) throws IOException {
            stream.defaultWriteObject();
            stream.writeObject(factory);
            stream.writeObject(backingMap());
        }

        @SuppressWarnings("unchecked")
        // reading data stored by writeObject
        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
            stream.defaultReadObject();
            factory = (Supplier<? extends Set<V>>) stream.readObject();
            Map<K, Collection<V>> map = (Map<K, Collection<V>>) stream.readObject();
            setMap(map);
        }

        private static final long serialVersionUID = 0;
    }

    /**
     * Creates a new {@code SetMultimap} that uses the provided map and factory.
     * It can generate a multimap based on arbitrary {@link Map} and {@link Set}
     * classes.
     * <p/>
     * <p/>
     * The {@code factory}-generated and {@code map} classes determine the
     * multimap iteration order. They also specify the behavior of the {@code
     * equals}, {@code hashCode}, and {@code toString} methods for the multimap
     * and its returned views. However, the multimap's {@code get} method returns
     * instances of a different class than {@code factory.get()} does.
     * <p/>
     * <p/>
     * The multimap is serializable if {@code map}, {@code factory}, the sets
     * generated by {@code factory}, and the multimap contents are all
     * serializable.
     * <p/>
     * <p/>
     * The multimap is not threadsafe when any concurrent operations update the
     * multimap, even if {@code map} and the instances generated by {@code
     * factory} are. Concurrent read operations will work correctly. To allow
     * concurrent update operations, wrap the multimap with a call to
     * {@link #synchronizedSetMultimap}.
     * <p/>
     * <p/>
     * Call this method only when the simpler methods HashMultimap#create(),
     * LinkedHashMultimap#create(), TreeMultimap#create(), and
     * TreeMultimap#create(Comparator, Comparator) won't suffice.
     * <p/>
     * <p/>
     * Note: the multimap assumes complete ownership over of {@code map} and the
     * sets returned by {@code factory}. Those objects should not be manually
     * updated and they should not use soft, weak, or phantom references.
     *
     * @param map     place to store the mapping from each key to its corresponding
     *                values
     * @param factory supplier of new, empty sets that will each hold all values
     *                for a given key
     * @throws IllegalArgumentException if {@code map} is not empty
     */
    public static <K, V> SetMultimap<K, V> newSetMultimap(Map<K, Collection<V>> map, final Supplier<? extends Set<V>> factory) {
        return new CustomSetMultimap<K, V>(map, factory);
    }

}
