001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2016-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core.internal;
020
021import java.util.IdentityHashMap;
022import java.util.Map;
023import java.util.logging.Level;
024import java.util.logging.Logger;
025
026import org.jgrapes.core.ComponentType;
027
028/**
029 * A registry for generators. Used to track generators and determine
030 * whether the application has stopped.
031 */
032public class GeneratorRegistry {
033
034    @SuppressWarnings("PMD.FieldNamingConventions")
035    private static final Logger generatorTracking
036        = Logger.getLogger(ComponentType.class.getPackage().getName()
037            + ".generatorTracking");
038
039    private long running;
040    private Thread keepAlive;
041    private Map<Object, Object> generators;
042
043    /**
044     * Holds a generator instance.
045     */
046    private static final class InstanceHolder {
047        @SuppressWarnings("PMD.AccessorClassGeneration")
048        private static final GeneratorRegistry INSTANCE
049            = new GeneratorRegistry();
050    }
051
052    private GeneratorRegistry() {
053        if (generatorTracking.isLoggable(Level.FINE)) {
054            generators = new IdentityHashMap<>();
055        }
056    }
057
058    /**
059     * Returns the singleton instance of the registry.
060     *
061     * @return the generator registry
062     */
063    public static GeneratorRegistry instance() {
064        return InstanceHolder.INSTANCE;
065    }
066
067    /**
068     * Adds a generator.
069     *
070     * @param obj the obj
071     */
072    @SuppressWarnings({ "PMD.GuardLogStatement", "PMD.AvoidDuplicateLiterals" })
073    public void add(Object obj) {
074        synchronized (this) {
075            running += 1;
076            if (generators != null) {
077                generators.put(obj, null);
078                generatorTracking.finest(() -> "Added generator " + obj
079                    + ", " + generators.size() + " generators registered: "
080                    + generators.keySet());
081            }
082            if (running == 1) { // NOPMD, no, not using a constant for this.
083                keepAlive = new Thread("GeneratorRegistry") {
084                    @Override
085                    public void run() {
086                        try {
087                            while (true) {
088                                Thread.sleep(Long.MAX_VALUE);
089                            }
090                        } catch (InterruptedException e) {
091                            // Okay, then stop
092                        }
093                    }
094                };
095                keepAlive.start();
096            }
097        }
098    }
099
100    /**
101     * Removes the generator.
102     *
103     * @param obj the generator
104     */
105    @SuppressWarnings("PMD.GuardLogStatement")
106    public void remove(Object obj) {
107        synchronized (this) {
108            running -= 1;
109            if (generators != null) {
110                generators.remove(obj);
111                generatorTracking.finest(() -> "Removed generator " + obj
112                    + ", " + generators.size() + " generators registered: "
113                    + generators.keySet());
114            }
115            if (running == 0) {
116                generatorTracking
117                    .finest(() -> "Zero generators, notifying all.");
118                keepAlive.interrupt();
119                notifyAll();
120            }
121        }
122    }
123
124    /**
125     * Checks if is exhausted (no generators left)
126     *
127     * @return true, if is exhausted
128     */
129    public boolean isExhausted() {
130        return running == 0;
131    }
132
133    /**
134     * Await exhaustion.
135     *
136     * @throws InterruptedException the interrupted exception
137     */
138    @SuppressWarnings({ "PMD.CollapsibleIfStatements",
139        "PMD.GuardLogStatement" })
140    public void awaitExhaustion() throws InterruptedException {
141        synchronized (this) {
142            if (generators != null) {
143                if (running != generators.size()) {
144                    generatorTracking
145                        .severe(() -> "Generator count doesn't match tracked.");
146                }
147            }
148            while (running > 0) {
149                if (generators != null) {
150                    generatorTracking
151                        .fine(() -> "Thread " + Thread.currentThread().getName()
152                            + " is waiting, " + generators.size()
153                            + " generators registered: "
154                            + generators.keySet());
155                }
156                wait();
157            }
158            generatorTracking
159                .finest("Thread " + Thread.currentThread().getName()
160                    + " continues.");
161        }
162    }
163
164    /**
165     * Await exhaustion with a timeout.
166     *
167     * @param timeout the timeout
168     * @return true, if successful
169     * @throws InterruptedException the interrupted exception
170     */
171    @SuppressWarnings({ "PMD.CollapsibleIfStatements",
172        "PMD.GuardLogStatement" })
173    public boolean awaitExhaustion(long timeout)
174            throws InterruptedException {
175        synchronized (this) {
176            if (generators != null) {
177                if (running != generators.size()) {
178                    generatorTracking.severe(
179                        "Generator count doesn't match tracked.");
180                }
181            }
182            if (isExhausted()) {
183                return true;
184            }
185            if (generators != null) {
186                generatorTracking
187                    .fine(() -> "Waiting, generators: " + generators.keySet());
188            }
189            wait(timeout);
190            if (generators != null) {
191                generatorTracking
192                    .fine(() -> "Waited, generators: " + generators.keySet());
193            }
194            return isExhausted();
195        }
196    }
197}