1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/net/fortuna/ical4j/model/PeriodList.java Tue Feb 10 18:12:00 2015 +0100 1.3 @@ -0,0 +1,436 @@ 1.4 +/** 1.5 + * Copyright (c) 2012, Ben Fortuna 1.6 + * All rights reserved. 1.7 + * 1.8 + * Redistribution and use in source and binary forms, with or without 1.9 + * modification, are permitted provided that the following conditions 1.10 + * are met: 1.11 + * 1.12 + * o Redistributions of source code must retain the above copyright 1.13 + * notice, this list of conditions and the following disclaimer. 1.14 + * 1.15 + * o Redistributions in binary form must reproduce the above copyright 1.16 + * notice, this list of conditions and the following disclaimer in the 1.17 + * documentation and/or other materials provided with the distribution. 1.18 + * 1.19 + * o Neither the name of Ben Fortuna nor the names of any other contributors 1.20 + * may be used to endorse or promote products derived from this software 1.21 + * without specific prior written permission. 1.22 + * 1.23 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.24 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.25 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.26 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 1.27 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 1.28 + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 1.29 + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 1.30 + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 1.31 + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 1.32 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 1.33 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.34 + */ 1.35 +package net.fortuna.ical4j.model; 1.36 + 1.37 +import java.io.Serializable; 1.38 +import java.text.ParseException; 1.39 +import java.util.Collection; 1.40 +import java.util.Collections; 1.41 +import java.util.Iterator; 1.42 +import java.util.Set; 1.43 +import java.util.StringTokenizer; 1.44 +import java.util.TreeSet; 1.45 + 1.46 +import org.apache.commons.lang.builder.EqualsBuilder; 1.47 +import org.apache.commons.lang.builder.HashCodeBuilder; 1.48 + 1.49 +/** 1.50 + * $Id$ [23-Apr-2004] 1.51 + * 1.52 + * Defines a list of iCalendar periods. NOTE: By implementing the 1.53 + * <code>java.util.SortedSet</code> interface period lists will always be 1.54 + * sorted according to natural ordering. 1.55 + * 1.56 + * @author Ben Fortuna 1.57 + */ 1.58 +public class PeriodList implements Set, Serializable { 1.59 + 1.60 + private static final long serialVersionUID = -2317587285790834492L; 1.61 + 1.62 + private final Set periods; 1.63 + 1.64 + private TimeZone timezone; 1.65 + 1.66 + private boolean utc; 1.67 + 1.68 + private final boolean unmodifiable; 1.69 + 1.70 + /** 1.71 + * Default constructor. 1.72 + */ 1.73 + public PeriodList() { 1.74 + this(true); 1.75 + } 1.76 + 1.77 + /** 1.78 + * @param utc indicates whether the period list is in UTC time 1.79 + */ 1.80 + public PeriodList(boolean utc) { 1.81 + this(utc, false); 1.82 + } 1.83 + 1.84 + /** 1.85 + * @param utc indicates whether the period list is in UTC time 1.86 + */ 1.87 + public PeriodList(boolean utc, final boolean unmodifiable) { 1.88 + this.utc = utc; 1.89 + this.unmodifiable = unmodifiable; 1.90 + if (unmodifiable) { 1.91 + periods = Collections.EMPTY_SET; 1.92 + } 1.93 + else { 1.94 + periods = new TreeSet(); 1.95 + } 1.96 + } 1.97 + 1.98 + /** 1.99 + * Parses the specified string representation to create a list of periods. 1.100 + * 1.101 + * @param aValue 1.102 + * a string representation of a list of periods 1.103 + * @throws ParseException 1.104 + * thrown when an invalid string representation of a period list 1.105 + * is specified 1.106 + */ 1.107 + public PeriodList(final String aValue) throws ParseException { 1.108 + this(); 1.109 + final StringTokenizer t = new StringTokenizer(aValue, ","); 1.110 + while (t.hasMoreTokens()) { 1.111 + add((Object) new Period(t.nextToken())); 1.112 + } 1.113 + } 1.114 + 1.115 + /** 1.116 + * {@inheritDoc} 1.117 + */ 1.118 + public final String toString() { 1.119 + final StringBuffer b = new StringBuffer(); 1.120 + for (final Iterator i = iterator(); i.hasNext();) { 1.121 + b.append(i.next().toString()); 1.122 + if (i.hasNext()) { 1.123 + b.append(','); 1.124 + } 1.125 + } 1.126 + return b.toString(); 1.127 + } 1.128 + 1.129 + /** 1.130 + * Add a period to the list. 1.131 + * 1.132 + * @param period 1.133 + * the period to add 1.134 + * @return true 1.135 + * @see java.util.List#add(java.lang.Object) 1.136 + */ 1.137 + public final boolean add(final Period period) { 1.138 + if (isUtc()) { 1.139 + period.setUtc(true); 1.140 + } 1.141 + else { 1.142 + period.setTimeZone(timezone); 1.143 + } 1.144 + return add((Object) period); 1.145 + } 1.146 + 1.147 + /** 1.148 + * Overrides superclass to throw an <code>IllegalArgumentException</code> 1.149 + * where argument is not a <code>net.fortuna.ical4j.model.Period</code>. 1.150 + * @param period a period to add to the list 1.151 + * @return true if the period was added, otherwise false 1.152 + * @see java.util.List#add(E) 1.153 + */ 1.154 + public final boolean add(final Object period) { 1.155 + if (!(period instanceof Period)) { 1.156 + throw new IllegalArgumentException("Argument not a " 1.157 + + Period.class.getName()); 1.158 + } 1.159 + return periods.add(period); 1.160 + } 1.161 + 1.162 + /** 1.163 + * Remove a period from the list. 1.164 + * 1.165 + * @param period 1.166 + * the period to remove 1.167 + * @return true if the list contained the specified period 1.168 + * @see java.util.List#remove(java.lang.Object) 1.169 + */ 1.170 + public final boolean remove(final Period period) { 1.171 + return remove((Object) period); 1.172 + } 1.173 + 1.174 + /** 1.175 + * Returns a normalised version of this period list. Normalisation includes 1.176 + * combining overlapping periods, removing periods contained by other 1.177 + * periods, combining adjacent periods, and removing periods that consume 1.178 + * no time. NOTE: If the period list is 1.179 + * already normalised then this period list is returned. 1.180 + * 1.181 + * @return a period list 1.182 + */ 1.183 + public final PeriodList normalise() { 1.184 + Period prevPeriod = null; 1.185 + Period period = null; 1.186 + final PeriodList newList = new PeriodList(isUtc()); 1.187 + if (timezone != null) { 1.188 + newList.setTimeZone(timezone); 1.189 + } 1.190 + boolean normalised = false; 1.191 + for (final Iterator i = iterator(); i.hasNext();) { 1.192 + period = (Period) i.next(); 1.193 + if (period.isEmpty()) { 1.194 + period = prevPeriod; 1.195 + normalised = true; 1.196 + } 1.197 + else if (prevPeriod != null) { 1.198 + // ignore periods contained by other periods.. 1.199 + if (prevPeriod.contains(period)) { 1.200 + period = prevPeriod; 1.201 + normalised = true; 1.202 + } 1.203 + // combine intersecting periods.. 1.204 + else if (prevPeriod.intersects(period)) { 1.205 + period = prevPeriod.add(period); 1.206 + normalised = true; 1.207 + } 1.208 + // combine adjacent periods.. 1.209 + else if (prevPeriod.adjacent(period)) { 1.210 + period = prevPeriod.add(period); 1.211 + normalised = true; 1.212 + } 1.213 + else { 1.214 + // if current period is recognised as distinct 1.215 + // from previous period, add the previous period 1.216 + // to the list.. 1.217 + newList.add(prevPeriod); 1.218 + } 1.219 + } 1.220 + prevPeriod = period; 1.221 + } 1.222 + // remember to add the last period to the list.. 1.223 + if (prevPeriod != null) { 1.224 + newList.add(prevPeriod); 1.225 + } 1.226 + // only return new list if normalisation 1.227 + // has ocurred.. 1.228 + if (normalised) { 1.229 + return newList; 1.230 + } 1.231 + else { 1.232 + return this; 1.233 + } 1.234 + } 1.235 + 1.236 + /** 1.237 + * A convenience method that combines all the periods in the specified list to 1.238 + * this list. The result returned is a new PeriodList instance, except where 1.239 + * no periods are specified in the arguments. In such cases this instance is returned. 1.240 + * 1.241 + * Normalisation is also performed automatically after all periods have been added. 1.242 + * 1.243 + * @param periods a list of periods to add 1.244 + * @return a period list instance 1.245 + */ 1.246 + public final PeriodList add(final PeriodList periods) { 1.247 + if (periods != null) { 1.248 + final PeriodList newList = new PeriodList(); 1.249 + newList.addAll(this); 1.250 + for (final Iterator i = periods.iterator(); i.hasNext();) { 1.251 + newList.add((Period) i.next()); 1.252 + } 1.253 + return newList.normalise(); 1.254 + } 1.255 + return this; 1.256 + } 1.257 + 1.258 + /** 1.259 + * Subtracts the intersection of this list with the specified list of 1.260 + * periods from this list and returns the results as a new period list. If 1.261 + * no intersection is identified this list is returned. 1.262 + * 1.263 + * @param subtractions 1.264 + * a list of periods to subtract from this list 1.265 + * @return a period list 1.266 + */ 1.267 + public final PeriodList subtract(final PeriodList subtractions) { 1.268 + if (subtractions == null || subtractions.isEmpty()) { 1.269 + return this; 1.270 + } 1.271 + 1.272 + PeriodList result = this; 1.273 + PeriodList tmpResult = new PeriodList(); 1.274 + 1.275 + for (final Iterator i = subtractions.iterator(); i.hasNext();) { 1.276 + final Period subtraction = (Period) i.next(); 1.277 + for (final Iterator j = result.iterator(); j.hasNext();) { 1.278 + final Period period = (Period) j.next(); 1.279 + tmpResult.addAll(period.subtract(subtraction)); 1.280 + } 1.281 + result = tmpResult; 1.282 + tmpResult = new PeriodList(); 1.283 + } 1.284 + 1.285 + return result; 1.286 + } 1.287 + 1.288 + public final boolean isUnmodifiable() { 1.289 + return unmodifiable; 1.290 + } 1.291 + 1.292 + /** 1.293 + * Indicates whether this list is in local or UTC format. 1.294 + * @return Returns true if in UTC format, otherwise false. 1.295 + */ 1.296 + public final boolean isUtc() { 1.297 + return utc; 1.298 + } 1.299 + 1.300 + /** 1.301 + * Sets whether this list is in UTC or local time format. 1.302 + * @param utc The utc to set. 1.303 + */ 1.304 + public final void setUtc(final boolean utc) { 1.305 + for (final Iterator i = iterator(); i.hasNext();) { 1.306 + final Period period = (Period) i.next(); 1.307 + period.setUtc(utc); 1.308 + } 1.309 + this.timezone = null; 1.310 + this.utc = utc; 1.311 + } 1.312 + 1.313 + /** 1.314 + * Applies the specified timezone to all dates in the list. 1.315 + * All dates added to this list will also have this timezone 1.316 + * applied. 1.317 + * @param timeZone the timezone for the period list 1.318 + */ 1.319 + public final void setTimeZone(final TimeZone timeZone) { 1.320 + for (final Iterator i = iterator(); i.hasNext();) { 1.321 + final Period period = (Period) i.next(); 1.322 + period.setTimeZone(timeZone); 1.323 + } 1.324 + this.timezone = timeZone; 1.325 + this.utc = false; 1.326 + } 1.327 + 1.328 + /** 1.329 + * @return Returns the timeZone. 1.330 + */ 1.331 + public final TimeZone getTimeZone() { 1.332 + return timezone; 1.333 + } 1.334 + 1.335 + /** 1.336 + * {@inheritDoc} 1.337 + */ 1.338 + public final boolean addAll(Collection arg0) { 1.339 + for (Iterator i = arg0.iterator(); i.hasNext();) { 1.340 + add(i.next()); 1.341 + } 1.342 + return true; 1.343 + } 1.344 + 1.345 + /** 1.346 + * {@inheritDoc} 1.347 + */ 1.348 + public final void clear() { 1.349 + periods.clear(); 1.350 + } 1.351 + 1.352 + /** 1.353 + * {@inheritDoc} 1.354 + */ 1.355 + public final boolean contains(Object o) { 1.356 + return periods.contains(o); 1.357 + } 1.358 + 1.359 + /** 1.360 + * {@inheritDoc} 1.361 + */ 1.362 + public final boolean containsAll(Collection arg0) { 1.363 + return periods.containsAll(arg0); 1.364 + } 1.365 + 1.366 + /** 1.367 + * {@inheritDoc} 1.368 + */ 1.369 + public final boolean isEmpty() { 1.370 + return periods.isEmpty(); 1.371 + } 1.372 + 1.373 + /** 1.374 + * {@inheritDoc} 1.375 + */ 1.376 + public final Iterator iterator() { 1.377 + return periods.iterator(); 1.378 + } 1.379 + 1.380 + /** 1.381 + * {@inheritDoc} 1.382 + */ 1.383 + public final boolean remove(Object o) { 1.384 + return periods.remove(o); 1.385 + } 1.386 + 1.387 + /** 1.388 + * {@inheritDoc} 1.389 + */ 1.390 + public final boolean removeAll(Collection arg0) { 1.391 + return periods.removeAll(arg0); 1.392 + } 1.393 + 1.394 + /** 1.395 + * {@inheritDoc} 1.396 + */ 1.397 + public final boolean retainAll(Collection arg0) { 1.398 + return periods.retainAll(arg0); 1.399 + } 1.400 + 1.401 + /** 1.402 + * {@inheritDoc} 1.403 + */ 1.404 + public final int size() { 1.405 + return periods.size(); 1.406 + } 1.407 + 1.408 + /** 1.409 + * {@inheritDoc} 1.410 + */ 1.411 + public final Object[] toArray() { 1.412 + return periods.toArray(); 1.413 + } 1.414 + 1.415 + /** 1.416 + * {@inheritDoc} 1.417 + */ 1.418 + public final Object[] toArray(Object[] arg0) { 1.419 + return periods.toArray(arg0); 1.420 + } 1.421 + 1.422 + public final boolean equals(Object obj) { 1.423 + if (!getClass().isAssignableFrom(obj.getClass())) { 1.424 + return false; 1.425 + } 1.426 + final PeriodList rhs = (PeriodList) obj; 1.427 + return new EqualsBuilder().append(periods, rhs.periods) 1.428 + .append(timezone, rhs.timezone) 1.429 + .append(utc, utc) 1.430 + .isEquals(); 1.431 + } 1.432 + 1.433 + public final int hashCode() { 1.434 + return new HashCodeBuilder().append(periods) 1.435 + .append(timezone) 1.436 + .append(utc) 1.437 + .toHashCode(); 1.438 + } 1.439 +}