// FileResource.java
// $Id: FileResource.java,v 1.1 1998/01/22 12:55:27 bmahe 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 ;
  
  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 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 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 {
	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 {
	  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 {
	  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();
  }

}
