+
+ /**
+ * Find all component properties that do not match the value given in a tech type table.
+ *
+ * @param table A TechTypeTable instance
+ * @return Lists of all non-matching properties, indexed by component resource
+ * @throws DatabaseException
+ */
+ public static Map<Resource, List<PropertyInfo>> findConsistencyViolations(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<Map<Resource, List<PropertyInfo>>>() {
+ @Override
+ public Map<Resource, List<PropertyInfo>> perform(ReadGraph graph) throws DatabaseException {
+ Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType);
+ if (type == null)
+ return Collections.emptyMap();
+
+ Resource model = graph.syncRequest(new IndexRoot(table));
+ Map<String, PropertyInfo> props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance());
+
+ String keyName = graph.syncRequest(new PossibleTechTypeKeyName(type));
+ PropertyInfo keyPredicate = props.get(keyName);
+
+ if (keyName.startsWith("_"))
+ keyName = keyName.substring(1);
+
+ Map<String, Map<String, String>> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance());
+ Map<Resource, List<PropertyInfo>> result = new HashMap<>();
+
+ for (Resource component : QueryIndexUtils.searchByType(graph, model, type)) {
+ String key = graph.getRelatedValue2(component, keyPredicate.predicate);
+ Map<String, String> values = data.get(key);
+ if (values == null) {
+ // Highlight the missing tech type key
+ addMapListItem(result, component, keyPredicate);
+ continue;
+ }
+
+ for (PropertyInfo prop : props.values()) {
+ if (!prop.isHasProperty)
+ continue;
+
+ String valueString = values.get(prop.name);
+ if (valueString == null)
+ continue;
+
+ Object value = null;
+ if (prop.requiredDatatype instanceof NumberType) {
+ value = getPossibleNumericValue(prop, valueString);
+ } else if (prop.requiredDatatype instanceof StringType) {
+ value = valueString;
+ } else {
+ continue;
+ }
+
+ if (value == null) {
+ Statement statement = graph.getPossibleStatement(component, prop.predicate);
+ if (statement != null && statement.getObject().equals(component)) {
+ addMapListItem(result, component, prop);
+ }
+
+ continue;
+ }
+
+ Object currentValue = graph.getRelatedValue2(component, prop.predicate);
+ if (!Objects.equals(value, currentValue)) {
+ addMapListItem(result, component, prop);
+ }
+ }
+ }
+
+ return result;
+ }
+ });
+ }
+
+ private static Number getPossibleNumericValue(PropertyInfo info, String valueString) {
+ try {
+ Double num = Double.valueOf(valueString.replace(",", "."));
+ NumberBinding binding = (NumberBinding)info.defaultBinding;
+ Number value = (Number) binding.create(num);
+ return value;
+ } catch (NumberFormatException e) {
+ return null;
+ } catch (BindingException e) {
+ LOGGER.error("Binding error for property {}", info.name, e);
+ return null;
+ }
+ }
+
+ private static <A, B> void addMapListItem(Map<A, List<B>> result, A a, B b) {
+ List<B> list = result.get(a);
+ if (list == null) {
+ list = new ArrayList<>();
+ result.put(a, list);
+ }
+
+ list.add(b);
+ }