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 "/". * * @param table A tech type table resource * @return A set of labels for invalid values * @throws DatabaseException */ public static Set 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>() { @Override public Set perform(ReadGraph graph) throws DatabaseException { Resource type = graph.getPossibleObject(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasComponentType); if (type == null) return Collections.emptySet(); Map props = graph.syncRequest(new PropertyInfoMapOfType(type), TransientCacheListener.instance()); Set result = new HashSet<>(); Map> data = graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance()); for (String code : data.keySet()) { LOGGER.trace(" type code {}", code); Map 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> { protected PropertyInfoMapOfType(Resource type) { super(type); } @Override public Map perform(ReadGraph graph) throws DatabaseException { Map result = new HashMap(); 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; } } }