/*******************************************************************************
 * This file is part of TERMINAL RECALL
 * Copyright (c) 2012-2015 Chuck Ritola
 * Part of the jTRFP.org project
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     chuck - initial API and implementation
 ******************************************************************************/

package org.jtrfp.trcl.coll;

import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength;

import com.ochafik.util.Adapter;

public abstract class CachedAdapter<U,V> implements Adapter<U, V> {
    private final BidiReferenceMap<U,V> cache;
    private volatile boolean tolerateNull = false;
    
    public CachedAdapter(){
	this(new BidiReferenceMap<U,V>(ReferenceStrength.WEAK));
    }
    
    public CachedAdapter(BidiReferenceMap<U,V> cache){
	this.cache=cache;
    }

    @Override
    public final V adapt(U key) {
	if(key==null){
	    if(tolerateNull)
		return null;
	    else throw new NullPointerException("Key is intolerably null.");
	}//end if(key null)
	synchronized(cache){
	 V result = cache.get(key);
	 if(!cache.containsKey(key)){
	    V v = _adapt(key);
	    if(v==null){
		if(tolerateNull)
		    return null;
		else throw new NullPointerException("Adapted value is intolerably null.");
	    }//end if(key null)
	    cache.put(key, v);
	    result = v;
	 }//end if(null)
	 return result;
	}//end sync(cache)
    }//end adapt()

    @Override
    public final U reAdapt(V value) {
	if(value==null){
	    if(tolerateNull)
		return null;
	    else throw new NullPointerException("Value is intolerably null.");
	}//end if(key null)
	synchronized(cache){
	 U result = cache.getKey(value);
	 if(!cache.containsValue(value)){
	    U key = _reAdapt(value);
	    if(key==null){
		if(tolerateNull)
		    return null;
		else throw new NullPointerException("Re-Adapted key is intolerably null.");
	    }//end if(key null)
	    cache.put(key, value);
	    result = key;
	 }//end if(null)
	 return result;
	}//end sync(cache)
    }//end reAdapt(...)
    
    protected abstract V _adapt  (U value) throws UnsupportedOperationException;
    protected abstract U _reAdapt(V value) throws UnsupportedOperationException;
    
    public static <U,V> CachedAdapter<U,V> decorate(final Adapter<U,V> adapter, BidiReferenceMap<U,V> mapToUse){
	return new CachedAdapter<U,V>(mapToUse){

	    @Override
	    protected V _adapt(U value) throws UnsupportedOperationException {
		return adapter.adapt(value);
	    }

	    @Override
	    protected U _reAdapt(V value) throws UnsupportedOperationException {
		return adapter.reAdapt(value);
	    }};
    }
    
    public static <U,V> CachedAdapter<U,V> decorate(final Adapter<U,V> adapter){
	return new CachedAdapter<U,V>(){

	    @Override
	    protected V _adapt(U value) throws UnsupportedOperationException {
		return adapter.adapt(value);
	    }

	    @Override
	    protected U _reAdapt(V value) throws UnsupportedOperationException {
		return adapter.reAdapt(value);
	    }};
    }

    /**
     * @return the tolerateNull
     */
    public boolean isTolerateNull() {
        return tolerateNull;
    }

    /**
     * @param tolerateNull the tolerateNull to set
     */
    public CachedAdapter<U,V> setTolerateNull(boolean tolerateNull) {
        this.tolerateNull = tolerateNull;
        return this;
    }

}//end CachedAdapter

@@ -685,16 +685,50 @@
 trength;
+%0Aimport org.jtrfp.trcl.tools.Util;
 %0A%0Aimport
@@ -1098,24 +1098,244 @@
 cache;%0A    %7D
+%0A    %0A    public com.ochafik.util.listenable.Adapter%3CU,V%3E toForward()%7B%0A%09return Util.bidi2Forward(this);%0A    %7D%0A    %0A    public com.ochafik.util.listenable.Adapter%3CV,U%3E toBackward()%7B%0A%09return Util.bidi2Backward(this);%0A    %7D
 %0A%0A    @Overr

Add forward/backward Adapter extraction methods to CachedAdapter.