/* 
 * This file is part of the Echo Web Application Framework (hereinafter "Echo").
 * Copyright (C) 2002-2005 NextApp, Inc.
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 */

package nextapp.echo2.webcontainer.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import nextapp.echo2.app.RenderIdSupport;

/**
 * A table which provides an identifier-to-object mapping, with the objects 
 * being weakly referenced (i.e., the fact that they are held within this table
 * will not prevent them from being garbage collected).
 */
public class IdTable 
implements Serializable {
    
    private boolean hasHardReferences = false;
    private transient Map idToReferenceMap = new HashMap();
    private transient ReferenceQueue referenceQueue = new ReferenceQueue();
    
    /**
     * Registers an object with the <code>IdTable</code>
     * 
     * @param object the object to identify
     */
    public void register(RenderIdSupport object) {
        purge();
        String id = object.getRenderId();
        WeakReference weakReference;
        synchronized(idToReferenceMap) {
            if (!idToReferenceMap.containsKey(id)) {
                weakReference = new WeakReference(object, referenceQueue);
                idToReferenceMap.put(id, weakReference);
            }
        }
    }
    
    /**
     * Retrieves the object associated with the specified identifier.
     * 
     * @param id the identifier
     * @return the object (or null, if the object is not in the queue, perhaps
     *         due to having been dereferenced and garbage collected)
     */
    public Object getObject(String id) {
        purge();
        WeakReference weakReference;
        synchronized(idToReferenceMap) {
            weakReference = (WeakReference) idToReferenceMap.get(id);
        }
        if (weakReference == null) {
            return null;
        }
        Object object = weakReference.get();
        return object;
    }
    
    /**
     * Purges dereferenced/garbage collected entries from the 
     * <code>IdTable</code>.
     */
    private void purge() {
        // Convert any hard references to weak references.
        if (hasHardReferences) {
            synchronized (idToReferenceMap) {
                Iterator idIt = idToReferenceMap.keySet().iterator();
                while (idIt.hasNext()) {
                    String id = (String) idIt.next();
                    Object object = idToReferenceMap.get(id); 
                    if (!(object instanceof WeakReference)) {
                        WeakReference weakReference = new WeakReference(object, referenceQueue);
                        idToReferenceMap.put(id, weakReference);
                    }
                }
                hasHardReferences = false;
            }
        }
        
        // Purge weak references that are no longer hard referenced elsewhere.
        Reference reference = referenceQueue.poll();
        if (reference == null) {
            // No such references exist.
            return;
        }
        Set referenceSet = new HashSet();
        while (reference != null) {
            referenceSet.add(reference);
            reference = referenceQueue.poll();
        }
        
        synchronized (idToReferenceMap) {
            Iterator idIt = idToReferenceMap.keySet().iterator();
            while (idIt.hasNext()) {
                String id = (String) idIt.next();
                if (referenceSet.contains(idToReferenceMap.get(id))) {
                    idIt.remove();
                }
            }
        }
    }

    /**
     * @see java.io.Serializable
     * 
     * Writes objects directly into values of Map as straight references.
     * The values will be changed to <code>WeakReference</code>s when 
     * purge() is called.
     */
    private void readObject(ObjectInputStream in)
    throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        
        idToReferenceMap = new HashMap();
        referenceQueue = new ReferenceQueue();
       
        String id = (String) in.readObject();
        if (id != null) {
            // Hard references will be written.
            hasHardReferences = true;

            // Load map and store objects as hard references.
            while (id != null) {
                RenderIdSupport object = (RenderIdSupport) in.readObject();
                idToReferenceMap.put(id, object);
                id = (String) in.readObject();
            }
        }
    }

    /**
     * @see java.io.Serializable
     */
    private void writeObject(ObjectOutputStream out) 
    throws IOException {
        out.defaultWriteObject();
        Iterator it = idToReferenceMap.keySet().iterator();
        while (it.hasNext()) {
            String id = (String) it.next();
            out.writeObject(id);
            Object object = idToReferenceMap.get(id);
            if (object instanceof WeakReference) {
                object = ((WeakReference) object).get();
            }
            out.writeObject(object);
        }
        // Write null to specify end of object.
        out.writeObject(null);
    }
}
