/*
* (c) Copyright Ervacon 2007.
* All Rights Reserved.
*/
package com.anasoft.os.daofusion.bitemporal;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import org.joda.time.DateTime;
import org.joda.time.Interval;
/**
* A trace of {@link Bitemporal} objects, bitemporally tracking some value (for
* instance the name of a person). A bitemporal trace works on top of (wraps)
* a collection of {@link Bitemporal} objects, representing the raw data to query
* and manipulate.
*
* <p>
*
* Together with {@link Bitemporal}, {@link BitemporalTrace} provides a low-level API
* for bitemporal data tracking and manipulation expressed in terms of {@link Bitemporal}
* objects.
*
* <p>
*
* A bitemporal trace will be serializable if all {@link Bitemporal} objects it contains
* are serializable.
*
* <p>
*
* A bitemporal trace is not thread-safe.
*
* @see Bitemporal
*
* @author Erwin Vervaet
* @author Christophe Vanfleteren
* @author igor.mihalik
*/
public class BitemporalTrace implements Serializable {
private Collection<Bitemporal> data;
/**
* Create a new bitemporal trace working on top of given data collection.
*/
public BitemporalTrace(Collection<Bitemporal> data) {
if (data == null) {
throw new IllegalArgumentException("The bitemporal data is required");
}
this.data = data;
}
/**
* Returns the wrapped data collection.
*/
public Collection<Bitemporal> getData() {
return this.data;
}
/**
* Returns {@link Bitemporal} objects valid on given date as known on
* specified date.
*/
public Collection<Bitemporal> get(DateTime validOn, DateTime knownOn) {
Collection<Bitemporal> result = new LinkedList<Bitemporal>();
for (Bitemporal bitemporal : data) {
if (bitemporal.getValidityInterval().contains(validOn)
&& bitemporal.getRecordInterval().contains(knownOn)) {
result.add(bitemporal);
}
}
return result;
}
/**
* Returns the history of the tracked value, as known on specified time.
* The history informs you about how the valid value changed over time.
*/
public Collection<Bitemporal> getHistory(DateTime knownOn) {
Collection<Bitemporal> history = new LinkedList<Bitemporal>();
for (Bitemporal bitemporal : data) {
if (bitemporal.getRecordInterval().contains(knownOn)) {
history.add(bitemporal);
}
}
return history;
}
/**
* Returns the evolution of the tracked value for a specified validity date.
* The evolution informs you about how knowledge about the value valid at a
* certain date evolved.
*/
public Collection<Bitemporal> getEvolution(DateTime validOn) {
Collection<Bitemporal> evolution = new LinkedList<Bitemporal>();
for (Bitemporal bitemporal : data) {
if (bitemporal.getValidityInterval().contains(validOn)) {
evolution.add(bitemporal);
}
}
return evolution;
}
/**
* Adds the given {@link Bitemporal} object to the trace, manipulating the trace
* as necessary. This is essentially the basic bitemporal data manipulation
* operation.
*/
public void add(Bitemporal newValue) {
Collection<Bitemporal> toEnd = getItemsThatNeedToBeEnded(newValue);
Collection<Bitemporal> toAdd = new LinkedList<Bitemporal>();
DateTime validityStartOfNewValue = newValue.getValidityInterval().getStart();
for (Bitemporal validOnStartOfNewValue : get(validityStartOfNewValue, TimeUtils.now())) {
if (validityStartOfNewValue.compareTo(validOnStartOfNewValue.getValidityInterval().getStart()) > 0) {
Interval validityInterval = TimeUtils.interval(
validOnStartOfNewValue.getValidityInterval().getStart(),
validityStartOfNewValue);
toAdd.add(validOnStartOfNewValue.copyWith(validityInterval));
}
}
if (!(newValue.getValidityInterval().getEnd().getMillis() == TimeUtils.ACTUAL_END_OF_TIME)) {
DateTime validityEndOfNewValue = newValue.getValidityInterval().getEnd();
for (Bitemporal validOnEndOfNewValue : get(validityEndOfNewValue, TimeUtils.now())) {
if (validityEndOfNewValue.compareTo(validOnEndOfNewValue.getValidityInterval().getStart()) > 0) {
Interval validityInterval = TimeUtils.interval(validityEndOfNewValue,
validOnEndOfNewValue.getValidityInterval().getEnd());
toAdd.add(validOnEndOfNewValue.copyWith(validityInterval));
}
}
}
for (Bitemporal needsToEnd : toEnd) {
needsToEnd.end();
}
for (Bitemporal toBeAdded : toAdd) {
data.add(toBeAdded);
}
data.add(newValue.copyWith(newValue.getValidityInterval()));
}
Collection<Bitemporal> getItemsThatNeedToBeEnded(Bitemporal newValue) {
Collection<Bitemporal> toEnd = new HashSet<Bitemporal>();
for (Bitemporal possibleOverlap : getHistory(TimeUtils.now())) {
if (newValue.getValidityInterval().overlaps(possibleOverlap.getValidityInterval())) {
toEnd.add(possibleOverlap);
}
}
return toEnd;
}
@Override
public String toString() {
StringWriter buf = new StringWriter();
PrintWriter bufWriter = new PrintWriter(buf);
for (Bitemporal bitemporal : data) {
bufWriter.println(bitemporal);
}
return buf.toString();
}
}