001    // This file is part of the Attempto Java Packages.
002    // Copyright 2008-2009, Attempto Group, University of Zurich (see http://attempto.ifi.uzh.ch).
003    //
004    // The Attempto Java Packages is free software: you can redistribute it and/or modify it under the
005    // terms of the GNU Lesser General Public License as published by the Free Software Foundation,
006    // either version 3 of the License, or (at your option) any later version.
007    //
008    // The Attempto Java Packages is distributed in the hope that it will be useful, but WITHOUT ANY
009    // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
010    // PURPOSE. See the GNU Lesser General Public License for more details.
011    //
012    // You should have received a copy of the GNU Lesser General Public License along with the Attempto
013    // Java Packages. If not, see http://www.gnu.org/licenses/.
014    
015    package ch.uzh.ifi.attempto.acewiki.core.ontology;
016    
017    import java.io.File;
018    import java.io.FileInputStream;
019    import java.io.FileOutputStream;
020    import java.io.IOException;
021    import java.io.StringWriter;
022    import java.net.URI;
023    import java.net.URISyntaxException;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.HashSet;
029    import java.util.Hashtable;
030    import java.util.List;
031    import java.util.Set;
032    
033    import org.coode.owlapi.owlxml.renderer.OWLXMLRenderer;
034    import org.mindswap.pellet.owlapi.Reasoner;
035    import org.semanticweb.owl.apibinding.OWLManager;
036    import org.semanticweb.owl.inference.OWLReasoner;
037    import org.semanticweb.owl.inference.OWLReasonerException;
038    import org.semanticweb.owl.io.StringInputSource;
039    import org.semanticweb.owl.model.OWLClass;
040    import org.semanticweb.owl.model.OWLDescription;
041    import org.semanticweb.owl.model.OWLIndividual;
042    import org.semanticweb.owl.model.OWLObjectOneOf;
043    import org.semanticweb.owl.model.OWLOntology;
044    import org.semanticweb.owl.model.OWLOntologyCreationException;
045    import org.semanticweb.owl.model.OWLOntologySetProvider;
046    import org.semanticweb.owl.model.OWLSubClassAxiom;
047    import org.semanticweb.owl.util.OWLOntologyMerger;
048    
049    import uk.ac.manchester.cs.owl.OWLClassImpl;
050    import uk.ac.manchester.cs.owl.OWLDataFactoryImpl;
051    import ch.uzh.ifi.attempto.ape.LexiconEntry;
052    import ch.uzh.ifi.attempto.echocomp.Logger;
053    
054    /**
055     * This class represents an AceWiki ontology which consists of ontology element definitions and
056     * of ontological statements. Each ontology element has its own article that consists of ontological
057     * statements.
058     * 
059     * @author Tobias Kuhn
060     */
061    public class Ontology {
062            
063            private static final HashMap<String, Ontology> ontologies = new HashMap<String, Ontology>();
064            
065            private ArrayList<OntologyElement> elements = new ArrayList<OntologyElement>();
066            private Hashtable<String, OntologyElement> wordIndex = new Hashtable<String, OntologyElement>();
067            private Hashtable<Long, OntologyElement> idIndex = new Hashtable<Long, OntologyElement>();
068            
069            private final String name;
070            private final String baseURI;
071            private long idCount = 0;
072            private long stateID = 0;
073            
074            private OWLReasoner reasoner;
075            private OWLOntology differentIndividualsAxiom;
076            
077            /**
078             * Creates a new empty ontology with the given name and base URI.
079             * 
080             * @param name The name of the ontology.
081             * @param baseURI The base URI that is used to identify the ontology elements.
082             */
083            private Ontology(String name, String baseURI) {
084                    this.name = name.toString();  // null value throws an exception
085                    this.baseURI = baseURI;
086                    if (baseURI == null) {
087                            baseURI = "";
088                    }
089                    ontologies.put(name, this);
090            }
091            
092            /**
093             * Loads an ontology (or creates an empty ontology if the ontology cannot be found). The complete
094             * URI of the ontology is baseURI + name.
095             * 
096             * @param name The name of the ontology.
097             * @param baseURI The base URI that is used to identify the ontology elements.
098             * @return The loaded ontology.
099             */
100            public synchronized static Ontology loadOntology(String name, String baseURI) {
101                    if (ontologies.get(name) != null) {
102                            return ontologies.get(name);
103                    }
104                    Ontology ontology = new Ontology(name, baseURI);
105                    ontology.log("loading ontology");
106                    File dataDir = new File("data/" + name);
107                    if (dataDir.exists()) {
108                            for (File file : dataDir.listFiles()) {
109                                    try {
110                                            long id = new Long(file.getName());
111                                            ontology.log("reading file: " + file.getName());
112                                            FileInputStream in = new FileInputStream(file);
113                                            byte[] bytes = new byte[in.available()];
114                                            in.read(bytes);
115                                            in.close();
116                                            String s = new String(bytes, "UTF-8");
117                                            OntologyElement.loadOntologyElement(s, id, ontology);
118                                    } catch (NumberFormatException ex) {
119                                            ontology.log("ignoring file: " + file.getName());
120                                    } catch (IOException ex) {
121                                            ontology.log("cannot read file: " + file.getName());
122                                    }
123                            }
124                    } else {
125                            ontology.log("no data found; blank ontology is created");
126                    }
127                    ontology.refreshReasoner();
128                    return ontology;
129            }
130            
131            synchronized void save(OntologyElement oe) {
132                    if (!(new File("data")).exists()) (new File("data")).mkdir();
133                    if (!(new File("data/" + name)).exists()) (new File("data/" + name)).mkdir();
134                    
135                    if (!elements.contains(oe)) {
136                            (new File("data/" + name + "/" + oe.getId())).delete();
137                            return;
138                    }
139                    
140                    try {
141                            FileOutputStream out = new FileOutputStream("data/" + name + "/" + oe.getId());
142                            out.write(oe.serialize().getBytes("UTF-8"));
143                            out.close();
144                    } catch (IOException ex) {
145                            ex.printStackTrace();
146                    }
147            }
148            
149            synchronized void register(OntologyElement element) {
150                    if (elements.contains(element)) {
151                            log("error: element already registered");
152                            throw new RuntimeException("Registration failed: Element is already registered.");
153                    }
154                    
155                    log("register: " + element);
156                    stateID++;
157                    
158                    if (element.getId() == -1) {
159                            element.setId(nextId());
160                    }
161                    elements.add(element);
162                    idIndex.put(element.getId(), element);
163                    if (element.getId() > idCount) idCount = element.getId();
164                    
165                    for (String word : element.getWords()) {
166                            if (word == null) continue;
167                            
168                            if (wordIndex.get(word) == null) {
169                                    wordIndex.put(word, element);
170                            } else if (wordIndex.get(word) != element) {
171                                    log("error: word already used");
172                                    throw new RuntimeException("Registration failed: The word '" + word + "' is already used.");
173                            }
174                    }
175                    
176                    if (element instanceof Individual) {
177                            updateDifferentIndividualsAxiom();
178                    }
179                    
180            }
181            
182            synchronized void removeFromWordIndex(OntologyElement oe) {
183                    for (String word : oe.getWords()) {
184                            if (word != null) {
185                                    wordIndex.remove(word);
186                            }
187                    }
188            }
189            
190            synchronized void addToWordIndex(OntologyElement oe) {
191                    for (String word : oe.getWords()) {
192                            if (word != null) {
193                                    if (wordIndex.get(word) == null) {
194                                            wordIndex.put(word, oe);
195                                    } else if (wordIndex.get(word) != oe) {
196                                            throw new RuntimeException("Word update failed: The word '" + word + "' is already used.");
197                                    }
198                            }
199                    }
200            }
201            
202            /**
203             * Removes the given ontology element from the ontology.
204             * 
205             * @param element The ontology element to be removed.
206             */
207            public synchronized void remove(OntologyElement element) {
208                    if (!elements.contains(element)) {
209                            log("error: unknown element");
210                            return;
211                    }
212                    
213                    log("remove: " + element.getWord());
214                    stateID++;
215                    
216                    for (String word : element.getWords()) {
217                            if (word == null) continue;
218                            wordIndex.remove(word);
219                    }
220                    elements.remove(element);
221                    idIndex.remove(element.getId());
222                    for (Sentence s : element.getSentences()) {
223                            retractSentence(s);
224                    }
225                    save(element);
226                    
227                    if (element instanceof Individual) {
228                            updateDifferentIndividualsAxiom();
229                    }
230                    
231            }
232            
233            /**
234             * Returns all the sentences that use the given word form (by word number) of the given
235             * ontology element.
236             * 
237             * @param element The ontology element.
238             * @param wordNumber The word number.
239             * @return A list of all sentence that contain the word.
240             */
241            public synchronized List<Sentence> getReferences(OntologyElement element, int wordNumber) {
242                    List<Sentence> list = new ArrayList<Sentence>();
243                    for (OntologyElement el : elements) {
244                            for (Sentence s : el.getSentences()) {
245                                    if ((wordNumber == -1 && s.contains(element)) || (wordNumber > -1 && s.contains(element, wordNumber))) {
246                                            list.add(s);
247                                    }
248                            }
249                    }
250                    return list;
251            }
252    
253            /**
254             * Returns all the sentences that use the given ontology element (no matter which word form
255             * is used).
256             * 
257             * @param element The ontology element.
258             * @return A list of all sentence that contain the ontology element.
259             */
260            public synchronized List<Sentence> getReferences(OntologyElement element) {
261                    return getReferences(element, -1);
262            }
263            
264            /**
265             * Returns the ontology element with the given name, or null if there is no such element.
266             * 
267             * @param name The name of the ontology element.
268             * @return The ontology element.
269             */
270            public OntologyElement get(String name) {
271                    return wordIndex.get(name);
272            }
273            
274            OntologyElement get(long id) {
275                    return idIndex.get(id);
276            }
277            
278            /**
279             * Returns all ontology elements.
280             * 
281             * @return A collection of all ontology elements.
282             */
283            public Collection<OntologyElement> getOntologyElements() {
284                    return new ArrayList<OntologyElement>(elements);
285            }
286            
287            /**
288             * Returns true if the given ontology element is contained by the ontology (identity check).
289             * 
290             * @param ontologyElement The ontology element.
291             * @return true if the ontology element is contained by the ontology.
292             */
293            public boolean contains(OntologyElement ontologyElement) {
294                    return elements.contains(ontologyElement);
295            }
296            
297            /**
298             * Returns the name of the ontology.
299             * 
300             * @return The name of the ontology.
301             */
302            public String getName() {
303                    return name;
304            }
305            
306            /**
307             * Returns the URI of the ontology (baseURI + name).
308             * 
309             * @return The URI of the ontology.
310             */
311            public String getURI() {
312                    return baseURI + name;
313            }
314            
315            /**
316             * Returns the complete ontology as an OWL/XML formatted string.
317             * 
318             * @param onlyConsistentSubset If true then only the consistent part of the ontology is included.
319             * @return A string that contains the complete ontology in OWL/XML format.
320             */
321            public synchronized String getOWLOntologyAsXML(boolean onlyConsistentSubset) {
322            StringWriter sw = new StringWriter();
323            try {
324                OWLXMLRenderer renderer = new OWLXMLRenderer(OWLManager.createOWLOntologyManager());
325                renderer.render(getOWLOntology(onlyConsistentSubset), sw);
326                sw.close();
327            } catch (Exception ex) {
328                ex.printStackTrace();
329            }
330                    return sw.toString();
331            }
332            
333            /**
334             * Returns an OWL ontology object that contains the complete ontology.
335             * 
336             * @param onlyConsistentSubset If true then only the consistent part of the ontology is included.
337             * @return An OWL ontology object containing the complete ontology.
338             */
339            public synchronized OWLOntology getOWLOntology(final boolean onlyConsistentSubset) {
340                    OWLOntologySetProvider setProvider = new OWLOntologySetProvider() {
341                            
342                            public Set<OWLOntology> getOntologies() {
343                                    HashSet<OWLOntology> ontologies = new HashSet<OWLOntology>();
344                                    for (OntologyElement el : elements) {
345                                            for (Sentence s : el.getSentences()) {
346                                                    if (s.isQuestion() || !s.isOWL()) continue;
347                                                    if (onlyConsistentSubset && (!s.isReasonerParticipant() || !s.isIntegrated()) ) continue;
348                                                    
349                                                    OWLOntology o = s.getOWLOntology();
350                                                    if (o != null) ontologies.add(o);
351                                            }
352                                    }
353                                    ontologies.add(differentIndividualsAxiom);
354                                    return ontologies;
355                            }
356                            
357                    };
358                    
359                    URI uri = null;
360                    try {
361                            uri = new URI("http://attempto.ifi.uzh.ch/default/");
362                            uri = new URI(getURI());
363                    } catch (URISyntaxException ex) {
364                            ex.printStackTrace();
365                    }
366                    
367                    OWLOntology owlOntology = null;
368                    try {
369                            OWLOntologyMerger ontologyMerger = new OWLOntologyMerger(setProvider);
370                            owlOntology = ontologyMerger.createMergedOntology(OWLManager.createOWLOntologyManager(), uri);
371                    } catch (Exception ex) {
372                            ex.printStackTrace();
373                    }
374                    return owlOntology;
375            }
376            
377            /**
378             * Returns the complete ontology as one ACE text.
379             * 
380             * @param onlyConsistentSubset If true then only the consistent part of the ontology is included.
381             * @return A string that contains the complete ontology as an ACE text.
382             */
383            public synchronized String getACEText(boolean onlyConsistentSubset) {
384                    String t = "";
385                    @SuppressWarnings (value="unchecked")
386                    ArrayList<OntologyElement> elementsCopy = (ArrayList<OntologyElement>) elements.clone();
387                    Collections.sort(elementsCopy);
388                    for (OntologyElement oe : elementsCopy) {
389                            String heading = "\n# " + oe.getHeadword() + "\n\n";
390                            for (Sentence s : oe.getSentences()) {
391                                    if (!onlyConsistentSubset || (s.isIntegrated() && s.isReasonerParticipant()) ) {
392                                            if (heading != null) {
393                                                    t += heading;
394                                                    heading = null;
395                                            }
396                                            t += s.getText() + "\n\n";
397                                    }
398                            }
399                    }
400                    return t;
401            }
402            
403            /**
404             * Returns the lexicon definition for all ontology elements in the ACE lexicon format.
405             * 
406             * @return A string that contains the lexicon definition.
407             */
408            public synchronized String getLexiconDef() {
409                    ArrayList<String> lexiconEntries = new ArrayList<String>();
410                    for (OntologyElement oe : elements) {
411                            for (LexiconEntry le : oe.getLexiconEntries()) {
412                                    if (!lexiconEntries.contains(le.toString())) {
413                                            lexiconEntries.add(le.toString());
414                                    }
415                            }
416                    }
417                    Collections.sort(lexiconEntries);
418                    String t = "";
419                    for (String s : lexiconEntries) {
420                            t += s + ".\n";
421                    }
422                    return t;
423            }
424            
425            private synchronized void refreshReasoner() {
426                    log("refresh reasoner");
427                    
428                    if (reasoner == null) {
429                            // for Pellet:
430                            reasoner = new Reasoner(OWLManager.createOWLOntologyManager());
431                            // for HermiT: (doesn't work for some reason...)
432                            //reasoner = (new ReasonerFactory()).createReasoner(OWLManager.createOWLOntologyManager());
433                    } else {
434                            clearOntologies();
435                    }
436                    updateDifferentIndividualsAxiom();
437                    
438                    log("reasoner: loading statements");
439                    HashSet<OWLOntology> ontologies = new HashSet<OWLOntology>();
440                    
441                    for (OntologyElement oe : elements) {
442                            for (Sentence s : oe.getSentences()) {
443                                    if (s.isReasonerParticipant() && s.isIntegrated()) {
444                                            OWLOntology o = s.getOWLOntology();
445                                            if (o != null) ontologies.add(o);
446                                    }
447                            }
448                    }
449                    try {
450                            reasoner.loadOntologies(ontologies);
451                    } catch (OWLReasonerException ex) {
452                            ex.printStackTrace();
453                    }
454                    log("reasoner: statements loaded");
455            }
456            
457            /**
458             * Refreshes the given ontology element. All sentences that use the ontology element are
459             * updated.
460             * 
461             * @param element The ontology element to be refreshed.
462             */
463            synchronized void refresh(OntologyElement element) {
464                    for (Sentence s : getReferences(element)) {
465                            if (s.isIntegrated()) {
466                                    retractSentence(s);
467                                    s.parse();
468                                    commitSentence(s);
469                            } else {
470                                    s.parse();
471                            }
472                    }
473                    save(element);
474            }
475            
476            /**
477             * Uses the ontology manager to read an OWL ontology from a string (that contains an ontology
478             * in OWL-XML format).
479             * 
480             * @param owlxml The serialized OWL-XML ontology.
481             * @return The OWL ontology object.
482             * @throws OWLOntologyCreationException If the string cannot be parsed.
483             */
484            public OWLOntology readOWLOntology(String owlxml) throws OWLOntologyCreationException {
485                    return OWLManager.createOWLOntologyManager().loadOntology(new StringInputSource(owlxml));
486            }
487            
488            /**
489             * Commits the sentence. This means that it is added to the reasoner. An integer value is returned
490             * that denotes the success or failure of the operation:
491             * 0 is returned if the operation succeeds.
492             * 1 is returned if it fails because the sentence introduces inconsistency into the knowledge base.
493             * 2 is returned if the reasoner runs out of memory (this can occur sometimes with large ontologies).
494             * 
495             * @param sentence The sentence to be commited.
496             * @return An integer value denoting the success/failure of the operation.
497             */
498            protected synchronized int commitSentence(Sentence sentence) {
499                    if (reasoner == null || sentence == null || sentence.isIntegrated()) return 0;
500                    
501                    if (!sentence.isReasonerParticipant()) {
502                            sentence.setIntegrated(true);
503                            return 0;
504                    }
505                    
506                    log("commit sentence");
507                    
508                    try {
509                            loadOntology(sentence.getOWLOntology());
510                    } catch (OutOfMemoryError err) {
511                            log("error: out of memory");
512                            System.gc();
513                            refreshReasoner();
514                            return 2;
515                    }
516    
517                    log("check for consistency");
518                    if (isConsistent()) {
519                            log("consistent!");
520                            sentence.setIntegrated(true);
521                            stateID++;
522                            return 0;
523                    } else {
524                            log("not consistent!");
525                            unloadOntology(sentence.getOWLOntology());
526                            return 1;
527                    }
528            }
529            
530            /**
531             * Retracts the sentence. This means that the sentence is removed from the reasoner.
532             * 
533             * @param sentence The sentence to be retracted.
534             */
535            protected synchronized void retractSentence(Sentence sentence) {
536                    if (
537                            reasoner == null ||
538                            sentence == null ||
539                            !sentence.isIntegrated() ||
540                            !sentence.isReasonerParticipant()
541                    ) return;
542                    
543                    log("retract sentence");
544                    stateID++;
545                    unloadOntology(sentence.getOWLOntology());
546                    sentence.setIntegrated(false);
547            }
548            
549            void log(String text) {
550                    Logger.log(name, "onto", 0, "onto", text);
551            }
552            
553            /**
554             * Updates the axiom that states that all named individuals are different. Thus, unique
555             * name assumption is applied.
556             */
557            private synchronized void updateDifferentIndividualsAxiom() {
558                    if (reasoner == null) return;
559                    
560                    if (differentIndividualsAxiom != null) {
561                            unloadOntology(differentIndividualsAxiom);
562                    }
563                    
564                    String owlString =
565                            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n" +
566                            "<Ontology " +
567                            "xml:base=\"http://www.w3.org/2006/12/owl11-xml#\" " +
568                            "xmlns=\"http://www.w3.org/2006/12/owl11-xml#\" " +
569                            "URI=\"" + getURI() + "/different_individuals/" + stateID + "\">\n" +
570                            "\t<DifferentIndividuals>\n";
571                    for (OntologyElement oe : getOntologyElements()) {
572                            if (oe instanceof Individual) {
573                                    String word = ((Individual) oe).getWord();
574                                    if (word.startsWith("the ")) word = word.substring(4);
575                                    owlString += "\t\t<Individual URI=\"" + ((Individual) oe).getURI() + "\" />\n";
576                            }
577                    }
578                    owlString +=
579                            "\t</DifferentIndividuals>\n" +
580                            "</Ontology>";
581                    
582                    try {
583                            differentIndividualsAxiom = readOWLOntology(owlString);
584                            loadOntology(differentIndividualsAxiom);
585                    } catch (OWLOntologyCreationException ex) {
586                            log("unexpected error");
587                            ex.printStackTrace();
588                    }
589            }
590            
591            /**
592             * Returns all concepts the given individual belongs to. The reasoner is used for this.
593             * 
594             * @param ind The individual.
595             * @return A list of all concepts of the individual.
596             * @see Individual#getConcepts()
597             */
598            public synchronized List<Concept> getConcepts(Individual ind) {
599                    OWLIndividual owlIndividual = (new OWLDataFactoryImpl()).getOWLIndividual(ind.getURI());
600                    ArrayList<Concept> concepts = new ArrayList<Concept>();
601                    try {
602                            Set<Set<OWLClass>> owlClasses = reasoner.getTypes(owlIndividual, false);
603                            for (Set<OWLClass> s : owlClasses) {
604                                    for (OWLClass oc : s) {
605                                            if (oc.isOWLThing() || oc.isOWLNothing()) continue;
606                                            String conceptURI = oc.getURI().toASCIIString();
607                                            String conceptName = conceptURI.substring(conceptURI.indexOf("#") + 1);
608                                            concepts.add((Concept) get(conceptName));
609                                    }
610                            }
611                    } catch (OWLReasonerException ex) {
612                            ex.printStackTrace();
613                    }
614                    return concepts;
615            }
616            
617            /**
618             * Returns all individuals that belong to the given concept. The reasoner is used for this.
619             * 
620             * @param concept The concept.
621             * @return A list of all individuals of the concept.
622             * @see Concept#getIndividuals()
623             */
624            public synchronized List<Individual> getIndividuals(Concept concept) {
625                    OWLClass owlClass = new OWLClassImpl(new OWLDataFactoryImpl(), concept.getURI());
626                    ArrayList<Individual> individuals = new ArrayList<Individual>();
627                    try {
628                            Set<OWLIndividual> owlIndividuals = reasoner.getIndividuals(owlClass, false);
629                            for (OWLIndividual oi : owlIndividuals) {
630                                    String indURI = oi.getURI().toASCIIString();
631                                    String indName = indURI.substring(indURI.indexOf("#") + 1);
632                                    if (!indName.matches("Ind[0-9]+")) {
633                                            individuals.add((Individual) get(indName));
634                                    }
635                            }
636                    } catch (OWLReasonerException ex) {
637                            ex.printStackTrace();
638                    }
639                    return individuals;
640            }
641            
642            /**
643             * Returns all super-concepts of the given concept. The reasoner is used for this.
644             * 
645             * @param concept The concept for which all super-concepts should be returned.
646             * @return A list of all super-concepts.
647             * @see Concept#getSuperConcepts()
648             */
649            public synchronized List<Concept> getSuperConcepts(Concept concept) {
650                    OWLClass owlClass = new OWLClassImpl(new OWLDataFactoryImpl(), concept.getURI());
651                    ArrayList<Concept> concepts = new ArrayList<Concept>();
652                    try {
653                            Set<Set<OWLClass>> owlClasses = reasoner.getAncestorClasses(owlClass);
654                            for (Set<OWLClass> s : owlClasses) {
655                                    for (OWLClass oc : s) {
656                                            if (oc.isOWLThing() || oc.isOWLNothing()) continue;
657                                            String conceptURI = oc.getURI().toASCIIString();
658                                            String conceptName = conceptURI.substring(conceptURI.indexOf("#") + 1);
659                                            concepts.add((Concept) get(conceptName));
660                                    }
661                            }
662                    } catch (OWLReasonerException ex) {
663                            ex.printStackTrace();
664                    }
665                    return concepts;
666            }
667            
668            /**
669             * Returns all the sub-concepts of the given concept. The reasoner is used for this.
670             * 
671             * @param concept The concept for which all sub-concepts should be returned.
672             * @return A list of all sub-concepts.
673             * @see Concept#getSubConcepts()
674             */
675            public synchronized List<Concept> getSubConcepts(Concept concept) {
676                    OWLClass owlClass = new OWLClassImpl(new OWLDataFactoryImpl(), concept.getURI());
677                    ArrayList<Concept> concepts = new ArrayList<Concept>();
678                    try {
679                            Set<Set<OWLClass>> owlClasses = reasoner.getDescendantClasses(owlClass);
680                            for (Set<OWLClass> s : owlClasses) {
681                                    for (OWLClass oc : s) {
682                                            if (oc.isOWLThing() || oc.isOWLNothing()) continue;
683                                            String conceptURI = oc.getURI().toASCIIString();
684                                            String conceptName = conceptURI.substring(conceptURI.indexOf("#") + 1);
685                                            concepts.add((Concept) get(conceptName));
686                                    }
687                            }
688                    } catch (OWLReasonerException ex) {
689                            ex.printStackTrace();
690                    }
691                    return concepts;
692            }
693            
694            /**
695             * Returns a list of ontology elements that answer the given question. The reasoner is used
696             * for this. In the case the sentence has the form "what is (Individual)?" then the answer
697             * contains all concepts the individual belongs to. Otherwise, the question is
698             * processed as a "DL Query" that describes a concept. In this case, the answer consists
699             * of all individuals that belong to the concept. The null value is returned if the
700             * sentence is not a question.
701             * 
702             * @param questionSentence The question sentence that should be answered.
703             * @return A list of ontology elements that are the answer for the question.
704             * @see Sentence#getAnswer()
705             */
706            public synchronized List<OntologyElement> getAnswer(Sentence questionSentence) {
707                    if (!questionSentence.isQuestion()) return null;
708    
709                    OWLOntology o = questionSentence.getOWLOntology();
710                    if (o == null || o.isEmpty()) return null;
711                    
712                    ArrayList<OntologyElement> answer = new ArrayList<OntologyElement>();
713                    
714                    try {
715                            OWLSubClassAxiom answerOWLAxiom = (OWLSubClassAxiom) o.getAxioms().iterator().next();
716                            OWLDescription answerOWLClass = answerOWLAxiom.getSubClass();
717                            
718                            if (answerOWLClass instanceof OWLObjectOneOf && ((OWLObjectOneOf) answerOWLClass).getIndividuals().size() == 1) {
719                                    OWLIndividual oi = ((OWLObjectOneOf) answerOWLClass).getIndividuals().iterator().next();
720                                    Set<Set<OWLClass>> owlClasses = reasoner.getTypes(oi, false);
721                                    for (Set<OWLClass> classSet : owlClasses) {
722                                            for (OWLClass owlClass : classSet) {
723                                                    String classURI = owlClass.getURI().toASCIIString();
724                                                    String className = classURI.substring(classURI.indexOf("#") + 1);
725                                                    if (!owlClass.isOWLThing() && !owlClass.isOWLNothing()) {
726                                                            answer.add(get(className));
727                                                    }
728                                            }
729                                    }
730                            } else {
731                                    Set<OWLIndividual> owlIndividuals = reasoner.getIndividuals(answerOWLClass, false);
732                                    for (OWLIndividual oi : owlIndividuals) {
733                                            String indURI = oi.getURI().toASCIIString();
734                                            String indName = indURI.substring(indURI.indexOf("#") + 1);
735                                            if (!indName.matches("Ind[0-9]+")) {
736                                                    answer.add(get(indName));
737                                            }
738                                    }
739                            }
740                    } catch (Exception ex) {
741                            ex.printStackTrace();
742                    }
743                    return answer;
744            }
745            
746            /**
747             * Returns true if the ontology is consistent. If nothing goes wrong, this should always return true.
748             * The reasoner is used for this.
749             * 
750             * @return true if the ontology is consistent.
751             */
752            public synchronized boolean isConsistent() {
753                    boolean isConsistent = true;
754                    try {
755                            isConsistent = reasoner.isSatisfiable((new OWLDataFactoryImpl()).getOWLThing());
756                    } catch (OWLReasonerException ex) {
757                            ex.printStackTrace();
758                    }
759                    return isConsistent;
760            }
761            
762            /**
763             * Checks if the given concept is satisfiable. The reasoner is used for this.
764             * 
765             * @param concept The concept.
766             * @return true if the concept is satisfiable.
767             */
768            public synchronized boolean isSatisfiable(Concept concept) {
769                    OWLClass owlClass = new OWLClassImpl(new OWLDataFactoryImpl(), concept.getURI());
770                    boolean isSatisfiable = false;
771                    try {
772                            isSatisfiable = (!reasoner.isDefined(owlClass) || reasoner.isSatisfiable(owlClass));
773                    } catch (OWLReasonerException ex) {
774                            ex.printStackTrace();
775                    }
776                    return isSatisfiable;
777            }
778            
779            private void loadOntology(OWLOntology ontology) {
780                    if (ontology == null) return;
781                    
782                    HashSet<OWLOntology> ontologies = new HashSet<OWLOntology>();
783                    ontologies.add(ontology);
784                    try {
785                            reasoner.loadOntologies(ontologies);
786                    } catch (OWLReasonerException ex) {
787                            ex.printStackTrace();
788                    }
789            }
790            
791            private void unloadOntology(OWLOntology ontology) {
792                    if (ontology == null) return;
793                    
794                    HashSet<OWLOntology> ontologies = new HashSet<OWLOntology>();
795                    ontologies.add(ontology);
796                    try {
797                            reasoner.unloadOntologies(ontologies);
798                    } catch (OWLReasonerException ex) {
799                            ex.printStackTrace();
800                    }
801            }
802            
803            private void clearOntologies() {
804                    try {
805                            reasoner.clearOntologies();
806                    } catch (OWLReasonerException ex) {
807                            ex.printStackTrace();
808                    }
809            }
810            
811            private long nextId() {
812                    return ++idCount;
813            }
814            
815            /**
816             * Returns the state id of the ontology. This id increases each time the ontology changes (more precisely:
817             * each time the part of the ontology that participates in reasoning changes). This id is used to find out
818             * whether cached information is still valid or has to be recalculated.
819             * 
820             * @return The state id of the ontology.
821             */
822            long getStateID() {
823                    return stateID;
824            }
825            
826    }