001    package nl.tudelft.tbm.eeni.owlstructure.processor;
002    
003    import com.hp.hpl.jena.ontology.*;
004    import com.hp.hpl.jena.rdf.model.Resource;
005    import nl.tudelft.tbm.eeni.owlstructure.utils.CollectionUtils;
006    import org.apache.commons.logging.Log;
007    import org.apache.commons.logging.LogFactory;
008    
009    import java.util.Collection;
010    import java.util.HashSet;
011    import java.util.Iterator;
012    
013    /**
014     * Simplify complex intersectionOf / unionOf property ranges (as created by a.o. Protege)
015     * to a simple flat list of named classes, and deletes the anonymous classes from the ontology.
016     * <p/>
017     * This is needed because Owl2Java chokes on complex property ranges;
018     * therefore maybe this should be integrated in Owl2Java?
019     */
020    public class PropertyRangeSimplifier implements IOntologyProcessor {
021    
022        static Log log = LogFactory.getLog(FunctionalPropertyInferer.class);
023    
024        /**
025         * Creates a new property range simplifier
026         *
027         * @see PropertyRangeSimplifier
028         */
029        public PropertyRangeSimplifier() {
030        }
031    
032        /**
033         * Run the propery range inferer on all classes in the given ontology
034         *
035         * @param ontModel The ontology model to work on
036         */
037        @Override
038        public OntModel process(OntModel ontModel) {
039            // Loop over all properties
040            Collection<OntProperty> properties = ontModel.listAllOntProperties().toList();
041            for (OntProperty property : properties) {
042                // Find existing ranges
043                Collection<Resource> oldRanges = new HashSet<Resource>(property.listRange().toList());
044    
045                // Simplify the range list to a flat list of named classes
046                Collection<Resource> newRanges = simplifyRange(ontModel, oldRanges);
047    
048                // Remove existing ranges from property (retained ranges will be included in newRanges and thus re-added)
049                for (Resource range : oldRanges) {
050                    property.removeRange(range);
051                }
052                // Add new ranges to property
053                for (Resource range : newRanges) {
054                    property.addRange(range);
055                }
056    
057                // Debug output
058                log.info("Property range simplification for property: " + property.getLocalName() + "\n"
059                        + getLogMessage("retaining range(s)", CollectionUtils.intersectCollections(oldRanges, newRanges)) + "\n"
060                        + getLogMessage("adding range(s)", CollectionUtils.subtractCollections(newRanges, oldRanges)) + "\n"
061                        + getLogMessage("removing range(s)", CollectionUtils.subtractCollections(oldRanges, newRanges)));
062    
063            }
064    
065            return ontModel;
066        }
067    
068        /**
069         * Given a certain property, list what datatypes (boolean, double, string, etc)
070         * or classes are used for the values that instances refer to using this property.
071         */
072        private Collection<Resource> simplifyRange(OntModel ontModel, Collection<Resource> oldRanges) {
073            HashSet<Resource> newRanges = new HashSet<Resource>();
074    
075            /*
076               * If keepExistingRanges is enabled, start with current ranges already assigned in the property range
077               */
078            // Loop over all existing ranges
079            Iterator<Resource> rangeIterator = oldRanges.iterator();
080            while (rangeIterator.hasNext()) {
081                Resource range = rangeIterator.next();
082                // See whether it is a class or not
083                if (range.canAs(OntClass.class)) {
084                    // It is a class, cast it
085                    OntClass rangeClass = range.as(OntClass.class);
086    
087                    // Check whether it is an anonymous class
088                    if (rangeClass.isAnon()) {
089                        // Handle intersection class
090                        if (rangeClass.isIntersectionClass()) {
091                            // Cast to IntersectionClass
092                            IntersectionClass intersectionClass = rangeClass.asIntersectionClass();
093                            // Add operands to range
094                            newRanges.addAll(simplifyRange(ontModel, new HashSet<Resource>(intersectionClass.listOperands().toSet())));
095                            // Delete anonymous IntersectionClass from the ontology
096                            intersectionClass.remove();
097                            continue;
098                        }
099                        // Handle union class
100                        if (rangeClass.isUnionClass()) {
101                            // Cast to UnionClass
102                            UnionClass unionClass = rangeClass.asUnionClass();
103                            // Add operands to range
104                            newRanges.addAll(simplifyRange(ontModel, new HashSet<Resource>(unionClass.listOperands().toSet())));
105                            // Delete anonymous UnionClass from the ontology
106                            unionClass.remove();
107                            continue;
108                        }
109                    }
110                }
111    
112                // It wasn't an anonymous boolean class, so just keep it
113                newRanges.add(range);
114            }
115    
116            return newRanges;
117        }
118    
119        /**
120         * Format a debug message containing a message and a list of resource localNames
121         */
122        private String getLogMessage(String message, Collection<? extends Resource> resources) {
123            String result = "  - " + message + ": ";
124            if (resources.size() > 0) {
125                int counter = 0;
126                for (Resource resource : resources) {
127                    result += (counter++ > 0 ? ", " : "") + resource.getLocalName();
128                }
129            } else {
130                result += "none";
131            }
132            return result;
133        }
134    
135    }