From f3a0f53995001f5455b7d9589d6c849b42d996f1 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Tue, 25 Oct 2016 23:00:20 -0400
Subject: [PATCH 1/9] Add versioning
If a save file cannot be loaded, no changes happen to the pipeline and the user is notified. This will happen for any save file, pre-versioning or otherwise.
When saving a loaded project, the original version string will be changed to the version of GRIP that saved it. Pre-versioning save files will also be upgraded to the versioned format and have the current version of GRIP
---
build.gradle | 1 +
.../edu/wpi/grip/core/GripCoreModule.java | 4 ++
.../main/java/edu/wpi/grip/core/Palette.java | 58 +++++++++++++++++--
.../edu/wpi/grip/core/VersionManager.java | 38 ++++++++++++
.../IncompatibleVersionException.java | 17 ++++++
.../core/exception/InvalidSaveException.java | 16 +++++
.../grip/core/http/HttpPipelineSwitcher.java | 13 ++++-
.../operations/composite/BlurOperation.java | 8 +--
.../composite/ConvexHullsOperation.java | 12 ++--
.../composite/DesaturateOperation.java | 4 +-
.../composite/DistanceTransformOperation.java | 8 +--
.../composite/FilterContoursOperation.java | 28 ++++-----
.../composite/FilterLinesOperation.java | 8 +--
.../composite/FindBlobsOperation.java | 10 ++--
.../composite/FindContoursOperation.java | 6 +-
.../composite/FindLinesOperation.java | 4 +-
.../composite/HSLThresholdOperation.java | 10 ++--
.../composite/HSVThresholdOperation.java | 10 ++--
.../operations/composite/MaskOperation.java | 6 +-
.../composite/NormalizeOperation.java | 10 ++--
.../composite/PublishVideoOperation.java | 4 +-
.../composite/RGBThresholdOperation.java | 10 ++--
.../operations/composite/ResizeOperation.java | 13 +++--
.../composite/SaveImageOperation.java | 12 ++--
.../operations/composite/SwitchOperation.java | 2 +-
.../operations/composite/ThresholdMoving.java | 8 ++-
.../operations/composite/ValveOperation.java | 2 +-
.../composite/WatershedOperation.java | 6 +-
.../network/NetworkPublishOperation.java | 4 +-
.../operations/opencv/MatFieldAccessor.java | 16 ++---
.../core/operations/opencv/MinMaxLoc.java | 12 ++--
.../operations/opencv/NewPointOperation.java | 6 +-
.../operations/opencv/NewSizeOperation.java | 6 +-
.../wpi/grip/core/serialization/Project.java | 38 ++++++++++--
.../grip/core/serialization/ProjectModel.java | 37 ++++++++++++
.../core/serialization/SocketConverter.java | 36 ++++++++----
.../core/serialization/VersionConverter.java | 31 ++++++++++
.../wpi/grip/core/sockets/InputSocket.java | 16 ++++-
.../grip/core/sockets/InputSocketImpl.java | 15 +++--
.../wpi/grip/core/sockets/OutputSocket.java | 12 +++-
.../grip/core/sockets/OutputSocketImpl.java | 15 +++--
.../edu/wpi/grip/core/sockets/Socket.java | 13 +++++
.../edu/wpi/grip/core/sockets/SocketImpl.java | 10 +++-
.../wpi/grip/core/sources/CameraSource.java | 4 +-
.../edu/wpi/grip/core/sources/HttpSource.java | 2 +-
.../grip/core/sources/ImageFileSource.java | 2 +-
.../core/sources/MultiImageFileSource.java | 2 +-
.../core/sources/NetworkTableEntrySource.java | 2 +-
.../edu/wpi/grip/core/CoreSanityTest.java | 2 +
.../grip/core/sockets/MockInputSocket.java | 2 +-
.../grip/core/sockets/MockOutputSocket.java | 2 +-
.../edu/wpi/grip/ui/MainWindowController.java | 14 ++++-
.../InputSocketControllerFactoryTest.java | 1 +
53 files changed, 477 insertions(+), 151 deletions(-)
create mode 100644 core/src/main/java/edu/wpi/grip/core/VersionManager.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/exception/IncompatibleVersionException.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/exception/InvalidSaveException.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/serialization/ProjectModel.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/serialization/VersionConverter.java
diff --git a/build.gradle b/build.gradle
index 081223afa5..bef96e15eb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -252,6 +252,7 @@ project(":core") {
compile group: 'org.bytedeco.javacpp-presets', name: 'videoinput', version: '0.200-1.1', classifier: os
compile group: 'org.python', name: 'jython', version: '2.7.0'
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.9'
+ compile group: 'com.github.zafarkhaja', name: 'java-semver', version: '0.9.0'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'
compile group: 'com.google.guava', name: 'guava', version: '19.0'
compile group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
diff --git a/core/src/main/java/edu/wpi/grip/core/GripCoreModule.java b/core/src/main/java/edu/wpi/grip/core/GripCoreModule.java
index 314e77170a..d77b65bb90 100644
--- a/core/src/main/java/edu/wpi/grip/core/GripCoreModule.java
+++ b/core/src/main/java/edu/wpi/grip/core/GripCoreModule.java
@@ -15,6 +15,7 @@
import edu.wpi.grip.core.util.ExceptionWitness;
import edu.wpi.grip.core.util.GripMode;
+import com.github.zafarkhaja.semver.Version;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import com.google.inject.AbstractModule;
@@ -125,6 +126,9 @@ public void hear(TypeLiteral type, TypeEncounter encounter) {
// Allow for just injecting the settings provider, instead of the whole pipeline
bind(SettingsProvider.class).to(Pipeline.class);
+ // Bind the current GRIP version.
+ bind(Version.class).toInstance(VersionManager.CURRENT_VERSION);
+
install(new FactoryModuleBuilder().build(new TypeLiteral>() {
}));
diff --git a/core/src/main/java/edu/wpi/grip/core/Palette.java b/core/src/main/java/edu/wpi/grip/core/Palette.java
index 1768fc5e6a..3288d45a1c 100644
--- a/core/src/main/java/edu/wpi/grip/core/Palette.java
+++ b/core/src/main/java/edu/wpi/grip/core/Palette.java
@@ -1,13 +1,18 @@
package edu.wpi.grip.core;
import edu.wpi.grip.core.events.OperationAddedEvent;
+import edu.wpi.grip.core.sockets.Socket;
+import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.inject.Singleton;
@@ -24,10 +29,54 @@ public class Palette {
@Subscribe
public void onOperationAdded(OperationAddedEvent event) {
- final OperationMetaData operation = event.getOperation();
- map(operation.getDescription().name(), operation);
- for (String alias : operation.getDescription().aliases()) {
- map(alias, operation);
+ final OperationMetaData operationData = event.getOperation();
+ map(operationData.getDescription().name(), operationData);
+ for (String alias : operationData.getDescription().aliases()) {
+ map(alias, operationData);
+ }
+ // Validate that every input and output socket has a unique name and UID
+ Operation operation = operationData.getOperationSupplier().get();
+ try {
+ final List extends Socket> sockets = new ImmutableList.Builder()
+ .addAll(operation.getInputSockets())
+ .addAll(operation.getOutputSockets())
+ .build();
+ checkDuplicates(
+ operationData,
+ "input socket names",
+ operation.getInputSockets(), s -> s.getSocketHint().getIdentifier()
+ );
+ checkDuplicates(
+ operationData,
+ "output socket names",
+ operation.getOutputSockets(), s -> s.getSocketHint().getIdentifier()
+ );
+ checkDuplicates(operationData, "socket IDs", sockets, Socket::getUid);
+ } finally {
+ operation.cleanUp();
+ }
+ }
+
+ private static void checkDuplicates(OperationMetaData operationMetaData,
+ String type,
+ List list,
+ Function extractionFunction) {
+ List duplicates = list.stream()
+ .map(extractionFunction)
+ .collect(Collectors.toList());
+ list.stream()
+ .map(extractionFunction)
+ .distinct()
+ .forEach(duplicates::remove);
+ if (!duplicates.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Duplicate %s found in operation %s: %s",
+ type,
+ operationMetaData.getDescription().name(),
+ duplicates
+ )
+ );
}
}
@@ -36,6 +85,7 @@ public void onOperationAdded(OperationAddedEvent event) {
*
* @param key The key the operation should be mapped to
* @param operation The operation to map the key to
+ *
* @throws IllegalArgumentException if the key is already in the {@link #operations} map.
*/
private void map(String key, OperationMetaData operation) {
diff --git a/core/src/main/java/edu/wpi/grip/core/VersionManager.java b/core/src/main/java/edu/wpi/grip/core/VersionManager.java
new file mode 100644
index 0000000000..a512ec70d9
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/VersionManager.java
@@ -0,0 +1,38 @@
+package edu.wpi.grip.core;
+
+import edu.wpi.grip.core.exception.IncompatibleVersionException;
+
+import com.github.zafarkhaja.semver.Version;
+
+/**
+ * Manager for GRIP versions.
+ */
+public final class VersionManager {
+
+ /**
+ * The final release of GRIP without versioned saves.
+ */
+ public static final Version LAST_UNVERSIONED_RELEASE = Version.valueOf("1.5.0");
+
+ /**
+ * The current version of GRIP.
+ */
+ public static final Version CURRENT_VERSION = Version.valueOf("2.0.0-beta");
+
+ /**
+ * Checks compatibility between two versions of GRIP.
+ *
+ * @param current the current version of GRIP
+ * @param check the version to check
+ *
+ * @throws IncompatibleVersionException if the versions are incompatible
+ */
+ public static void checkVersionCompatibility(Version current, Version check)
+ throws IncompatibleVersionException {
+ if (check.getMajorVersion() > current.getMajorVersion()
+ || check.getMinorVersion() > current.getMinorVersion()) {
+ throw new IncompatibleVersionException("Incompatible future version: " + check);
+ }
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/exception/IncompatibleVersionException.java b/core/src/main/java/edu/wpi/grip/core/exception/IncompatibleVersionException.java
new file mode 100644
index 0000000000..0a95532cc2
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/exception/IncompatibleVersionException.java
@@ -0,0 +1,17 @@
+package edu.wpi.grip.core.exception;
+
+/**
+ * An exception thrown when trying to load a saved project created in an incompatible version
+ * of GRIP.
+ */
+public class IncompatibleVersionException extends InvalidSaveException {
+
+ public IncompatibleVersionException(String message) {
+ super(message);
+ }
+
+ public IncompatibleVersionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/exception/InvalidSaveException.java b/core/src/main/java/edu/wpi/grip/core/exception/InvalidSaveException.java
new file mode 100644
index 0000000000..3e33f3abbb
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/exception/InvalidSaveException.java
@@ -0,0 +1,16 @@
+package edu.wpi.grip.core.exception;
+
+/**
+ * An exception thrown when trying to load an invalid saved project.
+ */
+public class InvalidSaveException extends GripException {
+
+ public InvalidSaveException(String message) {
+ super(message);
+ }
+
+ public InvalidSaveException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/http/HttpPipelineSwitcher.java b/core/src/main/java/edu/wpi/grip/core/http/HttpPipelineSwitcher.java
index d349427193..004c6c39e0 100644
--- a/core/src/main/java/edu/wpi/grip/core/http/HttpPipelineSwitcher.java
+++ b/core/src/main/java/edu/wpi/grip/core/http/HttpPipelineSwitcher.java
@@ -1,5 +1,6 @@
package edu.wpi.grip.core.http;
+import edu.wpi.grip.core.exception.InvalidSaveException;
import edu.wpi.grip.core.serialization.Project;
import edu.wpi.grip.core.util.GripMode;
@@ -43,9 +44,15 @@ protected void handleIfPassed(String target,
}
switch (mode) {
case HEADLESS:
- project.open(new String(IOUtils.toByteArray(request.getInputStream()), "UTF-8"));
- response.setStatus(HttpServletResponse.SC_CREATED);
- baseRequest.setHandled(true);
+ try {
+ project.open(new String(IOUtils.toByteArray(request.getInputStream()), "UTF-8"));
+ response.setStatus(HttpServletResponse.SC_CREATED);
+ baseRequest.setHandled(true);
+ } catch (InvalidSaveException e) {
+ // 403 - Forbidden if given an invalid save
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ baseRequest.setHandled(true);
+ }
break;
case GUI:
// Don't run in GUI mode, it doesn't make much sense and can easily deadlock if pipelines
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java
index 0baefa312d..da1f49379e 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java
@@ -47,11 +47,11 @@ public class BlurOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public BlurOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.typeSocket = inputSocketFactory.create(typeHint);
- this.radiusSocket = inputSocketFactory.create(radiusHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.typeSocket = inputSocketFactory.create(typeHint, "blur-type");
+ this.radiusSocket = inputSocketFactory.create(radiusHint, "blur-radius");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java
index 583430fa50..331facbdf5 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java
@@ -26,9 +26,11 @@ public class ConvexHullsOperation implements Operation {
.category(OperationDescription.Category.FEATURE_DETECTION)
.build();
- private final SocketHint contoursHint = new SocketHint.Builder<>(ContoursReport
- .class)
- .identifier("Contours").initialValueSupplier(ContoursReport::new).build();
+ private final SocketHint contoursHint =
+ new SocketHint.Builder<>(ContoursReport.class)
+ .identifier("Contours")
+ .initialValueSupplier(ContoursReport::new)
+ .build();
private final InputSocket inputSocket;
private final OutputSocket outputSocket;
@@ -36,9 +38,9 @@ public class ConvexHullsOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public ConvexHullsOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(contoursHint);
+ this.inputSocket = inputSocketFactory.create(contoursHint, "contours");
- this.outputSocket = outputSocketFactory.create(contoursHint);
+ this.outputSocket = outputSocketFactory.create(contoursHint, "hulls");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java
index 49284ed13e..9023de3b17 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java
@@ -39,8 +39,8 @@ public class DesaturateOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public DesaturateOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java
index f720571c9c..f01238cbd9 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java
@@ -46,11 +46,11 @@ public class DistanceTransformOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public DistanceTransformOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.srcSocket = inputSocketFactory.create(srcHint);
- this.typeSocket = inputSocketFactory.create(typeHint);
- this.maskSizeSocket = inputSocketFactory.create(maskSizeHint);
+ this.srcSocket = inputSocketFactory.create(srcHint, "source-image");
+ this.typeSocket = inputSocketFactory.create(typeHint, "transform-type");
+ this.maskSizeSocket = inputSocketFactory.create(maskSizeHint, "mask-size");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
index 433ccd06cb..07a40d777a 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
@@ -95,20 +95,20 @@ public class FilterContoursOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public FilterContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.contoursSocket = inputSocketFactory.create(contoursHint);
- this.minAreaSocket = inputSocketFactory.create(minAreaHint);
- this.minPerimeterSocket = inputSocketFactory.create(minPerimeterHint);
- this.minWidthSocket = inputSocketFactory.create(minWidthHint);
- this.maxWidthSocket = inputSocketFactory.create(maxWidthHint);
- this.minHeightSocket = inputSocketFactory.create(minHeightHint);
- this.maxHeightSocket = inputSocketFactory.create(maxHeightHint);
- this.soliditySocket = inputSocketFactory.create(solidityHint);
- this.minVertexSocket = inputSocketFactory.create(minVertexHint);
- this.maxVertexSocket = inputSocketFactory.create(maxVertexHint);
- this.minRatioSocket = inputSocketFactory.create(minRatioHint);
- this.maxRatioSocket = inputSocketFactory.create(maxRatioHint);
-
- this.outputSocket = outputSocketFactory.create(contoursHint);
+ this.contoursSocket = inputSocketFactory.create(contoursHint, "contours");
+ this.minAreaSocket = inputSocketFactory.create(minAreaHint, "min-area");
+ this.minPerimeterSocket = inputSocketFactory.create(minPerimeterHint, "min-perimeter");
+ this.minWidthSocket = inputSocketFactory.create(minWidthHint, "min-width");
+ this.maxWidthSocket = inputSocketFactory.create(maxWidthHint, "max-width");
+ this.minHeightSocket = inputSocketFactory.create(minHeightHint, "min-height");
+ this.maxHeightSocket = inputSocketFactory.create(maxHeightHint, "max-height");
+ this.soliditySocket = inputSocketFactory.create(solidityHint, "solidity");
+ this.minVertexSocket = inputSocketFactory.create(minVertexHint, "min-vertices");
+ this.maxVertexSocket = inputSocketFactory.create(maxVertexHint, "max-vertices");
+ this.minRatioSocket = inputSocketFactory.create(minRatioHint, "min-ratio");
+ this.maxRatioSocket = inputSocketFactory.create(maxRatioHint, "max-ratio");
+
+ this.outputSocket = outputSocketFactory.create(contoursHint, "filtered-contours");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
index 1f4e9f46f8..5067e65b6c 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
@@ -50,11 +50,11 @@ public class FilterLinesOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public FilterLinesOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.minLengthSocket = inputSocketFactory.create(minLengthHint);
- this.angleSocket = inputSocketFactory.create(angleHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "lines");
+ this.minLengthSocket = inputSocketFactory.create(minLengthHint, "min-length");
+ this.angleSocket = inputSocketFactory.create(angleHint, "angle");
- this.linesOutputSocket = outputSocketFactory.create(outputHint);
+ this.linesOutputSocket = outputSocketFactory.create(outputHint, "filtered-lines");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
index 24dc7116ed..d17e4f245b 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
@@ -54,12 +54,12 @@ public class FindBlobsOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public FindBlobsOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.minAreaSocket = inputSocketFactory.create(minAreaHint);
- this.circularitySocket = inputSocketFactory.create(circularityHint);
- this.colorSocket = inputSocketFactory.create(colorHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.minAreaSocket = inputSocketFactory.create(minAreaHint, "min-area");
+ this.circularitySocket = inputSocketFactory.create(circularityHint, "circularity");
+ this.colorSocket = inputSocketFactory.create(colorHint, "find-dark-blobs");
- this.outputSocket = outputSocketFactory.create(blobsHint);
+ this.outputSocket = outputSocketFactory.create(blobsHint, "found-blobs");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java
index 7abab98167..ba5bcb399a 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java
@@ -52,10 +52,10 @@ public class FindContoursOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public FindContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.externalSocket = inputSocketFactory.create(externalHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.externalSocket = inputSocketFactory.create(externalHint, "find-external-only");
- this.contoursSocket = outputSocketFactory.create(contoursHint);
+ this.contoursSocket = outputSocketFactory.create(contoursHint, "found-contours");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java
index b05ea36034..e58dbca94e 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java
@@ -44,8 +44,8 @@ public class FindLinesOperation implements Operation {
public FindLinesOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.linesReportSocket = outputSocketFactory.create(linesHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.linesReportSocket = outputSocketFactory.create(linesHint, "found-lines");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
index d8de90c6ed..5b7aff737b 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
@@ -56,12 +56,12 @@ public class HSLThresholdOperation extends ThresholdOperation {
@SuppressWarnings("JavadocMethod")
public HSLThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.hueSocket = inputSocketFactory.create(hueHint);
- this.saturationSocket = inputSocketFactory.create(saturationHint);
- this.luminanceSocket = inputSocketFactory.create(luminanceHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.hueSocket = inputSocketFactory.create(hueHint, "hue");
+ this.saturationSocket = inputSocketFactory.create(saturationHint, "saturation");
+ this.luminanceSocket = inputSocketFactory.create(luminanceHint, "luminance");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
index c7e8700b95..9019a6deb5 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
@@ -56,12 +56,12 @@ public class HSVThresholdOperation extends ThresholdOperation {
@SuppressWarnings("JavadocMethod")
public HSVThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.hueSocket = inputSocketFactory.create(hueHint);
- this.saturationSocket = inputSocketFactory.create(saturationHint);
- this.valueSocket = inputSocketFactory.create(valueHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.hueSocket = inputSocketFactory.create(hueHint, "hue");
+ this.saturationSocket = inputSocketFactory.create(saturationHint, "saturation");
+ this.valueSocket = inputSocketFactory.create(valueHint, "value");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java
index 6521f5947a..772089537b 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java
@@ -42,10 +42,10 @@ public class MaskOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public MaskOperation(InputSocket.Factory inputSocketFactory,
OutputSocket.Factory outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.maskSocket = inputSocketFactory.create(maskHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.maskSocket = inputSocketFactory.create(maskHint, "mask-image");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java
index 6b98c5a979..44ae81ab96 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java
@@ -47,12 +47,12 @@ public class NormalizeOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public NormalizeOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.srcSocket = inputSocketFactory.create(srcHint);
- this.typeSocket = inputSocketFactory.create(typeHint);
- this.alphaSocket = inputSocketFactory.create(aHint);
- this.betaSocket = inputSocketFactory.create(bHint);
+ this.srcSocket = inputSocketFactory.create(srcHint, "source-image");
+ this.typeSocket = inputSocketFactory.create(typeHint, "normalize-type");
+ this.alphaSocket = inputSocketFactory.create(aHint, "alpha");
+ this.betaSocket = inputSocketFactory.create(bHint, "beta");
- this.outputSocket = outputSocketFactory.create(dstHint);
+ this.outputSocket = outputSocketFactory.create(dstHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java
index a30153bb9b..612a4fd9df 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java
@@ -142,9 +142,9 @@ public PublishVideoOperation(InputSocket.Factory inputSocketFactory) {
throw new IllegalStateException("Only one instance of PublishVideoOperation may exist");
}
this.inputSocket = inputSocketFactory.create(SocketHints.Inputs.createMatSocketHint("Image",
- false));
+ false), "source-image");
this.qualitySocket = inputSocketFactory.create(SocketHints.Inputs
- .createNumberSliderSocketHint("Quality", 80, 0, 100));
+ .createNumberSliderSocketHint("Quality", 80, 0, 100), "image-quality");
numSteps++;
serverThread = new Thread(runServer, "Camera Server");
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
index 965d4fea68..755cee5898 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
@@ -54,12 +54,12 @@ public class RGBThresholdOperation extends ThresholdOperation {
@SuppressWarnings("JavadocMethod")
public RGBThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(inputHint);
- this.redSocket = inputSocketFactory.create(redHint);
- this.greenSocket = inputSocketFactory.create(greenHint);
- this.blueSocket = inputSocketFactory.create(blueHint);
+ this.inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ this.redSocket = inputSocketFactory.create(redHint, "red");
+ this.greenSocket = inputSocketFactory.create(greenHint, "green");
+ this.blueSocket = inputSocketFactory.create(blueHint, "blue");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java
index dcf66f3b6b..9594b330a1 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java
@@ -46,16 +46,17 @@ public class ResizeOperation implements Operation {
public ResizeOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
this.inputSocket = inputSocketFactory.create(SocketHints.Inputs
- .createMatSocketHint("Input", false));
+ .createMatSocketHint("Input", false), "source-image");
this.widthSocket = inputSocketFactory.create(SocketHints.Inputs
- .createNumberSpinnerSocketHint("Width", 640));
+ .createNumberSpinnerSocketHint("Width", 640), "new-width");
this.heightSocket = inputSocketFactory.create(SocketHints.Inputs
- .createNumberSpinnerSocketHint("Height", 480));
- this.interpolationSocket = inputSocketFactory
- .create(SocketHints.createEnumSocketHint("Interpolation", Interpolation.CUBIC));
+ .createNumberSpinnerSocketHint("Height", 480), "new-height");
+ this.interpolationSocket = inputSocketFactory.create(
+ SocketHints.createEnumSocketHint("Interpolation", Interpolation.CUBIC), "interp-type"
+ );
this.outputSocket = outputSocketFactory.create(SocketHints.Outputs
- .createMatSocketHint("Output"));
+ .createMatSocketHint("Output"), "resized-image");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/SaveImageOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/SaveImageOperation.java
index 37b179481a..72228bec03 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/SaveImageOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/SaveImageOperation.java
@@ -79,13 +79,13 @@ public SaveImageOperation(InputSocket.Factory inputSocketFactory,
FileManager fileManager) {
this.fileManager = fileManager;
- inputSocket = inputSocketFactory.create(inputHint);
- fileTypesSocket = inputSocketFactory.create(fileTypeHint);
- qualitySocket = inputSocketFactory.create(qualityHint);
- periodSocket = inputSocketFactory.create(periodHint);
- activeSocket = inputSocketFactory.create(activeHint);
+ inputSocket = inputSocketFactory.create(inputHint, "source-image");
+ fileTypesSocket = inputSocketFactory.create(fileTypeHint, "file-type");
+ qualitySocket = inputSocketFactory.create(qualityHint, "compression-quality");
+ periodSocket = inputSocketFactory.create(periodHint, "period");
+ activeSocket = inputSocketFactory.create(activeHint, "enable-saving");
- outputSocket = outputSocketFactory.create(outputHint);
+ outputSocket = outputSocketFactory.create(outputHint, "passthrough");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java
index f3176ece20..8a9c42b01e 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java
@@ -40,7 +40,7 @@ public SwitchOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Fact
final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(inputSocketFactory,
outputSocketFactory);
- this.switcherSocket = inputSocketFactory.create(switcherHint);
+ this.switcherSocket = inputSocketFactory.create(switcherHint, "switch");
this.inputSocket1 = linkedSocketHint.linkedInputSocket("If True");
this.inputSocket2 = linkedSocketHint.linkedInputSocket("If False");
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java
index 6f11218a7c..9a7a8a6df6 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java
@@ -30,8 +30,12 @@ public class ThresholdMoving implements Operation {
@SuppressWarnings("JavadocMethod")
public ThresholdMoving(InputSocket.Factory inputSocketFactory,
OutputSocket.Factory outputSocketFactory) {
- imageSocket = inputSocketFactory.create(SocketHints.Inputs.createMatSocketHint("image", false));
- outputSocket = outputSocketFactory.create(SocketHints.Outputs.createMatSocketHint("moved"));
+ imageSocket = inputSocketFactory.create(
+ SocketHints.Inputs.createMatSocketHint("image", false), "source-image"
+ );
+ outputSocket = outputSocketFactory.create(
+ SocketHints.Outputs.createMatSocketHint("moved"), "diff-image"
+ );
lastImage = new Mat();
}
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java
index aff111cc08..4f0e1a9b7d 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java
@@ -35,7 +35,7 @@ public ValveOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Facto
outputSocketFactory);
final SocketHint switcherHint = SocketHints.createBooleanSocketHint("valve", true);
- this.switcherSocket = inputSocketFactory.create(switcherHint);
+ this.switcherSocket = inputSocketFactory.create(switcherHint, "switcher");
this.inputSocket = linkedSocketHint.linkedInputSocket("Input");
this.outputSocket = linkedSocketHint.linkedOutputSocket("Output");
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java
index 13181916e2..11ff0e9e84 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java
@@ -67,9 +67,9 @@ public class WatershedOperation implements Operation {
@SuppressWarnings("JavadocMethod")
public WatershedOperation(InputSocket.Factory inputSocketFactory,
OutputSocket.Factory outputSocketFactory) {
- srcSocket = inputSocketFactory.create(srcHint);
- contoursSocket = inputSocketFactory.create(contoursHint);
- outputSocket = outputSocketFactory.create(outputHint);
+ srcSocket = inputSocketFactory.create(srcHint, "source-image");
+ contoursSocket = inputSocketFactory.create(contoursHint, "contours");
+ outputSocket = outputSocketFactory.create(outputHint, "feature-contours");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java
index 9ebf11534c..95cf184009 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java
@@ -42,8 +42,8 @@ protected NetworkPublishOperation(InputSocket.Factory isf, Class dataType) {
.identifier("Data")
.build();
this.dataType = dataType;
- this.dataSocket = isf.create(dataHint);
- this.nameSocket = isf.create(nameHint);
+ this.dataSocket = isf.create(dataHint, "data-type");
+ this.nameSocket = isf.create(nameHint, "published-name");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java
index fb6dff4ac6..20c2d2cd89 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java
@@ -48,14 +48,14 @@ public class MatFieldAccessor implements CVOperation {
@SuppressWarnings("JavadocMethod")
public MatFieldAccessor(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.inputSocket = inputSocketFactory.create(matHint);
-
- this.sizeSocket = outputSocketFactory.create(sizeHint);
- this.emptySocket = outputSocketFactory.create(emptyHint);
- this.channelsSocket = outputSocketFactory.create(channelsHint);
- this.colsSocket = outputSocketFactory.create(colsHint);
- this.rowsSocket = outputSocketFactory.create(rowsHint);
- this.highValueSocket = outputSocketFactory.create(highValueHint);
+ this.inputSocket = inputSocketFactory.create(matHint, "mat");
+
+ this.sizeSocket = outputSocketFactory.create(sizeHint, "size");
+ this.emptySocket = outputSocketFactory.create(emptyHint, "empty");
+ this.channelsSocket = outputSocketFactory.create(channelsHint, "channels");
+ this.colsSocket = outputSocketFactory.create(colsHint, "columns");
+ this.rowsSocket = outputSocketFactory.create(rowsHint, "rows");
+ this.highValueSocket = outputSocketFactory.create(highValueHint, "largest-value");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java
index cc0771597c..cb8015ef4b 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java
@@ -51,13 +51,13 @@ public class MinMaxLoc implements CVOperation {
@SuppressWarnings("JavadocMethod")
public MinMaxLoc(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.srcSocket = inputSocketFactory.create(srcInputHint);
- this.maskSocket = inputSocketFactory.create(maskInputHint);
+ this.srcSocket = inputSocketFactory.create(srcInputHint, "source-image");
+ this.maskSocket = inputSocketFactory.create(maskInputHint, "mask-image");
- this.minValSocket = outputSocketFactory.create(minValOutputHint);
- this.maxValSocket = outputSocketFactory.create(maxValOutputHint);
- this.minLocSocket = outputSocketFactory.create(minLocOutputHint);
- this.maxLocSocket = outputSocketFactory.create(maxLocOutputHint);
+ this.minValSocket = outputSocketFactory.create(minValOutputHint, "min-value");
+ this.maxValSocket = outputSocketFactory.create(maxValOutputHint, "max-value");
+ this.minLocSocket = outputSocketFactory.create(minLocOutputHint, "min-value-location");
+ this.maxLocSocket = outputSocketFactory.create(maxLocOutputHint, "max-value-location");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java
index 5389ed710f..608c1a843d 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java
@@ -38,10 +38,10 @@ public class NewPointOperation implements CVOperation {
@SuppressWarnings("JavadocMethod")
public NewPointOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
- this.xSocket = inputSocketFactory.create(xHint);
- this.ySocket = inputSocketFactory.create(yHint);
+ this.xSocket = inputSocketFactory.create(xHint, "x");
+ this.ySocket = inputSocketFactory.create(yHint, "y");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java
index 8f2eaffb45..a7b3b920b4 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java
@@ -37,10 +37,10 @@ public class NewSizeOperation implements CVOperation {
@SuppressWarnings("JavadocMethod")
public NewSizeOperation(InputSocket.Factory inputSocketFactory,
OutputSocket.Factory outputSocketFactory) {
- this.widthSocket = inputSocketFactory.create(widthHint);
- this.heightSocket = inputSocketFactory.create(heightHint);
+ this.widthSocket = inputSocketFactory.create(widthHint, "width");
+ this.heightSocket = inputSocketFactory.create(heightHint, "height");
- this.outputSocket = outputSocketFactory.create(outputHint);
+ this.outputSocket = outputSocketFactory.create(outputHint, "result");
}
@Override
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
index 64812c491e..ca8d2fdaae 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
@@ -2,13 +2,17 @@
import edu.wpi.grip.core.Pipeline;
import edu.wpi.grip.core.PipelineRunner;
+import edu.wpi.grip.core.VersionManager;
import edu.wpi.grip.core.events.DirtiesSaveEvent;
+import edu.wpi.grip.core.exception.IncompatibleVersionException;
+import edu.wpi.grip.core.exception.InvalidSaveException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.Subscribe;
import com.google.common.reflect.ClassPath;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import java.io.File;
@@ -25,6 +29,7 @@
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
+
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -37,6 +42,7 @@ public class Project {
protected final XStream xstream = new XStream(new PureJavaReflectionProvider());
@Inject
private Pipeline pipeline;
+ private ProjectModel model;
@Inject
private PipelineRunner pipelineRunner;
private Optional file = Optional.empty();
@@ -47,13 +53,16 @@ public void initialize(StepConverter stepConverter,
SourceConverter sourceConverter,
SocketConverter socketConverter,
ConnectionConverter connectionConverter,
- ProjectSettingsConverter projectSettingsConverter) {
+ ProjectSettingsConverter projectSettingsConverter,
+ VersionConverter versionConverter) {
+ model = new ProjectModel(pipeline, VersionManager.CURRENT_VERSION);
xstream.setMode(XStream.NO_REFERENCES);
xstream.registerConverter(stepConverter);
xstream.registerConverter(sourceConverter);
xstream.registerConverter(socketConverter);
xstream.registerConverter(connectionConverter);
xstream.registerConverter(projectSettingsConverter);
+ xstream.registerConverter(versionConverter);
try {
ClassPath cp = ClassPath.from(getClass().getClassLoader());
cp.getAllClasses()
@@ -109,10 +118,31 @@ public void open(String projectXml) {
}
@VisibleForTesting
- void open(Reader reader) {
+ void open(Reader reader) throws IncompatibleVersionException {
pipelineRunner.stopAndAwait();
this.pipeline.clear();
- this.xstream.fromXML(reader);
+ try {
+ Object loaded = xstream.fromXML(reader);
+ if (loaded instanceof Pipeline) {
+ // Unversioned pre-2.0.0 save.
+ // It's compatible with the current version because it loaded without exceptions.
+ // When saved, the old save file will be upgraded to the current version.
+ model = new ProjectModel(pipeline, VersionManager.CURRENT_VERSION);
+ } else if (loaded instanceof ProjectModel) {
+ // Version 2.0.0 or above. Since we got to this point, we know it's compatible
+ // (otherwise a ConversionException would have been thrown).
+ // Saves from different versions of GRIP will be upgraded/downgraded to the current version
+ model = new ProjectModel(pipeline, VersionManager.CURRENT_VERSION);
+ } else {
+ // Uhh... probably a future version
+ throw new InvalidSaveException(
+ String.format("Unknown save format (loaded a %s)", loaded.getClass().getName())
+ );
+ }
+ } catch (ConversionException e) {
+ // Incompatible save, or a bug with de/serialization
+ throw new InvalidSaveException("Incompatible operations in save file", e);
+ }
pipelineRunner.startAsync();
saveIsDirty.set(false);
}
@@ -129,7 +159,7 @@ public void save(File file) throws IOException {
}
public void save(Writer writer) {
- this.xstream.toXML(this.pipeline, writer);
+ this.xstream.toXML(model, writer);
saveIsDirty.set(false);
}
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/ProjectModel.java b/core/src/main/java/edu/wpi/grip/core/serialization/ProjectModel.java
new file mode 100644
index 0000000000..d4ee4f89dd
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/ProjectModel.java
@@ -0,0 +1,37 @@
+package edu.wpi.grip.core.serialization;
+
+import edu.wpi.grip.core.Pipeline;
+
+import com.github.zafarkhaja.semver.Version;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ * Data model class for saving and loading projects from save files.
+ */
+@XStreamAlias("grip:Project")
+public class ProjectModel {
+
+ private Pipeline pipeline;
+ private Version version;
+
+ /**
+ * Only used for XStream deserialization.
+ */
+ public ProjectModel() {
+ this(null, null);
+ }
+
+ public ProjectModel(Pipeline pipeline, Version version) {
+ this.pipeline = pipeline;
+ this.version = version;
+ }
+
+ public Pipeline getPipeline() {
+ return pipeline;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
index 060f3a9012..2c6b4d6226 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
@@ -18,6 +18,7 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -50,18 +51,14 @@ public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingCont
// Save the location of the socket in the pipeline.
socket.getStep().ifPresent(step -> {
- final List extends Socket> sockets = (socket.getDirection() == Socket.Direction.INPUT)
- ? step.getInputSockets()
- : step.getOutputSockets();
writer.addAttribute(STEP_ATTRIBUTE, String.valueOf(pipeline.getSteps().indexOf(step)));
- writer.addAttribute(SOCKET_ATTRIBUTE, String.valueOf(sockets.indexOf(socket)));
+ writer.addAttribute(SOCKET_ATTRIBUTE, socket.getUid());
});
socket.getSource().ifPresent(source -> {
- final List extends Socket> sockets = source.getOutputSockets();
writer.addAttribute(SOURCE_ATTRIBUTE, String.valueOf(pipeline.getSources()
.indexOf(source)));
- writer.addAttribute(SOCKET_ATTRIBUTE, String.valueOf(sockets.indexOf(socket)));
+ writer.addAttribute(SOCKET_ATTRIBUTE, socket.getUid());
});
// Save whether or not output sockets are previewed
@@ -114,18 +111,16 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
Socket socket;
if (reader.getAttribute(STEP_ATTRIBUTE) != null) {
final int stepIndex = Integer.parseInt(reader.getAttribute(STEP_ATTRIBUTE));
- final int socketIndex = Integer.parseInt(reader.getAttribute(SOCKET_ATTRIBUTE));
final Step step = pipeline.getSteps().get(stepIndex);
socket = (direction == Socket.Direction.INPUT)
- ? step.getInputSockets().get(socketIndex)
- : step.getOutputSockets().get(socketIndex);
+ ? socketFor(step.getInputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE))
+ : socketFor(step.getOutputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE));
} else if (reader.getAttribute(SOURCE_ATTRIBUTE) != null) {
final int sourceIndex = Integer.parseInt(reader.getAttribute(SOURCE_ATTRIBUTE));
- final int socketIndex = Integer.parseInt(reader.getAttribute(SOCKET_ATTRIBUTE));
final Source source = pipeline.getSources().get(sourceIndex);
- socket = source.getOutputSockets().get(socketIndex);
+ socket = socketFor(source.getOutputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE));
} else {
throw new ConversionException("Sockets must have either a step or source attribute");
}
@@ -174,6 +169,25 @@ private Class> getDeserializedType(Socket socket) {
+ socketType);
}
+ private S socketFor(List extends S> sockets, String attributeValue) {
+ if (attributeValue.matches("\\d+")) {
+ // All numbers, assume it's an index
+ return sockets.get(Integer.parseInt(attributeValue));
+ } else {
+ // Letters -- assume it's a socket UID
+ List matching = sockets.stream()
+ .filter(s -> s.getUid().equals(attributeValue))
+ .collect(Collectors.toList());
+ if (matching.size() > 1) {
+ throw new ConversionException("Multiple sockets with UID '" + attributeValue + "'");
+ } else if (matching.isEmpty()) {
+ throw new ConversionException("No sockets with UID '" + attributeValue + "'");
+ } else {
+ return matching.get(0);
+ }
+ }
+ }
+
@Override
public boolean canConvert(Class type) {
return Socket.class.isAssignableFrom(type);
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/VersionConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/VersionConverter.java
new file mode 100644
index 0000000000..a735f13c8b
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/VersionConverter.java
@@ -0,0 +1,31 @@
+package edu.wpi.grip.core.serialization;
+
+import com.github.zafarkhaja.semver.Version;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+/**
+ * Converter for {@link Version GRIP verisons}.
+ */
+public class VersionConverter implements Converter {
+
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ Version v = (Version) source;
+ writer.setValue(v.toString());
+ }
+
+ @Override
+ public Version unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ return Version.valueOf(reader.getValue());
+ }
+
+ @Override
+ public boolean canConvert(Class type) {
+ return Version.class.isAssignableFrom(type);
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java
index 84b5450998..6bc8e68684 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java
@@ -28,7 +28,16 @@ public interface InputSocket extends Socket {
void onValueChanged();
interface Factory {
- InputSocket create(SocketHint hint);
+ /**
+ * Creates a new input socket from a socket hint. This should only be used for
+ * generated sockets (like for Python operations) or for templated operations. For
+ * everything else, use {@link #create(SocketHint, String)}.
+ */
+ default InputSocket create(SocketHint hint) {
+ return create(hint, hint.getIdentifier().toLowerCase().replaceAll("\\s+", "-"));
+ }
+
+ InputSocket create(SocketHint hint, String uid);
}
/**
@@ -47,6 +56,11 @@ public Decorator(InputSocket socket) {
this.decorated = socket;
}
+ @Override
+ public String getUid() {
+ return decorated.getUid();
+ }
+
@Override
public Direction getDirection() {
return decorated.getDirection();
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java
index 40456510e5..83537b11ec 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java
@@ -3,6 +3,7 @@
import edu.wpi.grip.core.Connection;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -19,12 +20,18 @@
public class InputSocketImpl extends SocketImpl implements InputSocket {
private final AtomicBoolean dirty = new AtomicBoolean(false);
+ @VisibleForTesting
+ InputSocketImpl(EventBus eventBus, SocketHint socketHint) {
+ this(eventBus, socketHint, socketHint.getIdentifier());
+ }
+
/**
* @param eventBus The Guava {@link EventBus} used by the application.
* @param socketHint {@link #getSocketHint}
+ * @param uid a unique string for identifying this socket
*/
- InputSocketImpl(EventBus eventBus, SocketHint socketHint) {
- super(eventBus, socketHint, Socket.Direction.INPUT);
+ InputSocketImpl(EventBus eventBus, SocketHint socketHint, String uid) {
+ super(eventBus, socketHint, Socket.Direction.INPUT, uid);
}
/**
@@ -63,8 +70,8 @@ public static class FactoryImpl implements Factory {
}
@Override
- public InputSocket create(SocketHint hint) {
- return new InputSocketImpl<>(eventBus, hint);
+ public InputSocket create(SocketHint hint, String uid) {
+ return new InputSocketImpl<>(eventBus, hint, uid);
}
}
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java
index 3bb44e484f..27c9eaa8ad 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java
@@ -12,6 +12,7 @@ public interface OutputSocket extends Socket {
/**
* @return Whether or not this socket is shown in a preview in the GUI.
+ *
* @see #setPreviewed(boolean) d(boolean)
*/
boolean isPreviewed();
@@ -27,6 +28,15 @@ public interface OutputSocket extends Socket {
void resetValueToInitial();
interface Factory {
- OutputSocket create(SocketHint hint);
+ /**
+ * Creates a new output socket from a socket hint. This should only be used for
+ * generated sockets (like for Python operations) or for templated operations. For
+ * everything else, use {@link #create(SocketHint, String)}.
+ */
+ default OutputSocket create(SocketHint hint) {
+ return create(hint, hint.getIdentifier().toLowerCase().replaceAll("\\s+", "-"));
+ }
+
+ OutputSocket create(SocketHint hint, String uid);
}
}
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java
index 7fed378fec..03e615c141 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java
@@ -3,6 +3,7 @@
import edu.wpi.grip.core.events.SocketPreviewChangedEvent;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
@@ -21,12 +22,18 @@ public class OutputSocketImpl extends SocketImpl implements OutputSocket socketHint) {
+ this(eventBus, socketHint, socketHint.getIdentifier());
+ }
+
/**
* @param eventBus The Guava {@link EventBus} used by the application.
* @param socketHint {@link #getSocketHint}
+ * @param uid a unique string for identifying this socket
*/
- OutputSocketImpl(EventBus eventBus, SocketHint socketHint) {
- super(eventBus, socketHint, Direction.OUTPUT);
+ OutputSocketImpl(EventBus eventBus, SocketHint socketHint, String uid) {
+ super(eventBus, socketHint, Direction.OUTPUT, uid);
this.eventBus = eventBus;
}
@@ -72,8 +79,8 @@ public static class FactoryImpl implements OutputSocket.Factory {
}
@Override
- public OutputSocket create(SocketHint hint) {
- return new OutputSocketImpl<>(eventBus, hint);
+ public OutputSocket create(SocketHint hint, String uid) {
+ return new OutputSocketImpl<>(eventBus, hint, uid);
}
}
}
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java b/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java
index 35a46ab2e5..e2e2b4d71d 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java
@@ -25,6 +25,17 @@ public interface Socket {
*/
SocketHint getSocketHint();
+ /**
+ * Gets a String that uniquely identifies this socket. This only needs to be unique for the set
+ * of sockets containing this one (e.g. only per-operation or per-source); it does not need to be
+ * universally unique. However, this is not allowed to change; even if
+ * the name, view, or even the type changes, the UID has to be constant in order for projects
+ * saved from different versions of GRIP to be compatible.
+ *
+ * @implSpec This value MAY NOT change
+ */
+ String getUid();
+
/**
* Set the value of the socket using an {@link Optional}, and fire off a {@link
* edu.wpi.grip.core.events.SocketChangedEvent}.
@@ -60,6 +71,7 @@ default void setValue(@Nullable T value) {
* If this socket is in a step return it.
*
* @return The step that this socket is part of
+ *
* @see #getSource()
*/
Optional getStep();
@@ -73,6 +85,7 @@ default void setValue(@Nullable T value) {
* If this socket is in a source return it.
*
* @return The source that this socket is part of.
+ *
* @see #getStep()
*/
Optional
*/
public class WarningEvent {
@@ -21,8 +20,7 @@ public class WarningEvent {
* @param header the header or title of the warning (e.g. "Cannot generate code").
* This should be short and descriptive.
* @param body the body of the warning.
- * This should go into detail about what the user did wrong and may use markdown
- * formatting.
+ * This should go into detail about what the user did wrong.
*/
public WarningEvent(String header, String body) {
checkNotNull(header, "Header text cannot be null");
diff --git a/ui/src/main/java/edu/wpi/grip/ui/WarningAlert.java b/ui/src/main/java/edu/wpi/grip/ui/WarningAlert.java
index 87a50eb5c5..c6fe9aa8bf 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/WarningAlert.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/WarningAlert.java
@@ -1,11 +1,7 @@
package edu.wpi.grip.ui;
-import org.pegdown.PegDownProcessor;
-
import javafx.scene.control.Alert;
-import javafx.scene.effect.BlendMode;
-import javafx.scene.web.WebEngine;
-import javafx.scene.web.WebView;
+import javafx.scene.layout.Region;
import javafx.stage.StageStyle;
import javafx.stage.Window;
@@ -14,28 +10,15 @@
/**
* An alert that warns the user when they try to do something unsupported, such as trying to
* generate code with an operation that doesn't support code gen.
- *
- *
The alert renders the body text as markdown.
- *
*/
public class WarningAlert extends Alert {
- /**
- * Markdown processor. Converts markdown to HTML.
- */
- private static final PegDownProcessor mdProcessor = new PegDownProcessor();
- private final WebView webView = new WebView();
- private final WebEngine webEngine = webView.getEngine();
-
- // Width and height of the alert dialog
- private static final double WIDTH = 360;
- private static final double HEIGHT = 180;
-
/**
* Creates a new warning alert.
*
* @param header the header text of the alert. This should be short and descriptive.
- * @param body the body text of the alert. This supports markdown formatting.
+ * @param body the body text of the alert. This should go into detail about the warning
+ * and what prompted it.
* @param owner the owner window of this alert
*
* @throws NullPointerException if any of the parameters are null
@@ -48,22 +31,11 @@ public WarningAlert(String header, String body, Window owner) {
initStyle(StageStyle.UNDECORATED);
initOwner(owner);
- initWebView();
+ getDialogPane().setMinHeight(Region.USE_PREF_SIZE); // expand to fit content
+ getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
setHeaderText(header);
- webEngine.loadContent(mdProcessor.markdownToHtml(body));
- getDialogPane().setContent(webView);
- }
-
- /**
- * Initializes the web view and engine prior to setting its content.
- */
- private void initWebView() {
- webView.setBlendMode(BlendMode.DARKEN); // make the background transparent
- webEngine.setUserStyleSheetLocation(
- getClass().getResource("warning_alert.css").toExternalForm()
- );
- webView.setPrefSize(WIDTH, HEIGHT);
+ setContentText(body);
}
}
diff --git a/ui/src/main/resources/edu/wpi/grip/ui/warning_alert.css b/ui/src/main/resources/edu/wpi/grip/ui/warning_alert.css
deleted file mode 100644
index 62dfc1b558..0000000000
--- a/ui/src/main/resources/edu/wpi/grip/ui/warning_alert.css
+++ /dev/null
@@ -1,3 +0,0 @@
-html {
- font: 14px Roboto;
-}
From 4f10cd766a46358c2ff52027dd5b8dc7073c8341 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Fri, 28 Oct 2016 13:22:50 -0400
Subject: [PATCH 5/9] Add IDs to Steps and Sources
Put the version as an attribute of the project rather than an XML entry.
---
.../wpi/grip/core/AbstractPipelineEntry.java | 148 ++++++++++++++++++
.../java/edu/wpi/grip/core/PipelineEntry.java | 61 ++++++++
.../main/java/edu/wpi/grip/core/Source.java | 36 ++++-
.../src/main/java/edu/wpi/grip/core/Step.java | 37 ++---
.../wpi/grip/core/serialization/Project.java | 6 +-
.../core/serialization/ProjectConverter.java | 40 +++++
.../core/serialization/SocketConverter.java | 39 +++--
.../core/serialization/SourceConverter.java | 7 +
.../core/serialization/StepConverter.java | 13 +-
.../wpi/grip/core/sources/CameraSource.java | 22 +--
.../edu/wpi/grip/core/sources/HttpSource.java | 12 +-
.../grip/core/sources/ImageFileSource.java | 2 +-
.../core/sources/MultiImageFileSource.java | 2 +-
.../core/sources/NetworkTableEntrySource.java | 10 +-
.../java/edu/wpi/grip/core/MockSource.java | 2 +-
.../grip/core/sources/MockNumberSource.java | 6 +-
.../input/RangeInputSocketController.java | 8 +-
17 files changed, 379 insertions(+), 72 deletions(-)
create mode 100644 core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/PipelineEntry.java
create mode 100644 core/src/main/java/edu/wpi/grip/core/serialization/ProjectConverter.java
diff --git a/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java b/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
new file mode 100644
index 0000000000..f89f2252c0
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
@@ -0,0 +1,148 @@
+package edu.wpi.grip.core;
+
+import edu.wpi.grip.core.sockets.InputSocket;
+import edu.wpi.grip.core.sockets.OutputSocket;
+import edu.wpi.grip.core.sockets.Socket;
+
+import com.google.common.base.CaseFormat;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A partial implementation of {@code PipelineEntry} that implements the removal and ID methods.
+ */
+public abstract class AbstractPipelineEntry implements PipelineEntry {
+
+ private static final IdPool idPool = IdPool.INSTANCE;
+
+ protected final Object removedLock = new Object();
+ private boolean removed = false;
+ private String id;
+
+ /**
+ * Creates a unique ID string for the given subclass.
+ *
+ * @param entryClass the subclass
+ *
+ * @return an ID string for the subclass.
+ */
+ protected static String makeId(Class extends PipelineEntry> entryClass) {
+ String id;
+ do {
+ id = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, entryClass.getSimpleName())
+ + "." + RandomStringUtils.randomAlphanumeric(8);
+ } while (idPool.checkId(entryClass, id));
+ return id;
+ }
+
+ /**
+ * Creates a new pipeline entry.
+ *
+ * @param id the ID of the new entry. This must be unique for all instances of the concrete class.
+ *
+ * @throws NullPointerException if the ID is null
+ * @throws IllegalArgumentException if the ID is already taken
+ */
+ protected AbstractPipelineEntry(String id) {
+ idPool.get(getClass()).add(id);
+ this.id = id;
+ }
+
+ @Override
+ public final Socket getSocketByUid(String uid) throws NoSuchElementException {
+ checkNotNull(uid, "UID");
+ for (InputSocket in : getInputSockets()) {
+ if (in.getUid().equals(uid)) {
+ return in;
+ }
+ }
+ for (OutputSocket out : getOutputSockets()) {
+ if (out.getUid().equals(uid)) {
+ return out;
+ }
+ }
+ throw new NoSuchElementException(uid);
+ }
+
+ /**
+ * Cleans up this entry, such as by freeing resources or disabling callbacks.
+ */
+ protected abstract void cleanUp();
+
+ @Override
+ public final void setRemoved() {
+ synchronized (removedLock) {
+ cleanUp();
+ idPool.removeId(this);
+ removed = true;
+ }
+ }
+
+ @Override
+ public final boolean removed() {
+ synchronized (removedLock) {
+ return removed;
+ }
+ }
+
+ @Override
+ public final void setId(String id) throws NullPointerException, IllegalArgumentException {
+ checkNotNull(id, "The ID cannot be null");
+ boolean inDeserialization = Arrays.stream(new Exception().getStackTrace())
+ .map(e -> e.getClassName())
+ .anyMatch(n -> n.matches(".*(Step|Source)Converter"));
+ if (!inDeserialization) {
+ throw new IllegalStateException(
+ "This method may only be called during project deserialization");
+ }
+ idPool.get(getClass()).add(id);
+ this.id = id;
+ }
+
+ @Override
+ public final String getId() {
+ return id;
+ }
+
+ @Override
+ public String toString() {
+ return getId();
+ }
+
+ /**
+ * Pool of used IDs.
+ */
+ private static class IdPool extends HashMap, Set> {
+ private static final IdPool INSTANCE = new IdPool();
+
+ /**
+ * Checks if an ID is already used by an instance of a pipeline entry class.
+ */
+ public boolean checkId(Class extends PipelineEntry> clazz, String id) {
+ return get(clazz).contains(id);
+ }
+
+ /**
+ * Removes the ID of the given entry.
+ */
+ public void removeId(PipelineEntry e) {
+ get(e.getClass()).remove(e.getId());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Set get(Object key) {
+ return computeIfAbsent((Class extends PipelineEntry>) key, k -> new HashSet<>());
+ }
+
+ }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/PipelineEntry.java b/core/src/main/java/edu/wpi/grip/core/PipelineEntry.java
new file mode 100644
index 0000000000..89091a9e2b
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/PipelineEntry.java
@@ -0,0 +1,61 @@
+package edu.wpi.grip.core;
+
+import edu.wpi.grip.core.sockets.InputSocket;
+import edu.wpi.grip.core.sockets.OutputSocket;
+import edu.wpi.grip.core.sockets.Socket;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * An entry in the pipeline.
+ */
+public interface PipelineEntry {
+
+ /**
+ * Gets a read-only list of the input sockets to this entry.
+ * This may be empty, but may never be null.
+ */
+ List getInputSockets();
+
+ /**
+ * Gets a read-only list of the output sockets from this entry.
+ * This may be empty, but may never be null.
+ */
+ List getOutputSockets();
+
+ /**
+ * Gets the socket with the given ID in this entry.
+ *
+ * @param uid the UID of the socket to get
+ *
+ * @throws NoSuchElementException if there is no socket with the given UID,
+ */
+ Socket getSocketByUid(String uid) throws NoSuchElementException;
+
+ /**
+ * Sets this entry as removed from the pipeline.
+ */
+ void setRemoved();
+
+ /**
+ * Checks if this entry has been removed from the pipeline.
+ */
+ boolean removed();
+
+ /**
+ * Sets the ID of this entry. This should only be used by deserialization.
+ *
+ * @param newId the new ID
+ *
+ * @throws NullPointerException if the ID is null
+ * @throws IllegalArgumentException if the ID is already taken
+ */
+ void setId(String newId) throws NullPointerException, IllegalArgumentException;
+
+ /**
+ * Gets the ID of this entry. This ID should be unique for all instances of the concrete class.
+ */
+ String getId();
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/Source.java b/core/src/main/java/edu/wpi/grip/core/Source.java
index 5c4e9087dc..9974f62658 100644
--- a/core/src/main/java/edu/wpi/grip/core/Source.java
+++ b/core/src/main/java/edu/wpi/grip/core/Source.java
@@ -1,5 +1,7 @@
package edu.wpi.grip.core;
+import edu.wpi.grip.core.events.SourceRemovedEvent;
+import edu.wpi.grip.core.sockets.InputSocket;
import edu.wpi.grip.core.sockets.OutputSocket;
import edu.wpi.grip.core.sources.CameraSource;
import edu.wpi.grip.core.sources.HttpSource;
@@ -9,6 +11,7 @@
import edu.wpi.grip.core.util.ExceptionWitness;
import com.google.common.collect.ImmutableList;
+import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import java.io.IOException;
@@ -21,17 +24,23 @@
/**
* Base class for an input into the pipeline.
*/
-public abstract class Source {
+public abstract class Source extends AbstractPipelineEntry {
private static final Logger logger = Logger.getLogger(Source.class.getName());
private final ExceptionWitness exceptionWitness;
/**
* @param exceptionWitnessFactory Factory to create the exceptionWitness.
*/
- protected Source(ExceptionWitness.Factory exceptionWitnessFactory) {
+ protected Source(String id, ExceptionWitness.Factory exceptionWitnessFactory) {
+ super(id);
this.exceptionWitness = exceptionWitnessFactory.create(this);
}
+ @Override
+ public final List getInputSockets() {
+ return ImmutableList.of();
+ }
+
/**
* This is used by the GUI to distinguish different sources. For example, {@link
* edu.wpi.grip.core.sources.ImageFileSource} returns the filename of the image.
@@ -45,6 +54,7 @@ protected Source(ExceptionWitness.Factory exceptionWitnessFactory) {
*
* @return @return An array of {@link OutputSocket}s for the outputs that the source produces.
*/
+ @Override
public final ImmutableList getOutputSockets() {
final List outputSockets = createOutputSockets();
for (OutputSocket socket : outputSockets) {
@@ -99,6 +109,28 @@ public final void initializeSafely() {
}
}
+ /**
+ * Sets this source as removed if an event is posted to clean it up.
+ * Subclasses that need additional functionality should subscribe to the event bus.
+ */
+ @Subscribe
+ public void onSourceRemoved(SourceRemovedEvent event) {
+ if (event.getSource() == this) {
+ setRemoved();
+ }
+ }
+
+ /**
+ * Cleans up this source when it gets removed from the pipeline. The default implementation is a
+ * NOP, but subclasses should override this method if they need to free any resources or
+ * de-register callbacks.
+ */
+ @Override
+ @SuppressWarnings("PMD")
+ protected void cleanUp() {
+ // Default to NOP
+ }
+
public interface SourceFactory {
Source create(Class> type, Properties properties) throws IOException;
}
diff --git a/core/src/main/java/edu/wpi/grip/core/Step.java b/core/src/main/java/edu/wpi/grip/core/Step.java
index dde05e335c..20f17dc235 100644
--- a/core/src/main/java/edu/wpi/grip/core/Step.java
+++ b/core/src/main/java/edu/wpi/grip/core/Step.java
@@ -22,7 +22,7 @@
* sockets, and it runs the operation whenever one of the input sockets changes.
*/
@XStreamAlias(value = "grip:Step")
-public class Step {
+public class Step extends AbstractPipelineEntry {
private static final Logger logger = Logger.getLogger(Step.class.getName());
private static final String MISSING_SOCKET_MESSAGE_END = " must have a value to run this step.";
@@ -32,8 +32,6 @@ public class Step {
private final OperationDescription description;
private final List inputSockets;
private final List outputSockets;
- private final Object removedLock = new Object();
- private boolean removed = false;
/**
* @param operation The operation that is performed at this step.
@@ -47,6 +45,7 @@ public class Step {
List inputSockets,
List outputSockets,
ExceptionWitness.Factory exceptionWitnessFactory) {
+ super(makeId(Step.class));
this.operation = operation;
this.description = description;
this.inputSockets = inputSockets;
@@ -64,6 +63,7 @@ public OperationDescription getOperationDescription() {
/**
* @return An array of {@link InputSocket InputSockets} that hold the inputs to this step.
*/
+ @Override
public ImmutableList getInputSockets() {
return ImmutableList.copyOf(inputSockets);
}
@@ -71,6 +71,7 @@ public ImmutableList getInputSockets() {
/**
* @return A list of {@link OutputSocket OutputSockets} that hold the outputs of this step.
*/
+ @Override
public ImmutableList getOutputSockets() {
return ImmutableList.copyOf(outputSockets);
}
@@ -112,10 +113,9 @@ protected final void runPerformIfPossible() {
try {
// We need to ensure that if perform disabled is switching states that we don't run the
- // perform method
- // while that is happening.
+ // perform method while that is happening.
synchronized (removedLock) {
- if (!removed) {
+ if (!removed()) {
this.operation.perform();
}
}
@@ -132,27 +132,9 @@ protected final void runPerformIfPossible() {
witness.clearException();
}
- /**
- * Sets this step as having been removed.
- */
- public final void setRemoved() {
- // We need to wait for the perform method to complete before returning.
- // if we don't wait then the perform method could end up being run concurrently with the
- // perform methods execution
- synchronized (removedLock) {
- removed = true;
- operation.cleanUp();
- }
- }
-
- /**
- * Allows checks to see if this step has had its perform method disabled. If this value ever
- * returns false it will never return true again.
- *
- * @return true if runPerformIfPossible can run successfully
- */
- protected boolean removed() {
- return removed;
+ @Override
+ protected void cleanUp() {
+ operation.cleanUp();
}
@Singleton
@@ -166,6 +148,7 @@ public Factory(ExceptionWitness.Factory exceptionWitnessFactory) {
/**
* @param operationData The operation data to use to construct the step.
+ *
* @return The constructed Step.
*/
public Step create(OperationMetaData operationData) {
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
index 0b24feb084..e505e3653d 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
@@ -50,7 +50,8 @@ public class Project {
private final ObservableBoolean saveIsDirty = new ObservableBoolean();
@Inject
- public void initialize(StepConverter stepConverter,
+ public void initialize(ProjectConverter projectConverter,
+ StepConverter stepConverter,
SourceConverter sourceConverter,
SocketConverter socketConverter,
ConnectionConverter connectionConverter,
@@ -58,6 +59,7 @@ public void initialize(StepConverter stepConverter,
VersionConverter versionConverter) {
model = new ProjectModel(pipeline, VersionManager.CURRENT_VERSION);
xstream.setMode(XStream.NO_REFERENCES);
+ xstream.registerConverter(projectConverter);
xstream.registerConverter(stepConverter);
xstream.registerConverter(sourceConverter);
xstream.registerConverter(socketConverter);
@@ -142,7 +144,7 @@ void open(Reader reader) throws InvalidSaveException {
}
} catch (ConversionException e) {
// Incompatible save, or a bug with de/serialization
- throw new InvalidSaveException("Incompatible operations in save file", e);
+ throw new InvalidSaveException("There are incompatible operations in the pipeline", e);
} catch (StreamException e) {
// Invalid XML
throw new InvalidSaveException("Invalid XML", e);
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/ProjectConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/ProjectConverter.java
new file mode 100644
index 0000000000..0eeabc8993
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/ProjectConverter.java
@@ -0,0 +1,40 @@
+package edu.wpi.grip.core.serialization;
+
+import edu.wpi.grip.core.Pipeline;
+import edu.wpi.grip.core.VersionManager;
+
+import com.github.zafarkhaja.semver.Version;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+
+public class ProjectConverter implements Converter {
+
+ private static final String VERSION_ATTRIBUTE = "version";
+
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ ProjectModel model = (ProjectModel) source;
+ writer.addAttribute(VERSION_ATTRIBUTE, model.getVersion().toString());
+ writer.startNode("grip:Pipeline");
+ context.convertAnother(model.getPipeline());
+ writer.endNode();
+ }
+
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String v = reader.getAttribute(VERSION_ATTRIBUTE);
+ Version version = v == null ? VersionManager.LAST_UNVERSIONED_RELEASE : Version.valueOf(v);
+ reader.moveDown();
+ Pipeline pipeline = (Pipeline) context.convertAnother(null, Pipeline.class);
+ reader.moveUp();
+ return new ProjectModel(pipeline, version);
+ }
+
+ @Override
+ public boolean canConvert(Class type) {
+ return type.equals(ProjectModel.class);
+ }
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
index 2c6b4d6226..fdad0ccdf3 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java
@@ -51,13 +51,12 @@ public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingCont
// Save the location of the socket in the pipeline.
socket.getStep().ifPresent(step -> {
- writer.addAttribute(STEP_ATTRIBUTE, String.valueOf(pipeline.getSteps().indexOf(step)));
+ writer.addAttribute(STEP_ATTRIBUTE, step.getId());
writer.addAttribute(SOCKET_ATTRIBUTE, socket.getUid());
});
socket.getSource().ifPresent(source -> {
- writer.addAttribute(SOURCE_ATTRIBUTE, String.valueOf(pipeline.getSources()
- .indexOf(source)));
+ writer.addAttribute(SOURCE_ATTRIBUTE, source.getId());
writer.addAttribute(SOCKET_ATTRIBUTE, socket.getUid());
});
@@ -110,16 +109,12 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
// (such as connections) can reference sockets in the pipeline.
Socket socket;
if (reader.getAttribute(STEP_ATTRIBUTE) != null) {
- final int stepIndex = Integer.parseInt(reader.getAttribute(STEP_ATTRIBUTE));
-
- final Step step = pipeline.getSteps().get(stepIndex);
+ final Step step = stepFor(reader.getAttribute(STEP_ATTRIBUTE));
socket = (direction == Socket.Direction.INPUT)
? socketFor(step.getInputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE))
: socketFor(step.getOutputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE));
} else if (reader.getAttribute(SOURCE_ATTRIBUTE) != null) {
- final int sourceIndex = Integer.parseInt(reader.getAttribute(SOURCE_ATTRIBUTE));
-
- final Source source = pipeline.getSources().get(sourceIndex);
+ final Source source = sourceFor(reader.getAttribute(SOURCE_ATTRIBUTE));
socket = socketFor(source.getOutputSockets(), reader.getAttribute(SOCKET_ATTRIBUTE));
} else {
throw new ConversionException("Sockets must have either a step or source attribute");
@@ -169,6 +164,32 @@ private Class> getDeserializedType(Socket socket) {
+ socketType);
}
+ private Source sourceFor(String attributeValue) {
+ if (attributeValue.matches("\\d+")) {
+ // All numbers, it's an index
+ return pipeline.getSources().get(Integer.parseInt(attributeValue));
+ } else {
+ // Alphanumeric
+ return pipeline.getSources().stream()
+ .filter(s -> s.getId().equals(attributeValue))
+ .findAny()
+ .get();
+ }
+ }
+
+ private Step stepFor(String attributeValue) {
+ if (attributeValue.matches("\\d+")) {
+ // All numbers, it's an index
+ return pipeline.getSteps().get(Integer.parseInt(attributeValue));
+ } else {
+ // Alphanumeric
+ return pipeline.getSteps().stream()
+ .filter(s -> s.getId().equals(attributeValue))
+ .findAny()
+ .get();
+ }
+ }
+
private S socketFor(List extends S> sockets, String attributeValue) {
if (attributeValue.matches("\\d+")) {
// All numbers, assume it's an index
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/SourceConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/SourceConverter.java
index 4db9491694..9837505e9e 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/SourceConverter.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/SourceConverter.java
@@ -27,6 +27,8 @@
*/
public class SourceConverter implements Converter {
+ private static final String ID_ATTRIBUTE = "id";
+
@Inject
private EventBus eventBus;
@Inject
@@ -36,6 +38,7 @@ public class SourceConverter implements Converter {
@Override
public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
+ writer.addAttribute(ID_ATTRIBUTE, ((Source) obj).getId());
context.convertAnother(((Source) obj).getProperties());
}
@@ -45,6 +48,7 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
try {
final Class sourceClass = (Class) project.xstream.getMapper()
.realClass(reader.getNodeName());
+ final String id = reader.getAttribute(ID_ATTRIBUTE);
final Properties properties = (Properties) context.convertAnother(null, Properties.class);
// Although sources may block briefly upon creation, we intentionally do this in one thread
@@ -53,6 +57,9 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
// sources that
// are in the process of loading.
final Source source = sourceFactory.create(sourceClass, properties);
+ if (id != null) {
+ source.setId(id);
+ }
// Instead of returning the source, post it to the event bus so both the core and GUI
// classes know it
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java
index 885e034891..9c807c5ce3 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java
@@ -28,6 +28,7 @@
public class StepConverter implements Converter {
private static final String NAME_ATTRIBUTE = "name";
+ private static final String ID_ATTRIBUTE = "id";
@Inject
private EventBus eventBus;
@@ -43,6 +44,7 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC
final Step step = ((Step) source);
writer.addAttribute(NAME_ATTRIBUTE, step.getOperationDescription().name());
+ writer.addAttribute(ID_ATTRIBUTE, step.getId());
// Also save any sockets in the step
for (InputSocket> socket : step.getInputSockets()) {
@@ -64,11 +66,12 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co
throw new ConversionException("Unknown operation: " + operationName);
}
- // Instead of simply returning the step and having XStream insert it into the pipeline using
- // reflection, send a
- // StepAddedEvent. This allows other interested classes (such as PipelineView) to also know
- // when steps are added.
- pipeline.addStep(stepFactory.create(operationMetaData.get()));
+ final String id = reader.getAttribute(ID_ATTRIBUTE);
+ Step step = stepFactory.create(operationMetaData.get());
+ if (id != null) {
+ step.setId(id);
+ }
+ pipeline.addStep(step);
while (reader.hasMoreChildren()) {
context.convertAnother(this, Socket.class);
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
index 9936ec1ecb..b437dbfc2e 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
@@ -133,7 +133,7 @@ public class CameraSource extends Source implements RestartableService {
final FrameGrabberFactory grabberFactory,
final ExceptionWitness.Factory exceptionWitnessFactory,
@Assisted final Properties properties) throws MalformedURLException {
- super(exceptionWitnessFactory);
+ super(makeId(CameraSource.class), exceptionWitnessFactory);
this.eventBus = eventBus;
this.frameOutputSocket = outputSocketFactory.create(imageOutputHint, "image-output");
this.frameRateOutputSocket = outputSocketFactory.create(frameRateOutputHint, "fps-output");
@@ -329,22 +329,26 @@ public State state() {
}
@Subscribe
- public void onSourceRemovedEvent(SourceRemovedEvent event) throws InterruptedException,
- TimeoutException, IOException {
+ public void onSourceRemoved(SourceRemovedEvent event) {
if (event.getSource() == this) {
try {
- // Stop the camera service and wait for it to terminate.
- // If we just use stopAsync(), the camera service won't always have terminated by the time
- // a new camera source is added. For webcam sources, this means that the video stream
- // won't be freed and new sources won't be able to connect to the webcam until the
- // application is closed.
- this.stopAndAwait();
+ setRemoved();
} finally {
this.eventBus.unregister(this);
}
}
}
+ @Override
+ protected void cleanUp() {
+ // Stop the camera service and wait for it to terminate.
+ // If we just use stopAsync(), the camera service won't always have terminated by the time
+ // a new camera source is added. For webcam sources, this means that the video stream
+ // won't be freed and new sources won't be able to connect to the webcam until the
+ // application is closed.
+ stopAndAwait();
+ }
+
public interface Factory {
CameraSource create(int deviceNumber) throws IOException;
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/HttpSource.java b/core/src/main/java/edu/wpi/grip/core/sources/HttpSource.java
index a78f65cfc6..3ecfab72f2 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/HttpSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/HttpSource.java
@@ -3,7 +3,6 @@
import edu.wpi.grip.core.Source;
import edu.wpi.grip.core.events.SourceHasPendingUpdateEvent;
-import edu.wpi.grip.core.events.SourceRemovedEvent;
import edu.wpi.grip.core.http.ContextStore;
import edu.wpi.grip.core.http.GripServer;
import edu.wpi.grip.core.sockets.OutputSocket;
@@ -13,7 +12,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
-import com.google.common.eventbus.Subscribe;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@@ -87,7 +85,7 @@ public interface Factory {
GripServer server,
ContextStore store,
@Assisted String path) {
- super(exceptionWitnessFactory);
+ super(makeId(HttpSource.class), exceptionWitnessFactory);
this.path = path;
this.imageHandler = handlers.computeIfAbsent(path, p -> new HttpImageHandler(store, p));
this.imageOutput = osf.create(outputHint, "image-output");
@@ -138,11 +136,9 @@ public void initialize() {
imageHandler.getImage().ifPresent(this::setImage);
}
- @Subscribe
- public void onSourceRemovedEvent(SourceRemovedEvent event) {
- if (event.getSource() == this) {
- imageHandler.removeCallback(callback);
- }
+ @Override
+ protected void cleanUp() {
+ imageHandler.removeCallback(callback);
}
}
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java b/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java
index 397f84fa35..f256a58dc6 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java
@@ -69,7 +69,7 @@ private ImageFileSource(
final OutputSocket.Factory outputSocketFactory,
final ExceptionWitness.Factory exceptionWitnessFactory,
final String path) {
- super(exceptionWitnessFactory);
+ super(makeId(ImageFileSource.class), exceptionWitnessFactory);
this.path = checkNotNull(path, "Path can not be null");
this.name = Files.getNameWithoutExtension(this.path);
this.outputSocket = outputSocketFactory.create(imageOutputHint, "image-output");
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java b/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java
index d643dcb1f9..abc46f065a 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java
@@ -104,7 +104,7 @@ private MultiImageFileSource(
final ExceptionWitness.Factory exceptionWitnessFactory,
final String[] paths,
final int index) {
- super(exceptionWitnessFactory);
+ super(makeId(MultiImageFileSource.class), exceptionWitnessFactory);
this.eventBus = eventBus;
this.outputSocket = outputSocketFactory.create(imageOutputHint, "image-output");
this.index = new AtomicInteger(checkElementIndex(index, paths.length, "File List Index"));
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/NetworkTableEntrySource.java b/core/src/main/java/edu/wpi/grip/core/sources/NetworkTableEntrySource.java
index f7b4863599..da0c868d09 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/NetworkTableEntrySource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/NetworkTableEntrySource.java
@@ -102,7 +102,7 @@ public String toProperty() {
@Named("ntManager") MapNetworkReceiverFactory networkReceiverFactory,
@Assisted String path,
@Assisted Types type) {
- super(exceptionWitnessFactory);
+ super(makeId(NetworkTableEntrySource.class), exceptionWitnessFactory);
this.eventBus = eventBus;
this.path = path;
this.type = type;
@@ -150,7 +150,13 @@ public void initialize() {
@Subscribe
public void onSourceRemovedEvent(SourceRemovedEvent event) {
if (event.getSource() == this) {
- networkReceiver.close();
+ setRemoved();
}
}
+
+ @Override
+ protected void cleanUp() {
+ networkReceiver.close();
+ }
+
}
diff --git a/core/src/test/java/edu/wpi/grip/core/MockSource.java b/core/src/test/java/edu/wpi/grip/core/MockSource.java
index cdd48b77ec..fddbb09a16 100644
--- a/core/src/test/java/edu/wpi/grip/core/MockSource.java
+++ b/core/src/test/java/edu/wpi/grip/core/MockSource.java
@@ -12,7 +12,7 @@
public class MockSource extends Source {
protected MockSource() {
- super(origin -> null);
+ super(makeId(MockSource.class), origin -> null);
}
@Override
diff --git a/core/src/test/java/edu/wpi/grip/core/sources/MockNumberSource.java b/core/src/test/java/edu/wpi/grip/core/sources/MockNumberSource.java
index 98f2790a09..daa2cf5d61 100644
--- a/core/src/test/java/edu/wpi/grip/core/sources/MockNumberSource.java
+++ b/core/src/test/java/edu/wpi/grip/core/sources/MockNumberSource.java
@@ -15,17 +15,17 @@
import java.util.Properties;
public class MockNumberSource extends Source {
-
+
private static int numberOf = 0;
private final int id;
private final OutputSocket outputSocket;
- private final SocketHint outputSocketHint =
+ private final SocketHint outputSocketHint =
SocketHints.Outputs.createNumberSocketHint("Num", Math.PI);
@SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
justification = "Do not need to synchronize inside of a constructor")
public MockNumberSource(Factory exceptionWitnessFactory, double value, OutputSocket.Factory osf) {
- super(exceptionWitnessFactory);
+ super(makeId(MockNumberSource.class), exceptionWitnessFactory);
id = numberOf++;
outputSocket = osf.create(outputSocketHint);
outputSocket.setValue(new Double(value));
diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
index f1cfe9a76c..2c9d897db2 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
@@ -12,6 +12,7 @@
import java.util.List;
+import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@@ -105,8 +106,11 @@ private String getLowHighLabelText() {
@Subscribe
public void updateSliderValue(SocketChangedEvent event) {
if (event.isRegarding(this.getSocket())) {
- this.slider.setLowValue(this.getSocket().getValue().get().get(0).doubleValue());
- this.slider.setHighValue(this.getSocket().getValue().get().get(1).doubleValue());
+ // There's no guarantee that the event was fired from the FX thread
+ Platform.runLater(() -> {
+ this.slider.setLowValue(this.getSocket().getValue().get().get(0).doubleValue());
+ this.slider.setHighValue(this.getSocket().getValue().get().get(1).doubleValue());
+ });
}
}
From a04f0121c259fe4963d2dd04512f379c52fc1cc7 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Fri, 28 Oct 2016 14:46:30 -0400
Subject: [PATCH 6/9] Add tests
---
.../wpi/grip/core/AbstractPipelineEntry.java | 7 +-
.../wpi/grip/core/serialization/Project.java | 2 +-
.../grip/core/AbstractPipelineEntryTest.java | 95 +++++++++++++++++++
.../grip/core/serialization/ProjectTest.java | 34 +++++++
.../grip/core/sockets/MockInputSocket.java | 4 +-
.../grip/core/sockets/MockOutputSocket.java | 6 +-
6 files changed, 142 insertions(+), 6 deletions(-)
create mode 100644 core/src/test/java/edu/wpi/grip/core/AbstractPipelineEntryTest.java
diff --git a/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java b/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
index f89f2252c0..0d7018db43 100644
--- a/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
+++ b/core/src/main/java/edu/wpi/grip/core/AbstractPipelineEntry.java
@@ -4,6 +4,7 @@
import edu.wpi.grip.core.sockets.OutputSocket;
import edu.wpi.grip.core.sockets.Socket;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CaseFormat;
import org.apache.commons.lang.RandomStringUtils;
@@ -21,7 +22,8 @@
*/
public abstract class AbstractPipelineEntry implements PipelineEntry {
- private static final IdPool idPool = IdPool.INSTANCE;
+ @VisibleForTesting
+ static final IdPool idPool = IdPool.INSTANCE;
protected final Object removedLock = new Object();
private boolean removed = false;
@@ -120,7 +122,8 @@ public String toString() {
/**
* Pool of used IDs.
*/
- private static class IdPool extends HashMap, Set> {
+ @VisibleForTesting
+ static class IdPool extends HashMap, Set> {
private static final IdPool INSTANCE = new IdPool();
/**
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
index e505e3653d..aefb0c81b1 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
@@ -144,7 +144,7 @@ void open(Reader reader) throws InvalidSaveException {
}
} catch (ConversionException e) {
// Incompatible save, or a bug with de/serialization
- throw new InvalidSaveException("There are incompatible operations in the pipeline", e);
+ throw new InvalidSaveException("There are incompatible sources or steps in the pipeline", e);
} catch (StreamException e) {
// Invalid XML
throw new InvalidSaveException("Invalid XML", e);
diff --git a/core/src/test/java/edu/wpi/grip/core/AbstractPipelineEntryTest.java b/core/src/test/java/edu/wpi/grip/core/AbstractPipelineEntryTest.java
new file mode 100644
index 0000000000..a563eb1bfa
--- /dev/null
+++ b/core/src/test/java/edu/wpi/grip/core/AbstractPipelineEntryTest.java
@@ -0,0 +1,95 @@
+package edu.wpi.grip.core;
+
+import edu.wpi.grip.core.sockets.InputSocket;
+import edu.wpi.grip.core.sockets.MockInputSocket;
+import edu.wpi.grip.core.sockets.MockOutputSocket;
+import edu.wpi.grip.core.sockets.OutputSocket;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static edu.wpi.grip.core.AbstractPipelineEntry.idPool;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class AbstractPipelineEntryTest {
+
+ private static class TestEntryImpl extends AbstractPipelineEntry {
+
+ TestEntryImpl() {
+ super(makeId(TestEntryImpl.class));
+ }
+
+ @Override
+ public List getInputSockets() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List getOutputSockets() {
+ return ImmutableList.of();
+ }
+
+ @Override
+ protected void cleanUp() {
+ // NOP
+ }
+
+ }
+
+ @Test
+ public void testMakeId() {
+ TestEntryImpl t = new TestEntryImpl();
+ String id = t.getId();
+ assertTrue("ID was not added to the pool", idPool.checkId(TestEntryImpl.class, id));
+ t.setRemoved();
+ assertFalse("ID was not removed from the pool", idPool.checkId(TestEntryImpl.class, id));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSetId() {
+ TestEntryImpl t = new TestEntryImpl();
+ t.setId("foo");
+ fail("setId() should fail if called outside deserialization");
+ }
+
+ @Test
+ public void testSetRemoved() {
+ AtomicBoolean didCleanUp = new AtomicBoolean(false);
+ TestEntryImpl t = new TestEntryImpl() {
+ @Override
+ protected void cleanUp() {
+ didCleanUp.set(true);
+ }
+ };
+ t.setRemoved();
+ assertTrue("cleanUp was not called", didCleanUp.get());
+ assertTrue("Entry was not removed", t.removed());
+ }
+
+ @Test
+ public void testGetSocketById() {
+ final InputSocket in = new MockInputSocket("foo");
+ final OutputSocket out = new MockOutputSocket("bar");
+ TestEntryImpl t = new TestEntryImpl() {
+ @Override
+ public List getInputSockets() {
+ return ImmutableList.of(in);
+ }
+
+ @Override
+ public List getOutputSockets() {
+ return ImmutableList.of(out);
+ }
+ };
+ assertEquals("Did not get correct socket", in, t.getSocketByUid(in.getUid()));
+ assertEquals("Did not get correct socket", out, t.getSocketByUid(out.getUid()));
+ }
+
+}
diff --git a/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java b/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java
index b119685574..55cf0bdbcf 100644
--- a/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java
+++ b/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java
@@ -12,6 +12,7 @@
import edu.wpi.grip.core.events.OperationAddedEvent;
import edu.wpi.grip.core.events.ProjectSettingsChangedEvent;
import edu.wpi.grip.core.events.SourceAddedEvent;
+import edu.wpi.grip.core.exception.InvalidSaveException;
import edu.wpi.grip.core.operations.PythonScriptFile;
import edu.wpi.grip.core.operations.network.MockGripNetworkModule;
import edu.wpi.grip.core.settings.ProjectSettings;
@@ -130,6 +131,39 @@ public void testSerializeEmptyPipeline() throws Exception {
0, pipeline.getConnections().size());
}
+ @Test
+ public void testLoadInvalidXML() {
+ try {
+ project.open("");
+ } catch (InvalidSaveException e) {
+ if (!e.getMessage().equals("Invalid XML")) {
+ // Not the exception we were expecting, throw it
+ throw e;
+ }
+ }
+ }
+
+ @Test
+ public void testLoadIncompatibleEntries() {
+ String xml = "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " "
+ + "";
+ try {
+ project.open(xml);
+ } catch (InvalidSaveException e) {
+ if (!e.getMessage().equals("There are incompatible sources or steps in the pipeline")) {
+ throw e;
+ }
+ }
+ }
+
@Test
public void testSerializePipelineWithSteps() throws Exception {
pipeline.addStep(stepFactory.create(additionOperation));
diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java
index 3855983097..f69c9abfc1 100644
--- a/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java
+++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java
@@ -5,6 +5,8 @@
public class MockInputSocket extends InputSocketImpl {
public MockInputSocket(String name) {
- super(new EventBus(), SocketHints.Outputs.createBooleanSocketHint(name, false), "mock");
+ super(new EventBus(),
+ SocketHints.Outputs.createBooleanSocketHint(name, false),
+ "mock-input-" + name);
}
}
diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java
index 72a59780d7..103063b9df 100644
--- a/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java
+++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java
@@ -3,7 +3,9 @@
import com.google.common.eventbus.EventBus;
public class MockOutputSocket extends OutputSocketImpl {
- public MockOutputSocket(String socketName) {
- super(new EventBus(), SocketHints.Outputs.createBooleanSocketHint(socketName, false), "mock");
+ public MockOutputSocket(String name) {
+ super(new EventBus(),
+ SocketHints.Outputs.createBooleanSocketHint(name, false),
+ "mock-output-" + name);
}
}
From 676fa485786b35367336c2ad78214fec419b0a21 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Thu, 16 Mar 2017 22:24:12 -0400
Subject: [PATCH 7/9] Fix ClassifierSource
---
.../main/java/edu/wpi/grip/core/sources/ClassifierSource.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/ClassifierSource.java b/core/src/main/java/edu/wpi/grip/core/sources/ClassifierSource.java
index 78d8c1805a..7d6488c402 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/ClassifierSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/ClassifierSource.java
@@ -39,7 +39,7 @@ protected ClassifierSource(EventBus eventBus,
OutputSocket.Factory osf,
ExceptionWitness.Factory exceptionWitnessFactory,
@Assisted String filePath) {
- super(exceptionWitnessFactory);
+ super(makeId(ClassifierSource.class), exceptionWitnessFactory);
this.classifierSocket = osf.create(classifierHint);
this.filePath = filePath;
}
From 46d38866c4b33050a88d1f206a0ff2c2adf83ca6 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Thu, 16 Mar 2017 22:49:51 -0400
Subject: [PATCH 8/9] Make CameraSource use cleanUp() to do cleanup. Appease
checkstyle
---
.../wpi/grip/core/serialization/Project.java | 2 +-
.../wpi/grip/core/sources/CameraSource.java | 65 +++++++------------
.../input/RangeInputSocketController.java | 1 -
3 files changed, 26 insertions(+), 42 deletions(-)
diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
index 47e11685fa..54bcebcfe7 100644
--- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
+++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java
@@ -167,7 +167,7 @@ void open(Reader reader) throws InvalidSaveException {
* @param file the file to save to
*
* @return true if the project was successfully saved to the given file, or false if the file
- * could not be written to
+ * could not be written to
*/
public boolean trySave(File file) {
try {
diff --git a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
index 26b7e079b1..960a06f5d4 100644
--- a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
+++ b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java
@@ -3,7 +3,6 @@
import edu.wpi.grip.core.Source;
import edu.wpi.grip.core.events.SourceHasPendingUpdateEvent;
-import edu.wpi.grip.core.events.SourceRemovedEvent;
import edu.wpi.grip.core.sockets.OutputSocket;
import edu.wpi.grip.core.sockets.SocketHint;
import edu.wpi.grip.core.sockets.SocketHints;
@@ -16,7 +15,6 @@
import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
-import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -342,49 +340,36 @@ public State state() {
return cameraService.state();
}
- @Subscribe
- public void onSourceRemoved(SourceRemovedEvent event) {
- if (event.getSource() == this) {
- try {
- // Stop the camera service and wait for it to terminate.
- // If we just use stopAsync(), the camera service won't always have terminated by the time
- // a new camera source is added. For webcam sources, this means that the video stream
- // won't be freed and new sources won't be able to connect to the webcam until the
- // application is closed.
- if (StandardSystemProperty.OS_NAME.value().toLowerCase().contains("mac")) {
- // Workaround for #716. This affects webcams as well as IP camera sources.
- // Use only stopAsync() to avoid blocking. Since we have no way of knowing when
- // the capture has actually been freed, we use a dumb delay to try to make sure it's
- // freed before returning. THIS IS NOT A GOOD SOLUTION. But it's the best one we have
- // until the bug is fixed.
- stopAsync();
- try {
- // Wait a bit to try to make sure the capture is actually freed before returning
- Thread.sleep(100);
- } catch (InterruptedException ignore) {
- // We did our best. Hopefully, the webcam has been freed at this point.
- Thread.currentThread().interrupt();
- }
- } else {
- this.stopAndAwait();
+ @Override
+ protected void cleanUp() {
+ try {
+ // Stop the camera service and wait for it to terminate.
+ // If we just use stopAsync(), the camera service won't always have terminated by the time
+ // a new camera source is added. For webcam sources, this means that the video stream
+ // won't be freed and new sources won't be able to connect to the webcam until the
+ // application is closed.
+ if (StandardSystemProperty.OS_NAME.value().toLowerCase().contains("mac")) {
+ // Workaround for #716. This affects webcams as well as IP camera sources.
+ // Use only stopAsync() to avoid blocking. Since we have no way of knowing when
+ // the capture has actually been freed, we use a dumb delay to try to make sure it's
+ // freed before returning. THIS IS NOT A GOOD SOLUTION. But it's the best one we have
+ // until the bug is fixed.
+ stopAsync();
+ try {
+ // Wait a bit to try to make sure the capture is actually freed before returning
+ Thread.sleep(100);
+ } catch (InterruptedException ignore) {
+ // We did our best. Hopefully, the webcam has been freed at this point.
+ Thread.currentThread().interrupt();
}
- setRemoved();
- } finally {
- this.eventBus.unregister(this);
+ } else {
+ this.stopAndAwait();
}
+ } finally {
+ this.eventBus.unregister(this);
}
}
- @Override
- protected void cleanUp() {
- // Stop the camera service and wait for it to terminate.
- // If we just use stopAsync(), the camera service won't always have terminated by the time
- // a new camera source is added. For webcam sources, this means that the video stream
- // won't be freed and new sources won't be able to connect to the webcam until the
- // application is closed.
- stopAndAwait();
- }
-
public interface Factory {
CameraSource create(int deviceNumber) throws IOException;
diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
index 179972772d..5e82665b05 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
@@ -13,7 +13,6 @@
import java.util.List;
-import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
From 51915a904e90b39887482d24c6b6fd801c09cdc5 Mon Sep 17 00:00:00 2001
From: Sam Carlberg
Date: Thu, 16 Mar 2017 22:57:48 -0400
Subject: [PATCH 9/9] Temporarily disable GodClass check for
MainWindowController
This class does need a serious refactor, though
---
ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java b/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java
index cec8ca26d0..dd4dcbaa4a 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/MainWindowController.java
@@ -64,6 +64,7 @@
/**
* The Controller for the application window.
*/
+@SuppressWarnings("PMD.GodClass") // temporary
public class MainWindowController {
@FXML