// ResourceFilter.java
// $Id: ResourceFilter.java,v 1.13 1997/01/24 12:02:36 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.* ;

public class ResourceFilter extends Resource
    implements HTTPFilter, ResourceShadower 
{
    /**
     * Attribute index - Our target attribute.
     */
    protected static int ATTR_TARGET = -1 ;

    static {
	Attribute a   = null ;
	Class     cls = null ;

	try {
	    cls = Class.forName("w3c.jigsaw.resources.ResourceFilter") ;
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    System.exit(1) ;
	}
	// The class for files having this extension.
	a = new ObjectAttribute("target"
				, "w3c.jigsaw.resources.FilteredResource"
				, null
				, Attribute.DONTSAVE) ;
	ATTR_TARGET = AttributeRegistry.registerAttribute(cls, a) ;
    }

    /**
     * Our target attribute description:
     */
    protected Attribute target_attributes[] = null ;
    /**
     * Our target shadowed values.
     */
    protected Object target_values[] = null ;


    /**
     * Get this resource's help url.
     * @return An URL, encoded as a String, or <strong>null</strong> if not
     * available.
     */

    public String getHelpURL() {
	httpd server = ((FilteredResource) getTargetResource()).getServer();
	if ( server == null ) 
	    return null;
	String docurl = server.getDocumentationURL();
	if ( docurl == null )
	    return null;
	return docurl + "/" + getClass().getName() + ".html";
    }

    /**
     * Get the help URL for that resource's attribute.
     * @param topic The topic (can be an attribute name, or a property, etc).
     * @return A String encoded URL, or <strong>null</strong>.
     */

    public String getHelpURL(String topic) {
	httpd server = ((FilteredResource) getTargetResource()).getServer();
	if ( server == null ) 
	    return null;
	String docurl = server.getDocumentationURL();
	if ( docurl == null )
	    return null;
	Class defines = AttributeRegistry.getAttributeClass(getClass(), topic);
	if ( defines != null ) 
	    return docurl + "/" + defines.getName() + ".html";
	return null;
    }

    /**
     * Get our target resource.
     */

    public Resource getTargetResource() {
	return (Resource) getValue(ATTR_TARGET, null) ;
    }

    /**
     * Get our target attribute list.
     */

    public Attribute[] getTargetAttributes() {
	return target_attributes;
    }

    /**
     * Get a shadowed attribute value.
     * @param idx The index of the attribute.
     * @param def The default value to return if undef.
     * @return The attribute value or the provided default value.
     */

    public Object getTargetValue(int idx, Object def) {
	if ((idx < 0) || (idx >= target_values.length))
	    throw new IllegalAttributeAccess(this, idx) ;
	Object value = target_values[idx] ;
	if ( value == null )
	    return (def == null) ? target_attributes[idx].getDefault() : def ;
	else
	    return value ;
    }

    /**
     * Get a shadowed attribute value (by name).
     * @param name The name of the attribute.
     * @param def The default value to return if undef.
     * @return The attribute value or the provided default value.
     */

    public Object getTargetValue(String name, Object def) {
	for (int i = 0 ; i < target_attributes.length ; i++) {
	    if ( name.equals(target_attributes[i].getName()) ) {
		return target_values[i] ;
	    }
	}
	return null ;
    }

    /**
     * Set a shadow attribute value.
     * @param idx The index of the attribute to set.
     * @param value Its new value.
     */

    public void setTargetValue(int idx, Object def) {
	if ((idx < 0) || (idx >= target_values.length))
	    throw new IllegalAttributeAccess(this, idx) ;
	target_values[idx] = def ;
	markModified() ;
    }

    /**
     * Set a shadow attribute value.
     * @param name The name of the attribute to set.
     * @param value Its new value.
     */

    public void setTargetValue(String name, Object def) {
	for (int i = 0 ; i < target_attributes.length ; i++) {
	    if ( name.equals(target_attributes[i].getName()) ) {
		setTargetValue(i, def) ;
		markModified() ;
		return ;
	    }
	}
	throw new IllegalAttributeAccess(this, name) ;
    }

    /**
     * Does this filters defines the given target attribute.
     * @param idx The index of the target attribute.
     * @return A boolean <strong>true</strong if attribute is defined.
     */

    public boolean definesTargetAttribute(int idx) {
	if (idx < 0)
	    throw new IllegalAttributeAccess(this, idx) ;
	if (idx >= target_attributes.length)
	    return false ;
	return (target_values[idx] != null) ;
    }

    /**
     * Does this filters defines the given target attribute.
     * @param name The name of the target attribute.
     * @return A boolean <strong>true</strong if attribute is defined.
     */

    public boolean definesTargetAttribute(String name) {
	for (int i = 0 ; i < target_attributes.length ; i++) {
	    if ( name.equals(target_attributes[i].getName()) ) 
		return (target_values[i] != null) ;
	}
	return false ;
    }


    /**
     * Mark this filter as modified.
     * Delegate the dirty bit to our target.
     */

    public void markModified() {
	getTargetResource().markModified() ;
    }

    /**
     * Lookup time filtering.
     * This method is called while Jigsaw performs resource lookup. Each time
     * a lookup end up on the target resource of that filter, this method
     * will be called.
     * @param ls The current lookup state.
     * @param lr The current lookup result.
     * @return A boolean, <strong>true</strong> if this filter has performed
     * the whole lookup, and side-effect the lookup result appropriatelly,
     * <strong>false</strong> otherwise.
     */

    public boolean lookup(LookupState ls, LookupResult lr) 
	throws HTTPException
    {
	return false;
    }

    /**
     * Simplified ingoingFilter API.
     * This is a default, simplified API to the ingoing filter method.
     * @param request The request to filter.
     * @return A Reply instance, or <strong>null</strong> if processing
     * should continue normally.
     * @exception HTTPException If processing should be interrupted, because
     * an abnormal situation occured.
     */

    public Reply ingoingFilter(Request request) 
	throws HTTPException
    {
	return null;
    }

    /**
     * The ingoingFilter method.
     * This is the method that really gets called by Jigsaw core. By default
     * it will invoke the simpler <code>ingoingFilter</code> method,
     * taking only the request has a parameter.
     * @param request The request that is about to be handled.
     * @param filters The whole filter list to be applied to the resource.
     * @param i A pointer into the above array, indicating which filters
     * have already been triggered (the one whose index are lower than 
     * <strong>i</strong>), and what filters have to be triggered (the one
     * whose index are greater or equal to <strong>i+1</strong>).
     * @return A Reply instance, if the filter did know how to answer
     * the request without further processing, <strong>null</strong>
     * otherwise.
     */

    public Reply ingoingFilter(Request request, HTTPFilter filters[], int i)
	throws HTTPException
    {
	return ingoingFilter(request);
    }


    /**
     * Simplified API to the outgoing filter metho.
     * This is a simplified API to the ougoing filter method, you can either
     * overide this method, or the more powerfull one.
     * @param request The original request.
     * @param reply It's original reply.
     * @return A Reply instance, or <strong>null</strong> if processing
     * should continue normally.
     * @exception HTTPException If processing should be interrupted, because
     * an abnormal situation occured.
     */
     
     public Reply outgoingFilter(Request request, Reply reply) 
	 throws HTTPException
    {
	return null;
    }

    /**
     * The outgoingFilter method.
     * This method is the one that gets called by Jigsaw core. By default it
     * will call the simpler <code>outgoingFilter</code> method that takes
     * only the request and the reply as parameters.
     * @param request The request that has been processed.
     * @param reply The original reply as emitted by the resource.
     * @param filters The whole filter that applies to the resource.
     * @param i The current index of filters. The <em>i</em> filter is ourself,
     * filters with lower indexes have already been applied, and filters with
     * greater indexes are still to be applied.
     * @return A Reply instance, if that filter know how to complete the
     * request processing, or <strong>null</strong> if reminaing filters
     * are to be called by Jigsaw engine.
     */

    public Reply outgoingFilter(Request request, Reply reply
				, HTTPFilter filters[], int fidx) 
	throws HTTPException
    {
	Reply fr = outgoingFilter(request, reply);
	return (fr != reply) ? fr : null;
    }

    /**
     * Pickle a filter.
     * Pickle the filter attributes themselves, and than the set of shadow
     * attribute values we maintain for our target.
     * @param out The data output stream to pickle to.
     */

    public void pickle(DataOutputStream out)
	throws IOException
    {
	// Pickle myself:
	super.pickle(out) ;
	// Pickle my set of shadow attribute values:
	if ( slowpickle ) {
	    // Slow but evolutive pickling
	    for (int i = 0 ; i < target_attributes.length ; i++) {
		Attribute a = target_attributes[i];
		if ((target_values[i] != null) 
		    && ! a.checkFlag(Attribute.DONTSAVE)) {
		    out.writeUTF(a.getName());
		    out.writeShort(a.getPickleLength(target_values[i]));
		    a.pickle(out, target_values[i]);
		}
	    }
	    out.writeUTF("");
	} else {
	    for (int i = 0 ; i < target_attributes.length ; i++) {
		Attribute attr = target_attributes[i] ;
		if ((target_values[i] != null)
		    && ! attr.checkFlag(Attribute.DONTSAVE)) {
		    out.writeBoolean(true) ;
		    attr.pickle(out, target_values[i]) ;
		} else {
		    out.writeBoolean(false) ;
		}
	    }
	} 
    }

    /**
     * Unpickle a resource filter.
     * Unpickle the attribute of the filter, and than the shadow attribute
     * of our target.
     * @param in The input stream to unpickle form.
     * @param defs The default values for our shadow attributes.
     * @param init Should the filter be initialized ?
     * @exception IOException If the input stream couldn't be read.
     */

    public AttributeHolder unpickleInstance(DataInputStream in
					    , Hashtable defs
					    , boolean init)
	throws IOException 
    {
	// Unpickle myself:
	super.unpickleInstance(in, defs, init) ;
	// Unpickle my additional infos:
	FilteredResource target = (FilteredResource) getTargetResource() ;
	Class            cls    = target.getClass() ;
	this.target_attributes = AttributeRegistry.getClassAttributes(cls) ;
	this.target_values     = new Object[target_attributes.length] ;
	if ( slowpickle ) {
	    // Slow unpickle
	    String name = null;
	    while ( ! (name = in.readUTF()).equals("") ) {
		int ai = lookupAttribute(name);
		int as = ((int) in.readShort()) & 0xffff;
		if ( ai >= 0 )
		    target_values[ai] = target_attributes[ai].unpickle(in);
		else
		    in.skip(as);
	    }
	} else {
	    for (int i = 0 ; i < target_values.length ; i++) {
		if ( in.readBoolean() ) {
		    target_values[i] = target_attributes[i].unpickle(in) ;
		} else {
		    target_values[i] = null ;
		}
	    }
	}
	return this ;
    }

    /**
     * Initialize the filter.
     * @param values The default attribute values.
     */

    public void initialize(Object values[]) {
	// Super initialize:
	super.initialize(values) ;
	// Check our target attributes:
	FilteredResource target = (FilteredResource) getTargetResource() ;
	if ( target == null )
	    throw new HolderInitException("No target !") ;
	if ( target_attributes == null ) {
	    Class cls = target.getClass() ;
	    target_attributes = AttributeRegistry.getClassAttributes(cls) ;
	}
	if ( target_values == null )
	    target_values = new Object[target_attributes.length] ;
    }

}
