<commit_before>/*
 * $Id$
 *
 * Copyright 2003-2005 Wanadoo Nederland B.V.
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.common.io;

import java.io.File;

import org.xins.common.Log;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.text.FastStringBuffer;

/**
 * File watcher thread. This thread checks if a file changed and if it has, it
 * notifies the listener. The check is performed every <em>n</em> seconds,
 * where <em>n</em> can be configured.
 *
 * <p>Initially this thread will be a daemon thread. This can be changed by
 * calling {@link #setDaemon(boolean)}.
 *
 * @version $Revision$ $Date$
 * @author <NAME> (<a href="mailto:<EMAIL>"><EMAIL></a>)
 *
 * @since XINS 1.0.0
 */
public final class FileWatcher extends Thread {

   //-------------------------------------------------------------------------
   // Class fields
   //-------------------------------------------------------------------------

   /**
    * Fully-qualified name of this class.
    */
   private static final String CLASSNAME = FileWatcher.class.getName();

   /**
    * Instance counter. Used to generate a unique ID for each instance.
    */
   private static int INSTANCE_COUNT;

   /**
    * Lock object for <code>INSTANCE_COUNT</code>. Never <code>null</code>.
    */
   private static final Object INSTANCE_COUNT_LOCK = new Object();

   /**
    * State in which this file watcher thread is not running.
    */
   private static final int NOT_RUNNING = 1;

   /**
    * State in which this file watcher thread is currently running and has not
    * been told to stop.
    */
   private static final int RUNNING = 2;

   /**
    * State in which this file watcher thread is currently running, but has
    * been told to stop.
    */
   private static final int SHOULD_STOP = 3;


   //-------------------------------------------------------------------------
   // Class functions
   //-------------------------------------------------------------------------

   //-------------------------------------------------------------------------
   // Constructors
   //-------------------------------------------------------------------------

   /**
    * Creates a new <code>FileWatcher</code> for the specified file, with the
    * specified interval.
    *
    * @param file
    *    the name of the file to watch, cannot be <code>null</code>.
    *
    * @param interval
    *    the interval in seconds, must be greater than or equal to 1.
    *
    * @param listener
    *    the object to notify on events, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>file == null || listener == null || interval &lt; 1</code>
    */
   public FileWatcher(String file, int interval, Listener listener)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("file", file, "listener", listener);
      if (interval < 1) {
         throw new IllegalArgumentException("interval (" + interval + ") < 1");
      }

      // Determine the unique instance ID
      int instanceID;
      synchronized (INSTANCE_COUNT_LOCK) {
         instanceID = INSTANCE_COUNT++;
      }

      // Initialize the fields
      _instanceID    = instanceID;
      _file          = new File(file);
      _interval      = interval;
      _listener      = listener;
      _listenerClass = listener.getClass().getName();
      _state         = NOT_RUNNING;

      // Configure thread as daemon
      setDaemon(true);

      // Set the name of this thread
      FastStringBuffer name = new FastStringBuffer(47, CLASSNAME);
      name.append(" #");
      name.append(_instanceID);
      name.append(" [file=\"");
      name.append(file);
      name.append("\"; interval=");
      name.append(interval);
      name.append(']');
      setName(name.toString());

