/* * Copyright (C) 2011 Andrea Schweer * * This file is part of the Digital Parrot. * * The Digital Parrot is free software; you can redistribute it and/or modify * it under the terms of the Eclipse Public License as published by the Eclipse * Foundation or its Agreement Steward, either version 1.0 of the License, or * (at your option) any later version. * * The Digital Parrot is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License for * more details. * * You should have received a copy of the Eclipse Public License along with the * Digital Parrot. If not, see http://www.eclipse.org/legal/epl-v10.html. * */ package net.schweerelos.parrot.model; import net.schweerelos.timeline.model.PayloadInterval; import net.schweerelos.timeline.model.IntervalChain; import org.joda.time.DateTime; import org.joda.time.Interval; import com.hp.hpl.jena.datatypes.RDFDatatype; import com.hp.hpl.jena.datatypes.xsd.XSDDateTime; import com.hp.hpl.jena.ontology.Individual; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntResource; import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.NodeIterator; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.util.iterator.ExtendedIterator; public class TimedThingsHelper { private static final String TIMED_THING = "http://parrot.resnet.scms.waikato.ac.nz/Parrot/Terms/TimeAndPlace/2008/11/TimeAndPlace.owl#TimedThing"; static private final String ABSOLUTELY_TIMED_THING = "http://parrot.resnet.scms.waikato.ac.nz/Parrot/Terms/TimeAndPlace/2008/11/TimeAndPlace.owl#AbsolutelyTimedThing"; static private final String ENDS_AT = "http://parrot.resnet.scms.waikato.ac.nz/Parrot/Terms/TimeAndPlace/2008/11/TimeAndPlace.owl#endsAt"; static private final String STARTS_AT = "http://parrot.resnet.scms.waikato.ac.nz/Parrot/Terms/TimeAndPlace/2008/11/TimeAndPlace.owl#startsAt"; private static final String DURING = "http://parrot.resnet.scms.waikato.ac.nz/Parrot/Terms/TimeAndPlace/2008/11/TimeAndPlace.owl#during"; public static IntervalChain<NodeWrapper> extractTimedThings( ParrotModel pModel) { OntModel model = pModel.getOntModel(); Resource timedThingClass = model.createClass(TIMED_THING); IntervalChain<NodeWrapper> timelineModel = new IntervalChain<NodeWrapper>(); ExtendedIterator<Individual> instances = model .listIndividuals(timedThingClass); while (instances.hasNext()) { Individual instance = instances.next(); final NodeWrapper node = pModel.getNodeWrapper(instance); DateTime startsAt; DateTime endsAt; try { startsAt = extractStartDate(instance, model); } catch (NotTimedThingException e) { // ignore this individual continue; } try { endsAt = extractEndDate(instance, model); } catch (NotTimedThingException e) { // ignore this individual continue; } if (startsAt == null || endsAt == null) { // only proceed if we have a start time *and* an end time continue; } if (endsAt.isBefore(startsAt)) { System.out.printf("end timestamp %s is before start timestamp %s, ignoring instance %s as TimedThing\n", endsAt.toString(), startsAt.toString(), instance.getURI()); continue; } final Interval base = new Interval(startsAt, endsAt); PayloadInterval<NodeWrapper> interval = new PayloadInterval<NodeWrapper>() { Interval baseInterval = base; @Override public DateTime getEnd() { return baseInterval.getEnd(); } @Override public DateTime getStart() { return baseInterval.getStart(); } @Override public NodeWrapper getPayload() { return node; } @Override public boolean contains(Interval interval) { return baseInterval.contains(interval); } @Override public Interval toInterval() { return baseInterval; } }; timelineModel.add(interval); } return timelineModel; } /** * Determines whether the node is a timedthing with reasonably well-known * temporal boundaries. This is the case if the node is an instance of * AbsolutelyTimedThing or if it occurred during an AbsolutelyTimedThing. * * @param node * the node to check * @param model * the ont model from which the node is taken * @return true if the node is a timedthing with reasonably well-known * boundaries. */ public static boolean isTimedThing(OntResource node, ParrotModel pModel) { OntModel model = pModel.getOntModel(); if (isAbsolutelyTimedThing(node)) { return true; } return isIndirectlyTimedThing(node, model); } public static DateTime extractStartDate(OntResource subject, ParrotModel model) throws NotTimedThingException { return extractStartDate(subject, model.getOntModel()); } public static DateTime extractEndDate(OntResource subject, ParrotModel model) throws NotTimedThingException { return extractEndDate(subject, model.getOntModel()); } private static DateTime extractStartDate(OntResource subject, OntModel model) throws NotTimedThingException { if (isAbsolutelyTimedThing(subject)) { return extractAbsoluteDate(subject, model, STARTS_AT); } else if (isIndirectlyTimedThing(subject, model)) { return extractNearestDate(subject, model, STARTS_AT, true); } throw new NotTimedThingException(subject + " is not a timed thing"); } /** * Extracts a {@code DateTime} representation of a date associated with the * subject, by iterating through all spanning {@code AbsolutelyTimedThing}s * and taking the nearest date. The type of date is specified via its * property name. * * @param subject * the subject for which the date should be extracted. The * assumption is that the subject is in indirectly timed thing * (ie {@code TimedThingsHelper#isIndirectlyTimedThing(subject, * model)} is true). * @param model * the model from which the subject has been taken. * @param propertyName * name of the property to use (would normally be {@code * TimedThingsHelper#STARTS_AT} or {@code * TimedThingsHelper#ENDS_AT}) * @param before * whether the nearest date should be before or after the thing's * time. * @return a {@code DateTime} representation of a date associated with the * subject * @throws NotTimedThingException * if the subject isn't timed */ private static DateTime extractNearestDate(OntResource subject, OntModel model, String propertyName, boolean before) throws NotTimedThingException { DateTime currentBestCandidate = null; Property duringProperty = model.createProperty(DURING); // TODO #42 this should go through before and after as well if (!subject.hasProperty(duringProperty)) { throw new NotTimedThingException(subject + " doesn't have property " + DURING); } NodeIterator values = subject.listPropertyValues(duringProperty); while (values.hasNext()) { RDFNode value = values.next(); if (isAbsolutelyTimedThing(value)) { DateTime valueDate = extractAbsoluteDate(value, model, propertyName); boolean betterThanCurrentBest = false; if (currentBestCandidate == null) { betterThanCurrentBest = true; } else { betterThanCurrentBest = (valueDate .isBefore(currentBestCandidate) == before); } if (betterThanCurrentBest) { currentBestCandidate = valueDate; } } } return currentBestCandidate; } private static DateTime extractAbsoluteDate(RDFNode subject, OntModel model, String propertyName) throws NotTimedThingException { if (subject.canAs(OntResource.class)) { return extractAbsoluteDate(subject.as(OntResource.class), model, propertyName); } throw new NotTimedThingException( "can't extract date for nodes that aren't OntResources"); } /** * Extracts a {@code DateTime} representation of the subject's date. The * type of date is specified via its property name. * * @param subject * the subject for which a date should be extracted. The * assumption is that this is an absolutely timed thing, ie * {@code #isAbsolutelyTimedThing(subject, model)} is true. * @param model * the model from which the subject has been taken. * @param propertyName * name of the property to use (would normally be {@code * TimedThingsHelper#STARTS_AT} or {@code * TimedThingsHelper#ENDS_AT}) * @return a date representation of the subject's value of the specified * property * @throws NotTimedThingException * if the subject isn't timed */ private static DateTime extractAbsoluteDate(OntResource subject, OntModel model, String propertyName) throws NotTimedThingException { Property prop = model.createProperty(propertyName); RDFNode propValue = subject.getPropertyValue(prop); return extractDate(propValue); } private static DateTime extractEndDate(OntResource subject, OntModel model) throws NotTimedThingException { if (isAbsolutelyTimedThing(subject)) { return extractAbsoluteDate(subject, model, ENDS_AT); } else if (isIndirectlyTimedThing(subject, model)) { return extractNearestDate(subject, model, ENDS_AT, false); } throw new NotTimedThingException(subject + " is not a timed thing"); } private static boolean isAbsolutelyTimedThing(RDFNode node) { if (!node.canAs(OntResource.class)) { return false; } OntResource res = node.as(OntResource.class); return isAbsolutelyTimedThing(res); } private static boolean isAbsolutelyTimedThing(OntResource node) { if (!node.isIndividual()) { return false; } Individual individual = node.asIndividual(); return individual.hasOntClass(ABSOLUTELY_TIMED_THING); } /** * check whether it happened during an AbsolutelyTimedThing * * @param node * @param model * @return */ private static boolean isIndirectlyTimedThing(OntResource node, OntModel model) { Property duringProperty = model.createProperty(DURING); if (!node.hasProperty(duringProperty)) { return false; } NodeIterator values = node.listPropertyValues(duringProperty); while (values.hasNext()) { RDFNode value = values.next(); if (isAbsolutelyTimedThing(value)) { return true; } } return false; } private static DateTime extractDate(RDFNode node) throws NotTimedThingException { if (!node.isLiteral()) { throw new NotTimedThingException("Node is not a literal"); } RDFDatatype type = ((Literal) node).getDatatype(); Object value = ((Literal) node).getValue(); if (type != null && value != null && value instanceof XSDDateTime) { return new DateTime(((XSDDateTime) value).asCalendar()); } else { throw new NotTimedThingException("Node does not represent a date"); } } }