Skip to content

Nonstandard Components

Mike Angstadt edited this page Jun 24, 2017 · 2 revisions

iCalendar objects can contain non-standard components that are not part of the iCal specification. These are called "experimental" components.

The iCalendar object below contains an example of such a component. It is called "X-PARTY" (note that the names of experimental components must begin with "X-").

BEGIN:VCALENDAR
PRODID:-//Company//Application//EN
VERSION:2.0
BEGIN:X-PARTY
DTSTART:20130625T200000Z
DTEND:20130626T020000Z
X-DJ:Jonny D
END:X-PARTY
END:VCALENDAR

1 Retrieving Experimental Components

To get an experimental component, call the getExperimentalComponent(String) method. This returns the first component with that name, in the form of a RawComponent object. Since the DTSTART and DTEND properties are standard properties (part of the iCal spec), they can be retrieved by calling the getProperty(Class) method. The experimental property X-DJ can be retrieved by calling getExperimentalProperty(String).

ICalendar ical = ...
RawComponent party = ical.getExperimentalComponent("X-PARTY");
DateStart start = party.getProperty(DateStart.class);
DateEnd end = party.getProperty(DateEnd.class);
RawProperty dj = party.getExperimentalProperty("X-DJ");

If there is more than one instance of an experimental component, the getExperimentalComponents(String) method can be called to retrieve all instances:

ICalendar ical = ...
List<RawComponent> parties = ical.getExperimentalComponents("X-PARTY");

2 Creating Experimental Components

To add an experimental component to an iCalendar object, call the addExperimentalComponent(String) method. This method (1) creates a RawComponent object, (2) adds it to the iCalendar object, and (3) returns the RawComponent object. Returning the created object allows you to add properties and other sub-components to it.

ICalendar ical = new ICalendar();
RawComponent party = ical.addExperimentalComponent("X-PARTY");
java.util.Date start = ...
java.util.Date end = ...
party.addProperty(new DateStart(start));
party.addProperty(new DateEnd(end));
party.addExperimentalProperty("X-DJ", "Johnny D");

3 Creating a plugin

biweekly supports a plugin system that allows you to marshal and unmarshal experimental components into Java objects. It requires the creation of a component class and a scribe class. This system can also be used to override the scribes of the standard components, if need be.

The example below will create component and scribe classes for our "X-PARTY" example.

3.1 Component class

The component class is a POJO that extends ICalComponent.

public class Party extends ICalComponent {
  public DateStart getDateStart() {
    return getProperty(DateStart.class);
  }
  public void setDateStart(DateStart dateStart) {
    setProperty(DateStart.class, dateStart);
  }

  public DateEnd getDateEnd() {
    return getProperty(DateEnd.class);
  }
  public void setDateEnd(DateEnd dateEnd) {
    setProperty(DateEnd.class, dateEnd);
  }

  public String getDj(){
    RawProperty prop = getExperimentalProperty("X-DJ");
    return (prop == null) ? null : prop.getValue();
  }
  public void setDj(String dj) {
    setExperimentalProperty("X-DJ", dj);
  }

  @Override
  protected void validate(List<ICalComponent> parentComponents, List<String> warnings) {
    if (getDateStart() == null){
      warnings.add("Start date required.");
    }
  }
}

The getter/setter methods were added to make it easier to get/set the properties that the component is expected to have (they are not required).

The validate() method is also optional. It validates the contents of the object when ICalendar.validate() is called. The first parameter, parentComponents, contains the hierarchy of components to which the component belongs. For example, if the component is inside of a VEVENT component, which is inside of an VCALENDAR component, index 0 of the list would be an ICalendar object and index 1 of the list would be a VEvent object (the first element of the list will always be an ICalendar object). The validation warnings are added to the warnings list.

3.2 Scribe class

The scribe class is responsible for reading/writing the component to/from the actual data stream (such as an ".ics" file). It extends the ICalComponentScribe class.

public class PartyScribe extends ICalComponentScribe<Party> {
  public PartyScribe() {
    super(Party.class, "X-PARTY");
  }

  @Override
  public Party newInstance() {
    return new Party();
  }
}

The name of the component is passed into the parent constructor. The class must have a newInstance() method, which creates a new instance of the component object.

3.3 Plugin Usage

3.3.1 Reading

Before an iCalendar data stream is parsed, the scribe class must be registered with the reader object. Then, once an ICalendar object has been read, the instances of the component can be retrieved by calling the getComponent(Class) method.

String icalStr =
"BEGIN:VCALENDAR\r\n" +
  "PRODID:example.com\r\n" +
  "VERSION:2.0\r\n" +
  "BEGIN:X-PARTY\r\n" +
    "DTSTART:20130625T200000Z\r\n" +
    "DTEND:20130626T020000Z\r\n" +
    "X-DJ:Jonny D\r\n" +
  "END:X-PARTY\r\n" +
"END:VCALENDAR\r\n";

//using "Biweekly" class
ICalendar ical = Biweekly.parse(icalStr)
                         .register(new PartyScribe())
                         .first();
Party party = ical.getComponent(Party.class);

//using "ICalReader" class
ICalReader icalReader = new ICalReader(icalStr);
icalReader.registerScribe(new PartyScribe());
ICalendar ical = icalReader.readNext();
Party party = ical.getComponent(Party.class);

3.3.2 Writing

To add instances of the component class to the iCalendar object, call the addComponent() method on the component that the experimental component belongs to. Then, register the scribe class with the writer object and perform the write operation.

ICalendar ical = new ICalendar();

Party party = new Party();
java.util.Date start = ...
java.util.Date end = ...
party.setDateStart(new DateStart(start));
party.setDateEnd(new DateEnd(end));
party.setDj("Johnny D");
ical.addComponent(party);

//using "Biweekly" class
String icalStr = Biweekly.write(ical)
                         .register(new PartyScribe())
                         .go();

//using "ICalWriter" class
StringWriter sw = new StringWriter();
ICalWriter icalWriter = new ICalWriter(sw);
icalWriter.registerScribe(new PartyScribe());
icalWriter.write(ical);
String icalStr = sw.toString();