/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection.map;

import java.util.Map;
import java.util.Map.Entry;
import net.ranides.assira.generic.CoFunction;

/**
 * Klasa abstrakcyjna umożliwiająca leniwą konwersję listy elementów między dwoma
 * formatami w obie strony (a więc tworzenie map do odczytu i zapisu). 
 * Konwertowane są tylko wartości umieszczone w mapie - klucze nie mogą być 
 * transformowane.
 * <p>
 * Ogólny wariant adaptera zakłada, że konwersja może zwracać jako wynik obiekty
 * innej klasy, niż źródłowa. Specjalizacja {@link MapBiAdapterTT} służy do przeprowadzania
 * konwersji, które mają wpływ jedynie na wartości, ale nie na klasy elementów.
 * </p><p>
 * Rozwiązuje problem synchronizacji między dwoma mapami, które reprezentują ten 
 * sam zbiór, ale w innym formacie: modyfikacja jednej natychmiast aktualizuje 
 * drugą - nie musimy pamiętać o kontroli spójności oraz wstawianiu do obu map
 * wartości w odpowiednich formatach. Scenariusze w których MapAdapter dobrze 
 * się sprawdza:
 * </p>
 * <pre>
 *      <span style="color:#808080">// spójna obsługa dwóch map</span>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      operacje get(PUBLIC &amp; INTERN) + put(PUBLIC &amp; INTERN)
 *
 *      <span style="color:#808080">// mieszana obsługa dwóch map</span>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      bardzo dużo operacji na get(PUBLIC) + put(PUBLIC &amp; INTERN)
 *
 *      <span style="color:#808080">// obsługa dwóch map niszcząca aktualność danych (A)</span>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      bardzo dużo operacji na get(PUBLIC &amp; INTERN) + put(PUBLIC)
 *
 *      <span style="color:#808080">// obsługa dwóch map niszcząca aktualność danych (B)</span>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      bardzo dużo operacji na get(PUBLIC &amp; INTERN) + put(INTERN)
 *
 *      <span style="color:#808080">// obsługa dwóch list niszcząca spójność danych (C)</span>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      bardzo dużo operacji na get(PUBLIC &amp; INTERN) + put(INTERN) + put(PUBLIC)
 * </pre>
 *
 * <p>
 * <b>Wada:</b> konwersja odbywa się przy <u>każdym</u> odczycie i zapisie, jeśli mapa
 * adaptująca jest bardzo intensywnie używana klasyczne rozwiązanie będzie znacznie
 * wydajniejsze (jakkolwiek mapa źródłowa nie będzie na bieżąco odświeżana):
 * </p>
 * <pre>
 *      Map&lt;K,INTERN&gt; source = ...
 *      Map&lt;K,PUBLIC&gt; target = konwersja
 *      ...
 *      bardzo dużo operacji get(PUBLIC) + put(PUBLIC)
 *      ...
 *      source = konwersja powrotna
 * </pre>
 *
 * <p>"bardzo intensywne użycie" jest pojęciem bardzo względnym. W przypadku 
 * adapterów list można oszacować ilość operacji odczytu/zapisu, która jest granicą
 * użyteczności adaptera. W przypadku map takie oszacowanie jest praktycznie
 * niemożliwe, a granica dużo dalej przesunięta, ponieważ dzięki adapterowi <b>unikamy
 * kosztu przeindeksowania kluczy</b>. Koszt przebudowania całej mapy może być
 * ekstremalnie wysoki, zarówno dla TreeMap (przebudowa drzewa) jak i HashMap
 * (przechashowanie wszystkich kluczy). Również narzut pamięciowy związany z 
 * konstrukcją map jest ekstremalnie wyższy od konstrukcji np ArrayList. 
 * </p><p>
 * Zasadniczo utworzenie adaptera zamiast przebudowy całej istniejącej mapy
 * na nowy format powinno być preferowane. Rezygnacja z adapterów powinna być 
 * poparta wynikami realnych testów wydajnościowych.
 * </p><p>
 * W metodach {@link #apply(java.lang.Object) apply} oraz 
 * {@link #inverse(java.lang.Object) inverse} można nie tylko konwertować wartości, 
 * ale również logować operacje odczytu i zapisu. Z tego powodu adapter może być
 * użyty z powodzeniem jako wydajna realizacja obserwatora.
 * </p>
 * 
 * @param <K> 
 * @param <VS> 
 * @param <VT> 
 * @author ranides
 */
public abstract class MapBiAdapter<K, VS, VT> extends MapAdapter<K, VS, VT> implements CoFunction<Entry<K,VT>, VS> {

    private final EntrySource<K,VT> invEntry = new EntrySource<>();

    /**
     * Tworzy nowy adapter odwzorowujący podaną mapę.
     * @param content
     */
    public MapBiAdapter(Map<K, VS> content) {
        super(content);
    }

    @Override
    public VT put(K key, VT value) {
        VS result = this.content.put(key, inverse(invEntry.set(key, value)) );
        return null == result ? null : apply(new EntryConst<K, VS>(key, result));
    }

}