      // Immediately check if the file can be read from
      firstCheck();
   }

   /**
    * Creates a new <code>FileWatcher</code> for the specified file.
    *
    * <p>The interval must be set before the thread can be started.
    *
    * @param file
    *    the name of the file to watch, cannot be <code>null</code>.
    *
    * @param listener
    *    the object to notify on events, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>file == null || listener == null</code>
    *
    * @since XINS 1.2.0
    */
   public FileWatcher(String file, Listener listener)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("file", file, "listener", listener);

      // Initialize the fields
      _instanceID    = INSTANCE_COUNT++;
      _file          = new File(file);
      _interval      = 0;
      _listener      = listener;
      _listenerClass = listener.getClass().getName();
      _state         = NOT_RUNNING;

      // Configure thread as daemon
      setDaemon(true);

      // Immediately check if the file can be read from
      firstCheck();
   }


   //-------------------------------------------------------------------------
   // Fields
   //-------------------------------------------------------------------------

   /**
    * Unique instance identifier.
    */
   private final int _instanceID;

   /**
    * The file to watch. Not <code>null</code>.
    */
   private final File _file;

   /**
    * Delay in seconds, at least 1. When the interval is uninitialized, the
    * value of this field is less than 1.
    */
   private int _interval;

   /**
    * The listener. Not <code>null</code>
    */
   private final Listener _listener;

   /**
    * The name of the class of the listener. Not <code>null</code>
    */
   private final String _listenerClass;

   /**
    * Timestamp of the last modification of the file. The value
    * <code>-1L</code> indicates that the file could not be found the last
    * time this was checked.
    *
    * <p>Initially this field is <code>-1L</code>.
    */
   private long _lastModified;

   /**
    * Current state. Never <code>null</code>. Value is one of the following
    * values:
    *
    * <ul>
    *    <li>{@link #NOT_RUNNING}
    *    <li>{@link #RUNNING}
    *    <li>{@link #SHOULD_STOP}
    * </ul>
    *
    * Once the thread is stopped, the state will be changed to
    * {@link #NOT_RUNNING} again.
    */
   private int _state;


   //-------------------------------------------------------------------------
   // Methods
   //-------------------------------------------------------------------------

   /**
    * Performs the first check on the file to determine the date the file was
    * last modified. This method is called from the constructors. If the file
    * cannot be accessed due to a {@link SecurityException}, then this
    * exception is logged and ignored.
    */
   private void firstCheck() {

      String subjectMethod = "canRead";
      try {
         if (_file.canRead()) {
            subjectMethod = "lastModified()";
            _lastModified = _file.lastModified();
         }

      // Ignore a SecurityException
      } catch (SecurityException exception) {
         String thisMethod = "<init>(java.lang.String,int,"
                                  + Listener.class.getName()
                                  + ")";
         String subjectClass = "java.io.File";
         Utils.logIgnoredException(exception,
                                   CLASSNAME,    thisMethod,
                                   subjectClass, subjectMethod);
      }
   }

   /**
    * Runs this thread. This method should not be called directly, call
    * {@link #start()} instead. That method will call this method.
    *
    * @throws IllegalStateException
    *    if <code>{@link Thread#currentThread()} != this</code>, if the thread
    *    is already running or should stop, or if the interval was not set yet.
    */
   public void run() throws IllegalStateException {

      // Check preconditions
      if (Thread.currentThread() != this) {
         throw new IllegalStateException("Thread.currentThread() != this");
      } else if (_state == RUNNING) {
         throw new IllegalStateException("The thread is already running.");
      } else if (_state == SHOULD_STOP) {
         throw new IllegalStateException("The thread should stop running.");
      } else if (_interval < 1) {
         throw new IllegalStateException("The interval has not been set yet.");
      }

      Log.log_1200(_instanceID, _file.getPath(), _interval);

      // Move to the RUNNING state
      synchronized (this) {
         _state = RUNNING;
      }

      // Loop while we should keep running
      boolean shouldStop = false;
      while (! shouldStop) {
         try {
            while(! shouldStop) {

               // Wait for the designated amount of time
               sleep(((long)_interval) * 1000L);

               // Should we stop?
               synchronized (this) {
                  shouldStop = (_state != RUNNING);
               }

               // If we do not have to stop yet, check if the file changed
               if (! shouldStop) {
                  check();
               }
            }
         } catch (InterruptedException exception) {
            // TODO: (#HERE#) Compute how much time we still need to sleep
            // before checking the file, depending on the new interval (if
            // that caused the InterruptedException here)
         }
      }

      // Thread stopped
      Log.log_1203(_instanceID);
   }

   /**
    * Returns the current interval.
    *
    * @return interval
    *    the current interval in seconds, always greater than or equal to 1,
    *    except if the interval is not initialized yet, in which case 0 is
    *    returned.
    */
   public synchronized int getInterval() {
      return _interval;
   }

   /**
    * Changes the file check interval.
    *
    * @param newInterval
    *    the new interval in seconds, must be greater than or equal to 1.
    *
    * @throws IllegalArgumentException
    *    if <code>interval &lt; 1</code>
    */
   public synchronized void setInterval(int newInterval)
   throws IllegalArgumentException {

      // Check preconditions
      if (newInterval < 1) {
         throw new IllegalArgumentException("newInterval (" + newInterval + ") < 1");
      }

      // Change the interval
      if (newInterval != _interval) {
         Log.log_1201(_instanceID, _file.getPath(), _interval, newInterval);
         _interval = newInterval;
      }

      // TODO: Interrupt the thread (see #HERE#)
   }

   /**
    * Stops this thread.
    *
    * @throws IllegalStateException
    *    if the thread is currently not running or already stopping.
    */
   public synchronized void end() throws IllegalStateException {

      // Check state
      if (_state == NOT_RUNNING) {
         throw new IllegalStateException("The thread is currently not running.");
      } else if (_state == SHOULD_STOP) {
         throw new IllegalStateException("The thread is already stopping.");
      }

      Log.log_1202(_instanceID, _file.getPath());

      // Change the state and interrupt the thread
      _state = SHOULD_STOP;
      this.interrupt();
   }

   /**<commit_msg>Properly setting the name of the thread, also in 2nd constructor. And updating the name when the interval changes.<commit_after>/*
 * $Id$
 *
 * Copyright 2003-2005 Wanadoo Nederland B.V.
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.common.io;

import java.io.File;

import org.xins.common.Log;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.text.FastStringBuffer;

/**
 * File watcher thread. This thread checks if a file changed and if it has, it
 * notifies the listener. The check is performed every <em>n</em> seconds,
 * where <em>n</em> can be configured.
 *
 * <p>Initially this thread will be a daemon thread. This can be changed by
 * calling {@link #setDaemon(boolean)}.
 *
 * @version $Revision$ $Date$
 * @author <NAME> (<a href="mailto:<EMAIL>"><EMAIL></a>)
 *
 * @since XINS 1.0.0
 */
