/*
 * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
 * the spring framework, http://el4j.sf.net
 * Copyright (C) 2006 by ELCA Informatique SA, Av. de la Harpe 22-24,
 * 1000 Lausanne, Switzerland, http://www.elca.ch
 *
 * EL4J is published under the GNU General Public License (GPL) Version 2.0.
 * http://www.gnu.org/licenses/
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * For alternative licensing, please contact info@elca.ch
 */
package ch.elca.el4j.services.persistence.hibernate;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * Fixes object identities mangled by loosing ORM context or by remoting.
 * 
 * <p>We call objects in the database logical objects, and their fully 
 * materialized local proxies representatives. 
 * 
 * <p> An IdentityFixer ensures uniqueness of representatives, i.e. for every 
 * logical object, an IdentityFixer will always return the same representative. 
 * 
 * <p> An IdentityFixer can be shown a new version of a logical object to update
 * the representative's state.
 * 
 * <p> If the client refrains from 
 * checking the dynamic type or the identity of non-materialized proxies, every
 * proxy to a logical object behaves exactly like this object unless 
 * non-materialized properties are accessed.
 *
 * <script type="text/javascript">printFileStatus
 *   ("$URL$",
 *    "$Revision$",
 *    "$Date$",
 *    "$Author$"
 * );</script>
 *
 * @author Adrian Moos (AMS)
 */
public abstract class IdentityFixerPrototype {
    /** The representatives, keyed by their id. */
    Map<Object, Object> representatives = new HashMap<Object, Object>(); 
     
    /**
     * Updates the unique representitive by duplicating the state in
     * {@code updated}. If no representative exists so far, one is created.
     * 
     * @param anchor the representative known to be identical with updated,
     *               or null, if logical identity is already defined.
     * @return the representative
     */
    Object merge(Object anchor, Object updated) {
        assert isMaterialized(updated);
        assert id(updated) != null;
        
        Object attached;
        if (anchor == null) {
            attached = representatives.get(id(updated));
            assert attached != null;
        } else {
            assert id(anchor) == null 
                || id(anchor) == id(updated);
            attached = anchor;
        }
        
        for (Field f : updated.getClass().getFields()) {
            // remove access protection from f
            
            try {
                f.set(
                    attached,
                    mergeIfNeeded(
                        anchor != null ? f.get(anchor) : null,
                        f.get(updated)
                    )
                );
            } catch (IllegalAccessException e) { assert false : e; }
        }
        
        representatives.put(id(attached), attached);
        return attached;
    }

    /**
     * Merges if updated is materialized, immediately returns {@code updated} 
     * otherwise.
     * @see #merge(Object, Object)
     */
    Object mergeIfNeeded(Object anchor, Object updated) {
        return isMaterialized(updated)
             ? merge(anchor, updated)
             : updated;
    }
    
    abstract Object id(Object o);
    
    abstract boolean isMaterialized(Object o);
}
