Bug 42042: Copy org.apache.log4j.scheduler to component project

git-svn-id: https://svn.apache.org/repos/asf/logging/sandbox/log4j/component@527664 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/log4j/scheduler/Job.java b/src/main/java/org/apache/log4j/scheduler/Job.java
new file mode 100644
index 0000000..a0e9be4
--- /dev/null
+++ b/src/main/java/org/apache/log4j/scheduler/Job.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.scheduler;
+
+
+/**
+ * Job is a very simple interface. It only has a single method {@link #execute}
+ * which is called by the {@link Scheduler} when a task is ready for execution.
+ * <p/>
+ * It is assumed that the execution context
+ * are contained within the implementing
+ * {@link Job} itself.
+ *
+ * @author Ceki G&uuml;lc&uuml;
+ */
+public interface Job {
+    /**
+     * Execute job.
+     */
+    void execute();
+}
diff --git a/src/main/java/org/apache/log4j/scheduler/Scheduler.java b/src/main/java/org/apache/log4j/scheduler/Scheduler.java
new file mode 100644
index 0000000..72809f7
--- /dev/null
+++ b/src/main/java/org/apache/log4j/scheduler/Scheduler.java
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.scheduler;
+
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A simple but still useful implementation of a Scheduler (in memory only).
+ * <p/>
+ * This implementation will work very well when the number of scheduled job is
+ * small, say less than 100 jobs. If a larger number of events need to be
+ * scheduled, than a better adapted data structure for the jobList can give
+ * improved performance.
+ *
+ * @author Ceki
+ */
+public class Scheduler extends Thread {
+
+    /**
+     * Job list.
+     */
+    List jobList;
+    /**
+     * If set true, scheduler has or should shut down.
+     */
+    boolean shutdown = false;
+
+    /**
+     * Create new instance.
+     */
+    public Scheduler() {
+        super();
+        jobList = new Vector();
+    }
+
+    /**
+     * Find the index of a given job.
+     * @param job job
+     * @return -1 if the job could not be found.
+     */
+    int findIndex(final Job job) {
+        int size = jobList.size();
+        boolean found = false;
+
+        int i = 0;
+        for (; i < size; i++) {
+            ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
+            if (se.job == job) {
+                found = true;
+                break;
+            }
+        }
+        if (found) {
+            return i;
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Delete the given job.
+     * @param job job.
+     * @return true if the job could be deleted, and
+     * false if the job could not be found or if the Scheduler is about to
+     * shutdown in which case deletions are not permitted.
+     */
+    public synchronized boolean delete(final Job job) {
+        // if already shutdown in the process of shutdown, there is no
+        // need to remove Jobs as they will never be executed.
+        if (shutdown) {
+            return false;
+        }
+        int i = findIndex(job);
+        if (i != -1) {
+            ScheduledJobEntry se = (ScheduledJobEntry) jobList.remove(i);
+            if (se.job != job) { // this should never happen
+                new IllegalStateException("Internal programming error");
+            }
+            // if the job is the first on the list,
+            // then notify the scheduler thread to schedule a new job
+            if (i == 0) {
+                this.notifyAll();
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    /**
+     * Schedule a {@link Job} for execution at system time given by
+     * the <code>desiredTime</code> parameter.
+     * @param job job to schedule.
+     * @param desiredTime desired time of execution.
+     */
+    public synchronized void schedule(final Job job,
+                                      final long desiredTime) {
+        schedule(new ScheduledJobEntry(job, desiredTime));
+    }
+
+    /**
+     * Schedule a {@link Job} for execution at system time given by
+     * the <code>desiredTime</code> parameter.
+     * <p/>
+     * The job will be rescheduled. It will execute with a frequency determined
+     * by the period parameter.
+     * @param job job to schedule.
+     * @param desiredTime desired time of execution.
+     * @param period repeat period.
+     */
+    public synchronized void schedule(final Job job,
+                                      final long desiredTime,
+                                      final long period) {
+        schedule(new ScheduledJobEntry(job, desiredTime, period));
+    }
+
+    /**
+     * Change the period of a job. The original job must exist for its period
+     * to be changed.
+     * <p/>
+     * The method returns true if the period could be changed, and false
+     * otherwise.
+     * @param job job.
+     * @param newPeriod new repeat period.
+     * @return true if period could be changed.
+     */
+    public synchronized boolean changePeriod(final Job job,
+                                             final long newPeriod) {
+        if (newPeriod <= 0) {
+            throw new IllegalArgumentException(
+                    "Period must be an integer langer than zero");
+        }
+
+        int i = findIndex(job);
+        if (i == -1) {
+            return false;
+        } else {
+            ScheduledJobEntry se = (ScheduledJobEntry) jobList.get(i);
+            se.period = newPeriod;
+            return true;
+        }
+    }
+
+    /**
+     * Schedule a job.
+     * @param newSJE new job entry.
+     */
+    private synchronized void schedule(final ScheduledJobEntry newSJE) {
+        // disallow new jobs after shutdown
+        if (shutdown) {
+            return;
+        }
+        int max = jobList.size();
+        long desiredExecutionTime = newSJE.desiredExecutionTime;
+
+        // find the index i such that timeInMillis < jobList[i]
+        int i = 0;
+        for (; i < max; i++) {
+
+            ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(i);
+
+            if (desiredExecutionTime < sje.desiredExecutionTime) {
+                break;
+            }
+        }
+        jobList.add(i, newSJE);
+        // if the jobList was empty, then notify the scheduler thread
+        if (i == 0) {
+            this.notifyAll();
+        }
+    }
+
+    /**
+     * Shut down scheduler.
+     */
+    public synchronized void shutdown() {
+        shutdown = true;
+    }
+
+    /**
+     * Run scheduler.
+     */
+    public synchronized void run() {
+        while (!shutdown) {
+            if (jobList.isEmpty()) {
+                linger();
+            } else {
+                ScheduledJobEntry sje = (ScheduledJobEntry) jobList.get(0);
+                long now = System.currentTimeMillis();
+                if (now >= sje.desiredExecutionTime) {
+                    executeInABox(sje.job);
+                    jobList.remove(0);
+                    if (sje.period > 0) {
+                        sje.desiredExecutionTime = now + sje.period;
+                        schedule(sje);
+                    }
+                } else {
+                    linger(sje.desiredExecutionTime - now);
+                }
+            }
+        }
+        // clear out the job list to facilitate garbage collection
+        jobList.clear();
+        jobList = null;
+        System.out.println("Leaving scheduler run method");
+    }
+
+    /**
+     * We do not want a single failure to affect the whole scheduler.
+     * @param job job to execute.
+     */
+    void executeInABox(final Job job) {
+        try {
+            job.execute();
+        } catch (Exception e) {
+            System.err.println("The execution of the job threw an exception");
+            e.printStackTrace(System.err);
+        }
+    }
+
+    /**
+     * Wait for notification.
+     */
+    void linger() {
+        try {
+            while (jobList.isEmpty() && !shutdown) {
+                this.wait();
+            }
+        } catch (InterruptedException ie) {
+            shutdown = true;
+        }
+    }
+
+    /**
+     * Wait for notification or time to elapse.
+     * @param timeToLinger time to linger.
+     */
+    void linger(final long timeToLinger) {
+        try {
+            this.wait(timeToLinger);
+        } catch (InterruptedException ie) {
+            shutdown = true;
+        }
+    }
+
+    /**
+     * Represents an entry in job scheduler.
+     */
+    static final class ScheduledJobEntry {
+        /**
+         * Desired execution time.
+         */
+        long desiredExecutionTime;
+        /**
+         * Job to run.
+         */
+        Job job;
+        /**
+         * Repeat period.
+         */
+        long period = 0;
+
+        /**
+         * Create new instance.
+         * @param job job
+         * @param desiredTime desired time.
+         */
+        ScheduledJobEntry(final Job job, final long desiredTime) {
+            this(job, desiredTime, 0);
+        }
+
+        /**
+         * Create new instance.
+         * @param job job
+         * @param desiredTime desired time
+         * @param period repeat period
+         */
+        ScheduledJobEntry(final Job job,
+                          final long desiredTime,
+                          final long period) {
+            super();
+            this.desiredExecutionTime = desiredTime;
+            this.job = job;
+            this.period = period;
+        }
+    }
+
+}
+
+