jfxtras.icalendarfx.components.VDisplayable Maven / Gradle / Ivy
package jfxtras.icalendarfx.components;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jfxtras.icalendarfx.VCalendar;
import jfxtras.icalendarfx.components.VComponent;
import jfxtras.icalendarfx.components.VDescribable;
import jfxtras.icalendarfx.components.VDisplayable;
import jfxtras.icalendarfx.components.VLastModified;
import jfxtras.icalendarfx.components.VPersonal;
import jfxtras.icalendarfx.components.VRepeatable;
import jfxtras.icalendarfx.components.VRepeatableBase;
import jfxtras.icalendarfx.properties.VPropertyElement;
import jfxtras.icalendarfx.properties.component.change.DateTimeCreated;
import jfxtras.icalendarfx.properties.component.change.LastModified;
import jfxtras.icalendarfx.properties.component.change.Sequence;
import jfxtras.icalendarfx.properties.component.descriptive.Attachment;
import jfxtras.icalendarfx.properties.component.descriptive.Categories;
import jfxtras.icalendarfx.properties.component.descriptive.Classification;
import jfxtras.icalendarfx.properties.component.descriptive.Status;
import jfxtras.icalendarfx.properties.component.descriptive.Summary;
import jfxtras.icalendarfx.properties.component.descriptive.Classification.ClassificationType;
import jfxtras.icalendarfx.properties.component.descriptive.Status.StatusType;
import jfxtras.icalendarfx.properties.component.recurrence.ExceptionDates;
import jfxtras.icalendarfx.properties.component.recurrence.RecurrenceDates;
import jfxtras.icalendarfx.properties.component.recurrence.RecurrenceRule;
import jfxtras.icalendarfx.properties.component.recurrence.RecurrenceRuleCache;
import jfxtras.icalendarfx.properties.component.relationship.Contact;
import jfxtras.icalendarfx.properties.component.relationship.RecurrenceId;
import jfxtras.icalendarfx.properties.component.relationship.RelatedTo;
import jfxtras.icalendarfx.properties.component.relationship.UniqueIdentifier;
import jfxtras.icalendarfx.properties.component.time.DateTimeStart;
import jfxtras.icalendarfx.utilities.DateTimeUtilities;
import jfxtras.icalendarfx.utilities.DateTimeUtilities.DateTimeType;
/**
* {@link VComponent} with the following properties
*
* - {@link Attachment ATTACH}
*
- {@link Categories CATEGORIES}
*
- {@link Classification CLASS}
*
- {@link Contact CONTACT}
*
- {@link DateTimeCreated CREATED}
*
- {@link ExceptionDates EXDATE}
*
- {@link LastModified LAST-MODIFIED}
*
- {@link RecurrenceId RECURRENCE-ID}
*
- {@link RelatedTo RELATED-TO}
*
- {@link RecurrenceRule RRULE}
*
- {@link Sequence SEQUENCE}
*
- {@link Status STATUS}
*
- {@link Summary SUMMARY}
*
*
*
* @author David Bal
*
* @param concrete subclass
*/
public abstract class VDisplayable extends VPersonal implements VRepeatable, VDescribable, VLastModified
{
/**
* This property provides the capability to associate a document object with a calendar component.
*
*Example: The following is an example of this property:
*
*- ATTACH:CID:[email protected]
*
- ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/
* reports/r-960812.ps
*
*
*/
@Override
public List> getAttachments() { return attachments; }
private List> attachments;
@Override
public void setAttachments(List> attachments)
{
if (this.attachments != null)
{
this.attachments.forEach(e -> orderChild(e, null)); // remove old elements
}
this.attachments = attachments;
if (attachments != null)
{
attachments.forEach(e -> orderChild(e));
}
}
/**
* CATEGORIES:
* RFC 5545 iCalendar 3.8.1.12. page 81
* This property defines the categories for a calendar component.
* Example:
* CATEGORIES:APPOINTMENT,EDUCATION
* CATEGORIES:MEETING
*/
public List getCategories() { return categories; }
private List categories;
public void setCategories(List categories)
{
if (this.categories != null)
{
this.categories.forEach(e -> orderChild(e, null)); // remove old elements
}
this.categories = categories;
if (categories != null)
{
categories.forEach(e -> orderChild(e));
}
}
public T withCategories(List categories)
{
if (getCategories() == null)
{
setCategories(new ArrayList<>());
}
getCategories().addAll(categories);
if (categories != null)
{
categories.forEach(c -> orderChild(c));
}
return (T) this;
}
public T withCategories(String...categories)
{
return withCategories(new Categories(categories));
}
public T withCategories(Categories...categories)
{
withCategories(new ArrayList<>(Arrays.asList(categories)));
return (T) this;
}
/**
* CLASS
* Classification
* RFC 5545, 3.8.1.3, page 82
*
* This property defines the access classification for a calendar component.
*
* Example:
* CLASS:PUBLIC
*/
public Classification getClassification() { return classification; }
private Classification classification;
public void setClassification(String classification) { setClassification(Classification.parse(classification)); }
public void setClassification(Classification classification)
{
orderChild(this.classification, classification);
this.classification = classification;
}
public void setClassification(ClassificationType classification) { setClassification(new Classification(classification)); }
public T withClassification(Classification classification)
{
setClassification(classification);
return (T) this;
}
public T withClassification(ClassificationType classification)
{
setClassification(classification);
return (T) this;
}
public T withClassification(String classification)
{
setClassification(classification);
return (T) this;
}
/**
* CONTACT:
* RFC 5545 iCalendar 3.8.4.2. page 109
* This property is used to represent contact information or
* alternately a reference to contact information associated with the
* calendar component.
*
* Example:
* CONTACT;ALTREP="ldap://example.com:6666/o=ABC%20Industries\,
* c=US???(cn=Jim%20Dolittle)":Jim Dolittle\, ABC Industries\,
* +1-919-555-1234
*/
public List getContacts() { return contacts; }
private List contacts;
public void setContacts(List contacts)
{
if (this.contacts != null)
{
this.contacts.forEach(e -> orderChild(e, null)); // remove old elements
}
this.contacts = contacts;
if (contacts != null)
{
contacts.forEach(c -> orderChild(c));
}
}
public T withContacts(List contacts)
{
if (getContacts() == null)
{
setContacts(new ArrayList<>());
}
getContacts().addAll(contacts);
if (contacts != null)
{
contacts.forEach(c -> orderChild(c));
}
return (T) this;
}
public T withContacts(String...contacts)
{
List list = Arrays.stream(contacts)
.map(c -> Contact.parse(c))
.collect(Collectors.toList());
return withContacts(list);
}
public T withContacts(Contact...contacts)
{
return withContacts(Arrays.asList(contacts));
}
/**
* CREATED: Date-Time Created
* RFC 5545 iCalendar 3.8.7.1 page 136
* This property specifies the date and time that the calendar information was created.
* This is analogous to the creation date and time for a file in the file system.
* The value MUST be specified in the UTC time format.
*
* Example:
* CREATED:19960329T133000Z
*/
public DateTimeCreated getDateTimeCreated() { return dateTimeCreated; }
private DateTimeCreated dateTimeCreated;
public void setDateTimeCreated(String dateTimeCreated) { setDateTimeCreated(DateTimeCreated.parse(dateTimeCreated)); }
public void setDateTimeCreated(DateTimeCreated dateTimeCreated)
{
orderChild(this.dateTimeCreated, dateTimeCreated);
this.dateTimeCreated = dateTimeCreated;
}
public void setDateTimeCreated(ZonedDateTime dateTimeCreated) { setDateTimeCreated(new DateTimeCreated(dateTimeCreated)); }
public T withDateTimeCreated(ZonedDateTime dateTimeCreated)
{
setDateTimeCreated(dateTimeCreated);
return (T) this;
}
public T withDateTimeCreated(String dateTimeCreated)
{
setDateTimeCreated(dateTimeCreated);
return (T) this;
}
public T withDateTimeCreated(DateTimeCreated dateTimeCreated)
{
setDateTimeCreated(dateTimeCreated);
return (T) this;
}
/**
* EXDATE
* Exception Date-Times
* RFC 5545 iCalendar 3.8.5.1, page 117.
*
* This property defines the list of DATE-TIME exceptions for
* recurring events, to-dos, journal entries, or time zone definitions.
*/
public List getExceptionDates() { return exceptionDates; }
private List exceptionDates;
public void setExceptionDates(List exceptionDates)
{
if (this.exceptionDates != null)
{
this.exceptionDates.forEach(e -> orderChild(e, null)); // remove old elements
}
this.exceptionDates = exceptionDates;
if (exceptionDates != null)
{
exceptionDates.forEach(e -> orderChild(e)); // order new elements
}
}
public T withExceptionDates(List exceptions)
{
if (getExceptionDates() == null)
{
setExceptionDates(new ArrayList<>());
}
getExceptionDates().addAll(exceptions);
if (exceptions != null)
{
exceptions.forEach(c -> orderChild(c));
}
return (T) this;
}
public T withExceptionDates(String...exceptions)
{
List list = Arrays.stream(exceptions)
.map(c -> ExceptionDates.parse(c))
.collect(Collectors.toList());
return withExceptionDates(list);
}
public T withExceptionDates(Temporal...exceptions)
{
return withExceptionDates(new ExceptionDates(exceptions));
}
public T withExceptionDates(ExceptionDates...exceptions)
{
return withExceptionDates(Arrays.asList(exceptions));
}
/**
* LAST-MODIFIED
* RFC 5545, 3.8.7.3, page 138
*
* This property specifies the date and time that the
* information associated with the calendar component was last
* revised in the calendar store.
*
* Note: This is analogous to the modification date and time for a
* file in the file system.
*
* The value MUST be specified as a date with UTC time.
*
* Example:
* LAST-MODIFIED:19960817T133000Z
*/
@Override
public LastModified getDateTimeLastModified() { return lastModified; }
private LastModified lastModified;
@Override
public void setDateTimeLastModified(LastModified lastModified)
{
orderChild(this.lastModified, lastModified);
this.lastModified = lastModified;
}
// Other setters are default methods in interface
/**
* RDATE
* Recurrence Date-Times
* RFC 5545 iCalendar 3.8.5.2, page 120.
*
* This property defines the list of DATE-TIME values for
* recurring events, to-dos, journal entries, or time zone definitions.
*
* NOTE: DOESN'T CURRENTLY SUPPORT PERIOD VALUE TYPE
* */
@Override
public List getRecurrenceDates() { return recurrenceDates; }
private List recurrenceDates;
@Override
public void setRecurrenceDates(List recurrenceDates)
{
this.recurrenceDates = recurrenceDates;
if (recurrenceDates != null)
{
recurrenceDates.forEach(c -> orderChild(c));
}
}
/**
* RECURRENCE-ID: Recurrence Identifier
* RFC 5545 iCalendar 3.8.4.4 page 112
* The property value is the original value of the "DTSTART" property of the
* recurrence instance before an edit that changed the value.
*
* Example:
* RECURRENCE-ID;VALUE=DATE:19960401
*/
public RecurrenceId getRecurrenceId() { return recurrenceId; }
private RecurrenceId recurrenceId;
public void setRecurrenceId(RecurrenceId recurrenceId)
{
orderChild(this.recurrenceId, recurrenceId);
this.recurrenceId = recurrenceId;
}
public void setRecurrenceId(String recurrenceId) { setRecurrenceId(RecurrenceId.parse(recurrenceId)); }
public void setRecurrenceId(Temporal temporal)
{
if ((temporal instanceof LocalDate) || (temporal instanceof LocalDateTime) || (temporal instanceof ZonedDateTime))
{
if (getRecurrenceId() == null)
{
setRecurrenceId(new RecurrenceId(temporal));
} else
{
getRecurrenceId().setValue(temporal);
}
} else
{
throw new DateTimeException("Only LocalDate, LocalDateTime and ZonedDateTime supported. "
+ temporal.getClass().getSimpleName() + " is not supported");
}
}
public T withRecurrenceId(RecurrenceId recurrenceId)
{
setRecurrenceId(recurrenceId);
return (T) this;
}
public T withRecurrenceId(String recurrenceId)
{
setRecurrenceId(recurrenceId);
return (T) this;
}
public T withRecurrenceId(Temporal recurrenceId)
{
setRecurrenceId(recurrenceId);
return (T) this;
}
/** Checks if RecurrenceId has same date-time type as DateTimeStart. Returns String containing
* error message if there is a problem, null otherwise. */
String checkRecurrenceIdConsistency()
{
if (getRecurrenceId() != null && recurrenceParent() != null)
{
DateTimeType recurrenceIdType = DateTimeUtilities.DateTimeType.of(getRecurrenceId().getValue());
DateTimeType parentDateTimeStartType = DateTimeUtilities.DateTimeType.of(recurrenceParent().getDateTimeStart().getValue());
if (recurrenceIdType != parentDateTimeStartType)
{
return VPropertyElement.RECURRENCE_IDENTIFIER.toString() + ":RecurrenceId DateTimeType (" + recurrenceIdType +
") must be same as the type of its parent's DateTimeStart (" + parentDateTimeStartType + ")";
}
}
return null;
}
/**
* RELATED-TO:
* 3.8.4.5, RFC 5545 iCalendar, page 115
* This property is used to represent a relationship or reference between
* one calendar component and another. By default, the property value points to another
* calendar component's UID that has a PARENT relationship to the referencing object.
* This field is null unless the object contains as RECURRENCE-ID value.
*
* Example:
* RELATED-TO:[email protected]
*/
public List getRelatedTo() { return relatedTo; }
private List relatedTo;
public void setRelatedTo(List relatedTo)
{
if (this.relatedTo != null)
{
this.relatedTo.forEach(e -> orderChild(e, null)); // remove old elements
}
this.relatedTo = relatedTo;
if (relatedTo != null)
{
relatedTo.forEach(c -> orderChild(c));
}
}
public T withRelatedTo(List relatedTo)
{
if (getRelatedTo() == null)
{
setRelatedTo(new ArrayList<>());
}
getRelatedTo().addAll(relatedTo);
if (relatedTo != null)
{
relatedTo.forEach(c -> orderChild(c));
}
return (T) this;
}
public T withRelatedTo(String...relatedTo)
{
List list = Arrays.stream(relatedTo)
.map(c -> RelatedTo.parse(c))
.collect(Collectors.toList());
return withRelatedTo(list);
}
public T withRelatedTo(RelatedTo...relatedTo)
{
return withRelatedTo(Arrays.asList(relatedTo));
}
/**
* RRULE, Recurrence Rule
* RFC 5545 iCalendar 3.8.5.3, page 122.
* This property defines a rule or repeating pattern for recurring events,
* to-dos, journal entries, or time zone definitions
* If component is not repeating the value is null.
*
* Examples:
* RRULE:FREQ=DAILY;COUNT=10
* RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH
*/
@Override
public RecurrenceRule getRecurrenceRule() { return recurrenceRule; }
private RecurrenceRule recurrenceRule;
@Override
public void setRecurrenceRule(RecurrenceRule recurrenceRule)
{
orderChild(this.recurrenceRule, recurrenceRule);
this.recurrenceRule = recurrenceRule;
}
/**
* SEQUENCE:
* RFC 5545 iCalendar 3.8.7.4. page 138
* This property defines the revision sequence number of the calendar component within a sequence of revisions.
* Example: The following is an example of this property for a calendar
* component that was just created by the "Organizer":
*
* SEQUENCE:0
*
* The following is an example of this property for a calendar
* component that has been revised two different times by the
* "Organizer":
*
* SEQUENCE:2
*/
public Sequence getSequence() { return sequence; }
private Sequence sequence;
public void setSequence(Sequence sequence)
{
orderChild(this.sequence, sequence);
this.sequence = sequence;
}
public void setSequence(String sequence) { setSequence(Sequence.parse(sequence)); }
public void setSequence(Integer sequence) { setSequence(new Sequence(sequence)); }
public T withSequence(Sequence sequence)
{
if (getSequence() == null)
{
setSequence(sequence);
return (T) this;
} else
{
throw new IllegalArgumentException("Property can only occur once in the calendar component");
}
}
public T withSequence(Integer sequence)
{
if (getSequence() == null)
{
setSequence(sequence);
return (T) this;
} else
{
throw new IllegalArgumentException("Property can only occur once in the calendar component");
}
}
public T withSequence(String sequence)
{
if (getSequence() == null)
{
setSequence(sequence);
return (T) this;
} else
{
throw new IllegalArgumentException("Property can only occur once in the calendar component");
}
}
public void incrementSequence()
{
if (getSequence() != null)
{
setSequence(getSequence().getValue()+1);
} else
{
setSequence(1);
}
}
/**
* STATUS
* RFC 5545 iCalendar 3.8.1.11. page 92
*
* This property defines the overall status or confirmation for the calendar component.
*
* Example:
* STATUS:TENTATIVE
*/
public Status getStatus() { return status; }
private Status status;
public void setStatus(Status status)
{
orderChild(this.status, status);
this.status = status;
}
public void setStatus(String status) { setStatus(Status.parse(status)); }
public void setStatus(StatusType status) { setStatus(new Status(status)); }
public T withStatus(Status status)
{
setStatus(status);
return (T) this;
}
public T withStatus(StatusType status)
{
setStatus(status);
return (T) this;
}
public T withStatus(String status)
{
setStatus(status);
return (T) this;
}
/**
* SUMMARY
* RFC 5545 iCalendar 3.8.1.12. page 93
*
* This property defines a short summary or subject for the calendar component.
*
* Example:
* SUMMARY:Department Party
*/
@Override
public Summary getSummary() { return summary; }
private Summary summary;
@Override
public void setSummary(Summary summary)
{
orderChild(this.summary, summary);
this.summary = summary;
}
@Override
void dateTimeStartListenerHook()
{
super.dateTimeStartListenerHook();
String reccurenceIDErrorString = checkRecurrenceIdConsistency();
if (reccurenceIDErrorString != null)
{
throw new RuntimeException(reccurenceIDErrorString);
}
}
/*
* CONSTRUCTORS
*/
public VDisplayable() { super(); }
public VDisplayable(VDisplayable source)
{
super(source);
}
@Override
@Deprecated // need to move to VCalendar
public Stream streamRecurrences(Temporal start)
{
// get stream with recurrence rule (RRULE) and recurrence date (RDATE)
Stream inStream = VRepeatable.super.streamRecurrences(start);
// assign temporal comparator to match start type
final Comparator temporalComparator = DateTimeUtilities.getTemporalComparator(start);
// Handle Recurrence IDs
final Stream stream2;
List> children = recurrenceChildren();
if (children != null)
{
// If present, remove recurrence ID original values
List recurrenceIDTemporals = recurrenceChildren()
.stream()
.map(c -> c.getRecurrenceId().getValue())
.collect(Collectors.toList());
stream2 = inStream.filter(t -> ! recurrenceIDTemporals.contains(t));
} else
{
stream2 = inStream;
}
// If present, remove exceptions
final Stream stream3;
if (getExceptionDates() != null)
{
List exceptions = getExceptionDates()
.stream()
.flatMap(r -> r.getValue().stream())
.map(v -> v)
.sorted(temporalComparator)
.collect(Collectors.toList());
stream3 = stream2.filter(d -> ! exceptions.contains(d));
} else
{
stream3 = stream2;
}
if (getRecurrenceRule() == null)
{
return stream3; // no cache is no recurrence rule
}
if (getRecurrenceRule().getValue().getCount() == null)
{
return recurrenceCache().makeCache(stream3); // make cache of start date/times
} else
{ // if RRULE has COUNT must start at DTSTART
return stream3;
}
}
/*
* RECURRENCE STREAMER
* produces recurrence set
*/
private RecurrenceRuleCache recurrenceCache = new RecurrenceRuleCache(this);
@Override
public RecurrenceRuleCache recurrenceCache() { return recurrenceCache; }
/*
* RECURRENCE CHILDREN - (RECURRENCE-IDs AND MATCHING UID)
*/
public List> recurrenceChildren()
{
if ((getParent() != null) && (getRecurrenceId() == null))
{
UniqueIdentifier myUid = getUniqueIdentifier();
return calendarList()
.stream()
.map(c -> (VDisplayable>) c)
.filter(c -> ! (c == this))
.filter(c -> c.getUniqueIdentifier().equals(myUid))
.filter(c -> c.getRecurrenceId() != null)
.collect(Collectors.toList());
} else
{
return Collections.emptyList();
}
}
/*
* RECURRENCE PARENT - (the VComponent with matching UID and no RECURRENCEID)
*/
public VDisplayable> recurrenceParent()
{
if (getParent() != null && (getRecurrenceId() != null))
{
UniqueIdentifier myUid = getUniqueIdentifier();
@SuppressWarnings("rawtypes")
Optional recurrenceParent = calendarList()
.stream()
.map(c -> (VDisplayable) c)
.filter(c -> ! (c == this))
.filter(c -> c.getUniqueIdentifier().equals(myUid))
.filter(c -> c.getRecurrenceId() == null)
.findAny();
return (recurrenceParent.isPresent()) ? recurrenceParent.get() : null;
} else
{
return null;
}
}
/** returns list of orphaned recurrence components due to a change. These
* components should be deleted */
public List> orphanedRecurrenceChildren()
{
boolean isRecurrenceParent = getRecurrenceId() == null;
if (isRecurrenceParent)
{
VCalendar vCalendar = (VCalendar) getParent();
if (vCalendar != null)
{
final UniqueIdentifier uid = getUniqueIdentifier();
return calendarList()
.stream()
.map(v -> (VDisplayable>) v)
.filter(v -> v.getUniqueIdentifier().equals(uid))
.filter(v -> v.getRecurrenceId() != null)
.filter(v ->
{
Temporal myRecurrenceID = v.getRecurrenceId().getValue();
Temporal cacheStart = recurrenceCache().getClosestStart(myRecurrenceID);
Temporal nextRecurrenceDateTime = getRecurrenceRule().getValue()
.streamRecurrences(cacheStart)
.filter(t -> ! DateTimeUtilities.isBefore(t, myRecurrenceID))
.findFirst()
.orElseGet(() -> null);
return ! Objects.equals(nextRecurrenceDateTime, myRecurrenceID);
})
.collect(Collectors.toList());
}
}
return Collections.emptyList();
}
@Override
public List errors()
{
List errors = super.errors();
String reccurenceIDErrorString = checkRecurrenceIdConsistency();
if (reccurenceIDErrorString != null)
{
errors.add(reccurenceIDErrorString);
}
// Tests from Repeatable
errors.addAll(VRepeatableBase.errorsRepeatable(this));
errors.addAll(VRepeatableBase.errorsRecurrence(getExceptionDates(), getDateTimeStart()));
errors.addAll(VRepeatableBase.errorsRecurrence(getRecurrenceDates(), getDateTimeStart()));
return errors;
}
/** Erase all date/time properties such as DTSTART, DTEND, DURATION, and DUE (which ever exist). This
* is necessary to prepare a CANCEL iTIP message for one recurrence instance. */
public void eraseDateTimeProperties()
{
setDateTimeStart((DateTimeStart) null);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy