/*
 * 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.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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() {
        /*
         * Thread-safety was originally provided through use of a
         * ConcurrentHashMap as the subscribers by event collection. However,
         * the semantics of ConcurrentHashMap are such that it was possible for
         * a subscriber to receive an event after unsubscribing. To eliminate
         * this possibility, the map was changed to an ordinary map and the
         * methods of this class were made synchronized instead.
         */
        subscribersByEvent = new HashMap<>();
    }

    /**
     * Adds an event subscriber to this publisher.
     *
     * @param eventNames The events the subscriber is interested in.
     * @param subscriber The subscriber to add.
     */
    synchronized 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.
     */
    synchronized void addSubscriber(String eventName, PropertyChangeListener subscriber) {
        if (!subscribersByEvent.containsKey(eventName)) {
            subscribersByEvent.put(eventName, new HashSet<>());
        }
        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.
     */
    synchronized 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.
     */
    synchronized 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.
     */
    synchronized 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,20 +778,16 @@
 va.util.
-Hash
 Map;%0Aimp
@@ -800,20 +800,16 @@
 va.util.
-Hash
 Set;%0Aimp
@@ -826,33 +826,36 @@
 til.
-Map;%0Aimport java.util.Set
+concurrent.ConcurrentHashMap
 ;%0Aim
@@ -1603,474 +1603,8 @@
 ) %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
     
@@ -1628,24 +1628,34 @@
 Event = new 
+Concurrent
 HashMap%3C%3E();
@@ -1853,37 +1853,24 @@
      */%0A    
-synchronized 
 void addSubs
@@ -2240,37 +2240,24 @@
      */%0A    
-synchronized 
 void addSubs
@@ -2330,13 +2330,8 @@
     
-if (!
 subs
@@ -2349,108 +2349,87 @@
 ent.
-containsKey(eventName)) %7B%0A            subscribersByEvent.put(eventName, new Hash
+putIfAbsent(eventName, ConcurrentHashMap.%3CPropertyChangeListener%3EnewKey
 Set
-%3C%3E
 ());%0A
-        %7D%0A
     
@@ -2762,37 +2762,24 @@
      */%0A    
-synchronized 
 void removeS
@@ -3173,37 +3173,24 @@
      */%0A    
-synchronized 
 void removeS
@@ -3634,21 +3634,8 @@
     
-synchronized 
 void

Rollback LocalEventPublisher commit f5dd822128878096d79add9bdc4aae7034a3a164