Skip to content

Commit 54daac9

Browse files
committed
Merge pull request #215 from JLLeitschuh/feat/dependencyInjection
Feat/dependency injection
2 parents 5f6a37c + 02e9824 commit 54daac9

120 files changed

Lines changed: 3959 additions & 2318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ allprojects {
2525
mavenCentral()
2626
jcenter()
2727
}
28+
jacoco {
29+
toolVersion = "0.7.5.201505241946"
30+
}
31+
2832

2933
dependencies {
34+
testCompile group: 'net.jodah', name: 'concurrentunit', version: '0.4.2'
3035
testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
3136
testCompile group: 'junit', name: 'junit', version: '4.12'
3237
}
@@ -58,6 +63,12 @@ allprojects {
5863
events "failed"
5964
exceptionFormat "full"
6065
}
66+
doFirst {
67+
filter.includePatterns.each {
68+
include "${it.replaceAll('\\.', "\\${File.separator}")}.class"
69+
}
70+
filter.setIncludePatterns('*')
71+
}
6172
}
6273
}
6374

@@ -66,6 +77,7 @@ def os = osdetector.classifier.replace("osx", "macosx").replace("windows-x86_32"
6677
project(":core") {
6778
apply plugin: 'java'
6879
apply plugin: 'idea'
80+
apply plugin: 'jacoco'
6981
apply plugin: "com.github.johnrengelman.shadow"
7082

7183
configurations {
@@ -88,6 +100,8 @@ project(":core") {
88100
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.8'
89101
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'
90102
compile group: 'com.google.guava', name: 'guava', version: '18.0'
103+
compile group: 'com.google.inject', name: 'guice', version: '4.0'
104+
compile group: 'com.google.inject.extensions', name: 'guice-assistedinject', version: '4.0'
91105
}
92106

93107
mainClassName = 'edu.wpi.grip.core.Main'
@@ -161,6 +175,7 @@ project(":core") {
161175
project(":ui") {
162176
apply plugin: 'java'
163177
apply plugin: 'idea'
178+
apply plugin: 'jacoco'
164179
apply plugin: 'application'
165180
apply from: 'http://dl.bintray.com/shemnon/javafx-gradle/8.1.1/javafx.plugin'
166181

@@ -173,6 +188,7 @@ project(":ui") {
173188
ideProvider project(path: ':core', configuration: 'compile')
174189
compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.10'
175190
testCompile files(project(':core').sourceSets.test.output.classesDir)
191+
testCompile files(project(':core').sourceSets.test.output.resourcesDir)
176192
testCompile group: 'org.testfx', name: 'testfx-core', version: '4.0.+'
177193
testCompile group: 'org.testfx', name: 'testfx-junit', version: '4.0.+'
178194
}

core/src/main/java/edu/wpi/grip/core/Connection.java

Lines changed: 20 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,91 +2,39 @@
22

33
import com.google.common.eventbus.EventBus;
44
import com.google.common.eventbus.Subscribe;
5+
import com.google.inject.Inject;
6+
import com.google.inject.assistedinject.Assisted;
57
import com.thoughtworks.xstream.annotations.XStreamAlias;
6-
import edu.wpi.grip.core.events.ConnectionRemovedEvent;
7-
import edu.wpi.grip.core.events.SocketChangedEvent;
8-
import edu.wpi.grip.core.events.SourceRemovedEvent;
9-
import edu.wpi.grip.core.events.StepRemovedEvent;
8+
import edu.wpi.grip.core.events.*;
109

1110
import static com.google.common.base.Preconditions.checkArgument;
12-
import static com.google.common.base.Preconditions.checkNotNull;
1311

1412
/**
1513
* A connection is a rule that causes one socket to update to always the value of another socket.
1614
*/
1715
@XStreamAlias(value = "grip:Connection")
1816
public class Connection<T> {
17+
1918
private final EventBus eventBus;
2019
private final OutputSocket<? extends T> outputSocket;
2120
private final InputSocket<T> inputSocket;
2221

22+
23+
public interface Factory <T> {
24+
Connection<T> create(OutputSocket<? extends T> outputSocket, InputSocket<T> inputSocket);
25+
}
26+
2327
/**
24-
* @param eventBus The Guava {@link EventBus} used by the application.
28+
* @param pipeline The pipeline to create the connection inside of.
2529
* @param outputSocket The socket to listen for changes in.
2630
* @param inputSocket A different socket to update when a change occurs in the first.
2731
*/
28-
public Connection(EventBus eventBus, OutputSocket<? extends T> outputSocket, InputSocket<T> inputSocket) {
32+
@Inject
33+
Connection(EventBus eventBus, Pipeline pipeline, @Assisted OutputSocket<? extends T> outputSocket, @Assisted InputSocket<T> inputSocket) {
2934
this.eventBus = eventBus;
3035
this.outputSocket = outputSocket;
3136
this.inputSocket = inputSocket;
32-
33-
checkNotNull(inputSocket);
34-
checkNotNull(outputSocket);
35-
checkNotNull(eventBus);
36-
checkArgument(Connection.canConnect(outputSocket, inputSocket), "Cannot connect sockets");
37-
38-
inputSocket.setValueOptional(outputSocket.getValue());
39-
40-
eventBus.register(this);
41-
}
42-
43-
/**
44-
* @return true if a connection can be made from the given output socket to the given input socket
45-
*/
46-
@SuppressWarnings("unchecked")
47-
public static boolean canConnect(Socket socket1, Socket socket2) {
48-
final OutputSocket<?> outputSocket;
49-
final InputSocket<?> inputSocket;
50-
51-
// One socket must be an input and one must be an output
52-
if (socket1.getDirection() == socket2.getDirection()) {
53-
return false;
54-
}
55-
56-
if (socket1.getDirection().equals(Socket.Direction.OUTPUT)) {
57-
outputSocket = (OutputSocket) socket1;
58-
inputSocket = (InputSocket) socket2;
59-
} else {
60-
inputSocket = (InputSocket) socket1;
61-
outputSocket = (OutputSocket) socket2;
62-
}
63-
64-
final SocketHint outputHint = socket1.getSocketHint();
65-
final SocketHint inputHint = socket2.getSocketHint();
66-
67-
// The input socket must be able to hold the type of value that the output socket contains
68-
if (!inputHint.getType().isAssignableFrom(outputHint.getType())) {
69-
return false;
70-
}
71-
72-
// Input sockets can only be connected to one thing
73-
if (!inputSocket.getConnections().isEmpty()) {
74-
return false;
75-
}
76-
77-
// If both sockets are in steps, the output must be before the input in the pipeline. This prevents "backwards"
78-
// connections, which both enforces a well-organized pipeline and prevents feedback loops.
79-
final boolean[] backwards = {false};
80-
outputSocket.getStep().ifPresent(outputStep -> inputSocket.getStep().ifPresent(inputStep -> {
81-
final Pipeline pipeline = checkNotNull(inputStep.getPipeline(), "Pipeline is null");
82-
if (!pipeline.isBefore(outputStep, inputStep)) {
83-
backwards[0] = true;
84-
}
85-
}));
86-
87-
return !backwards[0];
88-
89-
37+
checkArgument(pipeline.canConnect(outputSocket, inputSocket), "Cannot connect sockets");
9038
}
9139

9240
public OutputSocket<? extends T> getOutputSocket() {
@@ -97,6 +45,13 @@ public InputSocket<T> getInputSocket() {
9745
return this.inputSocket;
9846
}
9947

48+
@Subscribe
49+
public void onConnectionAdded(ConnectionAddedEvent event) {
50+
if (event.getConnection().equals(this)) {
51+
inputSocket.setValueOptional(outputSocket.getValue());
52+
}
53+
}
54+
10055
@Subscribe
10156
public void onOutputChanged(SocketChangedEvent e) {
10257
if (e.getSocket() == outputSocket) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package edu.wpi.grip.core;
2+
3+
import com.google.common.eventbus.EventBus;
4+
import com.google.common.eventbus.SubscriberExceptionContext;
5+
import com.google.inject.AbstractModule;
6+
import com.google.inject.TypeLiteral;
7+
import com.google.inject.assistedinject.FactoryModuleBuilder;
8+
import com.google.inject.matcher.Matchers;
9+
import com.google.inject.spi.InjectionListener;
10+
import com.google.inject.spi.TypeEncounter;
11+
import com.google.inject.spi.TypeListener;
12+
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
13+
import edu.wpi.grip.core.serialization.Project;
14+
import edu.wpi.grip.core.sources.CameraSource;
15+
import edu.wpi.grip.core.sources.ImageFileSource;
16+
import edu.wpi.grip.core.sources.MultiImageFileSource;
17+
import edu.wpi.grip.core.util.ExceptionWitness;
18+
19+
/**
20+
* A Guice {@link com.google.inject.Module} for GRIP's core package. This is where instances of {@link Pipeline},
21+
* {@link Palette}, {@link Project}, etc... are created.
22+
*/
23+
public class GRIPCoreModule extends AbstractModule {
24+
25+
private final EventBus eventBus = new EventBus(this::onSubscriberException);
26+
27+
public GRIPCoreModule() {
28+
Thread.setDefaultUncaughtExceptionHandler(this::onThreadException);
29+
}
30+
31+
@Override
32+
protected void configure() {
33+
// Register any injected object on the event bus
34+
bindListener(Matchers.any(), new TypeListener() {
35+
@Override
36+
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
37+
encounter.register((InjectionListener<I>) eventBus::register);
38+
}
39+
});
40+
41+
bind(EventBus.class).toInstance(eventBus);
42+
43+
install(new FactoryModuleBuilder().build(new TypeLiteral<Connection.Factory<Object>>() {
44+
}));
45+
46+
47+
bind(Source.SourceFactory.class).to(Source.SourceFactoryImpl.class);
48+
bind(CameraSource.FrameGrabberFactory.class).to(CameraSource.FrameGrabberFactoryImpl.class);
49+
install(new FactoryModuleBuilder()
50+
.implement(CameraSource.class, CameraSource.class)
51+
.build(CameraSource.Factory.class));
52+
install(new FactoryModuleBuilder()
53+
.implement(ImageFileSource.class, ImageFileSource.class)
54+
.build(ImageFileSource.Factory.class));
55+
install(new FactoryModuleBuilder()
56+
.implement(MultiImageFileSource.class, MultiImageFileSource.class)
57+
.build(MultiImageFileSource.Factory.class));
58+
59+
install(new FactoryModuleBuilder().build(ExceptionWitness.Factory.class));
60+
}
61+
62+
private void onSubscriberException(Throwable exception, SubscriberExceptionContext context) {
63+
eventBus.post(new UnexpectedThrowableEvent(exception, "An event subscriber threw an exception"));
64+
}
65+
66+
private void onThreadException(Thread thread, Throwable exception) {
67+
eventBus.post(new UnexpectedThrowableEvent(exception, thread + " threw an exception"));
68+
}
69+
}

core/src/main/java/edu/wpi/grip/core/Main.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package edu.wpi.grip.core;
22

33
import com.google.common.eventbus.EventBus;
4+
import com.google.common.eventbus.Subscribe;
5+
import com.google.inject.Guice;
6+
import com.google.inject.Injector;
7+
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
48
import edu.wpi.grip.core.operations.Operations;
59
import edu.wpi.grip.core.serialization.Project;
610
import edu.wpi.grip.generated.CVOperations;
711

12+
import javax.inject.Inject;
813
import java.io.File;
914
import java.io.IOException;
1015
import java.util.logging.*;
@@ -13,7 +18,21 @@
1318
* Main driver class for headless mode
1419
*/
1520
public class Main {
21+
22+
@Inject
23+
private Project project;
24+
@Inject
25+
private EventBus eventBus;
26+
@Inject
27+
private Logger logger;
28+
29+
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
1630
public static void main(String[] args) throws Exception {
31+
final Injector injector = Guice.createInjector(new GRIPCoreModule());
32+
injector.getInstance(Main.class).start(args);
33+
}
34+
35+
public void start(String[] args) throws IOException, InterruptedException {
1736
if (args.length != 1) {
1837
System.err.println("Usage: GRIP.jar project.grip");
1938
return;
@@ -41,16 +60,12 @@ public static void main(String[] args) throws Exception {
4160
throw new IllegalStateException(exception);
4261
}
4362

44-
final String projectPath = args[0];
45-
46-
final EventBus eventBus = new EventBus((exception, context) -> exception.printStackTrace());
47-
final Pipeline pipeline = new Pipeline(eventBus);
48-
final Palette palette = new Palette(eventBus);
49-
final Project project = new Project(eventBus, pipeline, palette);
5063

5164
Operations.addOperations(eventBus);
5265
CVOperations.addOperations(eventBus);
5366

67+
final String projectPath = args[0];
68+
5469
// Open a project from a .grip file specified on the command line
5570
project.open(new File(projectPath));
5671

@@ -59,4 +74,15 @@ public static void main(String[] args) throws Exception {
5974
Thread.sleep(Integer.MAX_VALUE);
6075
}
6176
}
77+
78+
/**
79+
* When an unexpected error happens in headless mode, print a stack trace and exit.
80+
*/
81+
@Subscribe
82+
public final void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
83+
logger.log(Level.SEVERE, "UnexpectedThrowableEvent", event.getThrowable());
84+
if (event.isFatal()) {
85+
System.exit(1);
86+
}
87+
}
6288
}

0 commit comments

Comments
 (0)