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}