mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 10:58:42 +00:00
docs: Event-Based Asynchronous explanation + refactor (#2916)
* explation + refactor * update logging
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Event-Based Asynchronous
|
||||
category: Concurrency
|
||||
language: en
|
||||
tag:
|
||||
- Asynchronous
|
||||
- Decoupling
|
||||
- Event-driven
|
||||
- Fault tolerance
|
||||
- Messaging
|
||||
- Reactive
|
||||
- Scalability
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
* Asynchronous Event Handling
|
||||
|
||||
## Intent
|
||||
|
||||
The Event-Based Asynchronous pattern allows a system to handle tasks that might take some time to complete without blocking the execution of the program. It enables better resource utilization by freeing up a thread that would otherwise be blocked waiting for the task to complete.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real-world example
|
||||
|
||||
> A real-world analogy of the Event-Based Asynchronous design pattern is how a restaurant operates. When a customer places an order, the waiter records the order and passes it to the kitchen. Instead of waiting at the kitchen for the food to be prepared, the waiter continues to serve other tables. Once the kitchen completes the order, they signal (event) the waiter, who then delivers the food to the customer. This allows the waiter to handle multiple tasks efficiently without idle waiting, similar to how asynchronous programming handles tasks in parallel, enhancing overall efficiency and responsiveness.
|
||||
|
||||
In Plain Words
|
||||
|
||||
> The Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations.
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
The Event-Based Asynchronous design pattern in this project is implemented using several key classes:
|
||||
|
||||
* App: This is the main class that runs the application. It interacts with the EventManager to create, start, stop, and check the status of events. It can run in either interactive mode or non-interactive mode.
|
||||
* EventManager: This class is the core of the Event-Based Asynchronous pattern implementation. It manages the lifecycle of events, including creating, starting, stopping, and checking the status of events. It maintains a map of event IDs to Event objects.
|
||||
* Event: This is an abstract class that represents an event. It has two concrete subclasses: AsyncEvent and SyncEvent. An Event has an ID, a runtime (how long it should run), and a status (whether it's running, completed, or ready to start). It also has methods to start and stop the event.
|
||||
* AsyncEvent: This is a subclass of Event that represents an asynchronous event. When an AsyncEvent is started, it runs in a separate thread without blocking the main thread.
|
||||
* SyncEvent: This is a subclass of Event that represents a synchronous event. When a SyncEvent is started, it runs on the main thread and blocks it until the event is completed.
|
||||
* MaxNumOfEventsAllowedException, LongRunningEventException, EventDoesNotExistException, InvalidOperationException: These are custom exceptions that are thrown by the EventManager when certain conditions are not met. For example, MaxNumOfEventsAllowedException is thrown when the maximum number of allowed events is exceeded.
|
||||
|
||||
Here's a simplified code example of how these classes interact:
|
||||
|
||||
```java
|
||||
// Create an EventManager
|
||||
EventManager eventManager=new EventManager();
|
||||
|
||||
// Create an asynchronous event that runs for 60 seconds
|
||||
int asyncEventId=eventManager.createAsync(60);
|
||||
|
||||
// Start the asynchronous event
|
||||
eventManager.start(asyncEventId);
|
||||
|
||||
// Check the status of the asynchronous event
|
||||
eventManager.status(asyncEventId);
|
||||
|
||||
// Stop the asynchronous event
|
||||
eventManager.cancel(asyncEventId);
|
||||
```
|
||||
|
||||
In this example, the App class creates an EventManager, then uses it to create, start, check the status of, and stop an asynchronous event. The EventManager creates an AsyncEvent object, starts it in a separate thread, checks its status, and stops it when requested.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
* When multiple tasks can be processed in parallel and independently.
|
||||
* Systems that require responsiveness and cannot afford to have threads blocked waiting for an operation to complete.
|
||||
* In GUI applications where user interface responsiveness is critical.
|
||||
* Distributed systems where long network operations are involved.
|
||||
|
||||
## Known Uses
|
||||
|
||||
* GUI libraries in Java (e.g., JavaFX, Swing with SwingWorker).
|
||||
* Java Message Service (JMS) for handling asynchronous messaging.
|
||||
* Java’s CompletableFuture and various Event-Driven Frameworks.
|
||||
|
||||
## Consequences
|
||||
|
||||
Benefits:
|
||||
|
||||
* Improves application scalability and responsiveness.
|
||||
* Reduces the resources wasted on threads that would simply wait for I/O operations.
|
||||
* Enhances fault tolerance through isolation of process execution.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
* Increases complexity of error handling as errors may occur in different threads or at different times.
|
||||
* Can lead to harder-to-follow code and debugging challenges due to the non-linear nature of asynchronous code execution.
|
||||
|
||||
Related Patterns
|
||||
|
||||
* [Observer](https://java-design-patterns.com/patterns/observer/): Often used in conjunction where the observer reacts to events as they occur.
|
||||
* Publish/Subscribe: Related in terms of event handling mechanisms, particularly for messaging and event distribution across components.
|
||||
* [Command](https://java-design-patterns.com/patterns/command/): Useful for encapsulating all information needed to perform an action or trigger an event.
|
||||
|
||||
## Credits
|
||||
|
||||
* [Event-based Asynchronous Pattern Overview](https://msdn.microsoft.com/en-us/library/wewwczdw%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396)
|
||||
* [Java Concurrency in Practice](https://amzn.to/4cYY4kU)
|
||||
* [Patterns of Enterprise Application Architecture](https://amzn.to/3Uh7rW1)
|
||||
* [Pro JavaFX 8: A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients](https://amzn.to/3vHUqLL)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.2.2" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true" router="FAN">
|
||||
<interface id="1" language="java" name="com.iluwatar.event.asynchronous.IEvent" project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/IEvent.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="117" width="151" x="42" y="15"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<interface id="2" language="java" name="com.iluwatar.event.asynchronous.ThreadCompleteListener"
|
||||
project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="326" y="79"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<class id="3" language="java" name="com.iluwatar.event.asynchronous.App" project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="554" y="119"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="4" language="java" name="com.iluwatar.event.asynchronous.EventAsynchronousTest"
|
||||
project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="207" width="234" x="669" y="16"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="5" language="java" name="com.iluwatar.event.asynchronous.Event" project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="141" y="441"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="6" language="java" name="com.iluwatar.event.asynchronous.EventManager" project="java-design-patterns"
|
||||
file="/java-design-patterns/event-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java"
|
||||
binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="431" y="439"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<association id="7">
|
||||
<end type="SOURCE" refId="5" navigable="false">
|
||||
<attribute id="8" name="eventListener"/>
|
||||
<multiplicity id="9" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="2" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<realization id="10">
|
||||
<end type="SOURCE" refId="5"/>
|
||||
<end type="TARGET" refId="1"/>
|
||||
</realization>
|
||||
<association id="11">
|
||||
<end type="SOURCE" refId="4" navigable="false">
|
||||
<attribute id="12" name="app"/>
|
||||
<multiplicity id="13" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="3" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<association id="14">
|
||||
<end type="SOURCE" refId="6" navigable="false">
|
||||
<attribute id="15" name="eventPool"/>
|
||||
<multiplicity id="16" minimum="0" maximum="2147483647"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="5" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<realization id="17">
|
||||
<end type="SOURCE" refId="6"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</realization>
|
||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</classifier-display>
|
||||
<association-display labels="true" multiplicity="true"/>
|
||||
</class-diagram>
|
||||
@@ -0,0 +1,70 @@
|
||||
@startuml
|
||||
package com.iluwatar.event.asynchronous {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
+ PROP_FILE_NAME : String {static}
|
||||
~ interactiveMode : boolean
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
- processOption1(eventManager : EventManager, s : Scanner)
|
||||
- processOption2(eventManager : EventManager, s : Scanner)
|
||||
- processOption3(eventManager : EventManager, s : Scanner)
|
||||
+ quickRun()
|
||||
+ run()
|
||||
+ runInteractiveMode()
|
||||
+ setUp()
|
||||
}
|
||||
class Event {
|
||||
- LOGGER : Logger {static}
|
||||
- eventId : int
|
||||
- eventListener : ThreadCompleteListener
|
||||
- eventTime : int
|
||||
- isComplete : boolean
|
||||
- isSynchronous : boolean
|
||||
- thread : Thread
|
||||
+ Event(eventId : int, eventTime : int, isSynchronous : boolean)
|
||||
+ addListener(listener : ThreadCompleteListener)
|
||||
- completed()
|
||||
+ isSynchronous() : boolean
|
||||
+ removeListener(listener : ThreadCompleteListener)
|
||||
+ run()
|
||||
+ start()
|
||||
+ status()
|
||||
+ stop()
|
||||
}
|
||||
class EventManager {
|
||||
- DOES_NOT_EXIST : String {static}
|
||||
+ MAX_EVENT_TIME : int {static}
|
||||
+ MAX_ID : int {static}
|
||||
+ MAX_RUNNING_EVENTS : int {static}
|
||||
+ MIN_ID : int {static}
|
||||
- currentlyRunningSyncEvent : int
|
||||
- eventPool : Map<Integer, Event>
|
||||
- rand : Random
|
||||
+ EventManager()
|
||||
+ cancel(eventId : int)
|
||||
+ completedEventHandler(eventId : int)
|
||||
+ create(eventTime : int) : int
|
||||
+ createAsync(eventTime : int) : int
|
||||
- createEvent(eventTime : int, isSynchronous : boolean) : int
|
||||
- generateId() : int
|
||||
+ getEventPool() : Map<Integer, Event>
|
||||
+ numOfCurrentlyRunningSyncEvent() : int
|
||||
+ shutdown()
|
||||
+ start(eventId : int)
|
||||
+ status(eventId : int)
|
||||
+ statusOfAllEvents()
|
||||
}
|
||||
interface IEvent {
|
||||
+ start() {abstract}
|
||||
+ status() {abstract}
|
||||
+ stop() {abstract}
|
||||
}
|
||||
interface ThreadCompleteListener {
|
||||
+ completedEventHandler(int) {abstract}
|
||||
}
|
||||
}
|
||||
Event --> "-eventListener" ThreadCompleteListener
|
||||
Event ..|> IEvent
|
||||
EventManager ..|> ThreadCompleteListener
|
||||
@enduml
|
||||
@@ -0,0 +1,70 @@
|
||||
@startuml
|
||||
package com.iluwatar.event.asynchronous {
|
||||
class App {
|
||||
- LOGGER : Logger {static}
|
||||
+ PROP_FILE_NAME : String {static}
|
||||
~ interactiveMode : boolean
|
||||
+ App()
|
||||
+ main(args : String[]) {static}
|
||||
- processOption1(eventManager : EventManager, s : Scanner)
|
||||
- processOption2(eventManager : EventManager, s : Scanner)
|
||||
- processOption3(eventManager : EventManager, s : Scanner)
|
||||
+ quickRun()
|
||||
+ run()
|
||||
+ runInteractiveMode()
|
||||
+ setUp()
|
||||
}
|
||||
class AsyncEvent {
|
||||
- LOGGER : Logger {static}
|
||||
- eventId : int
|
||||
- eventListener : ThreadCompleteListener
|
||||
- eventTime : int
|
||||
- isComplete : AtomicBoolean
|
||||
- synchronous : boolean
|
||||
- thread : Thread
|
||||
+ AsyncEvent(eventId : int, eventTime : int, synchronous : boolean)
|
||||
+ addListener(listener : ThreadCompleteListener)
|
||||
- completed()
|
||||
+ isSynchronous() : boolean
|
||||
+ removeListener()
|
||||
+ run()
|
||||
+ start()
|
||||
+ status()
|
||||
+ stop()
|
||||
}
|
||||
interface Event {
|
||||
+ start() {abstract}
|
||||
+ status() {abstract}
|
||||
+ stop() {abstract}
|
||||
}
|
||||
class EventManager {
|
||||
- DOES_NOT_EXIST : String {static}
|
||||
+ MAX_EVENT_TIME : int {static}
|
||||
+ MAX_ID : int {static}
|
||||
+ MAX_RUNNING_EVENTS : int {static}
|
||||
+ MIN_ID : int {static}
|
||||
- currentlyRunningSyncEvent : int
|
||||
- eventPool : Map<Integer, AsyncEvent>
|
||||
- rand : SecureRandom
|
||||
+ EventManager()
|
||||
+ cancel(eventId : int)
|
||||
+ completedEventHandler(eventId : int)
|
||||
+ create(eventTime : int) : int
|
||||
+ createAsync(eventTime : int) : int
|
||||
- createEvent(eventTime : int, isSynchronous : boolean) : int
|
||||
- generateId() : int
|
||||
+ getEventPool() : Map<Integer, AsyncEvent>
|
||||
+ numOfCurrentlyRunningSyncEvent() : int
|
||||
+ shutdown()
|
||||
+ start(eventId : int)
|
||||
+ status(eventId : int)
|
||||
+ statusOfAllEvents()
|
||||
}
|
||||
interface ThreadCompleteListener {
|
||||
+ completedEventHandler(int) {abstract}
|
||||
}
|
||||
}
|
||||
AsyncEvent --> "-eventListener" ThreadCompleteListener
|
||||
AsyncEvent ..|> Event
|
||||
EventManager ..|> ThreadCompleteListener
|
||||
@enduml
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
|
||||
The MIT License
|
||||
Copyright © 2014-2022 Ilkka Seppälä
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.26.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>event-based-asynchronous</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>com.iluwatar.event.asynchronous.App</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.Scanner;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* This application demonstrates the <b>Event-based Asynchronous</b> pattern. Essentially, users (of
|
||||
* the pattern) may choose to run events in an Asynchronous or Synchronous mode. There can be
|
||||
* multiple Asynchronous events running at once but only one Synchronous event can run at a time.
|
||||
* Asynchronous events are synonymous to multi-threads. The key point here is that the threads run
|
||||
* in the background and the user is free to carry on with other processes. Once an event is
|
||||
* complete, the appropriate listener/callback method will be called. The listener then proceeds to
|
||||
* carry out further processing depending on the needs of the user.
|
||||
*
|
||||
* <p>The {@link EventManager} manages the events/threads that the user creates. Currently, the
|
||||
* supported event operations are: <code>start</code>, <code>stop</code>, <code>getStatus</code>.
|
||||
* For Synchronous events, the user is unable to start another (Synchronous) event if one is already
|
||||
* running at the time. The running event would have to either be stopped or completed before a new
|
||||
* event can be started.
|
||||
*
|
||||
* <p>The Event-based Asynchronous Pattern makes available the advantages of multithreaded
|
||||
* applications while hiding many of the complex issues inherent in multithreaded design. Using a
|
||||
* class that supports this pattern can allow you to:- (1) Perform time-consuming tasks, such as
|
||||
* downloads and database operations, "in the background," without interrupting your application.
|
||||
* (2) Execute multiple operations simultaneously, receiving notifications when each completes. (3)
|
||||
* Wait for resources to become available without stopping ("hanging") your application. (4)
|
||||
* Communicate with pending asynchronous operations using the familiar events-and-delegates model.
|
||||
*
|
||||
* @see EventManager
|
||||
* @see AsyncEvent
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
|
||||
public static final String PROP_FILE_NAME = "config.properties";
|
||||
|
||||
boolean interactiveMode = false;
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var app = new App();
|
||||
app.setUp();
|
||||
app.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* App can run in interactive mode or not. Interactive mode == Allow user interaction with command
|
||||
* line. Non-interactive is a quick sequential run through the available {@link EventManager}
|
||||
* operations.
|
||||
*/
|
||||
public void setUp() {
|
||||
var prop = new Properties();
|
||||
|
||||
var inputStream = App.class.getClassLoader().getResourceAsStream(PROP_FILE_NAME);
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
prop.load(inputStream);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("{} was not found. Defaulting to non-interactive mode.", PROP_FILE_NAME, e);
|
||||
}
|
||||
var property = prop.getProperty("INTERACTIVE_MODE");
|
||||
if (property.equalsIgnoreCase("YES")) {
|
||||
interactiveMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run program in either interactive mode or not.
|
||||
*/
|
||||
public void run() {
|
||||
if (interactiveMode) {
|
||||
runInteractiveMode();
|
||||
} else {
|
||||
quickRun();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run program in non-interactive mode.
|
||||
*/
|
||||
public void quickRun() {
|
||||
var eventManager = new EventManager();
|
||||
|
||||
try {
|
||||
// Create an Asynchronous event.
|
||||
var asyncEventId = eventManager.createAsync(60);
|
||||
LOGGER.info("Async Event [{}] has been created.", asyncEventId);
|
||||
eventManager.start(asyncEventId);
|
||||
LOGGER.info("Async Event [{}] has been started.", asyncEventId);
|
||||
|
||||
// Create a Synchronous event.
|
||||
var syncEventId = eventManager.create(60);
|
||||
LOGGER.info("Sync Event [{}] has been created.", syncEventId);
|
||||
eventManager.start(syncEventId);
|
||||
LOGGER.info("Sync Event [{}] has been started.", syncEventId);
|
||||
|
||||
eventManager.status(asyncEventId);
|
||||
eventManager.status(syncEventId);
|
||||
|
||||
eventManager.cancel(asyncEventId);
|
||||
LOGGER.info("Async Event [{}] has been stopped.", asyncEventId);
|
||||
eventManager.cancel(syncEventId);
|
||||
LOGGER.info("Sync Event [{}] has been stopped.", syncEventId);
|
||||
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException
|
||||
| InvalidOperationException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run program in interactive mode.
|
||||
*/
|
||||
public void runInteractiveMode() {
|
||||
var eventManager = new EventManager();
|
||||
|
||||
var s = new Scanner(System.in);
|
||||
var option = -1;
|
||||
while (option != 4) {
|
||||
LOGGER.info("Hello. Would you like to boil some eggs?");
|
||||
LOGGER.info("""
|
||||
(1) BOIL AN EGG
|
||||
(2) STOP BOILING THIS EGG
|
||||
(3) HOW ARE MY EGGS?
|
||||
(4) EXIT
|
||||
""");
|
||||
LOGGER.info("Choose [1,2,3,4]: ");
|
||||
option = s.nextInt();
|
||||
|
||||
if (option == 1) {
|
||||
processOption1(eventManager, s);
|
||||
} else if (option == 2) {
|
||||
processOption2(eventManager, s);
|
||||
} else if (option == 3) {
|
||||
processOption3(eventManager, s);
|
||||
} else if (option == 4) {
|
||||
eventManager.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
s.close();
|
||||
}
|
||||
|
||||
private void processOption3(EventManager eventManager, Scanner s) {
|
||||
s.nextLine();
|
||||
LOGGER.info("Just one egg (O) OR all of them (A) ?: ");
|
||||
var eggChoice = s.nextLine();
|
||||
|
||||
if (eggChoice.equalsIgnoreCase("O")) {
|
||||
LOGGER.info("Which egg?: ");
|
||||
int eventId = s.nextInt();
|
||||
try {
|
||||
eventManager.status(eventId);
|
||||
} catch (EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
} else if (eggChoice.equalsIgnoreCase("A")) {
|
||||
eventManager.statusOfAllEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void processOption2(EventManager eventManager, Scanner s) {
|
||||
LOGGER.info("Which egg?: ");
|
||||
var eventId = s.nextInt();
|
||||
try {
|
||||
eventManager.cancel(eventId);
|
||||
LOGGER.info("Egg [{}] is removed from boiler.", eventId);
|
||||
} catch (EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processOption1(EventManager eventManager, Scanner s) {
|
||||
s.nextLine();
|
||||
LOGGER.info("Boil multiple eggs at once (A) or boil them one-by-one (S)?: ");
|
||||
var eventType = s.nextLine();
|
||||
LOGGER.info("How long should this egg be boiled for (in seconds)?: ");
|
||||
var eventTime = s.nextInt();
|
||||
if (eventType.equalsIgnoreCase("A")) {
|
||||
try {
|
||||
var eventId = eventManager.createAsync(eventTime);
|
||||
eventManager.start(eventId);
|
||||
LOGGER.info("Egg [{}] is being boiled.", eventId);
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException
|
||||
| EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
} else if (eventType.equalsIgnoreCase("S")) {
|
||||
try {
|
||||
var eventId = eventManager.create(eventTime);
|
||||
eventManager.start(eventId);
|
||||
LOGGER.info("Egg [{}] is being boiled.", eventId);
|
||||
} catch (MaxNumOfEventsAllowedException | InvalidOperationException
|
||||
| LongRunningEventException | EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Unknown event type.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Each Event runs as a separate/individual thread.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AsyncEvent implements Event, Runnable {
|
||||
|
||||
private final int eventId;
|
||||
private final int eventTime;
|
||||
@Getter
|
||||
private final boolean synchronous;
|
||||
private Thread thread;
|
||||
private AtomicBoolean isComplete = new AtomicBoolean(false);
|
||||
private ThreadCompleteListener eventListener;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
thread = new Thread(this);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (null == thread) {
|
||||
return;
|
||||
}
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void status() {
|
||||
if (isComplete.get()) {
|
||||
LOGGER.info("[{}] is not done.", eventId);
|
||||
} else {
|
||||
LOGGER.info("[{}] is done.", eventId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
var currentTime = System.currentTimeMillis();
|
||||
var endTime = currentTime + (eventTime * 1000L);
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread was interrupted: ", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
isComplete.set(true);
|
||||
completed();
|
||||
}
|
||||
|
||||
public final void addListener(final ThreadCompleteListener listener) {
|
||||
this.eventListener = listener;
|
||||
}
|
||||
|
||||
public final void removeListener() {
|
||||
this.eventListener = null;
|
||||
}
|
||||
|
||||
private void completed() {
|
||||
if (eventListener != null) {
|
||||
eventListener.completedEventHandler(eventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Events that fulfill the start stop and list out current status behaviour follow this interface.
|
||||
*/
|
||||
public interface Event {
|
||||
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
|
||||
void status();
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Custom Exception Class for Non-Existent Event.
|
||||
*/
|
||||
public class EventDoesNotExistException extends Exception {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -3398463738273811509L;
|
||||
|
||||
public EventDoesNotExistException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are created
|
||||
* upon user request. Thre are two types of events; Asynchronous and Synchronous. There can be
|
||||
* multiple Asynchronous events running at once but only one Synchronous event running at a time.
|
||||
* Currently supported event operations are: start, stop, and getStatus. Once an event is complete,
|
||||
* it then notifies EventManager through a listener. The EventManager then takes the event out of
|
||||
* the pool.
|
||||
*/
|
||||
public class EventManager implements ThreadCompleteListener {
|
||||
|
||||
public static final int MAX_RUNNING_EVENTS = 1000;
|
||||
// Just don't want to have too many running events. :)
|
||||
public static final int MIN_ID = 1;
|
||||
public static final int MAX_ID = MAX_RUNNING_EVENTS;
|
||||
public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes.
|
||||
private int currentlyRunningSyncEvent = -1;
|
||||
private final SecureRandom rand;
|
||||
|
||||
@Getter
|
||||
private final Map<Integer, AsyncEvent> eventPool;
|
||||
|
||||
private static final String DOES_NOT_EXIST = " does not exist.";
|
||||
|
||||
/**
|
||||
* EventManager constructor.
|
||||
*/
|
||||
public EventManager() {
|
||||
rand = new SecureRandom();
|
||||
eventPool = new ConcurrentHashMap<>(MAX_RUNNING_EVENTS);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Synchronous event.
|
||||
*
|
||||
* @param eventTime Time an event should run for.
|
||||
* @return eventId
|
||||
* @throws MaxNumOfEventsAllowedException When too many events are running at a time.
|
||||
* @throws InvalidOperationException No new synchronous events can be created when one is
|
||||
* already running.
|
||||
* @throws LongRunningEventException Long-running events are not allowed in the app.
|
||||
*/
|
||||
public int create(int eventTime)
|
||||
throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException {
|
||||
if (currentlyRunningSyncEvent != -1) {
|
||||
throw new InvalidOperationException("Event [" + currentlyRunningSyncEvent + "] is still"
|
||||
+ " running. Please wait until it finishes and try again.");
|
||||
}
|
||||
|
||||
var eventId = createEvent(eventTime, true);
|
||||
currentlyRunningSyncEvent = eventId;
|
||||
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Asynchronous event.
|
||||
*
|
||||
* @param eventTime Time an event should run for.
|
||||
* @return eventId
|
||||
* @throws MaxNumOfEventsAllowedException When too many events are running at a time.
|
||||
* @throws LongRunningEventException Long-running events are not allowed in the app.
|
||||
*/
|
||||
public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException,
|
||||
LongRunningEventException {
|
||||
return createEvent(eventTime, false);
|
||||
}
|
||||
|
||||
private int createEvent(int eventTime, boolean isSynchronous)
|
||||
throws MaxNumOfEventsAllowedException, LongRunningEventException {
|
||||
if (eventPool.size() == MAX_RUNNING_EVENTS) {
|
||||
throw new MaxNumOfEventsAllowedException("Too many events are running at the moment."
|
||||
+ " Please try again later.");
|
||||
}
|
||||
|
||||
if (eventTime >= MAX_EVENT_TIME) {
|
||||
throw new LongRunningEventException(
|
||||
"Maximum event time allowed is " + MAX_EVENT_TIME + " seconds. Please try again.");
|
||||
}
|
||||
|
||||
var newEventId = generateId();
|
||||
|
||||
var newEvent = new AsyncEvent(newEventId, eventTime, isSynchronous);
|
||||
newEvent.addListener(this);
|
||||
eventPool.put(newEventId, newEvent);
|
||||
|
||||
return newEventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts event.
|
||||
*
|
||||
* @param eventId The event that needs to be started.
|
||||
* @throws EventDoesNotExistException If event does not exist in our eventPool.
|
||||
*/
|
||||
public void start(int eventId) throws EventDoesNotExistException {
|
||||
if (!eventPool.containsKey(eventId)) {
|
||||
throw new EventDoesNotExistException(eventId + DOES_NOT_EXIST);
|
||||
}
|
||||
|
||||
eventPool.get(eventId).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops event.
|
||||
*
|
||||
* @param eventId The event that needs to be stopped.
|
||||
* @throws EventDoesNotExistException If event does not exist in our eventPool.
|
||||
*/
|
||||
public void cancel(int eventId) throws EventDoesNotExistException {
|
||||
if (!eventPool.containsKey(eventId)) {
|
||||
throw new EventDoesNotExistException(eventId + DOES_NOT_EXIST);
|
||||
}
|
||||
|
||||
if (eventId == currentlyRunningSyncEvent) {
|
||||
currentlyRunningSyncEvent = -1;
|
||||
}
|
||||
|
||||
eventPool.get(eventId).stop();
|
||||
eventPool.remove(eventId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of a running event.
|
||||
*
|
||||
* @param eventId The event to inquire status of.
|
||||
* @throws EventDoesNotExistException If event does not exist in our eventPool.
|
||||
*/
|
||||
public void status(int eventId) throws EventDoesNotExistException {
|
||||
if (!eventPool.containsKey(eventId)) {
|
||||
throw new EventDoesNotExistException(eventId + DOES_NOT_EXIST);
|
||||
}
|
||||
|
||||
eventPool.get(eventId).status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets status of all running events.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void statusOfAllEvents() {
|
||||
eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all running events.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void shutdown() {
|
||||
eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).stop());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random number between min and max, inclusive. The difference between min and
|
||||
* max can be at most
|
||||
* <code>Integer.MAX_VALUE - 1</code>.
|
||||
*/
|
||||
private int generateId() {
|
||||
// nextInt is normally exclusive of the top value,
|
||||
// so add 1 to make it inclusive
|
||||
var randomNum = rand.nextInt((MAX_ID - MIN_ID) + 1) + MIN_ID;
|
||||
while (eventPool.containsKey(randomNum)) {
|
||||
randomNum = rand.nextInt((MAX_ID - MIN_ID) + 1) + MIN_ID;
|
||||
}
|
||||
|
||||
return randomNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from an {@link AsyncEvent} (once it is complete). The Event is then removed from the pool.
|
||||
*/
|
||||
@Override
|
||||
public void completedEventHandler(int eventId) {
|
||||
eventPool.get(eventId).status();
|
||||
if (eventPool.get(eventId).isSynchronous()) {
|
||||
currentlyRunningSyncEvent = -1;
|
||||
}
|
||||
eventPool.remove(eventId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of currently running Synchronous events.
|
||||
*/
|
||||
public int numOfCurrentlyRunningSyncEvent() {
|
||||
return currentlyRunningSyncEvent;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the Operation being invoked is Invalid.
|
||||
*/
|
||||
public class InvalidOperationException extends Exception {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -6191545255213410803L;
|
||||
|
||||
public InvalidOperationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the Operation being invoked is Long Running.
|
||||
*/
|
||||
public class LongRunningEventException extends Exception {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -483423544320148809L;
|
||||
|
||||
public LongRunningEventException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the max number of allowed events is exceeded.
|
||||
*/
|
||||
public class MaxNumOfEventsAllowedException extends Exception {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -8430876973516292695L;
|
||||
|
||||
public MaxNumOfEventsAllowedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Interface with listener behaviour related to Thread Completion.
|
||||
*/
|
||||
public interface ThreadCompleteListener {
|
||||
void completedEventHandler(final int eventId);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# The MIT License
|
||||
# Copyright © 2014-2021 Ilkka Seppälä
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
INTERACTIVE_MODE=NO
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
/**
|
||||
* Tests that EventAsynchronous example runs without errors.
|
||||
*/
|
||||
class AppTest {
|
||||
|
||||
/**
|
||||
* Issue: Add at least one assertion to this test case.
|
||||
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
|
||||
* throws an exception.
|
||||
*/
|
||||
|
||||
@Test
|
||||
void shouldExecuteApplicationWithoutException() {
|
||||
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||
*
|
||||
* The MIT License
|
||||
* Copyright © 2014-2022 Ilkka Seppälä
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
class EventAsynchronousTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EventAsynchronousTest.class);
|
||||
|
||||
@Test
|
||||
void testAsynchronousEvent() {
|
||||
var eventManager = new EventManager();
|
||||
try {
|
||||
var aEventId = eventManager.createAsync(60);
|
||||
eventManager.start(aEventId);
|
||||
assertEquals(1, eventManager.getEventPool().size());
|
||||
assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS);
|
||||
assertEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent());
|
||||
eventManager.cancel(aEventId);
|
||||
assertTrue(eventManager.getEventPool().isEmpty());
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSynchronousEvent() {
|
||||
var eventManager = new EventManager();
|
||||
try {
|
||||
var sEventId = eventManager.create(60);
|
||||
eventManager.start(sEventId);
|
||||
assertEquals(1, eventManager.getEventPool().size());
|
||||
assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS);
|
||||
assertNotEquals(-1, eventManager.numOfCurrentlyRunningSyncEvent());
|
||||
eventManager.cancel(sEventId);
|
||||
assertTrue(eventManager.getEventPool().isEmpty());
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException
|
||||
| InvalidOperationException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnsuccessfulSynchronousEvent() {
|
||||
assertThrows(InvalidOperationException.class, () -> {
|
||||
var eventManager = new EventManager();
|
||||
try {
|
||||
var sEventId = eventManager.create(60);
|
||||
eventManager.start(sEventId);
|
||||
sEventId = eventManager.create(60);
|
||||
eventManager.start(sEventId);
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullSynchronousEvent() {
|
||||
var eventManager = new EventManager();
|
||||
try {
|
||||
var eventTime = 1;
|
||||
|
||||
var sEventId = eventManager.create(eventTime);
|
||||
assertEquals(1, eventManager.getEventPool().size());
|
||||
eventManager.start(sEventId);
|
||||
|
||||
var currentTime = System.currentTimeMillis();
|
||||
// +2 to give a bit of buffer time for event to complete properly.
|
||||
var endTime = currentTime + (eventTime + 2 * 1000);
|
||||
|
||||
long sleepTime = endTime - System.currentTimeMillis();
|
||||
if (sleepTime > 0) {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: ", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(eventManager.getEventPool().isEmpty());
|
||||
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException
|
||||
| InvalidOperationException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullAsynchronousEvent() {
|
||||
var eventManager = new EventManager();
|
||||
try {
|
||||
var eventTime = 1;
|
||||
|
||||
var aEventId1 = eventManager.createAsync(eventTime);
|
||||
var aEventId2 = eventManager.createAsync(eventTime);
|
||||
var aEventId3 = eventManager.createAsync(eventTime);
|
||||
assertEquals(3, eventManager.getEventPool().size());
|
||||
|
||||
eventManager.start(aEventId1);
|
||||
eventManager.start(aEventId2);
|
||||
eventManager.start(aEventId3);
|
||||
|
||||
var currentTime = System.currentTimeMillis();
|
||||
// +2 to give a bit of buffer time for event to complete properly.
|
||||
var endTime = currentTime + (eventTime + 2 * 1000);
|
||||
|
||||
long sleepTime = endTime - System.currentTimeMillis();
|
||||
if (sleepTime > 0) {
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: ", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(eventManager.getEventPool().isEmpty());
|
||||
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLongRunningEventException(){
|
||||
assertThrows(LongRunningEventException.class, () -> {
|
||||
var eventManager = new EventManager();
|
||||
eventManager.createAsync(2000);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMaxNumOfEventsAllowedException(){
|
||||
assertThrows(MaxNumOfEventsAllowedException.class, () -> {
|
||||
final var eventManager = new EventManager();
|
||||
for(int i=0;i<1100;i++){
|
||||
eventManager.createAsync(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user