Skip to content

Commit 2d9f401

Browse files
Merge (internal) runtime
1 parent 9e2caf5 commit 2d9f401

4 files changed

Lines changed: 281 additions & 419 deletions

File tree

‎pi4j-core/src/main/java/com/pi4j/context/impl/DefaultContext.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import com.pi4j.provider.impl.DefaultProviders;
3939
import com.pi4j.registry.Registry;
4040
import com.pi4j.runtime.Runtime;
41-
import com.pi4j.runtime.impl.DefaultRuntime;
4241
import org.slf4j.Logger;
4342
import org.slf4j.LoggerFactory;
4443

@@ -85,7 +84,7 @@ protected DefaultContext(ContextConfig config) {
8584
this.config = config;
8685

8786
// create internal runtime state instance (READ-ONLY ACCESS OBJECT)
88-
this.runtime = DefaultRuntime.newInstance(this);
87+
this.runtime = Runtime.newInstance(this);
8988

9089
// create API accessible providers instance (READ-ONLY ACCESS OBJECT)
9190
this.providers = DefaultProviders.newInstance(this.runtime.providers());

pi4j-core/src/main/java/com/pi4j/runtime/impl/MutableRegistry.java renamed to pi4j-core/src/main/java/com/pi4j/runtime/MutableRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.pi4j.runtime.impl;
1+
package com.pi4j.runtime;
22

33
/*-
44
* #%L

‎pi4j-core/src/main/java/com/pi4j/runtime/Runtime.java‎

Lines changed: 279 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* **********************************************************************
66
* ORGANIZATION : Pi4J
77
* PROJECT : Pi4J :: LIBRARY :: Java Library (CORE)
8-
* FILENAME : Runtime.java
8+
* FILENAME : DefaultRuntime.java
99
*
1010
* This file is part of the Pi4J project. More information about
1111
* this project can be found here: https://pi4j.com/
@@ -26,80 +26,310 @@
2626
*/
2727

2828
import com.pi4j.context.Context;
29-
import com.pi4j.event.InitializedEventProducer;
30-
import com.pi4j.event.ShutdownEventProducer;
29+
import com.pi4j.context.ContextConfig;
30+
import com.pi4j.event.*;
3131
import com.pi4j.exception.InitializeException;
3232
import com.pi4j.exception.ShutdownException;
33+
import com.pi4j.extension.Plugin;
34+
import com.pi4j.extension.impl.DefaultPluginService;
35+
import com.pi4j.extension.impl.PluginStore;
3336
import com.pi4j.io.IO;
37+
import com.pi4j.io.IOType;
38+
import com.pi4j.provider.Provider;
39+
import com.pi4j.provider.impl.DefaultRuntimeProviders;
3440
import com.pi4j.provider.impl.RuntimeProviders;
3541
import com.pi4j.registry.Registry;
42+
import com.pi4j.util.ExecutorPool;
43+
import org.slf4j.Logger;
44+
import org.slf4j.LoggerFactory;
3645

46+
import java.util.*;
47+
import java.util.concurrent.CompletableFuture;
48+
import java.util.concurrent.ExecutorService;
3749
import java.util.concurrent.Future;
3850

3951
/**
40-
* <p>Runtime interface.</p>
41-
*
42-
* The runtime manages the IO registry and the providers on behalf of the context. It's the only entity with
43-
* write access to the registry.
52+
* <p>DefaultRuntime class.</p>
4453
*
4554
* @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
4655
* @version $Id: $Id
4756
*/
48-
public interface Runtime extends InitializedEventProducer<Runtime>, ShutdownEventProducer<Runtime> {
49-
/**
50-
* <p>registry.</p>
51-
*
52-
* @return a {@link com.pi4j.registry.Registry} object.
53-
*/
54-
Registry registry();
57+
public class Runtime {
5558

56-
/**
57-
* <p>providers.</p>
58-
*
59-
* @return a {@link com.pi4j.provider.impl.RuntimeProviders} object.
60-
*/
61-
RuntimeProviders providers();
59+
private final Logger logger = LoggerFactory.getLogger(this.getClass());
60+
private final Context context;
61+
private final RuntimeProviders providers;
62+
private final List<Plugin> plugins;
63+
private boolean isShutdown = false;
64+
private final EventManager<Runtime, ShutdownListener, ShutdownEvent> shutdownEventManager;
65+
private final EventManager<Runtime, InitializedListener, InitializedEvent> initializedEventManager;
66+
private final ExecutorPool executorPool;
67+
private final ExecutorService runtimeExecutor;
68+
private final MutableRegistry registry;
6269

6370
/**
64-
* <p>context.</p>
71+
* <p>newInstance.</p>
6572
*
66-
* @return a {@link com.pi4j.context.Context} object.
67-
*/
68-
Context context();
69-
70-
Future<?> submitTask(Runnable task);
71-
72-
/**
73-
* <p>shutdown.</p>
73+
* @param context a {@link com.pi4j.context.Context} object.
7474
*
7575
* @return a {@link com.pi4j.runtime.Runtime} object.
76-
*
77-
* @throws com.pi4j.exception.ShutdownException if any.
7876
*/
79-
Runtime shutdown() throws ShutdownException;
77+
public static Runtime newInstance(Context context) {
78+
return new Runtime(context);
79+
}
8080

81-
Future<Context> asyncShutdown();
81+
// private constructor
82+
private Runtime(Context context) {
8283

83-
/**
84-
* @return Flag indicating if the runtime has been shutdown
85-
*/
86-
boolean isShutdown();
84+
// set local references
85+
this.context = context;
86+
plugins = new ArrayList<>();
87+
this.registry = new MutableRegistry(context);
88+
this.providers = DefaultRuntimeProviders.newInstance(this);
89+
90+
this.shutdownEventManager = new EventManager(this,
91+
(EventDelegate<ShutdownListener, ShutdownEvent>) (listener, event) -> listener.onShutdown(event));
92+
this.initializedEventManager = new EventManager(this,
93+
(EventDelegate<InitializedListener, InitializedEvent>) (listener, event) -> listener.onInitialized(event));
94+
95+
// initialize executor pool and runtime executor
96+
this.executorPool = new ExecutorPool();
97+
this.runtimeExecutor = this.executorPool.getExecutor("Pi4J.RUNTIME");
98+
99+
logger.debug("Pi4J runtime context successfully created & initialized.'");
100+
101+
// listen for shutdown to properly clean up
102+
// TODO :: ADD PI4J INTERNAL SHUTDOWN CALLBACKS/EVENTS
103+
if (this.context.config().enableShutdownHook()) {
104+
java.lang.Runtime.getRuntime().addShutdownHook(new Thread(() -> {
105+
try {
106+
// shutdown Pi4J
107+
if (!isShutdown)
108+
shutdown();
109+
} catch (Exception e) {
110+
logger.error("Failed to shutdown Pi4J runtime", e);
111+
}
112+
}, "pi4j-shutdown"));
113+
}
114+
}
115+
116+
public Context context() {
117+
return this.context;
118+
}
119+
120+
public Registry registry() {
121+
return registry;
122+
}
123+
124+
public RuntimeProviders providers() {
125+
return this.providers;
126+
}
127+
128+
public Future<?> submitTask(Runnable task) {
129+
return this.runtimeExecutor.submit(task);
130+
}
131+
132+
public Runtime shutdown() throws ShutdownException {
133+
if (isShutdown) {
134+
logger.warn("Pi4J context/runtime is already shutdown.'");
135+
return this;
136+
}
137+
138+
isShutdown = true;
139+
logger.info("Shutting down Pi4J context/runtime...");
140+
141+
// notify before shutdown event listeners (requires custom delegate to invoke appropriate listener method)
142+
shutdownEventManager.dispatch(new ShutdownEvent(this.context), ShutdownListener::beforeShutdown);
143+
144+
try {
145+
146+
// remove shutdown monitoring thread
147+
//java.lang.Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
148+
149+
// remove all I/O instances
150+
this.registry.shutdown();
151+
152+
// shutdown all providers
153+
this.providers.shutdown();
154+
155+
// shutdown all plugins
156+
for (Plugin plugin : this.plugins) {
157+
try {
158+
plugin.shutdown(this.context);
159+
} catch (Exception e) {
160+
logger.error(e.getMessage(), e);
161+
}
162+
}
163+
164+
// shutdown executor pool
165+
this.executorPool.destroy();
166+
167+
} catch (Exception e) {
168+
logger.error("failed to 'shutdown(); '", e);
169+
throw new ShutdownException(e);
170+
}
171+
172+
logger.info("Pi4J context/runtime successfully shutdown. Dispatching shutdown event.");
173+
174+
// notify shutdown event listeners
175+
shutdownEventManager.dispatch(new ShutdownEvent(this.context));
176+
177+
// remove all shutdown event listeners
178+
this.shutdownEventManager.clear();
179+
180+
return this;
181+
}
182+
183+
public Future<Context> asyncShutdown() {
184+
return CompletableFuture.supplyAsync(() -> {
185+
try {
186+
shutdown();
187+
} catch (Exception e) {
188+
logger.error(e.getMessage(), e);
189+
}
190+
return context;
191+
});
192+
}
193+
194+
public boolean isShutdown() {
195+
return isShutdown;
196+
}
197+
198+
public Runtime initialize() throws InitializeException {
199+
logger.info("Initializing Pi4J context/runtime...");
200+
try {
201+
// clear plugins container
202+
plugins.clear();
203+
204+
// container sets for providers to load
205+
Map<IOType, Provider> providers = new HashMap<>();
206+
207+
// only attempt to load platforms and providers from the classpath if an auto detect option is enabled
208+
ContextConfig config = context.config();
209+
if (config.autoDetectPlatforms() || config.autoDetectProviders()) {
210+
211+
// detect available Pi4J Plugins by scanning the classpath looking for plugin instances
212+
ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class);
213+
for (Plugin plugin : plugins) {
214+
if (plugin == null)
215+
continue;
216+
217+
if (!config.autoDetectMockPlugins() && plugin.isMock()) {
218+
logger.trace("Ignoring mock plugin: [{}] in classpath", plugin.getClass().getName());
219+
continue;
220+
}
221+
222+
logger.trace("detected plugin: [{}] in classpath; calling 'initialize()'",
223+
plugin.getClass().getName());
224+
try {
225+
// add plugin to internal cache
226+
this.plugins.add(plugin);
227+
228+
PluginStore store = new PluginStore();
229+
plugin.initialize(DefaultPluginService.newInstance(this.context(), store));
230+
231+
// if auto-detect providers is enabled,
232+
// OR
233+
// Detecting Mocks is enabled and this is a mock plugin
234+
// then add any detected providers to the collection to load
235+
if (config.autoDetectProviders() || (config.autoDetectMockPlugins() && plugin.isMock())) {
236+
store.providers.forEach(provider -> addProvider(provider, providers));
237+
}
238+
239+
} catch (Exception ex) {
240+
// unable to initialize this provider instance
241+
logger.error("unable to 'initialize()' plugin: [{}]; {}", plugin.getClass().getName(),
242+
ex.getMessage(), ex);
243+
}
244+
}
245+
}
246+
247+
context().config().getProviders().forEach(provider -> {
248+
Provider replaced = providers.put(provider.getType(), provider);
249+
if (replaced != null) {
250+
logger.info("Replacing auto detected provider {} {} with provider {} from context config",
251+
replaced.getType(), replaced.getName(), provider.getName());
252+
}
253+
});
254+
255+
// initialize all providers
256+
this.providers.initialize(providers.values());
257+
258+
} catch (Exception e) {
259+
logger.error("failed to 'initialize(); '", e);
260+
throw new InitializeException(e);
261+
}
262+
263+
logger.info("Pi4J context/runtime successfully initialized.");
264+
265+
// notify initialized event listeners
266+
notifyInitListeners();
267+
268+
return this;
269+
}
270+
271+
public <T extends IO> T shutdown(T instance) {
272+
return registry.shutdown(instance);
273+
}
274+
275+
public void register(IO instance) {
276+
registry.register(instance);
277+
}
87278

88279
/**
89-
* <p>initialize.</p>
90-
*
91-
* @return a {@link com.pi4j.runtime.Runtime} object.
280+
* <p>Adds providers to the given collection, to later be used in the runtime after initialization.</p>
281+
* <p>This method validates the priority of a {@link Provider}, and guarantees, that we don't have multiple
282+
* providers for the same {@link IOType}</p>
92283
*
93-
* @throws com.pi4j.exception.InitializeException if any.
284+
* @param provider
285+
* @param providers
94286
*/
95-
Runtime initialize() throws InitializeException;
287+
private void addProvider(Provider provider, Map<IOType, Provider> providers) {
288+
if (!providers.containsKey(provider.getType())) {
289+
providers.put(provider.getType(), provider);
290+
} else {
291+
Provider existingProvider = providers.get(provider.getType());
292+
if (provider.getPriority() <= existingProvider.getPriority()) {
293+
if (existingProvider.getName().equals(provider.getName()))
294+
throw new InitializeException(
295+
provider.getType() + " with name " + provider.getName() + " (" + provider.getId() + ") is already registered.");
296+
logger.info("Ignoring provider {} {} ({}) with priority {} as lower priority than {} which has priority {}",
297+
provider.getType(), provider.getName(), provider.getId(), provider.getPriority(),
298+
existingProvider.getName(), existingProvider.getPriority());
299+
} else {
300+
logger.info("Replacing provider {} {} ({}) with priority {} with provider {} ({}) with higher priority {}",
301+
existingProvider.getType(), existingProvider.getName(), existingProvider.getId(), existingProvider.getPriority(),
302+
provider.getName(), provider.getId(), provider.getPriority());
303+
providers.put(provider.getType(), provider);
304+
}
305+
}
306+
}
96307

97-
/**
98-
* Removes an IO instance from the IO registry and shuts it down. This is called indirectly when calling close()
99-
* on the IO instance.
100-
*/
101-
<T extends IO> T shutdown(T instance);
308+
private void notifyInitListeners() {
309+
initializedEventManager.dispatch(new InitializedEvent(this.context));
310+
}
311+
312+
public Runtime addListener(ShutdownListener... listener) {
313+
return shutdownEventManager.add(listener);
314+
}
315+
316+
public Runtime removeListener(ShutdownListener... listener) {
317+
return shutdownEventManager.remove(listener);
318+
}
319+
320+
public Runtime removeAllShutdownListeners() {
321+
return shutdownEventManager.clear();
322+
}
323+
324+
public Runtime removeAllInitializedListeners() {
325+
return initializedEventManager.clear();
326+
}
327+
328+
public Runtime addListener(InitializedListener... listener) {
329+
return initializedEventManager.add(listener);
330+
}
102331

103-
/** Add an IO instance to the IO registry. */
104-
void register(IO instance);
332+
public Runtime removeListener(InitializedListener... listener) {
333+
return initializedEventManager.remove(listener);
334+
}
105335
}

0 commit comments

Comments
 (0)