/******************************************************************************* * Copyright 2014 Miami-Dade County * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.sharegov.cirm.utils; import org.coode.owlapi.manchesterowlsyntax.ManchesterOWLSyntaxEditorParser; import org.semanticweb.owlapi.expression.OWLEntityChecker; import org.semanticweb.owlapi.expression.ParserException; import org.semanticweb.owlapi.expression.ShortFormEntityChecker; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLAnnotationProperty; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLClassExpression; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLIndividual; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLDatatype; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLObjectProperty; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyManager; import org.semanticweb.owlapi.model.PrefixManager; import org.semanticweb.owlapi.util.BidirectionalShortFormProvider; import org.semanticweb.owlapi.util.BidirectionalShortFormProviderAdapter; import org.semanticweb.owlapi.util.ShortFormProvider; import org.semanticweb.owlapi.util.SimpleShortFormProvider; import org.sharegov.cirm.OWL; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * <p> * Parse a DL query with Manchester syntax. Adapted from the first author below. * Be sure to call the {@link #dispose()} method after you're done with the parser * to avoid a memory leak due to a change listener being registered with the * OWLOntologyManager. * </p> * * This class is thread safe. If changes to the ontology occur, the ontology should be managed by a synchronized manager, for thread safe change propagation. * The data factory used by this class and acquired through the manager should be synchronized also. * * Author: Matthew Horridge<br> * The University of Manchester<br> * Bio-Health Informatics Group<br>, * Borislav Iordanov, Thomas Hilpold Miami-Dade County * Date: 13-May-2010 */ public class DLQueryParser { /** * Creates and caches a or returns a cached DLQueryParser. * This method is thread safe. * @param rootOntology an ontology (with a synchronized manager, if changes to the ontology occur.) * @return */ public static synchronized DLQueryParser getParser(OWLOntology rootOntology) { return getParser(rootOntology, DEFAULT_SHORTFORM_PROVIDER); } /** * Creates and caches a or returns a cached DLQueryParser. * This method is thread safe. * @param rootOntology an ontology (with a synchronized manager, if changes to the ontology occur). * @param shortFormProvider callers should make sure to pass in the same provider for the same ontologies or providers that overwrite equals. * @return */ public static synchronized DLQueryParser getParser(OWLOntology rootOntology, ShortFormProvider shortFormProvider) { IRI ontoIRI = rootOntology.getOntologyID().getOntologyIRI(); DLQueryParser result = cache.get(ontoIRI); if (result == null || !result.getShortFormProviderOnCreation().equals(shortFormProvider)) { result = new DLQueryParser(rootOntology, shortFormProvider); cache.put(ontoIRI, result); } return result; } /** * Clears the cache and ensures change listeners are unregisterd from the ontologie's manager to allow garbage collection of the cached parsers. */ public static synchronized void disposeCachedParsers() { for (DLQueryParser cur : cache.values()) { //unregister listener at manager to allow GCign cur.dispose(); } cache.clear(); } public static synchronized void disposeCachedParser(OWLOntology rootOntology) { DLQueryParser cur = getParser(rootOntology); //unregister listener at manager to allow GCign cur.dispose(); cache.remove(rootOntology); } //SimpleShortFormProvider determined stateless and therefore thread safe in OWLAPI 3.2.4 private static final SimpleShortFormProvider DEFAULT_SHORTFORM_PROVIDER = new SimpleShortFormProvider(); private static volatile ConcurrentHashMap<IRI, DLQueryParser> cache = new ConcurrentHashMap<IRI, DLQueryParser>(); private OWLOntology rootOntology; private BidirectionalShortFormProvider bidiShortFormProvider; private ShortFormProvider shortFormProviderOnCreation; private String[] swapDefaultPrefixNameSpecification; private DLQueryParser(OWLOntology rootOntology) { this(rootOntology, new SimpleShortFormProvider()); } /** * Constructs a DLQueryParser using the specified ontology and short form * provider to map entity IRIs to short names. * * @param rootOntology * The root ontology. This essentially provides the domain * vocabulary for the query. * @param shortFormProvider * A short form provider to be used for mapping back and forth * between entities and their short names (renderings). */ private DLQueryParser(OWLOntology rootOntology, ShortFormProvider shortFormProvider) { shortFormProviderOnCreation = shortFormProvider; this.rootOntology = rootOntology; OWLOntologyManager manager = rootOntology.getOWLOntologyManager(); Set<OWLOntology> importsClosure = rootOntology.getImportsClosure(); // Create a bidirectional short form provider to do the actual mapping. // It will generate names using the input // short form provider. synchronized(manager) { BidirectionalShortFormProviderAdapter bsfa = new BidirectionalShortFormProviderAdapter( manager, importsClosure, shortFormProvider); bsfa.add(OWL.dataFactory().getOWLNothing()); bsfa.add(OWL.dataFactory().getOWLThing()); bidiShortFormProvider = bsfa; swapDefaultPrefixNameSpecification = getPrefixNameToSwapForDoubleDefault(shortFormProviderOnCreation); } } private ShortFormProvider getShortFormProviderOnCreation() { return shortFormProviderOnCreation; } /** * Parses a class expression string to obtain a class expression. * This method is thread safe. * @param classExpressionString * The class expression string * @return The corresponding class expression * @throws ParserException * if the class expression string is malformed or contains * unknown entity names. */ public OWLClassExpression parseClassExpression(String classExpressionString) throws ParserException { OWLOntologyManager manager = rootOntology.getOWLOntologyManager(); OWLDataFactory dataFactory = manager.getOWLDataFactory(); // Set up the real parser ManchesterOWLSyntaxEditorParser parser = new ManchesterOWLSyntaxEditorParser( dataFactory, classExpressionString); parser.setDefaultOntology(rootOntology); // Specify an entity checker that wil be used to check a class // expression contains the correct names. final OWLEntityChecker ec = new ShortFormEntityChecker(bidiShortFormProvider); OWLEntityChecker entityChecker = new DoubleDefaultPrefixEntityCheckerAdapter(ec); parser.setOWLEntityChecker(entityChecker); // parser.setOWLEntityChecker(ec); // Do the actual parsing synchronized(manager) { // Sync necessary, because manager might fire changes to the CacheBidiShortFormProviderAdapter // used by the parser in another thread. return parser.parseClassExpression(); } } /** * In case one iris can be mapped to a default as well as a custom prefix, * determines which prefix the bidiShortFormProvider (accidentally) uses and returns * which prefix therefore needs to be replaced for which other. * e.g. bidi uses : for (default and mdc:) -> [0]=mdc:, [1]=: * bidi uses mdc: for (default and mdc:) -> [0]=:, [1]=mdc: * @return a String[2] [0]=fromPrefix [1]=toPrefix */ private String[] getPrefixNameToSwapForDoubleDefault(ShortFormProvider shortFormProvider) { String[] result = new String[] { ":", ":" }; if (shortFormProvider instanceof PrefixManager) { PrefixManager pfm = (PrefixManager)shortFormProviderOnCreation; if (pfm.containsPrefixMapping(":")) { String defaultPrefix = pfm.getDefaultPrefix(); //e.g. http://www.miamidade.gov/ontology# String alternativeDefaultPrefixName = null; for (Map.Entry<String, String> nameToPrefix : pfm.getPrefixName2PrefixMap().entrySet()) { if (!nameToPrefix.getKey().equals(":") && nameToPrefix.getValue().equals(defaultPrefix)) { //another prefix name (e.g. mdc:) that maps to the same prefix as default was found. alternativeDefaultPrefixName = nameToPrefix.getKey(); } } if (alternativeDefaultPrefixName != null) { OWLNamedIndividual testShortFormIndividual = OWL.individual(defaultPrefix + "TEST"); String testShortForm = shortFormProviderOnCreation.getShortForm(testShortFormIndividual); if (testShortForm.indexOf(':') > 0) { //after first char //using alterNativeDefaultPrefixName for Bidicache result = new String[] { ":", alternativeDefaultPrefixName }; } else { //using default prefix for Bidicache result = new String[] { alternativeDefaultPrefixName, ":" }; } } } } return result; } /** * This method is thread safe. * @param individual * @return * @throws ParserException */ public OWLIndividual parseIndExpression(String individual) throws ParserException { OWLOntologyManager manager = rootOntology.getOWLOntologyManager(); OWLDataFactory dataFactory = manager.getOWLDataFactory(); // Set up the real parser ManchesterOWLSyntaxEditorParser parser = new ManchesterOWLSyntaxEditorParser( dataFactory, individual); parser.setDefaultOntology(rootOntology); // Specify an entity checker that wil be used to check a class // expression contains the correct names. OWLEntityChecker delegateEc = new ShortFormEntityChecker( bidiShortFormProvider); OWLEntityChecker entityChecker = new DoubleDefaultPrefixEntityCheckerAdapter(delegateEc); parser.setOWLEntityChecker(entityChecker); // Do the actual parsing synchronized(manager) { // Sync necessary, because manager might fire changes to the CacheBidiShortFormProviderAdapter // used by the parser in another thread. return parser.parseIndividual(); } } /* * This method is thread safe. */ public void dispose() { OWLOntologyManager manager = rootOntology.getOWLOntologyManager(); synchronized(manager) { // Sync necessary, because manager might fire changes to the CacheBidiShortFormProviderAdapter // during disposal in another thread. bidiShortFormProvider.dispose(); } } private class DoubleDefaultPrefixEntityCheckerAdapter implements OWLEntityChecker { OWLEntityChecker ec; public DoubleDefaultPrefixEntityCheckerAdapter(OWLEntityChecker ec) { this.ec = ec; } public OWLAnnotationProperty getOWLAnnotationProperty(String name) { return ec.getOWLAnnotationProperty(name); } public OWLClass getOWLClass(String name) { if (name.equals("Nothing")) return OWL.dataFactory().getOWLNothing(); return ec.getOWLClass(fixDefaultPrefixNameFor(name)); } @Override public OWLDataProperty getOWLDataProperty(String name) { return ec.getOWLDataProperty(fixDefaultPrefixNameFor(name)); } @Override public OWLDatatype getOWLDatatype(String name) { return ec.getOWLDatatype(name); } @Override public OWLNamedIndividual getOWLIndividual(String name) { return ec.getOWLIndividual(fixDefaultPrefixNameFor(name)); } @Override public OWLObjectProperty getOWLObjectProperty(String name) { return ec.getOWLObjectProperty(fixDefaultPrefixNameFor(name)); } /** * If there are two equal prefixes and one is mapped to the defaultPrefixName ":", * all names have to be checked to use the same prefixName as bidicache does (either ":" or "mdc:"). * @param name * @return */ public String fixDefaultPrefixNameFor(final String name) { String result = name; int colonIndex = name.indexOf(":"); if (colonIndex <= 0) { //() and or State //not found or first char boolean isColonFirstChar = colonIndex == 0; if (swapDefaultPrefixNameSpecification[0].equals(":")) { result = swapDefaultPrefixNameSpecification[1] + (isColonFirstChar? name.substring(1) : name); } else { if (!isColonFirstChar) { result = ":" + name; } } } else { String prefixName = name.substring(0, colonIndex + 1); if (swapDefaultPrefixNameSpecification[0].equals(prefixName)) { result = swapDefaultPrefixNameSpecification[1] + name.substring(colonIndex + 1); } } return result; } } }