public final class FileWatcher extends Thread {

   //-------------------------------------------------------------------------
   // Class fields
   //-------------------------------------------------------------------------

   /**
    * Fully-qualified name of this class.
    */
   private static final String CLASSNAME = FileWatcher.class.getName();

   /**
    * Instance counter. Used to generate a unique ID for each instance.
    */
   private static int INSTANCE_COUNT;

   /**
    * Lock object for <code>INSTANCE_COUNT</code>. Never <code>null</code>.
    */
   private static final Object INSTANCE_COUNT_LOCK = new Object();

   /**
    * State in which this file watcher thread is not running.
    */
   private static final int NOT_RUNNING = 1;

   /**
    * State in which this file watcher thread is currently running and has not
    * been told to stop.
    */
   private static final int RUNNING = 2;

   /**
    * State in which this file watcher thread is currently running, but has
    * been told to stop.
    */
   private static final int SHOULD_STOP = 3;


   //-------------------------------------------------------------------------
   // Class functions
   //-------------------------------------------------------------------------

   //-------------------------------------------------------------------------
   // Constructors
   //-------------------------------------------------------------------------

   /**
    * Creates a new <code>FileWatcher</code> for the specified file, with the
    * specified interval.
    *
    * @param file
    *    the name of the file to watch, cannot be <code>null</code>.
    *
    * @param interval
    *    the interval in seconds, must be greater than or equal to 1.
    *
    * @param listener
    *    the object to notify on events, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>file == null || listener == null || interval &lt; 1</code>
    */
   public FileWatcher(String file, int interval, Listener listener)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("file", file, "listener", listener);
      if (interval < 1) {
         throw new IllegalArgumentException("interval (" + interval + ") < 1");
      }

