Skip to content

Commit e81267e

Browse files
committed
Merge pull request #246 from JLLeitschuh/fix/invalidSourceLoading
Fixes invalid sources preventing saves from being loaded
2 parents 2e503ad + 87b79c8 commit e81267e

File tree

17 files changed

+217
-79
lines changed

17 files changed

+217
-79
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.google.common.eventbus.Subscribe;
55
import com.google.inject.Guice;
66
import com.google.inject.Injector;
7+
import edu.wpi.grip.core.events.ExceptionClearedEvent;
8+
import edu.wpi.grip.core.events.ExceptionEvent;
79
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
810
import edu.wpi.grip.core.operations.Operations;
911
import edu.wpi.grip.core.serialization.Project;
@@ -78,6 +80,21 @@ public void start(String[] args) throws IOException, InterruptedException {
7880
}
7981
}
8082

83+
@Subscribe
84+
public final void onExceptionEvent(ExceptionEvent event) {
85+
Logger.getLogger(event.getOrigin().getClass().getName()).log(
86+
Level.SEVERE,
87+
event.getMessage(),
88+
// The throwable can be null
89+
event.getException().orElse(null)
90+
);
91+
}
92+
93+
@Subscribe
94+
public final void onExceptionClearedEvent(ExceptionClearedEvent event) {
95+
Logger.getLogger(event.getOrigin().getClass().getName()).log(Level.INFO, "Exception Cleared Event");
96+
}
97+
8198
/**
8299
* When an unexpected error happens in headless mode, print a stack trace and exit.
83100
*/

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
import edu.wpi.grip.core.sources.CameraSource;
55
import edu.wpi.grip.core.sources.ImageFileSource;
66
import edu.wpi.grip.core.sources.MultiImageFileSource;
7+
import edu.wpi.grip.core.util.ExceptionWitness;
78

89
import java.io.IOException;
910
import java.util.Optional;
1011
import java.util.Properties;
12+
import java.util.logging.Level;
13+
import java.util.logging.Logger;
1114

