/*
* $Id: PeriodList.java,v 1.13 2005/11/03 12:27:42 fortuna Exp $ [23-Apr-2004]
*
* Copyright (c) 2004, Ben Fortuna
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* o Neither the name of Ben Fortuna nor the names of any other contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.fortuna.ical4j.model;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.TreeSet;
/**
* Defines a list of iCalendar periods. NOTE: By implementing the
* <code>java.util.SortedSet</code> interface period lists will always be
* sorted according to natural ordering.
*
* @author Ben Fortuna
*/
public class PeriodList extends TreeSet implements Serializable {
private static final long serialVersionUID = -6319585959747194724L;
/**
* Default constructor.
*/
public PeriodList() {
}
/**
* Parses the specified string representation to create a list of periods.
*
* @param aValue
* a string representation of a list of periods
* @throws ParseException
* thrown when an invalid string representation of a period list
* is specified
*/
public PeriodList(final String aValue) throws ParseException {
for (StringTokenizer t = new StringTokenizer(aValue, ","); t
.hasMoreTokens();) {
add(new Period(t.nextToken()));
}
}
/**
* @see java.util.AbstractCollection#toString()
*/
public final String toString() {
StringBuffer b = new StringBuffer();
for (Iterator i = iterator(); i.hasNext();) {
b.append(i.next().toString());
if (i.hasNext()) {
b.append(',');
}
}
return b.toString();
}
/**
* Add a period to the list.
*
* @param period
* the period to add
* @return true
* @see java.util.List#add(java.lang.Object)
*/
public final boolean add(final Period period) {
return add((Object) period);
}
/**
* Overrides superclass to throw an <code>IllegalArgumentException</code>
* where argument is not a <code>net.fortuna.ical4j.model.Period</code>.
*
* @see List#add(E)
*/
public final boolean add(final Object arg0) {
if (!(arg0 instanceof Period)) {
throw new IllegalArgumentException("Argument not a "
+ Period.class.getName());
}
return super.add(arg0);
}
/**
* Remove a period from the list.
*
* @param period
* the period to remove
* @return true if the list contained the specified period
* @see java.util.List#remove(java.lang.Object)
*/
public final boolean remove(final Period period) {
return remove((Object) period);
}
/**
* Returns a normalised version of this period list. Normalisation includes
* combining overlapping periods, removing periods contained by other
* periods, and combining adjacent periods. NOTE: If the period list is
* already normalised then this period list is returned.
*
* @return a period list
*/
public final PeriodList normalise() {
Period prevPeriod = null;
Period period = null;
PeriodList newList = new PeriodList();
boolean normalised = false;
for (Iterator i = iterator(); i.hasNext();) {
period = (Period) i.next();
if (prevPeriod != null) {
// ignore periods contained by other periods..
if (prevPeriod.contains(period)) {
period = prevPeriod;
normalised = true;
}
// combine intersecting periods..
else if (prevPeriod.intersects(period)) {
period = prevPeriod.add(period);
normalised = true;
}
// combine adjacent periods..
else if (prevPeriod.adjacent(period)) {
period = prevPeriod.add(period);
normalised = true;
} else {
// if current period is recognised as distinct
// from previous period, add the previous period
// to the list..
newList.add(prevPeriod);
}
}
prevPeriod = period;
}
// remember to add the last period to the list..
if (prevPeriod != null) {
newList.add(prevPeriod);
}
// only return new list if normalisation
// has ocurred..
if (normalised) {
return newList;
}
return this;
}
/**
* A convenience method that adds all the periods in the specified list to
* this list. Normalisation is also performed automatically after all
* periods have been added.
*
* @param periods
*/
public final PeriodList add(final PeriodList periods) {
if (periods != null) {
PeriodList newList = new PeriodList();
newList.addAll(this);
for (Iterator i = periods.iterator(); i.hasNext();) {
newList.add((Period) i.next());
}
return newList.normalise();
}
return this;
}
/**
* Subtracts the intersection of this list with the specified list of
* periods from this list and returns the results as a new period list. If
* no intersection is identified this list is returned.
*
* @param periods
* a list of periods to subtract from this list
* @return a period list
*/
public final PeriodList subtract(final PeriodList subtractions) {
if (subtractions != null) {
// intialise result list as identical to this..
PeriodList result = new PeriodList();
result.addAll(this);
boolean intersects = false;
// for each subtracted period update the resulting period
// list..
for (Iterator i = subtractions.iterator(); i.hasNext();) {
Period subtraction = (Period) i.next();
PeriodList tempResult = new PeriodList();
for (Iterator j = result.iterator(); j.hasNext();) {
Period period = (Period) j.next();
if (subtraction.contains(period)) {
intersects = true;
// period is consumed by subtraction..
continue;
} else if (subtraction.intersects(period)) {
DateTime newPeriodStart;
DateTime newPeriodEnd;
if (subtraction.getStart().before(period.getStart())) {
newPeriodStart = subtraction.getEnd();
newPeriodEnd = period.getEnd();
} else if (subtraction.getEnd().before(period.getEnd())) {
newPeriodStart = period.getStart();
newPeriodEnd = subtraction.getStart();
} else {
// subtraction consumed by period..
// initialise head period..
newPeriodStart = period.getStart();
newPeriodEnd = subtraction.getStart();
tempResult.add(new Period(newPeriodStart,
newPeriodEnd));
// initialise tail period..
newPeriodStart = subtraction.getEnd();
newPeriodEnd = period.getEnd();
}
tempResult
.add(new Period(newPeriodStart, newPeriodEnd));
intersects = true;
} else {
tempResult.add(period);
}
}
result = tempResult;
}
// only return new list if intersection has ocurred..
if (intersects) {
return result;
}
}
return this;
}
}