001/**
002 * Copyright (C) 2007 - 2016, Jens Lehmann
003 *
004 * This file is part of DL-Learner.
005 *
006 * DL-Learner is free software; you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation; either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * DL-Learner is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
018 */
019package org.dllearner.algorithms.qtl.util;
020
021import org.apache.jena.datatypes.RDFDatatype;
022import org.apache.jena.datatypes.xsd.XSDDatatype;
023import org.apache.jena.rdf.model.Literal;
024import org.apache.jena.vocabulary.OWL;
025import org.apache.jena.vocabulary.RDF;
026import org.apache.jena.vocabulary.RDFS;
027import org.dllearner.algorithms.qtl.datastructures.QueryTree;
028import org.dllearner.algorithms.qtl.datastructures.impl.QueryTreeImpl;
029import org.dllearner.algorithms.qtl.datastructures.impl.QueryTreeImpl.LiteralNodeConversionStrategy;
030import org.dllearner.algorithms.qtl.datastructures.impl.QueryTreeImpl.NodeType;
031import org.dllearner.core.StringRenderer;
032import org.dllearner.core.StringRenderer.Rendering;
033import org.semanticweb.owlapi.apibinding.OWLManager;
034import org.semanticweb.owlapi.model.*;
035import org.semanticweb.owlapi.util.DefaultPrefixManager;
036import org.semanticweb.owlapi.vocab.OWL2Datatype;
037import org.semanticweb.owlapi.vocab.OWLFacet;
038import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
039
040import javax.xml.bind.DatatypeConverter;
041import java.util.*;
042
043/**
044 * Converts query trees into OWL class expressions and vice versa.
045 * @author Lorenz Buehmann
046 *
047 */
048public class QueryTreeConverter implements OWLClassExpressionVisitor, OWLDataRangeVisitor{
049        
050        OWLDataFactory df = new OWLDataFactoryImpl();
051        
052        Stack<QueryTree<String>> stack = new Stack<>();
053        int id = 0;
054        
055        /**
056         * Returns a OWL class expression of the given query trees.
057         * @param tree the query tree
058         */
059        public OWLClassExpression asOWLClassExpression(QueryTree<String> tree){
060                Set<OWLClassExpression> classExpressions = asOWLClassExpressions(tree);
061                OWLClassExpression expression;
062                if(classExpressions.isEmpty()){
063                        expression = df.getOWLThing();
064                } else if(classExpressions.size() == 1){
065                        expression = classExpressions.iterator().next();
066                } else {
067                        expression = df.getOWLObjectIntersectionOf(classExpressions);
068                }
069                return expression;
070        }
071        
072        /**
073         * Returns a set of OWL class expression representations of the given query tree.
074         * @param tree the query tree
075         */
076        public Set<OWLClassExpression> asOWLClassExpressions(QueryTree<String> tree){
077                Set<OWLClassExpression> classExpressions = new HashSet<>();
078        
079        List<QueryTree<String>> children = tree.getChildren();
080        for(QueryTree<String> child : children){
081                String childLabel = child.getUserObject();
082                String predicateString = (String) tree.getEdge(child);
083                if(predicateString.equals(RDF.type.getURI()) || predicateString.equals(RDFS.subClassOf.getURI())){//A
084                        if(child.isVarNode()){
085                                classExpressions.addAll(asOWLClassExpressions(child));
086                        } else {
087                                if(!childLabel.equals(OWL.Thing.getURI())){//avoid trivial owl:Thing statements
088                                        classExpressions.add(df.getOWLClass(IRI.create(childLabel)));
089                                }
090                        }
091                } else {
092                        if(child.isLiteralNode()){
093                                OWLDataProperty p = df.getOWLDataProperty(IRI.create((String) tree.getEdge(child)));
094                                if(childLabel.equals("?")){//p some int
095                                        Set<Literal> literals = child.getLiterals();
096                                        OWLDataRange dataRange = null;
097                                        if(literals.isEmpty()){//happens if there are heterogeneous datatypes
098                                                String datatypeURI = OWL2Datatype.RDFS_LITERAL.getIRI().toString();
099                                                dataRange = df.getOWLDatatype(IRI.create(datatypeURI));
100                                        } else {
101                                                for (LiteralNodeConversionStrategy strategy : LiteralNodeConversionStrategy.values()) {
102                                                                switch (strategy) {
103                                                                        case DATATYPE:
104                                                                                Literal lit = literals.iterator().next();
105                                                                                RDFDatatype datatype = lit.getDatatype();
106                                                                                String datatypeURI;
107                                                                                if (datatype == null) {
108                                                                                        datatypeURI = OWL2Datatype.RDF_PLAIN_LITERAL.getIRI().toString();
109                                                                                } else {
110                                                                                        datatypeURI = datatype.getURI();
111                                                                                }
112                                                                                dataRange = df.getOWLDatatype(IRI.create(datatypeURI));
113                                                                                break;
114                                                                        case DATA_ONE_OF:
115                                                                                dataRange = asDataOneOf(df, literals);
116                                                                                break;
117                                                                        case MIN_MAX:
118                                                                                dataRange = asFacet(df, literals);
119                                                                                break;
120                                                                        case MIN:
121                                                                                dataRange = asMinFacet(df, literals);
122                                                                                break;
123                                                                        case MAX:
124                                                                                dataRange = asMaxFacet(df, literals);
125                                                                                break;
126                                                                }
127                                                        }
128                                        }
129                                classExpressions.add(df.getOWLDataSomeValuesFrom(p, dataRange));
130                                } else {//p value 1.2
131                                        Set<Literal> literals = child.getLiterals();
132                                Literal lit = literals.iterator().next();
133                                OWLLiteral owlLiteral = asOWLLiteral(df, lit);
134                                classExpressions.add(df.getOWLDataHasValue(p, owlLiteral));
135                                }
136                        } else {
137                                OWLObjectProperty p = df.getOWLObjectProperty(IRI.create((String) tree.getEdge(child)));
138                                OWLClassExpression filler;
139                                if(child.isVarNode()){//p some C
140                                Set<OWLClassExpression> fillerClassExpressions = asOWLClassExpressions(child);
141                                if(fillerClassExpressions.isEmpty()){
142                                        filler = df.getOWLThing();
143                                } else if(fillerClassExpressions.size() == 1){
144                                        filler = fillerClassExpressions.iterator().next();
145                                } else {
146                                        filler = df.getOWLObjectIntersectionOf(fillerClassExpressions);
147                                }
148                                classExpressions.add(df.getOWLObjectSomeValuesFrom(p, filler));
149                        } else {//p value {a}
150                                classExpressions.add(df.getOWLObjectHasValue(p, df.getOWLNamedIndividual(IRI.create(childLabel))));
151                        }
152                        }
153                }
154        }
155        return classExpressions;
156        }
157        
158        private OWLDataRange asFacet(OWLDataFactory df, Set<Literal> literals){
159        //return Boolean datatype because it doesn't make sense to return a facet of Boolean values
160        if(getOWLDatatype(df, literals).equals(df.getBooleanOWLDatatype())){
161                return df.getBooleanOWLDatatype();
162        }
163        Literal min = getMin(literals);
164        Literal max = getMax(literals);
165        
166        OWLFacetRestriction minRestriction = df.getOWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, asOWLLiteral(df, min));
167        OWLFacetRestriction maxRestriction = df.getOWLFacetRestriction(OWLFacet.MAX_INCLUSIVE, asOWLLiteral(df, max));
168        
169        return df.getOWLDatatypeRestriction(getOWLDatatype(df, literals), minRestriction, maxRestriction);
170    }
171    
172    private OWLDataRange asMinFacet(OWLDataFactory df, Set<Literal> literals){
173        //return Boolean datatype because it doesn't make sense to return a facet of Boolean values
174        if(getOWLDatatype(df, literals).equals(df.getBooleanOWLDatatype())){
175                return df.getBooleanOWLDatatype();
176        }
177        Literal min = getMin(literals);
178        
179        OWLFacetRestriction minRestriction = df.getOWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, asOWLLiteral(df, min));
180        
181        return df.getOWLDatatypeRestriction(getOWLDatatype(df, literals), minRestriction);
182    }
183    
184    private OWLDataRange asMaxFacet(OWLDataFactory df, Set<Literal> literals){
185        //return Boolean datatype because it doesn't make sense to return a facet of Boolean values
186        if(getOWLDatatype(df, literals).equals(df.getBooleanOWLDatatype())){
187                return df.getBooleanOWLDatatype();
188        }
189        Literal max = getMax(literals);
190        
191        OWLFacetRestriction maxRestriction = df.getOWLFacetRestriction(OWLFacet.MAX_INCLUSIVE, asOWLLiteral(df, max));
192        
193        return df.getOWLDatatypeRestriction(getOWLDatatype(df, literals), maxRestriction);
194    }
195    
196    private OWLDataRange asDataOneOf(OWLDataFactory df, Set<Literal> literals){
197        //return Boolean datatype because it doesn't make sense to return a enumeration of Boolean values
198        if(getOWLDatatype(df, literals).equals(df.getBooleanOWLDatatype())){
199                return df.getBooleanOWLDatatype();
200        }
201        return df.getOWLDataOneOf(asOWLLiterals(df, literals));
202    }
203    
204    private Set<OWLLiteral> asOWLLiterals(OWLDataFactory df, Set<Literal> literals){
205        Set<OWLLiteral> owlLiterals = new HashSet<>(literals.size());
206        for (Literal literal : literals) {
207                        owlLiterals.add(asOWLLiteral(df, literal));
208                }
209        return owlLiterals;
210    }
211    
212    private OWLLiteral asOWLLiteral(OWLDataFactory df, Literal literal){
213        OWLLiteral owlLiteral;
214                if(literal.getDatatypeURI() == null){
215                        owlLiteral = df.getOWLLiteral(literal.getLexicalForm(), literal.getLanguage());
216                } else {
217                        owlLiteral = df.getOWLLiteral(literal.getLexicalForm(), df.getOWLDatatype(IRI.create(literal.getDatatypeURI())));
218                }
219        return owlLiteral;
220    }
221    
222    private Literal getMin(Set<Literal> literals){
223        Iterator<Literal> iter = literals.iterator();
224        Literal min = iter.next();
225        Literal l;
226        while(iter.hasNext()){
227                l = iter.next();
228                if(l.getDatatype() == XSDDatatype.XSDinteger || l.getDatatype() == XSDDatatype.XSDint){
229                        min = (l.getInt() < min.getInt()) ? l : min;
230                } else if(l.getDatatype() == XSDDatatype.XSDdouble || l.getDatatype() == XSDDatatype.XSDdecimal){
231                        min = (l.getDouble() < min.getDouble()) ? l : min;
232                } else if(l.getDatatype() == XSDDatatype.XSDfloat){
233                        min = (l.getFloat() < min.getFloat()) ? l : min;
234                } else if(l.getDatatype() == XSDDatatype.XSDdate){
235                        min = (DatatypeConverter.parseDate(l.getLexicalForm()).compareTo(DatatypeConverter.parseDate(min.getLexicalForm())) < 0) ? l : min;
236                } 
237        }
238        return min;
239    }
240    
241    private Literal getMax(Set<Literal> literals){
242        Iterator<Literal> iter = literals.iterator();
243        Literal max = iter.next();
244        Literal l;
245        while(iter.hasNext()){
246                l = iter.next();
247                if(l.getDatatype() == XSDDatatype.XSDinteger || l.getDatatype() == XSDDatatype.XSDint){
248                        max = (l.getInt() > max.getInt()) ? l : max;
249                } else if(l.getDatatype() == XSDDatatype.XSDdouble || l.getDatatype() == XSDDatatype.XSDdecimal){
250                        max = (l.getDouble() > max.getDouble()) ? l : max;
251                } else if(l.getDatatype() == XSDDatatype.XSDfloat){
252                        max = (l.getFloat() > max.getFloat()) ? l : max;
253                } else if(l.getDatatype() == XSDDatatype.XSDdate){
254                        max = (DatatypeConverter.parseDate(l.getLexicalForm()).compareTo(DatatypeConverter.parseDate(max.getLexicalForm())) > 0) ? l : max;
255                } 
256        }
257        return max;
258    }
259    
260    private OWLDatatype getOWLDatatype(OWLDataFactory df, Set<Literal> literals){
261        return df.getOWLDatatype(IRI.create(literals.iterator().next().getDatatypeURI()));
262    }
263        
264        
265        /**
266         * Converts a OWL class expression into a query tree, if possible. Note that this is not possible
267         * for all OWL constructs, e.g. universal restrictions are not allowed. An exceptions is thrown if the conversion
268         * fails.
269         * @param expression
270         * @return
271         */
272        public QueryTree<String> asQueryTree(OWLClassExpression expression){
273//              stack.push(new QueryTreeImpl<String>("?"));
274                reset();
275                expression.accept(this);
276                return stack.pop();
277        }
278        
279        private void reset(){
280                id = 0;
281                stack.clear();
282        }
283
284        private void fireUnsupportedFeatureException(OWLClassExpression expression) {
285                throw new IllegalArgumentException("Conversion of " + expression.getClass().getSimpleName() + " is not supported.");
286        }
287
288        /* (non-Javadoc)
289         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLClass)
290         */
291        @Override
292        public void visit(OWLClass cls) {
293                stack.peek().addChild(new QueryTreeImpl<>(cls.toStringID(), NodeType.RESOURCE, id++), RDF.type.getURI());
294        }
295
296        /* (non-Javadoc)
297         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectIntersectionOf)
298         */
299        @Override
300        public void visit(OWLObjectIntersectionOf expr) {
301                boolean root = stack.isEmpty();
302                stack.push(new QueryTreeImpl<>("?", NodeType.VARIABLE, id++));
303                for (OWLClassExpression op : expr.getOperandsAsList()) {
304                        op.accept(this);
305                }
306//              if(!root)
307//                      stack.pop();
308        }
309
310        /* (non-Javadoc)
311         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectUnionOf)
312         */
313        @Override
314        public void visit(OWLObjectUnionOf expr) {
315        }
316
317        /* (non-Javadoc)
318         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectComplementOf)
319         */
320        @Override
321        public void visit(OWLObjectComplementOf expr) {
322                fireUnsupportedFeatureException(expr);
323        }
324
325        /* (non-Javadoc)
326         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom)
327         */
328        @Override
329        public void visit(OWLObjectSomeValuesFrom expr) {
330                QueryTree<String> parent = stack.peek();
331                QueryTree<String> child;
332                OWLClassExpression filler = expr.getFiller();
333                if(filler.isAnonymous()){
334                        if(!(filler instanceof OWLObjectIntersectionOf)){
335                                stack.push(new QueryTreeImpl<>("?", NodeType.VARIABLE, id++));
336                        }
337                        expr.getFiller().accept(this);
338                        child = stack.pop();
339                } else {
340                        child = new QueryTreeImpl<>(filler.asOWLClass().toStringID(), NodeType.RESOURCE, id++);
341                }
342                parent.addChild(child, expr.getProperty().asOWLObjectProperty().toStringID());
343        }
344
345        /* (non-Javadoc)
346         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectAllValuesFrom)
347         */
348        @Override
349        public void visit(OWLObjectAllValuesFrom expr) {
350                fireUnsupportedFeatureException(expr);
351        }
352
353        /* (non-Javadoc)
354         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectHasValue)
355         */
356        @Override
357        public void visit(OWLObjectHasValue expr) {
358                QueryTree<String> tree = stack.peek();
359                tree.addChild(new QueryTreeImpl<>(expr.getFiller().asOWLNamedIndividual().toStringID(), NodeType.RESOURCE, id++), expr.getProperty().asOWLObjectProperty().toStringID());
360        }
361
362        /* (non-Javadoc)
363         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectMinCardinality)
364         */
365        @Override
366        public void visit(OWLObjectMinCardinality expr) {
367                fireUnsupportedFeatureException(expr);
368        }
369
370        /* (non-Javadoc)
371         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectExactCardinality)
372         */
373        @Override
374        public void visit(OWLObjectExactCardinality expr) {
375                fireUnsupportedFeatureException(expr);
376        }
377
378        /* (non-Javadoc)
379         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectMaxCardinality)
380         */
381        @Override
382        public void visit(OWLObjectMaxCardinality expr) {
383                fireUnsupportedFeatureException(expr);
384        }
385
386        /* (non-Javadoc)
387         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectHasSelf)
388         */
389        @Override
390        public void visit(OWLObjectHasSelf expr) {
391        }
392
393        /* (non-Javadoc)
394         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLObjectOneOf)
395         */
396        @Override
397        public void visit(OWLObjectOneOf expr) {
398        }
399
400        /* (non-Javadoc)
401         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataSomeValuesFrom)
402         */
403        @Override
404        public void visit(OWLDataSomeValuesFrom expr) {
405                QueryTree<String> tree = stack.peek();
406                expr.getFiller().accept(this);
407                QueryTree<String> child = stack.pop();
408                tree.addChild(child, expr.getProperty().asOWLDataProperty().toStringID());
409        }
410
411        /* (non-Javadoc)
412         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataAllValuesFrom)
413         */
414        @Override
415        public void visit(OWLDataAllValuesFrom expr) {
416                fireUnsupportedFeatureException(expr);
417        }
418
419        /* (non-Javadoc)
420         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataHasValue)
421         */
422        @Override
423        public void visit(OWLDataHasValue expr) {
424        }
425
426        /* (non-Javadoc)
427         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataMinCardinality)
428         */
429        @Override
430        public void visit(OWLDataMinCardinality expr) {
431                fireUnsupportedFeatureException(expr);
432        }
433
434        /* (non-Javadoc)
435         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataExactCardinality)
436         */
437        @Override
438        public void visit(OWLDataExactCardinality expr) {
439                fireUnsupportedFeatureException(expr);
440        }
441
442        /* (non-Javadoc)
443         * @see org.semanticweb.owlapi.model.OWLClassExpressionVisitor#visit(org.semanticweb.owlapi.model.OWLDataMaxCardinality)
444         */
445        @Override
446        public void visit(OWLDataMaxCardinality expr) {
447                fireUnsupportedFeatureException(expr);
448        }
449
450        /* (non-Javadoc)
451         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDatatype)
452         */
453        @Override
454        public void visit(OWLDatatype arg0) {
455        }
456
457        /* (non-Javadoc)
458         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDataOneOf)
459         */
460        @Override
461        public void visit(OWLDataOneOf arg0) {
462        }
463
464        /* (non-Javadoc)
465         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDataComplementOf)
466         */
467        @Override
468        public void visit(OWLDataComplementOf arg0) {
469        }
470
471        /* (non-Javadoc)
472         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDataIntersectionOf)
473         */
474        @Override
475        public void visit(OWLDataIntersectionOf arg0) {
476        }
477
478        /* (non-Javadoc)
479         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDataUnionOf)
480         */
481        @Override
482        public void visit(OWLDataUnionOf arg0) {
483        }
484
485        /* (non-Javadoc)
486         * @see org.semanticweb.owlapi.model.OWLDataRangeVisitor#visit(org.semanticweb.owlapi.model.OWLDatatypeRestriction)
487         */
488        @Override
489        public void visit(OWLDatatypeRestriction arg0) {
490        }
491        
492        public static void main(String[] args) throws Exception {
493                StringRenderer.setRenderer(Rendering.DL_SYNTAX);
494                OWLOntologyManager man = OWLManager.createOWLOntologyManager();
495                OWLDataFactory df = man.getOWLDataFactory();
496                PrefixManager pm = new DefaultPrefixManager();
497                pm.setDefaultPrefix("http://example.org/");
498                OWLClassExpression ce = df.getOWLObjectIntersectionOf(
499                                df.getOWLClass("A", pm),
500                                df.getOWLObjectSomeValuesFrom(
501                                                df.getOWLObjectProperty("p", pm),
502                                                df.getOWLObjectSomeValuesFrom(
503                                                                df.getOWLObjectProperty("r", pm),
504                                                                df.getOWLObjectIntersectionOf(
505                                                                                df.getOWLClass("A", pm),
506                                                                                df.getOWLClass("B", pm))))
507                                );
508                System.out.println(ce);
509                QueryTreeConverter converter = new QueryTreeConverter();
510                QueryTree<String> tree = converter.asQueryTree(ce);
511                tree.dump();
512        }
513
514}