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