/*******************************************************************************
* Copyright (c) 2009, 2010 Fraunhofer IWU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.komma.em.concepts;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.enilink.composition.properties.PropertySet;
import net.enilink.composition.properties.PropertySetFactory;
import net.enilink.composition.properties.traits.PropertySetOwner;
import net.enilink.composition.properties.util.UnmodifiablePropertySet;
import net.enilink.composition.traits.Behaviour;
import com.google.inject.Inject;
import com.google.inject.Injector;
import net.enilink.commons.iterator.ConvertingIterator;
import net.enilink.commons.iterator.IExtendedIterator;
import net.enilink.commons.iterator.UniqueExtendedIterator;
import net.enilink.commons.util.Pair;
import net.enilink.vocab.owl.FunctionalProperty;
import net.enilink.komma.em.internal.behaviours.OrderedPropertySet;
import net.enilink.komma.em.results.ResultDescriptor;
import net.enilink.komma.em.util.KommaUtil;
import net.enilink.komma.core.IBindings;
import net.enilink.komma.core.IEntity;
import net.enilink.komma.core.IQuery;
import net.enilink.komma.core.IReference;
import net.enilink.komma.core.IResultDescriptor;
import net.enilink.komma.core.IStatement;
import net.enilink.komma.core.IValue;
import net.enilink.komma.core.Statement;
public abstract class ResourceSupport extends BehaviorBase implements
IResource, Behaviour<IResource> {
@Inject
protected PropertySetFactory propertySetFactory;
class PropertyInfo {
private IReference property;
private PropertySet<Object> propertySet;
private Boolean single;
PropertyInfo(IReference property) {
this.property = getEntityManager().find(property);
}
PropertySet<Object> getPropertySet() {
if (propertySet != null) {
return propertySet;
}
if (property instanceof IProperty
&& ((IProperty) property).isOrderedContainment()) {
propertySet = new OrderedPropertySet<Object>(
getBehaviourDelegate(), property);
injector.injectMembers(propertySet);
} else {
Object self = getBehaviourDelegate();
if (self instanceof PropertySetOwner) {
propertySet = ((PropertySetOwner) self)
.getPropertySet(property.getURI().toString());
}
if (propertySet == null) {
propertySet = propertySetFactory.createPropertySet(self,
property.getURI().toString(), null);
}
if (propertySet instanceof UnmodifiablePropertySet<?>) {
// return a modifiable delegate
propertySet = ((UnmodifiablePropertySet<Object>) propertySet)
.getDelegate();
}
}
return propertySet;
}
boolean isSingle() {
if (single == null) {
// the following code checks if property is functional
// the property is functional if it is an FunctionalProperty or
// its maximum cardinality is 1
single = this.property instanceof FunctionalProperty;
if (!single) {
Pair<Integer, Integer> cardinality = getApplicableCardinality(property);
single = cardinality.getSecond() <= 1;
}
}
return single;
}
}
private static final String HAS_APPLICABLE_PROPERTY = PREFIX //
+ "ASK { " //
+ "{?property a owl:AnnotationProperty "
+ "OPTIONAL {?property rdfs:domain ?domain . "
+ " OPTIONAL {?resurce a ?class . ?class rdfs:subClassOf ?domain}} FILTER (!bound(?domain) || bound(?class))} UNION "
+ "{?property rdfs:subPropertyOf rdf:type} UNION "
+ "{?resource a ?class ." // given resource has type class
+ "{{?property rdfs:domain ?class} UNION" //
+ "{?class rdfs:subClassOf ?restriction . ?restriction owl:onProperty ?property}}}" //
+ "}";
private static final String SELECT_APPLICABLE_CARDINALITY = PREFIX //
+ "SELECT DISTINCT ?min ?max " //
+ "WHERE { " //
+ "?resource a ?class ." // given resource has type class
+ "?class rdfs:subClassOf ?restriction ." // class has a local
// restriction
+ "?restriction owl:onProperty ?property ." // for given property
+ "{"
+ "OPTIONAL {?restriction owl:minCardinality ?min }"
+ "OPTIONAL {?restriction owl:maxCardinality ?max }"
+ "OPTIONAL {"
+ "?restriction owl:cardinality ?min ."
+ "?restriction owl:cardinality ?max ."
+ "}"
// FIXME
// preliminary support for qualified cardinality restrictions
+ "} UNION {"
+ "OPTIONAL {?restriction owl:minQualifiedCardinality ?min }"
+ "OPTIONAL {?restriction owl:maxQualifiedCardinality ?max }"
+ "OPTIONAL {"
+ "?restriction owl:qualifiedCardinality ?min ."
+ "?restriction owl:qualifiedCardinality ?max ." + //
"}}} ORDER BY DESC(?min) ?max";
private static final String SELECT_APPLICABLE_CHILD_PROPERTIES = PREFIX //
+ "SELECT DISTINCT ?property " //
+ "WHERE { " //
// select potential child properties
+ "{" //
+ "?resource a ?class ." //
+ "?class rdfs:subClassOf+ [owl:onProperty ?property] ." //
+ "{ ?property rdfs:subPropertyOf+ komma:child } UNION { ?property rdfs:subPropertyOf+ komma:contains } ." //
+ "FILTER NOT EXISTS {" //
+ " ?otherProperty rdfs:subPropertyOf ?property ." //
+ " ?class rdfs:subClassOf [owl:onProperty ?otherProperty]" //
+ " FILTER (?property != ?otherProperty)" //
+ "} " //
// select already used child properties
+ "} UNION {" //
+ " ?resource ?property ?someObject ." //
+ " { ?property rdfs:subPropertyOf+ komma:child } UNION { ?property rdfs:subPropertyOf+ komma:contains } ." //
+ " FILTER NOT EXISTS {" //
+ " ?otherProperty rdfs:subPropertyOf ?property ." //
+ " ?resource ?otherProperty ?someObject ." //
+ " FILTER (?property != ?otherProperty)" //
+ " }" //
+ "}" //
+ "} ORDER BY ?property";
private static final String SELECT_APPLICABLE_PROPERTIES = PREFIX //
+ "SELECT DISTINCT ?property " //
+ "WHERE { " //
+ "{ ?property a owl:AnnotationProperty FILTER NOT EXISTS { ?property rdfs:domain ?domain }} UNION "
+ "{ ?property rdfs:subPropertyOf rdf:type } UNION "
+ "{ ?resource a ?class ." // given resource has type class
+ "{{ ?property rdfs:domain ?class } UNION" //
+ "{ ?class rdfs:subClassOf ?restriction . ?restriction owl:onProperty ?property }}" //
// exclude properties that can not be applied to
// the actual types of the subject
// + "OPTIONAL {" //
// + " ?property rdfs:domain ?someDomain ." //
// + " OPTIONAL {"
// + " ?subject a ?someDomain ."
// + " ?subject a ?matchDummy ."
// + " }"
// + " FILTER (! bound(?matchDummy))"
// + "}"
// + "FILTER (! bound(?someDomain))"
+ "}} ORDER BY ?property";
private static final String SELECT_CONTAINER = PREFIX //
+ "SELECT ?container WHERE { ?container komma:contains ?obj . }";
private static final String COUNT_PROPERTY_OBJECTS = PREFIX //
+ "SELECT (count(?obj) as ?count) WHERE { ?subj ?pred ?obj . }";
private static final String SELECT_PROPERTY_OBJECTS = PREFIX //
+ "SELECT ?obj WHERE { ?subj ?pred ?obj . }";
public static final IResultDescriptor<IClass> DIRECT_CLASSES_DESC() {
return new ResultDescriptor<IClass>(SELECT_DIRECT_CLASSES(false),
"komma:directClasses", "class", "resource")
.bindResultType(IClass.class);
}
public static final IResultDescriptor<IClass> DIRECT_NAMED_CLASSES_DESC() {
return new ResultDescriptor<IClass>(SELECT_DIRECT_CLASSES(true),
"komma:directNamedClasses", "class", "resource")
.bindResultType(IClass.class);
}
private static final String SELECT_CLASSES(boolean named) {
return PREFIX //
+ "SELECT ?class WHERE {"
+ "?resource a ?class"
+ (named ? " FILTER isIRI(?class)" : "") + "}";
}
private static final String SELECT_DIRECT_CLASSES(boolean named) {
return PREFIX //
+ "SELECT ?class WHERE {" //
+ "?resource a ?class " //
+ (named ? "FILTER (isIRI(?class)) " : "") //
+ "FILTER NOT EXISTS {?resource a ?otherClass . ?otherClass rdfs:subClassOf ?class "
+ "FILTER (" //
+ (named ? "isIRI(?otherClass) && " : "") //
+ "?otherClass != ?class)" //
+ " FILTER NOT EXISTS {?class rdfs:subClassOf ?otherClass}" //
+ "} " //
+ "FILTER NOT EXISTS {?resource a ?otherClass . FILTER ("
+ (named ? "isIRI(?otherClass) && " : "")
+ "(?class = owl:Thing || ?class = rdfs:Resource) && ?otherClass != ?class)}" //
+ "}";
}
@Inject
private Injector injector;
private Map<IReference, PropertyInfo> properties;
@Override
public void addProperty(IReference property, Object obj) {
getEntityManager().add(new Statement(this, property, obj));
}
private synchronized PropertyInfo ensurePropertyInfo(IReference property) {
if (properties == null) {
properties = new HashMap<IReference, PropertyInfo>();
}
PropertyInfo propertyInfo = properties.get(property);
if (propertyInfo == null) {
propertyInfo = new PropertyInfo(property);
properties.put(property, propertyInfo);
}
return propertyInfo;
}
@Override
public Object get(IReference property) {
PropertyInfo propertyInfo = ensurePropertyInfo(property);
if (propertyInfo.isSingle()) {
return propertyInfo.getPropertySet().getSingle();
}
return propertyInfo.getPropertySet().getAll();
}
@Override
public Set<Object> getAsSet(IReference property) {
return ensurePropertyInfo(property).getPropertySet().getAll();
}
@Override
public Pair<Integer, Integer> getApplicableCardinality(IReference property) {
IQuery<?> query = getEntityManager().createQuery(
SELECT_APPLICABLE_CARDINALITY);
query.setParameter("resource", getBehaviourDelegate());
query.setParameter("property", property);
int min = 0;
int max = Integer.MAX_VALUE;
for (//
@SuppressWarnings("rawtypes")
Iterator<IBindings> it = query.evaluate(IBindings.class); it.hasNext();) {
IBindings<?> values = it.next();
if (values.get("min") instanceof Number) {
min = Math.max(min, ((Number) values.get("min")).intValue());
}
if (values.get("max") instanceof Number) {
max = Math.min(max, ((Number) values.get("max")).intValue());
}
}
// if min is greater than max, then max = min
max = Math.max(min, max);
// handle functional properties
if (max > 1
&& getEntityManager().find(property) instanceof FunctionalProperty) {
max = 1;
}
return new Pair<Integer, Integer>(min, max);
}
@Override
public IExtendedIterator<IProperty> getApplicableChildProperties() {
IQuery<?> query = getEntityManager().createQuery(
SELECT_APPLICABLE_CHILD_PROPERTIES);
query.setParameter("resource", this);
return query.evaluate(IProperty.class);
}
@Override
public int getCardinality(IReference property) {
IQuery<?> query = getEntityManager().createQuery(
COUNT_PROPERTY_OBJECTS, true);
query.setParameter("subj", this);
query.setParameter("pred", property);
Object count = query.getSingleResult();
return (count instanceof Number) ? ((Number) count).intValue() : 0;
}
@Override
public IExtendedIterator<IClass> getClasses() {
return getClasses(false);
}
@Override
public IExtendedIterator<IClass> getClasses(boolean includeInferred) {
IQuery<?> query = getEntityManager().createQuery(SELECT_CLASSES(false),
includeInferred);
query.setParameter("resource", getBehaviourDelegate());
return query.evaluate(IClass.class);
}
@Override
public IResource getContainer() {
IQuery<?> query = getEntityManager().createQuery(SELECT_CONTAINER);
query.setParameter("obj", this);
IExtendedIterator<?> it = query.evaluate();
try {
return it.hasNext() ? (IResource) it.next() : null;
} finally {
it.close();
}
}
@Override
public IExtendedIterator<IClass> getDirectClasses() {
return getEntityManager()
.createQuery(DIRECT_CLASSES_DESC().toQueryString())
.setParameter("resource", getBehaviourDelegate())
.evaluate(IClass.class);
}
@Override
public IExtendedIterator<IClass> getDirectNamedClasses() {
return getEntityManager()
.createQuery(DIRECT_NAMED_CLASSES_DESC().toQueryString())
.setParameter("resource", getBehaviourDelegate())
.evaluate(IClass.class);
}
@Override
public IExtendedIterator<IClass> getNamedClasses() {
IQuery<?> query = getEntityManager().createQuery(SELECT_CLASSES(true));
query.setParameter("resource", getBehaviourDelegate());
return query.evaluate(IClass.class);
}
private synchronized PropertyInfo getPropertyInfo(IReference property) {
if (properties == null) {
return null;
}
return properties.get(property);
}
@Override
public IExtendedIterator<IStatement> getPropertyStatements(
final IReference property, boolean includeInferred) {
IExtendedIterator<IStatement> stmts = internalGetPropertyStmts(
property, false, false, false);
if (includeInferred) {
stmts = UniqueExtendedIterator.create(stmts
.andThen(internalGetPropertyStmts(property, false, false,
true)));
}
return stmts;
}
@Override
public IExtendedIterator<IStatement> getInversePropertyStatements(
final IReference property, boolean filterSymmetric,
boolean includeInferred) {
IExtendedIterator<IStatement> stmts = internalGetPropertyStmts(
property, true, filterSymmetric, false);
if (includeInferred) {
stmts = UniqueExtendedIterator.create(stmts
.andThen(internalGetPropertyStmts(property, true,
filterSymmetric, true)));
}
return stmts;
}
@Override
public IExtendedIterator<IStatement> getInversePropertyStatements(
final IReference property, boolean includeInferred) {
return getInversePropertyStatements(property, false, includeInferred);
}
@Override
public IExtendedIterator<IValue> getPropertyValues(IReference property,
boolean includeInferred) {
IQuery<IValue> query = getEntityManager().createQuery(
SELECT_PROPERTY_OBJECTS, includeInferred).bindResultType(
IValue.class);
query.setParameter("subj", this);
query.setParameter("pred", property);
return query.evaluate();
}
@Override
public IExtendedIterator<IProperty> getRelevantProperties() {
IQuery<?> query = getEntityManager().createQuery(
SELECT_APPLICABLE_PROPERTIES);
query.setParameter("resource", this);
return query.evaluate(IProperty.class);
}
@Override
public Object getSingle(IReference property) {
PropertyInfo propertyInfo = ensurePropertyInfo(property);
return propertyInfo.getPropertySet().getSingle();
}
@Override
public boolean hasApplicableProperty(IReference property) {
IQuery<?> query = getEntityManager().createQuery(
HAS_APPLICABLE_PROPERTY);
query.setParameter("resource", this);
query.setParameter("property", property);
return query.getBooleanResult();
}
@Override
public boolean hasProperty(IReference property, Object obj,
boolean includeInferred) {
return getEntityManager()
.createQuery("ASK { ?s ?p ?o }", includeInferred)
.setParameter("s", this).setParameter("p", property)
.setParameter("o", obj).getBooleanResult();
}
protected IExtendedIterator<IStatement> internalGetPropertyStmts(
final IReference propertyRef, final boolean inverse,
final boolean filterSymmetric, final boolean includeInferred) {
final IEntity property = (propertyRef instanceof IEntity || propertyRef == null) ? (IEntity) propertyRef
: getEntityManager().find(propertyRef);
StringBuilder sb = new StringBuilder(PREFIX);
sb.append("SELECT ");
if (property == null) {
sb.append("?pred ");
}
String targetVar = inverse ? "?subj" : "?obj";
sb.append(targetVar);
sb.append(" WHERE { ?subj ?pred ?obj . ");
if (filterSymmetric) {
sb.append("FILTER (?subj != ?obj)");
}
sb.append(" }");
IQuery<?> query = getEntityManager().createQuery(sb.toString(),
includeInferred);
query.setParameter("pred", property);
if (!inverse) {
query.setParameter("subj", this);
query.bindResultType("obj", IValue.class);
return new ConvertingIterator<Object, IStatement>(query.evaluate()) {
@Override
protected IStatement convert(Object value) {
if (value instanceof IBindings<?>) {
IBindings<?> values = (IBindings<?>) value;
return new Statement(getBehaviourDelegate(),
property != null ? property
: (IReference) values.get("pred"),
values.get("obj"), includeInferred);
}
return new Statement(getBehaviourDelegate(), property,
value, includeInferred);
}
};
} else {
query.setParameter("obj", this);
query.bindResultType("subj", IValue.class);
return new ConvertingIterator<Object, IStatement>(query.evaluate()) {
@Override
protected IStatement convert(Object value) {
if (value instanceof IBindings<?>) {
IBindings<?> values = (IBindings<?>) value;
return new Statement((IReference) values.get("subj"),
property != null ? property
: (IReference) values.get("pred"),
getBehaviourDelegate(), includeInferred);
}
return new Statement((IReference) value, property,
getBehaviourDelegate(), includeInferred);
}
};
}
}
@Override
public boolean isOntLanguageTerm() {
net.enilink.komma.core.URI uri = getURI();
return uri != null && KommaUtil.isW3cNamespace(uri.namespace());
}
@Override
public boolean isPropertySet(IReference property, boolean includeInferred) {
return hasProperty(property, null, includeInferred);
}
public void refresh(IReference property) {
PropertyInfo propertyInfo = getPropertyInfo(property);
if (propertyInfo != null) {
propertyInfo.getPropertySet().refresh();
}
}
@Override
public void removeProperty(IReference property) {
getEntityManager().remove(new Statement(this, property, null));
}
@Override
public void removeProperty(IReference property, Object value) {
getEntityManager().remove(new Statement(this, property, value));
}
@SuppressWarnings("unchecked")
public void set(IReference property, Object value) {
PropertyInfo propertyInfo = ensurePropertyInfo(property);
if (value == null) {
propertyInfo.getPropertySet().getAll().clear();
} else if (value instanceof Collection<?> && !propertyInfo.isSingle()) {
if (value instanceof Set<?>) {
propertyInfo.getPropertySet().setAll((Set<Object>) value);
} else {
propertyInfo.getPropertySet().setAll(
(new HashSet<Object>((Collection<?>) value)));
}
} else {
propertyInfo.getPropertySet().setSingle(value);
}
}
@Override
public void refresh() {
// reset all cached properties
properties = null;
}
}