<elements xsi:type="commands:Command" xmi:id="_Ee0TAHVvEemS_rRAHnCQSw" elementId="org.simantics.district.network.ui.command.changevertextoroutepoint" commandName="Change Vertex to Route Point"/>
<elements xsi:type="commands:Command" xmi:id="_QcEKQIAGEemKlokjSaREFQ" elementId="org.simantics.district.network.ui.command.importcsv" commandName="Import CSV"/>
<elements xsi:type="commands:Command" xmi:id="_VWtY8LqWEemcscVaZzEyWw" elementId="org.simantics.district.network.ui.command.importtechtypecsv" commandName="Import Tech Type CSV"/>
+ <elements xsi:type="commands:Command" xmi:id="_dN5OYPaJEeqB1-l6EhcCjg" elementId="org.simantics.district.network.ui.command.validatetechtypetable" commandName="Validate Tech Type Table"/>
</fragments>
<fragments xsi:type="fragment:StringModelFragment" xmi:id="_Js7rUMMAEea1mdgpHNVHMA" featurename="menuContributions" parentElementId="xpath:/">
<elements xsi:type="menu:MenuContribution" xmi:id="_T-jiEN8NEeaigNyzMJBOrg" elementId="org.simantics.district.network.ui.menucontribution.districtDiagramPopup" parentId="#DistrictDiagramPopup">
<elements xsi:type="commands:Handler" xmi:id="_H7TA8HVvEemS_rRAHnCQSw" elementId="org.simantics.district.network.ui.handler.8" contributionURI="bundleclass://org.simantics.district.network.ui/org.simantics.district.network.ui.contributions.ChangeVertexToRoutePointHandler" command="_Ee0TAHVvEemS_rRAHnCQSw"/>
<elements xsi:type="commands:Handler" xmi:id="_akE8EIAGEemKlokjSaREFQ" elementId="org.simantics.district.network.ui.handler.9" contributionURI="bundleclass://org.simantics.district.network.ui/org.simantics.district.network.ui.table.ImportCSVHandler" command="_QcEKQIAGEemKlokjSaREFQ"/>
<elements xsi:type="commands:Handler" xmi:id="_XjbIILqWEemcscVaZzEyWw" elementId="org.simantics.district.network.ui.handler.10" contributionURI="bundleclass://org.simantics.district.network.ui/org.simantics.district.network.ui.table.ImportTechTypeCSVHandler" command="_VWtY8LqWEemcscVaZzEyWw"/>
+ <elements xsi:type="commands:Handler" xmi:id="_luZP0PaJEeqB1-l6EhcCjg" elementId="org.simantics.district.network.ui.handler.11" contributionURI="bundleclass://org.simantics.district.network.ui/org.simantics.district.network.ui.techtype.table.ValidateTechTypeTableHandler" command="_dN5OYPaJEeqB1-l6EhcCjg"/>
</fragments>
</fragment:ModelFragments>
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.Set;
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.AbstractRegistryConfiguration;
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
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.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
+import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
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.style.CellStyleAttributes;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
+import org.eclipse.nebula.widgets.nattable.style.Style;
+import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.widgets.Text;
import org.simantics.Simantics;
import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+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.techtype.requests.PossibleTechTypeKeyName;
+import org.simantics.district.network.techtype.requests.PossibleTechTypeTable;
import org.simantics.district.network.techtype.requests.WriteTechTypeTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TechTypeTable extends Composite {
+ private static final String INVALID_LABEL = "INVALID";
+
private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTable.class);
NatTable table;
private TechTypeTableSortModel sortModel;
private Resource componentType;
+ private Resource tableResource;
+
+ protected Set<String> validationResult;
- public TechTypeTable(Composite parent, int style, Resource componentType, String data) {
+ public TechTypeTable(Composite parent, int style, Resource componentType, Resource tableResource, String data) {
super(parent, style);
this.componentType = componentType;
+ this.tableResource = tableResource;
defaultInitializeUI(data);
}
// directly as body layer is also working.
bodyDataProvider = new TechTypeTableDataProvider(data);
bodyDataLayer = new DataLayer(bodyDataProvider);
-
+
RowReorderLayer rowReorderLayer = new RowReorderLayer(
columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer));
table = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, gridLayer, false);
GridDataFactory.fillDefaults().grab(true, true).applyTo(table);
+
+ // Show entries labeled "INVALID" with red text
+ table.addConfiguration(new AbstractRegistryConfiguration() {
+ @Override
+ public void configureRegistry(IConfigRegistry configRegistry) {
+ Style cellStyle = new Style();
+ cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
+ configRegistry.registerConfigAttribute(
+ CellConfigAttributes.CELL_STYLE,
+ cellStyle,
+ DisplayMode.NORMAL,
+ INVALID_LABEL
+ );
+ }
+ });
// Register a CopyDataCommandHandler that also copies the headers and
// uses the configured IDisplayConverters
table.configure();
}
+ private static String getKeyColumnName(Resource componentType) {
+ String keyName = null;
+ if (componentType != null) {
+ try {
+ keyName = Simantics.getSession().syncRequest(new PossibleTechTypeKeyName(componentType));
+ } catch (DatabaseException e) {
+ LOGGER.error("Failed to read possible tech type key name for {}", componentType, e);
+ }
+ }
+ return keyName.startsWith("_") ? keyName.substring(1) : keyName;
+ }
+
@Override
public void dispose() {
cpb.dispose();
}
try {
- Simantics.getSession().syncRequest(new WriteTechTypeTable(componentType, data));
+ Simantics.getSession().syncRequest(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.syncRequest(new WriteTechTypeTable(componentType, data));
+ Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
+ if (model != null) {
+ tableResource = graph.syncRequest(new PossibleTechTypeTable(model, componentType));
+ }
+ }
+ });
} catch (DatabaseException e) {
LOGGER.error("Failed to write tech type table data to model", e);
}
setTechTypeData(data);
+ setValidationResult(null);
}
public void setTechTypeData(String data) {
this.componentType = componentType;
}
-}
+ /**
+ * Set results of a validation operation
+ *
+ * Invalid entries are designated by a string of the form "<type_code>/<property_name>".
+ *
+ * This method must be called in the SWT thread.
+ *
+ * @param result A set of strings representing invalid entries
+ */
+ public void setValidationResult(Set<String> result) {
+ if (result != null && result.isEmpty())
+ result = null;
+
+ this.validationResult = result;
+ if (result != null) {
+ String keyName = getKeyColumnName(componentType);
+
+ bodyDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() {
+ @Override
+ public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
+ if (keyName != null) {
+ int keyColumn = bodyDataProvider.getVariableIndex(keyName);
+ if (keyColumn >= 0) {
+ String key = (String) bodyDataProvider.getDataValue(keyColumn, rowPosition);
+ String columnName = bodyDataProvider.getVariableName(columnPosition);
+
+ if (validationResult.contains(key + "/" + columnName)) {
+ configLabels.addLabel(INVALID_LABEL);
+ }
+ }
+ }
+ }
+ });
+ } else {
+ bodyDataLayer.setConfigLabelAccumulator(null);
+ }
+
+ table.refresh();
+ }
+ /**
+ * Get a resource representation of the currently open table, or null if
+ * table is not stored in the model.
+ */
+ public Resource getCurrentTable() {
+ return tableResource;
+ }
+}
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.PossibleTechTypeTable;
import org.simantics.district.network.techtype.requests.PossibleTechTypeTableData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
MToolBar toolBar = MMenuFactory.INSTANCE.createToolBar();
toolBar.setToBeRendered(true);
toolBar.getChildren().add(createImportCSVDataToolItem(app));
+ toolBar.getChildren().add(createValidateTableToolItem(app));
part.setToolbar(toolBar);
}
+ private MHandledToolItem createValidateTableToolItem(MApplication app) {
+ MHandledToolItem createHandledToolItem = MMenuFactory.INSTANCE.createHandledToolItem();
+ // Command is contributed via fragment
+ MCommand command = app.getCommand("org.simantics.district.network.ui.command.validatetechtypetable");
+ createHandledToolItem.setCommand(command); //$NON-NLS-1$
+ createHandledToolItem.setLabel("Validate Tech Type Table");
+ createHandledToolItem.setIconURI("platform:/plugin/com.famfamfam.silk/icons/accept.png"); //$NON-NLS-1$
+ return createHandledToolItem;
+ }
+
private MHandledToolItem createImportCSVDataToolItem(MApplication app) {
MHandledToolItem createHandledToolItem = MMenuFactory.INSTANCE.createHandledToolItem();
// Command is contributed via fragment
.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);
+ Resource tableResource = null;
+ if (pipe != null) {
+ try {
+ Resource model = Simantics.getSession().syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
+ if (model != null) {
+ tableResource = Simantics.getSession().syncRequest(new PossibleTechTypeTable(model, pipe), TransientCacheListener.instance());
+ 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);
+ table = new TechTypeTable(parent, 0, pipe, tableResource, data);
}
@PreDestroy
--- /dev/null
+package org.simantics.district.network.ui.techtype.table;
+
+import java.util.Set;
+
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.simantics.db.Resource;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.district.network.techtype.TechTypeValidationUtils;
+import org.simantics.utils.ui.ExceptionUtils;
+
+public class ValidateTechTypeTableHandler {
+ @CanExecute
+ public boolean canExecute() {
+ return TechTypeTableView.table.getCurrentTable() != null;
+ }
+
+ @Execute
+ public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell s) {
+ Resource table = TechTypeTableView.table.getCurrentTable();
+ if (table == null)
+ return;
+
+ Set<String> result;
+ try {
+ result = TechTypeValidationUtils.validateTechTypeTable(table);
+ } catch (DatabaseException e) {
+ ExceptionUtils.logAndShowError("Tech type table validation failed", e);
+ return;
+ }
+
+ TechTypeTableView.table.setValidationResult(result);
+
+ MessageDialog.openInformation(s, "Validation result", result.isEmpty() ?
+ "No invalid values found" : result.size() + " invalid values found");
+ }
+}
--- /dev/null
+package org.simantics.district.network.techtype;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.Simantics;
+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.util.Range;
+import org.simantics.databoard.util.RangeException;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
+import org.simantics.db.common.request.ResourceRead;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.PropertyInfo;
+import org.simantics.db.layer0.request.PropertyInfoRequest;
+import org.simantics.district.network.ontology.DistrictNetworkResource;
+import org.simantics.district.network.techtype.requests.TechTypeTableData;
+import org.simantics.layer0.Layer0;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TechTypeValidationUtils {
+ private static final Logger LOGGER = LoggerFactory.getLogger(TechTypeValidationUtils.class);
+
+ /**
+ * Return a set of invalid tech type table entries.
+ *
+ * The tech type table values are validated against any limits that have been
+ * defined for associated component type properties.
+ *
+ * Invalid entries are designated by strings of the form "<type_code>/<property_name>".
+ *
+ * @param table A tech type table resource
+ * @return A set of labels for invalid values
+ * @throws DatabaseException
+ */
+ public static Set<String> validateTechTypeTable(Resource table) throws DatabaseException {
+ LOGGER.trace("Validating resource table {}", table);
+
+ // Use a unique read - we don't want to pollute the cache with this
+ return Simantics.getSession().syncRequest(new UniqueRead<Set<String>>() {
+ @Override
+ public Set<String> perform(ReadGraph graph) throws DatabaseException {
+ Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
+ if (type == null)
+ return Collections.emptySet();
+
+ Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
+ Set<String> result = new HashSet<>();
+
+ Map<String, Map<String, String>> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance());
+ for (String code : data.keySet()) {
+ LOGGER.trace(" type code {}", code);
+ Map<String, String> values = data.get(code);
+ for (String propertyName : values.keySet()) {
+ PropertyInfo info = props.get(propertyName);
+
+ // Allow property names to start with an underscore
+ if (info == null)
+ info = props.get("_" + propertyName);
+
+ if (info == null) {
+ LOGGER.trace(" {} - no property", propertyName);
+ continue;
+ }
+
+ Datatype dt = info.requiredDatatype;
+ if (dt == null || !(dt instanceof NumberType))
+ continue; // Only check ranges of numerical properties
+
+ String range = dt.metadata.get("range");
+ if (range != null) {
+ Range rng;
+ try {
+ rng = Range.valueOf(range);
+ } catch (RangeException e1) {
+ LOGGER.error("Invalid range string {} for property {}", range, propertyName, e1);
+ continue;
+ }
+
+ String valueString = values.get(propertyName).replace(",", ".");
+ try {
+ Double num = Double.valueOf(valueString);
+ NumberBinding binding = (NumberBinding)info.defaultBinding;
+ Number value = (Number) binding.create(num);
+
+ if (!rng.contains(value)) {
+ LOGGER.trace(" {} - range violation {} / {}", propertyName, valueString, range);
+ result.add(code + "/" + propertyName);
+ } else {
+ LOGGER.trace(" {} - valid value {} / {}", propertyName, valueString, range);
+ }
+ } catch (NumberFormatException e) {
+ // Nothing to do here, treat non-numeric strings as missing values
+ LOGGER.trace(" {} - no value {} / {}", propertyName, valueString, range);
+ } catch (BindingException e) {
+ LOGGER.error("Binding error for property {}", propertyName, e);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+ });
+ }
+
+ private static class PropertyInfoMapOfType extends ResourceRead<Map<String, PropertyInfo>> {
+ protected PropertyInfoMapOfType(Resource type) {
+ super(type);
+ }
+
+ @Override
+ public Map<String, PropertyInfo> perform(ReadGraph graph) throws DatabaseException {
+ Map<String, PropertyInfo> result = new HashMap<String, PropertyInfo>();
+ for (Resource prop : graph.getObjects(resource, Layer0.getInstance(graph).DomainOf)) {
+ PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop), TransientCacheListener.instance());
+ result.put(info.name, info);
+ }
+
+ return result;
+ }
+ }
+}
--- /dev/null
+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 PossibleTechTypeKeyName extends ResourceRead<String> {
+ public PossibleTechTypeKeyName(Resource componentType) {
+ super(componentType);
+ }
+
+ @Override
+ public String perform(ReadGraph graph) throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(graph);
+ DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
+
+ for (Resource r : graph.getObjects(resource, L0.DomainOf)) {
+ Resource accessor = graph.getPossibleObject(r, L0.valueAccessor);
+ if (accessor.equals(DN.TechType_Functions_techTypeCodeValueAccessor)) {
+ return graph.getRelatedValue2(r, L0.HasName);
+ }
+ }
+
+ return null;
+ }
+}
private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableData.class);
- protected TechTypeTableData(Resource table) {
+ public TechTypeTableData(Resource table) {
super(table);
}
import org.simantics.db.ReadGraph;
import org.simantics.db.Resource;
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;
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<String> {
@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;
+ return type != null ? graph.syncRequest(new PossibleTechTypeKeyName(type), TransientCacheListener.instance()) : null;
}
}
\ No newline at end of file