]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeValidationUtils.java
Utilities for checking component consistency against tech type data
[simantics/district.git] / org.simantics.district.network / src / org / simantics / district / network / techtype / TechTypeValidationUtils.java
1 package org.simantics.district.network.techtype;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.HashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Objects;
10 import java.util.Set;
11
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;
37
38 public class TechTypeValidationUtils {
39         private static final Logger LOGGER = LoggerFactory.getLogger(TechTypeValidationUtils.class);
40         
41         /**
42          * Return a set of invalid tech type table entries.
43          * 
44          * The tech type table values are validated against any limits that have been
45          * defined for associated component type properties.
46          * 
47          * Invalid entries are designated by strings of the form "<type_code>/<property_name>".
48          * 
49          * @param table  A tech type table resource
50          * @return  A set of labels for invalid values
51          * @throws DatabaseException
52          */
53         public static Set<String> validateTechTypeTable(Resource table) throws DatabaseException {
54                 LOGGER.trace("Validating resource table {}", table);
55                 
56                 // Use a unique read - we don't want to pollute the cache with this
57                 return Simantics.getSession().syncRequest(new UniqueRead<Set<String>>() {
58                         @Override
59                         public Set<String> perform(ReadGraph graph) throws DatabaseException {
60                                 Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
61                                 if (type == null)
62                                         return Collections.emptySet();
63                                 
64                                 Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
65                                 Set<String> result = new HashSet<>();
66                                 
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);
73                                                 
74                                                 // Allow property names to start with an underscore
75                                                 if (info == null)
76                                                         info = props.get("_" + propertyName);
77                                                 
78                                                 if (info == null) {
79                                                         LOGGER.trace("    {} - no property", propertyName);
80                                                         continue;
81                                                 }
82                                                 
83                                                 Datatype dt = info.requiredDatatype;
84                                                 if (dt == null || !(dt instanceof NumberType))
85                                                         continue;  // Only check ranges of numerical properties
86                                                 
87                                                 String range = dt.metadata.get("range");
88                                                 if (range != null) {
89                                                         Range rng;
90                                                         try {
91                                                                 rng = Range.valueOf(range);
92                                                         } catch (RangeException e1) {
93                                                                 LOGGER.error("Invalid range string {} for property {}", range, propertyName, e1);
94                                                                 continue;
95                                                         }
96                                                         
97                                                         Number value = getPossibleNumericValue(info, values.get(propertyName));
98                                                         if (value == null) {
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);
104                                                         } else {
105                                                                 LOGGER.trace("    {} - valid value {} / {}", propertyName, values.get(propertyName), range);
106                                                         }
107                                                 }
108                                         }
109                                 }
110                                 
111                                 return result;
112                         }
113                 });
114         }
115         
116         /**
117          * Find all component properties that do not match the value given in a tech type table.
118          * 
119          * @param table  A TechTypeTable instance
120          * @return  Lists of all non-matching properties, indexed by component resource
121          * @throws DatabaseException
122          */
123         public static Map<Resource, List<PropertyInfo>> findConsistencyViolations(Resource table) throws DatabaseException {
124                 LOGGER.trace("Validating resource table {}", table);
125                 
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>>>() {
128                         @Override
129                         public Map<Resource, List<PropertyInfo>> perform(ReadGraph graph) throws DatabaseException {
130                                 Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
131                                 if (type == null)
132                                         return Collections.emptyMap();
133                                 
134                                 Resource model = graph.syncRequest(new IndexRoot(table));
135                                 Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
136                                 
137                                 String keyName = graph.syncRequest(new PossibleTechTypeKeyName(type));
138                                 PropertyInfo keyPredicate = props.get(keyName);
139                                 
140                                 if (keyName.startsWith("_"))
141                                         keyName = keyName.substring(1);
142
143                                 Map<String, Map<String, String>> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance());
144                                 Map<Resource, List<PropertyInfo>> result = new HashMap<>();
145                                 
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);
152                                                 continue;
153                                         }
154                                         
155                                         for (PropertyInfo prop : props.values()) {
156                                                 if (!prop.isHasProperty)
157                                                         continue;
158                                                 
159                                                 String valueString = values.get(prop.name);
160                                                 if (valueString == null)
161                                                         continue;
162                                                 
163                                                 Object value = null;
164                                                 if (prop.requiredDatatype instanceof NumberType) {
165                                                         value = getPossibleNumericValue(prop, valueString);
166                                                 } else if (prop.requiredDatatype instanceof StringType) {
167                                                         value = valueString;
168                                                 } else {
169                                                         continue;
170                                                 }
171                                                 
172                                                 if (value == null) {
173                                                         Statement statement = graph.getPossibleStatement(component, prop.predicate);
174                                                         if (statement != null && statement.getObject().equals(component)) {
175                                                                 addMapListItem(result, component, prop);
176                                                         }
177                                                         
178                                                         continue;
179                                                 }
180                                                 
181                                                 Object currentValue = graph.getRelatedValue2(component, prop.predicate);
182                                                 if (!Objects.equals(value, currentValue)) {
183                                                         addMapListItem(result, component, prop);
184                                                 }
185                                         }
186                                 }
187                                 
188                                 return result;
189                         }
190                 });
191         }
192
193         private static Number getPossibleNumericValue(PropertyInfo info, String valueString) {
194                 try {
195                         Double num = Double.valueOf(valueString.replace(",", "."));
196                         NumberBinding binding = (NumberBinding)info.defaultBinding;
197                         Number value = (Number) binding.create(num);
198                         return value;
199                 } catch (NumberFormatException e) {
200                         return null;
201                 } catch (BindingException e) {
202                         LOGGER.error("Binding error for property {}", info.name, e);
203                         return null;
204                 }
205         }
206
207         private static <A, B> void addMapListItem(Map<A, List<B>> result, A a, B b) {
208                 List<B> list = result.get(a);
209                 if (list == null) {
210                         list = new ArrayList<>();
211                         result.put(a, list);
212                 }
213                 
214                 list.add(b);
215         }
216
217         private static class PropertyInfoMapOfType extends ResourceRead<Map<String, PropertyInfo>> {
218                 protected PropertyInfoMapOfType(Resource type) {
219                         super(type);
220                 }
221         
222                 @Override
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);
228                         }
229                         
230                         return result;
231                 }
232         }
233 }