Use Case: Collect lower level Motion events, correlate them to remove duplicates (e.g., a person walked around a house passes in front of one camera then another), then report the correlated Detection event.
Approach: (see pic) I initiate Motion Events from the video analytics and other sensors which are received and correlated by the AwarenessAnalytics component, which then raises a Detection Event to the Home Automation Main. It is similar to Chain-of-Responsibility pattern, though in reverse with events.
I have defined two completely separate event interfaces in separate files in the same package;
public interface MotionEventListener {
public void motionDetected(MotionEvent event);
public void motionLocation (MotionLocation location);
public void motionCeased(MotionEvent event);
public void eventVideoComplete(String eventId);
}
public interface DetectionEventListener {
public void motionIsDetected(DetectionEvent event);
public void motionAtLocation (MotionLocation location);
public void motionHasCeased(DetectionEvent event);
public void eventVideoNowComplete(String eventId);
}
I create the Motion Events in the VideoAnalytic thread;
private synchronized void fireDetectedEvent() {
Object source = new Object();
alertStartTime = getDateTime();
eventId++;
System.out.println("*** Motion Detected! ***" + alertStartTime + ", eventId = " +
eventId);
// Send alert to listener
String details ="";
MotionEvent event = new MotionEvent(source, alertActive, eventId,
Calendar.getInstance(), cameraId, Classification.unknown, details, alertStartTime);
Iterator i = listeners.iterator();
if (alertActive) {
while(i.hasNext()) {
((MotionEventListener) i.next()).motionDetected(event);
}
} else {
while(i.hasNext()) {
((MotionEventListener) i.next()).motionCeased(event);
}
resetVideoStreamEventCounter = 0;// keeps track of how many video resets occur from one
//event to another
}
}
The Motion events are successfully caught in the AwarenessAnalytic layer, where I createa new Detection Event if there is not already an ongoing event;
public void motionDetected(MotionEvent e) {
System.out.println("Motion Detected Listener activated " + e.getCameraId());
if (alertCounter == 0) {
Object source = new Object();
System.out.println("*** Motion Detected! ***" );
// Send alert to listener
alertCounter++;
alertId++;
alertActive = true;
DetectionEvent event = new DetectionEvent(
source,
alertActive,
alertId,
e.getEventDateTime(),
e.getCameraId(),
e.getEventType(),
e.getKnownDetails(),
e.getEventSubdirectory());
Iterator i = listeners.iterator();
if (alertActive) {
while(i.hasNext()) {
((DetectionEventListener) i.next()).motionDetected(event);
}
} else {
alertCounter++;
}
}
System.out.println("Motion Detected event received by AA from " + e.getCameraId());
}
Design pictorial:
Problem:
I've tried to catch the events in Home Automation Main as follows;
AwarenessAnalytics awarenessAnalytic = new AwarenessAnalytics();
// establish the listener set
awarenessAnalytic.addListener(this);
However, this results in a compilation error "Cannot use this in a static context"
Do I need a separate listener class for this? Or something else?
#Kon provided the clues needed to solve this problem (he is the one who deserves the credit). I created a separated DetectionListener class that implemented DetectionEventListener;
public class DetectionListener implements DetectionEventListener {
public DetectionListener() {
super();
}
public void motionIsDetected(DetectionEvent e) {
System.out.println("Motion Detected Awareness Listener test driver activated " +
e.getCameraId());
}
public void motionAtLocation (MotionLocation e) {
System.out.println("Test driver Motion location = " + e.getX() + ", " + e.getY());
}
public void motionHasCeased(DetectionEvent e) {
System.out.println("Motion Ceased Listener test driver activated from " +
e.getCameraId());
}
public void eventVideoNowComplete (String eventId) {
System.out.println("Event Video test driver activated");
}
}
And then in the Home Automation Main set up the AwarenessAnalytics instance, the DetectionListener instance, and add it to the AwarenessAnalytics instance;
AwarenessAnalytics awarenessAnalytic = new AwarenessAnalytics();
// establish the listener set
DetectionEventListener Del = new DetectionListener();
awarenessAnalytic.addListener(Del);
Now I need to call the main from the DetectionListener to complete the circle and take action on the events.
Related
#EventHandler
public void playerInteraction(PlayerInteractEvent event)
{
Action action = event.getAction();
Player player = event.getPlayer();
Block block = event.getClickedBlock();
if (action.equals(Action.RIGHT_CLICK_BLOCK))
{
if (block.getType().equals(Material.NETHER_WART_BLOCK))
{
player.setHealth(player.getHealth() -1);
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_HURT, 10, 1);
}
else if (block.getType().equals(Material.DIAMOND_BLOCK))
{
player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 1000, 2));
player.playSound(player.getLocation(), Sound.ENTITY_SPLASH_POTION_BREAK, 10, 1);
}
else if(block.getType().equals(Material.EMERALD_BLOCK))
{
if (player.getHealth() != 20)
{
player.setHealth(player.getHealth() + 1);
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 10, 1);;
}
if (player.getHealth() == 20)
{
player.sendMessage(ChatColor.DARK_RED + "You are already at full health!");
}
}
}
}
For some reason, all of these things happen twice whenever I right click the designated blocks. Anyone know why? I have posted the entire method, it's a player interaction event.
Thanks :)
First of all, make sure yo haven't registered the Listener class containing the event handler twice.
If that's not the case, according to this thread on the spigot forums, since Mojang added the left hand slot to Minecraft some events like PlayerInteractEvent or InventoryClickEvent will be called twice (once for each hand).
One possible fix is to "disable" the left hand on the event handler:
#EventHandler
public void onPlayerInteraction(PlayerInteractEvent event) {
if(event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getHand() == EquipmentSlot.HAND) {
//Do something once
}
}
If you require that both hands could be used to trigger the event you could do the following:
First time the code gets executed you add the player to a list.
Before executing the code you check if the player is in the list. If it's in the list it means the code was executed once so you can skip it.
Schedule a task to remove the player from the list some ticks later.
The code could be as follows:
public class Foo implements Listener {
//An instance of the main plugin class
private MainClass plugin;
private List<UUID> playerBlacklist = new ArrayList<>();
#EventHandler
public void onPlayerInteractEvent(PlayerInteractEvent event) {
if(playerBlacklist.contains(event.getPlayer().getUniqueId)) {
return;
} else {
blacklistPlayer(event.getPlayer());
}
//Do something
}
private void blacklistPlayer(UUID uuid) {
playerBlacklist.add(uuid);
BukkitRunnable runnable = new BukkitRunnable(){
#Override
public void run() {
playerBlacklist.remove(uuid);
}
}
runnable.runTaskLaterAsynchronously(plugin, 5L);
}
}
Let me know if this solved your issue.
I have been reading for hours about different multi-threading techniques for JavaFX and cannot seem to find what I'm looking for. The application being worked on is the "Messenger" which part of a bigger application to provide a trading marketplace for a game.
A breakdown of the process I am having trouble with:
A window with a 'Contact Seller' button is displayed
The user clicks 'Contact Seller', and the Messenger window should be displayed
Using the seller's name from the main window, the Messenger should check if a chat already exists with that name
If the chat already exists, get the index of that chat in the Messenger's ListView, and select the chat so the Messenger's text area is populated with the corresponding messages
If the chat doesn't exist, create one
The issue:
Chats are stored server-side
Messages between clients are stored in a message 'cache' on the server when being processed from one user to the other (they are sorted to their respective chats, inserted into the chats, and pushed to the database upon client disconnection)
Messages received on the client are stored locally in the respective chat
When the Messenger is opened, it requests a list of chats for the signed-in user
The server sends an ArrayList of chats, and upon receiving the client builds the Messenger's ListView with these objects
Now when I need to open the Messenger in order to 'Contact Seller', I need to make sure the sync with the server is complete. Without doing so, I won't be able to properly check if a chat already exists with that name since I won't have the most up-to-date list.
The 'RequestWorker' thread that handles incoming server messages is NOT on the JavaFX thread.
The RequestWorker 'gets' the Messenger instance if it is currently open, and populates the ListView with the newly received chat list. (This needs to happen on the JavaFX thread, since I am working in the Messenger GUI)
What I am trying to do is set a static AtomicBoolean syncInProgress to true when the Messenger initiates a sync upon being displayed. When the RequestWorker receives the latest list back from the server and finishes populating the Messengers ListView, it sets syncInProgress to false.
The sync takes longer than opening the Messenger and making it check if a chat exists. Doing it this way it has no items populated yet in the ListView and the method is ineffective.
Calling a while loop to wait until the boolean is changed, blocks the JavaFX thread which means the RequestWorker cannot do what it needs to in the JavaFX thread.
How can I continuously check for this variable to be set false, then continue to 'Contact Seller' once the ListView has been properly populated?
Contact Seller method: The while loop in here causes a block on the JavaFX thread, thus not enabling RequestWorker to properly populate the ListView.
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(e -> messenger.chatInput.clear());
}
RequestWorker process chat block:
switch(receivedFromServer)
case "chatlist":
// Client requested a chat list, results were returned from the server, and now we need to populate the messenger list
ChatListForm chatlistform = (ChatListForm)processedRequest;
if (Messenger.isOpen)
{
FutureTask<Void> updateChatList = new FutureTask(() -> {
Messenger tempMessenger = trader.getMessenger();
int currentIndex = tempMessenger.chatListView.getSelectionModel().getSelectedIndex();
tempMessenger.populate(chatlistform.getChatList());
tempMessenger.chatListView.getSelectionModel().select(currentIndex);
}, null);
Platform.runLater(updateChatList); // RequestWorker needs access to the JavaFX application thread
try {
updateChatList.get(); // Wait until the ListView has been populated before setting 'syncInProgress' to false again
}
catch (InterruptedException e) {
Alert.display(null, "Sync interrupted.");
}
catch (ExecutionException e) {
Alert.display(null, "Sync failed.");
}
TarkovTrader.syncInProgress.compareAndSet(true, false); // The value of syncInProgress should be true, change to false. Sync complete
}
else
{
Platform.runLater(() -> Alert.display(null, "New chat received."));
TarkovTrader.syncInProgress.compareAndSet(true, false);
}
break;
Contact Seller button logic: If a messenger is not open, create it and pass to static contactSeller method to use.
contactButton.setOnAction(e -> {
Messenger messenger;
if (Messenger.isOpen)
{
// Get messenger
messenger = trader.getMessenger();
}
else
{
messenger = new Messenger(worker);
messenger.display();
trader.setMessenger(messenger);
}
Messenger.contactSeller(messenger, item.getUsername(), item.getName());
itemdisplay.close();
});
EDIT:
Partially using Slaw's idea (AtomicBoolean is still being used since I'm not sure how to do this without it), this is what I came up with...
public static void contactSeller(Messenger messenger, String destination, String itemName)
{
Task<Void> waitForSync = new Task<Void>() {
#Override
public Void call()
{
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete
}
return null;
}
};
waitForSync.setOnSucceeded(e -> {
while (TarkovTrader.syncInProgress.get())
{
; // Wait until sync is complete to check the latest chat list for an existing chat
}
if (messenger.chatExists(destination))
{
// Chat exists, select the chat for the user
for (Chat openChat : messenger.chatListView.getItems())
{
if (openChat.getName(TarkovTrader.username).equals(destination))
{
messenger.chatListView.getSelectionModel().select(openChat);
messenger.unpackChatMessages(openChat.getMessages());
break;
}
}
}
else
{
messenger.buildNewChat(destination);
}
messenger.chatInput.setText("Hey " + destination + ". Interested in your '" + itemName + "'.");
messenger.chatInput.setOnMouseClicked(me -> messenger.chatInput.clear());
});
Thread t = new Thread(waitForSync);
t.setDaemon(true);
t.start();
}
Which does work, but this doesn't seem like a great solution. Is it fine to do something like this or is there a preferred method over this? I feel like the while loop and using a triggered boolean is sloppy..but is this common practice?
Don't try to handle the opened chat in the same method that schedules the chat retrieval. Instead create a queue of handlers that is executed as soon as the chat is ready.
Simplified Example
public class ChatManager {
private final Map<String, Chat> openChats = new HashMap<>();
// only call from application thread
public void openChat(String user, Consumer<Chat> chatReadyHandler) {
Chat chat = openChats.computeIfAbsent(user, this::createNewChat);
chat.addReadyHandler(chatReadyHandler);
}
private Chat createNewChat(String user) {
return new Chat(user);
}
public class Chat {
// list keeping track of handlers any used for synchronisation
private final ArrayList<Consumer<Chat>> readyHandlers = new ArrayList<>(1);
private boolean ready = false;
private final String user;
public String getUser() {
return user;
}
private void addReadyHandler(Consumer<Chat> chatReadyHandler) {
synchronized (readyHandlers) {
// if already ready, immediately execute, otherwise enqueue
if (ready) {
chatReadyHandler.accept(this);
} else {
readyHandlers.add(chatReadyHandler);
}
}
}
private void chatReady() {
synchronized (readyHandlers) {
ready = true;
}
// execute all handlers on the application thread
Platform.runLater(() -> {
synchronized (readyHandlers) {
for (Consumer<Chat> readyHandler : readyHandlers) {
readyHandler.accept(this);
}
readyHandlers.clear();
readyHandlers.trimToSize();
}
});
}
private Chat(String user) {
this.user = user;
new Thread(() -> {
try {
Thread.sleep(10000); // simulate time required to acquire chat
} catch (InterruptedException ex) {
}
chatReady();
}).start();
}
}
}
The following code creates a Chat with a user when Enter is pressed after typing the user name in the TextField and prints a message to the TextArea when the chat is ready.
#Override
public void start(Stage primaryStage) throws Exception {
ChatManager worker = new ChatManager();
TextField userName = new TextField();
TextArea textArea = new TextArea();
textArea.setEditable(false);
userName.setOnAction(evt -> {
String user = userName.getText();
userName.clear();
textArea.appendText("opening chat for " + user + "\n");
worker.openChat(user, chat -> textArea.appendText("chat for " + chat.getUser() + " ready\n"));
});
Scene scene = new Scene(new VBox(10, userName, textArea));
primaryStage.setScene(scene);
primaryStage.show();
}
I'm using a recursive method which implements the use of the SwingWorker class to do a research in one folder and all its subfolders - in the local hard drive.
Basically works fine but I'm stuck when I want to stop the SwingWorker method: when the user change the 'source folder' (I'm using a JTree - JAVAFX - to show all the folders in the local hard drive), I want to stop the current 'SwingWorker research' in that folder and start a new one, with the newest 'source path' results choosed from the user.
All the results of the research are stored in a private ObservableList - and updated everytime in the done() method, just by filling one TableView - JavaFX: so, when the user change the 'source path' I have to clean the results of the previous research.
Start method:
private static ObservableList<msg> data = FXCollections.observableArrayList();
private static SwingWorker<Void, Void> worker;
private static String currentFolder;
#Override
public void start(Stage primaryStage) throws Exception {
// TODO Auto-generated method stub
stage = primaryStage;
primaryStage.setScene(new Scene(createContent()));
styleControls();
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setFullScreen(false);
primaryStage.show();
msgp = new MsgParser();
}
createContent() method- recursive function its called here:
public Parent createContent() {
tree.getSelectionModel().selectedItemProperty().addListener( new ChangeListener<Object>() {
#Override
public void changed(ObservableValue observable, Object oldValue,
Object newValue) {
TreeItem<File> selectedItem = (TreeItem<File>) newValue;
currentFolder = selectedItem.getValue().getAbsolutePath();
// I want to stop here the previous SwingWorker call : the tree
// ChangeListener event is called when the user change the
// source folder of the research, by selecting one TreeItem on it.
if(worker!= null)
worker.cancel(true);
//Here I clean previous results
data.clear();
TV.setItems(data);
//And I call again the method with the new source Folder
ListMail(new File(currentFolder));
}
});
}
ListMail() method: [recursive SwingWorker]
private void ListMail(File dir) {
worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
if(!worker.isCancelled()) {
if(child != null){
if(!child.isDirectory()) {
if(child.getAbsolutePath().substring(child.getAbsolutePath().lastIndexOf('.')+1).equals("msg")) {
Message message = msgp.parseMsg(child.getPath());
String percorsoMail = child.getAbsolutePath().toUpperCase();
if(message != null) {
String fromEmail = message.getFromEmail();
String fromName = message.getFromName();
String subject = message.getSubject();
String received = message.getDate().toString();
String name;
if(fromEmail != null)
name = fromName + "(" + fromEmail + ")";
else name = fromName;
msg Message = new msg(name, subject, received);
if(!data.contains(Message))
data.add(Message);
//I use the Platform.runLater to
// take count of the number of results found
//It updates the GUI - works fine
Platform.runLater(new Runnable() {
#Override public void run() {
if(data != null && data.size() > 0)
setStatusLabel(data.size());
else
setStatusLabel(0);
}
});
}
}
} else {
/**
* Recursive call here : I do the research
* for the subfolders
*/
ListMail(child);
}
} else {
}
}
}
}
return null;
}
// Update GUI Here
protected void done() {
// I refresh here the TableView: works fine on-the-fly added results
TableView.setItems(data);
TableView.refresh();
}
};
//This doesn't do anything
if(!worker.isCancelled())
worker.execute();
}
Basically, the issue is that the SwingWorker thread never stop, I'm thinking because of the recursive calls which creates new pid process at every run or something ?
Also by using a dedicated external button, which I prefer to avoid, gives no results:
refreshBtn.setOnAction(e -> {
//Handle clicks on refreshBtn button
worker.cancel(true);
});
After I click on TreeItem to change source-folder, it just delete all the ObservableList elements created at that moment, but the previous research don't stop.
Everything works fine instead if I wait the research its finished - but this can works only when I'm in a deep-level folder, while I can't obviously wait when the research start with the "C:\" folder.
Ok so that's here how I managed this by using javafx.concurrent.
Just to point my experience with this, it seems using a recursive background Task for potentially long computations, such as scanning the Whole local drive like in my example, it's very memory consuming - also because I stored some results of this background computation in static local variables to access them faster: the result was a data-structure (ObservableList) with over 5000+ instances of a custom class to represent that specific data computed and then the OutOfMemoryError message or the background thread just going like in 'stand-by' without any advice after running for long time (waiting for garbage collection?).
Anyway here's the code that sum up how I solved: the threads are correctly closed. By the way, sometimes, there's a little 'GUI delay' due to cleaning the GUI on the isCancelled() method check: the GUI swing between clear/not clear, because in my opinion it keeps get filled by the results of the previous tasks in the recursion.
private static BackgroundTask backgroundTask;
private static Thread thread;
tree.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(final ObservableValue observable, final Object oldValue, final Object newValue) {
//I close previous running background tasks if there's any
if (backgroundTask != null) {
while (backgroundTask.isRunning()) {
backgroundTask.cancel(true);
// reset GUI nodes here used to show results of the previous thread
}
}
backgroundTask = new BackGoundTask();
thread= new Thread(backgroundTask);
thread.setDaemon(true);
thread.start();
//This will be called only when latest recursion is finished, not at every run
backgroundTask.setOnSucceeded(e -> {});
}
});
BackgroundTask class:
public static class BackgroundTask extends Task<Object> {
// .. variables used by the task here
//constructor: initialize variables at every run of the Task
public BackgroundTask() {
}
#Override
protected Object call() throws Exception {
if (!isCancelled()) {
// ... Do all background work here
Platform.runLater(new Runnable() {
#Override
public void run() {
// GUI progress can goes here
}
});
//recursion here
if(something) {
//...
} else {
call();
}
} else {
//user want to cancel task: clean GUI nodes
}
return null;
}
}
My ContentObserver for observing the history in the browser is not being called. I don't understand why it isn't. I'm not doing anything different or bizarre, I'm following the API specs exactly, but to no avail! Below is my code:
In my service:
public class MonitorService extends Service {
//some global variables declared here
private ContentObserver historyObserver, searchObserver, chromeObserver;
public void onCreate() {
super.onCreate();
isRunning = false;
this.preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
//this.historyObserver = new HistoryObserver();
this.historyObserver = new HistoryObserver(new Handler());
this.searchObserver = new HistoryObserver(new Handler());
this.chromeObserver = new HistoryObserver(new Handler());
getApplicationContext().getContentResolver().registerContentObserver(Uri.parse("content://com.android.chrome.browser/history"), false, this.chromeObserver);
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Browser.BOOKMARKS_URI, false, this.historyObserver);
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Browser.SEARCHES_URI, false, this.searchObserver);
}
//Other required methods in class
}//end of class
Then in my HistoryObserver Class we have:
public class HistoryObserver extends ContentObserver {
public final String TAG = "HistoryObserver";
public HistoryObserver(Handler handler) {
super(handler);
Log.d(TAG, "Creating new HistoryObserver");
}
public HistoryObserver() {
super(null);
Log.d(TAG, "Creating a new HistoryObserver without a Handler");
}
#Override
public boolean deliverSelfNotifications() {
Log.d(TAG, "delivering self notifications");
return true;
}
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d(TAG, "onChange without uri: " + selfChange);
//onChange(selfChange, null);
}
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
Log.d(TAG, "onChange: " + selfChange + "\t " + uri.toString());
}
}
Like I said there is nothing special or unique about this implementation. Yet, when I go go to a new website or search for something in Chrome, the onChange method is never fired.
I figured out the problem. The /history content provider isn't an observable. The observables come through the /bookmark uri. Once I discovered that, things got working very quickly and very well.
In java, I feed very confused on observer pattern or JMS. I want to use the event notification like below. Let us forget about those JMS or Observer, do you think it is possible or doable ? if yes, how to complete it ?
newSalesOrder = new SalesOrder();
newSalesOrder.notified("new SalesOrder order ID="+orderId);
EventRegister.bindEvent(SalesOrder.class, Delivery.class);
EventRegister.bindEvent(SalesOrder.class, Warehouse.class);
////////////
Delivery delivery = new Delivery();
delivery.listerning(new Event(source){
if(source == SalesOrder.class){
}
});
//////////
Warehouse warehouse = new Warehouse();
warehouse.listerning(new Event(source){
if(source == SalesOrder.class){
}
});
///////////
EventRegister{
static bindEvent(Class source, Class destination){
//???
}
}
You need to register (bind) objects not classes. You can keep registration list static at EventRegister but I think it's better to keep them as instance at SalesOrder. So it would be:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
SalesOrder mySalesOrder = new SalesOrder();
Warehouse myWarehouse = new Warehouse();
mySalesOrder.addListener(myWarehouse);
Delivery myDelivery = new Delivery();
mySalesOrder.addListener(myDelivery);
Event myEvent = new Event();
// Now 'myDelivery' and 'myWarehouse' objects will receive 'myEvent'
// object on their 'onEvent(Event event)' method
System.out.println("Event to be published: " + myEvent);
mySalesOrder.publishEvent(myEvent);
}
}
interface Listener {
public void onEvent(Event event);
}
class Event {
// Add reqired detail here!
}
class SalesOrder {
private List<Listener> listeners = new ArrayList<Listener>();
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
// Use proper access modifier
public void publishEvent(Event event) {
System.out.println(this + " is goint to publish " + event
+ " to " + listeners + " listeners.");
for (Listener listener : listeners) {
listener.onEvent(event);
}
}
// ...
}
class Warehouse implements Listener {
public void onEvent(Event event) {
// Do something when event received
System.out.println(event + " received at " + this);
}
// ...
}
class Delivery implements Listener {
public void onEvent(Event event) {
// Do something when event received
System.out.println(event + " received at " + this);
}
// ...
}
If you run it it will print something like:
Event to be published: Event#190d11
SalesOrder#a90653 is goint to publish Event#190d11 to [Warehouse#de6ced, Delivery#c17164] listeners.
Event#190d11 received at Warehouse#de6ced
Event#190d11 received at Delivery#c17164
This is a trivial sample, in real life use cases you may consider using a thread-safe implementation and refactoring registration out of SalesOrder using composition or inheritance.