From: Tuukka Lehtonen Date: Fri, 14 Dec 2018 22:01:14 +0000 (+0200) Subject: Initial version of the district network Routes view. X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F54%2F2554%2F5;p=simantics%2Fdistrict.git Initial version of the district network Routes view. The backend for the view is an OSGi service called RouteService which defines an interface for managing routes. The backend handles database writeback in the background. The UI only talks with the RouteService, not the database. Doesn't do much yet and nothing in the UI is finished yet. Just storing initial work. gitlab #25 Change-Id: Ifc79f7aebe943bdd5edae3a16052bf44ca3d3cbe --- diff --git a/org.simantics.district.network.ontology/META-INF/MANIFEST.MF b/org.simantics.district.network.ontology/META-INF/MANIFEST.MF index 9653d574..55e042f1 100644 --- a/org.simantics.district.network.ontology/META-INF/MANIFEST.MF +++ b/org.simantics.district.network.ontology/META-INF/MANIFEST.MF @@ -1,6 +1,6 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 -Bundle-Name: http://www.simantics.org/DistrictNetwork-1.0 +Bundle-Name: http://www.simantics.org/DistrictNetwork Bundle-SymbolicName: org.simantics.district.network.ontology Bundle-Version: 1.0.0.qualifier Require-Bundle: org.simantics.layer0, diff --git a/org.simantics.district.network.ui/META-INF/MANIFEST.MF b/org.simantics.district.network.ui/META-INF/MANIFEST.MF index a2884512..2e77a6c4 100644 --- a/org.simantics.district.network.ui/META-INF/MANIFEST.MF +++ b/org.simantics.district.network.ui/META-INF/MANIFEST.MF @@ -29,7 +29,9 @@ Require-Bundle: org.eclipse.e4.ui.model.workbench;bundle-version="1.1.100.v20150 org.eclipse.e4.core.commands, org.eclipse.e4.core.contexts, org.eclipse.jface, - org.simantics.scl.osgi + org.simantics.scl.osgi, + org.simantics.district.route.ui;bundle-version="1.0.0", + org.simantics.district.route Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Import-Package: javax.annotation;version="1.0.0";resolution:=optional, javax.inject;version="1.0.0" diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java index c6c510e0..642d02be 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictDiagramViewer.java @@ -6,6 +6,7 @@ import org.simantics.db.ReadGraph; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.procedure.Listener; +import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.district.network.DistrictNetworkUtil; import org.simantics.district.network.ui.participants.DNPointerInteractor; import org.simantics.district.network.ui.participants.MapRulerPainter; @@ -56,6 +57,7 @@ public class DistrictDiagramViewer extends DiagramViewer { IHintContext h = ctx.getDefaultHintContext(); h.setHint(PanZoomRotateHandler.KEY_ZOOM_IN_LIMIT, 10000.0); h.setHint(PanZoomRotateHandler.KEY_ZOOM_OUT_LIMIT, 0.01); + h.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagramResource); } @Override diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java index e0e7b475..9c56d543 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java @@ -1,16 +1,20 @@ package org.simantics.district.network.ui.internal; -import org.osgi.framework.BundleActivator; +import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; import org.simantics.district.network.ui.breakdown.SubgraphProvider; +import org.simantics.district.route.RouteService; -public class Activator implements BundleActivator { +public class Activator extends AbstractUIPlugin { public static final String PLUGIN_ID = "org.simantics.district.network.ui"; + private static Activator instance; private static BundleContext context; + private ServiceTracker subgraphProviderTracker; + private ServiceTracker routeServiceTracker; @Override public void start(BundleContext context) throws Exception { @@ -19,11 +23,14 @@ public class Activator implements BundleActivator { subgraphProviderTracker = new ServiceTracker<>(context, SubgraphProvider.class.getName(), null); subgraphProviderTracker.open(); + routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null); + routeServiceTracker.open(); } @Override public void stop(BundleContext context) throws Exception { subgraphProviderTracker.close(); + routeServiceTracker.close(); Activator.instance = null; Activator.context = null; @@ -41,4 +48,8 @@ public class Activator implements BundleActivator { return subgraphProviderTracker.getServices(new SubgraphProvider[0]); } + public RouteService getRouteService() { + return routeServiceTracker.getService(); + } + } diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/DNPointerInteractor.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/DNPointerInteractor.java index a1a8ae5a..ac90af7f 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/DNPointerInteractor.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/DNPointerInteractor.java @@ -14,6 +14,9 @@ import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalIn import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.IElement; import org.simantics.scenegraph.g2d.IG2DNode; +import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyReleasedEvent; +import org.simantics.scenegraph.g2d.events.KeyEvent; import org.simantics.scenegraph.g2d.nodes.SingleElementNode; public class DNPointerInteractor extends PointerInteractor { @@ -62,4 +65,13 @@ public class DNPointerInteractor extends PointerInteractor { Set elementsToDrag) { return new DNTranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag); } + + @EventHandler(priority = 1000) + public boolean enterroutingMode(KeyEvent ke) { + if (ke.character == 't' && ke instanceof KeyReleasedEvent) { + getContext().add(new RoutingMode(0)); + } + return false; + } + } diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/Modes.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/Modes.java new file mode 100644 index 00000000..9671f5eb --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/Modes.java @@ -0,0 +1,10 @@ +package org.simantics.district.network.ui.participants; + +import org.simantics.g2d.canvas.IToolMode; +import org.simantics.g2d.canvas.impl.ToolMode; + +public class Modes { + + public static final IToolMode ROUTE_TOOL = new ToolMode("RouteTool"); + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/RoutingMode.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/RoutingMode.java new file mode 100644 index 00000000..d274a617 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/participants/RoutingMode.java @@ -0,0 +1,212 @@ +package org.simantics.district.network.ui.participants; + +import java.awt.AlphaComposite; +import java.awt.Cursor; +import java.util.Collection; + +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.simantics.db.Resource; +import org.simantics.diagram.ui.DiagramModelHints; +import org.simantics.district.network.ui.internal.Activator; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteService; +import org.simantics.district.route.Waypoint; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.IMouseCursorContext; +import org.simantics.g2d.canvas.IMouseCursorHandle; +import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; +import org.simantics.g2d.canvas.impl.HintReflection.HintListener; +import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; +import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; +import org.simantics.g2d.diagram.handler.PickContext; +import org.simantics.g2d.diagram.participant.Selection; +import org.simantics.g2d.diagram.participant.pointertool.AbstractMode; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.g2d.events.Event; +import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; +import org.simantics.scenegraph.g2d.events.command.Command; +import org.simantics.scenegraph.g2d.events.command.CommandEvent; +import org.simantics.scenegraph.g2d.events.command.Commands; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.utils.datastructures.hints.IHintContext.Key; +import org.simantics.utils.datastructures.hints.IHintObservable; +import org.simantics.utils.ui.AdaptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This participant creates routes on district network diagrams using + * {@link RouteService}. + * + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RoutingMode extends AbstractMode { + + private static final Logger LOGGER = LoggerFactory.getLogger(RoutingMode.class); + + @Dependency + protected TransformUtil util; + + @Dependency + protected PickContext pickContext; + + @Dependency + protected Selection selection; + + protected IMouseCursorHandle cursor; + + private RouteService routeService; + + private Route route; + + /** + * The node under which the mutated diagram is ghosted in the scene graph. + */ + protected G2DParentNode parent; + + /** + * This stays null until the translated diagram parts have been initialized + * into the scene graph. After that, only the translations of the nodes in + * the scene graph are modified. + */ + protected SingleElementNode node = null; + + public RoutingMode(int mouseId) { + super(mouseId); + } + + @Override + public void addedToContext(ICanvasContext ctx) { + super.addedToContext(ctx); + IMouseCursorContext mcc = getContext().getMouseCursorContext(); + cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.CROSSHAIR_CURSOR)); + routeService = Activator.getInstance().getRouteService(); + if (routeService != null) { + Resource diagramResource = getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); + LOGGER.info("Diagram resource: " + diagramResource); + route = routeService.createRoute("Current", diagramResource); + routeService.registerRoute(route); + } + } + + @Override + public void removedFromContext(ICanvasContext ctx) { + if (cursor != null) { + cursor.remove(); + cursor = null; + } + // Discard route if it hasn't been persisted before this + if (route != null) { + routeService.discardRoute(route); + route = null; + routeService = null; + } + super.removedFromContext(ctx); + } + + @HintListener(Class = Selection.class, Field = "SELECTION0") + public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { + Collection elements = AdaptionUtils.adaptToCollection(newValue, IElement.class); + for (IElement element : elements) { + addRoutePoint(element); + } + } + + private void addRoutePoint(IElement element) { + if (route == null) + return; + + Object o = ElementUtils.getObject(element); + if (o instanceof Resource) { + Waypoint wp = route.createWaypoint(o); + route.addWaypoint(wp); + } + } + + @SGInit + public void initSG(G2DParentNode parent) { + this.parent = parent; + node = parent.addNode("route highlight", SingleElementNode.class); + node.setZIndex(1000); + node.setVisible(Boolean.TRUE); + node.setComposite(AlphaComposite.SrcOver.derive(0.75f)); + } + + @SGCleanup + public void cleanupSG() { + if (node != null) { + node.remove(); + node = null; + } + parent = null; + } + + @EventHandler(priority = 30) + public boolean handleEvent(Event e) { + if (e instanceof KeyPressedEvent) { + if (route == null) + return false; + KeyPressedEvent kpe = (KeyPressedEvent) e; + if (kpe.keyCode == java.awt.event.KeyEvent.VK_ENTER) { + routeService.persistRoute(route); + route = null; + dispose(); + } + } else if (e instanceof CommandEvent) { + Command cmd = ((CommandEvent) e).command; + if (cmd.equals(Commands.CANCEL)) { + return dispose(); + } else if (cmd.equals(Commands.RENAME)) { + InputDialog dialog = new InputDialog( + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Rename Route", + "Route name", + route.getName(), + s -> s.trim().length() > 0 ? null : "Name must be non-empty"); + if (dialog.open() == Window.OK) { + route.setName(dialog.getValue()); + } + } + } + return false; + } + + protected boolean dispose() { + setDirty(); + remove(); + return false; + } + + static class RenameDialog extends InputDialog { + + public RenameDialog(Shell parent, String title, String message, String initialValue, IInputValidator validator) { + super(parent, title, message, initialValue, validator); + } + + @Override + protected IDialogSettings getDialogBoundsSettings() { + String sectionName = getClass().getName() + "_dialogBounds"; //$NON-NLS-1$ + IDialogSettings settings= Activator.getInstance().getDialogSettings(); + IDialogSettings section= settings.getSection(sectionName); + if (section == null) + section= settings.addNewSection(sectionName); + return section; + } + + @Override + protected int getDialogBoundsStrategy() { + return DIALOG_PERSISTLOCATION; + } + } + +} diff --git a/org.simantics.district.region.ontology/.gitignore b/org.simantics.district.region.ontology/.gitignore new file mode 100644 index 00000000..eb92844f --- /dev/null +++ b/org.simantics.district.region.ontology/.gitignore @@ -0,0 +1 @@ +/graph.tg diff --git a/org.simantics.district.region.ontology/graph.tg b/org.simantics.district.region.ontology/graph.tg deleted file mode 100644 index 2cc8f4c6..00000000 Binary files a/org.simantics.district.region.ontology/graph.tg and /dev/null differ diff --git a/org.simantics.district.route.feature/.project b/org.simantics.district.route.feature/.project new file mode 100644 index 00000000..cc1eb3cd --- /dev/null +++ b/org.simantics.district.route.feature/.project @@ -0,0 +1,17 @@ + + + org.simantics.district.route.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/org.simantics.district.route.feature/build.properties b/org.simantics.district.route.feature/build.properties new file mode 100644 index 00000000..64f93a9f --- /dev/null +++ b/org.simantics.district.route.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/org.simantics.district.route.feature/feature.xml b/org.simantics.district.route.feature/feature.xml new file mode 100644 index 00000000..794f8a4a --- /dev/null +++ b/org.simantics.district.route.feature/feature.xml @@ -0,0 +1,41 @@ + + + + + [Enter Feature Description here.] + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + + + diff --git a/org.simantics.district.route.feature/pom.xml b/org.simantics.district.route.feature/pom.xml new file mode 100644 index 00000000..25d60a1a --- /dev/null +++ b/org.simantics.district.route.feature/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + + org.simantics.district + org.simantics.district.root + 1.0.0-SNAPSHOT + + + org.simantics.district.route.feature + eclipse-feature + 1.0.0-SNAPSHOT + + diff --git a/org.simantics.district.route.ontology/.classpath b/org.simantics.district.route.ontology/.classpath new file mode 100644 index 00000000..eca7bdba --- /dev/null +++ b/org.simantics.district.route.ontology/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.district.route.ontology/.gitignore b/org.simantics.district.route.ontology/.gitignore new file mode 100644 index 00000000..eb92844f --- /dev/null +++ b/org.simantics.district.route.ontology/.gitignore @@ -0,0 +1 @@ +/graph.tg diff --git a/org.simantics.district.route.ontology/.project b/org.simantics.district.route.ontology/.project new file mode 100644 index 00000000..5918c31f --- /dev/null +++ b/org.simantics.district.route.ontology/.project @@ -0,0 +1,34 @@ + + + org.simantics.district.route.ontology + + + + + + org.simantics.graph.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.simantics.graph.nature + + diff --git a/org.simantics.district.route.ontology/META-INF/MANIFEST.MF b/org.simantics.district.route.ontology/META-INF/MANIFEST.MF new file mode 100644 index 00000000..c9623fa0 --- /dev/null +++ b/org.simantics.district.route.ontology/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: http://www.simantics.org/DistrictNetworkRoutes +Bundle-SymbolicName: org.simantics.district.route.ontology +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: Semantum Oy +Require-Bundle: org.simantics.layer0, + org.simantics.district.network.ontology;bundle-version="1.0.0" +Automatic-Module-Name: fi.vtt.apros.district.route.ontology +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Export-Package: org.simantics.district.route.ontology diff --git a/org.simantics.district.route.ontology/build.properties b/org.simantics.district.route.ontology/build.properties new file mode 100644 index 00000000..e85b630a --- /dev/null +++ b/org.simantics.district.route.ontology/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + graph.tg diff --git a/org.simantics.district.route.ontology/graph/DistrictNetworkRoutes.pgraph b/org.simantics.district.route.ontology/graph/DistrictNetworkRoutes.pgraph new file mode 100644 index 00000000..3d8a95b2 --- /dev/null +++ b/org.simantics.district.route.ontology/graph/DistrictNetworkRoutes.pgraph @@ -0,0 +1,10 @@ +L0 = +DN = +DIA = + +DNR = : L0.Ontology + @L0.new + L0.HasResourceClass "org.simantics.district.route.ontology.RouteResource" + +DNR.RouteFolder + + 4.0.0 + + + org.simantics.district + org.simantics.district.root + 1.0.0-SNAPSHOT + + + org.simantics.district.route.ontology + eclipse-plugin + 1.0.0-SNAPSHOT + + diff --git a/org.simantics.district.route.ontology/src/org/simantics/district/route/ontology/RouteResource.java b/org.simantics.district.route.ontology/src/org/simantics/district/route/ontology/RouteResource.java new file mode 100644 index 00000000..def31255 --- /dev/null +++ b/org.simantics.district.route.ontology/src/org/simantics/district/route/ontology/RouteResource.java @@ -0,0 +1,61 @@ +package org.simantics.district.route.ontology; + +import org.simantics.db.RequestProcessor; +import org.simantics.db.Resource; +import org.simantics.db.ReadGraph; +import org.simantics.db.request.Read; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.service.QueryControl; + +public class RouteResource { + + public final Resource Route; + public final Resource RouteFolder; + + public static class URIs { + public static final String Route = "http://www.simantics.org/DistrictNetworkRoutes-1.0/Route"; + public static final String RouteFolder = "http://www.simantics.org/DistrictNetworkRoutes-1.0/RouteFolder"; + } + + public static Resource getResourceOrNull(ReadGraph graph, String uri) { + try { + return graph.getResource(uri); + } catch(DatabaseException e) { + System.err.println(e.getMessage()); + return null; + } + } + + public RouteResource(ReadGraph graph) { + Route = getResourceOrNull(graph, URIs.Route); + RouteFolder = getResourceOrNull(graph, URIs.RouteFolder); + } + + public static RouteResource getInstance(ReadGraph graph) { + Session session = graph.getSession(); + RouteResource ret = session.peekService(RouteResource.class); + if(ret == null) { + QueryControl qc = graph.getService(QueryControl.class); + ret = new RouteResource(qc.getIndependentGraph(graph)); + session.registerService(RouteResource.class, ret); + } + return ret; + } + + public static RouteResource getInstance(RequestProcessor session) throws DatabaseException { + RouteResource ret = session.peekService(RouteResource.class); + if(ret == null) { + ret = session.syncRequest(new Read() { + public RouteResource perform(ReadGraph graph) throws DatabaseException { + QueryControl qc = graph.getService(QueryControl.class); + return new RouteResource(qc.getIndependentGraph(graph)); + } + }); + session.registerService(RouteResource.class, ret); + } + return ret; + } + +} + diff --git a/org.simantics.district.route.ui/.classpath b/org.simantics.district.route.ui/.classpath new file mode 100644 index 00000000..eca7bdba --- /dev/null +++ b/org.simantics.district.route.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.district.route.ui/.project b/org.simantics.district.route.ui/.project new file mode 100644 index 00000000..5ea8ac59 --- /dev/null +++ b/org.simantics.district.route.ui/.project @@ -0,0 +1,28 @@ + + + org.simantics.district.route.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.simantics.district.route.ui/META-INF/MANIFEST.MF b/org.simantics.district.route.ui/META-INF/MANIFEST.MF new file mode 100644 index 00000000..299b2414 --- /dev/null +++ b/org.simantics.district.route.ui/META-INF/MANIFEST.MF @@ -0,0 +1,23 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: District Network Modelling Route UI +Bundle-SymbolicName: org.simantics.district.route.ui;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.simantics.district.route.ui.internal.Activator +Bundle-Vendor: Semantum Oy +Require-Bundle: org.eclipse.e4.ui.di, + org.eclipse.e4.ui.model.workbench, + org.eclipse.e4.core.di.annotations, + org.eclipse.e4.core.services, + org.simantics.ui, + org.simantics.district.route, + org.slf4j.api, + org.eclipse.e4.ui.workbench, + org.eclipse.e4.core.di, + org.eclipse.e4.ui.services +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Automatic-Module-Name: fi.vtt.apros.district.route.ui +Import-Package: javax.annotation;version="1.0.0";resolution:=optional, + javax.inject;version="1.0.0", + org.osgi.service.event;version="1.3.1" +Bundle-ActivationPolicy: lazy diff --git a/org.simantics.district.route.ui/OSGI-INF/l10n/bundle.properties b/org.simantics.district.route.ui/OSGI-INF/l10n/bundle.properties new file mode 100644 index 00000000..6bc47b42 --- /dev/null +++ b/org.simantics.district.route.ui/OSGI-INF/l10n/bundle.properties @@ -0,0 +1,4 @@ +#Properties file for fi.apros.visualization.table +view.name = Routes +command.commandname.1 = Open Routes View +command.commandname.2 = Select Route on Diagram diff --git a/org.simantics.district.route.ui/build.properties b/org.simantics.district.route.ui/build.properties new file mode 100644 index 00000000..e29652c5 --- /dev/null +++ b/org.simantics.district.route.ui/build.properties @@ -0,0 +1,7 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + fragment.e4xmi,\ + OSGI-INF/ diff --git a/org.simantics.district.route.ui/fragment.e4xmi b/org.simantics.district.route.ui/fragment.e4xmi new file mode 100644 index 00000000..d737ba6b --- /dev/null +++ b/org.simantics.district.route.ui/fragment.e4xmi @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.simantics.district.route.ui/plugin.xml b/org.simantics.district.route.ui/plugin.xml new file mode 100644 index 00000000..1d942a28 --- /dev/null +++ b/org.simantics.district.route.ui/plugin.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/org.simantics.district.route.ui/pom.xml b/org.simantics.district.route.ui/pom.xml new file mode 100644 index 00000000..fab4b8f3 --- /dev/null +++ b/org.simantics.district.route.ui/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + + org.simantics.district + org.simantics.district.root + 1.0.0-SNAPSHOT + + + org.simantics.district.route.ui + eclipse-plugin + 1.0.0-SNAPSHOT + + diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/OpenRouteView.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/OpenRouteView.java new file mode 100644 index 00000000..e348eb56 --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/OpenRouteView.java @@ -0,0 +1,19 @@ +package org.simantics.district.route.ui; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +public class OpenRouteView { + + @CanExecute + public boolean canExecute(ESelectionService selectionService) { + return true; + } + + @Execute + public void openPropertyTable(ESelectionService selectionService) { + RouteUI.openRouteView(); + } + +} diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteTree.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteTree.java new file mode 100644 index 00000000..c275b5b5 --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteTree.java @@ -0,0 +1,221 @@ +package org.simantics.district.route.ui; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.simantics.db.Resource; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteEvent; +import org.simantics.district.route.RouteJob; +import org.simantics.district.route.RouteService; +import org.simantics.district.route.RouteServiceListener; +import org.simantics.district.route.Waypoint; +import org.simantics.district.route.ui.internal.Activator; +import org.simantics.utils.ui.SWTUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RouteTree extends Composite { + + private static final Logger LOGGER = LoggerFactory.getLogger(RouteTree.class); + + ESelectionService selectionService; + + private final LocalResourceManager resourceManager; + + private TreeViewer tree; + + public RouteTree(Composite parent, int style, ESelectionService selectionService) { + super(parent, style); + this.selectionService = selectionService; + this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); + defaultInitializeUI(); + trackRouteInput(); + } + + private RouteServiceListener routeServiceListener = e -> { + System.out.println("Route event: " + e); + switch (e.type) { + case RouteEvent.TYPE_ROUTE_DISCARDED: + runUi(() -> tree.refresh()); + break; + + case RouteEvent.TYPE_ROUTE_REGISTERED: + runUi(() -> { + tree.refresh(); + tree.setExpandedState(e.obj, true); + }); + break; + + case RouteEvent.TYPE_ROUTE_MODIFIED: + runUi(() -> { + tree.refresh(e.obj); + tree.setExpandedState(e.obj, true); + }); + break; + + case RouteEvent.TYPE_ROUTE_RENAMED: + runUi(() -> tree.refresh(e.obj)); + break; + + case RouteEvent.TYPE_ROUTE_SOURCE_CHANGED: + runUi(() -> tree.setInput(e.service.listRoutes())); + break; + } + }; + + private void trackRouteInput() { + RouteService rs = Activator.getDefault().getRouteService(); + if (rs != null) { + rs.addListener(routeServiceListener); + addDisposeListener(e -> rs.removeListener(routeServiceListener)); + setInput(rs.listRoutes()); + } + } + + private void defaultInitializeUI() { + GridDataFactory.fillDefaults().grab(true, true).applyTo(this); + GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this); + + createTree(this); + } + + private void createTree(Composite parent) { + tree = new TreeViewer(parent, SWT.SINGLE); + tree.setUseHashlookup(true); + GridDataFactory.fillDefaults().grab(true, true).applyTo(tree.getControl()); + tree.setContentProvider(new ContentProvider()); + tree.setLabelProvider(new LabelProvider()); + tree.addSelectionChangedListener(this::treeSelectionChanged); + //tree.addPostSelectionChangedListener(this::treeSelectionChanged); + tree.addDoubleClickListener(this::itemDoubleClicked); + // TODO: add route renaming (F2), DnD reordering support + } + + private void itemDoubleClicked(DoubleClickEvent e) { + // TODO: default action, i.e. highlight route? + LOGGER.info("double click {}", e); + } + + private void treeSelectionChanged(SelectionChangedEvent e) { + selectionService.setSelection(e.getSelection()); + /* + IStructuredSelection ss = (IStructuredSelection) e.getSelection(); + Object[] arr = ss.toArray(); + if (arr.length == 1) { + Object o = arr[0]; + if (o instanceof Route) { + CompletableFuture> callback = new CompletableFuture<>(); + callback.thenAccept(dnElements -> { + runUi(() -> selectionService.setSelection(new StructuredSelection(dnElements))); + }); + new RouteJob((Route) o, callback).schedule(); + } else if (o instanceof Waypoint) { + selectionService.setSelection(new StructuredSelection(((Waypoint) o).getObject())); + } + } + */ + } + + protected void setInput(List result) { + runUi(() -> { + TreePath[] paths = tree.getExpandedTreePaths(); + tree.setInput(result); + tree.setExpandedTreePaths(paths); + }); + } + + protected void runUi(Runnable r) { + SWTUtils.asyncExec(getDisplay(), () -> { + if (!tree.getControl().isDisposed()) + r.run(); + }); + } + + private static class ContentProvider implements ITreeContentProvider { + + private static final Object[] NONE = {}; + @SuppressWarnings("unused") + private List input; + + @Override + public void dispose() { + input = null; + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + input = newInput instanceof List ? (List) newInput : null; + } + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof List) + return ((List) inputElement).toArray(); + return NONE; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof Route) { + return ((Route) parentElement).waypoints().toArray(); + } + return NONE; + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof Route) { + return !((Route) element).waypoints().isEmpty(); + } + return false; + } + + } + + private static class LabelProvider extends ColumnLabelProvider { + @Override + public String getText(Object element) { + if (element instanceof Route) { + Route r = (Route) element; + return NLS.bind("{0} ({1} waypoints)", r.getName(), r.count()); + } else if (element instanceof Waypoint) { + } + return element.toString(); + } + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + cell.setText(getText(element)); + } + } + +} diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUI.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUI.java new file mode 100644 index 00000000..48349c00 --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUI.java @@ -0,0 +1,17 @@ +package org.simantics.district.route.ui; + +import org.simantics.ui.workbench.e4.E4WorkbenchUtils; + +/** + * Facade + * + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RouteUI { + + public static void openRouteView() { + E4WorkbenchUtils.openAndShowPart("org.simantics.district.route.ui.routeView"); //$NON-NLS-1$ + } + +} diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUIConstants.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUIConstants.java new file mode 100644 index 00000000..2d211d4c --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteUIConstants.java @@ -0,0 +1,16 @@ +package org.simantics.district.route.ui; + +/** + * @author Tuukka Lehtonen + */ +public class RouteUIConstants { + + public static final String TOPIC_ROUTE_UI = "TOPIC_ROUTE_UI"; //$NON-NLS-1$ + public static final String TOPIC_ROUTE_UI_NEW_ROUTE = TOPIC_ROUTE_UI + "/NEW_ROUTE"; //$NON-NLS-1$ + public static final String TOPIC_ROUTE_UI_ADD_POINT = TOPIC_ROUTE_UI + "/ADD_POINT"; //$NON-NLS-1$ + + public static final String EVENT_ROUTE_NAME = "event.route.name"; //$NON-NLS-1$ + public static final String EVENT_ROUTE = "event.route.instance"; //$NON-NLS-1$ + public static final String EVENT_ROUTE_POINT = "event.route.point"; //$NON-NLS-1$ + +} diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteView.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteView.java new file mode 100644 index 00000000..77725b7c --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/RouteView.java @@ -0,0 +1,67 @@ +package org.simantics.district.route.ui; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.menu.MMenuContribution; +import org.eclipse.e4.ui.model.application.ui.menu.MMenuFactory; +import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.swt.widgets.Composite; + +/** + * @author Tuukka Lehtonen + * @since 6.08 + */ +public class RouteView { + + private static final String POPUP_CONTRIBUTION_ID = "org.simantics.district.route.ui.contextMenu"; + public static final String POPUP_ELEMENT_ID = "org.simantics.district.route.ui.popup"; + + @Inject ESelectionService selectionService; + @Inject IEventBroker broker; + RouteTree ui; + + /** + * See + * http://www.vogella.com/tutorials/EclipsePlugin/article.html#adding-eclipse-4-x-parts-to-eclipse-3-x-applications-via-the-code-org-eclipse-ui-views-code-extension-point + * + * Cannot contribute context menu via fragment at this point + */ + @Inject + public void init(MPart part, MApplication app) { + MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu(); + popupMenu.setElementId(POPUP_ELEMENT_ID); + List menuContributions = app.getMenuContributions(); + for (MMenuContribution menuContribution : menuContributions) { + if (POPUP_CONTRIBUTION_ID.equals(menuContribution.getParentId())) { + // ok, add to menu + popupMenu.getChildren().addAll(menuContribution.getChildren()); + } + } + part.getMenus().add(popupMenu); + } + + @PostConstruct + public void postConstruct(Composite parent) { + this.ui = new RouteTree(parent, 0, selectionService); + } + + @PreDestroy + public void dispose() { + ui = null; + } + + @Focus + public void onFocus() { + ui.setFocus(); + } + +} \ No newline at end of file diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/handlers/SelectRouteOnDiagram.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/handlers/SelectRouteOnDiagram.java new file mode 100644 index 00000000..7568ea30 --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/handlers/SelectRouteOnDiagram.java @@ -0,0 +1,38 @@ +package org.simantics.district.route.ui.handlers; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import javax.inject.Named; + +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.widgets.Shell; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteJob; +import org.simantics.district.route.RouterConfiguration; +import org.simantics.utils.ui.ISelectionUtils; + +/** + * @author Tuukka Lehtonen + */ +public class SelectRouteOnDiagram { + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell activeShell, + @Named(IServiceConstants.ACTIVE_PART) MPart part, + @Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection) { + // get selected route + Route route = ISelectionUtils.filterSingleSelection(selection, Route.class); + if (route == null) + return; + + CompletableFuture> result = new CompletableFuture<>(); + RouterConfiguration config = new RouterConfiguration(); + new RouteJob(config, route, result).schedule(); + } + +} \ No newline at end of file diff --git a/org.simantics.district.route.ui/src/org/simantics/district/route/ui/internal/Activator.java b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/internal/Activator.java new file mode 100644 index 00000000..c4325083 --- /dev/null +++ b/org.simantics.district.route.ui/src/org/simantics/district/route/ui/internal/Activator.java @@ -0,0 +1,60 @@ +package org.simantics.district.route.ui.internal; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; +import org.simantics.district.route.RouteService; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.simantics.district.route.ui"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + private ServiceTracker routeServiceTracker; + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + + // create a tracker and track the service + routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null); + routeServiceTracker.open(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + // close the service tracker + routeServiceTracker.close(); + routeServiceTracker = null; + + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + + public RouteService getRouteService() { + return (RouteService) routeServiceTracker.getService(); + } + +} diff --git a/org.simantics.district.route/.classpath b/org.simantics.district.route/.classpath new file mode 100644 index 00000000..eca7bdba --- /dev/null +++ b/org.simantics.district.route/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.district.route/.project b/org.simantics.district.route/.project new file mode 100644 index 00000000..562165d7 --- /dev/null +++ b/org.simantics.district.route/.project @@ -0,0 +1,28 @@ + + + org.simantics.district.route + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.simantics.district.route/META-INF/MANIFEST.MF b/org.simantics.district.route/META-INF/MANIFEST.MF new file mode 100644 index 00000000..48dce696 --- /dev/null +++ b/org.simantics.district.route/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: District Network Routing +Bundle-SymbolicName: org.simantics.district.route +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.simantics.district.route.internal.Activator +Bundle-Vendor: Semantum Oy +Require-Bundle: org.eclipse.core.runtime, + org.simantics.db.layer0;bundle-version="1.1.0", + org.simantics;bundle-version="1.0.0", + org.simantics.district.route.ontology;bundle-version="1.0.0", + org.slf4j.api;bundle-version="1.7.25", + org.simantics.diagram.ontology, + org.simantics.district.network.ontology +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Automatic-Module-Name: fi.vtt.apros.district.route +Bundle-ActivationPolicy: lazy +Export-Package: org.simantics.district.route diff --git a/org.simantics.district.route/build.properties b/org.simantics.district.route/build.properties new file mode 100644 index 00000000..34d2e4d2 --- /dev/null +++ b/org.simantics.district.route/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.simantics.district.route/pom.xml b/org.simantics.district.route/pom.xml new file mode 100644 index 00000000..46943e84 --- /dev/null +++ b/org.simantics.district.route/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + + org.simantics.district + org.simantics.district.root + 1.0.0-SNAPSHOT + + + org.simantics.district.route + eclipse-plugin + 1.0.0-SNAPSHOT + + diff --git a/org.simantics.district.route/src/org/simantics/district/route/Route.java b/org.simantics.district.route/src/org/simantics/district/route/Route.java new file mode 100644 index 00000000..fcd91171 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/Route.java @@ -0,0 +1,47 @@ +package org.simantics.district.route; + +import java.util.Collections; +import java.util.List; + +/** + * @author Tuukka Lehtonen + */ +public interface Route { + + default String getName() { + return "empty"; //$NON-NLS-1$ + } + + void setName(String name); + + Waypoint createWaypoint(Object backend); + + default void addWaypoint(Waypoint r) { + addWaypoint(count(), r); + } + + default void addWaypoint(int index, Waypoint r) { + throw new UnsupportedOperationException(); + } + + /** + * Removes the specified waypoint from the route + */ + default void removeWaypoint(Waypoint r) { + throw new UnsupportedOperationException(); + } + + /** + * @return amount of points in the route + */ + default int count() { + return waypoints().size(); + } + + default List waypoints() { + return Collections.emptyList(); + } + + // TODO: reorder route points + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RouteEvent.java b/org.simantics.district.route/src/org/simantics/district/route/RouteEvent.java new file mode 100644 index 00000000..f4845960 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RouteEvent.java @@ -0,0 +1,54 @@ +package org.simantics.district.route; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RouteEvent { + + public static final int TYPE_ROUTER_REGISTERED = 1; + public static final int TYPE_ROUTER_UNREGISTERED = 2; + + public static final int TYPE_ROUTE_CREATED = 3; + public static final int TYPE_ROUTE_REGISTERED = 4; + public static final int TYPE_ROUTE_DISCARDING = 5; + public static final int TYPE_ROUTE_DISCARDED = 6; + public static final int TYPE_ROUTE_RENAMED = 7; + public static final int TYPE_ROUTE_MODIFIED = 8; + public static final int TYPE_ROUTE_PERSISTING = 9; + public static final int TYPE_ROUTE_PERSISTED = 10; + public static final int TYPE_ROUTE_SOURCE_CHANGED = 11; + + public final RouteService service; + public final int type; + public final Object obj; + + public RouteEvent(RouteService service, int type, Object obj) { + this.service = service; + this.type = type; + this.obj = obj; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[type=" + typeString(type) + ", obj=" + obj + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + private static String typeString(int type) { + switch (type) { + case TYPE_ROUTER_REGISTERED: return "ROUTER REGISTERED"; //$NON-NLS-1$ + case TYPE_ROUTER_UNREGISTERED: return "ROUTER UNREGISTERED"; //$NON-NLS-1$ + case TYPE_ROUTE_CREATED: return "ROUTE CREATED"; //$NON-NLS-1$ + case TYPE_ROUTE_REGISTERED: return "ROUTE REGISTERED"; //$NON-NLS-1$ + case TYPE_ROUTE_DISCARDING: return "ROUTE DISCARDING"; //$NON-NLS-1$ + case TYPE_ROUTE_DISCARDED: return "ROUTE DISCARDED"; //$NON-NLS-1$ + case TYPE_ROUTE_RENAMED: return "ROUTE RENAMED"; //$NON-NLS-1$ + case TYPE_ROUTE_MODIFIED: return "ROUTE MODIFIED"; //$NON-NLS-1$ + case TYPE_ROUTE_PERSISTING: return "ROUTE PERSISTING"; //$NON-NLS-1$ + case TYPE_ROUTE_PERSISTED: return "ROUTE PERSISTED"; //$NON-NLS-1$ + case TYPE_ROUTE_SOURCE_CHANGED: return "ROUTE SOURCE CHANGED"; //$NON-NLS-1$ + default: return "" + type; //$NON-NLS-1$ + } + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RouteJob.java b/org.simantics.district.route/src/org/simantics/district/route/RouteJob.java new file mode 100644 index 00000000..84414937 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RouteJob.java @@ -0,0 +1,60 @@ +package org.simantics.district.route; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.simantics.ObjectIdentitySchedulingRule; +import org.simantics.db.Resource; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.district.route.internal.Activator; +import org.simantics.district.route.internal.RoutePersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RouteJob extends Job { + + private static final Logger LOGGER = LoggerFactory.getLogger(RouteJob.class); + + private RouterConfiguration config; + private List waypoints; + private CompletableFuture> callback; + + public RouteJob(RouterConfiguration config, Route route, CompletableFuture> callback) { + super("Compute route"); + Objects.requireNonNull(callback, "Callback must be non-null"); + setUser(true); + setRule(new ObjectIdentitySchedulingRule(route)); + this.config = config; + this.waypoints = RoutePersistence.toResources(route.waypoints()); + this.callback = callback; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + List routers = Activator.getInstance().getRouteService().routers(); + for (Router router : routers) { + try { + List path = router.findShortestPath(config, waypoints); + if (!path.isEmpty()) { + callback.complete(path); + return Status.OK_STATUS; + } + } catch (RoutingException e) { + LOGGER.error("Routing failed for waypoints {} and router {}", waypoints, router, e); + callback.completeExceptionally(e); + } + } + LOGGER.info("No router could calculate route between waypoints {}. Available routers: {}", waypoints, routers); + return Status.OK_STATUS; + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RouteService.java b/org.simantics.district.route/src/org/simantics/district/route/RouteService.java new file mode 100644 index 00000000..5c7180b6 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RouteService.java @@ -0,0 +1,31 @@ +package org.simantics.district.route; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * @author Tuukka Lehtonen + */ +public interface RouteService { + + void addListener(RouteServiceListener l); + + void removeListener(RouteServiceListener l); + + Route createRoute(String name, Object backendModelEntity); + + void registerRoute(Route route); + + CompletableFuture persistRoute(Route route); + + CompletableFuture discardRoute(Route route); + + List listRoutes(); + + void registerRouter(Router router); + + void unregisterRouter(Router router); + + List routers(); + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RouteServiceListener.java b/org.simantics.district.route/src/org/simantics/district/route/RouteServiceListener.java new file mode 100644 index 00000000..d26ae22c --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RouteServiceListener.java @@ -0,0 +1,12 @@ +package org.simantics.district.route; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +@FunctionalInterface +public interface RouteServiceListener { + + void handleEvent(RouteEvent e); + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/Router.java b/org.simantics.district.route/src/org/simantics/district/route/Router.java new file mode 100644 index 00000000..1d242322 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/Router.java @@ -0,0 +1,32 @@ +package org.simantics.district.route; + +import java.util.List; + +import org.simantics.db.Resource; +import org.simantics.db.layer0.variable.Variable; + +/** + * @author Tuukka Lehtonen + */ +public interface Router { + + /** + * @return descriptive name of the router + */ + default String name() { + return toString(); + } + + /** + * Must be invoked outside of any transaction realm, like a database request or + * experiment thread, preferably from a background job thread. + * + * @param wayPoints waypoints for the route to find in visiting order. The + * resources must represents district network diagram elements. + * @return the piece-wise shortest path between the specified waypoints as a + * fully baked path of district network diagram element resources + * @throws RoutingException in case of any problems in routing + */ + List findShortestPath(RouterConfiguration config, List wayPoints) throws RoutingException; + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RouterConfiguration.java b/org.simantics.district.route/src/org/simantics/district/route/RouterConfiguration.java new file mode 100644 index 00000000..c9ecb3e4 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RouterConfiguration.java @@ -0,0 +1,12 @@ +package org.simantics.district.route; + +import java.util.HashMap; + +/** + * @author Tuukka Lehtonen + */ +public class RouterConfiguration extends HashMap { + + private static final long serialVersionUID = -6852913957185485390L; + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/RoutingException.java b/org.simantics.district.route/src/org/simantics/district/route/RoutingException.java new file mode 100644 index 00000000..e47365ec --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/RoutingException.java @@ -0,0 +1,19 @@ +package org.simantics.district.route; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RoutingException extends Exception { + + private static final long serialVersionUID = -718352796086955155L; + + public RoutingException(String message) { + super(message); + } + + public RoutingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/Waypoint.java b/org.simantics.district.route/src/org/simantics/district/route/Waypoint.java new file mode 100644 index 00000000..62fd4507 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/Waypoint.java @@ -0,0 +1,11 @@ +package org.simantics.district.route; + +/** + * @author Tuukka Lehtonen + */ +public interface Waypoint { + + String getLabel(); + T getObject(); + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/Activator.java b/org.simantics.district.route/src/org/simantics/district/route/internal/Activator.java new file mode 100644 index 00000000..f26d0cd7 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/Activator.java @@ -0,0 +1,95 @@ +package org.simantics.district.route.internal; + +import java.util.Hashtable; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.ServiceTracker; +import org.simantics.district.route.RouteService; +import org.simantics.district.route.Router; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class Activator implements BundleActivator, ServiceListener { + + private static BundleContext context; + private static Activator instance; + + static BundleContext getContext() { + return context; + } + + public static Activator getInstance() { + return instance; + } + + private RouteServiceImpl routeService; + private ServiceRegistration routeServiceRegistration; + private ServiceTracker routeServiceTracker; + private ServiceTracker routerTracker; + + @Override + public void start(BundleContext context) throws Exception { + Activator.instance = this; + Activator.context = context; + routeService = new RouteServiceImpl(); + + // register the service + routeServiceRegistration = context.registerService(RouteService.class.getName(), routeService, new Hashtable<>()); + + // create a tracker and track the service + routeServiceTracker = new ServiceTracker<>(context, RouteService.class.getName(), null); + routeServiceTracker.open(); + + // Register initially availble routers + routerTracker = new ServiceTracker<>(context, Router.class.getName(), null); + routerTracker.open(); + for (Object router : routerTracker.getServices()) { + routeService.registerRouter((Router) router); + } + + // have a service listener to implement the whiteboard pattern + context.addServiceListener(this, "(" + Constants.OBJECTCLASS + "=" + Router.class.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + + // grab the service + routeService = (RouteServiceImpl) routeServiceTracker.getService(); + } + + @Override + public void stop(BundleContext context) throws Exception { + // close the service tracker + routeServiceTracker.close(); + routeServiceTracker = null; + + routeServiceRegistration.unregister(); + + routeService.close(); + routeService = null; + context = null; + instance = null; + } + + public void serviceChanged(ServiceEvent ev) { + ServiceReference sr = ev.getServiceReference(); + switch (ev.getType()) { + case ServiceEvent.REGISTERED: + routeService.registerRouter((Router) context.getService(sr)); + break; + case ServiceEvent.UNREGISTERING: + routeService.unregisterRouter((Router) context.getService(sr)); + break; + } + } + + public RouteService getRouteService() { + return routeService; + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/RouteImpl.java b/org.simantics.district.route/src/org/simantics/district/route/internal/RouteImpl.java new file mode 100644 index 00000000..3619b602 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/RouteImpl.java @@ -0,0 +1,107 @@ +package org.simantics.district.route.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.simantics.db.Resource; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteEvent; +import org.simantics.district.route.Waypoint; + +public class RouteImpl implements Route { + + private String name; + private Resource model; + private Resource backend; + + private RouteServiceImpl rs; + + private List waypoints; + private List unmodifiableWaypoints; + + public RouteImpl(String name) { + this.name = name; + routeService(rs); + waypoints(new ArrayList<>()); + } + + public RouteImpl modelEntity(Resource model) { + this.model = model; + return this; + } + + public Resource modelEntity() { + return model; + } + + public RouteImpl backend(Resource backend) { + this.backend = backend; + return this; + } + + public Resource backend() { + return backend; + } + + public RouteImpl waypoints(List waypoints) { + this.waypoints = waypoints; + this.unmodifiableWaypoints = Collections.unmodifiableList(waypoints); + return this; + } + + public RouteImpl routeService(RouteServiceImpl rs) { + this.rs = rs; + return this; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + fireEvent(RouteEvent.TYPE_ROUTE_RENAMED); + } + + @Override + public Waypoint createWaypoint(Object backend) { + if (backend instanceof Resource) + return new WaypointImpl((Resource) backend, "Point 1"); + throw new IllegalArgumentException("only Resource type waypoints supported, got " + backend); //$NON-NLS-1$ + } + + @Override + public void addWaypoint(int index, Waypoint r) { + waypoints.add(index, r); + fireEvent(RouteEvent.TYPE_ROUTE_MODIFIED); + } + + @Override + public void removeWaypoint(Waypoint r) { + waypoints.remove(r); + fireEvent(RouteEvent.TYPE_ROUTE_MODIFIED); + } + + @Override + public List waypoints() { + return unmodifiableWaypoints; + } + + @Override + public String toString() { + return String.format("%s@%x [%s, waypoints=%s]", + getClass().getName(), + System.identityHashCode(this), + name, + waypoints.toString()); + } + + private void fireEvent(int type) { + if (rs != null) + rs.fireEvent(type, this); + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistence.java b/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistence.java new file mode 100644 index 00000000..c3ba7571 --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistence.java @@ -0,0 +1,160 @@ +package org.simantics.district.route.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.eclipse.osgi.util.NLS; +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.databoard.util.ObjectUtils; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.common.utils.ListUtils; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.QueryIndexUtils; +import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.db.layer0.util.RemoverUtil; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.route.Waypoint; +import org.simantics.district.route.ontology.RouteResource; +import org.simantics.layer0.Layer0; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + */ +public class RoutePersistence { + + private static final Logger LOGGER = LoggerFactory.getLogger(RoutePersistence.class); + + public static Resource getRouteFolder(ReadGraph graph, Resource model) throws DatabaseException { + List diagrams = QueryIndexUtils.searchByType(graph, model, RouteResource.getInstance(graph).RouteFolder); + if (diagrams.size() > 0) + return diagrams.get(0); + return null; + } + + public static Resource getOrCreateRouteFolder(WriteGraph graph, Resource model) throws DatabaseException { + Resource rf = getRouteFolder(graph, model); + if (rf == null) { + Layer0 L0 = Layer0.getInstance(graph); + RouteResource RR = RouteResource.getInstance(graph); + rf = graph.newResource(); + graph.claim(rf, L0.InstanceOf, null, RR.RouteFolder); + graph.claimLiteral(rf, L0.HasName, L0.NameOf, L0.String, UUID.randomUUID().toString(), Bindings.STRING); + graph.claim(model, L0.ConsistsOf, L0.PartOf, rf); + } + return rf; + } + + public static Resource createRoute(WriteGraph graph, Resource model, String label, List waypoints) throws DatabaseException { + Resource rf = getOrCreateRouteFolder(graph, model); + + RouteResource RR = RouteResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + + Resource route = ListUtils.create(graph, RR.Route, waypoints); + graph.claim(rf, L0.ConsistsOf, L0.PartOf, route); + graph.claimLiteral(route, L0.HasName, UUID.randomUUID().toString(), Bindings.STRING); + graph.claimLiteral(route, L0.HasLabel, label, Bindings.STRING); + + LOGGER.info("Persisted route {} with label {} and waypoints {}", route, label, waypoints); + + return route; + } + + public static void updateRoute(WriteGraph graph, Resource route, String label, List waypoints) throws DatabaseException { + RouteResource RR = RouteResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + + String existingLabel = graph.getPossibleRelatedValue(route, L0.HasLabel, Bindings.STRING); + if (ObjectUtils.objectEquals(existingLabel, label)) { + graph.claimLiteral(route, L0.HasLabel, label, Bindings.STRING); + } + + List existingWaypoints = ListUtils.toList(graph, route); + if (!existingWaypoints.equals(waypoints)) { + // TODO: optimize this + ListUtils.removeElements(graph, route, new HashSet<>(existingWaypoints)); + ListUtils.createExisting(graph, RR.Route, waypoints); + } + + LOGGER.info("Updated route {} with label {} and waypoints {}", route, label, waypoints); + } + + public static void removeRoute(WriteGraph graph, Resource route) throws DatabaseException { + RemoverUtil.remove(graph, route); + } + + public static Waypoint toWaypoint(ReadGraph graph, Resource waypoint) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + Set types = graph.getTypes(waypoint); + String name = NameUtils.getSafeName(graph, waypoint); + if (types.contains(DN.Vertex)) { + String address = graph.getPossibleRelatedValue(waypoint, DN.Vertex_HasAddress, Bindings.STRING); + String label = address != null + ? NLS.bind("Node {0} - {1}", name, address).trim() + : NLS.bind("Node {0}", name, address).trim(); + return new WaypointImpl(waypoint, label); + } else if (types.contains(DN.Edge)) { + return new WaypointImpl(waypoint, NLS.bind("Edge {0}", name)); + } + LOGGER.warn("Tried to convert unrecognized resource {} to a route waypoint", waypoint); + return null; + } + + public static List toWaypoints(ReadGraph graph, List waypoints) throws DatabaseException { + List result = new ArrayList<>(); + for (Resource wpr : waypoints) { + Waypoint wp = toWaypoint(graph, wpr); + if (wp != null) + result.add(wp); + } + return result; + } + + public static List findRoutes(ReadGraph graph, Resource model) throws DatabaseException { + Resource rf = getRouteFolder(graph, model); + if (rf == null) + return Collections.emptyList(); + + Layer0 L0 = Layer0.getInstance(graph); + RouteResource RR = RouteResource.getInstance(graph); + + List routes = new ArrayList<>(); + + for (Resource route : graph.syncRequest(new ObjectsWithType(rf, L0.ConsistsOf, RR.Route))) { + RouteImpl ri = new RouteImpl(graph.getRelatedValue(route, L0.HasLabel, Bindings.STRING)) + .backend(route) + .modelEntity(model) + .waypoints(toWaypoints(graph, ListUtils.toList(graph, route))); + routes.add(ri); + } + + return routes; + } + + public static class ActiveModelRoutesRequest extends UniqueRead> { + @Override + public List perform(ReadGraph graph) throws DatabaseException { + return findRoutes(graph, graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()))); + } + } + + public static List toResources(List waypoints) { + return waypoints.stream() + .map(Waypoint::getObject) + .collect(Collectors.toList()); + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistenceJob.java b/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistenceJob.java new file mode 100644 index 00000000..037b590c --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/RoutePersistenceJob.java @@ -0,0 +1,94 @@ +package org.simantics.district.route.internal; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.simantics.DatabaseJob; +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleModel; +import org.simantics.db.request.Write; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class RoutePersistenceJob extends DatabaseJob { + + private static final Logger LOGGER = LoggerFactory.getLogger(RoutePersistenceJob.class); + + private RouteImpl route; + private int eventType; + private CompletableFuture future; + + public RoutePersistenceJob(RouteImpl route, int eventType, CompletableFuture future) { + super("Route Persistence"); + this.route = route; + this.eventType = eventType; + this.future = future; + setUser(false); + setSystem(true); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + Simantics.getSession().syncRequest((Write) graph -> persist(graph)); + future.complete(route); + } catch (DatabaseException e) { + LOGGER.error("Failed to persist route {}", route, e); + future.completeExceptionally(e); + } + return Status.OK_STATUS; + } + + private void persist(WriteGraph graph) throws DatabaseException { + switch (eventType) { + case RouteEvent.TYPE_ROUTE_DISCARDING: + remove(graph); + break; + + case RouteEvent.TYPE_ROUTE_PERSISTING: + write(graph); + break; + } + } + + private void write(WriteGraph graph) throws DatabaseException { + graph.markUndoPoint(); + Resource r = route.backend(); + Resource me = route.modelEntity(); + if (r == null) { + Resource model = graph.sync(new PossibleModel(me)); + route.backend( + RoutePersistence.createRoute(graph, + model, + route.getName(), + RoutePersistence.toResources(route.waypoints())) + ); + } else { + RoutePersistence.updateRoute(graph, + r, + route.getName(), + RoutePersistence.toResources(route.waypoints()) + ); + } + } + + private void remove(WriteGraph graph) throws DatabaseException { + graph.markUndoPoint(); + Resource r = route.backend(); + if (r != null) { + RoutePersistence.removeRoute(graph, r); + } + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/RouteServiceImpl.java b/org.simantics.district.route/src/org/simantics/district/route/internal/RouteServiceImpl.java new file mode 100644 index 00000000..e37a803b --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/RouteServiceImpl.java @@ -0,0 +1,195 @@ +package org.simantics.district.route.internal; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.ListenerList; +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.db.management.ISessionContext; +import org.simantics.db.management.ISessionContextChangedListener; +import org.simantics.db.management.SessionContextChangedEvent; +import org.simantics.db.procedure.SyncListener; +import org.simantics.district.route.Route; +import org.simantics.district.route.RouteEvent; +import org.simantics.district.route.RouteService; +import org.simantics.district.route.RouteServiceListener; +import org.simantics.district.route.Router; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + */ +public class RouteServiceImpl implements RouteService, ISessionContextChangedListener, Closeable { + + private final Logger LOGGER = LoggerFactory.getLogger(RouteServiceImpl.class); + + private ListenerList listeners = new ListenerList<>(); + private List routes = new ArrayList<>(); + private List unmodifiableRoutes = Collections.unmodifiableList(routes); + private List routers = new ArrayList<>(); + private List unmodifiableRouters = Collections.unmodifiableList(routers); + + private class StoreListener implements SyncListener { + private boolean disposed = false; + + @Override + public void execute(ReadGraph graph, Resource activeModel) { + try { + if (activeModel != null) { + resetRoutes(RoutePersistence.findRoutes(graph, activeModel)); + } else { + resetRoutes(Collections.emptyList()); + } + fireEvent(RouteEvent.TYPE_ROUTE_SOURCE_CHANGED, this); + } catch (DatabaseException e) { + LOGGER.error("Failed to read routes from model {}", activeModel, e); + } + } + + @Override + public void exception(ReadGraph graph, Throwable t) { + LOGGER.error("Failed to listen to current route service storage", t); + } + + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + } + + private StoreListener storeListener; + + private synchronized void listenToActiveModels(Session s) { + StoreListener sl = storeListener; + if (sl != null) + sl.dispose(); + if (s != null) { + s.asyncRequest( + new PossibleActiveModel(Simantics.getProjectResource()), + storeListener = new StoreListener()); + } else { + resetRoutes(Collections.emptyList()); + } + } + + void resetRoutes(List newRoutes) { + routes.clear(); + newRoutes.forEach(r -> r.routeService(this)); + routes.addAll(newRoutes); + } + + @Override + public void sessionContextChanged(SessionContextChangedEvent event) { + ISessionContext ctx = event.getNewValue(); + Session s = ctx != null ? ctx.getSession() : null; + listenToActiveModels(s); + } + + public RouteServiceImpl() { + Session s = Simantics.peekSession(); + Simantics.getSessionContextProvider().addContextChangedListener(this); + if (s != null) + listenToActiveModels(s); + } + + @Override + public void close() { + Simantics.getSessionContextProvider().removeContextChangedListener(this); + } + + @Override + public void addListener(RouteServiceListener l) { + listeners.add(l); + } + + @Override + public void removeListener(RouteServiceListener l) { + listeners.remove(l); + } + + @Override + public Route createRoute(String name, Object backendModelEntity) { + Route r = new RouteImpl(name).modelEntity((Resource) backendModelEntity).routeService(this); + fireEvent(RouteEvent.TYPE_ROUTE_CREATED, r); + return r; + } + + @Override + public void registerRoute(Route route) { + routes.add(route); + fireEvent(RouteEvent.TYPE_ROUTE_REGISTERED, route); + } + + @Override + public CompletableFuture persistRoute(Route route) { + fireEvent(RouteEvent.TYPE_ROUTE_PERSISTING, route); + CompletableFuture future = new CompletableFuture<>(); + new RoutePersistenceJob((RouteImpl) route, RouteEvent.TYPE_ROUTE_PERSISTING, future).schedule(); + future.thenAccept(r -> fireEvent(RouteEvent.TYPE_ROUTE_PERSISTED, r)); + return future; + } + + @Override + public CompletableFuture discardRoute(Route route) { + fireEvent(RouteEvent.TYPE_ROUTE_DISCARDING, route); + CompletableFuture result = new CompletableFuture<>(); + RouteImpl ri = (RouteImpl) route; + result.thenAccept(r -> { + routes.remove(route); + fireEvent(RouteEvent.TYPE_ROUTE_DISCARDED, route); + }); + if (ri.backend() != null) { + new RoutePersistenceJob(ri, RouteEvent.TYPE_ROUTE_DISCARDING, result).schedule(); + } else { + result.complete(route); + } + return result; + } + + @Override + public List listRoutes() { + return unmodifiableRoutes; + } + + @Override + public void registerRouter(Router router) { + routers.add(router); + fireEvent(RouteEvent.TYPE_ROUTER_REGISTERED, router); + } + + @Override + public void unregisterRouter(Router router) { + routers.add(router); + fireEvent(RouteEvent.TYPE_ROUTER_UNREGISTERED, router); + } + + public List routers() { + return unmodifiableRouters; + } + + void fireEvent(int type, Object obj) { + RouteEvent e = new RouteEvent(this, type, obj); + LOGGER.info("firing route event {}", e); + listeners.forEach(l -> { + try { + l.handleEvent(e); + } catch (Exception | LinkageError | AssertionError ex) { + LOGGER.error("Failed to invoke RouteListener {}", l, ex); + } + }); + } + +} diff --git a/org.simantics.district.route/src/org/simantics/district/route/internal/WaypointImpl.java b/org.simantics.district.route/src/org/simantics/district/route/internal/WaypointImpl.java new file mode 100644 index 00000000..d78f0c3b --- /dev/null +++ b/org.simantics.district.route/src/org/simantics/district/route/internal/WaypointImpl.java @@ -0,0 +1,67 @@ +package org.simantics.district.route.internal; + +import java.util.Objects; + +import org.simantics.db.Resource; +import org.simantics.district.route.Waypoint; + +/** + * @author Tuukka Lehtonen + * @since 6.09 + */ +public class WaypointImpl implements Waypoint { + + private final Resource element; + private String label; + + public WaypointImpl(Resource element, String label) { + Objects.requireNonNull(element, "Non-null element required"); + this.element = element; + this.label = label; + } + + @SuppressWarnings("unchecked") + @Override + public T getObject() { + return (T) element; + } + + @Override + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + + public Waypoint withLabel(String label) { + return new WaypointImpl(element, label); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + element.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + WaypointImpl other = (WaypointImpl) obj; + return element.equals(other.element); + } + +} diff --git a/pom.xml b/pom.xml index 7834a65a..65e92a3b 100644 --- a/pom.xml +++ b/pom.xml @@ -92,11 +92,15 @@ org.simantics.district.region org.simantics.district.region.ontology org.simantics.district.region.ui + org.simantics.district.route + org.simantics.district.route.ontology + org.simantics.district.route.ui org.simantics.maps.server org.simantics.maps.server.ui org.simantics.district.feature org.simantics.district.regions.feature + org.simantics.district.route.feature org.simantics.district.ui.feature org.simantics.maps.server.feature