// FilteredResource.java
// $Id: FilteredResource.java,v 1.14 1997/02/12 17:30:08 abaird Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.resources ;

import java.util.* ;
import java.io.* ;

import w3c.tools.store.*;
import w3c.jigsaw.http.* ;

class OutgoingElement {
    ResourceFilter filter = null ;
    int            status = 0 ;
    
    OutgoingElement (ResourceFilter filter, int status) {
	this.filter = filter ;
	this.status = status ;
    }
}


/**
 * Filtered resources.
 * A filtered resource is a resource that can be filtered through any filters.
 * Filters are trigered at three points during request processing:
 * <ul>
 * <li>During the lookup, so that they have a chance to check any access
 * rights,
 * <li>Before the request is actually processed by its target resource,
 * <li>And optionally, after the request has been processed (depending on
 * the return value of the second stage.).
 * </ul>
 * @see w3c.jigsaw.resources.ResourceFilter
 */

public class FilteredResource extends HTTPResource {
    /**
     * Our list of filters.
     */
    protected ResourceFilter filters[] = null ;

    /**
     * Clone this filtered resource.
     * The filters that are plugged on the resource get cloned too.
     */

    public synchronized Object getClone(Object values[]) {
	FilteredResource cl = (FilteredResource) super.getClone(values);
	// Clone the filters now:
	if ( filters != null ) {
	    Hashtable defs = new Hashtable(5);
	    defs.put("target", cl);
	    cl.filters = new ResourceFilter[filters.length];
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == null )
		    continue;
		cl.filters[i] = (ResourceFilter) filters[i].getClone(defs);
	    }
	}
	return cl;
    }

    /**
     * Get our whole list of filters.
     */

    public synchronized ResourceFilter[] getFilters() {
	return filters;
    }
    
    /**
     * Get the list of filters of this class.
     * @param cls The class of filters requested.
     * @param into The array of resource filters to save found filters.
     * @return The number of filters found.
     */
    
    public synchronized int getFilters(Class cls, ResourceFilter into[]) {
	int found = 0;
	int limit = into.length;
	for (int i = 0 ; (found < limit) && (i < filters.length) ; i++) {
	    // Skip null filters:
	    if ( filters[i] == null )
		continue;
	    // Is this filter of the right class ?
	    Class fc = filters[i].getClass();
	    while ((fc != null) && (fc != cls))
		fc = fc.getSuperclass();
	    if ( fc == cls )
		into[found++] = filters[i];
	}
	return found;
    }

    /**
     * Register a new filter to this resource.
     * This method register a new filter to the this filtered resource. The
     * provided filter should be an <em>uninitialized</em> ResourceFilter
     * instance.
     * @param The uninitialized filter.
     */

    public synchronized void registerFilter(ResourceFilter filter
					    , Hashtable defs) {
	// Allocate a filter slot:
	int slot = -1;
	if ( filters == null ) {
	    filters = new ResourceFilter[1] ;
	    slot    = 0;
	} else {
	    // Available slot ?
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == null ) {
		    slot = i;
		    break;
		}
	    }
	    if ( slot < 0 ) {
		// Extend the filters array
		ResourceFilter nf[] = new ResourceFilter[filters.length+1] ;
		System.arraycopy(filters, 0, nf, 0, filters.length) ;
		nf[filters.length] = filter ;
		slot = filters.length;
		filters = nf ;

	    }
	}
	// Initialize the filter (set default identifier):
	if ( defs == null ) {
	    defs = new Hashtable(3) ;
	    defs.put("identifier", "filter-"+slot);
	    defs.put("target", this);
	} else {
	    if ( defs.get("identifier") == null ) 
		defs.put("identifier", "filter-"+slot);
	    if ( defs.get("target") == null )
		defs.put("target", this) ;
	}
	filter.initialize(defs) ;
	// Add it to our list of filters:
	filters[slot] = filter;
	markModified() ;
    }

    /**
     * Unregister a filter to this target.
     * @param filter The filter to unregister.
     */

    public synchronized void unregisterFilter(ResourceFilter filter) {
	ResourceFilter filters[] = getFilters() ;
	if ( filters != null ) {
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == filter ) {
		    filters[i] = null ;
		    markModified();
		}
	    }
	}
    }

    /**
     * Does this resource or one of its filters define the given attribute.
     * @param idx The attribute index.
     * @return A boolean <strong>true</strong> if attribute is defined.
     */

    public synchronized boolean definesAttribute(int idx)
	throws IllegalAttributeAccess
    {
	ResourceFilter filters[] = getFilters() ;
	if ( filters != null ) {
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == null )
		    continue ;
		if ( filters[i].definesTargetAttribute(i) )
		    return true ;
	    }
	}
	return super.definesAttribute(idx) ;
    }

    /**
     * Filters can supply part of the attributes value for a resource.
     * This method lookup the current resource for an attribute value, 
     * and than examine all its filters, to look for a filter-supplied
     * value.
     * @param idx The index of the queried attribute.
     * @param def The default return value.
     * @return The attribute value, or the provided default value if undef.
     */

    public synchronized Object getValue(int idx, Object def)
	throws IllegalAttributeAccess
    {
	ResourceFilter filters[] = getFilters() ;
	if ( filters != null ) {
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == null )
		    continue ;
		if ( filters[i].definesTargetAttribute(idx) )
		    return filters[i].getTargetValue(idx, null) ;
	    }
	}
	return super.getValue(idx, def) ;
    }

    /**
     * Register our filters, if any in the lookup result object.
     * Sub-classes of the FilteredResource class should either register 
     * their filters themselves, or call their super method. The latest
     * way is prefered, of course.
     * @param ls The current lookup state.
     * @param lr The lookup result, being constructed.
     * @exception HTTPException If something went wrong.
     * @return A boolean, <strong>true</strong> if lookup has found
     * a terminal node, <strong>false</strong> otherwise. In the latter case,
     * the caller should make sure to continue the lookup process.
     */

    public boolean lookup(LookupState ls, LookupResult lr) 
	throws HTTPException
    {
	HTTPFilter filters[] = getFilters();
	if ( filters != null ) {
	    // Mark filters, for them to be called at outgoing time:
	    lr.addFilters(filters);
	    // Some clever filter around ?
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i].lookup(ls, lr) )
		    return true;
	    }
	}
	return super.lookup(ls, lr);
    }

    /**
     * Pickle a filtered resource.
     * Pickle the resource itself, along with its associated filters.
     * @param out The data output stream to pickle to.
     */
    
    public synchronized void pickle (DataOutputStream out) 
	throws IOException
    {
	super.pickle(out) ;
	// Pickle the filters:
	ResourceFilter filters[] = getFilters() ;
	if ( filters != null ) {
	    // Count the number of available filters:
	    int cnt = 0 ;
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] != null )
		    cnt++ ;
	    }
	    // Pickle them:
	    out.writeInt(cnt) ;
	    for (int i = 0 ; i < filters.length ; i++) {
		if ( filters[i] == null )
		    continue ;
		filters[i].pickle(out) ;
	    }
	} else {
	    out.writeInt(0);
	}
    }

    /**
     * Unpickle a filtered resource.
     * If the resource is not to be initialized, both the resource and its 
     * filters aren't initialized.
     * @param in The input stream to unpickle from.
     * @param defs The default attributes for the unpickled resource.
     * @param init Should the resource be initialize ?
     * @exception IOException If the stream could not be read.
     */

    public AttributeHolder unpickleInstance(DataInputStream in
					    , Hashtable defs
					    , boolean init)
	throws IOException
    {
	super.unpickleInstance(in, defs, init) ;
	// Unpickle the filters:
	int cnt = in.readInt() ;
	if ( cnt > 0 ) {
	    if ( defs == null )
		defs = new Hashtable(3) ;
	    ResourceFilter fs[] = new ResourceFilter[cnt] ;
	    for (int i = 0 ; i < cnt ; i++) {
		defs.put("target", this) ;
		try {
		    fs[i] = (ResourceFilter) AttributeHolder.unpickle(in
								      , defs
								      , init);
		} catch (HolderInitException ex) {
		    System.out.println("REMOVED FILTER !!");
		}
	    }
	    this.filters = fs;
	}
	return this ;
    }

}

