diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a87e394
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,47 @@
+# Project
+.settings/
+*.iml
+.idea/
+
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compilation folders
+obj/
+bin/
+
+#MacOS Files
+.DS_Store
+
+#CLion Files
+.idea/
+
+# Visual code
+.vscode
+
+# CMake
+*cmake-build-*/
+*CMakeLists.txt
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f199ea1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 William Niemiec
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b7a6702
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+![](https://github.com/wniemiec-task-java/scheduler/blob/master/docs/img/logo/logo.jpg)
+
+
Scheduler
+
Schedule routines to run after a certain time or whenever the the timer expires.
+
+
+
+
+
+
+
+
+
+
+
+## ❇ Introduction
+Scheduler allows you to perform operations with routines so that they are executed according to a criterion.
+
+## ❓ How to use
+1. Add one of the options below to the pom.xml file:
+
+#### Using Maven Central (recomended):
+```
+
+ io.github.wniemiec-task-java
+ scheduler
+ LATEST
+
+```
+
+#### Using GitHub Packages:
+```
+
+ wniemiec.task.java
+ scheduler
+ LATEST
+
+```
+
+2. Run
+```
+$ mvn install
+```
+
+3. Use it
+```
+[...]
+
+import wniemiec.task.java.Scheduler;
+
+[...]
+
+Scheduler.setTimeout(() -> { System.out.println("Hello..."); }, 1000);
+Scheduler.setTimeout(() -> { System.out.println("World!"); }, 1000);
+```
+
+## 📖 Documentation
+| Property |Parameter type|Return type|Description|Default parameter value|
+|----------------|-------------------------------|-----------------------------|--------|
+|setTimeout |`routine: Routine, delay: long`|`long`|Sets a timer which executes a routine once the timer expires| - |
+|setInterval |`routine: Routine, delay: long`|`long`|Repeatedly calls a routine with a fixed time delay between each call| - |
+|clearInterval |`id: long`|`void`|Cancels a timed, repeating action| - |
+|clearTimeout |`id: long`|`void`|Cancels a timed action| - |
+|clearAllTimeout | `void`|`void`|Clear all timeouts| - |
+|clearAllIntervals | `void`|`void`|Clear all intervals| - |
+|setTimeoutToRoutine|`routine: Routine, delay: long`|`long`|Runs a routine within a timeout. If the routine does not end on time, an interrupt signal will be sent to it| - |
+
+## 🚩 Changelog
+Details about each version are documented in the [releases section](https://github.com/williamniemiec/wniemiec-task-java/scheduler/releases).
+
+## 🤝 Contribute!
+See the documentation on how you can contribute to the project [here](https://github.com/wniemiec-task-java/scheduler/blob/master/CONTRIBUTING.md).
+
+## 📁 Files
+
+### /
+| Name |Type|Description|
+|----------------|-------------------------------|-----------------------------|
+|dist |`Directory`|Released versions|
+|docs |`Directory`|Documentation files|
+|src |`Directory`| Source files|
diff --git a/dist/1.x/scheduler-1.0.0.jar b/dist/1.x/scheduler-1.0.0.jar
new file mode 100644
index 0000000..56bd63e
Binary files /dev/null and b/dist/1.x/scheduler-1.0.0.jar differ
diff --git a/pom-mavencentral.xml b/pom-mavencentral.xml
new file mode 100644
index 0000000..0e2e02d
--- /dev/null
+++ b/pom-mavencentral.xml
@@ -0,0 +1,212 @@
+
+
+
+ 4.0.0
+
+ io.github.wniemiec-task-java
+ scheduler
+ 1.0.0
+
+ Checkpoint
+ Schedule routines to run after a certain time or whenever the the timer expires.
+ https://github.com/wniemiec-task-java/scheduler
+
+
+ 1
+ UTF-8
+ 11
+
+
+
+
+ MIT License
+ http://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ williamniemiec
+ William Niemiec
+ williamniemiec@hotmail.com
+
+
+
+
+ scm:git:git@github.com:wniemiec-task-java/scheduler.git
+ scm:git:ssh://github.com:wniemiec-task-java/scheduler.git
+ https://github.com/wniemiec-task-java/scheduler/tree/main
+
+
+
+
+ ossrh
+ https://s01.oss.sonatype.org/content/repositories/snapshots
+
+
+ ossrh
+ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2
+
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+ src/main/java
+
+ **/*.java
+ **/*.gwt.xml
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+
+ ${java.source-target.version}
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+ maven-jar-plugin
+ 3.0.2
+
+ ${project.build.directory}/../dist/${version.major}.x
+
+
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.2.0
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.6
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+ jacoco-site
+ package
+
+ report
+
+
+
+
+
+
+
+
+
+ ci-cd
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.6
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c9c035d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,209 @@
+
+
+
+ 4.0.0
+
+ wniemiec.task.java
+ scheduler
+ 1.0.0
+
+ Scheduler
+ Schedule routines to run after a certain time or whenever the the timer expires.
+ https://github.com/wniemiec-task-java/scheduler
+
+
+ 1
+ UTF-8
+ 11
+
+
+
+
+ MIT License
+ http://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ williamniemiec
+ William Niemiec
+ williamniemiec@hotmail.com
+
+
+
+
+ scm:git:git@github.com:wniemiec-task-java/scheduler.git
+ scm:git:ssh://github.com:wniemiec-task-java/scheduler.git
+ https://github.com/wniemiec-task-java/scheduler/tree/main
+
+
+
+
+ github-wniemiec-task-java
+ GitHub wniemiec-task-java Apache Maven Packages
+ https://maven.pkg.github.com/wniemiec-task-java/scheduler
+
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+ src/main/java
+
+ **/*.java
+ **/*.gwt.xml
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.0
+
+
+ ${java.source-target.version}
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+
+ maven-jar-plugin
+ 3.0.2
+
+ ${project.build.directory}/../dist/${version.major}.x
+
+
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.2.0
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.6
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+ jacoco-site
+ package
+
+ report
+
+
+
+
+
+
+
+
+
+ ci-cd
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.6
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/wniemiec/task/java/Scheduler.java b/src/main/java/wniemiec/task/java/Scheduler.java
new file mode 100644
index 0000000..f49c3a0
--- /dev/null
+++ b/src/main/java/wniemiec/task/java/Scheduler.java
@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) William Niemiec.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package wniemiec.task.java;
+
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Responsible for calling routines after a certain time or whenever the the
+ * timer expires.
+ */
+public class Scheduler {
+
+ //-------------------------------------------------------------------------
+ // Attributes
+ //-------------------------------------------------------------------------
+ /**
+ * Stores routines sent to setInterval method along with its ids.
+ */
+ private static final Map intervalRoutines;
+
+ /**
+ * Stores routines sent to setTimeout method along with its ids.
+ */
+ private static final Map delayRoutines;
+
+ private static final Map timeoutRoutines;
+ private static final Map timeoutRoutineThreads;
+ private static Long currentRoutineId;
+
+
+ //-------------------------------------------------------------------------
+ // Initialization blocks
+ //-------------------------------------------------------------------------
+ static {
+ intervalRoutines = new HashMap();
+ delayRoutines = new HashMap();
+ timeoutRoutines = new HashMap<>();
+ timeoutRoutineThreads = new HashMap<>();
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Constructor
+ //-------------------------------------------------------------------------
+ private Scheduler() {
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Methods
+ //-------------------------------------------------------------------------
+ /**
+ * Sets a timer which executes a routine once the timer expires.
+ *
+ * @param routine Routine to be performed
+ * @param delay Waiting time before the routine is executed (in
+ * milliseconds)
+ *
+ * @return Routine identifier (necessary to be able to stop it)
+ *
+ * @throws IllegalArgumentException If routine is null
+ */
+ public static long setTimeout(Runnable routine, long delay) {
+ if (routine == null)
+ throw new IllegalArgumentException("Routine cannot be null");
+
+ if (delay < 0)
+ throw new IllegalArgumentException("Delay cannot be negative");
+
+ initializeRoutineId();
+ scheduleTimeout(routine, delay);
+
+ return currentRoutineId;
+ }
+
+ private static void initializeRoutineId() {
+ currentRoutineId = getCurrentTime();
+ }
+
+ private static void scheduleTimeout(Runnable routine, long delay) {
+ if (currentRoutineId == null)
+ throw new IllegalStateException("Routine id was not initialized");
+
+ Timer timer = new Timer();
+ timer.schedule(createTaskFromRoutine(routine), delay);
+
+ delayRoutines.put(currentRoutineId, timer);
+ }
+
+ private static TimerTask createTaskFromRoutine(Runnable routine) {
+ return new TimerTask() {
+ @Override
+ public void run() {
+ routine.run();
+ }
+ };
+ }
+
+ /**
+ * Repeatedly calls a routine with a fixed time delay between each call.
+ *
+ * @param routine Routine to be performed
+ * @param interval Interval that the routine will be invoked (in
+ * milliseconds)
+ *
+ * @return Routine identifier (necessary to be able to stop it)
+ *
+ * @throws IllegalArgumentException If routine is null
+ */
+ public static long setInterval(Runnable routine, long interval) {
+ if (routine == null)
+ throw new IllegalArgumentException("Routine cannot be null");
+
+ if (interval < 0)
+ throw new IllegalArgumentException("Interval cannot be negative");
+
+ initializeRoutineId();
+ scheduleInterval(routine, interval);
+
+ return currentRoutineId;
+ }
+
+ private static void scheduleInterval(Runnable routine, long interval) {
+ if (currentRoutineId == null)
+ throw new IllegalStateException("Routine id was not initialized");
+
+ Timer timer = new Timer();
+ timer.scheduleAtFixedRate(createTaskFromRoutine(routine), 0, interval);
+
+ intervalRoutines.put(currentRoutineId, timer);
+ }
+
+ /**
+ * Cancels a timed, repeating action, which was previously established by a
+ * call to {@link #setInterval(Runnable, long)}.
+ *
+ * @param id Routine identifier
+ */
+ public static void clearInterval(long id) {
+ if (!intervalRoutines.containsKey(id))
+ return;
+
+ intervalRoutines.get(id).cancel();
+ intervalRoutines.remove(id);
+ }
+
+ /**
+ * Cancels a timed action which was previously established by a
+ * call to {@link #setTimeout(Runnable, long)}.
+ *
+ * @param id Routine identifier
+ */
+ public static void clearTimeout(long id) {
+ if (!delayRoutines.containsKey(id))
+ return;
+
+ delayRoutines.get(id).cancel();
+ delayRoutines.remove(id);
+ }
+
+ public static void clearAllTimeout() {
+ for (Long timeoutId : delayRoutines.keySet()) {
+ clearTimeout(timeoutId);
+ }
+ }
+
+ public static void clearAllInterval() {
+ for (Long intervalId : intervalRoutines.keySet()) {
+ clearInterval(intervalId);
+ }
+ }
+
+ /**
+ * Runs a routine within a timeout. If the routine does not end on time, an
+ * interrupt signal will be sent to it.
+ *
+ * @param routine Routine
+ * @param timeout Maximum execution time (in milliseconds)
+ *
+ * @return True if the routine has not finished executing within the
+ * time limit; false otherwise
+ *
+ * @throws IllegalArgumentException If routine is null or if timeout is
+ * is negative
+ */
+ public static boolean setTimeoutToRoutine(Runnable routine, long timeout) {
+ if (routine == null)
+ throw new IllegalArgumentException("Routine cannot be null");
+
+ if (timeout < 0)
+ throw new IllegalArgumentException("Timeout cannot be negative");
+
+ runRoutine(routine);
+ waitRoutineFor(timeout);
+ finishRoutine();
+
+ return !hasRoutineFinished();
+ }
+
+ private static long getCurrentTime() {
+ return new Date().getTime();
+ }
+
+ private static void runRoutine(Runnable routine) {
+ initializeRoutineId();
+ timeoutRoutines.put(currentRoutineId, false);
+
+ ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+ Future handler = executor.submit(() -> {
+ routine.run();
+
+ if (timeoutRoutines.containsKey(currentRoutineId)) {
+ timeoutRoutines.put(currentRoutineId, true);
+ timeoutRoutineThreads.remove(currentRoutineId);
+ }
+ });
+ executor.shutdown();
+
+ timeoutRoutineThreads.put(currentRoutineId, handler);
+ }
+
+ private static void waitRoutineFor(long time) {
+ if (currentRoutineId < 0)
+ return;
+
+ long start = getCurrentTime();
+
+ while ((timeElapsedInMilliseconds(start) < time) && !hasRoutineFinished()) {
+ try {
+ Thread.sleep(200);
+ }
+ catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+
+ private static long timeElapsedInMilliseconds(long start) {
+ return (getCurrentTime() - start);
+ }
+
+ private static boolean hasRoutineFinished() {
+ if (currentRoutineId < 0)
+ return true;
+
+ if (!timeoutRoutines.containsKey(currentRoutineId))
+ return false;
+
+ return timeoutRoutines.get(currentRoutineId);
+ }
+
+ private static void finishRoutine() {
+ if (hasRoutineFinished())
+ return;
+
+ timeoutRoutines.remove(currentRoutineId);
+ timeoutRoutineThreads.get(currentRoutineId).cancel(true);
+ timeoutRoutineThreads.remove(currentRoutineId);
+ }
+}
diff --git a/src/test/java/wniemiec/task/java/SchedulerTest.java b/src/test/java/wniemiec/task/java/SchedulerTest.java
new file mode 100644
index 0000000..213bb61
--- /dev/null
+++ b/src/test/java/wniemiec/task/java/SchedulerTest.java
@@ -0,0 +1,158 @@
+package wniemiec.task.java;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class SchedulerTest {
+
+ //-------------------------------------------------------------------------
+ // Attributes
+ //-------------------------------------------------------------------------
+ private static final long DELAY_TIME;
+ private static final long INTERVAL_TIME;
+ private static final long MAX_WAIT_TIME;
+ private volatile boolean insideRoutine;
+ private int totInsideRoutine;
+
+
+ //-------------------------------------------------------------------------
+ // Initialization blocks
+ //-------------------------------------------------------------------------
+ static {
+ DELAY_TIME = 1000L;
+ INTERVAL_TIME = 100L;
+ MAX_WAIT_TIME = 1000L;
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Test hooks
+ //-------------------------------------------------------------------------
+ @Before
+ public void beforeEachTest() {
+ insideRoutine = false;
+ totInsideRoutine = 0;
+ Scheduler.clearAllTimeout();
+ Scheduler.clearAllInterval();
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Tests
+ //-------------------------------------------------------------------------
+ @Test
+ public void testTimeout() throws InterruptedException {
+ Scheduler.setTimeout(() -> {
+ insideRoutine = true;
+ }, DELAY_TIME);
+
+ waitForTimeout();
+
+ assertTrue(insideRoutine);
+ }
+
+ @Test
+ public void testInterval() throws InterruptedException {
+ totInsideRoutine = 0;
+
+ long scheduleId = Scheduler.setInterval(() -> {
+ if (totInsideRoutine == 3)
+ return;
+
+ totInsideRoutine++;
+ }, INTERVAL_TIME);
+
+ waitForInterval(3);
+
+ if (totInsideRoutine == 3)
+ Scheduler.clearInterval(scheduleId);
+
+ assertTrue(totInsideRoutine == 3);
+ }
+
+ @Test
+ public void testClearTimeout() throws InterruptedException {
+ long scheduleId = Scheduler.setTimeout(() -> {
+ insideRoutine = true;
+ }, DELAY_TIME);
+
+ Scheduler.clearTimeout(scheduleId);
+
+ waitForTimeout();
+
+ assertFalse(insideRoutine);
+ }
+
+ @Test
+ public void testClearInterval() throws InterruptedException {
+ totInsideRoutine = 0;
+
+ long scheduleId = Scheduler.setInterval(() -> {
+ totInsideRoutine++;
+ }, INTERVAL_TIME);
+
+ Scheduler.clearInterval(scheduleId);
+
+ waitForInterval(2);
+
+ assertTrue(totInsideRoutine == 0);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTimeoutWithNullRoutine() {
+ Scheduler.setTimeout(null, DELAY_TIME);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIntervalWithNullRoutine() {
+ Scheduler.setInterval(null, INTERVAL_TIME);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTimeoutWithNegativeDelay() {
+ Scheduler.setTimeout(() -> {}, -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testIntervalWithNegativeDelay() {
+ Scheduler.setInterval(() -> {}, -1);
+ }
+
+ @Test
+ public void testSetTimeoutToRoutine() {
+ boolean timeout = Scheduler.setTimeoutToRoutine(() -> {
+ while (true)
+ ;
+ }, MAX_WAIT_TIME);
+
+ assertTrue(timeout);
+ }
+
+ @Test
+ public void testSetTimeoutToRoutine2() {
+ boolean timeout = Scheduler.setTimeoutToRoutine(() -> {
+ int x = 1;
+
+ while (x >= 0)
+ x--;
+ }, 99000);
+
+ assertFalse(timeout);
+ }
+
+
+
+ //-------------------------------------------------------------------------
+ // Methods
+ //-------------------------------------------------------------------------\
+ private void waitForInterval(int totalExecutions) throws InterruptedException {
+ Thread.sleep((INTERVAL_TIME * totalExecutions) + 100);
+ }
+
+ private void waitForTimeout() throws InterruptedException {
+ Thread.sleep(DELAY_TIME + 100);
+ }
+}