From: jsimomaa Date: Wed, 12 Aug 2020 14:35:56 +0000 (+0300) Subject: TechTypeTable features X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F45%2F4445%2F1;p=simantics%2Fdistrict.git TechTypeTable features Squashed cherry pick of change ids * I552cfac33369bf960342836428bc005ed3b1505f * I24f2b651176a7b9aa8b8871cf8e49473350d9193 * I37e9db42c3e7b4a95ba12bed87107cddccd66bc1 * Iee2cae3751983d059216fea7ec11fd47a9e9dda4 * Iba22b4ea4db7a772664e2b76ab44a88a70ea8e80 * I7f7dd9aae0d90b00250ecf42e01ce566e57544d0 * I8550ac1edb7d650211c51ce6379487788001569c * Ia5ac3f513adfd57d4a2e499c9a7dd4ef77355e9f gitlab #93 Change-Id: I33967fb28b9ec41364d68787c359d6c363089b65 --- diff --git a/org.simantics.district.imports/src/org/simantics/district/imports/DistrictImportUtils.java b/org.simantics.district.imports/src/org/simantics/district/imports/DistrictImportUtils.java index 836ebd02..1ff69279 100644 --- a/org.simantics.district.imports/src/org/simantics/district/imports/DistrictImportUtils.java +++ b/org.simantics.district.imports/src/org/simantics/district/imports/DistrictImportUtils.java @@ -1,6 +1,8 @@ package org.simantics.district.imports; import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -64,7 +66,7 @@ public class DistrictImportUtils { public static Map readCSVHeader(Path source, CSVFormat format, boolean firstAsHeader) throws IOException { if (firstAsHeader) format = format.withFirstRecordAsHeader(); - try (CSVParser parser = format.parse(Files.newBufferedReader(source))) { + try (CSVParser parser = format.parse(Files.newBufferedReader(source, Charset.defaultCharset()))) { return parser.getHeaderMap(); } } @@ -105,11 +107,16 @@ public class DistrictImportUtils { } public static void consumeCSV(Path source, char delim, boolean firstAsHeader, Function consumer) throws IOException { - CSVFormat format = CSVFormat.newFormat(delim); + consumeCSV(Files.newBufferedReader(source), delim, firstAsHeader, consumer); + } + + public static void consumeCSV(Reader reader, char delim, boolean firstAsHeader, + Function consumer) throws IOException { + CSVFormat format = CSVFormat.newFormat(delim).withQuote('"'); if (firstAsHeader) { format = format.withFirstRecordAsHeader(); } - try (CSVParser parser = format.parse(Files.newBufferedReader(source))) { + try (CSVParser parser = format.parse(reader)) { Iterator records = parser.iterator(); while (records.hasNext()) { Boolean cont = consumer.apply(records.next()); diff --git a/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph b/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph new file mode 100644 index 00000000..96865b34 --- /dev/null +++ b/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph @@ -0,0 +1,23 @@ +L0 = +STR = +SEL = + +DN = + +TT = DN.TechType : L0.Library + +table = TT.TechTypeTable -- table.HasData L0.String + + // Link to corresponding component type + >-- table.HasComponentType STR.ComponentType + +TT.Functions : L0.Library +TT.Functions.techTypeCodeValueAccessor : L0.ExternalValue + L0.HasValueType "ValueAccessor" + +TT.TechTypeCodeParameterType : SEL.GenericParameterType + @L0.assert L0.valueAccessor TT.Functions.techTypeCodeValueAccessor diff --git a/org.simantics.district.network.ontology/src/org/simantics/district/network/ontology/DistrictNetworkResource.java b/org.simantics.district.network.ontology/src/org/simantics/district/network/ontology/DistrictNetworkResource.java index d12c2755..1b4d4e24 100644 --- a/org.simantics.district.network.ontology/src/org/simantics/district/network/ontology/DistrictNetworkResource.java +++ b/org.simantics.district.network.ontology/src/org/simantics/district/network/ontology/DistrictNetworkResource.java @@ -376,6 +376,14 @@ public class DistrictNetworkResource { public final Resource SupplyConnectionType; public final Resource SupplyInConnectionType; public final Resource SupplyOutConnectionType; + public final Resource TechType; + public final Resource TechType_Functions; + public final Resource TechType_Functions_techTypeCodeValueAccessor; + public final Resource TechType_TechTypeCodeParameterType; + public final Resource TechType_TechTypeTable; + public final Resource TechType_TechTypeTable_HasComponentType; + public final Resource TechType_TechTypeTable_HasData; + public final Resource TechType_TechTypeTable_HasData_Inverse; public final Resource Vertex; public final Resource VertexDefaultMapping; public final Resource VertexDefaultMapping_Inverse; @@ -828,6 +836,14 @@ public class DistrictNetworkResource { public static final String SupplyConnectionType = "http://www.simantics.org/DistrictNetwork-1.0/SupplyConnectionType"; public static final String SupplyInConnectionType = "http://www.simantics.org/DistrictNetwork-1.0/SupplyInConnectionType"; public static final String SupplyOutConnectionType = "http://www.simantics.org/DistrictNetwork-1.0/SupplyOutConnectionType"; + public static final String TechType = "http://www.simantics.org/DistrictNetwork-1.0/TechType"; + public static final String TechType_Functions = "http://www.simantics.org/DistrictNetwork-1.0/TechType/Functions"; + public static final String TechType_Functions_techTypeCodeValueAccessor = "http://www.simantics.org/DistrictNetwork-1.0/TechType/Functions/techTypeCodeValueAccessor"; + public static final String TechType_TechTypeCodeParameterType = "http://www.simantics.org/DistrictNetwork-1.0/TechType/TechTypeCodeParameterType"; + public static final String TechType_TechTypeTable = "http://www.simantics.org/DistrictNetwork-1.0/TechType/TechTypeTable"; + public static final String TechType_TechTypeTable_HasComponentType = "http://www.simantics.org/DistrictNetwork-1.0/TechType/TechTypeTable/HasComponentType"; + public static final String TechType_TechTypeTable_HasData = "http://www.simantics.org/DistrictNetwork-1.0/TechType/TechTypeTable/HasData"; + public static final String TechType_TechTypeTable_HasData_Inverse = "http://www.simantics.org/DistrictNetwork-1.0/TechType/TechTypeTable/HasData/Inverse"; public static final String Vertex = "http://www.simantics.org/DistrictNetwork-1.0/Vertex"; public static final String VertexDefaultMapping = "http://www.simantics.org/DistrictNetwork-1.0/VertexDefaultMapping"; public static final String VertexDefaultMapping_Inverse = "http://www.simantics.org/DistrictNetwork-1.0/VertexDefaultMapping/Inverse"; @@ -1290,6 +1306,14 @@ public class DistrictNetworkResource { SupplyConnectionType = getResourceOrNull(graph, URIs.SupplyConnectionType); SupplyInConnectionType = getResourceOrNull(graph, URIs.SupplyInConnectionType); SupplyOutConnectionType = getResourceOrNull(graph, URIs.SupplyOutConnectionType); + TechType = getResourceOrNull(graph, URIs.TechType); + TechType_Functions = getResourceOrNull(graph, URIs.TechType_Functions); + TechType_Functions_techTypeCodeValueAccessor = getResourceOrNull(graph, URIs.TechType_Functions_techTypeCodeValueAccessor); + TechType_TechTypeCodeParameterType = getResourceOrNull(graph, URIs.TechType_TechTypeCodeParameterType); + TechType_TechTypeTable = getResourceOrNull(graph, URIs.TechType_TechTypeTable); + TechType_TechTypeTable_HasComponentType = getResourceOrNull(graph, URIs.TechType_TechTypeTable_HasComponentType); + TechType_TechTypeTable_HasData = getResourceOrNull(graph, URIs.TechType_TechTypeTable_HasData); + TechType_TechTypeTable_HasData_Inverse = getResourceOrNull(graph, URIs.TechType_TechTypeTable_HasData_Inverse); Vertex = getResourceOrNull(graph, URIs.Vertex); VertexDefaultMapping = getResourceOrNull(graph, URIs.VertexDefaultMapping); VertexDefaultMapping_Inverse = getResourceOrNull(graph, URIs.VertexDefaultMapping_Inverse); diff --git a/org.simantics.district.network.ui/fragment.e4xmi b/org.simantics.district.network.ui/fragment.e4xmi index c199a959..89afdc5d 100644 --- a/org.simantics.district.network.ui/fragment.e4xmi +++ b/org.simantics.district.network.ui/fragment.e4xmi @@ -16,7 +16,7 @@ - + @@ -49,6 +49,6 @@ - + diff --git a/org.simantics.district.network.ui/plugin.xml b/org.simantics.district.network.ui/plugin.xml index cee0a77f..3b8bf45c 100644 --- a/org.simantics.district.network.ui/plugin.xml +++ b/org.simantics.district.network.ui/plugin.xml @@ -80,6 +80,10 @@ priority="300" class="org.simantics.district.network.ui.OpenDiagramFromNetworkElementAdapter"> + + diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/table/ImportTechTypeCSVHandler.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/table/ImportTechTypeCSVHandler.java new file mode 100644 index 00000000..4f833fca --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/table/ImportTechTypeCSVHandler.java @@ -0,0 +1,54 @@ +package org.simantics.district.network.ui.table; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; +import org.simantics.district.imports.DistrictImportUtils; +import org.simantics.district.network.ui.techtype.table.TechTypeTableView; +import org.simantics.utils.ui.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImportTechTypeCSVHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ImportTechTypeCSVHandler.class); + + @Inject + EPartService partService; + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell s) { + // here we can import based on the current CSV table data + + FileDialog dialog = new FileDialog(s); + String path = dialog.open(); + try { + if (path != null) { + + Path p = Paths.get(path); + if (Files.exists(p)) { + @SuppressWarnings("unused") + Map readCSVHeader = DistrictImportUtils.readCSVHeader(p, ';', true); + TechTypeTableView.table.setTechTypePath(path); + } else { + LOGGER.warn("Path does not exist even though path != null: {}", p); + } + } else { + LOGGER.error("Invalid file path given {}", path); + } + } catch (Exception e) { + LOGGER.error("Could not read file {}", path, e); + ExceptionUtils.logAndShowError("Could not read file " + path + " : " + e.getMessage(), e); + } + } +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java new file mode 100644 index 00000000..5ebe6ed4 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java @@ -0,0 +1,79 @@ +package org.simantics.district.network.ui.techtype.adapters; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.PlatformUI; +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.network.ui.techtype.table.TechTypeTableView; +import org.simantics.ui.utils.ResourceAdaptionUtils; +import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter; +import org.simantics.ui.workbench.editor.EditorAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Reino Ruusu + */ +public class TechTypeEditorAdapter extends AbstractResourceEditorAdapter implements EditorAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TechTypeEditorAdapter.class); + + public TechTypeEditorAdapter() throws MalformedURLException, IOException { + super("Tech Type Table View"); + setIcon(ImageDescriptor.createFromURL(FileLocator.resolve(new URL("platform:/plugin/com.famfamfam.silk/icons/table.png")))); //$NON-NLS-1$ + } + + @Override + public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException { + Resource r = ResourceAdaptionUtils.toSingleResource(input); + if (r == null) + return false; + return graph.isInstanceOf(r, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable); + } + + @Override + public void openEditor(Resource table) throws Exception { + IEclipseContext eclipseContext = PlatformUI.getWorkbench().getService(IEclipseContext.class); + if (eclipseContext == null) { + LOGGER.error("No Eclipse context available"); + return; + } + + eclipseContext = eclipseContext.getActiveLeaf(); + EPartService partService = eclipseContext.get(EPartService.class); + if (partService == null) + return; + + partService.showPart(TechTypeTableView.ID, PartState.ACTIVATE); + + try { + Simantics.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + Resource componentType = graph.getPossibleObject(table, DN.TechType_TechTypeTable_HasComponentType); + String data = graph.getPossibleRelatedValue2(table, DN.TechType_TechTypeTable_HasData); + + TechTypeTableView.table.getDisplay().asyncExec(() -> { + TechTypeTableView.table.setComponentType(componentType); + TechTypeTableView.table.setTechTypeData(data); + }); + } + }); + } catch (DatabaseException e) { + LOGGER.error("Failed to read tech type table {}", table, e); + } + } +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeColumnHeaderTableDataProvider.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeColumnHeaderTableDataProvider.java new file mode 100644 index 00000000..9b632192 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeColumnHeaderTableDataProvider.java @@ -0,0 +1,33 @@ +package org.simantics.district.network.ui.techtype.table; + +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; + +public class TechTypeColumnHeaderTableDataProvider implements IDataProvider { + + private TechTypeTableDataProvider bodyDataProvider; + + public TechTypeColumnHeaderTableDataProvider(TechTypeTableDataProvider bodyDataProvider) { + this.bodyDataProvider = bodyDataProvider; + } + + @Override + public Object getDataValue(int columnIndex, int rowIndex) { + return bodyDataProvider.getHeaderValue(columnIndex); + } + + @Override + public void setDataValue(int columnIndex, int rowIndex, Object newValue) { + + } + + @Override + public int getColumnCount() { + return bodyDataProvider.getColumnCount(); + } + + @Override + public int getRowCount() { + return 1; + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeRowHeaderTableDataProvider.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeRowHeaderTableDataProvider.java new file mode 100644 index 00000000..4d21bd55 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeRowHeaderTableDataProvider.java @@ -0,0 +1,33 @@ +package org.simantics.district.network.ui.techtype.table; + +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; + +public class TechTypeRowHeaderTableDataProvider implements IDataProvider { + + protected final IDataProvider bodyDataProvider; + + public TechTypeRowHeaderTableDataProvider(IDataProvider bodyDataProvider) { + this.bodyDataProvider = bodyDataProvider; + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public int getRowCount() { + return this.bodyDataProvider.getRowCount(); + } + + @Override + public Object getDataValue(int columnIndex, int rowIndex) { + return Integer.valueOf(rowIndex + 1); + } + + @Override + public void setDataValue(int columnIndex, int rowIndex, Object newValue) { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java new file mode 100644 index 00000000..0c44bff0 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java @@ -0,0 +1,224 @@ +package org.simantics.district.network.ui.techtype.table; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataCommandHandler; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer; +import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; +import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer; +import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel; +import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; +import org.eclipse.nebula.widgets.nattable.hover.HoverLayer; +import org.eclipse.nebula.widgets.nattable.hover.config.BodyHoverStylingBindings; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayer; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.reorder.RowReorderLayer; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer; +import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.techtype.requests.WriteTechTypeTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TechTypeTable extends Composite { + + private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTable.class); + + NatTable table; + private TechTypeTableDataProvider bodyDataProvider; + DataLayer bodyDataLayer; + private IConfigRegistry summaryConfigRegistry; + private IUniqueIndexLayer summaryRowLayer; + private ViewportLayer viewportLayer; + private CompositeFreezeLayer compositeFreezeLayer; + private FreezeLayer freezeLayer; + // private TableDataSortModel sortModel; + private ColumnHideShowLayer columnHideShowLayer; + private ColumnGroupModel columnGroupModel = new ColumnGroupModel(); + private TechTypeColumnHeaderTableDataProvider columnHeaderDataProvider; + Clipboard cpb; + public SelectionLayer selectionLayer; + private TechTypeTableSortModel sortModel; + + private Resource componentType; + + public TechTypeTable(Composite parent, int style, Resource componentType, String data) { + super(parent, style); + this.componentType = componentType; + + defaultInitializeUI(data); + } + + private void defaultInitializeUI(String data) { + GridDataFactory.fillDefaults().grab(true, true).applyTo(this); + GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this); + + Composite filterComposite = new Composite(this, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, false).applyTo(filterComposite); + GridLayoutFactory.fillDefaults().numColumns(1).applyTo(filterComposite); + + createFilterBar(filterComposite); + + Composite tableComposite = new Composite(this, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite); + GridLayoutFactory.fillDefaults().numColumns(1).applyTo(tableComposite); + + createTable(tableComposite, data); + } + + private void createFilterBar(Composite filterComposite) { + + Text filterText = new Text(filterComposite, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, true).applyTo(filterText); + filterText.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + System.out.println("text modified"); + bodyDataProvider.setFilter(filterText.getText()); + table.refresh(true); + } + }); + + } + + private void createTable(Composite parent, String data) { + + // build the body layer stack + // Usually you would create a new layer stack by extending + // AbstractIndexLayerTransform and + // setting the ViewportLayer as underlying layer. But in this case using the + // ViewportLayer + // directly as body layer is also working. + bodyDataProvider = new TechTypeTableDataProvider(data); + bodyDataLayer = new DataLayer(bodyDataProvider); + + RowReorderLayer rowReorderLayer = new RowReorderLayer( + columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer)); + + HoverLayer hoverLayer = new HoverLayer(rowReorderLayer, false); + // we need to ensure that the hover styling is removed when the mouse + // cursor moves out of the cell area + hoverLayer.addConfiguration(new BodyHoverStylingBindings(hoverLayer)); + + selectionLayer = new SelectionLayer(hoverLayer); + + viewportLayer = new ViewportLayer(selectionLayer); + viewportLayer.setRegionName(GridRegion.BODY); + freezeLayer = new FreezeLayer(selectionLayer); + compositeFreezeLayer = new CompositeFreezeLayer(freezeLayer, viewportLayer, selectionLayer); + + // build the column header layer + columnHeaderDataProvider = new TechTypeColumnHeaderTableDataProvider(bodyDataProvider); + DataLayer columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider); + columnHeaderDataLayer.setRowsResizableByDefault(false); + columnHeaderDataLayer.setColumnsResizableByDefault(true); + ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, compositeFreezeLayer, + selectionLayer); + ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer, + columnGroupModel); + columnGroupHeaderLayer.setCalculateHeight(true); + SortHeaderLayer columnSortHeaderLayer = new SortHeaderLayer<>(columnGroupHeaderLayer, + sortModel = new TechTypeTableSortModel(bodyDataProvider)); + + // build the row header layer + IDataProvider rowHeaderDataProvider = new TechTypeRowHeaderTableDataProvider(bodyDataProvider); + DataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider); + rowHeaderDataLayer.setRowsResizableByDefault(false); + rowHeaderDataLayer.setColumnsResizableByDefault(false); + RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, compositeFreezeLayer, selectionLayer); + + // build the corner layer + IDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, + rowHeaderDataProvider); + DataLayer cornerDataLayer = new DataLayer(cornerDataProvider); + ILayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnSortHeaderLayer); + + // build the grid layer + GridLayer gridLayer = new GridLayer(compositeFreezeLayer, columnSortHeaderLayer, rowHeaderLayer, cornerLayer); + + table = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, gridLayer, false); + GridDataFactory.fillDefaults().grab(true, true).applyTo(table); + + // Register a CopyDataCommandHandler that also copies the headers and + // uses the configured IDisplayConverters + CopyDataCommandHandler copyHandler = new CopyDataCommandHandler(selectionLayer, columnHeaderDataLayer, + rowHeaderDataLayer); + copyHandler.setCopyFormattedText(true); + gridLayer.registerCommandHandler(copyHandler); + + // initialize paste handler with SWT clipboard + cpb = new Clipboard(getDisplay()); + // PasteDataCommandHandler pasteHandler = new + // PasteDataCommandHandler(bodyDataProvider, bodyDataLayer, selectionLayer, + // cpb); + // bodyDataLayer.registerCommandHandler(pasteHandler); + + table.addConfiguration(new DefaultNatTableStyleConfiguration()); + // table.addConfiguration(new EditingSupportConfiguration(bodyDataProvider)); + table.configure(); + } + + @Override + public void dispose() { + cpb.dispose(); + super.dispose(); + } + + public void setTechTypePath(String path) { + String data; + try { + data = Files.lines(Paths.get(path), Charset.defaultCharset()).collect(Collectors.joining("\n")); + } catch (IOException e) { + LOGGER.error("Failed to read contents of file '{}' as {}", path, Charset.defaultCharset(), e); + return; + } + + try { + Simantics.getSession().syncRequest(new WriteTechTypeTable(componentType, data)); + } catch (DatabaseException e) { + LOGGER.error("Failed to write tech type table data to model", e); + } + + setTechTypeData(data); + } + + public void setTechTypeData(String data) { + bodyDataProvider.setData(data); + table.refresh(true); + } + + public void setComponentType(Resource componentType) { + this.componentType = componentType; + } + +} + diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java new file mode 100644 index 00000000..850cc39f --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java @@ -0,0 +1,161 @@ +package org.simantics.district.network.ui.techtype.table; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.csv.CSVRecord; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.simantics.district.imports.DistrictImportUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TechTypeTableDataProvider implements IDataProvider { + + @SuppressWarnings("unused") + private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableDataProvider.class); + + private List records = new ArrayList<>(); + private List filteredRecords = new ArrayList<>(); + private String filter = ""; + private List variables = null; + private List headers = null; + + public TechTypeTableDataProvider(String data) { + // load csv + setData(data); + } + + public String getVariableName(int columnIndex) { + return variables != null && columnIndex < variables.size() ? variables.get(columnIndex) : null; + } + + public int getVariableIndex(String variableName) { + return variables != null ? variables.indexOf(variableName) : -1; + } + + public String getHeaderValue(int columnIndex) { + if (headers == null) { + return ""; + } + return headers.get(columnIndex); + } + + @Override + public Object getDataValue(int columnIndex, int rowIndex) { + return filteredRecords.get(rowIndex).get(columnIndex); + } + + @Override + public void setDataValue(int columnIndex, int rowIndex, Object newValue) { + + } + + @Override + public int getColumnCount() { + if (records.isEmpty()) { + return 0; + } + return records.get(0).size(); + } + + @Override + public int getRowCount() { + return filteredRecords.size() - 1; + } + + public boolean isEditable(int columnIndex, int rowIndex) { + return false; + } + + public void setFilter(String text) { + this.filter = text.toLowerCase(); + + filteredRecords = records.stream().filter(record -> { + for (int i = 0; i < record.size(); i++) { + String columnContent = record.get(i); + if (columnContent.toLowerCase().contains(filter)) { + return true; + } + } + return false; + }).collect(Collectors.toList()); + } + + /** + * Read a CSV file into table contents. + * + * Set path to null to create an empty table. + * + * @param path The path of the CSV file to be read. + */ + public void setPath(String path) { + records.clear(); + filteredRecords.clear(); + if (path != null) { + Path techTypeCsv = Paths.get(path); + try { + DistrictImportUtils.consumeCSV(techTypeCsv, ';', false, record -> { + records.add(record); + return true; + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + + setFilter(""); + } + + /** + * Set table data contents to a given string of CSV data. + * + * Set 'data' to null to create an empty table. + * + * @param data The CSV data to be shown in the table. + */ + public void setData(String data) { + records.clear(); + filteredRecords.clear(); + if (data != null) { + long ncommas = data.chars().filter(c -> c == ',').count(); + long nsemis = data.chars().filter(c -> c == ';').count(); + char delim = nsemis > ncommas ? ';' : ','; + StringReader reader = new StringReader(data); + try { + DistrictImportUtils.consumeCSV(reader, delim, false, record -> { + records.add(record); + return true; + }); + } catch (IOException e) { + LOGGER.error("Error reading CSV file", e); + return; + } + + CSVRecord header = records.remove(0); + CSVRecord units = records.remove(0); + + variables = new ArrayList<>(); + headers = new ArrayList<>(); + + Iterator it = header.iterator(); + Iterator uit = units.iterator(); + + while (it.hasNext()) { + String variable = it.next().trim(); + String unit = uit.hasNext() ? uit.next().trim() : null; + + variables.add(variable); + headers.add(variable + (unit != null && !unit.isEmpty() && !(unit.startsWith("(") && unit.endsWith(")")) ? " [" + unit + "]" : "")); + } + } + + setFilter(""); + } + +} \ No newline at end of file diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableSortModel.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableSortModel.java new file mode 100644 index 00000000..f6a02f4a --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableSortModel.java @@ -0,0 +1,127 @@ +package org.simantics.district.network.ui.techtype.table; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.nebula.widgets.nattable.sort.ISortModel; +import org.eclipse.nebula.widgets.nattable.sort.SortDirectionEnum; +import org.simantics.utils.strings.AlphanumComparator; + +public class TechTypeTableSortModel implements ISortModel { + + private static final SortDirectionEnum[] NO_DIRECTIONS = {}; + private static final boolean[] NO_BOOLEANS = {}; + + /** + * Array that contains the sort direction for every column. + * Needed to access the current sort state of a column. + */ + protected SortDirectionEnum[] sortDirections = NO_DIRECTIONS; + + /** + * Array that contains the sorted flags for every column. + * Needed to access the current sort state of a column. + */ + protected boolean[] sorted = NO_BOOLEANS; + + /** + * As this implementation only supports single column sorting, + * this property contains the the column index of the column that + * is currently used for sorting. + * Initial value = -1 for no sort column + */ + protected int currentSortColumn = -1; + + /** + * As this implementation only supports single column sorting, + * this property contains the current sort direction of the column that + * is currently used for sorting. + */ + protected SortDirectionEnum currentSortDirection = SortDirectionEnum.ASC; + + private TechTypeTableDataProvider bodyDataProvider; + + public TechTypeTableSortModel(TechTypeTableDataProvider bodyDataProvider) { + this.bodyDataProvider = bodyDataProvider; + } + + + @Override + public List getSortedColumnIndexes() { + List indexes = new ArrayList(); + if (currentSortColumn > -1) { + indexes.add(Integer.valueOf(currentSortColumn)); + } + return indexes; + } + + @Override + public boolean isColumnIndexSorted(int columnIndex) { + if (sorted.length <= columnIndex) + return false; + return sorted[columnIndex]; + } + + @Override + public SortDirectionEnum getSortDirection(int columnIndex) { + if (sortDirections.length <= columnIndex) + return SortDirectionEnum.NONE; + return sortDirections[columnIndex]; + } + + @Override + public int getSortOrder(int columnIndex) { + return 0; + } + + @Override + public List getComparatorsForColumnIndex(int columnIndex) { + return Collections.singletonList(AlphanumComparator.COMPARATOR); + } + + @Override + public Comparator getColumnComparator(int columnIndex) { + return AlphanumComparator.COMPARATOR; + } + + @Override + public void sort(int columnIndex, SortDirectionEnum sortDirection, boolean accumulate) { + + if (!isColumnIndexSorted(columnIndex)) { + clear(); + } + int columnCount = bodyDataProvider.getColumnCount(); + sortDirections = ensureArraySize(sortDirections, columnCount, SortDirectionEnum.class, SortDirectionEnum.NONE); + + + sortDirections[columnIndex] = sortDirection; + sorted[columnIndex] = !sortDirection.equals(SortDirectionEnum.NONE); + currentSortColumn = columnIndex; + currentSortDirection = sortDirection; + } + + @Override + public void clear() { + Arrays.fill(this.sortDirections, SortDirectionEnum.NONE); + Arrays.fill(this.sorted, false); + this.currentSortColumn = -1; + this.currentSortDirection = SortDirectionEnum.NONE; + } + + @SuppressWarnings("unchecked") + public static T[] ensureArraySize(T[] array, int l, Class clazz, T defaultValue) { + boolean fill = true; + if (array == null || array.length != l) { + array = (T[]) Array.newInstance(clazz, l); + fill = defaultValue != null; + } + if (fill) + Arrays.fill(array, defaultValue); + return array; + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableUI.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableUI.java new file mode 100644 index 00000000..7cfa44d1 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableUI.java @@ -0,0 +1,11 @@ +package org.simantics.district.network.ui.techtype.table; + +import org.eclipse.swt.widgets.Composite; + +public class TechTypeTableUI extends Composite { + + public TechTypeTableUI(Composite parent, int style) { + super(parent, style); + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java new file mode 100644 index 00000000..15b31784 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java @@ -0,0 +1,96 @@ +package org.simantics.district.network.ui.techtype.table; + + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.commands.MCommand; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.menu.MHandledToolItem; +import org.eclipse.e4.ui.model.application.ui.menu.MMenuFactory; +import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.swt.widgets.Composite; +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.common.NamedResource; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.db.layer0.request.PossibleResource; +import org.simantics.district.network.DistrictNetworkUtil; +import org.simantics.district.network.techtype.requests.PossibleTechTypeTableData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TechTypeTableView { + + private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableView.class); + + public static final String ID = "org.simantics.district.network.ui.techtype.table.techtypeTableView"; + + @Inject ESelectionService selectionService; + + public static TechTypeTable table; + + @Inject + public void init(MPart part, MApplication app) { + MToolBar toolBar = MMenuFactory.INSTANCE.createToolBar(); + toolBar.setToBeRendered(true); + toolBar.getChildren().add(createImportCSVDataToolItem(app)); + part.setToolbar(toolBar); + } + + private MHandledToolItem createImportCSVDataToolItem(MApplication app) { + MHandledToolItem createHandledToolItem = MMenuFactory.INSTANCE.createHandledToolItem(); + // Command is contributed via fragment + MCommand command = app.getCommand("org.simantics.district.network.ui.command.importtechtypecsv"); + createHandledToolItem.setCommand(command); //$NON-NLS-1$ + createHandledToolItem.setLabel("Import Tech Type"); + createHandledToolItem.setIconURI("platform:/plugin/com.famfamfam.silk/icons/table_edit.png"); //$NON-NLS-1$ + return createHandledToolItem; + } + + @PostConstruct + public void postConstruct(Composite parent) { + Resource pipe = null; + try { + List componentTypes = DistrictNetworkUtil.getDistrictComponents(); + + pipe = componentTypes.stream() + .filter(r -> r.getName().toLowerCase().contains("pipe")) + .map(r -> r.getResource()) + .findFirst().orElse(null); + + if (pipe == null) { + pipe = Simantics.getSession().syncRequest(new PossibleResource("http://DistrictComponents@C/dh_pipe@1")); + } + } catch (DatabaseException e) { + LOGGER.error("Failed to read district component types for active model", e); + } + + LOGGER.debug("Pipe component type is {}", pipe); + + String data = null; + try { + Resource model = Simantics.getSession().syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); + if (model != null) + data = Simantics.getSession().syncRequest(new PossibleTechTypeTableData(model, pipe), TransientCacheListener.instance()); + } catch (DatabaseException e) { + LOGGER.error("Failed to read tech type table data for {}", pipe, e); + } + + table = new TechTypeTable(parent, 0, pipe, data); + } + + @PreDestroy + public void dispose() { + table.dispose(); + table = null; + } + +} diff --git a/org.simantics.district.network/META-INF/MANIFEST.MF b/org.simantics.district.network/META-INF/MANIFEST.MF index 79671648..022a0cfa 100644 --- a/org.simantics.district.network/META-INF/MANIFEST.MF +++ b/org.simantics.district.network/META-INF/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Simantics District Network -Bundle-SymbolicName: org.simantics.district.network +Bundle-SymbolicName: org.simantics.district.network;singleton:=true Bundle-Version: 1.0.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.simantics.db, @@ -22,10 +22,14 @@ Require-Bundle: org.simantics.db, org.simantics.scl.osgi, org.eclipse.collections.eclipse-collections, org.eclipse.collections.eclipse-collections-api, - org.simantics.structural.synchronization.client + org.simantics.structural.synchronization.client, + org.apache.commons.csv Export-Package: org.simantics.district.network, org.simantics.district.network.changeset, org.simantics.district.network.profile, + org.simantics.district.network.techtype, + org.simantics.district.network.techtype.requests, + org.simantics.district.network.techtype.variable, org.simantics.district.network.visualisations, org.simantics.district.network.visualisations.model, org.simantics.district.network.visualisations.triggers diff --git a/org.simantics.district.network/build.properties b/org.simantics.district.network/build.properties index 21a6c989..1ae7e4e9 100644 --- a/org.simantics.district.network/build.properties +++ b/org.simantics.district.network/build.properties @@ -3,4 +3,5 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ adapters.xml,\ - scl/ + scl/,\ + plugin.xml diff --git a/org.simantics.district.network/plugin.xml b/org.simantics.district.network/plugin.xml new file mode 100644 index 00000000..b8e40a32 --- /dev/null +++ b/org.simantics.district.network/plugin.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/org.simantics.district.network/scl/Simantics/District/TechType.scl b/org.simantics.district.network/scl/Simantics/District/TechType.scl new file mode 100644 index 00000000..9b6ddeb9 --- /dev/null +++ b/org.simantics.district.network/scl/Simantics/District/TechType.scl @@ -0,0 +1,7 @@ +import "Simantics/DB" + +importJava "org.simantics.district.network.techtype.TechTypeUtils" where + "Update component properties from tech type table: `updateComponent component`" + updateComponent :: Resource -> () + "Reset all components to values in a tech type table: `resetComponents table`" + resetComponents :: Resource -> () diff --git a/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java b/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java index 2bb29660..85ebd06c 100644 --- a/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java +++ b/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java @@ -3,8 +3,12 @@ package org.simantics.district.network; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -13,12 +17,16 @@ import org.simantics.databoard.Bindings; import org.simantics.datatypes.literal.RGB; import org.simantics.datatypes.literal.RGB.Integer; import org.simantics.db.ReadGraph; +import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; +import org.simantics.db.common.NamedResource; import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.BinaryRead; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.request.PossibleChild; +import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.exception.BindingException; @@ -26,6 +34,8 @@ import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.ServiceException; import org.simantics.db.indexing.IndexUtils; +import org.simantics.db.layer0.QueryIndexUtils; +import org.simantics.db.layer0.request.PossibleActiveModel; import org.simantics.db.layer0.request.PossibleVariable; import org.simantics.db.layer0.variable.Variable; import org.simantics.diagram.stubs.DiagramResource; @@ -466,4 +476,53 @@ public class DistrictNetworkUtil { } } + public static class DistrictComponentListRequest extends ResourceRead> { + protected DistrictComponentListRequest(Resource model) { + super(model); + } + + @Override + public List perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Resource model = this.resource; + + Set componentTypes = new HashSet<>(); + Collection mappings = QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base); + for (Resource r : mappings) { + String componentType = graph.getPossibleRelatedValue2(r, DN.Mapping_ComponentType); + if (componentType != null) { + Resource root = graph.syncRequest(new PossibleIndexRoot(r)); + if (root != null) { + Resource type = graph.syncRequest(new PossibleChild(root, componentType)); + if (type != null) + componentTypes.add(type); + } + } + } + + List result = new ArrayList(componentTypes.size()); + for (Resource r : componentTypes) { + String name = graph.getPossibleRelatedValue(r, L0.HasName); + result.add(new NamedResource(name, r)); + } + + result.sort(Comparator.comparing(NamedResource::getName)); + return result; + } + } + + public static List getDistrictComponents() throws DatabaseException { + return getDistrictComponents(Simantics.getSession()); + } + + public static List getDistrictComponents(RequestProcessor session) throws DatabaseException { + Resource model = session.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); + if (model == null) + return Collections.emptyList(); + + return session.syncRequest(new DistrictComponentListRequest(model), TransientCacheListener.instance()); + } + } diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java new file mode 100644 index 00000000..7a25ff84 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java @@ -0,0 +1,207 @@ +package org.simantics.district.network.techtype; + +import java.util.Collection; +import java.util.Map; + +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.databoard.binding.NumberBinding; +import org.simantics.databoard.binding.error.BindingException; +import org.simantics.databoard.type.Datatype; +import org.simantics.databoard.type.NumberType; +import org.simantics.databoard.type.StringType; +import org.simantics.db.ReadGraph; +import org.simantics.db.RequestProcessor; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.common.request.PossibleIndexRoot; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.QueryIndexUtils; +import org.simantics.db.layer0.request.PossibleVariable; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.network.techtype.requests.PossibleTechTypeItem; +import org.simantics.district.network.techtype.requests.PossibleTechTypeTable; +import org.simantics.district.network.techtype.requests.TechTypeTableKeyName; +import org.simantics.scl.runtime.SCLContext; +import org.simantics.structural.stubs.StructuralResource2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TechTypeUtils { + + final static Logger LOGGER = LoggerFactory.getLogger(TechTypeUtils.class); + + public static String DEFAULT_KEY_NAME = "pipeCode"; + + /** + * Execute a cached query for a possible tech type table item in a table with a given item key. + * + * Result is null, if no such item was found. + * + * @param session A request processor on which to run the query + * @param itemCode A key value, such as a pipe code + * @throws DatabaseException + */ + public static Map getTableItem(RequestProcessor session, Resource table, String itemCode) throws DatabaseException { + return session.syncRequest(new PossibleTechTypeItem(table, itemCode), TransientCacheListener.instance()); + } + + /** + * Get a single row in a tech type table for a given item code + * + * Result is null, if no such item was found. + * + * @param table A TechTypeTable resource + * @param itemCode A key value, such as a pipe code + * @return A map from property name to value + * @throws DatabaseException + */ + public static Map getTableItem(Resource table, String itemCode) throws DatabaseException { + Object graph = SCLContext.getCurrent().get("graph"); + if (graph != null && graph instanceof ReadGraph) + return getTableItem((ReadGraph) graph, table, itemCode); + else + return getTableItem(Simantics.getSession(), table, itemCode); + } + + /** + * Reset all components that address the given tech type table to the table values. + * + * @param table A tech type table + * @throws DatabaseException + */ + public static void resetComponents(Resource table) throws DatabaseException { + Simantics.getSession().syncRequest(new WriteRequest() { + @Override + public void perform(WriteGraph graph) throws DatabaseException { + Resource model = graph.syncRequest(new PossibleIndexRoot(table), TransientCacheListener.instance()); + if (model == null) + return; + + Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType); + if (type == null) + return; + + Collection components = QueryIndexUtils.searchByType(graph, model, type); + for (Resource component : components) { + updateComponent(graph, component, table); + } + } + }); + } + + /** + * Update property values of a component based on the values in an associated tech type table, if any + * + * @param graph A write access interface to the graph database + * @param component A structural component + * @throws DatabaseException + */ + public static void updateComponent(WriteGraph graph, Resource component) throws DatabaseException { + StructuralResource2 STR = StructuralResource2.getInstance(graph); + + Resource type = graph.getSingleType(component, STR.Component); + Resource model = graph.syncRequest(new PossibleIndexRoot(component)); + if (model == null) { + LOGGER.info("updateComponent: No model for {}", component); + return; + } + + Resource table = graph.syncRequest(new PossibleTechTypeTable(model, type), TransientCacheListener.instance()); + if (table == null) { + LOGGER.info("updateComponent: No tech type table for {} in {}", type, model); + return; + } + + updateComponent(graph, component, table); + } + + /** + * Update property values of a component based on the values in an associated tech type table, if any + * + * @param component A structural component + * @throws DatabaseException + */ + public static void updateComponent(Resource component) throws DatabaseException { + if (LOGGER.isTraceEnabled()) + LOGGER.trace("updateComponent({})", component); + + Simantics.getSession().syncRequest(new WriteRequest() { + @Override + public void perform(WriteGraph graph) throws DatabaseException { + updateComponent(graph, component); + } + }); + } + + /** + * Update a single component's property values to those selected from a tech + * type table + * + * @param graph A write interface to the db + * @param component The component to be updated + * @param table A tech type table + * @throws DatabaseException + */ + public static void updateComponent(WriteGraph graph, Resource component, Resource table) + throws DatabaseException { + if (LOGGER.isTraceEnabled()) + LOGGER.trace("updateComponent(graph, {}, {}), component, table)"); + + Variable v = graph.syncRequest(new PossibleVariable(component)); + if (v == null) { + LOGGER.info("No variable found for {}", component); + return; + } + + String keyProp = getKeyPropertyName(graph, table); + String itemCode = v.getPropertyValue(graph, keyProp); + + Map map = graph.syncRequest(new PossibleTechTypeItem(table, itemCode), TransientCacheListener.instance()); + if (map == null) { + LOGGER.info("No entry found for \"{}\" in tech type table {}", itemCode, table); + return; + } + + for (String key : map.keySet()) { + // Don't write the key property + if (key.equals(DEFAULT_KEY_NAME)) + continue; + + Variable prop = v.getPossibleProperty(graph, key); + if (prop == null) continue; + + Datatype dt = prop.getPossibleDatatype(graph); + if (dt == null) continue; + + String value = map.get(key); + if (dt instanceof NumberType) { + try { + Double num = Double.valueOf(value.replace(",", ".")); + NumberBinding binding = Bindings.getBinding(dt); + prop.setValue(graph, binding.create(num)); + } catch (NumberFormatException e) { + // Revert to asserted value + Resource pred = prop.getPossiblePredicateResource(graph); + if (pred != null) + graph.deny(component, pred); + } catch (BindingException e) { + LOGGER.error("Failed to get binding for datatype {}", dt, e); + } + } else if (dt instanceof StringType) { + prop.setValue(graph, value); + } else { + LOGGER.warn("updateComponent: Unsupported property type {}", dt); + } + } + } + + public static String getKeyPropertyName(ReadGraph graph, Resource table) throws DatabaseException { + String name = graph.syncRequest(new TechTypeTableKeyName(table), TransientCacheListener.instance()); + if (name == null) name = "_" + DEFAULT_KEY_NAME; + return name; + } +} diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeItem.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeItem.java new file mode 100644 index 00000000..05e43087 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeItem.java @@ -0,0 +1,30 @@ +package org.simantics.district.network.techtype.requests; + +import java.util.Map; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.common.request.BinaryRead; +import org.simantics.db.exception.DatabaseException; + +/** + * Query for a possible tech type table item in a table with a given item key. + * + * Results in null, if no such item was found. + */ +public class PossibleTechTypeItem extends BinaryRead> { + + public PossibleTechTypeItem(Resource table, String itemCode) { + super(table, itemCode); + } + + @Override + public Map perform(ReadGraph graph) throws DatabaseException { + Resource table = this.parameter; + String itemCode = this.parameter2; + + Map> map = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance()); + return map.get(itemCode); + } +} \ No newline at end of file diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTable.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTable.java new file mode 100644 index 00000000..68fc12f1 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTable.java @@ -0,0 +1,33 @@ +package org.simantics.district.network.techtype.requests; + +import java.util.Collection; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead2; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.layer0.Layer0; + +public class PossibleTechTypeTable extends ResourceRead2 { + public PossibleTechTypeTable(Resource model, Resource componentType) { + super(model, componentType); + } + + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Collection children = graph.getObjects(resource, L0.ConsistsOf); + for (Resource child : children) { + if (!graph.isInstanceOf(child, DN.TechType_TechTypeTable)) + continue; + + if (graph.hasStatement(child, DN.TechType_TechTypeTable_HasComponentType, resource2)) + return child; + } + + return null; + } +} \ No newline at end of file diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTableData.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTableData.java new file mode 100644 index 00000000..8dd88666 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/PossibleTechTypeTableData.java @@ -0,0 +1,23 @@ +package org.simantics.district.network.techtype.requests; + +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.common.request.ResourceRead2; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; + +public class PossibleTechTypeTableData extends ResourceRead2 { + public PossibleTechTypeTableData(Resource model, Resource componentType) { + super(model, componentType); + } + + @Override + public String perform(ReadGraph graph) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Resource ttt = graph.syncRequest(new PossibleTechTypeTable(resource, resource2), TransientCacheListener.instance()); + return ttt != null ? graph.getPossibleRelatedValue2(ttt, DN.TechType_TechTypeTable_HasData, Bindings.STRING) : null; + } +} diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java new file mode 100644 index 00000000..2ff575b7 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java @@ -0,0 +1,97 @@ +package org.simantics.district.network.techtype.requests; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.network.techtype.TechTypeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TechTypeTableData extends ResourceRead>> { + + private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableData.class); + + protected TechTypeTableData(Resource table) { + super(table); + } + + @Override + public Map> perform(ReadGraph graph) throws DatabaseException { + Resource table = this.resource; + + String data = graph.getRelatedValue2(table, + DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasData); + if (data == null) + return Collections.emptyMap(); + + String keyName = TechTypeUtils.getKeyPropertyName(graph, table); + if (keyName.startsWith("_")) + keyName = keyName.substring(1); + + Map> map = new HashMap<>(); + + long ncommas = data.chars().filter(c -> c == ',').count(); + long nsemis = data.chars().filter(c -> c == ';').count(); + char delim = nsemis > ncommas ? ';' : ','; + StringReader reader = new StringReader(data); + + List records = new ArrayList<>(); + try { + CSVFormat format = CSVFormat.newFormat(delim).withQuote('"'); + try (CSVParser parser = format.parse(reader)) { + Iterator it = parser.iterator(); + while (it.hasNext()) { + records.add(it.next()); + } + } + } catch (IOException e) { + LOGGER.error("Error reading CSV data", e); + return Collections.emptyMap(); + } + + CSVRecord header = records.remove(0); + records.remove(0); // Eliminate units row + + int keyIndex = 0; + for (int i = 0; i < header.size(); i++) { + if (header.get(i).equals(keyName)) { + keyIndex = i; + break; + } + } + + for (CSVRecord r : records) { + String key = r.get(keyIndex).intern(); + if (key == null) + continue; + + Map valueMap = new HashMap<>(); + map.put(key, valueMap); + + Iterator h = header.iterator(); + Iterator v = r.iterator(); + + while (h.hasNext() && v.hasNext()) { + String name = h.next().intern(); + String value = v.next(); + valueMap.put(name, value); + } + } + + return map; + } +} \ No newline at end of file diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableKeyName.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableKeyName.java new file mode 100644 index 00000000..86b7d720 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableKeyName.java @@ -0,0 +1,33 @@ +package org.simantics.district.network.techtype.requests; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.layer0.Layer0; + +public class TechTypeTableKeyName extends ResourceRead { + + public TechTypeTableKeyName(Resource table) { + super(table); + } + + @Override + public String perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Resource type = graph.getPossibleObject(resource, DN.TechType_TechTypeTable_HasComponentType); + if (type != null) { + for (Resource r : graph.getObjects(type, L0.DomainOf)) { + Resource accessor = graph.getPossibleObject(r, L0.valueAccessor); + if (accessor.equals(DN.TechType_Functions_techTypeCodeValueAccessor)) { + return graph.getRelatedValue2(r, L0.HasName); + } + } + } + + return null; + } +} \ No newline at end of file diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java new file mode 100644 index 00000000..1f85c5a4 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java @@ -0,0 +1,45 @@ +package org.simantics.district.network.techtype.requests; + +import java.util.UUID; + +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.layer0.Layer0; + +public final class WriteTechTypeTable extends WriteRequest { + private final String data; + private final Resource componentType; + + public WriteTechTypeTable(Resource componentType, String data) { + this.data = data; + this.componentType = componentType; + } + + @Override + public void perform(WriteGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); + if (model == null) + throw new DatabaseException("No active model for storing tech type data"); + + Resource ttt = graph.syncRequest(new PossibleTechTypeTable(model, componentType), TransientCacheListener.instance()); + + if (ttt == null) { + ttt = graph.newResource(); + graph.claim(ttt, L0.InstanceOf, DN.TechType_TechTypeTable); + graph.claimLiteral(ttt, L0.HasName, UUID.randomUUID().toString()); + graph.claim(model, L0.ConsistsOf, ttt); + graph.claim(ttt, DN.TechType_TechTypeTable_HasComponentType, componentType); + } + + graph.claimLiteral(ttt, DN.TechType_TechTypeTable_HasData, data); + } +} \ No newline at end of file diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/variable/Functions.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/variable/Functions.java new file mode 100644 index 00000000..beb92684 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/variable/Functions.java @@ -0,0 +1,59 @@ +package org.simantics.district.network.techtype.variable; + +import org.simantics.databoard.binding.Binding; +import org.simantics.databoard.type.Datatype; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.ValueAccessor; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.district.network.techtype.TechTypeUtils; +import org.simantics.scl.reflection.annotations.SCLValue; +import org.simantics.structural.stubs.StructuralResource2; + +public class Functions { + @SCLValue(type = "ValueAccessor") + public static final ValueAccessor techTypeCodeValueAccessor = new ValueAccessor() { + + @Override + public Object getValue(ReadGraph graph, Variable context) throws DatabaseException { + return org.simantics.db.layer0.function.All.standardValueAccessor.getValue(graph, context); + } + + @Override + public Object getValue(ReadGraph graph, Variable context, Binding binding) throws DatabaseException { + return org.simantics.db.layer0.function.All.standardValueAccessor.getValue(graph, context, binding); + } + + @Override + public void setValue(WriteGraph graph, Variable context, Object value) throws DatabaseException { + org.simantics.db.layer0.function.All.standardValueAccessor.setValue(graph, context, value); + + updateComponentProperties(graph, context, value); + } + + @Override + public void setValue(WriteGraph graph, Variable context, Object value, Binding binding) + throws DatabaseException { + org.simantics.db.layer0.function.All.standardValueAccessor.setValue(graph, context, value, binding); + + updateComponentProperties(graph, context, value); + } + + @Override + public Datatype getDatatype(ReadGraph graph, Variable context) throws DatabaseException { + return org.simantics.db.layer0.function.All.standardValueAccessor.getDatatype(graph, context); + } + + }; + + protected static void updateComponentProperties(WriteGraph graph, Variable context, Object value) throws DatabaseException { + Variable component = context.getParent(graph); + Resource type = component.getPossibleType(graph, StructuralResource2.getInstance(graph).Component); + if (type == null) + return; + + TechTypeUtils.updateComponent(graph, component.getRepresents(graph)); + } +}