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.Collections;
022import java.util.IdentityHashMap;
023import java.util.Map;
024import java.util.logging.Level;
025import java.util.logging.Logger;
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 */
032@SuppressWarnings("PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal")
033public class GeneratorRegistry {
034
035    @SuppressWarnings("PMD.FieldNamingConventions")
036    private static final Logger generatorTracking
037        = Logger.getLogger(ComponentType.class.getPackage().getName()
038            + ".generatorTracking");
039
040    private long running;
041    private Thread keepAlive;
042    @SuppressWarnings("PMD.ImmutableField")
043    private Map<Object, Object> generators;
044
045    /**
046     * Holds a generator instance.
047     */
048    private static final class InstanceHolder {
049        @SuppressWarnings("PMD.AccessorClassGeneration")
050        private static final GeneratorRegistry INSTANCE
051            = new GeneratorRegistry();
052    }
053
054    private GeneratorRegistry() {
055        if (generatorTracking.isLoggable(Level.FINE)) {
056            generators = Collections.synchronizedMap(new IdentityHashMap<>());
057        }
058    }
059
060    /**
061     * Returns the singleton instance of the registry.
062     *
063     * @return the generator registry
064     */
065    public static GeneratorRegistry instance() {
066        return InstanceHolder.INSTANCE;
067    }
068
069    /**
070     * Adds a generator.
071     *
072     * @param obj the obj
073     */
074    @SuppressWarnings({ "PMD.GuardLogStatement", "PMD.AvoidDuplicateLiterals" })
075    public void add(Object obj) {
076        synchronized (this) {
077            running += 1;
078            if (generators != null) {
079                generators.put(obj, null);
080                generatorTracking.finest(() -> "Added generator " + obj
081                    + ", " + generators.size() + " generators registered: "
082                    + generators.keySet());
083            }
084            if (running == 1) { // NOPMD, no, not using a constant for this.
085                keepAlive = new Thread("GeneratorRegistry") {
086                    @Override
087                    @SuppressWarnings("PMD.EmptyCatchBlock")
088                    public void run() {
089                        try {
090                            while (true) {
091                                sleep(Long.MAX_VALUE);
092                            }
093                        } catch (InterruptedException e) {
094                            // Okay, then stop
095                        }
096                    }
097                };
098                keepAlive.start();
099            }
100        }
101    }
102
103    /**
104     * Removes the generator.
105     *
106     * @param obj the generator
107     */
108    @SuppressWarnings("PMD.GuardLogStatement")
109    public void remove(Object obj) {
110        synchronized (this) {
111            running -= 1;
112            if (generators != null) {
113                generators.remove(obj);
114                generatorTracking.finest(() -> "Removed generator " + obj
115                    + ", " + generators.size() + " generators registered: "
116                    + generators.keySet());
117            }
118            if (running == 0) {
119                generatorTracking
120                    .finest(() -> "Zero generators, notifying all.");
121                keepAlive.interrupt();
122                notifyAll();
123            }
124        }
125    }
126
127    /**
128     * Checks if is exhausted (no generators left)
129     *
130     * @return true, if is exhausted
131     */
132    public boolean isExhausted() {
133        return running == 0;
134    }
135
136    /**
137     * Await exhaustion.
138     *
139     * @throws InterruptedException the interrupted exception
140     */
141    @SuppressWarnings({ "PMD.CollapsibleIfStatements",
142        "PMD.GuardLogStatement" })
143    public void awaitExhaustion() throws InterruptedException {
144        if (generators != null) {
145            synchronized (this) {
146                if (running != generators.size()) {
147                    generatorTracking
148                        .severe(() -> "Generator count doesn't match tracked.");
149                }
150            }
151        }
152        while (running > 0) {
153            // generators.keySet() may call EventProcessor.toString()
154            // which locks on the EventProcessor which may want a lock
155            // on the registry (deadlock). So keep this out of the
156            // synchronized.
157            if (generators != null) {
158                generatorTracking
159                    .fine(() -> "Thread " + Thread.currentThread().getName()
160                        + " is waiting, " + generators.size()
161                        + " generators registered: "
162                        + generators.keySet());
163            }
164            synchronized (this) {
165                if (running > 0) {
166                    wait();
167                }
168            }
169        }
170        generatorTracking
171            .finest("Thread " + Thread.currentThread().getName()
172                + " continues.");
173    }
174
175    /**
176     * Await exhaustion with a timeout.
177     *
178     * @param timeout the timeout
179     * @return true, if successful
180     * @throws InterruptedException the interrupted exception
181     */
182    @SuppressWarnings({ "PMD.CollapsibleIfStatements",
183        "PMD.GuardLogStatement" })
184    public boolean awaitExhaustion(long timeout)
185            throws InterruptedException {
186        synchronized (this) {
187            if (generators != null) {
188                if (running != generators.size()) {
189                    generatorTracking.severe(
190                        "Generator count doesn't match tracked.");
191                }
192            }
193            if (isExhausted()) {
194                return true;
195            }
196            if (generators != null) {
197                generatorTracking
198                    .fine(() -> "Waiting, generators: " + generators.keySet());
199            }
200            wait(timeout);
201            if (generators != null) {
202                generatorTracking
203                    .fine(() -> "Waited, generators: " + generators.keySet());
204            }
205            return isExhausted();
206        }
207    }
208}