/*
 * Autopsy Forensic Browser
 *
 * Copyright 2011-2015 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sleuthkit.autopsy.events;

import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;

/**
 * Provides thread-safe support for publishing events to registered subscribers
 * on this Autopsy node. Subscribers are constrained to be
 * PropertyChangeListeners to integrate with the legacy use of JavaBeans
 * PropertyChangeEvents and PropertyChangeListeners as an application event
 * system.
 */
final class LocalEventPublisher {

    private static final Logger logger = Logger.getLogger(LocalEventPublisher.class.getName());
    private final Map<String, Set<PropertyChangeListener>> subscribersByEvent;

    /**
     * Constructs an object for publishing events to registered subscribers on
     * this Autopsy node.
     */
    LocalEventPublisher() {
        subscribersByEvent = new ConcurrentHashMap<>();
    }

    /**
     * Adds an event subscriber to this publisher.
     *
     * @param eventNames The events the subscriber is interested in.
     * @param subscriber The subscriber to add.
     */
    void addSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
        for (String eventName : eventNames) {
            addSubscriber(eventName, subscriber);
        }
    }

    /**
     * Adds an event subscriber to this publisher.
     *
     * @param eventName  The event the subscriber is interested in.
     * @param subscriber The subscriber to add.
     */
    void addSubscriber(String eventName, PropertyChangeListener subscriber) {
        subscribersByEvent.putIfAbsent(eventName, ConcurrentHashMap.<PropertyChangeListener>newKeySet());
        Set<PropertyChangeListener> subscribers = subscribersByEvent.get(eventName);
        subscribers.add(subscriber);
    }

    /**
     * Removes an event subscriber from this publisher.
     *
     * @param eventNames The events the subscriber is no longer interested in.
     * @param subscriber The subscriber to remove.
     */
    void removeSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
        for (String eventName : eventNames) {
            removeSubscriber(eventName, subscriber);
        }
    }

    /**
     * Removes an event subscriber from this publisher.
     *
     * @param eventNames The event the subscriber is no longer interested in.
     * @param subscriber The subscriber to remove.
     */
    void removeSubscriber(String eventName, PropertyChangeListener subscriber) {
        Set<PropertyChangeListener> subscribers = subscribersByEvent.getOrDefault(eventName, null);
        if (null != subscribers) {
            subscribers.remove(subscriber);
        }
    }

    /**
     * Publishes an event to all registered subscribers, even if a subscriber
     * throws an exception.
     *
     * @param event The event to be published.
     */
    void publish(AutopsyEvent event) {
        Set<PropertyChangeListener> subscribers = subscribersByEvent.getOrDefault(event.getPropertyName(), null);
        if (null != subscribers) {
            for (PropertyChangeListener subscriber : subscribers) {
                try {
                    subscriber.propertyChange(event);
                } catch (Exception ex) {
                    logger.log(Level.SEVERE, "Exception thrown by subscriber", ex);
                }
            }
        }
    }

}

@@ -778,16 +778,20 @@
 va.util.
+Hash
 Map;%0Aimp
@@ -804,16 +804,20 @@
 va.util.
+Hash
 Set;%0Aimp
@@ -834,36 +834,33 @@
 til.
-concurrent.ConcurrentHashMap
+Map;%0Aimport java.util.Set
 ;%0Aim
@@ -1600,24 +1600,490 @@
 blisher() %7B%0A
+        /*%0A         * Thread-safety was originally provided through use of a%0A         * ConcurrentHashMap as the subscribers by event collection. However,%0A         * the semantics of ConcurrentHashMap are such that it was possible for%0A         * a subscriber to receive an event after unsubscribing. To eliminate%0A         * this possibility, the map was changed to an ordinary map and the%0A         * methods of this class were made synchronized instead.%0A         */%0A
         subs
@@ -2103,26 +2103,16 @@
 t = new 
-Concurrent
 HashMap%3C
@@ -2310,32 +2310,45 @@
 dd.%0A     */%0A    
+synchronized 
 void addSubscrib
@@ -2714,24 +2714,37 @@
      */%0A    
+synchronized 
 void addSubs
@@ -2805,32 +2805,37 @@
 iber) %7B%0A        
+if (!
 subscribersByEve
@@ -2841,87 +2841,108 @@
 ent.
-putIfAbsent(eventName, ConcurrentHashMap.%3CPropertyChangeListener%3EnewKey
+containsKey(eventName)) %7B%0A            subscribersByEvent.put(eventName, new Hash
 Set
+%3C%3E
 ());%0A
+        %7D%0A
     
@@ -3271,32 +3271,45 @@
 ve.%0A     */%0A    
+synchronized 
 void removeSubsc
@@ -3699,24 +3699,37 @@
      */%0A    
+synchronized 
 void removeS
@@ -4169,16 +4169,29 @@
  */%0A    
+synchronized 
 void pub

Correct semantics of LocalEventPublisher