Skip to content

Commit

Permalink
Merge pull request #196 from kbss-cvut/reafctor/issue-195-refactor-SP…
Browse files Browse the repository at this point in the history
…ipesUtil-in-s-pipes-forms

[FIX #195] Refactoring SPipesUtil.java and code that uses it.
  • Loading branch information
blcham authored Aug 22, 2023
2 parents 82c4873 + 47def39 commit e18d712
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
//import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.util.Map;

import static cz.cvut.spipes.transform.SPipesUtil.SpinQueries;
import java.util.Optional;

public class AnonNodeTransformer {

Expand All @@ -28,26 +27,20 @@ public static String serialize(RDFNode node) {
if (SPINExpressions.isExpression(r)) {
return SPINFactory.asExpression(r).toString();
}
for (SpinQueries c : SpinQueries.values())
if (r.canAs(c.getClazz()))
return getFromQuery(r, c.getClazz());
Class commandCls = Optional.ofNullable(SPipesUtil.getSPinCommandType(r))
.map(p -> p.getClazz())
.filter(c -> r.canAs(c)).orElse(null);
if(commandCls != null)
return getFromQuery(r, commandCls);

return ARQFactory.get().createExpressionString(r);
}

public static Query parse(Question q, Model m) {
String t = q.getProperties().get(Vocabulary.s_p_has_answer_value_type).iterator().next();
switch (t) {
case Vocabulary.s_c_Ask:
break;
case Vocabulary.s_c_Construct:
break;
case Vocabulary.s_c_Describe:
break;
case Vocabulary.s_c_Select:
break;
default:
throw new UnsupportedOperationException();
}
if(!SPipesUtil.isSpinQueryType(t))
throw new UnsupportedOperationException();

StringBuilder b = new StringBuilder();
Map<String, String> map = m.getNsPrefixMap();
String s = q.getAnswers().iterator().next().getTextValue();
Expand Down
240 changes: 214 additions & 26 deletions s-pipes-forms/src/main/java/cz/cvut/spipes/transform/SPipesUtil.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,114 @@
package cz.cvut.spipes.transform;

import cz.cvut.sforms.Vocabulary;
import cz.cvut.kbss.jopa.loaders.ClasspathScanner;
import cz.cvut.kbss.jopa.loaders.DefaultClasspathScanner;
import cz.cvut.spipes.constants.SM;
import org.apache.jena.rdf.model.InfModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.RDF;
import org.topbraid.spin.model.Ask;
import org.topbraid.spin.model.Construct;
import org.topbraid.spin.model.Describe;
import org.topbraid.spin.model.Select;
import org.apache.jena.vocabulary.RDFS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.topbraid.spin.model.Command;
import org.topbraid.spin.vocabulary.SP;

import java.util.HashSet;
import java.util.Set;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class SPipesUtil {

enum SpinQueries {
ASK(Ask.class, Vocabulary.s_c_Ask),
CONSTRUCT(Construct.class, Vocabulary.s_c_Construct),
DESCRIBE(Describe.class, Vocabulary.s_c_Describe),
SELECT(Select.class, Vocabulary.s_c_Select);
private static final Logger LOG = LoggerFactory.getLogger(SPipesUtil.class);

private Class clazz;
private String uri;
static Set<Class> superClasses;

SpinQueries(Class clazz, String uri) {
this.clazz = clazz;
this.uri = uri;
/** Contains SP ontology loaded from file "form-ontology/sp.ttl" */
static InfModel spModel;
static Map<Class, SpinCommandType> spinQueries;
static Map<Resource, SpinCommandType> spinQueriesReverse;

static {
if(spinQueries == null){
spinQueries = buildSpinQueryMap();
spinQueriesReverse = new HashMap<>();
spinQueries.entrySet().forEach(e -> spinQueriesReverse.put(e.getValue().getResource(), e.getValue()));
}
}

public Class getClazz() {
return clazz;
protected static Map<Class, SpinCommandType> buildSpinQueryMap(){
List<Class> classes = new ArrayList<>();
Model raw = ModelFactory.createDefaultModel();
URL modelUrl = SPipesUtil.class.getResource("/form-ontology/sp.ttl");
try {
raw.read(modelUrl.openStream(), null, "TTL");
} catch (IOException e) {
throw new RuntimeException(e);
}

public String getUri() {
return uri;
spModel = ModelFactory.createRDFSModel(raw);

// Find command related classes

ClasspathScanner scanner = new DefaultClasspathScanner();
scanner.addListener(c -> {
if(Command.class.isAssignableFrom(c) && c.isInterface())
classes.add(c);
});
scanner.processClasses("org.topbraid.spin.model");
superClasses = new HashSet<>();

// calculate superclasses
for(int i = 0; i < classes.size(); i ++){
Class cls = classes.get(i);
for(int j = 0; j < classes.size(); j ++){
if(j == i)
continue;
if(cls.isAssignableFrom(classes.get(j))) {
superClasses.add(cls);
break;
}
}
}
// remove super classes
// classes.removeAll(superClasses);

//fill map
Map<Class, SpinCommandType> map = new HashMap<>();
classes.forEach(c -> {
List<Resource> matchingResources = getMatchingResourceClasses(c, spModel);
if(!matchingResources.isEmpty()) {
Resource r = matchingResources.get(0);
if(matchingResources.size() > 1)
LOG.warn("Ambiguous mapping spin command classes to SP resources, using <%s> from ", r.getURI(), matchingResources);
map.put(c, type(r,c));
}
});

return map;
}

protected static List<Resource> getMatchingResourceClasses(Class cls, Model m) {
// Pattern.compile("(?i)^.*#" + cls.getSimpleName());
Pattern pattern = Pattern.compile("^.*#" + cls.getSimpleName());
String name = "#" + cls.getName().toLowerCase();
List<Resource> matchingResources = new ArrayList<>();
for(Resource rCls : m.listSubjectsWithProperty(RDF.type, RDFS.Class).toList()){
if(!rCls.isURIResource())
continue;
if(pattern.matcher(rCls.getURI()).matches())
matchingResources.add(rCls);

}
return matchingResources;
}




private static final Set<String> S_PIPES_TERMS = new HashSet<String>() {
{
add(SM.next.getURI());
Expand All @@ -48,10 +120,126 @@ public static boolean isSPipesTerm(Resource term) {
return S_PIPES_TERMS.contains(term.getURI());
}

public static String getSpinQueryUri(Resource r) {
for (SpinQueries v : SpinQueries.values())
if (r.canAs(v.getClazz()))
return v.getUri();
return null;
public static SpinCommandType getSPinCommandType(Resource res){
List<SpinCommandType> classes = res.listProperties(RDF.type)
.mapWith(s -> s.getObject())
// .filterKeep(o -> o.isURIResource())
// .mapWith(o -> o.asResource())
.mapWith(spinQueriesReverse::get)
.filterKeep(c -> c != null)
.toList();

if(classes.isEmpty()){
// not a spin command
return null;
}

// get most specific command types
List<SpinCommandType> mostSpecificClasses = getMostSpecificTypes(classes);

if(mostSpecificClasses.isEmpty()) {
LOG.warn("Spin command has no specific spin command type , command is assigned following types {}." +
"This issue could be caused by a cycle of rdfs:subClassOf axioms. ",
classes.stream()
.map(p -> p.getResource())
.map(r -> String.format("<%s>", r.getURI()))
.collect(Collectors.joining(", "))
);

// when there are no leaf command types use all command types
mostSpecificClasses = classes;
}

// filter non leaf classes if possible
List<SpinCommandType> mostSpecificLeafClasses = mostSpecificClasses.stream()
.filter(p -> !superClasses.contains(p.getClazz())).collect(Collectors.toList());
if(mostSpecificLeafClasses.isEmpty()){
LOG.warn("Spin command has no leaf spin command type, command is assigned following types {}." +
"This issue could resolved by specifying a leaf spin command type to the command.",
mostSpecificClasses.stream()
.map(p -> p.getResource())
.map(r -> String.format("<%s>", r.getURI()))
.collect(Collectors.joining(", "))
);
// use mostSpecificClasses instead
mostSpecificLeafClasses = mostSpecificClasses;
}

SpinCommandType type = mostSpecificClasses.get(0);
if(mostSpecificLeafClasses.size() > 1) {
LOG.warn("Ambiguous spin command is assigned multiple spin command types {}. Using sping command type <{}>",
mostSpecificLeafClasses.stream()
.map(p -> p.getResource())
.map(r -> String.format("<%s>", r.getURI()))
.collect(Collectors.joining(", ")),
type.getResource()
);
}

return type;
}

/**
* Reduce the list of pairs to contain only those pairs which reference the most specific resource type according to
* the {@link SPipesUtil#spModel}
*
* @param types
* @return a List of pairs referencing the most specific resource types according to the {@link SPipesUtil#spModel}
*/
protected static List<SpinCommandType> getMostSpecificTypes(List<SpinCommandType> types){
Map<Resource, SpinCommandType> map = new HashMap<>();
types.forEach(p -> map.put(p.getResource(), p));

Set<Resource> superTypes = map.keySet().stream()
.flatMap(t -> t
.listProperties(RDFS.subClassOf)
.mapWith(s -> s.getObject().asResource())
.filterKeep(st -> !st.equals(t))
.toList().stream())
.collect(Collectors.toSet());
superTypes.forEach(st -> map.remove(st));
return new ArrayList<>(map.values());
}

public static boolean isSpinQueryType(String uri){
return isSpinCommandType(uri, SP.Query);
}

public static boolean isSpinUpdateType(String uri){
return isSpinCommandType(uri, SP.Update);
}

public static boolean isSpinCommandType(String uri){
return isSpinCommandType(uri, SP.Command);
}

protected static boolean isSpinCommandType(final String uri, Resource commandType){
return spinQueriesReverse.keySet().stream()
.filter(r -> r.getURI().equals(uri))
.filter(r -> !r.listProperties(RDFS.subClassOf).mapWith(s -> s.getObject().asResource())
.filterKeep(st -> st.equals(commandType)).toList().isEmpty()
).findAny().isPresent();
}

public static class SpinCommandType {
protected Resource resource;
protected Class clazz;

public SpinCommandType(Resource resource, Class clazz) {
this.resource = resource;
this.clazz = clazz;
}

public Resource getResource() {
return resource;
}

public Class getClazz() {
return clazz;
}
}

protected static SpinCommandType type(Resource resource, Class clazz){
return new SpinCommandType(resource, clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ public Question script2Form(Resource module, Resource moduleType) {
Question subQ = createQuestion(p);
subQ.setProperties(extractQuestionMetadata(st));

if (st.getObject().isAnon() && SPipesUtil.getSpinQueryUri(st.getObject().asResource()) != null) {
if (st.getObject().isAnon() && SPipesUtil.getSPinCommandType(st.getObject().asResource()) != null) {
subQ.setLayoutClass(Collections.singleton("sparql"));
subQ.getProperties().put(Vocabulary.s_p_has_answer_value_type, Collections.singleton(SPipesUtil.getSpinQueryUri(st.getObject().asResource())));
subQ.setDeclaredPrefix(p.getModel().getNsPrefixMap().entrySet().stream().map(prefix -> new PrefixDefinition(prefix.getKey(), prefix.getValue())).collect(Collectors.toSet()));
subQ.getProperties().put(
Vocabulary.s_p_has_answer_value_type,
Collections.singleton(SPipesUtil.getSPinCommandType(st.getObject().asResource()).getResource().getURI())
);
subQ.setDeclaredPrefix(p.getModel().getNsPrefixMap().entrySet().stream().map(
prefix -> new PrefixDefinition(prefix.getKey(), prefix.getValue())).collect(Collectors.toSet())
);
}

Answer a = getAnswer(st.getObject());
Expand Down

0 comments on commit e18d712

Please sign in to comment.