src/net/fortuna/ical4j/model/Component.java

Tue, 10 Feb 2015 19:58:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 19:58:00 +0100
changeset 4
45d57ecba757
parent 0
fb9019fb1bf7
permissions
-rw-r--r--

Upgrade the upgraded ical4j component to use org.apache.commons.lang3.

michael@0 1 /**
michael@0 2 * Copyright (c) 2012, Ben Fortuna
michael@0 3 * All rights reserved.
michael@0 4 *
michael@0 5 * Redistribution and use in source and binary forms, with or without
michael@0 6 * modification, are permitted provided that the following conditions
michael@0 7 * are met:
michael@0 8 *
michael@0 9 * o Redistributions of source code must retain the above copyright
michael@0 10 * notice, this list of conditions and the following disclaimer.
michael@0 11 *
michael@0 12 * o Redistributions in binary form must reproduce the above copyright
michael@0 13 * notice, this list of conditions and the following disclaimer in the
michael@0 14 * documentation and/or other materials provided with the distribution.
michael@0 15 *
michael@0 16 * o Neither the name of Ben Fortuna nor the names of any other contributors
michael@0 17 * may be used to endorse or promote products derived from this software
michael@0 18 * without specific prior written permission.
michael@0 19 *
michael@0 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
michael@0 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
michael@0 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
michael@0 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
michael@0 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
michael@0 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
michael@0 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
michael@0 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 31 */
michael@0 32 package net.fortuna.ical4j.model;
michael@0 33
michael@0 34 import java.io.IOException;
michael@0 35 import java.io.Serializable;
michael@0 36 import java.net.URISyntaxException;
michael@0 37 import java.text.ParseException;
michael@0 38 import java.util.Iterator;
michael@0 39
michael@0 40 import net.fortuna.ical4j.model.parameter.Value;
michael@0 41 import net.fortuna.ical4j.model.property.DateProperty;
michael@0 42 import net.fortuna.ical4j.model.property.DtStart;
michael@0 43 import net.fortuna.ical4j.model.property.Duration;
michael@0 44 import net.fortuna.ical4j.model.property.ExDate;
michael@0 45 import net.fortuna.ical4j.model.property.ExRule;
michael@0 46 import net.fortuna.ical4j.model.property.RDate;
michael@0 47 import net.fortuna.ical4j.model.property.RRule;
michael@0 48 import net.fortuna.ical4j.util.Strings;
michael@0 49
michael@4 50 import org.apache.commons.lang3.builder.EqualsBuilder;
michael@4 51 import org.apache.commons.lang3.builder.HashCodeBuilder;
michael@0 52
michael@0 53 /**
michael@0 54 * $Id$ [Apr 5, 2004]
michael@0 55 *
michael@0 56 * Defines an iCalendar component. Subclasses of this class provide additional validation and typed values for specific
michael@0 57 * iCalendar components.
michael@0 58 * @author Ben Fortuna
michael@0 59 */
michael@0 60 public abstract class Component implements Serializable {
michael@0 61
michael@0 62 private static final long serialVersionUID = 4943193483665822201L;
michael@0 63
michael@0 64 /**
michael@0 65 * Component start token.
michael@0 66 */
michael@0 67 public static final String BEGIN = "BEGIN";
michael@0 68
michael@0 69 /**
michael@0 70 * Component end token.
michael@0 71 */
michael@0 72 public static final String END = "END";
michael@0 73
michael@0 74 /**
michael@0 75 * Component token.
michael@0 76 */
michael@0 77 public static final String VEVENT = "VEVENT";
michael@0 78
michael@0 79 /**
michael@0 80 * Component token.
michael@0 81 */
michael@0 82 public static final String VTODO = "VTODO";
michael@0 83
michael@0 84 /**
michael@0 85 * Component token.
michael@0 86 */
michael@0 87 public static final String VJOURNAL = "VJOURNAL";
michael@0 88
michael@0 89 /**
michael@0 90 * Component token.
michael@0 91 */
michael@0 92 public static final String VFREEBUSY = "VFREEBUSY";
michael@0 93
michael@0 94 /**
michael@0 95 * Component token.
michael@0 96 */
michael@0 97 public static final String VTIMEZONE = "VTIMEZONE";
michael@0 98
michael@0 99 /**
michael@0 100 * Component token.
michael@0 101 */
michael@0 102 public static final String VALARM = "VALARM";
michael@0 103
michael@0 104 /**
michael@0 105 * Component token.
michael@0 106 */
michael@0 107 public static final String VAVAILABILITY = "VAVAILABILITY";
michael@0 108
michael@0 109 /**
michael@0 110 * Component token.
michael@0 111 */
michael@0 112 public static final String VVENUE = "VVENUE";
michael@0 113
michael@0 114 /**
michael@0 115 * Component token.
michael@0 116 */
michael@0 117 public static final String AVAILABLE = "AVAILABLE";
michael@0 118
michael@0 119 /**
michael@0 120 * Prefix for non-standard components.
michael@0 121 */
michael@0 122 public static final String EXPERIMENTAL_PREFIX = "X-";
michael@0 123
michael@0 124 private String name;
michael@0 125
michael@0 126 private PropertyList properties;
michael@0 127
michael@0 128 /**
michael@0 129 * Constructs a new component containing no properties.
michael@0 130 * @param s a component name
michael@0 131 */
michael@0 132 protected Component(final String s) {
michael@0 133 this(s, new PropertyList());
michael@0 134 }
michael@0 135
michael@0 136 /**
michael@0 137 * Constructor made protected to enforce the use of <code>ComponentFactory</code> for component instantiation.
michael@0 138 * @param s component name
michael@0 139 * @param p a list of properties
michael@0 140 */
michael@0 141 protected Component(final String s, final PropertyList p) {
michael@0 142 this.name = s;
michael@0 143 this.properties = p;
michael@0 144 }
michael@0 145
michael@0 146 /**
michael@0 147 * {@inheritDoc}
michael@0 148 */
michael@0 149 public String toString() {
michael@0 150 final StringBuffer buffer = new StringBuffer();
michael@0 151 buffer.append(BEGIN);
michael@0 152 buffer.append(':');
michael@0 153 buffer.append(getName());
michael@0 154 buffer.append(Strings.LINE_SEPARATOR);
michael@0 155 buffer.append(getProperties());
michael@0 156 buffer.append(END);
michael@0 157 buffer.append(':');
michael@0 158 buffer.append(getName());
michael@0 159 buffer.append(Strings.LINE_SEPARATOR);
michael@0 160
michael@0 161 return buffer.toString();
michael@0 162 }
michael@0 163
michael@0 164 /**
michael@0 165 * @return Returns the name.
michael@0 166 */
michael@0 167 public final String getName() {
michael@0 168 return name;
michael@0 169 }
michael@0 170
michael@0 171 /**
michael@0 172 * @return Returns the properties.
michael@0 173 */
michael@0 174 public final PropertyList getProperties() {
michael@0 175 return properties;
michael@0 176 }
michael@0 177
michael@0 178 /**
michael@0 179 * Convenience method for retrieving a list of named properties.
michael@0 180 * @param name name of properties to retrieve
michael@0 181 * @return a property list containing only properties with the specified name
michael@0 182 */
michael@0 183 public final PropertyList getProperties(final String name) {
michael@0 184 return getProperties().getProperties(name);
michael@0 185 }
michael@0 186
michael@0 187 /**
michael@0 188 * Convenience method for retrieving a named property.
michael@0 189 * @param name name of the property to retrieve
michael@0 190 * @return the first matching property in the property list with the specified name
michael@0 191 */
michael@0 192 public final Property getProperty(final String name) {
michael@0 193 return getProperties().getProperty(name);
michael@0 194 }
michael@0 195
michael@0 196 /**
michael@0 197 * Perform validation on a component and its properties.
michael@0 198 * @throws ValidationException where the component is not in a valid state
michael@0 199 */
michael@0 200 public final void validate() throws ValidationException {
michael@0 201 validate(true);
michael@0 202 }
michael@0 203
michael@0 204 /**
michael@0 205 * Perform validation on a component.
michael@0 206 * @param recurse indicates whether to validate the component's properties
michael@0 207 * @throws ValidationException where the component is not in a valid state
michael@0 208 */
michael@0 209 public abstract void validate(final boolean recurse)
michael@0 210 throws ValidationException;
michael@0 211
michael@0 212 /**
michael@0 213 * Invoke validation on the component properties in its current state.
michael@0 214 * @throws ValidationException where any of the component properties is not in a valid state
michael@0 215 */
michael@0 216 protected final void validateProperties() throws ValidationException {
michael@0 217 for (final Iterator i = getProperties().iterator(); i.hasNext();) {
michael@0 218 final Property property = (Property) i.next();
michael@0 219 property.validate();
michael@0 220 }
michael@0 221 }
michael@0 222
michael@0 223 /**
michael@0 224 * {@inheritDoc}
michael@0 225 */
michael@0 226 public boolean equals(final Object arg0) {
michael@0 227 if (arg0 instanceof Component) {
michael@0 228 final Component c = (Component) arg0;
michael@0 229 return new EqualsBuilder().append(getName(), c.getName())
michael@0 230 .append(getProperties(), c.getProperties()).isEquals();
michael@0 231 }
michael@0 232 return super.equals(arg0);
michael@0 233 }
michael@0 234
michael@0 235 /**
michael@0 236 * {@inheritDoc}
michael@0 237 */
michael@0 238 public int hashCode() {
michael@0 239 return new HashCodeBuilder().append(getName()).append(getProperties())
michael@0 240 .toHashCode();
michael@0 241 }
michael@0 242
michael@0 243 /**
michael@0 244 * Create a (deep) copy of this component.
michael@0 245 * @return the component copy
michael@0 246 * @throws IOException where an error occurs reading the component data
michael@0 247 * @throws ParseException where parsing component data fails
michael@0 248 * @throws URISyntaxException where component data contains an invalid URI
michael@0 249 */
michael@0 250 public Component copy() throws ParseException, IOException,
michael@0 251 URISyntaxException {
michael@0 252
michael@0 253 // Deep copy properties..
michael@0 254 final PropertyList newprops = new PropertyList(getProperties());
michael@0 255
michael@0 256 return ComponentFactory.getInstance().createComponent(getName(),
michael@0 257 newprops);
michael@0 258 }
michael@0 259
michael@0 260 /**
michael@0 261 * Calculates the recurrence set for this component using the specified period.
michael@0 262 * The recurrence set is derived from a combination of the component start date,
michael@0 263 * recurrence rules and dates, and exception rules and dates. Note that component
michael@0 264 * transparency and anniversary-style dates do not affect the resulting
michael@0 265 * intersection.
michael@0 266 * <p>If an explicit DURATION is not specified, the effective duration of each
michael@0 267 * returned period is derived from the DTSTART and DTEND or DUE properties.
michael@0 268 * If the component has no DURATION, DTEND or DUE, the effective duration is set
michael@0 269 * to PT0S</p>
michael@0 270 * @param period a range to calculate recurrences for
michael@0 271 * @return a list of periods
michael@0 272 */
michael@0 273 public final PeriodList calculateRecurrenceSet(final Period period) {
michael@0 274
michael@0 275 // validate();
michael@0 276
michael@0 277 final PeriodList recurrenceSet = new PeriodList();
michael@0 278
michael@0 279 final DtStart start = (DtStart) getProperty(Property.DTSTART);
michael@0 280 DateProperty end = (DateProperty) getProperty(Property.DTEND);
michael@0 281 if (end == null) {
michael@0 282 end = (DateProperty) getProperty(Property.DUE);
michael@0 283 }
michael@0 284 Duration duration = (Duration) getProperty(Property.DURATION);
michael@0 285
michael@0 286 // if no start date specified return empty list..
michael@0 287 if (start == null) {
michael@0 288 return recurrenceSet;
michael@0 289 }
michael@0 290
michael@0 291 final Value startValue = (Value) start.getParameter(Parameter.VALUE);
michael@0 292
michael@0 293 // initialise timezone..
michael@0 294 // if (startValue == null || Value.DATE_TIME.equals(startValue)) {
michael@0 295 if (start.isUtc()) {
michael@0 296 recurrenceSet.setUtc(true);
michael@0 297 }
michael@0 298 else if (start.getDate() instanceof DateTime) {
michael@0 299 recurrenceSet.setTimeZone(((DateTime) start.getDate()).getTimeZone());
michael@0 300 }
michael@0 301
michael@0 302 // if an explicit event duration is not specified, derive a value for recurring
michael@0 303 // periods from the end date..
michael@0 304 Dur rDuration;
michael@0 305 // if no end or duration specified, end date equals start date..
michael@0 306 if (end == null && duration == null) {
michael@0 307 rDuration = new Dur(start.getDate(), start.getDate());
michael@0 308 }
michael@0 309 else if (duration == null) {
michael@0 310 rDuration = new Dur(start.getDate(), end.getDate());
michael@0 311 }
michael@0 312 else {
michael@0 313 rDuration = duration.getDuration();
michael@0 314 }
michael@0 315
michael@0 316 // add recurrence dates..
michael@0 317 for (final Iterator i = getProperties(Property.RDATE).iterator(); i.hasNext();) {
michael@0 318 final RDate rdate = (RDate) i.next();
michael@0 319 final Value rdateValue = (Value) rdate.getParameter(Parameter.VALUE);
michael@0 320 if (Value.PERIOD.equals(rdateValue)) {
michael@0 321 for (final Iterator j = rdate.getPeriods().iterator(); j.hasNext();) {
michael@0 322 final Period rdatePeriod = (Period) j.next();
michael@0 323 if (period.intersects(rdatePeriod)) {
michael@0 324 recurrenceSet.add(rdatePeriod);
michael@0 325 }
michael@0 326 }
michael@0 327 }
michael@0 328 else if (Value.DATE_TIME.equals(rdateValue)) {
michael@0 329 for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) {
michael@0 330 final DateTime rdateTime = (DateTime) j.next();
michael@0 331 if (period.includes(rdateTime)) {
michael@0 332 recurrenceSet.add(new Period(rdateTime, rDuration));
michael@0 333 }
michael@0 334 }
michael@0 335 }
michael@0 336 else {
michael@0 337 for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) {
michael@0 338 final Date rdateDate = (Date) j.next();
michael@0 339 if (period.includes(rdateDate)) {
michael@0 340 recurrenceSet.add(new Period(new DateTime(rdateDate), rDuration));
michael@0 341 }
michael@0 342 }
michael@0 343 }
michael@0 344 }
michael@0 345
michael@0 346 // allow for recurrence rules that start prior to the specified period
michael@0 347 // but still intersect with it..
michael@0 348 final DateTime startMinusDuration = new DateTime(period.getStart());
michael@0 349 startMinusDuration.setTime(rDuration.negate().getTime(
michael@0 350 period.getStart()).getTime());
michael@0 351
michael@0 352 // add recurrence rules..
michael@0 353 for (final Iterator i = getProperties(Property.RRULE).iterator(); i.hasNext();) {
michael@0 354 final RRule rrule = (RRule) i.next();
michael@0 355 final DateList rruleDates = rrule.getRecur().getDates(start.getDate(),
michael@0 356 new Period(startMinusDuration, period.getEnd()), startValue);
michael@0 357 for (final Iterator j = rruleDates.iterator(); j.hasNext();) {
michael@0 358 final Date rruleDate = (Date) j.next();
michael@0 359 recurrenceSet.add(new Period(new DateTime(rruleDate), rDuration));
michael@0 360 }
michael@0 361 }
michael@0 362
michael@0 363 // add initial instance if intersection with the specified period..
michael@0 364 Period startPeriod = null;
michael@0 365 if (end != null) {
michael@0 366 startPeriod = new Period(new DateTime(start.getDate()),
michael@0 367 new DateTime(end.getDate()));
michael@0 368 }
michael@0 369 else {
michael@0 370 /*
michael@0 371 * PeS: Anniversary type has no DTEND nor DUR, define DUR
michael@0 372 * locally, otherwise we get NPE
michael@0 373 */
michael@0 374 if (duration == null) {
michael@0 375 duration = new Duration(rDuration);
michael@0 376 }
michael@0 377
michael@0 378 startPeriod = new Period(new DateTime(start.getDate()),
michael@0 379 duration.getDuration());
michael@0 380 }
michael@0 381 if (period.intersects(startPeriod)) {
michael@0 382 recurrenceSet.add(startPeriod);
michael@0 383 }
michael@0 384
michael@0 385 // subtract exception dates..
michael@0 386 for (final Iterator i = getProperties(Property.EXDATE).iterator(); i.hasNext();) {
michael@0 387 final ExDate exdate = (ExDate) i.next();
michael@0 388 for (final Iterator j = recurrenceSet.iterator(); j.hasNext();) {
michael@0 389 final Period recurrence = (Period) j.next();
michael@0 390 // for DATE-TIME instances check for DATE-based exclusions also..
michael@0 391 if (exdate.getDates().contains(recurrence.getStart())
michael@0 392 || exdate.getDates().contains(new Date(recurrence.getStart()))) {
michael@0 393 j.remove();
michael@0 394 }
michael@0 395 }
michael@0 396 }
michael@0 397
michael@0 398 // subtract exception rules..
michael@0 399 for (final Iterator i = getProperties(Property.EXRULE).iterator(); i.hasNext();) {
michael@0 400 final ExRule exrule = (ExRule) i.next();
michael@0 401 final DateList exruleDates = exrule.getRecur().getDates(start.getDate(),
michael@0 402 period, startValue);
michael@0 403 for (final Iterator j = recurrenceSet.iterator(); j.hasNext();) {
michael@0 404 final Period recurrence = (Period) j.next();
michael@0 405 // for DATE-TIME instances check for DATE-based exclusions also..
michael@0 406 if (exruleDates.contains(recurrence.getStart())
michael@0 407 || exruleDates.contains(new Date(recurrence.getStart()))) {
michael@0 408 j.remove();
michael@0 409 }
michael@0 410 }
michael@0 411 }
michael@0 412
michael@0 413 return recurrenceSet;
michael@0 414 }
michael@0 415 }

mercurial