      // Determine the unique instance ID
      int instanceID;
      synchronized (INSTANCE_COUNT_LOCK) {
         instanceID = INSTANCE_COUNT++;
      }

      // Initialize the fields
      _instanceID    = instanceID;
      _file          = new File(file);
      _filePath      = _file.getPath();
      _interval      = interval;
      _listener      = listener;
      _listenerClass = listener.getClass().getName();
      _state         = NOT_RUNNING;

      // Configure thread as daemon
      setDaemon(true);

      // Set the name of this thread
      configureThreadName();

      // Immediately check if the file can be read from
      firstCheck();
   }

   /**
    * Creates a new <code>FileWatcher</code> for the specified file.
    *
    * <p>The interval must be set before the thread can be started.
    *
    * @param file
    *    the name of the file to watch, cannot be <code>null</code>.
    *
    * @param listener
    *    the object to notify on events, cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>file == null || listener == null</code>
    *
    * @since XINS 1.2.0
    */
   public FileWatcher(String file, Listener listener)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("file", file, "listener", listener);

      // Initialize the fields
      _instanceID    = INSTANCE_COUNT++;
      _file          = new File(file);
      _filePath      = _file.getPath();
      _interval      = 0;
      _listener      = listener;
      _listenerClass = listener.getClass().getName();
      _state         = NOT_RUNNING;

      // Configure thread as daemon
      setDaemon(true);

      // Set the name of this thread
      configureThreadName();

      // Immediately check if the file can be read from
      firstCheck();
   }


   //-------------------------------------------------------------------------
   // Fields
   //-------------------------------------------------------------------------

   /**
    * Unique instance identifier.
    */
   private final int _instanceID;

   /**
    * The file to watch. Not <code>null</code>.
    */
   private final File _file;

   /**
    * The path of the file to watch. Not <code>null</code>.
    */
   private final String _filePath;

   /**
    * Delay in seconds, at least 1. When the interval is uninitialized, the
    * value of this field is less than 1.
    */
   private int _interval;

   /**
    * The listener. Not <code>null</code>
    */
   private final Listener _listener;

   /**
    * The name of the class of the listener. Not <code>null</code>
    */
   private final String _listenerClass;

   /**
    * Timestamp of the last modification of the file. The value
    * <code>-1L</code> indicates that the file could not be found the last
    * time this was checked.
    *
    * <p>Initially this field is <code>-1L</code>.
    */
   private long _lastModified;

   /**
    * Current state. Never <code>null</code>. Value is one of the following
    * values:
    *
    * <ul>
    *    <li>{@link #NOT_RUNNING}
    *    <li>{@link #RUNNING}
    *    <li>{@link #SHOULD_STOP}
    * </ul>
    *
    * Once the thread is stopped, the state will be changed to
    * {@link #NOT_RUNNING} again.
    */
   private int _state;


   //-------------------------------------------------------------------------
   // Methods
   //-------------------------------------------------------------------------

   /**
    * Configures the name of this thread.
    */
   private void configureThreadName() {
      FastStringBuffer name = new FastStringBuffer(47, CLASSNAME);
      name.append(" #");
      name.append(_instanceID);
      name.append(" [file=\"");
      name.append(_filePath);
      name.append("\"; interval=");
      name.append(_interval);
      name.append(']');
      setName(name.toString());
   }

   /**
    * Performs the first check on the file to determine the date the file was
    * last modified. This method is called from the constructors. If the file
    * cannot be accessed due to a {@link SecurityException}, then this
    * exception is logged and ignored.
    */
   private void firstCheck() {

      String subjectMethod = "canRead";
      try {
         if (_file.canRead()) {
            subjectMethod = "lastModified()";
            _lastModified = _file.lastModified();
         }

      // Ignore a SecurityException
      } catch (SecurityException exception) {
         String thisMethod = "<init>(java.lang.String,int,"
                                  + Listener.class.getName()
                                  + ")";
         String subjectClass = "java.io.File";
         Utils.logIgnoredException(exception,
                                   CLASSNAME,    thisMethod,
                                   subjectClass, subjectMethod);
      }
   }

   /**
    * Runs this thread. This method should not be called directly, call
    * {@link #start()} instead. That method will call this method.
    *
    * @throws IllegalStateException
    *    if <code>{@link Thread#currentThread()} != this</code>, if the thread
    *    is already running or should stop, or if the interval was not set yet.
    */
   public void run() throws IllegalStateException {

      // Check preconditions
      if (Thread.currentThread() != this) {
         throw new IllegalStateException("Thread.currentThread() != this");
      } else if (_state == RUNNING) {
         throw new IllegalStateException("The thread is already running.");
      } else if (_state == SHOULD_STOP) {
         throw new IllegalStateException("The thread should stop running.");
      } else if (_interval < 1) {
         throw new IllegalStateException("The interval has not been set yet.");
      }

      Log.log_1200(_instanceID, _filePath, _interval);

      // Move to the RUNNING state
      synchronized (this) {
         _state = RUNNING;
      }

      // Loop while we should keep running
      boolean shouldStop = false;
      while (! shouldStop) {
         try {
            while(! shouldStop) {

               // Wait for the designated amount of time
               sleep(((long)_interval) * 1000L);

               // Should we stop?
               synchronized (this) {
                  shouldStop = (_state != RUNNING);
               }

               // If we do not have to stop yet, check if the file changed
               if (! shouldStop) {
                  check();
               }
            }
         } catch (InterruptedException exception) {
            // TODO: (#HERE#) Compute how much time we still need to sleep
            // before checking the file, depending on the new interval (if
            // that caused the InterruptedException here)
         }
      }

      // Thread stopped
      Log.log_1203(_instanceID);
   }

   /**
    * Returns the current interval.
    *
    * @return interval
    *    the current interval in seconds, always greater than or equal to 1,
    *    except if the interval is not initialized yet, in which case 0 is
    *    returned.
    */
   public synchronized int getInterval() {
      return _interval;
   }

   /**
    * Changes the file check interval.
    *
    * @param newInterval
    *    the new interval in seconds, must be greater than or equal to 1.
    *
    * @throws IllegalArgumentException
    *    if <code>interval &lt; 1</code>
    */
   public synchronized void setInterval(int newInterval)
   throws IllegalArgumentException {

      // Check preconditions
      if (newInterval < 1) {
         throw new IllegalArgumentException("newInterval (" + newInterval + ") < 1");
      }

      // Change the interval
      if (newInterval != _interval) {
         Log.log_1201(_instanceID, _filePath, _interval, newInterval);
         _interval = newInterval;
      }

      // Update the thread name
      configureThreadName();

      // TODO: Interrupt the thread (see #HERE#)
   }

   /**
    * Stops this thread.
    *
    * @throws IllegalStateException
    *    if the thread is currently not running or already stopping.
    */
   public synchronized void end() throws IllegalStateException {

      // Check state
      if (_state == NOT_RUNNING) {
         throw new IllegalStateException("The thread is currently not running.");
      } else if (_state == SHOULD_STOP) {
         throw new IllegalStateException("The thread is already stopping.");
      }

      Log.log_1202(_instanceID, _filePath);

      // Change the state and interrupt the thread
      _state = SHOULD_STOP;
      this.interrupt();
   }

   /**
{'fraction_non_alphanumeric': 0.16003119178616299, 'fraction_numerical': 0.004808733700125634, 'mean_word_length': 1.74287072243346, 'pattern_counts': {'":': 0, '<': 123, '<?xml version=': 0, '>': 113, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4147990', 'n_tokens_mistral': 6105, 'n_tokens_neox': 5903, 'n_words': 2027}