1 package org.simantics.district.network.techtype;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.HashSet;
9 import java.util.Objects;
12 import org.simantics.Simantics;
13 import org.simantics.databoard.binding.NumberBinding;
14 import org.simantics.databoard.binding.error.BindingException;
15 import org.simantics.databoard.type.Datatype;
16 import org.simantics.databoard.type.NumberType;
17 import org.simantics.databoard.type.StringType;
18 import org.simantics.databoard.util.Range;
19 import org.simantics.databoard.util.RangeException;
20 import org.simantics.db.ReadGraph;
21 import org.simantics.db.Resource;
22 import org.simantics.db.Statement;
23 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
24 import org.simantics.db.common.request.IndexRoot;
25 import org.simantics.db.common.request.ResourceRead;
26 import org.simantics.db.common.request.UniqueRead;
27 import org.simantics.db.exception.DatabaseException;
28 import org.simantics.db.layer0.QueryIndexUtils;
29 import org.simantics.db.layer0.request.PropertyInfo;
30 import org.simantics.db.layer0.request.PropertyInfoRequest;
31 import org.simantics.district.network.ontology.DistrictNetworkResource;
32 import org.simantics.district.network.techtype.requests.PossibleTechTypeKeyName;
33 import org.simantics.district.network.techtype.requests.TechTypeTableData;
34 import org.simantics.layer0.Layer0;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 public class TechTypeValidationUtils {
39 private static final Logger LOGGER = LoggerFactory.getLogger(TechTypeValidationUtils.class);
42 * Return a set of invalid tech type table entries.
44 * The tech type table values are validated against any limits that have been
45 * defined for associated component type properties.
47 * Invalid entries are designated by strings of the form "<type_code>/<property_name>".
49 * @param table A tech type table resource
50 * @return A set of labels for invalid values
51 * @throws DatabaseException
53 public static Set<String> validateTechTypeTable(Resource table) throws DatabaseException {
54 LOGGER.trace("Validating resource table {}", table);
56 // Use a unique read - we don't want to pollute the cache with this
57 return Simantics.getSession().syncRequest(new UniqueRead<Set<String>>() {
59 public Set<String> perform(ReadGraph graph) throws DatabaseException {
60 Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
62 return Collections.emptySet();
64 Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
65 Set<String> result = new HashSet<>();
67 Map<String, Map<String, String>> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance());
68 for (String code : data.keySet()) {
69 LOGGER.trace(" type code {}", code);
70 Map<String, String> values = data.get(code);
71 for (String propertyName : values.keySet()) {
72 PropertyInfo info = props.get(propertyName);
74 // Allow property names to start with an underscore
76 info = props.get("_" + propertyName);
79 LOGGER.trace(" {} - no property", propertyName);
83 Datatype dt = info.requiredDatatype;
84 if (dt == null || !(dt instanceof NumberType))
85 continue; // Only check ranges of numerical properties
87 String range = dt.metadata.get("range");
91 rng = Range.valueOf(range);
92 } catch (RangeException e1) {
93 LOGGER.error("Invalid range string {} for property {}", range, propertyName, e1);
97 Number value = getPossibleNumericValue(info, values.get(propertyName));
99 // Nothing to do here, treat non-numeric strings as missing values
100 LOGGER.trace(" {} - no value {} / {}", propertyName, values.get(propertyName), range);
101 } else if (!rng.contains(value)) {
102 LOGGER.trace(" {} - range violation {} / {}", propertyName, values.get(propertyName), range);
103 result.add(code + "/" + propertyName);
105 LOGGER.trace(" {} - valid value {} / {}", propertyName, values.get(propertyName), range);
117 * Find all component properties that do not match the value given in a tech type table.
119 * @param table A TechTypeTable instance
120 * @return Lists of all non-matching properties, indexed by component resource
121 * @throws DatabaseException
123 public static Map<Resource, List<PropertyInfo>> findConsistencyViolations(Resource table) throws DatabaseException {
124 LOGGER.trace("Validating resource table {}", table);
126 // Use a unique read - we don't want to pollute the cache with this
127 return Simantics.getSession().syncRequest(new UniqueRead<Map<Resource, List<PropertyInfo>>>() {
129 public Map<Resource, List<PropertyInfo>> perform(ReadGraph graph) throws DatabaseException {
130 Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
132 return Collections.emptyMap();
134 Resource model = graph.syncRequest(new IndexRoot(table));
135 Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
137 String keyName = graph.syncRequest(new PossibleTechTypeKeyName(type));
138 PropertyInfo keyPredicate = props.get(keyName);
140 if (keyName.startsWith("_"))
141 keyName = keyName.substring(1);
143 Map<String, Map<String, String>> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance());
144 Map<Resource, List<PropertyInfo>> result = new HashMap<>();
146 for (Resource component : QueryIndexUtils.searchByType(graph, model, type)) {
147 String key = graph.getRelatedValue2(component, keyPredicate.predicate);
148 Map<String, String> values = data.get(key);
149 if (values == null) {
150 // Highlight the missing tech type key
151 addMapListItem(result, component, keyPredicate);
155 for (PropertyInfo prop : props.values()) {
156 if (!prop.isHasProperty)
159 String valueString = values.get(prop.name);
160 if (valueString == null)
164 if (prop.requiredDatatype instanceof NumberType) {
165 value = getPossibleNumericValue(prop, valueString);
166 } else if (prop.requiredDatatype instanceof StringType) {
173 Statement statement = graph.getPossibleStatement(component, prop.predicate);
174 if (statement != null && statement.getObject().equals(component)) {
175 addMapListItem(result, component, prop);
181 Object currentValue = graph.getRelatedValue2(component, prop.predicate);
182 if (!Objects.equals(value, currentValue)) {
183 addMapListItem(result, component, prop);
193 private static Number getPossibleNumericValue(PropertyInfo info, String valueString) {
195 Double num = Double.valueOf(valueString.replace(",", "."));
196 NumberBinding binding = (NumberBinding)info.defaultBinding;
197 Number value = (Number) binding.create(num);
199 } catch (NumberFormatException e) {
201 } catch (BindingException e) {
202 LOGGER.error("Binding error for property {}", info.name, e);
207 private static <A, B> void addMapListItem(Map<A, List<B>> result, A a, B b) {
208 List<B> list = result.get(a);
210 list = new ArrayList<>();
217 private static class PropertyInfoMapOfType extends ResourceRead<Map<String, PropertyInfo>> {
218 protected PropertyInfoMapOfType(Resource type) {
223 public Map<String, PropertyInfo> perform(ReadGraph graph) throws DatabaseException {
224 Map<String, PropertyInfo> result = new HashMap<String, PropertyInfo>();
225 for (Resource prop : graph.getObjects(resource, Layer0.getInstance(graph).DomainOf)) {
226 PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop), TransientCacheListener.instance());
227 result.put(info.name, info);