    // FileResource.java
    // $Id: FileResource.html,v 1.2 1999/10/27 22:10:34 ylafon Exp $
    // (c) COPYRIGHT MIT and INRIA, 1996.
    // Please first read the full copyright statement in file COPYRIGHT.html

    package org.w3c.tools.resources ;

    import java.io.* ;

    /**
     * A simple file resource.
     */
    public class FileResource extends FramedResource {

        /**
         * Attributes index - The filename attribute.
         */
        protected static int ATTR_FILENAME = -1 ;

        /**
         * Attribute index - The date at which we last checked the file content.
         */
        protected static int ATTR_FILESTAMP = -1 ;

        /**
         * Attribute index - The index for the content length attribute.
         */
        protected static int ATTR_FILE_LENGTH = -1 ;

        /**
         * Attribute index - The index for the backup flag
         */
        protected static int ATTR_FILE_BACKUP = -1 ;

        static {
    	Attribute a   = null ;
    	Class     cls = null ;
    	try {
    	    cls = Class.forName("org.w3c.tools.resources.FileResource") ;
    	} catch (Exception ex) {
    	    ex.printStackTrace();
    	    System.exit(0);
    	}
    	// The filename attribute.
    	a = new FilenameAttribute("filename"
    				  , null
    				  , Attribute.EDITABLE) ;
    	ATTR_FILENAME = AttributeRegistry.registerAttribute(cls, a) ;
    	// The file stamp attribute
    	a = new DateAttribute("file-stamp"
    			      , new Long(-1)
    			      , Attribute.COMPUTED) ;
    	ATTR_FILESTAMP = AttributeRegistry.registerAttribute(cls, a) ;
    	// The file length attribute:
    	a = new IntegerAttribute("file-length"
    				 , null
    				 , Attribute.COMPUTED);
    	ATTR_FILE_LENGTH = AttributeRegistry.registerAttribute(cls,a);
    	// The backup attribute:
    	a = new BooleanAttribute("backup"
    				 , Boolean.FALSE
    				 , Attribute.EDITABLE);
    	ATTR_FILE_BACKUP = AttributeRegistry.registerAttribute(cls,a);
        }

        /**
         * The file we refer to.
         * This is a cached version of some attributes, so we need to override
         * the setValue method in order to be able to catch any changes to it.
         */
        protected File file = null ;

        /**
         * Get this resource filename attribute.
         */
        public String getFilename() {
    	return (String) getValue(ATTR_FILENAME, null);
        }

        /**
         * Get this file length
         */
        public int getFileLength() {
    	return ((Integer) getValue(ATTR_FILE_LENGTH,
    				   new Integer(0))).intValue();
        }

        /**
         * Get the date at which we last examined the file.
         */

        public long getFileStamp() {
    	return getLong(ATTR_FILESTAMP, (long) -1) ;
        }

        /**
         * Get the backup flag, create a backup file when content change
         * if true.
         */

        public boolean getBackupFlag() {
    	return getBoolean(ATTR_FILE_BACKUP, false) ;
        }

        /**
         * Get the name of the backup file for this resource.
         * @return A File object suitable to receive the backup version of this
         *    file.
         */

        public File getBackupFile() {
    	File   file = getFile() ;
    	String name = file.getName() ;
    	return new File(file.getParent(), name+"~") ;
        }

        /**
         * Save the given stream as the underlying file content.
         * This method preserve the old file version in a <code>~</code> file.
         * @param in The input stream to use as the resource entity.
         * @return A boolean, <strong>true</strong> if the resource was just
         * created, <strong>false</strong> otherwise.
         * @exception IOException If dumping the content failed.
         */

        public synchronized boolean newContent(InputStream in)
    	throws IOException
        {
    	File   file     = getFile() ;
    	boolean created = (! file.exists() | (file.length() == 0));
    	String name     = file.getName() ;
    	File   temp     = new File(file.getParent(), "#"+name+"#") ;
    	String iomsg    = null ;

    	// We are not catching IO exceptions here, except to remove temp:
    	try {
    	    FileOutputStream fout  = new FileOutputStream(temp) ;
    	    byte             buf[] = new byte[4096] ;
    	    for (int got = 0 ; (got = in.read(buf)) > 0 ; )
    		fout.write(buf, 0, got) ;
    	    fout.close() ;
    	} catch (IOException ex) {
    	    iomsg = ex.getMessage() ;
    	} finally {
    	    if ( iomsg != null ) {
    		temp.delete() ;
    		throw new IOException(iomsg) ;
    	    } else {
    		if (getBackupFlag()) {
    		    File backup = getBackupFile();
    		    if ( backup.exists() )
    			backup.delete();
    		    file.renameTo(getBackupFile()) ;
    		}
    		temp.renameTo(file) ;
    		// update our attributes for this new content:
    		updateFileAttributes() ;
    	    }
    	}
    	return created;
        }

        /**
         * Check this file content, and update attributes if needed.
         * This method is normally called before any perform request is done, so
         * that we make sure that all meta-informations is up to date before
         * handling a request.
         * @return The time of the last update to the resource.
         */

        public long checkContent() {
    	File file = getFile() ;
    	// Has this resource changed since last queried ?
    	long lmt = file.lastModified() ;
    	long cmt = getFileStamp() ;
    	if ((cmt < 0) || (cmt < lmt)) {
    	    updateFileAttributes() ;
    	    return getLastModified() ;
    	} else {
    	    return cmt;
    	}
        }

        /**
         * Set some of this resource attribute.
         * We just catch here any write access to the filename's, to update
         * our cache file object.
         */

        public synchronized void setValue(int idx, Object value) {
    	super.setValue(idx, value) ;
    	if ((idx == ATTR_FILENAME) || (idx == ATTR_IDENTIFIER))
    	    file = null;
        }

        /**
         * Get this file resource file name.
         */

        public synchronized File getFile() {
    	// Have we already computed this ?
    	if ( file == null ) {
    	    // Get the file name:
    	    String name = getFilename() ;
    	    if ( name == null )
    		name = getIdentifier() ;
    	    // Get the file directory:
    	    ResourceReference rr = getParent();
    	    ResourceReference rrtemp = null;
    	    Resource p = null;

    	    while ( true ) {
    		try {
    		    if (rr == null)
    			return null;
    		    p = rr.lock();
    		    if (p instanceof DirectoryResource) {
    			file = new File(((DirectoryResource) p)
    					.getDirectory(), name);
    			return file;
    		    }
    		    rrtemp = p.getParent();
    		} catch (InvalidResourceException ex) {
    		    return null;
    		} finally {
    		    if (rr != null)
    			rr.unlock();
    		}
    		rr = rrtemp;
    	    }
    	}
    	return file ;
        }

        /**
         * Is that resource still wrapping an existing file ?
         * If the underlying file has disappeared <string> and if</strong> the
         * container directory is extensible, remove the resource.
         */

        public synchronized boolean verify()
    	throws MultipleLockException
        {
    	File file = getFile();
    	if ( ! file.exists() ) {
    	    // Is the parent extensible:
    	    ResourceReference rr = getParent();
    	    ResourceReference rrtemp = null;
    	    Resource p = null;

    	    while ( true ) {
    		try {
    		    if (rr == null)
    			return false;
    		    p = rr.lock();
    		    if (p instanceof DirectoryResource) {
    			DirectoryResource d = (DirectoryResource) p;
    			if ( ! d.getExtensibleFlag() )
    			    return false;
    			else {
    			    // Emit an error message, and delete the resource:
    			    String msg = file+
    				": deleted, removing the FileResource.";
    			    getServer().errlog(this, msg);
    			    delete();
    			    return false;
    			}
    		    }
    		    rrtemp = p.getParent();
    		} catch (InvalidResourceException ex) {
    		    return false;
    		} finally {
    		    if (rr != null)
    			rr.unlock();
    		}
    		rr = rrtemp;
    	    }
    	} else {
    	    return true;
    	}
        }

        /**
         * Update the file related attributes.
         * The file we serve has changed since the last time we checked it, if
         * any of the attribute values depend on the file content, this is the
         * appropriate place to recompute them.
         */

        public void updateFileAttributes() {
    	File file = getFile() ;
    	setValue(ATTR_FILESTAMP, new Long(file.lastModified()));
    	setValue(ATTR_FILE_LENGTH, new Integer((int)file.length()));
    	return ;
        }

        /**
         * Update our computed attributes.
         */

        public void updateAttributes() {
    	long fstamp = getFile().lastModified() ;
    	long stamp  = getLong(ATTR_FILESTAMP, -1) ;

    	if ((stamp < 0) || (stamp < fstamp))
    	    updateFileAttributes() ;
        }

        /**
         * Initialize the FileResource instance.
         */

        public void initialize(Object values[]) {
    	super.initialize(values);
    	disableEvent();
    	// If we have a filename attribute, update url:
    	String filename = getFilename();
    	if ( filename != null ) {
    	    ResourceReference rr = getParent();
    	    try {
    		Resource parent = rr.lock();
    		setValue(ATTR_URL, parent.getURLPath()+filename);
    	    } catch (InvalidResourceException ex) {
    		//FIXME
    	    } finally {
    		rr.unlock();
    	    }
    	}
    	enableEvent();
        }

    }