001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2;
018
019import java.util.HashMap;
020import java.util.LinkedHashSet;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.commons.scxml2.model.EnterableState;
025import org.apache.commons.scxml2.model.Observable;
026import org.apache.commons.scxml2.model.Transition;
027import org.apache.commons.scxml2.model.TransitionTarget;
028
029/**
030 * The registry where SCXML listeners are recorded for nodes of
031 * interest such as the <code>SCXML</code> root,
032 * <code>EnterableState</code>s and <code>Transition</code>s.
033 * The notification registry keeps track of all
034 * <code>SCXMLListener</code>s attached and notifies relevant
035 * listeners of the events that interest them.
036 *
037 */
038public final class NotificationRegistry {
039
040    /**
041     * The Map of all listeners keyed by {@link Observable#getObservableId()}.
042     */
043    private final Map<Integer, Set<SCXMLListener>> regs;
044
045    /**
046     * Constructor.
047     */
048    public NotificationRegistry() {
049        this.regs = new HashMap<Integer, Set<SCXMLListener>>();
050    }
051
052    /**
053     * Register this SCXMLListener for this Observable.
054     *
055     * @param source The observable this listener wants to listen to
056     * @param lst The listener
057     */
058    synchronized void addListener(final Observable source, final SCXMLListener lst) {
059        if (source != null && source.getObservableId() != null) {
060            Set<SCXMLListener> entries = regs.get(source.getObservableId());
061            if (entries == null) {
062                entries = new LinkedHashSet<SCXMLListener>();
063                regs.put(source.getObservableId(), entries);
064            }
065            entries.add(lst);
066        }
067    }
068
069    /**
070     * Deregister this SCXMLListener for this Observable.
071     *
072     * @param source The observable this listener wants to stop listening to
073     * @param lst The listener
074     */
075    synchronized void removeListener(final Observable source, final SCXMLListener lst) {
076        if (source != null && source.getObservableId() != null) {
077            Set<SCXMLListener> entries = regs.get(source.getObservableId());
078            if (entries != null) {
079                entries.remove(lst);
080                if (entries.size() == 0) {
081                    regs.remove(source.getObservableId());
082                }
083            }
084        }
085    }
086
087    /**
088     * Inform all relevant listeners that a EnterableState has been
089     * entered.
090     *
091     * @param source The Observable
092     * @param state The EnterableState that was entered
093     */
094    public synchronized void fireOnEntry(final Observable source,
095            final EnterableState state) {
096        if (source != null && source.getObservableId() != null) {
097            Set<SCXMLListener> entries = regs.get(source.getObservableId());
098            if (entries != null) {
099                for (SCXMLListener lst : entries) {
100                    lst.onEntry(state);
101                }
102            }
103        }
104    }
105
106    /**
107     * Inform all relevant listeners that a EnterableState has been
108     * exited.
109     *
110     * @param source The Observable
111     * @param state The EnterableState that was exited
112     */
113    public synchronized void fireOnExit(final Observable source,
114            final EnterableState state) {
115        if (source != null && source.getObservableId() != null) {
116            Set<SCXMLListener> entries = regs.get(source.getObservableId());
117            if (entries != null) {
118                for (SCXMLListener lst : entries) {
119                    lst.onExit(state);
120                }
121            }
122        }
123    }
124
125    /**
126     * Inform all relevant listeners of a transition that has occured.
127     *
128     * @param source The Observable
129     * @param from The source EnterableState
130     * @param to The destination EnterableState
131     * @param transition The Transition that was taken
132     * @param event The event name triggering the transition
133     */
134    public synchronized void fireOnTransition(final Observable source,
135            final TransitionTarget from, final TransitionTarget to,
136            final Transition transition, final String event) {
137        if (source != null && source.getObservableId() != null) {
138            Set<SCXMLListener> entries = regs.get(source.getObservableId());
139            if (entries != null) {
140                for (SCXMLListener lst : entries) {
141                    lst.onTransition(from, to, transition, event);
142                }
143            }
144        }
145    }
146}
147