1215
/**
1316
* Base class for an input into the pipeline.
1417
*/
1518
public abstract class Source {
19+
private final Logger logger = Logger.getLogger(getClass().getName());
20+
private final ExceptionWitness exceptionWitness;
1621

1722
public static class SourceFactoryImpl implements SourceFactory {
1823
@Inject
@@ -35,6 +40,13 @@ public interface SourceFactory {
3540
Source create(Class<?> type, Properties properties) throws IOException;
3641
}
3742

43+
/**
44+
* @param exceptionWitnessFactory Factory to create the exceptionWitness
45+
*/
46+
protected Source(ExceptionWitness.Factory exceptionWitnessFactory) {
47+
this.exceptionWitness = exceptionWitnessFactory.create(this);
48+
}
49+
3850
/**
3951
* @return The name of this source. This is used by the GUI to distinguish different sources. For example,
4052
* {@link edu.wpi.grip.core.sources.ImageFileSource} returns the filename of the image.
@@ -62,4 +74,33 @@ public final OutputSocket[] getOutputSockets() {
6274
* serialization/deserialization.
6375
*/
6476
public abstract Properties getProperties();
77+
78+
protected ExceptionWitness getExceptionWitness() {
79+
return this.exceptionWitness;
80+
}
81+
82+
/**
83+
* Initializes the source. This should not try to handle initialization exceptions. Instead, the
84+
* {@link #initializeSafely()} should report the problem with initializing to the exception witness.
85+
*
86+
* @throws IOException
87+
*/
88+
public abstract void initialize() throws IOException;
89+
90+
/**
91+
* Initializes the source in a safe way such that the exception caused by initializing will be reported to
92+
* the {@link ExceptionWitness}.
93+
* This method should be used by the deserializer to ensure that a source that is invalid can display
94+
* this info to the UI and allow the user to modify the save file.
95+
*/
96+
public final void initializeSafely() {
97+
try {
98+
initialize();
99+
} catch (IOException e) {
100+
final String message = "Failed to initialize " + getClass().getSimpleName();
101+
logger.log(Level.WARNING, message, e);
102+
getExceptionWitness().flagException(e, message);
103+
}
104+
}
105+
65106
}

core/src/main/java/edu/wpi/grip/core/serialization/SourceConverter.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
1010
import edu.wpi.grip.core.Source;
1111
import edu.wpi.grip.core.events.SourceAddedEvent;
12-
import edu.wpi.grip.core.sources.LoadableSource;
1312

1413
import javax.inject.Inject;
1514
import java.io.IOException;
@@ -50,13 +49,16 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
5049
// ensure that other objects being deserialized (such as connections) don't try to access sources that
5150
// are in the process of loading.
5251
final Source source = sourceFactory.create(sourceClass, properties);
53-
if (source instanceof LoadableSource) {
54-
((LoadableSource) source).load();
55-
}
5652

5753
// Instead of returning the source, post it to the event bus so both the core and GUI classes know it
5854
// exists.
5955
eventBus.post(new SourceAddedEvent(source));
56+
57+
// Now that the source has been added it needs to be initialized
58+
// We do it safely here in case the source has changed in some way out
59+
// of our control. For example, if a webcam is no longer available.
60+
source.initializeSafely();
61+
6062
return null;
6163
} catch (IOException | RuntimeException e) {
6264
throw new ConversionException("Error deserializing source", e);

core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import edu.wpi.grip.core.events.SourceRemovedEvent;
1212
import edu.wpi.grip.core.events.StartedStoppedEvent;
1313
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
14+
import edu.wpi.grip.core.util.ExceptionWitness;
1415
import org.bytedeco.javacpp.opencv_core.Mat;
1516
import org.bytedeco.javacv.*;
1617

@@ -39,7 +40,7 @@ public class CameraSource extends Source implements StartStoppable {
3940

4041
private final static String DEVICE_NUMBER_PROPERTY = "deviceNumber";
4142
private final static String ADDRESS_PROPERTY = "address";
42-
private static Logger logger = Logger.getLogger(CameraSource.class.getName());
43+
private static Logger logger = Logger.getLogger(CameraSource.class.getName());
4344

4445
private final EventBus eventBus;
4546
private final String name;
@@ -98,8 +99,12 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException
9899
* @param deviceNumber The device number of the webcam
99100
*/
100101
@AssistedInject
101-
CameraSource(EventBus eventBus, FrameGrabberFactory grabberFactory, @Assisted int deviceNumber) throws IOException {
102-
this(eventBus, grabberFactory, createProperties(deviceNumber));
102+
CameraSource(
103+
final EventBus eventBus,
104+
final FrameGrabberFactory grabberFactory,
105+
final ExceptionWitness.Factory exceptionWitnessFactory,
106+
@Assisted final int deviceNumber) throws IOException {
107+
this(eventBus, grabberFactory, exceptionWitnessFactory, createProperties(deviceNumber));
103108
}
104109

105110
/**
@@ -109,15 +114,24 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException
109114
* @param address A URL to stream video from an IP camera
110115
*/
111116
@AssistedInject
112-
CameraSource(EventBus eventBus, FrameGrabberFactory grabberFactory, @Assisted String address) throws IOException {
113-
this(eventBus, grabberFactory, createProperties(address));
117+
CameraSource(
118+
final EventBus eventBus,
119+
final FrameGrabberFactory grabberFactory,
120+
final ExceptionWitness.Factory exceptionWitnessFactory,
121+
@Assisted final String address) throws IOException {
122+
this(eventBus, grabberFactory, exceptionWitnessFactory, createProperties(address));
114123
}
115124

116125
/**
117126
* Used for serialization
118127
*/
119128
@AssistedInject
120-
CameraSource(EventBus eventBus, FrameGrabberFactory grabberFactory, @Assisted Properties properties) throws MalformedURLException {
129+
CameraSource(
130+
final EventBus eventBus,
131+
final FrameGrabberFactory grabberFactory,
132+
final ExceptionWitness.Factory exceptionWitnessFactory,
133+
@Assisted final Properties properties) throws MalformedURLException {
134+
super(exceptionWitnessFactory);
121135
this.frameThread = Optional.empty();
122136
this.eventBus = eventBus;
123137
this.frameOutputSocket = new OutputSocket<>(eventBus, imageOutputHint);
@@ -155,6 +169,11 @@ public Properties getProperties() {
155169
return this.properties;
156170
}
157171

172+
@Override
173+
public void initialize() throws IOException {
174+
start();
175+
}
176+
158177
/**
159178
* Starts the video capture from this frame grabber.
160179
*/
@@ -196,6 +215,7 @@ public void start() throws IOException, IllegalStateException {
196215
final long elapsedTime = thisMoment - lastFrame;
197216
if (elapsedTime != 0) frameRateOutputSocket.setValue(1e9 / elapsedTime);
198217
lastFrame = thisMoment;
218+
getExceptionWitness().clearException();
199219
}
200220
}, "Camera");
201221

core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import edu.wpi.grip.core.OutputSocket;
99
import edu.wpi.grip.core.SocketHint;
1010
import edu.wpi.grip.core.SocketHints;
11+
import edu.wpi.grip.core.Source;
12+
import edu.wpi.grip.core.util.ExceptionWitness;
1113
import edu.wpi.grip.core.util.ImageLoadingUtility;
1214
import org.bytedeco.javacpp.opencv_core.Mat;
1315
import org.bytedeco.javacpp.opencv_imgcodecs;
@@ -25,7 +27,7 @@
2527
* Provides a way to generate a {@link Mat} from an image on the filesystem.
2628
*/
2729
@XStreamAlias(value = "grip:ImageFile")
28-
public final class ImageFileSource extends LoadableSource {
30+
public final class ImageFileSource extends Source {
2931

3032
private static final String PATH_PROPERTY = "path";
3133

@@ -38,25 +40,37 @@ public final class ImageFileSource extends LoadableSource {
3840

3941
public interface Factory {
4042
ImageFileSource create(File file);
43+
4144
ImageFileSource create(Properties properties);
4245
}
4346

4447
/**
45-
* @param eventBus The event bus for the pipeline.
46-
* @param file The location on the file system where the image exists.
48+
* @param eventBus The event bus for the pipeline.
49+
* @param exceptionWitnessFactory Factory to create the exceptionWitness
50+
* @param file The location on the file system where the image exists.
4751
*/
4852
@AssistedInject
49-
ImageFileSource(EventBus eventBus, @Assisted File file) {
50-
this(eventBus, URLDecoder.decode(Paths.get(file.toURI()).toString()));
53+
ImageFileSource(
54+
final EventBus eventBus,
55+
final ExceptionWitness.Factory exceptionWitnessFactory,
56+
@Assisted final File file) {
57+
this(eventBus, exceptionWitnessFactory, URLDecoder.decode(Paths.get(file.toURI()).toString()));
5158
}
5259

5360

5461
@AssistedInject
55-
ImageFileSource(EventBus eventBus, @Assisted Properties properties) {
56-
this(eventBus, properties.getProperty(PATH_PROPERTY));
62+
ImageFileSource(
63+
final EventBus eventBus,
64+
final ExceptionWitness.Factory exceptionWitnessFactory,
65+
@Assisted final Properties properties) {
66+
this(eventBus, exceptionWitnessFactory, properties.getProperty(PATH_PROPERTY));
5767
}
5868

59-
private ImageFileSource(EventBus eventBus, String path) {
69+
private ImageFileSource(
70+
final EventBus eventBus,
71+
final ExceptionWitness.Factory exceptionWitnessFactory,
72+
final String path) {
73+
super(exceptionWitnessFactory);
6074
this.eventBus = checkNotNull(eventBus, "Event Bus was null.");
6175
this.path = checkNotNull(path, "Path can not be null");
6276
this.name = Files.getNameWithoutExtension(this.path);
@@ -65,10 +79,11 @@ private ImageFileSource(EventBus eventBus, String path) {
6579

6680
/**
6781
* Performs the loading of the image from the file system
82+
*
6883
* @throws IOException If the image fails to load from the filesystem
6984
*/
7085
@Override
71-
public void load() throws IOException {
86+
public void initialize() throws IOException {
7287
this.loadImage(path);
7388
this.loaded = true;
7489
}
@@ -81,7 +96,7 @@ public String getName() {
8196

8297
@Override
8398
public OutputSocket[] createOutputSockets() {
84-
if(!loaded) {
99+
if (!loaded) {
85100
throw new IllegalStateException("The images were never loaded from the filesystem. Must call `loadImage` first.");
86101
}
87102
return new OutputSocket[]{this.outputSocket};

core/src/main/java/edu/wpi/grip/core/sources/LoadableSource.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.google.inject.assistedinject.AssistedInject;
88
import com.thoughtworks.xstream.annotations.XStreamAlias;
99
import edu.wpi.grip.core.*;
10+
import edu.wpi.grip.core.util.ExceptionWitness;
1011
import edu.wpi.grip.core.util.ImageLoadingUtility;
1112
import org.bytedeco.javacpp.opencv_core.Mat;
1213

@@ -27,7 +28,7 @@
2728
* {@link MultiImageFileSource#previous()}
2829
*/
2930
@XStreamAlias(value = "grip:MultiImageFile")
30-
public final class MultiImageFileSource extends LoadableSource implements PreviousNext {
31+
public final class MultiImageFileSource extends Source implements PreviousNext {
3132
private static final String INDEX_PROPERTY = "index";
3233
private static final String SIZE_PROPERTY = "numImages";
3334

@@ -47,39 +48,53 @@ public interface Factory {
4748
}
4849

4950
/**
50-
* @param eventBus The event bus.
51-
* @param files A list of files to be loaded.
52-
* @param index The index to use as the first file that is in the socket.
53-
* @throws IOException If the source fails to load any of the images
51+
* @param eventBus The event bus.
52+
* @param exceptionWitnessFactory Factory to create the exceptionWitness
53+
* @param files A list of files to be loaded.
54+
* @param index The index to use as the first file that is in the socket.
5455
*/
5556
@AssistedInject
56-
MultiImageFileSource(final EventBus eventBus, @Assisted final List<File> files, @Assisted int index) throws IOException {
57-
this(eventBus, files.stream()
58-
.map(file -> URLDecoder.decode(Paths.get(file.toURI()).toString()))
59-
.collect(Collectors.toList()).toArray(new String[files.size()]), index);
57+
MultiImageFileSource(
58+
final EventBus eventBus,
59+
final ExceptionWitness.Factory exceptionWitnessFactory,
60+
@Assisted final List<File> files,
61+
@Assisted final int index) {
62+
this(eventBus, exceptionWitnessFactory, files.stream()
63+
.map(file -> URLDecoder.decode(Paths.get(file.toURI()).toString()))
64+
.collect(Collectors.toList()).toArray(new String[files.size()]), index);
6065
}
6166

6267
@AssistedInject
63-
MultiImageFileSource(final EventBus eventBus, @Assisted final List<File> files) throws IOException {
64-
this(eventBus, files, 0);
68+
MultiImageFileSource(
69+
final EventBus eventBus,
70+
final ExceptionWitness.Factory exceptionWitnessFactory,
71+
@Assisted final List<File> files) {
72+
this(eventBus, exceptionWitnessFactory, files, 0);
6573
}
6674

6775
/**
6876
* Used only for serialization
6977
*/
7078
@AssistedInject
71-
MultiImageFileSource(final EventBus eventBus, @Assisted Properties properties) throws IOException {
72-
this(eventBus, pathsFromProperties(properties), indexFromProperties(properties));
73-
}
74-
75-
private MultiImageFileSource(final EventBus eventBus, final String[] paths, int index) throws IOException {
79+
MultiImageFileSource(final EventBus eventBus,
80+
final ExceptionWitness.Factory exceptionWitnessFactory,
81+
@Assisted final Properties properties) {
82+
this(eventBus, exceptionWitnessFactory, pathsFromProperties(properties), indexFromProperties(properties));
83+
}
84+
85+
private MultiImageFileSource(
86+
final EventBus eventBus,
87+
final ExceptionWitness.Factory exceptionWitnessFactory,
88+
final String[] paths,
89+
final int index) {
90+
super(exceptionWitnessFactory);
7691
this.outputSocket = new OutputSocket(eventBus, imageOutputHint);
7792
this.index = new AtomicInteger(checkElementIndex(index, paths.length, "File List Index"));
7893
this.paths = Arrays.asList(paths);
7994
}
8095

8196
@Override
82-
public void load() throws IOException {
97+
public void initialize() throws IOException {
8398
this.images = createImagesArray(this.paths);
8499
this.outputSocket.setValue(addIndexAndGetImageByOffset(0));
85100
}

0 commit comments

Comments
 (0)