/* * This software is Copyright 2005,2006,2007,2008 Langdale Consultants. * Langdale Consultants can be contacted at: http://www.langdale.com.au */ package au.com.langdale.profiles; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Set; import au.com.langdale.profiles.ProfileClass.PropertyInfo; import au.com.langdale.util.MultiMap; import au.com.langdale.kena.OntResource; import com.hp.hpl.jena.vocabulary.OWL; /** * Utilities used in analysing and manipulating profile models. */ public class ProfileUtility { /** * A multi-map from base classes to profile classes. */ public static class BaseMap extends MultiMap { public void add(OntResource base, OntResource clss) { putRaw(base, clss); } } /** * A two-way mapping between base classes and profile classes. */ public static class ProfileMap extends BaseMap { private Map profiles = new HashMap(); // profile to base class @Override public void add(OntResource base, OntResource clss) { // record the back trace super.add(base, clss); // record the profile to base mapping profiles.put(clss, base); } public void removeProfile(OntResource clss) { OntResource base = getBase(clss); profiles.remove(clss); super.remove(base, clss); } public OntResource getBase(OntResource clss) { return (OntResource) profiles.get(clss); } /** * Select the preferred profile from a collection of profiles. * The preferred profile has the same local name as the base class. * * @param profiles: the candidate profile classes * @return the preferred profile if any, * otherwise a random profile or <code>null</code> if there are none */ public OntResource chooseBestProfile(Collection profiles) { Iterator it = profiles.iterator(); OntResource any = null; while (it.hasNext()) { OntResource cand = (OntResource) it.next(); if(cand.getLocalName().equals(getBase(cand).getLocalName())) return cand; any = cand; } return any; } /** * Find the preferred profile for a base class. * * @param base: the base class * @return the preferred profile */ public OntResource chooseBestProfile(OntResource base) { return chooseBestProfile(find(base)); } /** * Construct a set of profile classes related to an information class. * * The result is a set of profile classes. If subclass is true, these will be * profiles of subclasses of the base class, otherwise profiles of superclasses. * * Only the direct sub or super classes will be included. * * If unique is true there will be only be one profile class in the result for each * information class. * */ public Set findRelatedProfiles(OntResource base, boolean subclass, boolean unique) { HashSet result = new HashSet(); // consider relatives of the base OntResource subject = base; Iterator it = subclass? subject.listSubClasses(false): subject.listSuperClasses(false); while (it.hasNext()) { OntResource related = (OntResource) it.next(); if( related.isClass() && (subclass || !related.equals(base))) { // consider the profiles of each relative Collection cands = find(related); if( ! cands.isEmpty()) { if(unique) { // add 'best' profile for this base addBestClass(result, chooseBestProfile(cands), subclass); } else { // add all candidate profiles Iterator jt = cands.iterator(); while (jt.hasNext()) addBestClass(result, (OntResource) jt.next(), subclass); } } } } return result; } } /** * A specification of a property profile. */ public static class PropertyGroup { private final OntResource prop; private PropertySpec summary; private Collection restrictions = new LinkedList(); public PropertyGroup(PropertySpec spec) { summary = spec; prop = spec.prop; restrictions.add(spec); } public OntResource getProperty() { return prop; } public PropertySpec getSummary() { return summary; } public Collection getRestrictions() { return restrictions; } public void add(PropertySpec other) { mergeSummary(other); addRestriction(other); } private void mergeSummary(PropertySpec other) { if( summary.base_domain.equals(other.base_domain)) { summary = new PropertySpec(summary, other); } else { PropertySpec dominant = summary.selectDominant(other); if( dominant != null) summary = dominant; else summary = new PropertySpec(prop, null, null); } } private void addRestriction(PropertySpec other) { for (Iterator jt = restrictions.iterator(); jt.hasNext();) { PropertySpec extant = (PropertySpec) jt.next(); if( other.base_domain.equals(extant.base_domain) ) { other = new PropertySpec(extant, other); jt.remove(); break; } } restrictions.add(other); } } /** * A specification of a property profile. */ public static class PropertySpec { public final OntResource prop; public final boolean required, functional, reference; public final OntResource base_range, base_domain; // FIXME: base_range should be OntResource public final String label, comment; public PropertySpec(PropertyInfo info, ProfileClass range_profile) { prop = info.getProperty(); required = info.isRequired(); functional = info.isFunctional(); reference = range_profile != null && range_profile.isReference(); // repair domain and range base_domain = selectType(prop.getDomain(), info.getDomainProfile().getBaseClass()); base_range = selectType(prop.getRange(), range_profile != null? range_profile.getBaseClass(): null); OntResource range = info.getRange(); String l = range != null? range.getLabel(null): null; if( l != null ) label = l; else label = prop.getLabel(null); comment = extractComment(range); } public PropertySpec(OntResource prop, OntResource domain, OntResource range) { this.prop = prop; required = reference = false; functional = prop.isFunctionalProperty() || prop.isDatatypeProperty(); base_domain = selectType(prop.getDomain(), domain); base_range = selectType(prop.getRange(), range); label = prop.getLabel(null); comment = ""; } /** * Merge two property specifications */ private PropertySpec(PropertySpec lhs, PropertySpec rhs) { prop = lhs.prop; // == rhs.prop base_domain = lhs.base_domain; // == rhs.base_domain // take the greater restriction functional = lhs.functional || rhs.functional; reference = lhs.reference || rhs.reference; required = lhs.required || rhs.required; base_range = mergeRange(prop.getRange(), lhs.base_range, rhs.base_range); // take the profile label if both sides agree if( lhs.label.equals(rhs.label)) label = lhs.label; else label = prop.getLabel(null); comment = ""; } private PropertySpec selectDominant(PropertySpec other) { // choose the narrowest class if( base_domain != null && other.base_domain != null) { if( base_domain.hasSuperClass(other.base_domain)) return other; else if( other.base_domain.hasSuperClass(base_domain)) return this; } return null; } public void create(ProfileClass profile) { profile.createAllValuesFrom(prop, required); profile.setReference(reference);// FIXME: is this right? PropertyInfo info = profile.getPropertyInfo(prop); if(functional) info.setMaxCardinality(1); if( label != null ) info.getRange().setLabel(label, null); if( comment != null ) info.getRange().setComment(comment, null); OntResource native_range = prop.getRange(); if( base_range != null && (native_range == null || ! base_range.equals(native_range))) info.getRange().addSuperClass(base_range); } } /** * A mapping of property to <code>ProertySpec</code>. As property profiles * are added in the form of <code>PropertyInfo</code> objects, a * <code>PropertySpec</code> is constructed for each property that is * a superset of all the properties profiles. * */ public static class PropertyAccumulator extends MultiMap { private Map props = new HashMap(); // property to property collection public void add(PropertySpec spec) { PropertyGroup extant = (PropertyGroup)props.get(spec.prop); if( extant != null ) extant.add(spec); else props.put(spec.prop, new PropertyGroup(spec)); } public void add(OntResource prop, OntResource domain, OntResource range ) { add(new PropertySpec(prop, domain, range)); } public ProfileClass add(PropertyInfo info) { ProfileClass range_profile = null; if( ! info.getProperty().isDatatypeProperty()) range_profile = info.createProfileClass(); PropertySpec spec = new PropertySpec(info, range_profile); add(spec); return range_profile; } public PropertySpec get(OntResource prop) { return (PropertySpec) props.get(prop); } public boolean containsKey(OntResource prop) { return props.containsKey(prop); } public Collection getGroups() { return props.values(); } } public static class EnumAccumulator { private Map enums = new HashMap(); public void add(OntResource base, Iterator insts) { Set extent = creatExtent(base); while (insts.hasNext()) { extent.add((OntResource) insts.next()); } } public void add(OntResource base, OntResource inst) { Set extent = creatExtent(base); extent.add(inst); } private Set creatExtent(OntResource base) { Set extent = (Set) enums.get(base); if( extent == null) { extent = new HashSet(); enums.put(base, extent); } return extent; } public Collection get(OntResource base) { Set extent = (Set) enums.get(base); if( extent == null) return Collections.EMPTY_SET; else return extent; } } private static OntResource selectType(OntResource prop_type, OntResource profile_type) { if( prop_type != null && prop_type.hasRDFType(OWL.Class)) { if (profile_type != null && ( profile_type.hasSuperClass(prop_type) || profile_type.hasSubClass(prop_type))) return profile_type; else return prop_type; } else return profile_type; } private static OntResource mergeRange(OntResource type, OntResource lhs, OntResource rhs) { // choose the narrowest class if( lhs != null && rhs != null) { if( lhs.equals(rhs)) return lhs; if( lhs.hasSuperClass(rhs)) return lhs; else if( rhs.hasSuperClass(lhs)) return rhs; } // choose the base class or null for a datatype if( type != null && type.hasRDFType(OWL.Class)) return type; else return null; } /** * Add class to set of classes if it is not less (not greater) than any member. * Remove any members less (greater) than the class. */ public static void addBestClass(Set result, OntResource clss, boolean greater) { if(! result.contains(clss)) { // compare candidate with each extant result Iterator kt = result.iterator(); while (kt.hasNext()) { OntResource extant = (OntResource) kt.next(); if( greater ) { // if candidate greater extant then remove extant if( clss.hasSubClass(extant)) { kt.remove(); } // if candidate less then ignore it else if( clss.hasSuperClass(extant)) { return; } } // super instead of subclass else { if( clss.hasSuperClass(extant)) { kt.remove(); } else if( clss.hasSubClass(extant)) { return; } } } result.add(clss); } } public static String appendComment(String comment, OntResource subject) { if( subject == null) return comment; return appendComment(comment, subject.getComment(null)); } public static String appendComment(String comment, String addendum) { if( addendum == null || addendum.length() == 0) return comment; if( comment == null || comment.length() == 0) return addendum; return comment + "\n" + addendum; } public static String extractComment(OntResource subject) { return appendComment(null, subject); } }