package org.simantics.modeling.scl.ontologymodule; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import org.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.uri.UnescapedChildMapOfResource; import org.simantics.db.exception.DatabaseException; import org.simantics.db.request.Read; import org.simantics.layer0.Layer0; import org.simantics.scl.compiler.common.names.Name; import org.simantics.scl.compiler.constants.StringConstant; import org.simantics.scl.compiler.elaboration.contexts.SimplificationContext; import org.simantics.scl.compiler.elaboration.expressions.EApply; import org.simantics.scl.compiler.elaboration.expressions.EExternalConstant; import org.simantics.scl.compiler.elaboration.expressions.ELiteral; import org.simantics.scl.compiler.elaboration.expressions.ESimpleLambda; import org.simantics.scl.compiler.elaboration.expressions.EVariable; import org.simantics.scl.compiler.elaboration.expressions.Expression; import org.simantics.scl.compiler.elaboration.expressions.Variable; import org.simantics.scl.compiler.elaboration.macros.MacroRule; import org.simantics.scl.compiler.elaboration.modules.SCLValue; import org.simantics.scl.compiler.elaboration.relations.SCLEntityType; import org.simantics.scl.compiler.elaboration.relations.SCLRelation; import org.simantics.scl.compiler.environment.filter.NamespaceFilter; import org.simantics.scl.compiler.errors.Locations; import org.simantics.scl.compiler.module.ImportDeclaration; import org.simantics.scl.compiler.module.LazyModule; import org.simantics.scl.compiler.types.TCon; import org.simantics.scl.compiler.types.TVar; import org.simantics.scl.compiler.types.Type; import org.simantics.scl.compiler.types.Types; import org.simantics.scl.compiler.types.exceptions.SCLTypeParseException; import org.simantics.scl.compiler.types.kinds.Kinds; import org.simantics.scl.runtime.SCLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectProcedure; public class OntologyModule extends LazyModule { private static final Logger LOGGER = LoggerFactory.getLogger(OntologyModule.class); private static final String DB_MODULE = "Simantics/DB"; private static final String VARIABLE_MODULE = "Simantics/Variable"; private static final Collection DEPENDENCIES = Arrays.asList( new ImportDeclaration(DB_MODULE, null), new ImportDeclaration(VARIABLE_MODULE, null) ); private static final TCon RESOURCE = Types.con(DB_MODULE, "Resource"); private static final TCon BROWSABLE = Types.con(DB_MODULE, "Browsable"); private static final TCon VARIABLE = Types.con(VARIABLE_MODULE, "Variable"); Resource ontology; String defaultLocalName; THashMap> childMaps = new THashMap>(); public OntologyModule(ReadGraph graph, String moduleName) throws DatabaseException { super(moduleName); ontology = graph.getResource(moduleName); readDefaultLocalName(graph); childMaps.put(ontology, createLocalMap(graph, ontology)); } private void readDefaultLocalName(ReadGraph graph) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); defaultLocalName = graph.getPossibleRelatedValue(ontology, L0.Ontology_defaultLocalName); if(defaultLocalName == null) defaultLocalName = ""; } @Override public String getDefaultLocalName() { return defaultLocalName; } @Override public List getDependencies() { //return DEPENDENCIES; return Collections.emptyList(); } private static interface ResourceSearchResult {} private static class JustResource implements ResourceSearchResult { public final Resource resource; public JustResource(Resource resource) { this.resource = resource; } } private static class ResourceAndSuffix implements ResourceSearchResult { public final Resource resource; public final String suffix; public ResourceAndSuffix(Resource resource, String suffix) { this.resource = resource; this.suffix = suffix; } } private ResourceSearchResult getResourceOrSuffixedResource(String name) { Map localMap = childMaps.get(ontology); if(localMap == null) return null; Resource parent = ontology; while(true) { int p = name.indexOf('.'); if(p < 0) break; String localName = name.substring(0, p); parent = localMap.get(localName); if(parent == null) return null; name = name.substring(p+1); // Get new local map localMap = getLocalMap(parent); if(localMap == null) return null; } Resource child = localMap.get(name); if(child != null) return new JustResource(child); else return new ResourceAndSuffix(parent, name); } private Resource getResource(String name) { ResourceSearchResult searchResult = getResourceOrSuffixedResource(name); if(searchResult instanceof JustResource) return ((JustResource)searchResult).resource; else return null; } private Map getLocalMap(Resource parent) { Map localMap = childMaps.get(parent); if(localMap == null) { if(childMaps.contains(parent)) return null; localMap = createLocalMap(parent); childMaps.put(parent, localMap); } return localMap; } private static Map createLocalMap(final Resource parent) { ReadGraph graph = (ReadGraph)SCLContext.getCurrent().get("graph"); if(graph != null) return createLocalMap(graph, parent); else try { return Simantics.getSession().syncRequest(new Read>() { @Override public Map perform(ReadGraph graph) throws DatabaseException { return createLocalMap(graph, parent); } }); } catch(DatabaseException e) { e.printStackTrace(); return null; } } private static Map createLocalMap(ReadGraph graph, Resource parent) { try { return graph.syncRequest(new UnescapedChildMapOfResource(parent)); } catch (DatabaseException e) { e.printStackTrace(); return null; } } @FunctionalInterface private static interface ResourceFunctionGenerator { SCLValue createValue(Name name, Resource resource); } private static class RelatedValueMacroRule implements MacroRule { private final Resource relation; private final SCLRelationInfo relationInfo; private final boolean optionalValue; public RelatedValueMacroRule(Resource relation, SCLRelationInfo relationInfo, boolean optionalValue) { this.relation = relation; this.relationInfo = relationInfo; this.optionalValue = optionalValue; } private Expression applyWithSubject(SimplificationContext context, Type subjectType, Expression evidence, Expression subject) { if(Types.equals(subjectType, RESOURCE)) return new EApply( Locations.NO_LOCATION, Types.READ_GRAPH, context.getConstant(Name.create(DB_MODULE, optionalValue ? "possibleRelatedValue2" : "relatedValue2"), relationInfo.rangeType), subject, new EExternalConstant(relation, RESOURCE)); else if(Types.equals(subjectType, VARIABLE)) return new EApply( Locations.NO_LOCATION, Types.READ_GRAPH, context.getConstant(Name.create(DB_MODULE, optionalValue ? "untypedPossiblePropertyValue" : "untypedPropertyValue"), relationInfo.rangeType), subject, new ELiteral(new StringConstant(relationInfo.name))); else return new EApply( Locations.NO_LOCATION, Types.READ_GRAPH, context.getConstant(Name.create(DB_MODULE, optionalValue ? "genericPossibleRelatedValue" : "genericRelatedValue"), subjectType, relationInfo.rangeType), evidence, subject, new EExternalConstant(relation, RESOURCE)); } @Override public Expression apply(SimplificationContext context, Type[] typeParameters, EApply apply) { Type subjectType = typeParameters[0]; if(apply.parameters.length == 1) { Variable subject = new Variable("subject", subjectType); return new ESimpleLambda(subject, applyWithSubject(context, subjectType, apply.parameters[0], new EVariable(subject))); } else if(apply.parameters.length >= 2) { Expression valueReplacement = applyWithSubject(context, subjectType, apply.parameters[0], apply.parameters[1]); if(apply.parameters.length == 2) return valueReplacement; else { apply.set(valueReplacement, Arrays.copyOfRange(apply.parameters, 2, apply.parameters.length)); return apply; } } else { LOGGER.error("Application of relation following functions should have at least one parameter (the evidence of Browsable)."); return null; } } } private final static HashMap VALUE_GENERATOR_MAP = new HashMap<>(); static { TVar A = Types.var(Kinds.STAR); VALUE_GENERATOR_MAP.put("value", (name, resource) -> { SCLRelationInfo relationInfo = SCLRelationInfoRequest.getRelationInfo(resource); if(relationInfo == null) return null; SCLValue value = new SCLValue(name); value.setType(Types.forAll(A, Types.function(Types.pred(BROWSABLE, A), Types.functionE(A, Types.READ_GRAPH, relationInfo.rangeType)))); value.setMacroRule(new RelatedValueMacroRule(resource, relationInfo, false)); return value; }); VALUE_GENERATOR_MAP.put("possibleValue", (name, resource) -> { SCLRelationInfo relationInfo = SCLRelationInfoRequest.getRelationInfo(resource); if(relationInfo == null) return null; SCLValue value = new SCLValue(name); value.setType(Types.forAll(A, Types.function(Types.pred(BROWSABLE, A), Types.functionE(A, Types.READ_GRAPH, Types.apply(Types.MAYBE, relationInfo.rangeType))))); value.setMacroRule(new RelatedValueMacroRule(resource, relationInfo, true)); return value; }); } @Override protected SCLValue createValue(String name) { ResourceSearchResult searchResult = getResourceOrSuffixedResource(name); if(searchResult instanceof JustResource) { Resource resource = ((JustResource)searchResult).resource; SCLValue value = new SCLValue(Name.create(getName(), name)); value.setType(RESOURCE); value.setExpression(new EExternalConstant(resource, RESOURCE)); value.setInlineInSimplification(true); return value; } else if(searchResult instanceof ResourceAndSuffix){ ResourceAndSuffix resourceAndSuffix = (ResourceAndSuffix)searchResult; ResourceFunctionGenerator generator = VALUE_GENERATOR_MAP.get(resourceAndSuffix.suffix); if(generator == null) return null; else return generator.createValue(Name.create(getName(), name), resourceAndSuffix.resource); } else return null; } @Override protected SCLRelation createRelation(String name) { final Resource resource = getResource(name); if(resource == null) return null; ReadGraph graph = (ReadGraph)SCLContext.getCurrent().get("graph"); if(graph != null) return createRelation(graph, resource); else try { return Simantics.getSession().syncRequest(new Read() { @Override public SCLRelation perform(ReadGraph graph) throws DatabaseException { return createRelation(graph, resource); } }); } catch(DatabaseException e) { e.printStackTrace(); return null; } } public static SCLRelation createRelation(ReadGraph graph, Resource relation) { try { Layer0 L0 = Layer0.getInstance(graph); if(!graph.isInstanceOf(relation, L0.Relation)) return null; if(graph.isInstanceOf(relation, L0.PropertyRelation) && graph.isInstanceOf(relation, L0.FunctionalRelation)) { Type valueType = getValueType(graph, relation); if(valueType != null) return new GraphPropertyRelation(relation, valueType); } Resource inverseRelation = graph.getPossibleInverse(relation); return new GraphRelation(relation, getSelectivity(graph, relation), inverseRelation, getSelectivity(graph, inverseRelation)); } catch(DatabaseException e) { e.printStackTrace(); return null; } } @Override protected SCLEntityType createEntityType(String name) { final Resource resource = getResource(name); if(resource == null) return null; ReadGraph graph = (ReadGraph)SCLContext.getCurrent().get("graph"); if(graph != null) return createEntityType(graph, resource); else try { return Simantics.getSession().syncRequest(new Read() { @Override public SCLEntityType perform(ReadGraph graph) throws DatabaseException { return createEntityType(graph, resource); } }); } catch(DatabaseException e) { e.printStackTrace(); return null; } } private SCLEntityType createEntityType(ReadGraph graph, Resource type) { try { Layer0 L0 = Layer0.getInstance(graph); if(!graph.isInstanceOf(type, L0.Type)) return null; return new GraphEntityType(graph, type); } catch(DatabaseException e) { e.printStackTrace(); return null; } } private static double getSelectivity(ReadGraph graph, Resource relation) throws DatabaseException { if(relation == null) return Double.POSITIVE_INFINITY; Layer0 L0 = Layer0.getInstance(graph); if(graph.isInstanceOf(relation, L0.FunctionalRelation)) return 1.0; else return 10.0; } private static Type getValueType(ReadGraph graph, Resource relation) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); Type valueType = parseValueType((String)graph.getPossibleRelatedValue(relation, L0.RequiresValueType, Bindings.STRING)); if(valueType != null) return valueType; Resource range = graph.getPossibleObject(relation, L0.HasRange); if(range != null) { for(Resource valueTypeLiteral : graph.getAssertedObjects(range, L0.HasValueType)) { valueType = parseValueType((String)graph.getValue(valueTypeLiteral, Bindings.STRING)); if(valueType != null) return valueType; } } return null; } private static Type parseValueType(String valueTypeString) { if(valueTypeString == null) return null; try { return Types.parseType(valueTypeString); } catch (SCLTypeParseException e) { e.printStackTrace(); return null; } } @Override public void findValuesForPrefix(String prefix, NamespaceFilter filter, TObjectProcedure proc) { Map localMap = childMaps.get(ontology); if(localMap == null) return; String namePrefix = ""; while(true) { int p = prefix.indexOf('.'); if(p < 0) break; String localName = prefix.substring(0, p); Resource newParent = localMap.get(localName); if(newParent == null) return; prefix = prefix.substring(p+1); namePrefix = namePrefix + localName + "."; // Get new local map localMap = getLocalMap(newParent); if(localMap == null) return; } for(String name : localMap.keySet()) if(name.startsWith(prefix) && filter.isValueIncluded(name)) proc.execute(getValue(namePrefix+name)); } @Override public void findValuesForPrefix(String prefix, NamespaceFilter filter, Consumer consumer) { Map localMap = childMaps.get(ontology); if(localMap == null) return; String namePrefix = ""; while(true) { int p = prefix.indexOf('.'); if(p < 0) break; String localName = prefix.substring(0, p); Resource newParent = localMap.get(localName); if(newParent == null) return; prefix = prefix.substring(p+1); namePrefix = namePrefix + localName + "."; // Get new local map localMap = getLocalMap(newParent); if(localMap == null) return; } for(String name : localMap.keySet()) if(name.startsWith(prefix) && filter.isValueIncluded(name)) consumer.accept(getValue(namePrefix+name)); } @Override public void findTypesForPrefix(String prefix, NamespaceFilter instance, Consumer consumer) { } @Override public void dispose() { childMaps.clear(); childMaps = null; ontology = null; } @Override public String toString() { return new StringBuilder().append("OntologyModule ").append(getName()).toString(); } @Override public ClassLoader getParentClassLoader() { return getClass().getClassLoader(); } }