/*
 ************************************************************************
 *
 * Copyright (c) 2003-2004, C&C Research Laboratories, NEC Europe Ltd.
 *
 * Copyright in this software belongs to C&C Research Laboratories,
 * Rathausallee 10, 53757 Sankt Augustin, Germany.
 *
 * This software may not be used, sold, licensed, transferred, copied
 * or reproduced in whole or in part in any manner or form or in or
 * on any media by any person other than in accordance with the terms
 * of the Licence Agreement supplied with the software, or otherwise
 * without the prior written consent of the copyright owners.
 *
 * This software is distributed WITHOUT ANY WARRANTY, without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE, except where stated in the Licence Agreement supplied with
 * the software.
 *
 *  Created By :           G.A. Kohring
 *  Created for Project :  GEMSS (IST-2001-37153)
 *
 ************************************************************************
 */

package de.nece.ccrle.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

/**
 * Monitor files and directories for any changes by external entities.
 *
 * @author Greg Kohring
 */
public class FileMonitor extends TimerTask {

    private Timer timer = null;
    private Map lastModified = null;
    private File file = null;
    private boolean running = false;
    private Vector listeners = null;
    private Vector changed = null;
    private Vector added   = null;
    private Vector deleted = null;


    // period between executions of this task;
    private long period = 60;

    /**
     * Construct a monitor for the specified file.
     * If the file denoted by the specified abstract pathname is a directory, 
     * all the files in the directory and any subdirectories will be 
     * monitored.
     *
     * @param file the abstract pathname of a file which is to be monitored.
     * @throws FileNotFoundException if the file denoted by the specified
     *      abstract pathname does not exist.
     */
    public FileMonitor( File file ) throws FileNotFoundException {
        if ( file.exists() ){
            this.file = file;
            timer = new Timer( true );
            lastModified = new HashMap();
            listeners = new Vector();
            changed = new Vector();
            added   = new Vector();
            deleted = new Vector();
        } else {
            throw new FileNotFoundException( " file: " + file.toString() );
        }
    }

    /**
     * Retrieves the <code>File</code> this monitor is monitoring.
     *
     * @return a <code>File</code> object denoting the file or directory being
     *      monitored.
     */
    public File getFile(){
        return file;
    }

    /**
     * Start monitoring.
     */
    public void start() {
        if ( !running ){
            lastModified.clear();
            timer.schedule( this, 0L, period*1000 );
            running = true;
        }
    }

    // Generates a list of all files in the specified abstract pathname.
    // If the specified abstract pathname is a directory, then the 
    // directory will be searched recursively.
    private void listAllFiles( List fileList, File file ){
        if ( file.isDirectory() ){
            File[] listing = file.listFiles();
            for ( int f = 0; f < listing.length; f++){
                listAllFiles( fileList, listing[f] );
            }
        } else {
            fileList.add( file );
        }
    }

    /**
     * Stop monitoring
     */
    public void stop() {
        this.cancel();
        running = false;
    }

    /**
     * Retrieves the period between monitoring events.
     * 
     * @return a long denoting the period in seconds, between monitoring
     *      events.
     */
    public long getPeriod(){
        return period;
    }

    /**
     * Sets the period between monitoring events.
     * <p>
     * NOTE:Setting the period too short for a large directory may
     *      degrade performance.
     * 
     * @param period the period in seconds, between monitoring
     *      events. 
     */
    public void setPeriod( long period ){
        this.period = period;
    }

    /**
     * Adds the specified listener to the list of listeners this monitor
     * will notify if there are any changes.
     *
     * @param fcl the <code>FileChangeListener</code> to be notified.
     */
    public void addListener( FileChangeListener fcl ) {
        listeners.add( fcl );
    }

    /**
     * Removes the specified listener from the list of listeners this monitor
     * notifies in the event of any changes.
     *
     * @param fcl the <code>FileChangeListener</code> to be removed.
     */
    public void removeListener(  FileChangeListener fcl ){
        listeners.remove( fcl );
    }

    /**
     * Executes a single monitoring event. All the files in the range of this
     * monitor will checked for changes, additions and deletions. If any
     * modifications are found, any listeners which happen to be listening
     * are called.
     *
     */
    public void run() {

        // initialize the table of last modified values

        if ( lastModified.isEmpty() ){

            Vector fileList = new Vector();
            listAllFiles( fileList, file );
            for ( int f = 0; f < fileList.size(); f++ ){
                File file = (File) fileList.get( f );
                lastModified.put( file, new Long( file.lastModified() ) );
            }

            return;
        }

        // Get the current list of files 

        Vector current = new Vector();

        listAllFiles( current, file );

        // Create new lists of modified entries

        changed.clear();
        added.clear();
        deleted.clear();

        // Check for files changed or added.

        for ( int f = 0; f < current.size(); f++ ){
            File file = (File) current.get( f );
            Long last = (Long) lastModified.get( file );
            if ( last != null ){
                long lm = last.longValue();
                if ( lm != file.lastModified() ){
                    lastModified.put( file, new Long( file.lastModified() ) );
                    changed.add( file );
                }
            } else {
                lastModified.put( file, new Long( file.lastModified() ) );
                added.add( file );
            }
        }

        // Check for files deleted.

        Iterator it = lastModified.keySet().iterator();
        while( it.hasNext() ){
            File file = (File) it.next();
            if ( !current.contains( file ) ){
                deleted.add( file );
                lastModified.remove( file );
            }
        }

        // if there are any changes, notify all listeners

        if ( !changed.isEmpty() || !added.isEmpty() || !deleted.isEmpty() ){

            for ( int l = 0; l < listeners.size(); l++ ){

                FileChangeListener fcl = (FileChangeListener) listeners.get(l);

                if ( !changed.isEmpty() ) fcl.changed( changed );
                if ( !added.isEmpty() ) fcl.added( added );
                if ( !deleted.isEmpty() ) fcl.deleted( deleted );
            }
        }

    }
}
