Updated biweekly

Refs mangstadt/biweekly#127
pull/214/head
M66B 6 months ago
parent 98bd662201
commit 9c4a81bda9

@ -12,6 +12,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product=
* Replaced [javadns](https://github.com/dnsjava/dnsjava) by [MiniDNS](https://github.com/MiniDNS/minidns)
* Added account/identity options to enforce DNSSEC and/or DANE, see [the FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#faq202)
* Small improvements and minor bug fixes
* Updated [biweekly](https://github.com/mangstadt/biweekly)
* Updated [translations](https://crowdin.com/project/open-source-email)
Preview/test versions are [available here](https://bitbucket.org/M66B/fairemail-test/downloads/).

@ -553,7 +553,7 @@ dependencies {
def openpgp_version = "12.0"
def badge_version = "1.1.22"
def bugsnag_version = "6.1.0"
def biweekly_version = "0.6.7"
def biweekly_version = "0.6.8"
def vcard_version = "0.12.1"
def relinker_version = "1.4.5"
def markwon_version = "4.6.2"
@ -742,12 +742,12 @@ dependencies {
// https://github.com/mangstadt/biweekly
// https://mvnrepository.com/artifact/net.sf.biweekly/biweekly
//implementation("net.sf.biweekly:biweekly:$biweekly_version") {
// exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
// exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
//}
api "com.fasterxml.jackson.core:jackson-core:2.14.2"
api "com.fasterxml.jackson.core:jackson-databind:2.14.2"
implementation("net.sf.biweekly:biweekly:$biweekly_version") {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
}
//api "com.fasterxml.jackson.core:jackson-core:2.14.2"
//api "com.fasterxml.jackson.core:jackson-databind:2.14.2"
// https://github.com/mangstadt/ez-vcard
implementation("com.googlecode.ez-vcard:ez-vcard:$vcard_version") {

@ -12,6 +12,7 @@ For support you can use [the contact form](https://contact.faircode.eu/?product=
* Replaced [javadns](https://github.com/dnsjava/dnsjava) by [MiniDNS](https://github.com/MiniDNS/minidns)
* Added account/identity options to enforce DNSSEC and/or DANE, see [the FAQ](https://github.com/M66B/FairEmail/blob/master/FAQ.md#faq202)
* Small improvements and minor bug fixes
* Updated [biweekly](https://github.com/mangstadt/biweekly)
* Updated [translations](https://crowdin.com/project/open-source-email)
Preview/test versions are [available here](https://bitbucket.org/M66B/fairemail-test/downloads/).

@ -1,443 +0,0 @@
package biweekly;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Properties;
import org.w3c.dom.Document;
import biweekly.io.chain.ChainingJsonParser;
import biweekly.io.chain.ChainingJsonStringParser;
import biweekly.io.chain.ChainingJsonWriter;
import biweekly.io.chain.ChainingTextParser;
import biweekly.io.chain.ChainingTextStringParser;
import biweekly.io.chain.ChainingTextWriter;
import biweekly.io.chain.ChainingXmlMemoryParser;
import biweekly.io.chain.ChainingXmlParser;
import biweekly.io.chain.ChainingXmlWriter;
import biweekly.io.json.JCalReader;
import biweekly.io.json.JCalWriter;
import biweekly.io.text.ICalReader;
import biweekly.io.text.ICalWriter;
import biweekly.io.xml.XCalDocument;
import biweekly.util.IOUtils;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Contains static chaining factory methods for reading/writing iCalendar
* objects.
* </p>
*
* <p>
* <b>Writing an iCalendar object</b>
* </p>
*
* <pre class="brush:java">
* ICalendar ical = new ICalendar();
*
* //string
* String icalString = Biweekly.write(ical).go();
*
* //file
* File file = new File("meeting.ics");
* Biweekly.write(ical).go(file);
*
* //output stream
* OutputStream out = ...
* Biweekly.write(ical).go(out);
* out.close();
*
* //writer (should be configured to use UTF-8 encoding)
* Writer writer = ...
* Biweekly.write(ical).go(writer);
* writer.close();
* </pre>
*
* <p>
* <b>Writing multiple iCalendar objects</b>
* </p>
*
* <pre class="brush:java">
* ICalendar ical1 = new ICalendar();
* ICalendar ical2 = new ICalendar();
*
* String icalString = Biweekly.write(ical1, ical2).go();
* </pre>
*
* <p>
* <b>Writing an XML-encoded iCalendar object (xCal)</b>
* </p>
*
* <pre class="brush:java">
* //Call writeXml() instead of write()
* ICalendar ical = new ICalendar();
* String xml = Biweekly.writeXml(ical).indent(2).go();
* </pre>
*
* <p>
* <b>Writing a JSON-encoded iCalendar object (jCal)</b>
* </p>
*
* <pre class="brush:java">
* //Call writeJson() instead of write()
* ICalendar ical = new ICalendar();
* String json = Biweekly.writeJson(ical).go();
* </pre>
*
* <p>
* <b>Reading an iCalendar object</b>
* </p>
*
* <pre class="brush:java">
* ICalendar ical;
*
* //string
* String icalStr = ...
* ical = Biweekly.parse(icalStr).first();
*
* //file
* File file = new File("meeting.ics");
* ical = Biweekly.parse(file).first();
*
* //input stream
* InputStream in = ...
* ical = Biweekly.parse(in).first();
* in.close();
*
* //reader (should be configured to read UTF-8)
* Reader reader = ...
* ical = Biweekly.parse(reader).first();
* reader.close();
* </pre>
*
* <p>
* <b>Reading multiple iCalendar objects</b>
* </p>
*
* <pre class="brush:java">
* String icalStr = ...
* List&lt;ICalendar&gt; icals = Biweekly.parse(icalStr).all();
* </pre>
*
* <p>
* <b>Reading an XML-encoded iCalendar object (xCal)</b>
* </p>
*
* <pre class="brush:java">
* //Call parseXml() instead of parse()
* String xml = ...
* ICalendar ical = Biweekly.parseXml(xml).first();
* </pre>
*
* <p>
* <b>Reading a JSON-encoded iCalendar object (Cal)</b>
* </p>
*
* <pre class="brush:java">
* //Call parseJson() instead of parse()
* String json = ...
* ICalendar ical = Biweekly.parseJson(json).first();
* </pre>
*
* <p>
* <b>Retrieving parser warnings</b>
* </p>
*
* <pre class="brush:java">
* String icalStr = ...
* List&lt;List&lt;String&gt;&gt; warnings = new ArrayList&lt;List&lt;String&gt;&gt;();
* List&lt;ICalendar&gt; icals = Biweekly.parse(icalStr).warnings(warnings).all();
* int i = 0;
* for (List&lt;String&gt; icalWarnings : warnings) {
* System.out.println("iCal #" + (i++) + " warnings:");
* for (String warning : icalWarnings) {
* System.out.println(warning);
* }
* }
* </pre>
*
* <p>
* The methods in this class make use of the following classes. These classes
* can be used if greater control over the read/write operation is required:
* </p>
*
* <table class="simpleTable">
* <caption>Classes used by this class</caption>
* <tr>
* <th></th>
* <th>Classes</th>
* <th>Supports<br>
* streaming?</th>
* </tr>
* <tr>
* <th>Text</th>
* <td>{@link ICalReader} / {@link ICalWriter}</td>
* <td>yes</td>
* </tr>
* <tr>
* <th>XML</th>
* <td>{@link XCalDocument}</td>
* <td>no</td>
* </tr>
* <tr>
* <th>JSON</th>
* <td>{@link JCalReader} / {@link JCalWriter}</td>
* <td>yes</td>
* </tr>
* </table>
* @author Michael Angstadt
*/
public final class Biweekly {
/**
* The version of the library.
*/
public static final String VERSION;
/**
* The Maven group ID.
*/
public static final String GROUP_ID;
/**
* The Maven artifact ID.
*/
public static final String ARTIFACT_ID;
/**
* The project webpage.
*/
public static final String URL;
static {
InputStream in = null;
try {
in = Biweekly.class.getResourceAsStream("/biweekly/biweekly.properties");
Properties props = new Properties();
props.load(in);
VERSION = props.getProperty("version");
GROUP_ID = props.getProperty("groupId");
ARTIFACT_ID = props.getProperty("artifactId");
URL = props.getProperty("url");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(in);
}
}
/**
* Parses an iCalendar object string.
* @param ical the iCalendar data
* @return chainer object for completing the parse operation
*/
public static ChainingTextStringParser parse(String ical) {
return new ChainingTextStringParser(ical);
}
/**
* Parses an iCalendar file.
* @param file the iCalendar file
* @return chainer object for completing the parse operation
*/
public static ChainingTextParser<ChainingTextParser<?>> parse(File file) {
return new ChainingTextParser<ChainingTextParser<?>>(file);
}
/**
* Parses an iCalendar data stream.
* @param in the input stream
* @return chainer object for completing the parse operation
*/
public static ChainingTextParser<ChainingTextParser<?>> parse(InputStream in) {
return new ChainingTextParser<ChainingTextParser<?>>(in);
}
/**
* Parses an iCalendar data stream.
* @param reader the reader
* @return chainer object for completing the parse operation
*/
public static ChainingTextParser<ChainingTextParser<?>> parse(Reader reader) {
return new ChainingTextParser<ChainingTextParser<?>>(reader);
}
/**
* Writes multiple iCalendar objects to a data stream.
* @param icals the iCalendar objects to write
* @return chainer object for completing the write operation
*/
public static ChainingTextWriter write(ICalendar... icals) {
return write(Arrays.asList(icals));
}
/**
* Writes multiple iCalendar objects to a data stream.
* @param icals the iCalendar objects to write
* @return chainer object for completing the write operation
*/
public static ChainingTextWriter write(Collection<ICalendar> icals) {
return new ChainingTextWriter(icals);
}
/**
* Parses an xCal document (XML-encoded iCalendar objects) from a string.
* @param xml the XML string
* @return chainer object for completing the parse operation
*/
public static ChainingXmlMemoryParser parseXml(String xml) {
return new ChainingXmlMemoryParser(xml);
}
/**
* Parses an xCal document (XML-encoded iCalendar objects) from a file.
* @param file the XML file
* @return chainer object for completing the parse operation
*/
public static ChainingXmlParser<ChainingXmlParser<?>> parseXml(File file) {
return new ChainingXmlParser<ChainingXmlParser<?>>(file);
}
/**
* Parses an xCal document (XML-encoded iCalendar objects) from an input
* stream.
* @param in the input stream
* @return chainer object for completing the parse operation
*/
public static ChainingXmlParser<ChainingXmlParser<?>> parseXml(InputStream in) {
return new ChainingXmlParser<ChainingXmlParser<?>>(in);
}
/**
* <p>
* Parses an xCal document (XML-encoded iCalendar objects) from a reader.
* </p>
* <p>
* Note that use of this method is discouraged. It ignores the character
* encoding that is defined within the XML document itself, and should only
* be used if the encoding is undefined or if the encoding needs to be
* ignored for whatever reason. The {@link #parseXml(InputStream)} method
* should be used instead, since it takes the XML document's character
* encoding into account when parsing.
* </p>
* @param reader the reader
* @return chainer object for completing the parse operation
*/
public static ChainingXmlParser<ChainingXmlParser<?>> parseXml(Reader reader) {
return new ChainingXmlParser<ChainingXmlParser<?>>(reader);
}
/**
* Parses an xCal document (XML-encoded iCalendar objects).
* @param document the XML document
* @return chainer object for completing the parse operation
*/
public static ChainingXmlMemoryParser parseXml(Document document) {
return new ChainingXmlMemoryParser(document);
}
/**
* Writes an xCal document (XML-encoded iCalendar objects).
* @param icals the iCalendar object(s) to write
* @return chainer object for completing the write operation
*/
public static ChainingXmlWriter writeXml(ICalendar... icals) {
return writeXml(Arrays.asList(icals));
}
/**
* Writes an xCal document (XML-encoded iCalendar objects).
* @param icals the iCalendar objects to write
* @return chainer object for completing the write operation
*/
public static ChainingXmlWriter writeXml(Collection<ICalendar> icals) {
return new ChainingXmlWriter(icals);
}
/**
* Parses a jCal data stream (JSON-encoded iCalendar objects).
* @param json the JSON data
* @return chainer object for completing the parse operation
*/
public static ChainingJsonStringParser parseJson(String json) {
return new ChainingJsonStringParser(json);
}
/**
* Parses a jCal data stream (JSON-encoded iCalendar objects).
* @param file the JSON file
* @return chainer object for completing the parse operation
*/
public static ChainingJsonParser<ChainingJsonParser<?>> parseJson(File file) {
return new ChainingJsonParser<ChainingJsonParser<?>>(file);
}
/**
* Parses a jCal data stream (JSON-encoded iCalendar objects).
* @param in the input stream
* @return chainer object for completing the parse operation
*/
public static ChainingJsonParser<ChainingJsonParser<?>> parseJson(InputStream in) {
return new ChainingJsonParser<ChainingJsonParser<?>>(in);
}
/**
* Parses a jCal data stream (JSON-encoded iCalendar objects).
* @param reader the reader
* @return chainer object for completing the parse operation
*/
public static ChainingJsonParser<ChainingJsonParser<?>> parseJson(Reader reader) {
return new ChainingJsonParser<ChainingJsonParser<?>>(reader);
}
/**
* Writes an xCal document (XML-encoded iCalendar objects).
* @param icals the iCalendar object(s) to write
* @return chainer object for completing the write operation
*/
public static ChainingJsonWriter writeJson(ICalendar... icals) {
return writeJson(Arrays.asList(icals));
}
/**
* Writes an xCal document (XML-encoded iCalendar objects).
* @param icals the iCalendar objects to write
* @return chainer object for completing the write operation
*/
public static ChainingJsonWriter writeJson(Collection<ICalendar> icals) {
return new ChainingJsonWriter(icals);
}
private Biweekly() {
//hide
}
}

@ -1,222 +0,0 @@
package biweekly;
import java.util.Collection;
import biweekly.util.CaseClasses;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Defines the data type of a property's value.
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-29">RFC 5545
* p.29-50</a>
*/
public class ICalDataType {
private static final CaseClasses<ICalDataType, String> enums = new CaseClasses<ICalDataType, String>(ICalDataType.class) {
@Override
protected ICalDataType create(String value) {
return new ICalDataType(value);
}
@Override
protected boolean matches(ICalDataType dataType, String value) {
return dataType.name.equalsIgnoreCase(value);
}
};
/**
* Binary data (such as an image or word-processing document).
* @see <a href="http://tools.ietf.org/html/rfc5545#page-30">RFC 5545
* p.30-1</a>
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.18</a>
*/
public static final ICalDataType BINARY = new ICalDataType("BINARY");
/**
* Boolean value ("true" or "false").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-31">RFC 5545
* p.31</a>
*/
public static final ICalDataType BOOLEAN = new ICalDataType("BOOLEAN");
/**
* A URI containing a calendar user address (typically, a "mailto" URI).
* @see <a href="http://tools.ietf.org/html/rfc5545#page-30">RFC 5545
* p.30-1</a>
*/
public static final ICalDataType CAL_ADDRESS = new ICalDataType("CAL-ADDRESS");
/**
* The property value is located in a separate MIME entity (vCal 1.0 only).
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.17</a>
*/
public static final ICalDataType CONTENT_ID = new ICalDataType("CONTENT-ID"); //1.0 only
/**
* A date (for example, "2014-03-12").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-32">RFC 5545
* p.32</a>
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.16-7</a>
*/
public static final ICalDataType DATE = new ICalDataType("DATE");
/**
* A date/time value (for example, "2014-03-12 13:30:00").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-32">RFC 5545
* p.32-4</a>
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.16-7</a>
*/
public static final ICalDataType DATE_TIME = new ICalDataType("DATE-TIME");
/**
* A duration of time (for example, "2 hours, 30 minutes").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-35">RFC 5545
* p.35-6</a>
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.17</a>
*/
public static final ICalDataType DURATION = new ICalDataType("DURATION");
/**
* A floating point value (for example, "3.14")
* @see <a href="http://tools.ietf.org/html/rfc5545#page-36">RFC 5545
* p.36</a>
*/
public static final ICalDataType FLOAT = new ICalDataType("FLOAT");
/**
* An integer value (for example, "42")
* @see <a href="http://tools.ietf.org/html/rfc5545#page-37">RFC 5545
* p.37</a>
*/
public static final ICalDataType INTEGER = new ICalDataType("INTEGER");
/**
* A period of time (for example, "October 3 through October 5").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-37-8">RFC 5545
* p.37-8</a>
*/
public static final ICalDataType PERIOD = new ICalDataType("PERIOD");
/**
* A recurrence rule (for example, "every Monday at 2pm").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-38">RFC 5545
* p.38-45</a>
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.18-23</a>
*/
public static final ICalDataType RECUR = new ICalDataType("RECUR");
/**
* A plain text value.
* @see <a href="http://tools.ietf.org/html/rfc5545#page-45">RFC 5545
* p.45-6</a>
*/
public static final ICalDataType TEXT = new ICalDataType("TEXT");
/**
* A time value (for example, "2pm").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-47">RFC 5545
* p.47-8</a>
*/
public static final ICalDataType TIME = new ICalDataType("TIME");
/**
* A URI value.
* @see <a href="http://tools.ietf.org/html/rfc5545#page-49">RFC 5545
* p.49</a>
*/
public static final ICalDataType URI = new ICalDataType("URI");
/**
* A URL (for example, "http://example.com/picture.jpg") (vCal 1.0 only).
* @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.17-8</a>
*/
public static final ICalDataType URL = new ICalDataType("URL");
/**
* A UTC-offset (for example, "+0500").
* @see <a href="http://tools.ietf.org/html/rfc5545#page-49">RFC 5545
* p.49-50</a>
*/
public static final ICalDataType UTC_OFFSET = new ICalDataType("UTC-OFFSET");
private final String name;
private ICalDataType(String name) {
this.name = name;
}
/**
* Gets the name of the data type.
* @return the name of the data type (e.g. "TEXT")
*/
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
/**
* Searches for a parameter value that is defined as a static constant in
* this class.
* @param value the parameter value
* @return the object or null if not found
*/
public static ICalDataType find(String value) {
if ("CID".equalsIgnoreCase(value)) {
//"CID" is an alias for "CONTENT-ID" (vCal 1.0, p.17)
return CONTENT_ID;
}
return enums.find(value);
}
/**
* Searches for a parameter value and creates one if it cannot be found. All
* objects are guaranteed to be unique, so they can be compared with
* {@code ==} equality.
* @param value the parameter value
* @return the object
*/
public static ICalDataType get(String value) {
if ("CID".equalsIgnoreCase(value)) {
//"CID" is an alias for "CONTENT-ID" (vCal 1.0, p.17)
return CONTENT_ID;
}
return enums.get(value);
}
/**
* Gets all of the parameter values that are defined as static constants in
* this class.
* @return the parameter values
*/
public static Collection<ICalDataType> all() {
return enums.all();
}
}

@ -1,104 +0,0 @@
package biweekly;
import com.github.mangstadt.vinnie.SyntaxStyle;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Defines all supported versions of the iCalendar standard.
* @author Michael Angstadt
*/
public enum ICalVersion {
/**
* The original vCalendar specification.
* @see <a href="http://www.imc.org/pdi/pdiproddev.html">1.0 specs</a>
*/
V1_0("1.0", SyntaxStyle.OLD),
/**
* An older, deprecated version of the iCalendar specification (very similar
* to {@link #V2_0}).
* @see <a href="https://tools.ietf.org/html/rfc2445">RFC 2445</a>
*/
V2_0_DEPRECATED("2.0", SyntaxStyle.NEW),
/**
* The latest iCalendar specification.
* @see <a href="https://tools.ietf.org/html/rfc5545">RFC 5545</a>
*/
V2_0("2.0", SyntaxStyle.NEW);
private final String version;
private final SyntaxStyle syntaxStyle;
/**
* @param version the version number
*/
ICalVersion(String version, SyntaxStyle syntaxStyle) {
this.version = version;
this.syntaxStyle = syntaxStyle;
}
/**
* Gets the text representation of this version.
* @return the text representation
*/
public String getVersion() {
return version;
}
/**
* Gets the syntax style used by this version when writing to a plain-text
* data stream.
* @return the syntax style
*/
public SyntaxStyle getSyntaxStyle() {
return syntaxStyle;
}
/**
* Gets a {@link ICalVersion} instance based on the given version number.
* @param version the version number (e.g. "2.0")
* @return the object or null if not found
*/
public static ICalVersion get(String version) {
if (V1_0.version.equals(version)) {
return V1_0;
}
if (V2_0.version.equals(version)) {
return V2_0;
}
return null;
}
@Override
public String toString() {
if (this == V2_0_DEPRECATED) {
return version + " (obsoleted)";
}
return version;
}
}

File diff suppressed because it is too large Load Diff

@ -1,100 +0,0 @@
package biweekly;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Singleton for accessing the i18n resource bundle.
* @author Michael Angstadt
*/
public enum Messages {
INSTANCE;
private final transient ResourceBundle messages;
Messages() {
messages = ResourceBundle.getBundle("biweekly.messages");
}
/**
* Gets a validation warning message.
* @param code the message code
* @param args the message arguments
* @return the message
*/
public String getValidationWarning(int code, Object... args) {
return getMessage("validate." + code, args);
}
/**
* Gets a parser warning message.
* @param code the message code
* @param args the message arguments
* @return the message
*/
public String getParseMessage(int code, Object... args) {
return getMessage("parse." + code, args);
}
/**
* Gets an exception message.
* @param code the message code
* @param args the message arguments
* @return the message or null if not found
*/
public String getExceptionMessage(int code, Object... args) {
return getMessage("exception." + code, args);
}
/**
* Builds an {@link IllegalArgumentException} from an exception message.
* @param code the message code
* @param args the message arguments
* @return the exception or null if the message was not found
*/
public IllegalArgumentException getIllegalArgumentException(int code, Object... args) {
String message = getExceptionMessage(code, args);
return (message == null) ? null : new IllegalArgumentException(message);
}
/**
* Gets a message.
* @param key the message key
* @param args the message arguments
* @return the message or null if not found
*/
public String getMessage(String key, Object... args) {
try {
String message = messages.getString(key);
return MessageFormat.format(message, args);
} catch (MissingResourceException e) {
return null;
}
}
}

@ -1,78 +0,0 @@
package biweekly;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a validation warning.
* @author Michael Angstadt
*/
public class ValidationWarning {
private final Integer code;
private final String message;
/**
* Creates a new validation warning.
* @param code the warning message code
* @param args the warning message arguments
*/
public ValidationWarning(int code, Object... args) {
this.code = code;
this.message = Messages.INSTANCE.getValidationWarning(code, args);
}
/**
* Creates a new validation warning.
* @param message the warning message
*/
public ValidationWarning(String message) {
this.code = null;
this.message = message;
}
/**
* Gets the validation warning code.
* @return the warning code or null if no code was specified
*/
public Integer getCode() {
return code;
}
/**
* Gets the validation warning message.
* @return the warning message
*/
public String getMessage() {
return message;
}
@Override
public String toString() {
if (code == null) {
return message;
}
return "(" + code + ") " + message;
}
}

@ -1,289 +0,0 @@
package biweekly;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import biweekly.ValidationWarnings.WarningsGroup;
import biweekly.component.ICalComponent;
import biweekly.property.ICalProperty;
import biweekly.util.StringUtils;
import biweekly.util.StringUtils.JoinCallback;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Holds the validation warnings of an iCalendar object.
* </p>
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* //validate an iCalendar object
* ValidationWarnings warnings = ical.validate();
*
* //print all warnings to a string:
* System.out.println(warnings.toString());
* //sample output:
* //[ICalendar]: ProductId is not set (it is a required property).
* //[ICalendar &gt; VEvent &gt; DateStart]: DateStart must come before DateEnd.
* //[ICalendar &gt; VEvent &gt; VAlarm]: The trigger must specify which date field its duration is relative to.
*
* //iterate over each warnings group
* //this gives you access to the property/component object and its parent components
* for (WarningsGroup group : warnings) {
* ICalProperty prop = group.getProperty();
* if (prop == null) {
* //then it was a component that caused the warnings
* ICalComponent comp = group.getComponent();
* }
*
* //get parent components
* List&lt;ICalComponent&gt; hierarchy = group.getComponentHierarchy();
*
* //get warning messages
* List&lt;String&gt; messages = group.getMessages();
* }
*
* //you can also get the warnings of specific properties/components
* List&lt;WarningsGroup&gt; dtstartWarnings = warnings.getByProperty(DateStart.class);
* List&lt;WarningsGroup&gt; veventWarnings = warnings.getByComponent(VEvent.class);
* </pre>
* @author Michael Angstadt
* @see ICalendar#validate(ICalVersion)
*/
public class ValidationWarnings implements Iterable<WarningsGroup> {
private final List<WarningsGroup> warnings;
/**
* Creates a new validation warnings list.
* @param warnings the validation warnings
*/
public ValidationWarnings(List<WarningsGroup> warnings) {
this.warnings = warnings;
}
/**
* Gets all validation warnings of a given property.
* @param propertyClass the property (e.g. {@code DateStart.class})
* @return the validation warnings
*/
public List<WarningsGroup> getByProperty(Class<? extends ICalProperty> propertyClass) {
List<WarningsGroup> warnings = new ArrayList<WarningsGroup>();
for (WarningsGroup group : this.warnings) {
ICalProperty property = group.getProperty();
if (property == null) {
continue;
}
if (propertyClass == property.getClass()) {
warnings.add(group);
}
}
return warnings;
}
/**
* Gets all validation warnings of a given component.
* @param componentClass the component (e.g. {@code VEvent.class})
* @return the validation warnings
*/
public List<WarningsGroup> getByComponent(Class<? extends ICalComponent> componentClass) {
List<WarningsGroup> warnings = new ArrayList<WarningsGroup>();
for (WarningsGroup group : this.warnings) {
ICalComponent component = group.getComponent();
if (component == null) {
continue;
}
if (componentClass == component.getClass()) {
warnings.add(group);
}
}
return warnings;
}
/**
* Gets all the validation warnings.
* @return the validation warnings
*/
public List<WarningsGroup> getWarnings() {
return warnings;
}
/**
* Determines whether there are any validation warnings.
* @return true if there are none, false if there are one or more
*/
public boolean isEmpty() {
return warnings.isEmpty();
}
/**
* <p>
* Outputs all validation warnings as a newline-delimited string. For
* example:
* </p>
*
* <pre>
* [ICalendar]: ProductId is not set (it is a required property).
* [ICalendar &gt; VEvent &gt; DateStart]: DateStart must come before DateEnd.
* [ICalendar &gt; VEvent &gt; VAlarm]: The trigger must specify which date field its duration is relative to.
* </pre>
*/
@Override
public String toString() {
return StringUtils.join(warnings, StringUtils.NEWLINE);
}
/**
* Iterates over each warning group (same as calling
* {@code getWarnings().iterator()}).
* @return the iterator
*/
public Iterator<WarningsGroup> iterator() {
return warnings.iterator();
}
/**
* Holds the validation warnings of a property or component.
* @author Michael Angstadt
*/
public static class WarningsGroup {
private final ICalProperty property;
private final ICalComponent component;
private final List<ICalComponent> componentHierarchy;
private final List<ValidationWarning> warnings;
/**
* Creates a new set of validation warnings for a property.
* @param property the property that caused the warnings
* @param componentHierarchy the hierarchy of components that the
* property belongs to
* @param warning the warnings
*/
public WarningsGroup(ICalProperty property, List<ICalComponent> componentHierarchy, List<ValidationWarning> warning) {
this(null, property, componentHierarchy, warning);
}
/**
* Creates a new set of validation warnings for a component.
* @param component the component that caused the warnings
* @param componentHierarchy the hierarchy of components that the
* component belongs to
* @param warning the warnings
*/
public WarningsGroup(ICalComponent component, List<ICalComponent> componentHierarchy, List<ValidationWarning> warning) {
this(component, null, componentHierarchy, warning);
}
private WarningsGroup(ICalComponent component, ICalProperty property, List<ICalComponent> componentHierarchy, List<ValidationWarning> warning) {
this.component = component;
this.property = property;
this.componentHierarchy = componentHierarchy;
this.warnings = warning;
}
/**
* Gets the property object that caused the validation warnings.
* @return the property object or null if a component caused the
* warnings.
*/
public ICalProperty getProperty() {
return property;
}
/**
* Gets the component object that caused the validation warnings.
* @return the component object or null if a property caused the
* warnings.
*/
public ICalComponent getComponent() {
return component;
}
/**
* Gets the hierarchy of components that the property or component
* belongs to.
* @return the component hierarchy
*/
public List<ICalComponent> getComponentHierarchy() {
return componentHierarchy;
}
/**
* Gets the warnings that belong to the property or component.
* @return the warnings
*/
public List<ValidationWarning> getWarnings() {
return warnings;
}
/**
* <p>
* Outputs each message in this warnings group as a newline-delimited
* string. Each line includes the component hierarchy and the name of
* the property/component. For example:
* </p>
*
* <pre>
* [ICalendar &gt; VEvent &gt; VAlarm]: Email alarms must have at least one attendee.
* [ICalendar &gt; VEvent &gt; VAlarm]: The trigger must specify which date field its duration is relative to.
* </pre>
*/
@Override
public String toString() {
final String prefix = "[" + buildPath() + "]: ";
return StringUtils.join(warnings, StringUtils.NEWLINE, new JoinCallback<ValidationWarning>() {
public void handle(StringBuilder sb, ValidationWarning warning) {
sb.append(prefix).append(warning);
}
});
}
private String buildPath() {
StringBuilder sb = new StringBuilder();
if (!componentHierarchy.isEmpty()) {
String delimitor = " > ";
StringUtils.join(componentHierarchy, delimitor, sb, new JoinCallback<ICalComponent>() {
public void handle(StringBuilder sb, ICalComponent component) {
sb.append(component.getClass().getSimpleName());
}
});
sb.append(delimitor);
}
Object obj = (property == null) ? component : property;
sb.append(obj.getClass().getSimpleName());
return sb.toString();
}
}
}

@ -1,22 +0,0 @@
Copyright (c) 2013-2021, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,67 +0,0 @@
package biweekly.component;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Defines the date range of a timezone's daylight savings time.
* </p>
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* VTimezone timezone = new VTimezone("Eastern Standard Time");
* DaylightSavingsTime daylight = new DaylightSavingsTime();
* DateTimeComponents components = new DateTimeComponents(1999, 4, 4, 2, 0, 0, false);
* daylight.setDateStart(components);
* daylight.setTimezoneOffsetFrom(-5, 0);
* daylight.setTimezoneOffsetTo(-4, 0);
* timezone.addDaylightSavingsTime(daylight);
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445 p.60-7</a>
*/
public class DaylightSavingsTime extends Observance {
public DaylightSavingsTime() {
//empty
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public DaylightSavingsTime(DaylightSavingsTime original) {
super(original);
}
@Override
public DaylightSavingsTime copy() {
return new DaylightSavingsTime(this);
}
}

@ -1,833 +0,0 @@
package biweekly.component;
import java.lang.reflect.Constructor;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.Messages;
import biweekly.ValidationWarnings.WarningsGroup;
import biweekly.ValidationWarning;
import biweekly.property.ICalProperty;
import biweekly.property.RawProperty;
import biweekly.property.Status;
import biweekly.util.ListMultimap;
import biweekly.util.StringUtils;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Base class for all iCalendar component classes.
* @author Michael Angstadt
*/
public abstract class ICalComponent {
protected final ListMultimap<Class<? extends ICalComponent>, ICalComponent> components;
protected final ListMultimap<Class<? extends ICalProperty>, ICalProperty> properties;
public ICalComponent() {
components = new ListMultimap<Class<? extends ICalComponent>, ICalComponent>();
properties = new ListMultimap<Class<? extends ICalProperty>, ICalProperty>();
}
/**
* Copy constructor. Performs a deep copy of the given component's
* properties and sub-components.
* @param original the component to make a copy of
*/
protected ICalComponent(ICalComponent original) {
this();
for (ICalProperty property : original.properties.values()) {
addProperty(property.copy());
}
for (ICalComponent component : original.components.values()) {
addComponent(component.copy());
}
}
/**
* Gets the first property of a given class.
* @param clazz the property class
* @param <T> the property class
* @return the property or null if not found
*/
public <T extends ICalProperty> T getProperty(Class<T> clazz) {
return clazz.cast(properties.first(clazz));
}
/**
* Gets all properties of a given class. Changes to the returned list will
* update the {@link ICalComponent} object, and vice versa.
* @param clazz the property class
* @param <T> the property class
* @return the properties
*/
public <T extends ICalProperty> List<T> getProperties(Class<T> clazz) {
return new ICalPropertyList<T>(clazz);
}
/**
* Gets all the properties associated with this component.
* @return the properties
*/
public ListMultimap<Class<? extends ICalProperty>, ICalProperty> getProperties() {
return properties;
}
/**
* Adds a property to this component.
* @param property the property to add
*/
public void addProperty(ICalProperty property) {
properties.put(property.getClass(), property);
}
/**
* Replaces all existing properties of the given property instance's class
* with the given property instance.
* @param property the property
* @return the replaced properties (this list is immutable)
*/
public List<ICalProperty> setProperty(ICalProperty property) {
return properties.replace(property.getClass(), property);
}
/**
* Replaces all existing properties of the given class with a single
* property instance. If the property instance is null, then all instances
* of that property will be removed.
* @param clazz the property class (e.g. "DateStart.class")
* @param property the property or null to remove all properties of the
* given class
* @param <T> the property class
* @return the replaced properties (this list is immutable)
*/
public <T extends ICalProperty> List<T> setProperty(Class<T> clazz, T property) {
List<ICalProperty> replaced = properties.replace(clazz, property);
return castList(replaced, clazz);
}
/**
* Removes a specific property instance from this component.
* @param property the property to remove
* @param <T> the property class
* @return true if it was removed, false if it wasn't found
*/
public <T extends ICalProperty> boolean removeProperty(T property) {
return properties.remove(property.getClass(), property);
}
/**
* Removes all properties of a given class from this component.
* @param clazz the class of the properties to remove (e.g.
* "DateStart.class")
* @param <T> the property class
* @return the removed properties (this list is immutable)
*/
public <T extends ICalProperty> List<T> removeProperties(Class<T> clazz) {
List<ICalProperty> removed = properties.removeAll(clazz);
return castList(removed, clazz);
}
/**
* Removes a specific sub-component instance from this component.
* @param component the component to remove
* @param <T> the component class
* @return true if it was removed, false if it wasn't found
*/
public <T extends ICalComponent> boolean removeComponent(T component) {
return components.remove(component.getClass(), component);
}
/**
* Removes all sub-components of the given class from this component.
* @param clazz the class of the components to remove (e.g. "VEvent.class")
* @param <T> the component class
* @return the removed components (this list is immutable)
*/
public <T extends ICalComponent> List<T> removeComponents(Class<T> clazz) {
List<ICalComponent> removed = components.removeAll(clazz);
return castList(removed, clazz);
}
/**
* Gets the first experimental property with a given name.
* @param name the property name (case insensitive, e.g. "X-ALT-DESC")
* @return the experimental property or null if none were found
*/
public RawProperty getExperimentalProperty(String name) {
for (RawProperty raw : getExperimentalProperties()) {
if (raw.getName().equalsIgnoreCase(name)) {
return raw;
}
}
return null;
}
/**
* Gets all experimental properties with a given name.
* @param name the property name (case insensitive, e.g. "X-ALT-DESC")
* @return the experimental properties (this list is immutable)
*/
public List<RawProperty> getExperimentalProperties(String name) {
/*
* Note: The returned list is not backed by the parent component because
* this would allow RawProperty objects without the specified name to be
* added to the list.
*/
List<RawProperty> toReturn = new ArrayList<RawProperty>();
for (RawProperty property : getExperimentalProperties()) {
if (property.getName().equalsIgnoreCase(name)) {
toReturn.add(property);
}
}
return Collections.unmodifiableList(toReturn);
}
/**
* Gets all experimental properties associated with this component. Changes
* to the returned list will update the {@link ICalComponent} object, and
* vice versa.
* @return the experimental properties
*/
public List<RawProperty> getExperimentalProperties() {
return getProperties(RawProperty.class);
}
/**
* Adds an experimental property to this component.
* @param name the property name (e.g. "X-ALT-DESC")
* @param value the property value
* @return the property object that was created
*/
public RawProperty addExperimentalProperty(String name, String value) {
return addExperimentalProperty(name, null, value);
}
/**
* Adds an experimental property to this component.
* @param name the property name (e.g. "X-ALT-DESC")
* @param dataType the property's data type or null if unknown
* @param value the property value
* @return the property object that was created
*/
public RawProperty addExperimentalProperty(String name, ICalDataType dataType, String value) {
RawProperty raw = new RawProperty(name, dataType, value);
addProperty(raw);
return raw;
}
/**
* Adds an experimental property to this component, removing all existing
* properties that have the same name.
* @param name the property name (e.g. "X-ALT-DESC")
* @param value the property value
* @return the property object that was created
*/
public RawProperty setExperimentalProperty(String name, String value) {
return setExperimentalProperty(name, null, value);
}
/**
* Adds an experimental property to this component, removing all existing
* properties that have the same name.
* @param name the property name (e.g. "X-ALT-DESC")
* @param dataType the property's data type or null if unknown
* @param value the property value
* @return the property object that was created
*/
public RawProperty setExperimentalProperty(String name, ICalDataType dataType, String value) {
removeExperimentalProperties(name);
return addExperimentalProperty(name, dataType, value);
}
/**
* Removes all experimental properties that have the given name.
* @param name the component name (e.g. "X-ALT-DESC")
* @return the removed properties (this list is immutable)
*/
public List<RawProperty> removeExperimentalProperties(String name) {
List<RawProperty> all = getExperimentalProperties();
List<RawProperty> toRemove = new ArrayList<RawProperty>();
for (RawProperty property : all) {
if (property.getName().equalsIgnoreCase(name)) {
toRemove.add(property);
}
}
all.removeAll(toRemove);
return Collections.unmodifiableList(toRemove);
}
/**
* Gets the first sub-component of a given class.
* @param clazz the component class
* @param <T> the component class
* @return the sub-component or null if not found
*/
public <T extends ICalComponent> T getComponent(Class<T> clazz) {
return clazz.cast(components.first(clazz));
}
/**
* Gets all sub-components of a given class. Changes to the returned list
* will update the parent component object, and vice versa.
* @param clazz the component class
* @param <T> the component class
* @return the sub-components
*/
public <T extends ICalComponent> List<T> getComponents(Class<T> clazz) {
return new ICalComponentList<T>(clazz);
}
/**
* Gets all the sub-components associated with this component.
* @return the sub-components
*/
public ListMultimap<Class<? extends ICalComponent>, ICalComponent> getComponents() {
return components;
}
/**
* Adds a sub-component to this component.
* @param component the component to add
*/
public void addComponent(ICalComponent component) {
components.put(component.getClass(), component);
}
/**
* Replaces all sub-components of a given class with the given component.
* @param component the component
* @return the replaced sub-components (this list is immutable)
*/
public List<ICalComponent> setComponent(ICalComponent component) {
return components.replace(component.getClass(), component);
}
/**
* Replaces all sub-components of a given class with the given component. If
* the component instance is null, then all instances of that component will
* be removed.
* @param clazz the component's class
* @param component the component or null to remove all components of the
* given class
* @param <T> the component class
* @return the replaced sub-components (this list is immutable)
*/
public <T extends ICalComponent> List<T> setComponent(Class<T> clazz, T component) {
List<ICalComponent> replaced = components.replace(clazz, component);
return castList(replaced, clazz);
}
/**
* Gets the first experimental sub-component with a given name.
* @param name the component name (case insensitive, e.g. "X-PARTY")
* @return the experimental component or null if none were found
*/
public RawComponent getExperimentalComponent(String name) {
for (RawComponent component : getExperimentalComponents()) {
if (component.getName().equalsIgnoreCase(name)) {
return component;
}
}
return null;
}
/**
* Gets all experimental sub-component with a given name.
* @param name the component name (case insensitive, e.g. "X-PARTY")
* @return the experimental components (this list is immutable)
*/
public List<RawComponent> getExperimentalComponents(String name) {
/*
* Note: The returned list is not backed by the parent component because
* this would allow RawComponent objects without the specified name to
* be added to the list.
*/
List<RawComponent> toReturn = new ArrayList<RawComponent>();
for (RawComponent component : getExperimentalComponents()) {
if (component.getName().equalsIgnoreCase(name)) {
toReturn.add(component);
}
}
return Collections.unmodifiableList(toReturn);
}
/**
* Gets all experimental sub-components associated with this component.
* Changes to the returned list will update the parent {@link ICalComponent}
* object, and vice versa.
* @return the experimental components
*/
public List<RawComponent> getExperimentalComponents() {
return getComponents(RawComponent.class);
}
/**
* Adds an experimental sub-component to this component.
* @param name the component name (e.g. "X-PARTY")
* @return the component object that was created
*/
public RawComponent addExperimentalComponent(String name) {
RawComponent raw = new RawComponent(name);
addComponent(raw);
return raw;
}
/**
* Adds an experimental sub-component to this component, removing all
* existing components that have the same name.
* @param name the component name (e.g. "X-PARTY")
* @return the component object that was created
*/
public RawComponent setExperimentalComponent(String name) {
removeExperimentalComponents(name);
return addExperimentalComponent(name);
}
/**
* Removes all experimental sub-components that have the given name.
* @param name the component name (e.g. "X-PARTY")
* @return the removed sub-components (this list is immutable)
*/
public List<RawComponent> removeExperimentalComponents(String name) {
List<RawComponent> all = getExperimentalComponents();
List<RawComponent> toRemove = new ArrayList<RawComponent>();
for (RawComponent property : all) {
if (property.getName().equalsIgnoreCase(name)) {
toRemove.add(property);
}
}
all.removeAll(toRemove);
return Collections.unmodifiableList(toRemove);
}
/**
* <p>
* Checks this component for data consistency problems or deviations from
* the specifications.
* </p>
* <p>
* The existence of validation warnings will not prevent the component
* object from being written to a data stream. Syntactically-correct output
* will still be produced. However, the consuming application may have
* trouble interpreting some of the data due to the presence of these
* warnings.
* </p>
* <p>
* These problems can largely be avoided by reading the Javadocs of the
* component and property classes, or by being familiar with the iCalendar
* standard.
* </p>
* @param hierarchy the hierarchy of components that the component belongs
* to
* @param version the version to validate against
* @see ICalendar#validate(List, ICalVersion)
* @return a list of warnings or an empty list if no problems were found
*/
public final List<WarningsGroup> validate(List<ICalComponent> hierarchy, ICalVersion version) {
List<WarningsGroup> warnings = new ArrayList<WarningsGroup>();
//validate this component
List<ValidationWarning> warningsBuf = new ArrayList<ValidationWarning>(0);
validate(hierarchy, version, warningsBuf);
if (!warningsBuf.isEmpty()) {
warnings.add(new WarningsGroup(this, hierarchy, warningsBuf));
}
//add this component to the hierarchy list
//copy the list so other validate() calls aren't effected
hierarchy = new ArrayList<ICalComponent>(hierarchy);
hierarchy.add(this);
//validate properties
for (ICalProperty property : properties.values()) {
List<ValidationWarning> propWarnings = property.validate(hierarchy, version);
if (!propWarnings.isEmpty()) {
warnings.add(new WarningsGroup(property, hierarchy, propWarnings));
}
}
//validate sub-components
for (ICalComponent component : components.values()) {
warnings.addAll(component.validate(hierarchy, version));
}
return warnings;
}
/**
* <p>
* Checks the component for data consistency problems or deviations from the
* spec.
* </p>
* <p>
* This method should be overridden by child classes that wish to provide
* validation logic. The default implementation of this method does nothing.
* </p>
* @param components the hierarchy of components that the component belongs
* to
* @param version the version to validate against
* @param warnings the list to add the warnings to
*/
protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) {
//do nothing
}
/**
* Utility method for validating that there is exactly one instance of each
* of the given properties.
* @param warnings the list to add the warnings to
* @param classes the properties to check
*/
protected void checkRequiredCardinality(List<ValidationWarning> warnings, Class<? extends ICalProperty>... classes) {
for (Class<? extends ICalProperty> clazz : classes) {
List<? extends ICalProperty> props = getProperties(clazz);
if (props.isEmpty()) {
warnings.add(new ValidationWarning(2, clazz.getSimpleName()));
continue;
}
if (props.size() > 1) {
warnings.add(new ValidationWarning(3, clazz.getSimpleName()));
continue;
}
}
}
/**
* Utility method for validating that there is no more than one instance of
* each of the given properties.
* @param warnings the list to add the warnings to
* @param classes the properties to check
*/
protected void checkOptionalCardinality(List<ValidationWarning> warnings, Class<? extends ICalProperty>... classes) {
for (Class<? extends ICalProperty> clazz : classes) {
List<? extends ICalProperty> props = getProperties(clazz);
if (props.size() > 1) {
warnings.add(new ValidationWarning(3, clazz.getSimpleName()));
continue;
}
}
}
/**
* Utility method for validating the {@link Status} property of a component.
* @param warnings the list to add the warnings to
* @param allowed the valid statuses
*/
protected void checkStatus(List<ValidationWarning> warnings, Status... allowed) {
Status actual = getProperty(Status.class);
if (actual == null) {
return;
}
List<String> allowedValues = new ArrayList<String>(allowed.length);
for (Status status : allowed) {
String value = status.getValue().toLowerCase();
allowedValues.add(value);
}
String actualValue = actual.getValue().toLowerCase();
if (!allowedValues.contains(actualValue)) {
warnings.add(new ValidationWarning(13, actual.getValue(), allowedValues));
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(0, sb);
return sb.toString();
}
/**
* <p>
* Gets string representations of any additional fields the component has
* (other than sub-components and properties) for the {@link #toString}
* method.
* </p>
* <p>
* Meant to be overridden by child classes. The default implementation
* returns an empty map.
* </p>
* @return the values of the component's fields (key = field name, value =
* field value)
*/
protected Map<String, Object> toStringValues() {
return Collections.emptyMap();
}
private void toString(int depth, StringBuilder sb) {
StringUtils.repeat(' ', depth * 2, sb);
sb.append(getClass().getName());
Map<String, Object> fields = toStringValues();
if (!fields.isEmpty()) {
sb.append(' ').append(fields.toString());
}
sb.append(StringUtils.NEWLINE);
depth++;
for (ICalProperty property : properties.values()) {
StringUtils.repeat(' ', depth * 2, sb);
sb.append(property).append(StringUtils.NEWLINE);
}
for (ICalComponent component : components.values()) {
component.toString(depth, sb);
}
}
/**
* <p>
* Creates a deep copy of this component object.
* </p>
* <p>
* The default implementation of this method uses reflection to look for a
* copy constructor. Child classes SHOULD override this method to avoid the
* performance overhead involved in using reflection.
* </p>
* <p>
* The child class's copy constructor, if present, MUST invoke the
* {@link #ICalComponent(ICalComponent)} super constructor to ensure that
* the component's properties and sub-components are copied.
* </p>
* <p>
* This method MUST be overridden by the child class if the child class does
* not have a copy constructor. Otherwise, an
* {@link UnsupportedOperationException} will be thrown when an attempt is
* made to copy the component (such as in the
* {@link ICalendar#ICalendar(ICalendar) ICalendar class's copy constructor}
* ).
* </p>
* @return the copy
* @throws UnsupportedOperationException if the class does not have a copy
* constructor or there is a problem invoking it
*/
public ICalComponent copy() {
Class<? extends ICalComponent> clazz = getClass();
try {
Constructor<? extends ICalComponent> copyConstructor = clazz.getConstructor(clazz);
return copyConstructor.newInstance(this);
} catch (Exception e) {
throw new UnsupportedOperationException(Messages.INSTANCE.getExceptionMessage(1, clazz.getName()), e);
}
}
/**
* Casts all objects in the given list to the given class, adding the casted
* objects to a new list.
* @param list the list to cast
* @param castTo the class to cast to
* @param <T> the class to cast to
* @return the new list (immutable)
*/
private static <T> List<T> castList(List<?> list, Class<T> castTo) {
List<T> casted = new ArrayList<T>(list.size());
for (Object object : list) {
casted.add(castTo.cast(object));
}
return Collections.unmodifiableList(casted);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
int propertiesHash = 1;
for (ICalProperty property : properties.values()) {
propertiesHash += property.hashCode();
}
result = prime * result + propertiesHash;
int componentsHash = 1;
for (ICalComponent component : components.values()) {
componentsHash += component.hashCode();
}
result = prime * result + componentsHash;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
ICalComponent other = (ICalComponent) obj;
if (properties.size() != other.properties.size()) return false;
if (components.size() != other.components.size()) return false;
if (!compareMultimaps(properties, other.properties)) return false;
if (!compareMultimaps(components, other.components)) return false;
return true;
}
private static <K, V> boolean compareMultimaps(ListMultimap<K, V> map1, ListMultimap<K, V> map2) {
for (Map.Entry<K, List<V>> entry : map1) {
K key = entry.getKey();
List<V> value = entry.getValue();
List<V> otherValue = map2.get(key);
if (value.size() != otherValue.size()) {
return false;
}
List<V> otherValueCopy = new ArrayList<V>(otherValue);
for (V property : value) {
if (!otherValueCopy.remove(property)) {
return false;
}
}
}
return true;
}
/**
* <p>
* A list that automatically casts {@link ICalComponent} instances stored in
* this component to a given component class.
* </p>
* <p>
* This list is backed by the {@link ICalComponent} object. Any changes made
* to the list will affect the {@link ICalComponent} object and vice versa.
* </p>
* @param <T> the component class
*/
private class ICalComponentList<T extends ICalComponent> extends AbstractList<T> {
protected final Class<T> componentClass;
protected final List<ICalComponent> components;
/**
* @param componentClass the component class
*/
public ICalComponentList(Class<T> componentClass) {
this.componentClass = componentClass;
components = ICalComponent.this.components.get(componentClass);
}
@Override
public void add(int index, T value) {
components.add(index, value);
}
@Override
public T remove(int index) {
ICalComponent removed = components.remove(index);
return cast(removed);
}
@Override
public T get(int index) {
ICalComponent property = components.get(index);
return cast(property);
}
@Override
public T set(int index, T value) {
ICalComponent replaced = components.set(index, value);
return cast(replaced);
}
@Override
public int size() {
return components.size();
}
protected T cast(ICalComponent value) {
return componentClass.cast(value);
}
}
/**
* <p>
* A list that automatically casts {@link ICalProperty} instances stored in
* this component to a given property class.
* </p>
* <p>
* This list is backed by the {@link ICalComponent} object. Any changes made
* to the list will affect the {@link ICalComponent} object and vice versa.
* </p>
* @param <T> the property class
*/
private class ICalPropertyList<T extends ICalProperty> extends AbstractList<T> {
protected final Class<T> propertyClass;
protected final List<ICalProperty> properties;
/**
* @param propertyClass the property class
*/
public ICalPropertyList(Class<T> propertyClass) {
this.propertyClass = propertyClass;
properties = ICalComponent.this.properties.get(propertyClass);
}
@Override
public void add(int index, T value) {
properties.add(index, value);
}
@Override
public T remove(int index) {
ICalProperty removed = properties.remove(index);
return cast(removed);
}
@Override
public T get(int index) {
ICalProperty property = properties.get(index);
return cast(property);
}
@Override
public T set(int index, T value) {
ICalProperty replaced = properties.set(index, value);
return cast(replaced);
}
@Override
public int size() {
return properties.size();
}
protected T cast(ICalProperty value) {
return propertyClass.cast(value);
}
}
}

@ -1,409 +0,0 @@
package biweekly.component;
import java.util.List;
import biweekly.ICalVersion;
import biweekly.ValidationWarning;
import biweekly.property.Comment;
import biweekly.property.DateStart;
import biweekly.property.ExceptionDates;
import biweekly.property.RecurrenceDates;
import biweekly.property.RecurrenceRule;
import biweekly.property.TimezoneName;
import biweekly.property.TimezoneOffsetFrom;
import biweekly.property.TimezoneOffsetTo;
import biweekly.util.DateTimeComponents;
import biweekly.util.ICalDate;
import biweekly.util.Recurrence;
import biweekly.util.UtcOffset;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a timezone observance (i.e. "daylight savings" and "standard"
* times).
* @author Michael Angstadt
* @see DaylightSavingsTime
* @see StandardTime
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445 p.60-7</a>
*/
/*
* Note: References to the vCal 1.0 spec are omitted from the property
* getter/setter method Javadocs because vCal does not use the VTIMEZONE
* component.
*/
public class Observance extends ICalComponent {
public Observance() {
//empty
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public Observance(Observance original) {
super(original);
}
/**
* Gets the date that the timezone observance starts.
* @return the start date or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart getDateStart() {
return getProperty(DateStart.class);
}
/**
* Sets the date that the timezone observance starts.
* @param dateStart the start date or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public void setDateStart(DateStart dateStart) {
setProperty(DateStart.class, dateStart);
}
/**
* Sets the date that the timezone observance starts.
* @param date the start date or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart setDateStart(ICalDate date) {
DateStart prop = (date == null) ? null : new DateStart(date);
setDateStart(prop);
return prop;
}
/**
* Sets the date that the timezone observance starts.
* @param rawComponents the start date or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart setDateStart(DateTimeComponents rawComponents) {
return setDateStart((rawComponents == null) ? null : new ICalDate(rawComponents, true));
}
/**
* Gets the UTC offset that the timezone observance transitions to.
* @return the UTC offset or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-105">RFC 5545
* p.105-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-100">RFC 2445
* p.100-1</a>
*/
public TimezoneOffsetTo getTimezoneOffsetTo() {
return getProperty(TimezoneOffsetTo.class);
}
/**
* Sets the UTC offset that the timezone observance transitions to.
* @param timezoneOffsetTo the UTC offset or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-105">RFC 5545
* p.105-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-100">RFC 2445
* p.100-1</a>
*/
public void setTimezoneOffsetTo(TimezoneOffsetTo timezoneOffsetTo) {
setProperty(TimezoneOffsetTo.class, timezoneOffsetTo);
}
/**
* Sets the UTC offset that the timezone observance transitions to.
* @param offset the offset
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-105">RFC 5545
* p.105-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-100">RFC 2445
* p.100-1</a>
*/
public TimezoneOffsetTo setTimezoneOffsetTo(UtcOffset offset) {
TimezoneOffsetTo prop = new TimezoneOffsetTo(offset);
setTimezoneOffsetTo(prop);
return prop;
}
/**
* Gets the UTC offset that the timezone observance transitions from.
* @return the UTC offset or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-104">RFC 5545
* p.104-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-99">RFC 2445
* p.99-100</a>
*/
public TimezoneOffsetFrom getTimezoneOffsetFrom() {
return getProperty(TimezoneOffsetFrom.class);
}
/**
* Sets the UTC offset that the timezone observance transitions from.
* @param timezoneOffsetFrom the UTC offset or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-104">RFC 5545
* p.104-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-99">RFC 2445
* p.99-100</a>
*/
public void setTimezoneOffsetFrom(TimezoneOffsetFrom timezoneOffsetFrom) {
setProperty(TimezoneOffsetFrom.class, timezoneOffsetFrom);
}
/**
* Sets the UTC offset that the timezone observance transitions from.
* @param offset the offset
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-104">RFC 5545
* p.104-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-99">RFC 2445
* p.99-100</a>
*/
public TimezoneOffsetFrom setTimezoneOffsetFrom(UtcOffset offset) {
TimezoneOffsetFrom prop = new TimezoneOffsetFrom(offset);
setTimezoneOffsetFrom(prop);
return prop;
}
/**
* Gets how often the timezone observance repeats.
* @return the recurrence rule or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-122">RFC 5545
* p.122-32</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-117">RFC 2445
* p.117-25</a>
*/
public RecurrenceRule getRecurrenceRule() {
return getProperty(RecurrenceRule.class);
}
/**
* Sets how often the timezone observance repeats.
* @param recur the recurrence rule or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-122">RFC 5545
* p.122-32</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-117">RFC 2445
* p.117-25</a>
*/
public RecurrenceRule setRecurrenceRule(Recurrence recur) {
RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
setRecurrenceRule(prop);
return prop;
}
/**
* Sets how often the timezone observance repeats.
* @param recurrenceRule the recurrence rule or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-122">RFC 5545
* p.122-32</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-117">RFC 2445
* p.117-25</a>
*/
public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
setProperty(RecurrenceRule.class, recurrenceRule);
}
/**
* Gets the comments attached to the timezone observance.
* @return the comments (any changes made this list will affect the parent
* component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public List<Comment> getComments() {
return getProperties(Comment.class);
}
/**
* Adds a comment to the timezone observance.
* @param comment the comment to add
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public void addComment(Comment comment) {
addProperty(comment);
}
/**
* Adds a comment to the timezone observance.
* @param comment the comment to add
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public Comment addComment(String comment) {
Comment prop = new Comment(comment);
addComment(prop);
return prop;
}
/**
* Gets the list of dates/periods that help define the recurrence rule of
* this timezone observance (if one is defined).
* @return the recurrence dates (any changes made this list will affect the
* parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-120">RFC 5545
* p.120-2</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-115">RFC 2445
* p.115-7</a>
*/
public List<RecurrenceDates> getRecurrenceDates() {
return getProperties(RecurrenceDates.class);
}
/**
* Adds a list of dates/periods that help define the recurrence rule of this
* timezone observance (if one is defined).
* @param recurrenceDates the recurrence dates
* @see <a href="http://tools.ietf.org/html/rfc5545#page-120">RFC 5545
* p.120-2</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-115">RFC 2445
* p.115-7</a>
*/
public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
addProperty(recurrenceDates);
}
/**
* Gets the traditional, non-standard names for the timezone observance.
* @return the timezone observance names (any changes made this list will
* affect the parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-103">RFC 5545
* p.103-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-98">RFC 2445
* p.98-9</a>
*/
public List<TimezoneName> getTimezoneNames() {
return getProperties(TimezoneName.class);
}
/**
* Adds a traditional, non-standard name for the timezone observance.
* @param timezoneName the timezone observance name
* @see <a href="http://tools.ietf.org/html/rfc5545#page-103">RFC 5545
* p.103-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-98">RFC 2445
* p.98-9</a>
*/
public void addTimezoneName(TimezoneName timezoneName) {
addProperty(timezoneName);
}
/**
* Adds a traditional, non-standard name for the timezone observance.
* @param timezoneName the timezone observance name (e.g. "EST")
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-103">RFC 5545
* p.103-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-98">RFC 2445
* p.98-9</a>
*/
public TimezoneName addTimezoneName(String timezoneName) {
TimezoneName prop = new TimezoneName(timezoneName);
addTimezoneName(prop);
return prop;
}
/**
* Gets the list of exceptions to the timezone observance.
* @return the list of exceptions (any changes made this list will affect
* the parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-118">RFC 5545
* p.118-20</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-112">RFC 2445
* p.112-4</a>
*/
public List<ExceptionDates> getExceptionDates() {
return getProperties(ExceptionDates.class);
}
/**
* Adds a list of exceptions to the timezone observance. Note that this
* property can contain multiple dates.
* @param exceptionDates the list of exceptions
* @see <a href="http://tools.ietf.org/html/rfc5545#page-118">RFC 5545
* p.118-20</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-112">RFC 2445
* p.112-4</a>
*/
public void addExceptionDates(ExceptionDates exceptionDates) {
addProperty(exceptionDates);
}
@SuppressWarnings("unchecked")
@Override
protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) {
if (version == ICalVersion.V1_0) {
warnings.add(new ValidationWarning(48, version));
}
checkRequiredCardinality(warnings, DateStart.class, TimezoneOffsetTo.class, TimezoneOffsetFrom.class);
//BYHOUR, BYMINUTE, and BYSECOND cannot be specified in RRULE if DTSTART's data type is "date"
//RFC 5545 p. 167
DateStart dateStart = getDateStart();
RecurrenceRule rrule = getRecurrenceRule();
if (dateStart != null && rrule != null) {
ICalDate start = dateStart.getValue();
Recurrence recur = rrule.getValue();
if (start != null && recur != null) {
if (!start.hasTime() && (!recur.getByHour().isEmpty() || !recur.getByMinute().isEmpty() || !recur.getBySecond().isEmpty())) {
warnings.add(new ValidationWarning(5));
}
}
}
//there *should* be only 1 instance of RRULE
//RFC 5545 p. 167
if (getProperties(RecurrenceRule.class).size() > 1) {
warnings.add(new ValidationWarning(6));
}
}
@Override
public Observance copy() {
return new Observance(this);
}
}

@ -1,74 +0,0 @@
package biweekly.component;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a component that does not have a scribe associated with it.
* @author Michael Angstadt
*/
public class RawComponent extends ICalComponent {
private final String name;
public RawComponent(String name) {
this.name = name;
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public RawComponent(RawComponent original) {
super(original);
this.name = original.name;
}
public String getName() {
return name;
}
@Override
public RawComponent copy() {
return new RawComponent(this);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
RawComponent other = (RawComponent) obj;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
return true;
}
}

@ -1,67 +0,0 @@
package biweekly.component;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Defines the date range of a timezone's standard time.
* </p>
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* VTimezone timezone = new VTimezone("Eastern Standard Time");
* StandardTime standard = new StandardTime();
* DateTimeComponents components = new DateTimeComponents(1998, 10, 25, 2, 0, 0, false);
* standard.setDateStart(components);
* standard.setTimezoneOffsetFrom(-4, 0);
* standard.setTimezoneOffsetTo(-5, 0);
* timezone.addStandardTime(standard);
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445 p.60-7</a>
*/
public class StandardTime extends Observance {
public StandardTime() {
//empty
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public StandardTime(StandardTime original) {
super(original);
}
@Override
public StandardTime copy() {
return new StandardTime(this);
}
}

@ -1,590 +0,0 @@
package biweekly.component;
import java.util.Arrays;
import java.util.List;
import biweekly.ICalVersion;
import biweekly.ValidationWarning;
import biweekly.parameter.Related;
import biweekly.property.Action;
import biweekly.property.Attachment;
import biweekly.property.Attendee;
import biweekly.property.DateDue;
import biweekly.property.DateEnd;
import biweekly.property.DateStart;
import biweekly.property.Description;
import biweekly.property.DurationProperty;
import biweekly.property.Repeat;
import biweekly.property.Summary;
import biweekly.property.Trigger;
import biweekly.util.Duration;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Defines a reminder for an event or to-do task. This class contains static
* factory methods to aid in the construction of valid alarms.
* </p>
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* //audio alarm
* Trigger trigger = ...
* Attachment sound = ...
* VAlarm audio = VAlarm.audio(trigger, sound);
*
* //display alarm
* Trigger trigger = ...
* String message = "Meeting at 1pm";
* VAlarm display = VAlarm.display(trigger, message);
*
* //email alarm
* Trigger trigger = ...
* String subject = "Reminder: Meeting at 1pm";
* String body = "Team,\n\nThe team meeting scheduled for 1pm is about to start. Snacks will be served!\n\nThanks,\nJohn";
* List&lt;String&gt; to = Arrays.asList("janedoe@example.com", "bobsmith@example.com");
* VAlarm email = VAlarm.email(trigger, subject, body, to);
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-71">RFC 5545 p.71-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-67">RFC 2445
* p.67-73</a>
*/
/*
* Note: References to the vCal 1.0 spec are omitted from the property
* getter/setter method Javadocs because vCal does not use the VALARM component.
*/
public class VAlarm extends ICalComponent {
/**
* Creates a new alarm. Consider using one of the static factory methods
* instead.
* @param action the alarm action (e.g. "email")
* @param trigger the trigger
*/
public VAlarm(Action action, Trigger trigger) {
setAction(action);
setTrigger(trigger);
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public VAlarm(VAlarm original) {
super(original);
}
/**
* Creates an audio alarm.
* @param trigger the trigger
* @return the alarm
*/
public static VAlarm audio(Trigger trigger) {
return audio(trigger, null);
}
/**
* Creates an audio alarm.
* @param trigger the trigger
* @param sound a sound to play when the alarm triggers
* @return the alarm
*/
public static VAlarm audio(Trigger trigger, Attachment sound) {
VAlarm alarm = new VAlarm(Action.audio(), trigger);
if (sound != null) {
alarm.addAttachment(sound);
}
return alarm;
}
/**
* Creates a display alarm.
* @param trigger the trigger
* @param displayText the display text
* @return the alarm
*/
public static VAlarm display(Trigger trigger, String displayText) {
VAlarm alarm = new VAlarm(Action.display(), trigger);
alarm.setDescription(displayText);
return alarm;
}
/**
* Creates an email alarm.
* @param trigger the trigger
* @param subject the email subject
* @param body the email body
* @param recipients the email address(es) to send the alert to
* @return the alarm
*/
public static VAlarm email(Trigger trigger, String subject, String body, String... recipients) {
return email(trigger, subject, body, Arrays.asList(recipients));
}
/**
* Creates a procedure alarm (vCal 1.0 only).
* @param trigger the trigger
* @param path the path or name of the procedure
* @return the alarm
*/
public static VAlarm procedure(Trigger trigger, String path) {
VAlarm alarm = new VAlarm(Action.procedure(), trigger);
alarm.setDescription(path);
return alarm;
}
/**
* Creates an email alarm.
* @param trigger the trigger
* @param subject the email subject
* @param body the email body
* @param recipients the email address(es) to send the alert to
* @return the alarm
*/
public static VAlarm email(Trigger trigger, String subject, String body, List<String> recipients) {
VAlarm alarm = new VAlarm(Action.email(), trigger);
alarm.setSummary(subject);
alarm.setDescription(body);
for (String recipient : recipients) {
alarm.addAttendee(new Attendee(null, recipient));
}
return alarm;
}
/**
* Gets any attachments that are associated with the alarm.
* @return the attachments (any changes made this list will affect the
* parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
* p.80-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-77">RFC 2445
* p.77-8</a>
*/
public List<Attachment> getAttachments() {
return getProperties(Attachment.class);
}
/**
* Adds an attachment to the alarm. Note that AUDIO alarms should only have
* 1 attachment.
* @param attachment the attachment to add
* @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
* p.80-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-77">RFC 2445
* p.77-8</a>
*/
public void addAttachment(Attachment attachment) {
addProperty(attachment);
}
/**
* <p>
* Gets a detailed description of the alarm. The description should be more
* detailed than the one provided by the {@link Summary} property.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>DISPLAY - the display text</li>
* <li>EMAIL - the body of the email message</li>
* <li>all others - a general description of the alarm</li>
* </ul>
* @return the description or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
* p.84-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-81">RFC 2445
* p.81-2</a>
*/
public Description getDescription() {
return getProperty(Description.class);
}
/**
* <p>
* Sets a detailed description of the alarm. The description should be more
* detailed than the one provided by the {@link Summary} property.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>DISPLAY - the display text</li>
* <li>EMAIL - the body of the email message</li>
* <li>all others - a general description of the alarm</li>
* </ul>
* @param description the description or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
* p.84-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-81">RFC 2445
* p.81-2</a>
*/
public void setDescription(Description description) {
setProperty(Description.class, description);
}
/**
* <p>
* Sets a detailed description of the alarm. The description should be more
* detailed than the one provided by the {@link Summary} property.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>DISPLAY - the display text</li>
* <li>EMAIL - the body of the email message</li>
* <li>all others - a general description of the alarm</li>
* </ul>
* @param description the description or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
* p.84-5</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-81">RFC 2445
* p.81-2</a>
*/
public Description setDescription(String description) {
Description prop = (description == null) ? null : new Description(description);
setDescription(prop);
return prop;
}
/**
* <p>
* Gets the summary of the alarm.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>EMAIL - the subject line of the email</li>
* <li>all others - a one-line summary of the alarm</li>
* </ul>
* @return the summary or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
* p.93-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-89">RFC 2445
* p.89-90</a>
*/
public Summary getSummary() {
return getProperty(Summary.class);
}
/**
* <p>
* Sets the summary of the alarm.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>EMAIL - the subject line of the email</li>
* <li>all others - a one-line summary of the alarm</li>
* </ul>
* @param summary the summary or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
* p.93-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-89">RFC 2445
* p.89-90</a>
*/
public void setSummary(Summary summary) {
setProperty(Summary.class, summary);
}
/**
* <p>
* Sets the summary of the alarm.
* </p>
* <p>
* This property has different meanings, depending on the alarm action:
* </p>
* <ul>
* <li>EMAIL - the subject line of the email</li>
* <li>all others - a one-line summary of the alarm</li>
* </ul>
* @param summary the summary or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
* p.93-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-89">RFC 2445
* p.89-90</a>
*/
public Summary setSummary(String summary) {
Summary prop = (summary == null) ? null : new Summary(summary);
setSummary(prop);
return prop;
}
/**
* Gets the people who will be emailed when the alarm fires (only applicable
* for EMAIL alarms).
* @return the email recipients (any changes made this list will affect the
* parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
* p.107-9</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-102">RFC 2445
* p.102-4</a>
*/
public List<Attendee> getAttendees() {
return getProperties(Attendee.class);
}
/**
* Adds a person who will be emailed when the alarm fires (only applicable
* for EMAIL alarms).
* @param attendee the email recipient
* @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
* p.107-9</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-102">RFC 2445
* p.102-4</a>
*/
public void addAttendee(Attendee attendee) {
addProperty(attendee);
}
/**
* Gets the type of action to invoke when the alarm is triggered.
* @return the action or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
* p.132-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126</a>
*/
public Action getAction() {
return getProperty(Action.class);
}
/**
* Sets the type of action to invoke when the alarm is triggered.
* @param action the action or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
* p.132-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126</a>
*/
public void setAction(Action action) {
setProperty(Action.class, action);
}
/**
* Gets the length of the pause between alarm repetitions.
* @return the duration or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
* p.99</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-94">RFC 2445
* p.94-5</a>
*/
public DurationProperty getDuration() {
return getProperty(DurationProperty.class);
}
/**
* Sets the length of the pause between alarm repetitions.
* @param duration the duration or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
* p.99</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-94">RFC 2445
* p.94-5</a>
*/
public void setDuration(DurationProperty duration) {
setProperty(DurationProperty.class, duration);
}
/**
* Sets the length of the pause between alarm repetitions.
* @param duration the duration or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
* p.99</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-94">RFC 2445
* p.94-5</a>
*/
public DurationProperty setDuration(Duration duration) {
DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
setDuration(prop);
return prop;
}
/**
* Gets the number of times an alarm should be repeated after its initial
* trigger.
* @return the repeat count or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126-7</a>
*/
public Repeat getRepeat() {
return getProperty(Repeat.class);
}
/**
* Sets the number of times an alarm should be repeated after its initial
* trigger.
* @param repeat the repeat count or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126-7</a>
*/
public void setRepeat(Repeat repeat) {
setProperty(Repeat.class, repeat);
}
/**
* Sets the number of times an alarm should be repeated after its initial
* trigger.
* @param count the repeat count (e.g. "2" to repeat it two more times after
* it was initially triggered, for a total of three times) or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126-7</a>
*/
public Repeat setRepeat(Integer count) {
Repeat prop = (count == null) ? null : new Repeat(count);
setRepeat(prop);
return prop;
}
/**
* Sets the repetition information for the alarm.
* @param count the repeat count (e.g. "2" to repeat it two more times after
* it was initially triggered, for a total of three times)
* @param pauseDuration the length of the pause between repeats
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-126">RFC 2445
* p.126-7</a>
*/
public void setRepeat(int count, Duration pauseDuration) {
Repeat repeat = new Repeat(count);
DurationProperty duration = new DurationProperty(pauseDuration);
setRepeat(repeat);
setDuration(duration);
}
/**
* Gets when the alarm will be triggered.
* @return the trigger time or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-127">RFC 2445
* p.127-9</a>
*/
public Trigger getTrigger() {
return getProperty(Trigger.class);
}
/**
* Sets when the alarm will be triggered.
* @param trigger the trigger time or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
* p.133-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-127">RFC 2445
* p.127-9</a>
*/
public void setTrigger(Trigger trigger) {
setProperty(Trigger.class, trigger);
}
@SuppressWarnings("unchecked")
@Override
protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) {
checkRequiredCardinality(warnings, Action.class, Trigger.class);
Action action = getAction();
if (action != null) {
//AUDIO alarms should not have more than 1 attachment
if (action.isAudio()) {
if (getAttachments().size() > 1) {
warnings.add(new ValidationWarning(7));
}
}
//DESCRIPTION is required for DISPLAY alarms
if (action.isDisplay()) {
checkRequiredCardinality(warnings, Description.class);
}
if (action.isEmail()) {
//SUMMARY and DESCRIPTION is required for EMAIL alarms
checkRequiredCardinality(warnings, Summary.class, Description.class);
//EMAIL alarms must have at least 1 ATTENDEE
if (getAttendees().isEmpty()) {
warnings.add(new ValidationWarning(8));
}
} else {
//only EMAIL alarms can have ATTENDEEs
if (!getAttendees().isEmpty()) {
warnings.add(new ValidationWarning(9));
}
}
if (action.isProcedure()) {
checkRequiredCardinality(warnings, Description.class);
}
}
Trigger trigger = getTrigger();
if (trigger != null) {
Related related = trigger.getRelated();
if (related != null) {
ICalComponent parent = components.get(components.size() - 1);
//if the TRIGGER is relative to DTSTART, confirm that DTSTART exists
if (related == Related.START && parent.getProperty(DateStart.class) == null) {
warnings.add(new ValidationWarning(11));
}
//if the TRIGGER is relative to DTEND, confirm that DTEND (or DUE) exists
if (related == Related.END) {
boolean noEndDate = false;
if (parent instanceof VEvent) {
noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
} else if (parent instanceof VTodo) {
noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
}
if (noEndDate) {
warnings.add(new ValidationWarning(12));
}
}
}
}
}
@Override
public VAlarm copy() {
return new VAlarm(this);
}
}

File diff suppressed because it is too large Load Diff

@ -1,655 +0,0 @@
package biweekly.component;
import static biweekly.property.ValuedProperty.getValue;
import java.util.Date;
import java.util.List;
import biweekly.ICalVersion;
import biweekly.ValidationWarning;
import biweekly.parameter.FreeBusyType;
import biweekly.property.Attendee;
import biweekly.property.Comment;
import biweekly.property.Contact;
import biweekly.property.DateEnd;
import biweekly.property.DateStart;
import biweekly.property.DateTimeStamp;
import biweekly.property.FreeBusy;
import biweekly.property.LastModified;
import biweekly.property.Method;
import biweekly.property.Organizer;
import biweekly.property.RequestStatus;
import biweekly.property.Uid;
import biweekly.property.Url;
import biweekly.util.Duration;
import biweekly.util.ICalDate;
import biweekly.util.Period;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Defines a collection of time ranges that describe when a person is available
* and unavailable.
* </p>
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* VFreeBusy freebusy = new VFreeBusy();
*
* Date start = ...
* Date end = ...
* freebusy.addFreeBusy(FreeBusyType.FREE, start, end);
*
* start = ...
* Duration duration = Duration.builder().hours(2).build();
* freebusy.addFreeBusy(FreeBusyType.BUSY, start, duration);
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-59">RFC 5545
* p.59-62</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-58">RFC 2445
* p.58-60</a>
*/
/*
* Note: References to the vCal 1.0 spec are omitted from the property
* getter/setter method Javadocs because vCal does not use the VFREEBUSY
* component.
*/
public class VFreeBusy extends ICalComponent {
/**
* <p>
* Creates a new free/busy component.
* </p>
* <p>
* The following properties are added to the component when it is created:
* </p>
* <ul>
* <li>{@link Uid}: Set to a UUID.</li>
* <li>{@link DateTimeStamp}: Set to the current time.</li>
* </ul>
*/
public VFreeBusy() {
setUid(Uid.random());
setDateTimeStamp(new Date());
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public VFreeBusy(VFreeBusy original) {
super(original);
}
/**
* Gets the unique identifier for this free/busy entry. This component
* object comes populated with a UID on creation. This is a <b>required</b>
* property.
* @return the UID or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
* p.117-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-111">RFC 2445
* p.111-2</a>
*/
public Uid getUid() {
return getProperty(Uid.class);
}
/**
* Sets the unique identifier for this free/busy entry. This component
* object comes populated with a UID on creation. This is a <b>required</b>
* property.
* @param uid the UID or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
* p.117-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-111">RFC 2445
* p.111-2</a>
*/
public void setUid(Uid uid) {
setProperty(Uid.class, uid);
}
/**
* Sets the unique identifier for this free/busy entry. This component
* object comes populated with a UID on creation. This is a <b>required</b>
* property.
* @param uid the UID or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
* p.117-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-111">RFC 2445
* p.111-2</a>
*/
public Uid setUid(String uid) {
Uid prop = (uid == null) ? null : new Uid(uid);
setUid(prop);
return prop;
}
/**
* Gets either (a) the creation date of the iCalendar object (if the
* {@link Method} property is defined) or (b) the date that the free/busy
* entry was last modified (the {@link LastModified} property also holds
* this information). This free/busy object comes populated with a
* {@link DateTimeStamp} property that is set to the current time. This is a
* <b>required</b> property.
* @return the date time stamp or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
* p.137-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-130">RFC 2445
* p.130-1</a>
*/
public DateTimeStamp getDateTimeStamp() {
return getProperty(DateTimeStamp.class);
}
/**
* Sets either (a) the creation date of the iCalendar object (if the
* {@link Method} property is defined) or (b) the date that the free/busy
* entry was last modified (the {@link LastModified} property also holds
* this information). This free/busy object comes populated with a
* {@link DateTimeStamp} property that is set to the current time. This is a
* <b>required</b> property.
* @param dateTimeStamp the date time stamp or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
* p.137-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-130">RFC 2445
* p.130-1</a>
*/
public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
setProperty(DateTimeStamp.class, dateTimeStamp);
}
/**
* Sets either (a) the creation date of the iCalendar object (if the
* {@link Method} property is defined) or (b) the date that the free/busy
* entry was last modified (the {@link LastModified} property also holds
* this information). This free/busy object comes populated with a
* {@link DateTimeStamp} property that is set to the current time. This is a
* <b>required</b> property.
* @param dateTimeStamp the date time stamp or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
* p.137-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-130">RFC 2445
* p.130-1</a>
*/
public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
setDateTimeStamp(prop);
return prop;
}
/**
* Gets the contact associated with the free/busy entry.
* @return the contact or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
* p.109-11</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-104">RFC 2445
* p.104-6</a>
*/
public Contact getContact() {
return getProperty(Contact.class);
}
/**
* Sets the contact for the free/busy entry.
* @param contact the contact or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
* p.109-11</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-104">RFC 2445
* p.104-6</a>
*/
public void setContact(Contact contact) {
setProperty(Contact.class, contact);
}
/**
* Sets the contact for the free/busy entry.
* @param contact the contact (e.g. "ACME Co - (123) 555-1234")
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
* p.109-11</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-104">RFC 2445
* p.104-6</a>
*/
public Contact addContact(String contact) {
Contact prop = new Contact(contact);
setContact(prop);
return prop;
}
/**
* Gets the date that the free/busy entry starts.
* @return the start date or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart getDateStart() {
return getProperty(DateStart.class);
}
/**
* Sets the date that the free/busy entry starts.
* @param dateStart the start date or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public void setDateStart(DateStart dateStart) {
setProperty(DateStart.class, dateStart);
}
/**
* Sets the date that the free/busy entry starts.
* @param dateStart the start date or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart setDateStart(Date dateStart) {
return setDateStart(dateStart, true);
}
/**
* Sets the date that the free/busy entry starts.
* @param dateStart the start date or null to remove
* @param hasTime true if the date has a time component, false if it is
* strictly a date (if false, the given Date object should be created by a
* {@link java.util.Calendar Calendar} object that uses the JVM's default
* timezone)
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
* p.97-8</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-93">RFC 2445
* p.93-4</a>
*/
public DateStart setDateStart(Date dateStart, boolean hasTime) {
DateStart prop = (dateStart == null) ? null : new DateStart(dateStart, hasTime);
setDateStart(prop);
return prop;
}
/**
* Gets the date that the free/busy entry ends.
* @return the end date or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
* p.95-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-91">RFC 2445
* p.91-2</a>
*/
public DateEnd getDateEnd() {
return getProperty(DateEnd.class);
}
/**
* Sets the date that the free/busy entry ends.
* @param dateEnd the end date or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
* p.95-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-91">RFC 2445
* p.91-2</a>
*/
public void setDateEnd(DateEnd dateEnd) {
setProperty(DateEnd.class, dateEnd);
}
/**
* Sets the date that the free/busy entry ends.
* @param dateEnd the end date or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
* p.95-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-91">RFC 2445
* p.91-2</a>
*/
public DateEnd setDateEnd(Date dateEnd) {
return setDateEnd(dateEnd, true);
}
/**
* Sets the date that the free/busy entry ends.
* @param dateEnd the end date or null to remove
* @param hasTime true if the date has a time component, false if it is
* strictly a date (if false, the given Date object should be created by a
* {@link java.util.Calendar Calendar} object that uses the JVM's default
* timezone)
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
* p.95-6</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-91">RFC 2445
* p.91-2</a>
*/
public DateEnd setDateEnd(Date dateEnd, boolean hasTime) {
DateEnd prop = (dateEnd == null) ? null : new DateEnd(dateEnd, hasTime);
setDateEnd(prop);
return prop;
}
/**
* Gets the person requesting the free/busy time.
* @return the person requesting the free/busy time or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
* p.111-2</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-106">RFC 2445
* p.106-7</a>
*/
public Organizer getOrganizer() {
return getProperty(Organizer.class);
}
/**
* Sets the person requesting the free/busy time.
* @param organizer the person requesting the free/busy time or null to
* remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
* p.111-2</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-106">RFC 2445
* p.106-7</a>
*/
public void setOrganizer(Organizer organizer) {
setProperty(Organizer.class, organizer);
}
/**
* Sets the person requesting the free/busy time.
* @param email the email address of the person requesting the free/busy
* time (e.g. "johndoe@example.com") or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
* p.111-2</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-106">RFC 2445
* p.106-7</a>
*/
public Organizer setOrganizer(String email) {
Organizer prop = (email == null) ? null : new Organizer(null, email);
setOrganizer(prop);
return prop;
}
/**
* Gets a URL to a resource that contains additional information about the
* free/busy entry.
* @return the URL or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
* p.116-7</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-110">RFC 2445
* p.110-1</a>
*/
public Url getUrl() {
return getProperty(Url.class);
}
/**
* Sets a URL to a resource that contains additional information about the
* free/busy entry.
* @param url the URL or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
* p.116-7</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-110">RFC 2445
* p.110-1</a>
*/
public void setUrl(Url url) {
setProperty(Url.class, url);
}
/**
* Sets a URL to a resource that contains additional information about the
* free/busy entry.
* @param url the URL (e.g. "http://example.com/resource.ics") or null to
* remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
* p.116-7</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-110">RFC 2445
* p.110-1</a>
*/
public Url setUrl(String url) {
Url prop = (url == null) ? null : new Url(url);
setUrl(prop);
return prop;
}
/**
* Gets the people who are involved in the free/busy entry.
* @return the attendees (any changes made this list will affect the parent
* component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
* p.107-9</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-102">RFC 2445
* p.102-4</a>
*/
public List<Attendee> getAttendees() {
return getProperties(Attendee.class);
}
/**
* Adds a person who is involved in the free/busy entry.
* @param attendee the attendee
* @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
* p.107-9</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-102">RFC 2445
* p.102-4</a>
*/
public void addAttendee(Attendee attendee) {
addProperty(attendee);
}
/**
* Gets the comments attached to the free/busy entry.
* @return the comments (any changes made this list will affect the parent
* component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public List<Comment> getComments() {
return getProperties(Comment.class);
}
/**
* Adds a comment to the free/busy entry.
* @param comment the comment to add
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public void addComment(Comment comment) {
addProperty(comment);
}
/**
* Adds a comment to the free/busy entry.
* @param comment the comment to add
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
* p.83-4</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-80">RFC 2445
* p.80-1</a>
*/
public Comment addComment(String comment) {
Comment prop = new Comment(comment);
addComment(prop);
return prop;
}
/**
* Gets the person's availabilities over certain time periods (for example,
* "free" between 1pm-3pm, but "busy" between 3pm-4pm).
* @return the availabilities (any changes made this list will affect the
* parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
* p.100-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-95">RFC 2445
* p.95-6</a>
*/
public List<FreeBusy> getFreeBusy() {
return getProperties(FreeBusy.class);
}
/**
* Adds a list of time periods for which the person is free or busy (for
* example, "free" between 1pm-3pm and 4pm-5pm). Note that a
* {@link FreeBusy} property can contain multiple time periods, but only one
* availability type (e.g. "busy").
* @param freeBusy the availabilities
* @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
* p.100-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-95">RFC 2445
* p.95-6</a>
*/
public void addFreeBusy(FreeBusy freeBusy) {
addProperty(freeBusy);
}
/**
* Adds a single time period for which the person is free or busy (for
* example, "free" between 1pm-3pm). This method will look for an existing
* property that has the given {@link FreeBusyType} and add the time period
* to it, or create a new property is one cannot be found.
* @param type the availability type (e.g. "free" or "busy")
* @param start the start date-time
* @param end the end date-time
* @return the property that was created/modified
* @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
* p.100-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-95">RFC 2445
* p.95-6</a>
*/
public FreeBusy addFreeBusy(FreeBusyType type, Date start, Date end) {
FreeBusy found = findByFreeBusyType(type);
found.getValues().add(new Period(start, end));
return found;
}
/**
* Adds a single time period for which the person is free or busy (for
* example, "free" for 2 hours after 1pm). This method will look for an
* existing property that has the given {@link FreeBusyType} and add the
* time period to it, or create a new property is one cannot be found.
* @param type the availability type (e.g. "free" or "busy")
* @param start the start date-time
* @param duration the length of time
* @return the property that was created/modified
* @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
* p.100-1</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-95">RFC 2445
* p.95-6</a>
*/
public FreeBusy addFreeBusy(FreeBusyType type, Date start, Duration duration) {
FreeBusy found = findByFreeBusyType(type);
found.getValues().add(new Period(start, duration));
return found;
}
private FreeBusy findByFreeBusyType(FreeBusyType type) {
for (FreeBusy freeBusy : getFreeBusy()) {
if (freeBusy.getType() == type) {
return freeBusy;
}
}
FreeBusy freeBusy = new FreeBusy();
freeBusy.setType(type);
addFreeBusy(freeBusy);
return freeBusy;
}
/**
* Gets the response to a scheduling request.
* @return the response
* @see <a href="http://tools.ietf.org/html/rfc5546#section-3.6">RFC 5546
* Section 3.6</a>
* @see <a href="http://tools.ietf.org/html/rfc5545#page-141">RFC 5545
* p.141-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-134">RFC 2445
* p.134-6</a>
*/
public RequestStatus getRequestStatus() {
return getProperty(RequestStatus.class);
}
/**
* Sets the response to a scheduling request.
* @param requestStatus the response
* @see <a href="http://tools.ietf.org/html/rfc5546#section-3.6">RFC 5546
* Section 3.6</a>
* @see <a href="http://tools.ietf.org/html/rfc5545#page-141">RFC 5545
* p.141-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-134">RFC 2445
* p.134-6</a>
*/
public void setRequestStatus(RequestStatus requestStatus) {
setProperty(RequestStatus.class, requestStatus);
}
@SuppressWarnings("unchecked")
@Override
protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) {
if (version == ICalVersion.V1_0) {
warnings.add(new ValidationWarning(48, version));
}
checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
checkOptionalCardinality(warnings, Contact.class, DateStart.class, DateEnd.class, Organizer.class, Url.class);
ICalDate dateStart = getValue(getDateStart());
ICalDate dateEnd = getValue(getDateEnd());
//DTSTART is required if DTEND exists
if (dateEnd != null && dateStart == null) {
warnings.add(new ValidationWarning(15));
}
//DTSTART and DTEND must contain a time component
if (dateStart != null && !dateStart.hasTime()) {
warnings.add(new ValidationWarning(20, DateStart.class.getSimpleName()));
}
if (dateEnd != null && !dateEnd.hasTime()) {
warnings.add(new ValidationWarning(20, DateEnd.class.getSimpleName()));
}
//DTSTART must come before DTEND
if (dateStart != null && dateEnd != null && dateStart.compareTo(dateEnd) >= 0) {
warnings.add(new ValidationWarning(16));
}
}
@Override
public VFreeBusy copy() {
return new VFreeBusy(this);
}
}

File diff suppressed because it is too large Load Diff

@ -1,282 +0,0 @@
package biweekly.component;
import java.util.Date;
import java.util.List;
import biweekly.ICalVersion;
import biweekly.ValidationWarning;
import biweekly.property.LastModified;
import biweekly.property.TimezoneId;
import biweekly.property.TimezoneUrl;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Defines a timezone's UTC offsets throughout the year.
* </p>
*
* <p>
* <b>Examples:</b>
* </p>
*
* <pre class="brush:java">
* VTimezone timezone = new VTimezone("Eastern Standard Time");
*
* StandardTime standard = new StandardTime();
* DateTimeComponents componentsStandard = new DateTimeComponents(1998, 10, 25, 2, 0, 0, false);
* standard.setDateStart(componentsStandard);
* standard.setTimezoneOffsetFrom(-4, 0);
* standard.setTimezoneOffsetTo(-5, 0);
* timezone.addStandardTime(standard);
*
* DaylightSavingsTime daylight = new DaylightSavingsTime();
* DateTimeComponents componentsDaylight = new DateTimeComponents(1999, 4, 4, 2, 0, 0, false);
* daylight.setDateStart(componentsDaylight);
* daylight.setTimezoneOffsetFrom(-5, 0);
* daylight.setTimezoneOffsetTo(-4, 0);
* timezone.addDaylightSavingsTime(daylight);
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445 p.60-7</a>
*/
/*
* Note: References to the vCal 1.0 spec are omitted from the property
* getter/setter method Javadocs because vCal does not use the VTIMEZONE
* component.
*/
public class VTimezone extends ICalComponent {
/**
* Creates a new timezone component.
* @param identifier a unique identifier for this timezone (allows it to be
* referenced by date-time properties that support timezones).
*/
public VTimezone(String identifier) {
setTimezoneId(identifier);
}
/**
* Copy constructor.
* @param original the component to make a copy of
*/
public VTimezone(VTimezone original) {
super(original);
}
/**
* Gets the ID for this timezone. This is a <b>required</b> property.
* @return the timezone ID or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-102">RFC 5545
* p.102-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-97">RFC 2445
* p.97-8</a>
*/
public TimezoneId getTimezoneId() {
return getProperty(TimezoneId.class);
}
/**
* Sets an ID for this timezone. This is a <b>required</b> property.
* @param timezoneId the timezone ID or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-102">RFC 5545
* p.102-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-97">RFC 2445
* p.97-8</a>
*/
public void setTimezoneId(TimezoneId timezoneId) {
setProperty(TimezoneId.class, timezoneId);
}
/**
* Sets an ID for this timezone. This is a <b>required</b> property.
* @param timezoneId the timezone ID or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-102">RFC 5545
* p.102-3</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-97">RFC 2445
* p.97-8</a>
*/
public TimezoneId setTimezoneId(String timezoneId) {
TimezoneId prop = (timezoneId == null) ? null : new TimezoneId(timezoneId);
setTimezoneId(prop);
return prop;
}
/**
* Gets the date-time that the timezone data was last changed.
* @return the last modified date or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-138">RFC 5545
* p.138</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-131">RFC 2445
* p.131</a>
*/
public LastModified getLastModified() {
return getProperty(LastModified.class);
}
/**
* Sets the date-time that the timezone data was last changed.
* @param lastModified the last modified date or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-138">RFC 5545
* p.138</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-131">RFC 2445
* p.131</a>
*/
public void setLastModified(LastModified lastModified) {
setProperty(LastModified.class, lastModified);
}
/**
* Sets the date-time that the timezone data was last changed.
* @param lastModified the last modified date or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-138">RFC 5545
* p.138</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-131">RFC 2445
* p.131</a>
*/
public LastModified setLastModified(Date lastModified) {
LastModified prop = (lastModified == null) ? null : new LastModified(lastModified);
setLastModified(prop);
return prop;
}
/**
* Gets the timezone URL, which points to an iCalendar object that contains
* further information on the timezone.
* @return the URL or null if not set
* @see <a href="http://tools.ietf.org/html/rfc5545#page-106">RFC 5545
* p.106</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-101">RFC 2445
* p.101</a>
*/
public TimezoneUrl getTimezoneUrl() {
return getProperty(TimezoneUrl.class);
}
/**
* Sets the timezone URL, which points to an iCalendar object that contains
* further information on the timezone.
* @param url the URL or null to remove
* @see <a href="http://tools.ietf.org/html/rfc5545#page-106">RFC 5545
* p.106</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-101">RFC 2445
* p.101</a>
*/
public void setTimezoneUrl(TimezoneUrl url) {
setProperty(TimezoneUrl.class, url);
}
/**
* Sets the timezone URL, which points to an iCalendar object that contains
* further information on the timezone.
* @param url the timezone URL (e.g.
* "http://example.com/America-New_York.ics") or null to remove
* @return the property that was created
* @see <a href="http://tools.ietf.org/html/rfc5545#page-106">RFC 5545
* p.106</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-101">RFC 2445
* p.101</a>
*/
public TimezoneUrl setTimezoneUrl(String url) {
TimezoneUrl prop = (url == null) ? null : new TimezoneUrl(url);
setTimezoneUrl(prop);
return prop;
}
/**
* Gets the timezone's "standard" observance time ranges.
* @return the "standard" observance time ranges (any changes made this list
* will affect the parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445
* p.60-7</a>
*/
public List<StandardTime> getStandardTimes() {
return getComponents(StandardTime.class);
}
/**
* Adds a "standard" observance time range.
* @param standardTime the "standard" observance time
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445
* p.60-7</a>
*/
public void addStandardTime(StandardTime standardTime) {
addComponent(standardTime);
}
/**
* Gets the timezone's "daylight savings" observance time ranges.
* @return the "daylight savings" observance time ranges (any changes made
* this list will affect the parent component object and vice versa)
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445
* p.60-7</a>
*/
public List<DaylightSavingsTime> getDaylightSavingsTime() {
return getComponents(DaylightSavingsTime.class);
}
/**
* Adds a "daylight savings" observance time range.
* @param daylightSavingsTime the "daylight savings" observance time
* @see <a href="http://tools.ietf.org/html/rfc5545#page-62">RFC 5545
* p.62-71</a>
* @see <a href="http://tools.ietf.org/html/rfc2445#page-60">RFC 2445
* p.60-7</a>
*/
public void addDaylightSavingsTime(DaylightSavingsTime daylightSavingsTime) {
addComponent(daylightSavingsTime);
}
@SuppressWarnings("unchecked")
@Override
protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) {
if (version == ICalVersion.V1_0) {
warnings.add(new ValidationWarning(48, version));
}
checkRequiredCardinality(warnings, TimezoneId.class);
checkOptionalCardinality(warnings, LastModified.class, TimezoneUrl.class);
//STANDARD or DAYLIGHT must be defined
if (getStandardTimes().isEmpty() && getDaylightSavingsTime().isEmpty()) {
warnings.add(new ValidationWarning(21));
}
}
@Override
public VTimezone copy() {
return new VTimezone(this);
}
}

File diff suppressed because it is too large Load Diff

@ -1,4 +0,0 @@
/**
* Contains DTO classes for each component.
*/
package biweekly.component;

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,79 +0,0 @@
package biweekly.io;
import biweekly.Messages;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Thrown during the unmarshalling of an iCalendar property to signal that the
* property's value could not be parsed (for example, being unable to parse a
* date string).
* @author Michael Angstadt
*/
public class CannotParseException extends RuntimeException {
private static final long serialVersionUID = 8299420302297241326L;
private final Integer code;
private final Object args[];
/**
* Creates a new "cannot parse" exception.
* @param code the warning message code
* @param args the warning message arguments
*/
public CannotParseException(int code, Object... args) {
this.code = code;
this.args = args;
}
/**
* Creates a new "cannot parse" exception.
* @param reason the reason why the property value cannot be parsed
*/
public CannotParseException(String reason) {
this(1, reason);
}
/**
* Gets the warning message code.
* @return the message code
*/
public Integer getCode() {
return code;
}
/**
* Gets the warning message arguments.
* @return the message arguments
*/
public Object[] getArgs() {
return args;
}
@Override
public String getMessage() {
return Messages.INSTANCE.getParseMessage(code, args);
}
}

@ -1,81 +0,0 @@
package biweekly.io;
import java.util.ArrayList;
import java.util.List;
import biweekly.component.ICalComponent;
import biweekly.component.VAlarm;
import biweekly.property.AudioAlarm;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Thrown when a component or property needs to be converted to a different
* component or property when being read or written. For example, converting a
* vCal {@link AudioAlarm} property to a {@link VAlarm} component when parsing a
* vCal file.
* @author Michael Angstadt
*/
public class DataModelConversionException extends RuntimeException {
private static final long serialVersionUID = -4789186852509057375L;
private final ICalProperty originalProperty;
private final List<ICalComponent> components = new ArrayList<ICalComponent>();
private final List<ICalProperty> properties = new ArrayList<ICalProperty>();
/**
* Creates a conversion exception.
* @param originalProperty the original property object that was parsed or
* null if not applicable
*/
public DataModelConversionException(ICalProperty originalProperty) {
this.originalProperty = originalProperty;
}
/**
* Gets the original property object that was parsed.
* @return the original property object or null if not applicable
*/
public ICalProperty getOriginalProperty() {
return originalProperty;
}
/**
* Gets the components that were converted from the original property.
* @return the components
*/
public List<ICalComponent> getComponents() {
return components;
}
/**
* Gets the properties that were converted from the original property.
* @return the properties
*/
public List<ICalProperty> getProperties() {
return properties;
}
}

@ -1,241 +0,0 @@
package biweekly.io;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import biweekly.component.DaylightSavingsTime;
import biweekly.component.Observance;
import biweekly.component.StandardTime;
import biweekly.component.VTimezone;
import biweekly.io.ICalTimeZone.Boundary;
import biweekly.property.Daylight;
import biweekly.property.Timezone;
import biweekly.property.UtcOffsetProperty;
import biweekly.property.ValuedProperty;
import biweekly.util.DateTimeComponents;
import biweekly.util.ICalDate;
import biweekly.util.UtcOffset;
import biweekly.util.com.google.ical.values.DateTimeValue;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Converts various properties/components into other properties/components for
* vCalendar-iCalendar compatibility.
* @author Michael Angstadt
*/
public final class DataModelConverter {
/**
* Converts vCalendar timezone information to an iCalendar {@link VTimezone}
* component.
* @param daylights the DAYLIGHT properties
* @param tz the TZ property
* @return the VTIMEZONE component
*/
public static VTimezone convert(List<Daylight> daylights, Timezone tz) {
UtcOffset tzOffset = ValuedProperty.getValue(tz);
if (daylights.isEmpty() && tzOffset == null) {
return null;
}
VTimezone timezone = new VTimezone("TZ");
if (daylights.isEmpty() && tzOffset != null) {
StandardTime st = new StandardTime();
st.setTimezoneOffsetFrom(tzOffset);
st.setTimezoneOffsetTo(tzOffset);
timezone.addStandardTime(st);
return timezone;
}
for (Daylight daylight : daylights) {
if (!daylight.isDaylight()) {
continue;
}
UtcOffset daylightOffset = daylight.getOffset();
UtcOffset standardOffset = new UtcOffset(daylightOffset.getMillis() - (1000 * 60 * 60));
DaylightSavingsTime dst = new DaylightSavingsTime();
dst.setDateStart(daylight.getStart());
dst.setTimezoneOffsetFrom(standardOffset);
dst.setTimezoneOffsetTo(daylightOffset);
dst.addTimezoneName(daylight.getDaylightName());
timezone.addDaylightSavingsTime(dst);
StandardTime st = new StandardTime();
st.setDateStart(daylight.getEnd());
st.setTimezoneOffsetFrom(daylightOffset);
st.setTimezoneOffsetTo(standardOffset);
st.addTimezoneName(daylight.getStandardName());
timezone.addStandardTime(st);
}
return timezone.getComponents().isEmpty() ? null : timezone;
}
/**
* Converts an iCalendar {@link VTimezone} component into the appropriate
* vCalendar properties.
* @param timezone the TIMEZONE component
* @param dates the date values in the vCalendar object that are effected by
* the timezone.
* @return the vCalendar properties
*/
public static VCalTimezoneProperties convert(VTimezone timezone, List<Date> dates) {
List<Daylight> daylights = new ArrayList<Daylight>();
Timezone tz = null;
if (dates.isEmpty()) {
return new VCalTimezoneProperties(daylights, tz);
}
ICalTimeZone icalTz = new ICalTimeZone(timezone);
Collections.sort(dates);
Set<DateTimeValue> daylightStartDates = new HashSet<DateTimeValue>();
boolean zeroObservanceUsed = false;
for (Date date : dates) {
Boundary boundary = icalTz.getObservanceBoundary(date);
Observance observance = boundary.getObservanceIn();
Observance observanceAfter = boundary.getObservanceAfter();
if (observance == null && observanceAfter == null) {
continue;
}
if (observance == null) {
//the date comes before the earliest observance
if (observanceAfter instanceof StandardTime && !zeroObservanceUsed) {
UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom());
DateTimeValue start = null;
DateTimeValue end = boundary.getObservanceAfterStart();
String standardName = icalTz.getDisplayName(false, TimeZone.SHORT);
String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT);
Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName);
daylights.add(daylight);
zeroObservanceUsed = true;
}
if (observanceAfter instanceof DaylightSavingsTime) {
UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom());
if (offset != null) {
tz = new Timezone(offset);
}
}
continue;
}
if (observance instanceof StandardTime) {
UtcOffset offset = getOffset(observance.getTimezoneOffsetTo());
if (offset != null) {
tz = new Timezone(offset);
}
continue;
}
if (observance instanceof DaylightSavingsTime && !daylightStartDates.contains(boundary.getObservanceInStart())) {
UtcOffset offset = getOffset(observance.getTimezoneOffsetTo());
DateTimeValue start = boundary.getObservanceInStart();
DateTimeValue end = null;
if (observanceAfter != null) {
end = boundary.getObservanceAfterStart();
}
String standardName = icalTz.getDisplayName(false, TimeZone.SHORT);
String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT);
Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName);
daylights.add(daylight);
daylightStartDates.add(start);
continue;
}
}
if (tz == null) {
int rawOffset = icalTz.getRawOffset();
UtcOffset offset = new UtcOffset(rawOffset);
tz = new Timezone(offset);
}
if (daylights.isEmpty()) {
Daylight daylight = new Daylight();
daylight.setDaylight(false);
daylights.add(daylight);
}
return new VCalTimezoneProperties(daylights, tz);
}
private static UtcOffset getOffset(UtcOffsetProperty property) {
return (property == null) ? null : property.getValue();
}
private static ICalDate convert(DateTimeValue value) {
if (value == null) {
return null;
}
//@formatter:off
DateTimeComponents components = new DateTimeComponents(
value.year(),
value.month(),
value.day(),
value.hour(),
value.minute(),
value.second(),
false
);
//@formatter:on
return new ICalDate(components, true);
}
public static class VCalTimezoneProperties {
private final List<Daylight> daylights;
private final Timezone tz;
public VCalTimezoneProperties(List<Daylight> daylights, Timezone tz) {
this.daylights = daylights;
this.tz = tz;
}
public List<Daylight> getDaylights() {
return daylights;
}
public Timezone getTz() {
return tz;
}
}
private DataModelConverter() {
//hide
}
}

@ -1,67 +0,0 @@
package biweekly.io;
import biweekly.util.ICalDateFormat;
import java.util.TimeZone;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Default implementation of {@link GlobalTimezoneIdResolver}.
* </p>
* <p>
* The following are examples of the kinds of TZID formats this class is able to
* handle.
* </p>
* <ul>
* <li>"TZID=/America/New_York" resolves to
* {@code TimeZone.getTimeZone("America/New_York")}</li>
* <li>"TZID=/mozilla.org/20050126_1/America/New_York" resolves to
* {@code TimeZone.getTimeZone("America/New_York")}</li>
* </ul>
* @author Michael Angstadt
*/
public class DefaultGlobalTimezoneIdResolver implements GlobalTimezoneIdResolver {
@Override
public TimeZone resolve(String globalId) {
globalId = removeMozillaPrefixIfPresent(globalId);
return ICalDateFormat.parseTimeZoneId(globalId);
}
/**
* Checks for, and removes, a global ID prefix that Mozilla software adds to
* its iCal files. Googling this prefix returns several search results,
* suggesting it is frequently encountered in the wild.
* @param globalId the global ID (may or may not contain the Mozilla prefix)
* @return the sanitized global ID, or the unchanged ID if it does not
* contain the prefix
*/
private String removeMozillaPrefixIfPresent(String globalId) {
String prefix = "mozilla.org/20050126_1/";
return globalId.startsWith(prefix) ? globalId.substring(prefix.length()) : globalId;
}
}

@ -1,50 +0,0 @@
package biweekly.io;
import java.util.TimeZone;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Gets Java {@link TimeZone} objects that correspond with TZID parameters that
* contain global timezone IDs (as opposed to IDs that correspond with a
* VTIMEZONE component).
* @author Michael Angstadt
* @see <a href="https://tools.ietf.org/html/rfc5545#section-3.8.3.1">RFC 5545
* section 3.8.3.1</a>
* @see <a href="https://tools.ietf.org/html/rfc2445#section-4.2.19">RFC 2445
* section 4.2.19</a>
*/
public interface GlobalTimezoneIdResolver {
/**
* Returns an appropriate Java {@link TimeZone} object that corresponds to
* the given global ID.
* @param globalId the global ID (the value of the TZID parameter, without
* the forward slash prefix)
* @return the corresponding {@link TimeZone} object or null if the global
* ID is not recognized
*/
TimeZone resolve(String globalId);
}

@ -1,720 +0,0 @@
package biweekly.io;
import static biweekly.property.ValuedProperty.getValue;
import static biweekly.util.Google2445Utils.convertFromRawComponents;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TimeZone;
import biweekly.Messages;
import biweekly.component.DaylightSavingsTime;
import biweekly.component.Observance;
import biweekly.component.StandardTime;
import biweekly.component.VTimezone;
import biweekly.property.ExceptionDates;
import biweekly.property.ExceptionRule;
import biweekly.property.RecurrenceDates;
import biweekly.property.RecurrenceRule;
import biweekly.property.TimezoneName;
import biweekly.util.ICalDate;
import biweekly.util.Recurrence;
import biweekly.util.UtcOffset;
import biweekly.util.com.google.ical.iter.RecurrenceIterator;
import biweekly.util.com.google.ical.iter.RecurrenceIteratorFactory;
import biweekly.util.com.google.ical.util.DTBuilder;
import biweekly.util.com.google.ical.values.DateTimeValue;
import biweekly.util.com.google.ical.values.DateTimeValueImpl;
import biweekly.util.com.google.ical.values.DateValue;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* A timezone that is based on an iCalendar {@link VTimezone} component. This
* class is not thread safe.
* @author Michael Angstadt
*/
@SuppressWarnings("serial")
public class ICalTimeZone extends TimeZone {
private final VTimezone component;
private final Map<Observance, List<DateValue>> observanceDateCache;
final List<Observance> sortedObservances;
private final int rawOffset;
private final TimeZone utc = TimeZone.getTimeZone("UTC");
private final Calendar utcCalendar = Calendar.getInstance(utc);
/**
* Creates a new timezone based on an iCalendar VTIMEZONE component.
* @param component the VTIMEZONE component to wrap
*/
public ICalTimeZone(VTimezone component) {
this.component = component;
int numObservances = component.getStandardTimes().size() + component.getDaylightSavingsTime().size();
observanceDateCache = new IdentityHashMap<Observance, List<DateValue>>(numObservances);
sortedObservances = calculateSortedObservances();
rawOffset = calculateRawOffset();
String id = getValue(component.getTimezoneId());
if (id != null) {
setID(id);
}
}
/**
* Builds a list of all the observances in the VTIMEZONE component, sorted
* by DTSTART.
* @return the sorted observances
*/
private List<Observance> calculateSortedObservances() {
List<DaylightSavingsTime> daylights = component.getDaylightSavingsTime();
List<StandardTime> standards = component.getStandardTimes();
int numObservances = standards.size() + daylights.size();
List<Observance> sortedObservances = new ArrayList<Observance>(numObservances);
sortedObservances.addAll(standards);
sortedObservances.addAll(daylights);
Collections.sort(sortedObservances, new Comparator<Observance>() {
public int compare(Observance left, Observance right) {
ICalDate startLeft = getValue(left.getDateStart());
ICalDate startRight = getValue(right.getDateStart());
if (startLeft == null && startRight == null) {
return 0;
}
if (startLeft == null) {
return -1;
}
if (startRight == null) {
return 1;
}
return startLeft.getRawComponents().compareTo(startRight.getRawComponents());
}
});
return Collections.unmodifiableList(sortedObservances);
}
@Override
public String getDisplayName(boolean daylight, int style, Locale locale) {
ListIterator<Observance> it = sortedObservances.listIterator(sortedObservances.size());
while (it.hasPrevious()) {
Observance observance = it.previous();
if (daylight && observance instanceof DaylightSavingsTime) {
List<TimezoneName> names = observance.getTimezoneNames();
if (!names.isEmpty()) {
String name = names.get(0).getValue();
if (name != null) {
return name;
}
}
}
if (!daylight && observance instanceof StandardTime) {
List<TimezoneName> names = observance.getTimezoneNames();
if (!names.isEmpty()) {
String name = names.get(0).getValue();
if (name != null) {
return name;
}
}
}
}
return super.getDisplayName(daylight, style, locale);
}
@Override
public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) {
int hour = millis / 1000 / 60 / 60;
millis -= hour * 1000 * 60 * 60;
int minute = millis / 1000 / 60;
millis -= minute * 1000 * 60;
int second = millis / 1000;
Observance observance = getObservance(year, month + 1, day, hour, minute, second);
if (observance == null) {
/*
* Find the first observance that has a DTSTART property and a
* TZOFFSETFROM property.
*/
for (Observance obs : sortedObservances) {
ICalDate dateStart = getValue(obs.getDateStart());
if (dateStart == null) {
continue;
}
UtcOffset offsetFrom = getValue(obs.getTimezoneOffsetFrom());
if (offsetFrom == null) {
continue;
}
return (int) offsetFrom.getMillis();
}
return 0;
}
UtcOffset offsetTo = getValue(observance.getTimezoneOffsetTo());
return (offsetTo == null) ? 0 : (int) offsetTo.getMillis();
}
@Override
public int getRawOffset() {
return rawOffset;
}
private int calculateRawOffset() {
Observance observance = getObservance(new Date());
if (observance == null) {
//return the offset of the first STANDARD component
for (Observance obs : sortedObservances) {
if (!(obs instanceof StandardTime)) {
continue;
}
UtcOffset offsetTo = getValue(obs.getTimezoneOffsetTo());
if (offsetTo == null) {
continue;
}
return (int) offsetTo.getMillis();
}
return 0;
}
UtcOffset offset = getValue((observance instanceof StandardTime) ? observance.getTimezoneOffsetTo() : observance.getTimezoneOffsetFrom());
return (offset == null) ? 0 : (int) offset.getMillis();
}
@Override
public boolean inDaylightTime(Date date) {
if (!useDaylightTime()) {
return false;
}
Observance observance = getObservance(date);
return (observance == null) ? false : (observance instanceof DaylightSavingsTime);
}
/**
* This method is not supported by this class.
* @throws UnsupportedOperationException thrown when this method is called
*/
@Override
public void setRawOffset(int offset) {
throw new UnsupportedOperationException(Messages.INSTANCE.getExceptionMessage(12));
}
@Override
public boolean useDaylightTime() {
for (Observance observance : sortedObservances) {
if (observance instanceof DaylightSavingsTime) {
return true;
}
}
return false;
}
/**
* Gets the timezone information of a date.
* @param date the date
* @return the timezone information
*/
public Boundary getObservanceBoundary(Date date) {
utcCalendar.setTime(date);
int year = utcCalendar.get(Calendar.YEAR);
int month = utcCalendar.get(Calendar.MONTH) + 1;
int day = utcCalendar.get(Calendar.DATE);
int hour = utcCalendar.get(Calendar.HOUR);
int minute = utcCalendar.get(Calendar.MINUTE);
int second = utcCalendar.get(Calendar.SECOND);
return getObservanceBoundary(year, month, day, hour, minute, second);
}
/**
* Gets the observance that a date is effected by.
* @param date the date
* @return the observance or null if an observance cannot be found
*/
public Observance getObservance(Date date) {
Boundary boundary = getObservanceBoundary(date);
return (boundary == null) ? null : boundary.getObservanceIn();
}
/**
* <p>
* Gets the VTIMEZONE component that is being wrapped.
* </p>
* <p>
* Note that the ICalTimeZone class makes heavy use of caching. Any
* modifications made to the VTIMEZONE component that is returned by this
* method may effect the accuracy of this ICalTimeZone instance.
* </p>
* @return the VTIMEZONE component
*/
public VTimezone getComponent() {
return component;
}
/**
* Gets the observance that a date is effected by.
* @param year the year
* @param month the month (1-12)
* @param day the day of the month
* @param hour the hour
* @param minute the minute
* @param second the second
* @return the observance or null if an observance cannot be found
*/
private Observance getObservance(int year, int month, int day, int hour, int minute, int second) {
Boundary boundary = getObservanceBoundary(year, month, day, hour, minute, second);
return (boundary == null) ? null : boundary.getObservanceIn();
}
/**
* Gets the observance information of a date.
* @param year the year
* @param month the month (1-12)
* @param day the day of the month
* @param hour the hour
* @param minute the minute
* @param second the second
* @return the observance information or null if none was found
*/
private Boundary getObservanceBoundary(int year, int month, int day, int hour, int minute, int second) {
if (sortedObservances.isEmpty()) {
return null;
}
DateValue givenTime = new DateTimeValueImpl(year, month, day, hour, minute, second);
int closestIndex = -1;
Observance closest = null;
DateValue closestValue = null;
for (int i = 0; i < sortedObservances.size(); i++) {
Observance observance = sortedObservances.get(i);
//skip observances that start after the given time
ICalDate dtstart = getValue(observance.getDateStart());
if (dtstart != null) {
DateValue dtstartValue = convertFromRawComponents(dtstart);
if (dtstartValue.compareTo(givenTime) > 0) {
continue;
}
}
DateValue dateValue = getObservanceDateClosestToTheGivenDate(observance, givenTime, false);
if (dateValue != null && (closestValue == null || closestValue.compareTo(dateValue) < 0)) {
closestValue = dateValue;
closest = observance;
closestIndex = i;
}
}
Observance observanceIn = closest;
DateValue observanceInStart = closestValue;
Observance observanceAfter = null;
DateValue observanceAfterStart = null;
if (closestIndex < sortedObservances.size() - 1) {
observanceAfter = sortedObservances.get(closestIndex + 1);
observanceAfterStart = getObservanceDateClosestToTheGivenDate(observanceAfter, givenTime, true);
}
/*
* If any of the DTSTART properties are missing their time components,
* then observanceInStart/observanceAfterStart could be a DateValue
* object. If so, convert it to a DateTimeValue object (see Issue 77).
*/
if (observanceInStart != null && !(observanceInStart instanceof DateTimeValue)) {
observanceInStart = new DTBuilder(observanceInStart).toDateTime();
}
if (observanceAfterStart != null && !(observanceAfterStart instanceof DateTimeValue)) {
observanceAfterStart = new DTBuilder(observanceAfterStart).toDateTime();
}
return new Boundary((DateTimeValue) observanceInStart, observanceIn, (DateTimeValue) observanceAfterStart, observanceAfter);
}
/**
* Iterates through each of the timezone boundary dates defined by the given
* observance and finds the date that comes closest to the given date.
* @param observance the observance
* @param givenDate the given date
* @param after true to return the closest date <b>greater than</b> the
* given date, false to return the closest date <b>less than or equal to</b>
* the given date.
* @return the closest date
*/
private DateValue getObservanceDateClosestToTheGivenDate(Observance observance, DateValue givenDate, boolean after) {
List<DateValue> dateCache = observanceDateCache.get(observance);
if (dateCache == null) {
dateCache = new ArrayList<DateValue>();
observanceDateCache.put(observance, dateCache);
}
if (dateCache.isEmpty()) {
DateValue prev = null, cur = null;
boolean stopped = false;
RecurrenceIterator it = createIterator(observance);
while (it.hasNext()) {
cur = it.next();
dateCache.add(cur);
if (givenDate.compareTo(cur) < 0) {
//stop if we have passed the givenTime
stopped = true;
break;
}
prev = cur;
}
return after ? (stopped ? cur : null) : prev;
}
DateValue last = dateCache.get(dateCache.size() - 1);
int comparison = last.compareTo(givenDate);
if ((after && comparison <= 0) || comparison < 0) {
RecurrenceIterator it = createIterator(observance);
/*
* Calling "it.advanceTo()" here causes problems.
*
* See: https://github.com/mangstadt/biweekly/issues/126
*/
//it.advanceTo(last);
DateValue prev = null, cur = null;
boolean stopped = false;
while (it.hasNext()) {
cur = it.next();
int curCompareToLast = cur.compareTo(last);
if (curCompareToLast < 0) {
continue;
}
if (curCompareToLast > 0) {
dateCache.add(cur);
}
if (curCompareToLast == 0) {
//do nothing; don't add to dateCache
}
if (givenDate.compareTo(cur) < 0) {
//stop if we have passed the givenTime
stopped = true;
break;
}
prev = cur;
}
return after ? (stopped ? cur : null) : prev;
}
/*
* The date is somewhere in the cached list, so find it.
*
* Note: Read the "binarySearch" method Javadoc carefully for an
* explanation of its return value.
*/
int index = Collections.binarySearch(dateCache, givenDate);
if (index < 0) {
/*
* The index where the date would be if it was inside the list.
*/
index = (index * -1) - 1;
if (after) {
/*
* This is where the date would be if it was inside the list, so
* we want to return the date value that's currently at that
* position.
*/
int afterIndex = index;
return (afterIndex < dateCache.size()) ? dateCache.get(afterIndex) : null;
}
int beforeIndex = index - 1;
if (beforeIndex < 0) {
return null;
}
if (beforeIndex >= dateCache.size()) {
return dateCache.get(dateCache.size() - 1);
}
return dateCache.get(beforeIndex);
}
/*
* An exact match was found.
*/
if (after) {
int afterIndex = index + 1; //remember: the date must be >
return (afterIndex < dateCache.size()) ? dateCache.get(afterIndex) : null;
}
return dateCache.get(index); //remember: the date must be <=
}
/**
* Creates an iterator which iterates over each of the dates in an
* observance.
* @param observance the observance
* @return the iterator
*/
RecurrenceIterator createIterator(Observance observance) {
List<RecurrenceIterator> inclusions = new ArrayList<RecurrenceIterator>();
List<RecurrenceIterator> exclusions = new ArrayList<RecurrenceIterator>();
ICalDate dtstart = getValue(observance.getDateStart());
if (dtstart != null) {
DateValue dtstartValue = convertFromRawComponents(dtstart);
//add DTSTART property
inclusions.add(new DateValueRecurrenceIterator(Collections.singletonList(dtstartValue)));
//add RRULE properties
for (RecurrenceRule rrule : observance.getProperties(RecurrenceRule.class)) {
Recurrence recur = rrule.getValue();
if (recur != null) {
inclusions.add(RecurrenceIteratorFactory.createRecurrenceIterator(recur, dtstartValue, utc));
}
}
//add EXRULE properties
for (ExceptionRule exrule : observance.getProperties(ExceptionRule.class)) {
Recurrence recur = exrule.getValue();
if (recur != null) {
exclusions.add(RecurrenceIteratorFactory.createRecurrenceIterator(recur, dtstartValue, utc));
}
}
}
//add RDATE properties
List<ICalDate> rdates = new ArrayList<ICalDate>();
for (RecurrenceDates rdate : observance.getRecurrenceDates()) {
rdates.addAll(rdate.getDates());
}
Collections.sort(rdates);
inclusions.add(new DateRecurrenceIterator(rdates));
//add EXDATE properties
List<ICalDate> exdates = new ArrayList<ICalDate>();
for (ExceptionDates exdate : observance.getProperties(ExceptionDates.class)) {
exdates.addAll(exdate.getValues());
}
Collections.sort(exdates);
exclusions.add(new DateRecurrenceIterator(exdates));
RecurrenceIterator included = join(inclusions);
if (exclusions.isEmpty()) {
return included;
}
RecurrenceIterator excluded = join(exclusions);
return RecurrenceIteratorFactory.except(included, excluded);
}
private static RecurrenceIterator join(List<RecurrenceIterator> iterators) {
if (iterators.isEmpty()) {
return new EmptyRecurrenceIterator();
}
RecurrenceIterator first = iterators.get(0);
if (iterators.size() == 1) {
return first;
}
List<RecurrenceIterator> theRest = iterators.subList(1, iterators.size());
return RecurrenceIteratorFactory.join(first, theRest.toArray(new RecurrenceIterator[0]));
}
/**
* A recurrence iterator that doesn't have any elements.
*/
private static class EmptyRecurrenceIterator implements RecurrenceIterator {
public boolean hasNext() {
return false;
}
public DateValue next() {
throw new NoSuchElementException();
}
public void advanceTo(DateValue newStartUtc) {
//empty
}
public void remove() {
//RecurrenceIterator does not support this method
throw new UnsupportedOperationException();
}
}
/**
* A recurrence iterator that takes a collection of {@link DateValue}
* objects.
*/
private static class DateValueRecurrenceIterator extends IteratorWrapper<DateValue> {
public DateValueRecurrenceIterator(Collection<DateValue> dates) {
super(dates.iterator());
}
@Override
protected DateValue toDateValue(DateValue value) {
return value;
}
}
/**
* A recurrence iterator that takes a collection of {@link ICalDate}
* objects.
*/
private static class DateRecurrenceIterator extends IteratorWrapper<ICalDate> {
public DateRecurrenceIterator(Collection<ICalDate> dates) {
super(dates.iterator());
}
@Override
protected DateValue toDateValue(ICalDate value) {
return convertFromRawComponents(value);
}
}
/**
* A recurrence iterator that wraps an {@link Iterator}.
*/
private static abstract class IteratorWrapper<T> implements RecurrenceIterator {
protected final Iterator<T> it;
private DateValue next;
public IteratorWrapper(Iterator<T> it) {
this.it = it;
}
public DateValue next() {
if (next != null) {
DateValue value = next;
next = null;
return value;
}
return toDateValue(it.next());
}
public boolean hasNext() {
return next != null || it.hasNext();
}
public void advanceTo(DateValue newStartUtc) {
if (this.next != null && this.next.compareTo(newStartUtc) >= 0) {
return;
}
while (it.hasNext()) {
DateValue next = toDateValue(it.next());
if (next.compareTo(newStartUtc) >= 0) {
this.next = next;
break;
}
}
}
public void remove() {
//RecurrenceIterator does not support this method
throw new UnsupportedOperationException();
}
protected abstract DateValue toDateValue(T next);
}
/**
* Holds the timezone observance information of a particular date.
*/
public static class Boundary {
private final DateTimeValue observanceInStart, observanceAfterStart;
private final Observance observanceIn, observanceAfter;
public Boundary(DateTimeValue observanceInStart, Observance observanceIn, DateTimeValue observanceAfterStart, Observance observanceAfter) {
this.observanceInStart = observanceInStart;
this.observanceAfterStart = observanceAfterStart;
this.observanceIn = observanceIn;
this.observanceAfter = observanceAfter;
}
/**
* Gets start time of the observance that the date resides in.
* @return the time
*/
public DateTimeValue getObservanceInStart() {
return observanceInStart;
}
/**
* Gets the start time the observance that comes after the observance
* that the date resides in.
* @return the time
*/
public DateTimeValue getObservanceAfterStart() {
return observanceAfterStart;
}
/**
* Gets the observance that the date resides in.
* @return the observance
*/
public Observance getObservanceIn() {
return observanceIn;
}
/**
* Gets the observance that comes after the observance that the date
* resides in.
* @return the observance
*/
public Observance getObservanceAfter() {
return observanceAfter;
}
@Override
public String toString() {
return "Boundary [observanceInStart=" + observanceInStart + ", observanceAfterStart=" + observanceAfterStart + ", observanceIn=" + observanceIn + ", observanceAfter=" + observanceAfter + "]";
}
}
}

@ -1,257 +0,0 @@
package biweekly.io;
import java.util.ArrayList;
import java.util.List;
import biweekly.ICalVersion;
import biweekly.parameter.ICalParameters;
import biweekly.property.ICalProperty;
import biweekly.util.ICalDate;
import biweekly.util.ListMultimap;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Stores information used during the parsing of an iCalendar object.
* @author Michael Angstadt
*/
public class ParseContext {
private ICalVersion version;
private List<ParseWarning> warnings = new ArrayList<ParseWarning>();
private ListMultimap<String, TimezonedDate> timezonedDates = new ListMultimap<String, TimezonedDate>();
private List<TimezonedDate> floatingDates = new ArrayList<TimezonedDate>();
private Integer lineNumber;
private String propertyName;
/**
* Gets the version of the iCalendar object being parsed.
* @return the iCalendar version
*/
public ICalVersion getVersion() {
return version;
}
/**
* Sets the version of the iCalendar object being parsed.
* @param version the iCalendar version
*/
public void setVersion(ICalVersion version) {
this.version = version;
}
/**
* Gets the line number the parser is currently on.
* @return the line number or null if not applicable
*/
public Integer getLineNumber() {
return lineNumber;
}
/**
* Sets the line number the parser is currently on.
* @param lineNumber the line number or null if not applicable
*/
public void setLineNumber(Integer lineNumber) {
this.lineNumber = lineNumber;
}
/**
* Gets the name of the property that the parser is currently parsing.
* @return the property name (e.g. "DTSTART") or null if not applicable
*/
public String getPropertyName() {
return propertyName;
}
/**
* Sets the name of the property that the parser is currently parsing.
* @param propertyName the property name (e.g. "DTSTART") or null if not
* applicable
*/
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* Adds a parsed date to this parse context so its timezone can be applied
* to it after the iCalendar object has been parsed (if it has one).
* @param icalDate the parsed date
* @param property the property that the date value belongs to
* @param parameters the property's parameters
*/
public void addDate(ICalDate icalDate, ICalProperty property, ICalParameters parameters) {
if (!icalDate.hasTime()) {
//dates don't have timezones
return;
}
if (icalDate.getRawComponents().isUtc()) {
//it's a UTC date, so it was already parsed under the correct timezone
return;
}
//TODO handle UTC offsets within the date strings (not part of iCal standard)
String tzid = parameters.getTimezoneId();
if (tzid == null) {
addFloatingDate(property, icalDate);
} else {
addTimezonedDate(tzid, property, icalDate);
}
}
/**
* Keeps track of a date-time property value that uses a timezone so it can
* be parsed later. Timezones cannot be handled until the entire iCalendar
* object has been parsed.
* @param tzid the timezone ID (TZID parameter)
* @param property the property
* @param date the date object that was assigned to the property object
*/
public void addTimezonedDate(String tzid, ICalProperty property, ICalDate date) {
timezonedDates.put(tzid, new TimezonedDate(date, property));
}
/**
* Gets the list of date-time property values that use a timezone.
* @return the date-time property values that use a timezone (key = TZID;
* value = the property)
*/
public ListMultimap<String, TimezonedDate> getTimezonedDates() {
return timezonedDates;
}
/**
* Keeps track of a date-time property that does not have a timezone
* (floating time), so it can be added to the {@link TimezoneInfo} object
* after the iCalendar object is parsed.
* @param property the property
* @param date the property's date value
*/
public void addFloatingDate(ICalProperty property, ICalDate date) {
floatingDates.add(new TimezonedDate(date, property));
}
/**
* Gets the date-time properties that are in floating time (lacking a
* timezone).
* @return the floating date-time properties
*/
public List<TimezonedDate> getFloatingDates() {
return floatingDates;
}
/**
* Adds a parse warning.
* @param code the warning code
* @param args the warning message arguments
*/
public void addWarning(int code, Object... args) {
//@formatter:off
warnings.add(new ParseWarning.Builder(this)
.message(code, args)
.build());
//@formatter:on
}
/**
* Adds a parse warning.
* @param message the warning message
*/
public void addWarning(String message) {
//@formatter:off
warnings.add(new ParseWarning.Builder(this)
.message(message)
.build());
//@formatter:on
}
/**
* Gets the parse warnings.
* @return the parse warnings
*/
public List<ParseWarning> getWarnings() {
return warnings;
}
/**
* Represents a property whose date-time value has a timezone.
* @author Michael Angstadt
*/
public static class TimezonedDate {
private final ICalDate date;
private final ICalProperty property;
/**
* @param date the date object that was assigned to the property object
* @param property the property object
*/
public TimezonedDate(ICalDate date, ICalProperty property) {
this.date = date;
this.property = property;
}
/**
* Gets the date object that was assigned to the property object (should
* be parsed under the JVM's default timezone)
* @return the date object
*/
public ICalDate getDate() {
return date;
}
/**
* Gets the property object.
* @return the property
*/
public ICalProperty getProperty() {
return property;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((date == null) ? 0 : date.hashCode());
result = prime * result + ((property == null) ? 0 : property.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
TimezonedDate other = (TimezonedDate) obj;
if (date == null) {
if (other.date != null) return false;
} else if (!date.equals(other.date)) return false;
if (property == null) {
if (other.property != null) return false;
} else if (!property.equals(other.property)) return false;
return true;
}
}
}

@ -1,186 +0,0 @@
package biweekly.io;
import biweekly.Messages;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a warning that occurred during the parsing of an iCalendar object.
* @author Michael Angstadt
*/
public class ParseWarning {
private final Integer code, lineNumber;
private final String propertyName, message;
private ParseWarning(Integer lineNumber, String propertyName, Integer code, String message) {
this.lineNumber = lineNumber;
this.propertyName = propertyName;
this.code = code;
this.message = message;
}
/**
* Gets the warning code.
* @return the warning code or null if no code was specified
*/
public Integer getCode() {
return code;
}
/**
* Gets the line number the warning occurred on.
* @return the line number or null if not applicable
*/
public Integer getLineNumber() {
return lineNumber;
}
/**
* Gets the warning message
* @return the warning message
*/
public String getMessage() {
return message;
}
/**
* Gets the name of the property that the warning occurred on.
* @return the property name (e.g. "DTSTART") or null if not applicable
*/
public String getPropertyName() {
return propertyName;
}
@Override
public String toString() {
String message = this.message;
if (code != null) {
message = "(" + code + ") " + message;
}
if (lineNumber == null && propertyName == null) {
return message;
}
String key = null;
if (lineNumber != null && propertyName == null) {
key = "parse.line";
} else if (lineNumber == null && propertyName != null) {
key = "parse.prop";
} else if (lineNumber != null && propertyName != null) {
key = "parse.lineWithProp";
}
return Messages.INSTANCE.getMessage(key, lineNumber, propertyName, message);
}
/**
* Constructs instances of the {@link ParseWarning} class.
* @author Michael Angstadt
*/
public static class Builder {
private Integer lineNumber, code;
private String propertyName, message;
/**
* Creates an empty builder.
*/
public Builder() {
//empty
}
/**
* Initializes the builder with data from the parse context.
* @param context the parse context
*/
public Builder(ParseContext context) {
lineNumber(context.getLineNumber());
propertyName(context.getPropertyName());
}
/**
* Sets the name of the property that the warning occurred on.
* @param propertyName the property name (e.g. "DTSTART") or null if not
* applicable
* @return this
*/
public Builder propertyName(String propertyName) {
this.propertyName = propertyName;
return this;
}
/**
* Sets the line number that the warning occurred on.
* @param lineNumber the line number or null if not applicable
* @return this
*/
public Builder lineNumber(Integer lineNumber) {
this.lineNumber = lineNumber;
return this;
}
/**
* Sets the warning message.
* @param code the message code
* @param args the message arguments
* @return this
*/
public Builder message(int code, Object... args) {
this.code = code;
message = Messages.INSTANCE.getParseMessage(code, args);
return this;
}
/**
* Sets the warning message.
* @param message the warning message
* @return this
*/
public Builder message(String message) {
code = null;
this.message = message;
return this;
}
/**
* Sets the warning message, based on the contents of a
* {@link CannotParseException}.
* @param exception the exception
* @return this
*/
public Builder message(CannotParseException exception) {
return message(exception.getCode(), exception.getArgs());
}
/**
* Builds the {@link ParseWarning} object.
* @return the {@link ParseWarning} object
*/
public ParseWarning build() {
return new ParseWarning(lineNumber, propertyName, code, message);
}
}
}

@ -1,56 +0,0 @@
package biweekly.io;
import biweekly.ICalendar;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Thrown during the reading or writing of an iCalendar property to show that
* the property should not be written to the iCalendar data stream or not be
* included in the parsed {@link ICalendar} object.
* @author Michael Angstadt
*/
public class SkipMeException extends RuntimeException {
private static final long serialVersionUID = 3384029056232963767L;
private final String reason;
/**
* Creates a new "skip me" exception.
* @param reason the reason why the property was skipped
*/
public SkipMeException(String reason) {
super(reason);
this.reason = reason;
}
/**
* Gets the reason why the property was skipped.
* @return the reason
*/
public String getReason() {
return reason;
}
}

@ -1,398 +0,0 @@
package biweekly.io;
import static biweekly.io.DataModelConverter.convert;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.component.VTimezone;
import biweekly.io.ParseContext.TimezonedDate;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.Daylight;
import biweekly.property.ICalProperty;
import biweekly.property.Timezone;
import biweekly.property.ValuedProperty;
import biweekly.util.ICalDate;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Parses iCalendar objects from a data stream.
* @author Michael Angstadt
*/
public abstract class StreamReader implements Closeable {
protected final List<ParseWarning> warnings = new ArrayList<ParseWarning>();
protected ScribeIndex index = new ScribeIndex();
protected ParseContext context;
private TimeZone defaultTimezone = TimeZone.getDefault();
private GlobalTimezoneIdResolver globalTimezoneIdResolver = new DefaultGlobalTimezoneIdResolver();
/**
* <p>
* Registers an experimental property scribe. Can also be used to override
* the scribe of a standard property (such as DTSTART). Calling this method
* is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}.
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) {
index.register(scribe);
}
/**
* <p>
* Registers an experimental component scribe. Can also be used to override
* the scribe of a standard component (such as VEVENT). Calling this method
* is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}.
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalComponentScribe<? extends ICalComponent> scribe) {
index.register(scribe);
}
/**
* Gets the object that manages the component/property scribes.
* @return the scribe index
*/
public ScribeIndex getScribeIndex() {
return index;
}
/**
* Sets the object that manages the component/property scribes.
* @param index the scribe index
*/
public void setScribeIndex(ScribeIndex index) {
this.index = index;
}
/**
* Gets the warnings from the last iCalendar object that was read.
* @return the warnings or empty list if there were no warnings
*/
public List<ParseWarning> getWarnings() {
return new ArrayList<ParseWarning>(warnings);
}
/**
* Gets the timezone that will be used for parsing date property values that
* are floating or that have invalid timezone definitions assigned to them.
* Defaults to {@link TimeZone#getDefault}.
* @return the default timezone
*/
public TimeZone getDefaultTimezone() {
return defaultTimezone;
}
/**
* Sets the timezone that will be used for parsing date property values that
* are floating or that have invalid timezone definitions assigned to them.
* Defaults to {@link TimeZone#getDefault}.
* @param defaultTimezone the default timezone
*/
public void setDefaultTimezone(TimeZone defaultTimezone) {
this.defaultTimezone = defaultTimezone;
}
/**
* Gets the resolver that maps global timezone IDs to Java {@link TimeZone}
* objects. Defaults to {@link DefaultGlobalTimezoneIdResolver}.
* @return the resolver
*/
public GlobalTimezoneIdResolver getGlobalTimezoneIdResolver() {
return globalTimezoneIdResolver;
}
/**
* Sets the resolver that maps global timezone IDs to Java {@link TimeZone}
* objects. Defaults to {@link DefaultGlobalTimezoneIdResolver}.
* @param globalTimezoneIdResolver the resolver
*/
public void setGlobalTimezoneIdResolver(GlobalTimezoneIdResolver globalTimezoneIdResolver) {
this.globalTimezoneIdResolver = globalTimezoneIdResolver;
}
/**
* Reads all iCalendar objects from the data stream.
* @return the iCalendar objects
* @throws IOException if there's a problem reading from the stream
*/
public List<ICalendar> readAll() throws IOException {
List<ICalendar> icals = new ArrayList<ICalendar>();
ICalendar ical;
while ((ical = readNext()) != null) {
icals.add(ical);
}
return icals;
}
/**
* Reads the next iCalendar object from the data stream.
* @return the next iCalendar object or null if there are no more
* @throws IOException if there's a problem reading from the stream
*/
public ICalendar readNext() throws IOException {
warnings.clear();
context = new ParseContext();
ICalendar ical = _readNext();
if (ical == null) {
return null;
}
ical.setVersion(context.getVersion());
handleTimezones(ical);
return ical;
}
/**
* Reads the next iCalendar object from the data stream.
* @return the next iCalendar object or null if there are no more
* @throws IOException if there's a problem reading from the stream
*/
protected abstract ICalendar _readNext() throws IOException;
private void handleTimezones(ICalendar ical) {
TimezoneInfo tzinfo = ical.getTimezoneInfo();
//convert vCalendar DAYLIGHT and TZ properties to a VTIMEZONE component
TimezoneAssignment vcalTimezone = extractVCalTimezone(ical);
//assign a TimeZone object to each VTIMEZONE component.
Iterator<VTimezone> it = ical.getComponents(VTimezone.class).iterator();
while (it.hasNext()) {
VTimezone component = it.next();
//make sure the component has an ID
String id = ValuedProperty.getValue(component.getTimezoneId());
if (id == null || id.trim().isEmpty()) {
//note: do not remove invalid VTIMEZONE components from the ICalendar object
warnings.add(new ParseWarning.Builder().message(39).build());
continue;
}
TimeZone timezone = new ICalTimeZone(component);
tzinfo.getTimezones().add(new TimezoneAssignment(timezone, component));
//remove the component from the ICalendar object
it.remove();
}
boolean userChangedTheDefaultTimezone = !defaultTimezone.equals(TimeZone.getDefault());
if (vcalTimezone != null) {
//vCal: parse floating dates according to the DAYLIGHT and TZ properties (which were converted to a VTIMEZONE component)
Calendar cal = Calendar.getInstance(vcalTimezone.getTimeZone());
for (TimezonedDate timezonedDate : context.getFloatingDates()) {
reparseDateUnderDifferentTimezone(timezonedDate, cal);
}
} else {
//iCal: treat floating dates as floating dates
for (TimezonedDate timezonedDate : context.getFloatingDates()) {
tzinfo.setFloating(timezonedDate.getProperty(), true);
}
//convert all floating dates to the default timezone
if (userChangedTheDefaultTimezone) {
Calendar cal = Calendar.getInstance(defaultTimezone);
for (TimezonedDate timezonedDate : context.getFloatingDates()) {
reparseDateUnderDifferentTimezone(timezonedDate, cal);
}
}
}
//convert all date values to their appropriate timezone
for (Map.Entry<String, List<TimezonedDate>> entry : context.getTimezonedDates()) {
String tzid = entry.getKey();
//determine which timezone is associated with the given TZID
TimezoneAssignment assignment = determineTimezoneAssignment(tzid, tzinfo);
/*
* If a timezone assignment could not be found for the given TZID
* and the user did not change the default timezone, then there is
* no need to further process the properties that are assigned to
* this TZID--the date value should remain unchanged (parsed under
* the local machine's default timezone), and its TZID parameter
* should also remain.
*/
if (assignment == null && !userChangedTheDefaultTimezone) {
continue;
}
//convert each property to the timezone
TimeZone tz = (assignment == null) ? defaultTimezone : assignment.getTimeZone();
Calendar cal = Calendar.getInstance(tz);
for (TimezonedDate timezonedDate : entry.getValue()) {
ICalProperty property = timezonedDate.getProperty();
if (assignment != null) {
tzinfo.setTimezone(property, assignment);
/*
* Only remove the TZID parameter if the TZID is *valid*.
* Invalid TZID parameters should remain so that user can
* inspect the invalid information.
*/
property.getParameters().setTimezoneId(null);
}
reparseDateUnderDifferentTimezone(timezonedDate, cal);
}
}
}
private void reparseDateUnderDifferentTimezone(TimezonedDate timezonedDate, Calendar cal) {
ICalDate date = timezonedDate.getDate();
//parse its raw date components under its real timezone
Date realDate = date.getRawComponents().toDate(cal);
//update the Date object with the new timestamp
date.setTime(realDate.getTime());
}
/**
* Determines the timezone definition that is associated with the given ID.
* @param tzid the timezone ID
* @param tzinfo the timezone settings of the iCalendar object
* @return the timezone definition or null to use the default timezone
*/
private TimezoneAssignment determineTimezoneAssignment(String tzid, TimezoneInfo tzinfo) {
boolean isOlsenId = tzid.startsWith("/");
//HANDLE OLSEN IDS======================================================
if (isOlsenId) {
String globalId = tzid.substring(1);
TimeZone timezone = globalTimezoneIdResolver.resolve(globalId);
if (timezone != null) {
/*
* Olsen ID is valid. Everything is Ok.
*/
TimezoneAssignment assignment = new TimezoneAssignment(timezone, globalId);
tzinfo.getTimezones().add(assignment);
return assignment;
}
/*
* Even though the TZID is marked as an Olsen ID, and the timezone
* isn't recognized by Java, try looking for a VTIMEZONE component
* that matches it.
*
* This is done as a courtesy and is not required by the specs.
*/
TimezoneAssignment assignment = tzinfo.getTimezoneById(tzid);
int warning;
if (assignment == null) {
/*
* TZID does not match any VTIMEZONE components, use the default
* timezone.
*/
warning = 38;
} else {
warning = 43;
}
warnings.add(new ParseWarning.Builder().message(warning, tzid).build());
return assignment;
}
//HANDLE VTIMEZONE COMPONENT IDS========================================
TimezoneAssignment assignment = tzinfo.getTimezoneById(tzid);
if (assignment != null) {
/*
* VTIMEZONE component with the given TZID was found.
* Everything is Ok.
*/
return assignment;
}
/*
* Try treating the TZID as an Olsen timezone ID, even though it does
* not start with a forward slash.
*
* This is done as a courtesy for users who do not know they must prefix
* Olsen IDs with a forward slash. It is not required by the specs.
*/
String globalId = tzid;
TimeZone timezone = globalTimezoneIdResolver.resolve(globalId);
int warning;
if (timezone == null) {
/*
* TZID is not a valid Olsen ID, use the default timezone.
*/
warning = 38;
assignment = null;
} else {
/*
* TZID was successfully parsed as an Olsen ID.
*/
warning = 37;
assignment = new TimezoneAssignment(timezone, globalId);
tzinfo.getTimezones().add(assignment);
}
warnings.add(new ParseWarning.Builder().message(warning, globalId).build());
return assignment;
}
private TimezoneAssignment extractVCalTimezone(ICalendar ical) {
List<Daylight> daylights = ical.removeProperties(Daylight.class);
List<Timezone> timezones = ical.removeProperties(Timezone.class);
Timezone timezone = timezones.isEmpty() ? null : timezones.get(0);
VTimezone vcalComponent = convert(daylights, timezone);
if (vcalComponent == null) {
return null;
}
TimeZone icalTimezone = new ICalTimeZone(vcalComponent);
TimezoneInfo tzinfo = ical.getTimezoneInfo();
TimezoneAssignment assignment = new TimezoneAssignment(icalTimezone, vcalComponent);
tzinfo.setDefaultTimezone(assignment);
return assignment;
}
}

@ -1,216 +0,0 @@
package biweekly.io;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.Messages;
import biweekly.component.ICalComponent;
import biweekly.component.RawComponent;
import biweekly.component.VTimezone;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
import biweekly.property.RawProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Writes iCalendar objects to a data stream.
* @author Michael Angstadt
*/
public abstract class StreamWriter implements Closeable {
protected ScribeIndex index = new ScribeIndex();
protected WriteContext context;
protected TimezoneAssignment globalTimezone;
private TimezoneInfo tzinfo;
/**
* Writes an iCalendar object to the data stream.
* @param ical the iCalendar object to write
* @throws IllegalArgumentException if the scribe class for a component or
* property object cannot be found (only happens when an experimental
* property/component scribe is not registered with the
* {@code registerScribe} method.)
* @throws IOException if there's a problem writing to the data stream
*/
public void write(ICalendar ical) throws IOException {
Collection<Class<?>> unregistered = findScribeless(ical);
if (!unregistered.isEmpty()) {
List<String> classNames = new ArrayList<String>(unregistered.size());
for (Class<?> clazz : unregistered) {
classNames.add(clazz.getName());
}
throw Messages.INSTANCE.getIllegalArgumentException(13, classNames);
}
tzinfo = ical.getTimezoneInfo();
context = new WriteContext(getTargetVersion(), tzinfo, globalTimezone);
_write(ical);
}
/**
* Gets the {@link VTimezone} components that need to be written to the
* output stream.
* @return the components
*/
protected Collection<VTimezone> getTimezoneComponents() {
if (globalTimezone != null) {
VTimezone component = globalTimezone.getComponent();
return (component == null) ? Collections.<VTimezone> emptyList() : Collections.singletonList(component);
}
return tzinfo.getComponents();
}
/**
* Gets the version that the next iCalendar object will be written as.
* @return the version
*/
protected abstract ICalVersion getTargetVersion();
/**
* Writes an iCalendar object to the data stream.
* @param ical the iCalendar object to write
* @throws IOException if there's a problem writing to the data stream
*/
protected abstract void _write(ICalendar ical) throws IOException;
/**
* Gets the timezone that all date/time property values will be formatted
* in. If set, this setting will override the timezone information
* associated with each {@link ICalendar} object.
* @return the global timezone or null if not set (defaults to null)
*/
public TimezoneAssignment getGlobalTimezone() {
return globalTimezone;
}
/**
* Sets the timezone that all date/time property values will be formatted
* in. This is a convenience method that overrides the timezone information
* associated with each {@link ICalendar} object that is passed into this
* writer.
* @param globalTimezone the global timezone or null not to set a global
* timezone (defaults to null)
*/
public void setGlobalTimezone(TimezoneAssignment globalTimezone) {
this.globalTimezone = globalTimezone;
}
/**
* <p>
* Registers an experimental property scribe. Can also be used to override
* the scribe of a standard property (such as DTSTART). Calling this method
* is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}.
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) {
index.register(scribe);
}
/**
* <p>
* Registers an experimental component scribe. Can also be used to override
* the scribe of a standard component (such as VEVENT). Calling this method
* is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}.
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalComponentScribe<? extends ICalComponent> scribe) {
index.register(scribe);
}
/**
* Gets the object that manages the component/property scribes.
* @return the scribe index
*/
public ScribeIndex getScribeIndex() {
return index;
}
/**
* Sets the object that manages the component/property scribes.
* @param scribe the scribe index
*/
public void setScribeIndex(ScribeIndex scribe) {
this.index = scribe;
}
/**
* Gets the component/property classes that don't have scribes associated
* with them.
* @param ical the iCalendar object
* @return the component/property classes
*/
private Collection<Class<?>> findScribeless(ICalendar ical) {
Set<Class<?>> unregistered = new HashSet<Class<?>>();
LinkedList<ICalComponent> components = new LinkedList<ICalComponent>();
components.add(ical);
while (!components.isEmpty()) {
ICalComponent component = components.removeLast();
Class<? extends ICalComponent> componentClass = component.getClass();
if (componentClass != RawComponent.class && index.getComponentScribe(componentClass) == null) {
unregistered.add(componentClass);
}
for (Map.Entry<Class<? extends ICalProperty>, List<ICalProperty>> entry : component.getProperties()) {
List<ICalProperty> properties = entry.getValue();
if (properties.isEmpty()) {
continue;
}
Class<? extends ICalProperty> clazz = entry.getKey();
if (clazz != RawProperty.class && index.getPropertyScribe(clazz) == null) {
unregistered.add(clazz);
}
}
components.addAll(component.getComponents().values());
}
return unregistered;
}
}

@ -1,127 +0,0 @@
package biweekly.io;
import java.util.TimeZone;
import biweekly.Messages;
import biweekly.component.VTimezone;
import biweekly.property.TimezoneId;
import biweekly.property.ValuedProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a timezone definition that is used within an iCalendar object.
* @author Michael Angstadt
*/
public class TimezoneAssignment {
private final TimeZone timezone;
private final VTimezone component;
private final String globalId;
/**
* Creates a timezone that will be inserted into the iCalendar object as a
* VTIMEZONE component. This what most iCalendar files use.
* @param timezone the Java timezone object
* @param component the iCalendar component
* @throws IllegalArgumentException if the given {@link VTimezone} component
* doesn't have a {@link TimezoneId} property
*/
public TimezoneAssignment(TimeZone timezone, VTimezone component) {
String id = ValuedProperty.getValue(component.getTimezoneId());
if (id == null || id.trim().isEmpty()) {
throw Messages.INSTANCE.getIllegalArgumentException(14);
}
this.timezone = timezone;
this.component = component;
this.globalId = null;
}
/**
* <p>
* Creates a timezone that will be inserted into the iCalendar object as a
* "global ID". This means that a {@link VTimezone} component containing the
* timezone definition will NOT be inserted into the iCalendar object.
* </p>
* <p>
* Because the timezone definition is not included inside of the iCalendar
* object, the client consuming the iCalendar object must know how to
* interpret such an ID. The iCalendar specification does not specify a list
* of such IDs, but suggests using the naming convention of an existing
* timezone specification, such as the
* <a href="http://www.twinsun.com/tz/tz-link.htm">public-domain TZ
* database</a>.
* </p>
* @param timezone the Java timezone object
* @param globalId the global ID (e.g. "America/New_York")
*/
public TimezoneAssignment(TimeZone timezone, String globalId) {
this.timezone = timezone;
this.component = null;
this.globalId = globalId;
}
/**
* Creates a timezone whose VTIMEZONE component is downloaded from
* <a href="http://www.tzurl.org">tzurl.org</a>.
* @param timezone the Java timezone object
* @param outlookCompatible true to download a {@link VTimezone} component
* that is tailored for Microsoft Outlook email clients, false to download a
* standards-based one.
* @return the timezone assignment
* @throws IllegalArgumentException if an appropriate VTIMEZONE component
* cannot be found on the website
*/
public static TimezoneAssignment download(TimeZone timezone, boolean outlookCompatible) {
TzUrlDotOrgGenerator generator = new TzUrlDotOrgGenerator(outlookCompatible);
VTimezone component = generator.generate(timezone);
return new TimezoneAssignment(timezone, component);
}
/**
* Gets the Java object associated with the timezone.
* @return the Java object
*/
public TimeZone getTimeZone() {
return timezone;
}
/**
* Gets the iCalendar component associated with the timezone.
* @return the component or null if the timezone uses a global ID
*/
public VTimezone getComponent() {
return component;
}
/**
* Gets the global ID associated with the timezone.
* @return the global ID or null if the timezone uses an iCalendar component
*/
public String getGlobalId() {
return globalId;
}
}

@ -1,305 +0,0 @@
package biweekly.io;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import biweekly.component.VTimezone;
import biweekly.property.ICalProperty;
import biweekly.property.TimezoneId;
import biweekly.property.ValuedProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Holds the timezone-related settings of an iCalendar object.
* @author Michael Angstadt
*/
public class TimezoneInfo {
@SuppressWarnings("serial")
private final Collection<TimezoneAssignment> assignments = new HashSet<TimezoneAssignment>() {
@Override
public boolean remove(Object assignment) {
//remove all property assignments
Collection<TimezoneAssignment> values = propertyTimezones.values();
while (values.remove(assignment)) {
//empty
}
return super.remove(assignment);
}
};
private final Map<ICalProperty, TimezoneAssignment> propertyTimezones = new IdentityHashMap<ICalProperty, TimezoneAssignment>();
private final List<ICalProperty> floatingProperties = new ArrayList<ICalProperty>();
private TimezoneAssignment defaultTimezone;
private boolean globalFloatingTime = false;
/**
* Gets all the timezones assigned to this object.
* @return the timezones (collection is mutable)
*/
public Collection<TimezoneAssignment> getTimezones() {
return assignments;
}
/**
* <p>
* Gets the timezone to format all date/time values in (by default, all
* dates are formatted in UTC).
* </p>
* <p>
* The default timezone is not used for properties that are configured to
* use their own timezone (see {@link #setTimezone}).
* </p>
* @return the timezone or null if using UTC
*/
public TimezoneAssignment getDefaultTimezone() {
return defaultTimezone;
}
/**
* <p>
* Sets the timezone to format all date/time values in (by default, all
* dates are formatted in UTC).
* </p>
* <p>
* The default timezone is not used for properties that are configured to
* use their own timezone (see {@link #setTimezone}).
* </p>
* @param timezone the timezone or null to use UTC
*/
public void setDefaultTimezone(TimezoneAssignment timezone) {
if (timezone == null) {
if (defaultTimezone != null && !propertyTimezones.containsValue(defaultTimezone)) {
assignments.remove(defaultTimezone);
}
} else {
assignments.add(timezone);
}
defaultTimezone = timezone;
}
/**
* Assigns a timezone to a specific property.
* @param property the property
* @param timezone the timezone or null to format the property according to
* the default timezone (see {@link #setDefaultTimezone}).
*/
public void setTimezone(ICalProperty property, TimezoneAssignment timezone) {
if (timezone == null) {
TimezoneAssignment existing = propertyTimezones.remove(property);
if (existing != null && existing != defaultTimezone && !propertyTimezones.containsValue(existing)) {
assignments.remove(existing);
}
return;
}
assignments.add(timezone);
propertyTimezones.put(property, timezone);
}
/**
* Gets the timezone that is assigned to a property.
* @param property the property
* @return the timezone or null if no timezone is assigned to the property
*/
public TimezoneAssignment getTimezone(ICalProperty property) {
return propertyTimezones.get(property);
}
/**
* <p>
* Determines the timezone that a particular property should be formatted in
* when written to an output stream.
* </p>
* <p>
* Note: You should call {@link #isFloating} first, to determine if the
* property's value is floating (without a timezone).
* </p>
* @param property the property
* @return the timezone or null for UTC
*/
public TimezoneAssignment getTimezoneToWriteIn(ICalProperty property) {
TimezoneAssignment assignment = getTimezone(property);
return (assignment == null) ? defaultTimezone : assignment;
}
/**
* Gets the timezone whose {@link VTimezone} component contains a
* {@link TimezoneId} property with the given value.
* @param tzid the value of the {@link TimezoneId} property
* @return the timezone or null if not found
*/
public TimezoneAssignment getTimezoneById(String tzid) {
for (TimezoneAssignment assignment : assignments) {
VTimezone component = assignment.getComponent();
if (component == null) {
continue;
}
String componentId = ValuedProperty.getValue(component.getTimezoneId());
if (tzid.equals(componentId)) {
return assignment;
}
}
return null;
}
/**
* <p>
* Gets whether to format all date/time values as floating times (defaults
* to false).
* </p>
* <p>
* This setting does not apply to properties whose floating time settings
* are configured individually (see: {@link #setFloating}) or that are
* configured to use their own timezone (see {@link #setTimezone}).
* </p>
* <p>
* A floating time value does not have a timezone associated with it, and is
* to be interpreted as being in the local timezone of the computer that is
* consuming the iCalendar object.
* </p>
* @return true if enabled, false if disabled
*/
public boolean isGlobalFloatingTime() {
return globalFloatingTime;
}
/**
* <p>
* Sets whether to format all date/time values as floating times (defaults
* to false).
* </p>
* <p>
* This setting does not apply to properties whose floating time settings
* are configured individually (see: {@link #setFloating}) or that are
* configured to use their own timezone (see {@link #setTimezone}).
* </p>
* <p>
* A floating time value does not have a timezone associated with it, and is
* to be interpreted as being in the local timezone of the computer that is
* consuming the iCalendar object.
* </p>
* @param enable true to enable, false to disable
*/
public void setGlobalFloatingTime(boolean enable) {
globalFloatingTime = enable;
}
/**
* Determines if a property value should be formatted in floating time when
* written to an output stream.
* @param property the property
* @return true to format in floating time, false not to
*/
public boolean isFloating(ICalProperty property) {
if (containsIdentity(floatingProperties, property)) {
return true;
}
if (propertyTimezones.containsKey(property)) {
return false;
}
return globalFloatingTime;
}
/**
* <p>
* Sets whether a property value should be formatted in floating time when
* written to an output stream (by default, floating time is disabled for
* all properties).
* </p>
* <p>
* A floating time value does not have a timezone associated with it, and is
* to be interpreted as being in the local timezone of the computer that is
* consuming the iCalendar object.
* </p>
* @param property the property
* @param enable true to enable floating time for this property, false to
* disable
*/
public void setFloating(ICalProperty property, boolean enable) {
if (enable) {
floatingProperties.add(property);
} else {
removeIdentity(floatingProperties, property);
}
}
/**
* Gets all of the iCalendar {@link VTimezone} components that have been
* registered with this object.
* @return the components (this collection is immutable)
*/
public Collection<VTimezone> getComponents() {
List<VTimezone> components = new ArrayList<VTimezone>(assignments.size());
for (TimezoneAssignment assignment : assignments) {
VTimezone component = assignment.getComponent();
if (component != null) {
components.add(component);
}
}
return Collections.unmodifiableList(components);
}
/**
* Removes an object from a list using reference equality.
* @param list the list
* @param object the object to remove
*/
private static <T> void removeIdentity(List<T> list, T object) {
Iterator<T> it = list.iterator();
while (it.hasNext()) {
if (object == it.next()) {
it.remove();
}
}
}
/**
* Searches for an item in a list using reference equality.
* @param list the list
* @param object the object to search for
* @return true if the object was found, false if not
*/
private static <T> boolean containsIdentity(List<T> list, T object) {
for (T item : list) {
if (item == object) {
return true;
}
}
return false;
}
}

@ -1,158 +0,0 @@
package biweekly.io;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import biweekly.ICalendar;
import biweekly.component.VTimezone;
import biweekly.io.text.ICalReader;
import biweekly.property.TimezoneId;
import biweekly.property.ValuedProperty;
import biweekly.util.IOUtils;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Downloads {@link VTimezone} components from <a
* href="http://www.tzurl.org">tzurl.org</a>. This class is thread-safe.
* @author Michael Angstadt
*/
public class TzUrlDotOrgGenerator {
private static final Map<URI, VTimezone> cache = Collections.synchronizedMap(new HashMap<URI, VTimezone>());
private final String baseUrl;
/**
* Creates a new tzurl.org generator.
* @param outlookCompatible true to download {@link VTimezone} components
* that are tailored for Microsoft Outlook email clients, false to download
* standards-based ones
*/
public TzUrlDotOrgGenerator(boolean outlookCompatible) {
baseUrl = "http://www.tzurl.org/zoneinfo" + (outlookCompatible ? "-outlook" : "") + "/";
}
/**
* Generates an iCalendar {@link VTimezone} components from a Java
* {@link TimeZone} object.
* @param timezone the timezone object
* @return the timezone component
* @throws IllegalArgumentException if a timezone definition cannot be found
*/
public VTimezone generate(TimeZone timezone) throws IllegalArgumentException {
URI uri;
try {
uri = new URI(buildUrl(timezone));
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
VTimezone component = cache.get(uri);
if (component != null) {
return component.copy();
}
ICalendar ical;
ICalReader reader = null;
try {
reader = new ICalReader(getInputStream(uri));
ical = reader.readNext();
} catch (FileNotFoundException e) {
throw notFound(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(reader);
}
/*
* There should always be exactly one iCalendar object in the file, but
* check to be sure.
*/
if (ical == null) {
throw notFound(null);
}
/*
* There should always be exactly one VTIMEZONE component, but check to
* be sure.
*/
TimezoneInfo tzinfo = ical.getTimezoneInfo();
Collection<VTimezone> components = tzinfo.getComponents();
if (components.isEmpty()) {
components = ical.getComponents(VTimezone.class); //VTIMEZONE components without TZID properties are treated as ordinary components
if (components.isEmpty()) {
throw notFound(null);
}
}
component = components.iterator().next();
/*
* There should always be a TZID property, but just in case there there
* isn't one, create one.
*/
TimezoneId id = component.getTimezoneId();
if (id == null) {
component.setTimezoneId(timezone.getID());
} else {
String value = ValuedProperty.getValue(id);
if (value == null || value.trim().isEmpty()) {
id.setValue(timezone.getID());
}
}
cache.put(uri, component);
return component.copy();
}
private String buildUrl(TimeZone timezone) {
return baseUrl + timezone.getID();
}
//for unit testing
InputStream getInputStream(URI uri) throws IOException {
return uri.toURL().openStream();
}
/**
* Clears the internal cache of downloaded timezone definitions.
*/
public static void clearCache() {
cache.clear();
}
private static IllegalArgumentException notFound(Exception e) {
return new IllegalArgumentException("Timezone ID not recognized.", e);
}
}

@ -1,117 +0,0 @@
package biweekly.io;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.util.ICalDate;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Stores information used to write the properties in an iCalendar object.
* @author Michael Angstadt
*/
public class WriteContext {
private final ICalVersion version;
private final TimezoneInfo timezoneOptions;
private final TimezoneAssignment globalTimezone;
private final List<Date> dates = new ArrayList<Date>();
private ICalComponent parent;
public WriteContext(ICalVersion version, TimezoneInfo timezoneOptions, TimezoneAssignment globalTimezone) {
this.version = version;
this.timezoneOptions = timezoneOptions;
this.globalTimezone = globalTimezone;
}
/**
* Gets the version of the iCalendar object that is being written.
* @return the iCalendar version
*/
public ICalVersion getVersion() {
return version;
}
/**
* Gets the timezone options for this iCalendar object.
* @return the timezone options
*/
public TimezoneInfo getTimezoneInfo() {
return timezoneOptions;
}
/**
* Gets the global timezone to format all date/time values in, regardless of
* each individual {@link ICalendar}'s timezone settings.
* @return the global timezone or null if not set
*/
public TimezoneAssignment getGlobalTimezone() {
return globalTimezone;
}
/**
* Gets the parent component of the property that is being written.
* @return the parent component
*/
public ICalComponent getParent() {
return parent;
}
/**
* Sets the parent component of the property that is being written.
* @param parent the parent component
*/
public void setParent(ICalComponent parent) {
this.parent = parent;
}
/**
* Gets the timezoned date-time property values that are in the iCalendar
* object.
* @return the timezoned date-time property values
*/
public List<Date> getDates() {
return dates;
}
/**
* Records the timezoned date-time values that are being written. This is
* used to generate a DAYLIGHT property for vCalendar objects.
* @param floating true if the date is floating, false if not
* @param tz the timezone to format the date in or null for UTC
* @param date the date value
*/
public void addDate(ICalDate date, boolean floating, TimeZone tz) {
if (date != null && date.hasTime() && !floating && tz != null) {
dates.add(date);
}
}
}

@ -1,74 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import biweekly.Biweekly;
import biweekly.io.StreamReader;
import biweekly.io.json.JCalReader;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing jCals (JSON-encoded iCalendar objects).
* @see Biweekly#parseJson(InputStream)
* @see Biweekly#parseJson(File)
* @see Biweekly#parseJson(Reader)
* @author Michael Angstadt
*/
public class ChainingJsonParser<T extends ChainingJsonParser<?>> extends ChainingParser<T> {
public ChainingJsonParser(String string) {
super(string);
}
public ChainingJsonParser(InputStream in) {
super(in);
}
public ChainingJsonParser(Reader reader) {
super(reader);
}
public ChainingJsonParser(File file) {
super(file);
}
@Override
StreamReader constructReader() throws IOException {
if (string != null) {
return new JCalReader(string);
}
if (in != null) {
return new JCalReader(in);
}
if (reader != null) {
return new JCalReader(reader);
}
return new JCalReader(file);
}
}

@ -1,64 +0,0 @@
package biweekly.io.chain;
import java.io.IOException;
import java.util.List;
import biweekly.Biweekly;
import biweekly.ICalendar;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing jCals (JSON-encoded iCalendar objects) from
* strings.
* @see Biweekly#parseJson(String)
* @author Michael Angstadt
*/
public class ChainingJsonStringParser extends ChainingJsonParser<ChainingJsonStringParser> {
public ChainingJsonStringParser(String json) {
super(json);
}
@Override
public ICalendar first() {
try {
return super.first();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
@Override
public List<ICalendar> all() {
try {
return super.all();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
}

@ -1,154 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.TimeZone;
import biweekly.Biweekly;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.json.JCalWriter;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for writing jCals (JSON-encoded iCalendar objects).
* @see Biweekly#writeJson(Collection)
* @see Biweekly#writeJson(ICalendar...)
* @author Michael Angstadt
*/
public class ChainingJsonWriter extends ChainingWriter<ChainingJsonWriter> {
private boolean prettyPrint = false;
/**
* @param icals the iCalendar objects to write
*/
public ChainingJsonWriter(Collection<ICalendar> icals) {
super(icals);
}
/**
* Sets whether or not to pretty-print the JSON.
* @param prettyPrint true to pretty-print it, false not to (defaults to
* false)
* @return this
*/
public ChainingJsonWriter prettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
return this;
}
@Override
public ChainingJsonWriter tz(TimeZone defaultTimeZone, boolean outlookCompatible) {
return super.tz(defaultTimeZone, outlookCompatible);
}
@Override
public ChainingJsonWriter register(ICalPropertyScribe<? extends ICalProperty> scribe) {
return super.register(scribe);
}
@Override
public ChainingJsonWriter register(ICalComponentScribe<? extends ICalComponent> scribe) {
return super.register(scribe);
}
/**
* Writes the iCalendar objects to a string.
* @return the JSON string
*/
public String go() {
StringWriter sw = new StringWriter();
try {
go(sw);
} catch (IOException e) {
//should never be thrown because we're writing to a string
throw new RuntimeException(e);
}
return sw.toString();
}
/**
* Writes the iCalendar objects to an output stream.
* @param out the output stream to write to
* @throws IOException if there's a problem writing to the output stream
*/
public void go(OutputStream out) throws IOException {
go(new JCalWriter(out, wrapInArray()));
}
/**
* Writes the iCalendar objects to a file.
* @param file the file to write to
* @throws IOException if there's a problem writing to the file
*/
public void go(File file) throws IOException {
JCalWriter writer = new JCalWriter(file, wrapInArray());
try {
go(writer);
} finally {
writer.close();
}
}
/**
* Writes the iCalendar objects to a writer.
* @param writer the writer to write to
* @throws IOException if there's a problem writing to the writer
*/
public void go(Writer writer) throws IOException {
go(new JCalWriter(writer, wrapInArray()));
}
private void go(JCalWriter writer) throws IOException {
if (defaultTimeZone != null) {
writer.setGlobalTimezone(defaultTimeZone);
}
writer.setPrettyPrint(prettyPrint);
if (index != null) {
writer.setScribeIndex(index);
}
try {
for (ICalendar ical : icals) {
writer.write(ical);
writer.flush();
}
} finally {
writer.closeJsonStream();
}
}
private boolean wrapInArray() {
return icals.size() > 1;
}
}

@ -1,206 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.ParseWarning;
import biweekly.io.StreamReader;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Parent class for all chaining parsers. This class is package-private in order
* to hide it from the generated Javadocs.
* @author Michael Angstadt
* @param <T> the object instance's type (for method chaining)
*/
abstract class ChainingParser<T extends ChainingParser<?>> {
final String string;
final InputStream in;
final Reader reader;
final File file;
ScribeIndex index;
List<List<ParseWarning>> warnings;
TimeZone defaultTimezone;
@SuppressWarnings("unchecked")
final T this_ = (T) this;
ChainingParser(String string) {
this(string, null, null, null);
}
ChainingParser(InputStream in) {
this(null, in, null, null);
}
ChainingParser(Reader reader) {
this(null, null, reader, null);
}
ChainingParser(File file) {
this(null, null, null, file);
}
ChainingParser() {
this(null, null, null, null);
}
private ChainingParser(String string, InputStream in, Reader reader, File file) {
this.string = string;
this.in = in;
this.reader = reader;
this.file = file;
}
/**
* Registers a property scribe.
* @param scribe the scribe
* @return this
*/
public T register(ICalPropertyScribe<? extends ICalProperty> scribe) {
if (index == null) {
index = new ScribeIndex();
}
index.register(scribe);
return this_;
}
/**
* Registers a component scribe.
* @param scribe the scribe
* @return this
*/
public T register(ICalComponentScribe<? extends ICalComponent> scribe) {
if (index == null) {
index = new ScribeIndex();
}
index.register(scribe);
return this_;
}
/**
* Provides a list object that any parser warnings will be put into.
* @param warnings the list object that will be populated with the warnings
* of each parsed iCalendar object. Each element in the list is a list of
* warnings for one parsed object. Therefore, the size of this list will be
* equal to the number of parsed iCalendar objects. If an iCalendar object
* does not have any warnings, then its warning list will be empty.
* @return this
*/
public T warnings(List<List<ParseWarning>> warnings) {
this.warnings = warnings;
return this_;
}
/**
* Sets the timezone that will be used for parsing date property values that
* are floating or that have invalid timezone definitions assigned to them.
* Defaults to {@link TimeZone#getDefault}.
* @param defaultTimezone the default timezone
* @return this
*/
public T defaultTimezone(TimeZone defaultTimezone) {
this.defaultTimezone = defaultTimezone;
return this_;
}
/**
* Reads the first iCalendar object from the stream.
* @return the iCalendar object or null if there are none
* @throws IOException if there's an I/O problem
*/
public ICalendar first() throws IOException {
StreamReader reader = constructReader();
if (index != null) {
reader.setScribeIndex(index);
}
if (defaultTimezone != null) {
reader.setDefaultTimezone(defaultTimezone);
}
try {
ICalendar ical = reader.readNext();
if (warnings != null) {
warnings.add(reader.getWarnings());
}
return ical;
} finally {
if (closeWhenDone()) {
reader.close();
}
}
}
/**
* Reads all iCalendar objects from the stream.
* @return the parsed iCalendar objects
* @throws IOException if there's an I/O problem
*/
public List<ICalendar> all() throws IOException {
StreamReader reader = constructReader();
if (index != null) {
reader.setScribeIndex(index);
}
if (defaultTimezone != null) {
reader.setDefaultTimezone(defaultTimezone);
}
try {
List<ICalendar> icals = new ArrayList<ICalendar>();
ICalendar ical;
while ((ical = reader.readNext()) != null) {
if (warnings != null) {
warnings.add(reader.getWarnings());
}
icals.add(ical);
}
return icals;
} finally {
if (closeWhenDone()) {
reader.close();
}
}
}
abstract StreamReader constructReader() throws IOException;
private boolean closeWhenDone() {
return in == null && reader == null;
}
}

@ -1,97 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import biweekly.Biweekly;
import biweekly.io.StreamReader;
import biweekly.io.text.ICalReader;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing traditional, plain-text iCalendar objects.
* @see Biweekly#parse(InputStream)
* @see Biweekly#parse(File)
* @see Biweekly#parse(Reader)
* @author Michael Angstadt
*/
public class ChainingTextParser<T extends ChainingTextParser<?>> extends ChainingParser<T> {
private boolean caretDecoding = true;
public ChainingTextParser(String string) {
super(string);
}
public ChainingTextParser(InputStream in) {
super(in);
}
public ChainingTextParser(Reader reader) {
super(reader);
}
public ChainingTextParser(File file) {
super(file);
}
/**
* Sets whether the reader will decode characters in parameter values that
* use circumflex accent encoding (enabled by default). This only applies to
* version 2.0 iCalendar objects.
*
* @param enable true to use circumflex accent decoding, false not to
* @return this
* @see ICalReader#setCaretDecodingEnabled(boolean)
* @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
*/
public T caretDecoding(boolean enable) {
caretDecoding = enable;
return this_;
}
@Override
StreamReader constructReader() throws IOException {
ICalReader reader = newReader();
reader.setCaretDecodingEnabled(caretDecoding);
return reader;
}
private ICalReader newReader() throws IOException {
if (string != null) {
return new ICalReader(string);
}
if (in != null) {
return new ICalReader(in);
}
if (reader != null) {
return new ICalReader(reader);
}
return new ICalReader(file);
}
}

@ -1,64 +0,0 @@
package biweekly.io.chain;
import java.io.IOException;
import java.util.List;
import biweekly.Biweekly;
import biweekly.ICalendar;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing traditional, plain-text iCalendar objects from
* strings.
* @see Biweekly#parse(String)
* @author Michael Angstadt
*/
public class ChainingTextStringParser extends ChainingTextParser<ChainingTextStringParser> {
public ChainingTextStringParser(String string) {
super(string);
}
@Override
public ICalendar first() {
try {
return super.first();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
@Override
public List<ICalendar> all() {
try {
return super.all();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
}

@ -1,233 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.TimeZone;
import biweekly.Biweekly;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.io.text.ICalWriter;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for writing traditional, plain-text iCalendar objects.
* @see Biweekly#write(Collection)
* @see Biweekly#write(ICalendar...)
* @author Michael Angstadt
*/
public class ChainingTextWriter extends ChainingWriter<ChainingTextWriter> {
private ICalVersion version;
private boolean caretEncoding = false;
private boolean foldLines = true;
/**
* @param icals the iCalendar objects to write
*/
public ChainingTextWriter(Collection<ICalendar> icals) {
super(icals);
}
/**
* <p>
* Sets the version that all the iCalendar objects will be marshalled to.
* The version that is attached to each individual {@link ICalendar} object
* will be ignored.
* </p>
* <p>
* If no version is passed into this method, the writer will look at the
* version attached to each individual {@link ICalendar} object and marshal
* it to that version. And if a {@link ICalendar} object has no version
* attached to it, then it will be marshalled to version 2.0.
* </p>
* @param version the version to marshal the iCalendar objects to
* @return this
*/
public ChainingTextWriter version(ICalVersion version) {
this.version = version;
return this;
}
/**
* Sets whether the writer will use circumflex accent encoding for parameter
* values (disabled by default). This only applies to version 2.0 iCalendar
* objects.
* @param enable true to use circumflex accent encoding, false not to
* @return this
* @see ICalWriter#setCaretEncodingEnabled(boolean)
* @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
*/
public ChainingTextWriter caretEncoding(boolean enable) {
this.caretEncoding = enable;
return this;
}
/**
* <p>
* Sets whether to fold long lines. Line folding is when long lines are
* split up into multiple lines. No data is lost or changed when a line is
* folded.
* </p>
* <p>
* Line folding is enabled by default. If the iCalendar consumer is not
* parsing your iCalendar objects properly, disabling line folding may help.
* </p>
* @param foldLines true to enable line folding, false to disable it
* (defaults to true)
* @return this
*/
public ChainingTextWriter foldLines(boolean foldLines) {
this.foldLines = foldLines;
return this;
}
@Override
public ChainingTextWriter tz(TimeZone defaultTimeZone, boolean outlookCompatible) {
return super.tz(defaultTimeZone, outlookCompatible);
}
@Override
public ChainingTextWriter register(ICalPropertyScribe<? extends ICalProperty> scribe) {
return super.register(scribe);
}
@Override
public ChainingTextWriter register(ICalComponentScribe<? extends ICalComponent> scribe) {
return super.register(scribe);
}
/**
* Writes the iCalendar objects to a string.
* @return the iCalendar string
*/
public String go() {
StringWriter sw = new StringWriter();
try {
go(sw);
} catch (IOException e) {
//should never be thrown because we're writing to a string
throw new RuntimeException(e);
}
return sw.toString();
}
/**
* Writes the iCalendar objects to an output stream.
* @param out the output stream to write to
* @throws IOException if there's a problem writing to the output stream
*/
public void go(OutputStream out) throws IOException {
go(new ICalWriter(out, getICalWriterConstructorVersion()));
}
/**
* Writes the iCalendar objects to a file. If the file exists, it will be
* overwritten.
* @param file the file to write to
* @throws IOException if there's a problem writing to the file
*/
public void go(File file) throws IOException {
go(file, false);
}
/**
* Writes the iCalendar objects to a file.
* @param file the file to write to
* @param append true to append onto the end of the file, false to overwrite
* it
* @throws IOException if there's a problem writing to the file
*/
public void go(File file, boolean append) throws IOException {
ICalWriter writer = new ICalWriter(file, append, getICalWriterConstructorVersion());
try {
go(writer);
} finally {
writer.close();
}
}
/**
* Writes the iCalendar objects to a writer.
* @param writer the writer to write to
* @throws IOException if there's a problem writing to the writer
*/
public void go(Writer writer) throws IOException {
go(new ICalWriter(writer, getICalWriterConstructorVersion()));
}
private void go(ICalWriter writer) throws IOException {
writer.setCaretEncodingEnabled(caretEncoding);
if (!foldLines) {
writer.getVObjectWriter().getFoldedLineWriter().setLineLength(null);
}
if (defaultTimeZone != null) {
writer.setGlobalTimezone(defaultTimeZone);
}
if (index != null) {
writer.setScribeIndex(index);
}
for (ICalendar ical : icals) {
if (version == null) {
//use the version that's assigned to each individual iCalendar object
ICalVersion icalVersion = ical.getVersion();
if (icalVersion == null) {
icalVersion = ICalVersion.V2_0;
}
writer.setTargetVersion(icalVersion);
}
writer.write(ical);
writer.flush();
}
}
/**
* <p>
* Gets the {@link ICalVersion} object to pass into the {@link ICalWriter}
* constructor. The constructor does not allow a null version, so this
* method ensures that a non-null version is passed in.
* </p>
* <p>
* If the user hasn't chosen a version, the version that is passed into the
* constructor doesn't matter. This is because the writer's target version
* is reset every time an iCalendar object is written (see the
* {@link #go(ICalWriter)} method).
* </p>
* @return the version to pass into the constructor
*/
private ICalVersion getICalWriterConstructorVersion() {
return (version == null) ? ICalVersion.V2_0 : version;
}
}

@ -1,108 +0,0 @@
package biweekly.io.chain;
import java.util.Collection;
import java.util.TimeZone;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.TimezoneAssignment;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Parent class for all chaining writers. This class is package-private in order
* to hide it from the generated Javadocs.
* @author Michael Angstadt
* @param <T> the object instance's type (for method chaining)
*/
class ChainingWriter<T extends ChainingWriter<?>> {
final Collection<ICalendar> icals;
ScribeIndex index;
// boolean prodId = true;
// boolean versionStrict = true;
TimezoneAssignment defaultTimeZone = null;
@SuppressWarnings("unchecked")
private final T this_ = (T) this;
/**
* @param icals the iCalendar objects to write
*/
ChainingWriter(Collection<ICalendar> icals) {
this.icals = icals;
}
/**
* <p>
* Sets the timezone to use when outputting date values (defaults to UTC).
* </p>
* <p>
* This method downloads an appropriate VTIMEZONE component from the <a
* href="http://www.tzurl.org">tzurl.org</a> website.
* </p>
* @param defaultTimeZone the default timezone or null for UTC
* @param outlookCompatible true to download a VTIMEZONE component that is
* tailored for Microsoft Outlook email clients, false to download a
* standards-based one
* @return this
* @throws IllegalArgumentException if an appropriate VTIMEZONE component
* cannot be found on the website
*/
T tz(TimeZone defaultTimeZone, boolean outlookCompatible) {
this.defaultTimeZone = (defaultTimeZone == null) ? null : TimezoneAssignment.download(defaultTimeZone, outlookCompatible);
return this_;
}
/**
* Registers a property scribe.
* @param scribe the scribe to register
* @return this
*/
T register(ICalPropertyScribe<? extends ICalProperty> scribe) {
if (index == null) {
index = new ScribeIndex();
}
index.register(scribe);
return this_;
}
/**
* Registers a component scribe.
* @param scribe the scribe to register
* @return this
*/
T register(ICalComponentScribe<? extends ICalComponent> scribe) {
if (index == null) {
index = new ScribeIndex();
}
index.register(scribe);
return this_;
}
}

@ -1,71 +0,0 @@
package biweekly.io.chain;
import java.io.IOException;
import java.util.List;
import org.w3c.dom.Document;
import biweekly.Biweekly;
import biweekly.ICalendar;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing xCals (XML-encoded iCalendar objects) from strings
* or DOMs.
* @see Biweekly#parseXml(String)
* @see Biweekly#parseXml(Document)
* @author Michael Angstadt
*/
public class ChainingXmlMemoryParser extends ChainingXmlParser<ChainingXmlMemoryParser> {
public ChainingXmlMemoryParser(String xml) {
super(xml);
}
public ChainingXmlMemoryParser(Document dom) {
super(dom);
}
@Override
public ICalendar first() {
try {
return super.first();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
@Override
public List<ICalendar> all() {
try {
return super.all();
} catch (IOException e) {
//should never be thrown because we're reading from a string
throw new RuntimeException(e);
}
}
}

@ -1,85 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import org.w3c.dom.Document;
import biweekly.Biweekly;
import biweekly.io.StreamReader;
import biweekly.io.xml.XCalReader;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for parsing xCals (XML-encoded iCalendar objects).
* @see Biweekly#parseXml(InputStream)
* @see Biweekly#parseXml(File)
* @see Biweekly#parseXml(Reader)
* @author Michael Angstadt
*/
public class ChainingXmlParser<T extends ChainingXmlParser<?>> extends ChainingParser<T> {
private Document dom;
public ChainingXmlParser(String string) {
super(string);
}
public ChainingXmlParser(InputStream in) {
super(in);
}
public ChainingXmlParser(File file) {
super(file);
}
public ChainingXmlParser(Reader reader) {
super(reader);
}
public ChainingXmlParser(Document dom) {
this.dom = dom;
}
@Override
StreamReader constructReader() throws IOException {
if (string != null) {
return new XCalReader(string);
}
if (in != null) {
return new XCalReader(in);
}
if (reader != null) {
return new XCalReader(reader);
}
if (file != null) {
return new XCalReader(file);
}
return new XCalReader(dom);
}
}

@ -1,213 +0,0 @@
package biweekly.io.chain;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Document;
import biweekly.Biweekly;
import biweekly.ICalDataType;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.io.xml.XCalDocument;
import biweekly.io.xml.XCalDocument.XCalDocumentStreamWriter;
import biweekly.io.xml.XCalOutputProperties;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Chainer class for writing xCal (XML-encoded iCalendar objects).
* @see Biweekly#writeXml(Collection)
* @see Biweekly#writeXml(ICalendar...)
* @author Michael Angstadt
*/
public class ChainingXmlWriter extends ChainingWriter<ChainingXmlWriter> {
private final XCalOutputProperties outputProperties = new XCalOutputProperties();
private final Map<String, ICalDataType> parameterDataTypes = new HashMap<String, ICalDataType>(0);
/**
* @param icals the iCValendar objects to write
*/
public ChainingXmlWriter(Collection<ICalendar> icals) {
super(icals);
}
/**
* Sets the number of indent spaces to use for pretty-printing. If not set,
* then the XML will not be pretty-printed.
* @param indent the number of spaces in the indent string or "null" not to
* pretty-print (disabled by default)
* @return this
*/
public ChainingXmlWriter indent(Integer indent) {
outputProperties.setIndent(indent);
return this;
}
/**
* Sets the XML version to use. Note that many JDKs only support 1.0
* natively. For XML 1.1 support, add a JAXP library like <a href=
* "http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22xalan%22%20AND%20a%3A%22xalan%22"
* >xalan</a> to your project.
* @param xmlVersion the XML version (defaults to "1.0")
* @return this
*/
public ChainingXmlWriter xmlVersion(String xmlVersion) {
outputProperties.setXmlVersion(xmlVersion);
return this;
}
/**
* Assigns an output property to the JAXP transformer (see
* {@link Transformer#setOutputProperty}).
* @param name the property name
* @param value the property value
* @return this
*/
public ChainingXmlWriter outputProperty(String name, String value) {
outputProperties.put(name, value);
return this;
}
/**
* Assigns all of the given output properties to the JAXP transformer (see
* {@link Transformer#setOutputProperty}).
* @param outputProperties the properties
* @return this
*/
public ChainingXmlWriter outputProperties(Map<String, String> outputProperties) {
this.outputProperties.putAll(outputProperties);
return this;
}
@Override
public ChainingXmlWriter tz(TimeZone defaultTimeZone, boolean outlookCompatible) {
return super.tz(defaultTimeZone, outlookCompatible);
}
@Override
public ChainingXmlWriter register(ICalPropertyScribe<? extends ICalProperty> scribe) {
return super.register(scribe);
}
@Override
public ChainingXmlWriter register(ICalComponentScribe<? extends ICalComponent> scribe) {
return super.register(scribe);
}
/**
* Registers the data type of a non-standard parameter. Non-standard
* parameters use the "unknown" data type by default.
* @param parameterName the parameter name (e.g. "x-foo")
* @param dataType the data type
* @return this
*/
public ChainingXmlWriter register(String parameterName, ICalDataType dataType) {
parameterDataTypes.put(parameterName, dataType);
return this;
}
/**
* Writes the iCalendar objects to a string.
* @return the XML document
*/
public String go() {
return createXCalDocument().write(outputProperties);
}
/**
* Writes the iCalendar objects to an output stream.
* @param out the output stream to write to
* @throws TransformerException if there's a problem writing to the output
* stream
*/
public void go(OutputStream out) throws TransformerException {
createXCalDocument().write(out, outputProperties);
}
/**
* Writes the iCalendar objects to a file.
* @param file the file to write to
* @throws IOException if the file can't be opened
* @throws TransformerException if there's a problem writing to the file
*/
public void go(File file) throws IOException, TransformerException {
createXCalDocument().write(file, outputProperties);
}
/**
* Writes the iCalendar objects to a writer.
* @param writer the writer to write to
* @throws TransformerException if there's a problem writing to the writer
*/
public void go(Writer writer) throws TransformerException {
createXCalDocument().write(writer, outputProperties);
}
/**
* Generates an XML document object model (DOM) containing the iCalendar
* objects.
* @return the DOM
*/
public Document dom() {
return createXCalDocument().getDocument();
}
private XCalDocument createXCalDocument() {
XCalDocument document = new XCalDocument();
XCalDocumentStreamWriter writer = document.writer();
if (defaultTimeZone != null) {
writer.setGlobalTimezone(defaultTimeZone);
}
for (Map.Entry<String, ICalDataType> entry : parameterDataTypes.entrySet()) {
String parameterName = entry.getKey();
ICalDataType dataType = entry.getValue();
writer.registerParameterDataType(parameterName, dataType);
}
if (index != null) {
writer.setScribeIndex(index);
}
for (ICalendar ical : icals) {
writer.write(ical);
}
return document;
}
}

@ -1,5 +0,0 @@
/**
* Contains classes used in the chaining API.
* @see biweekly.Biweekly
*/
package biweekly.io.chain;

@ -1,84 +0,0 @@
package biweekly.io.json;
import java.io.IOException;
import biweekly.ICalendar;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Deserializes jCals within the jackson-databind framework.
* @author Buddy Gorven
* @author Michael Angstadt
*/
public class JCalDeserializer extends JsonDeserializer<ICalendar> {
private ScribeIndex index = new ScribeIndex();
@Override
public ICalendar deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
@SuppressWarnings("resource")
JCalReader reader = new JCalReader(parser);
reader.setScribeIndex(index);
return reader.readNext();
}
/**
* <p>
* Registers a property scribe. This is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) {
index.register(scribe);
}
/**
* Gets the scribe index.
* @return the scribe index
*/
public ScribeIndex getScribeIndex() {
return index;
}
/**
* Sets the scribe index.
* @param index the scribe index
*/
public void setScribeIndex(ScribeIndex index) {
this.index = index;
}
}

@ -1,145 +0,0 @@
package biweekly.io.json;
import biweekly.Biweekly;
import biweekly.ICalendar;
import biweekly.io.TimezoneAssignment;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Module for jackson-databind that serializes and deserializes jCals.
* </p>
* <p>
* <b>Example:</b>
* </p>
*
* <pre class="brush:java">
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new JCalModule());
* ICalendar result = mapper.readValue(..., ICalendar.class);
* </pre>
* @author Buddy Gorven
* @author Michael Angstadt
*/
public class JCalModule extends SimpleModule {
private static final long serialVersionUID = 8022429868572303471L;
private static final String MODULE_NAME = "biweekly-jcal";
private static final Version MODULE_VERSION = moduleVersion();
private final JCalDeserializer deserializer = new JCalDeserializer();
private final JCalSerializer serializer = new JCalSerializer();
private ScribeIndex index;
/**
* Creates the module.
*/
public JCalModule() {
super(MODULE_NAME, MODULE_VERSION);
setScribeIndex(new ScribeIndex());
addSerializer(serializer);
addDeserializer(ICalendar.class, deserializer);
}
private static Version moduleVersion() {
String[] split = Biweekly.VERSION.split("[.-]");
if (split.length < 3) {
/*
* This can happen during development if the "biweekly.properties"
* file has not been filtered by Maven.
*/
return new Version(0, 0, 0, "", Biweekly.GROUP_ID, Biweekly.ARTIFACT_ID);
}
int major = Integer.parseInt(split[0]);
int minor = Integer.parseInt(split[1]);
int patch = Integer.parseInt(split[2]);
String snapshot = (split.length > 3) ? split[3] : "RELEASE";
return new Version(major, minor, patch, snapshot, Biweekly.GROUP_ID, Biweekly.ARTIFACT_ID);
}
/**
* <p>
* Registers a property scribe. This is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) {
index.register(scribe);
}
/**
* Gets the scribe index used by the serializer and deserializer.
* @return the scribe index
*/
public ScribeIndex getScribeIndex() {
return index;
}
/**
* Sets the scribe index for the serializer and deserializer to use.
* @param index the scribe index
*/
public void setScribeIndex(ScribeIndex index) {
this.index = index;
serializer.setScribeIndex(index);
deserializer.setScribeIndex(index);
}
/**
* Gets the timezone that all date/time property values will be formatted
* in. If set, this setting will override the timezone information
* associated with each {@link ICalendar} object.
* @return the global timezone or null if not set (defaults to null)
*/
public TimezoneAssignment getGlobalTimezone() {
return serializer.getGlobalTimezone();
}
/**
* Sets the timezone that all date/time property values will be formatted
* in. This is a convenience method that overrides the timezone information
* associated with each {@link ICalendar} object that is passed into this
* writer.
* @param globalTimezone the global timezone or null not to set a global
* timezone (defaults to null)
*/
public void setGlobalTimezone(TimezoneAssignment globalTimezone) {
serializer.setGlobalTimezone(globalTimezone);
}
}

@ -1,80 +0,0 @@
package biweekly.io.json;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonToken;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Thrown during the parsing of a JSON-encoded iCalendar object (jCal) when the
* jCal object is not formatted in the correct way (the JSON syntax is valid,
* but it's not in the correct jCal format).
* @author Michael Angstadt
*/
public class JCalParseException extends IOException {
private static final long serialVersionUID = -2447563507966434472L;
private final JsonToken expected, actual;
/**
* Creates a jCal parse exception.
* @param expected the JSON token that the parser was expecting
* @param actual the actual JSON token
*/
public JCalParseException(JsonToken expected, JsonToken actual) {
super("Expected " + expected + " but was " + actual + ".");
this.expected = expected;
this.actual = actual;
}
/**
* Creates a jCal parse exception.
* @param message the detail message
* @param expected the JSON token that the parser was expecting
* @param actual the actual JSON token
*/
public JCalParseException(String message, JsonToken expected, JsonToken actual) {
super(message);
this.expected = expected;
this.actual = actual;
}
/**
* Gets the JSON token that the parser was expected.
* @return the expected token
*/
public JsonToken getExpectedToken() {
return expected;
}
/**
* Gets the JSON token that was read.
* @return the actual token
*/
public JsonToken getActualToken() {
return actual;
}
}

@ -1,133 +0,0 @@
package biweekly.io.json;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* A JSON pretty-printer for jCals.
* @author Buddy Gorven
* @author Michael Angstadt
*/
public class JCalPrettyPrinter extends DefaultPrettyPrinter {
private static final long serialVersionUID = 1L;
/**
* The value that is assigned to {@link JsonGenerator#setCurrentValue} to
* let the pretty-printer know that a iCalendar property is currently being
* written.
*/
public static final Object PROPERTY_VALUE = "ical-property";
/**
* Alias for {@link DefaultIndenter#SYSTEM_LINEFEED_INSTANCE}
*/
private static final Indenter NEWLINE_INDENTER = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE;
/**
* Instance of {@link DefaultPrettyPrinter.FixedSpaceIndenter}
*/
private static final Indenter INLINE_INDENTER = new DefaultPrettyPrinter.FixedSpaceIndenter();
private Indenter propertyIndenter, arrayIndenter, objectIndenter;
public JCalPrettyPrinter() {
propertyIndenter = INLINE_INDENTER;
indentArraysWith(NEWLINE_INDENTER);
indentObjectsWith(NEWLINE_INDENTER);
}
public JCalPrettyPrinter(JCalPrettyPrinter base) {
super(base);
propertyIndenter = base.propertyIndenter;
indentArraysWith(base.arrayIndenter);
indentObjectsWith(base.objectIndenter);
}
@Override
public JCalPrettyPrinter createInstance() {
return new JCalPrettyPrinter(this);
}
@Override
public void indentArraysWith(Indenter indenter) {
arrayIndenter = indenter;
super.indentArraysWith(indenter);
}
@Override
public void indentObjectsWith(Indenter indenter) {
objectIndenter = indenter;
super.indentObjectsWith(indenter);
}
public void indentICalPropertiesWith(Indenter indenter) {
propertyIndenter = indenter;
}
protected static boolean isInICalProperty(JsonStreamContext context) {
if (context == null) {
return false;
}
Object currentValue = context.getCurrentValue();
if (currentValue == PROPERTY_VALUE) {
return true;
}
return isInICalProperty(context.getParent());
}
private void updateIndenter(JsonStreamContext context) {
boolean inICalProperty = isInICalProperty(context);
super.indentArraysWith(inICalProperty ? propertyIndenter : arrayIndenter);
super.indentObjectsWith(inICalProperty ? propertyIndenter : objectIndenter);
}
@Override
public void writeStartArray(JsonGenerator gen) throws IOException, JsonGenerationException {
updateIndenter(gen.getOutputContext().getParent());
super.writeStartArray(gen);
}
@Override
public void writeEndArray(JsonGenerator gen, int numValues) throws IOException, JsonGenerationException {
updateIndenter(gen.getOutputContext().getParent());
super.writeEndArray(gen, numValues);
}
@Override
public void writeArrayValueSeparator(JsonGenerator gen) throws IOException {
updateIndenter(gen.getOutputContext().getParent());
super.writeArrayValueSeparator(gen);
}
}

@ -1,335 +0,0 @@
package biweekly.io.json;
import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import biweekly.ICalDataType;
import biweekly.io.scribe.ScribeIndex;
import biweekly.parameter.ICalParameters;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Parses an iCalendar JSON data stream (jCal).
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc7265">RFC 7265</a>
*/
public class JCalRawReader implements Closeable {
private static final String VCALENDAR_COMPONENT_NAME = ScribeIndex.getICalendarScribe().getComponentName().toLowerCase(); //"vcalendar"
private final Reader reader;
private JsonParser parser;
private boolean eof = false;
private JCalDataStreamListener listener;
private boolean strict = false;
/**
* @param reader the reader to wrap
*/
public JCalRawReader(Reader reader) {
this.reader = reader;
}
/**
* @param parser the parser to read from
* @param strict true if the parser's current token is expected to be
* positioned at the start of a jCard, false if not. If this is true, and
* the parser is not positioned at the beginning of a jCard, a
* {@link JCalParseException} will be thrown. If this if false, the parser
* will consume input until it reaches the beginning of a jCard.
*/
public JCalRawReader(JsonParser parser, boolean strict) {
reader = null;
this.parser = parser;
this.strict = strict;
}
/**
* Gets the current line number.
* @return the line number
*/
public int getLineNum() {
return (parser == null) ? 0 : parser.getCurrentLocation().getLineNr();
}
/**
* Reads the next iCalendar object from the jCal data stream.
* @param listener handles the iCalendar data as it is read off the wire
* @throws JCalParseException if the jCal syntax is incorrect (the JSON
* syntax may be valid, but it is not in the correct jCal format).
* @throws JsonParseException if the JSON syntax is incorrect
* @throws IOException if there is a problem reading from the data stream
*/
public void readNext(JCalDataStreamListener listener) throws IOException {
if (parser == null) {
JsonFactory factory = new JsonFactory();
parser = factory.createParser(reader);
}
if (parser.isClosed()) {
return;
}
this.listener = listener;
//find the next iCalendar object
JsonToken prev = parser.getCurrentToken();
JsonToken cur;
while ((cur = parser.nextToken()) != null) {
if (prev == JsonToken.START_ARRAY && cur == JsonToken.VALUE_STRING && VCALENDAR_COMPONENT_NAME.equals(parser.getValueAsString())) {
//found
break;
}
if (strict) {
//the parser was expecting the jCal to be there
if (prev != JsonToken.START_ARRAY) {
throw new JCalParseException(JsonToken.START_ARRAY, prev);
}
if (cur != JsonToken.VALUE_STRING) {
throw new JCalParseException(JsonToken.VALUE_STRING, cur);
}
throw new JCalParseException("Invalid value for first token: expected \"vcalendar\" , was \"" + parser.getValueAsString() + "\"", JsonToken.VALUE_STRING, cur);
}
prev = cur;
}
if (cur == null) {
//EOF
eof = true;
return;
}
parseComponent(new ArrayList<String>());
}
private void parseComponent(List<String> components) throws IOException {
checkCurrent(JsonToken.VALUE_STRING);
String componentName = parser.getValueAsString();
listener.readComponent(components, componentName);
components.add(componentName);
//start properties array
checkNext(JsonToken.START_ARRAY);
//read properties
while (parser.nextToken() != JsonToken.END_ARRAY) { //until we reach the end properties array
checkCurrent(JsonToken.START_ARRAY);
parser.nextToken();
parseProperty(components);
}
//start sub-components array
checkNext(JsonToken.START_ARRAY);
//read sub-components
while (parser.nextToken() != JsonToken.END_ARRAY) { //until we reach the end sub-components array
checkCurrent(JsonToken.START_ARRAY);
parser.nextToken();
parseComponent(new ArrayList<String>(components));
}
//read the end of the component array (e.g. the last bracket in this example: ["comp", [ /* props */ ], [ /* comps */] ])
checkNext(JsonToken.END_ARRAY);
}
private void parseProperty(List<String> components) throws IOException {
//get property name
checkCurrent(JsonToken.VALUE_STRING);
String propertyName = parser.getValueAsString().toLowerCase();
ICalParameters parameters = parseParameters();
//get data type
checkNext(JsonToken.VALUE_STRING);
String dataTypeStr = parser.getText();
ICalDataType dataType = "unknown".equals(dataTypeStr) ? null : ICalDataType.get(dataTypeStr);
//get property value(s)
List<JsonValue> values = parseValues();
JCalValue value = new JCalValue(values);
listener.readProperty(components, propertyName, parameters, dataType, value);
}
private ICalParameters parseParameters() throws IOException {
checkNext(JsonToken.START_OBJECT);
ICalParameters parameters = new ICalParameters();
while (parser.nextToken() != JsonToken.END_OBJECT) {
String parameterName = parser.getText();
if (parser.nextToken() == JsonToken.START_ARRAY) {
//multi-valued parameter
while (parser.nextToken() != JsonToken.END_ARRAY) {
parameters.put(parameterName, parser.getText());
}
} else {
parameters.put(parameterName, parser.getValueAsString());
}
}
return parameters;
}
private List<JsonValue> parseValues() throws IOException {
List<JsonValue> values = new ArrayList<JsonValue>();
while (parser.nextToken() != JsonToken.END_ARRAY) { //until we reach the end of the property array
JsonValue value = parseValue();
values.add(value);
}
return values;
}
private Object parseValueElement() throws IOException {
switch (parser.getCurrentToken()) {
case VALUE_FALSE:
case VALUE_TRUE:
return parser.getBooleanValue();
case VALUE_NUMBER_FLOAT:
return parser.getDoubleValue();
case VALUE_NUMBER_INT:
return parser.getLongValue();
case VALUE_NULL:
return null;
default:
return parser.getText();
}
}
private List<JsonValue> parseValueArray() throws IOException {
List<JsonValue> array = new ArrayList<JsonValue>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
JsonValue value = parseValue();
array.add(value);
}
return array;
}
private Map<String, JsonValue> parseValueObject() throws IOException {
Map<String, JsonValue> object = new HashMap<String, JsonValue>();
parser.nextToken();
while (parser.getCurrentToken() != JsonToken.END_OBJECT) {
checkCurrent(JsonToken.FIELD_NAME);
String key = parser.getText();
parser.nextToken();
JsonValue value = parseValue();
object.put(key, value);
parser.nextToken();
}
return object;
}
private JsonValue parseValue() throws IOException {
switch (parser.getCurrentToken()) {
case START_ARRAY:
return new JsonValue(parseValueArray());
case START_OBJECT:
return new JsonValue(parseValueObject());
default:
return new JsonValue(parseValueElement());
}
}
private void checkNext(JsonToken expected) throws IOException {
JsonToken actual = parser.nextToken();
check(expected, actual);
}
private void checkCurrent(JsonToken expected) throws JCalParseException {
JsonToken actual = parser.getCurrentToken();
check(expected, actual);
}
private void check(JsonToken expected, JsonToken actual) throws JCalParseException {
if (actual != expected) {
throw new JCalParseException(expected, actual);
}
}
/**
* Determines whether the end of the data stream has been reached.
* @return true if the end has been reached, false if not
*/
public boolean eof() {
return eof;
}
/**
* Handles the iCalendar data as it is read off the data stream.
* @author Michael Angstadt
*/
public interface JCalDataStreamListener {
/**
* Called when the parser begins to read a component.
* @param parentHierarchy the component's parent components
* @param componentName the component name (e.g. "vevent")
*/
void readComponent(List<String> parentHierarchy, String componentName);
/**
* Called when a property is read.
* @param componentHierarchy the hierarchy of components that the
* property belongs to
* @param propertyName the property name (e.g. "summary")
* @param parameters the parameters
* @param dataType the data type (e.g. "text")
* @param value the property value
*/
void readProperty(List<String> componentHierarchy, String propertyName, ICalParameters parameters, ICalDataType dataType, JCalValue value);
}
/**
* Closes the underlying {@link Reader} object.
*/
public void close() throws IOException {
if (parser != null) {
parser.close();
}
if (reader != null) {
reader.close();
}
}
}

@ -1,364 +0,0 @@
package biweekly.io.json;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import biweekly.ICalDataType;
import biweekly.Messages;
import biweekly.parameter.ICalParameters;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.PrettyPrinter;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Writes data to an iCalendar JSON data stream (jCal).
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc7265">RFC 7265</a>
*/
public class JCalRawWriter implements Closeable, Flushable {
private final Writer writer;
private final boolean wrapInArray;
private final LinkedList<Info> stack = new LinkedList<Info>();
private JsonGenerator generator;
private boolean prettyPrint = false;
private boolean componentEnded = false;
private boolean closeGenerator = true;
private PrettyPrinter prettyPrinter;
/**
* @param writer the writer to wrap
* @param wrapInArray true to wrap everything in an array, false not to
* (useful when writing more than one iCalendar object)
*/
public JCalRawWriter(Writer writer, boolean wrapInArray) {
this.writer = writer;
this.wrapInArray = wrapInArray;
}
/**
* @param generator the generator to write to
*/
public JCalRawWriter(JsonGenerator generator) {
this.writer = null;
this.generator = generator;
this.closeGenerator = false;
this.wrapInArray = false;
}
/**
* Gets whether or not the JSON will be pretty-printed.
* @return true if it will be pretty-printed, false if not (defaults to
* false)
*/
public boolean isPrettyPrint() {
return prettyPrint;
}
/**
* Sets whether or not to pretty-print the JSON.
* @param prettyPrint true to pretty-print it, false not to (defaults to
* false)
*/
public void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
/**
* Sets the pretty printer to pretty-print the JSON with. Note that this
* method implicitly enables indenting, so {@code setPrettyPrint(true)} does
* not also need to be called.
* @param prettyPrinter the custom pretty printer (defaults to an instance
* of {@link JCalPrettyPrinter}, if {@code setPrettyPrint(true)} has been
* called)
*/
public void setPrettyPrinter(PrettyPrinter prettyPrinter) {
prettyPrint = true;
this.prettyPrinter = prettyPrinter;
}
/**
* Writes the beginning of a new component array.
* @param componentName the component name (e.g. "vevent")
* @throws IOException if there's an I/O problem
*/
public void writeStartComponent(String componentName) throws IOException {
if (generator == null) {
init();
}
componentEnded = false;
if (!stack.isEmpty()) {
Info parent = stack.getLast();
if (!parent.wroteEndPropertiesArray) {
generator.writeEndArray();
parent.wroteEndPropertiesArray = true;
}
if (!parent.wroteStartSubComponentsArray) {
generator.writeStartArray();
parent.wroteStartSubComponentsArray = true;
}
}
generator.writeStartArray();
generator.writeString(componentName);
generator.writeStartArray(); //start properties array
stack.add(new Info());
}
/**
* Closes the current component array.
* @throws IllegalStateException if there are no open components (
* {@link #writeStartComponent(String)} must be called first)
* @throws IOException if there's an I/O problem
*/
public void writeEndComponent() throws IOException {
if (stack.isEmpty()) {
throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(2));
}
Info cur = stack.removeLast();
if (!cur.wroteEndPropertiesArray) {
generator.writeEndArray();
}
if (!cur.wroteStartSubComponentsArray) {
generator.writeStartArray();
}
generator.writeEndArray(); //end sub-components array
generator.writeEndArray(); //end the array of this component
componentEnded = true;
}
/**
* Writes a property to the current component.
* @param propertyName the property name (e.g. "version")
* @param dataType the property's data type (e.g. "text")
* @param value the property value
* @throws IllegalStateException if there are no open components (
* {@link #writeStartComponent(String)} must be called first) or if the last
* method called was {@link #writeEndComponent()}.
* @throws IOException if there's an I/O problem
*/
public void writeProperty(String propertyName, ICalDataType dataType, JCalValue value) throws IOException {
writeProperty(propertyName, new ICalParameters(), dataType, value);
}
/**
* Writes a property to the current component.
* @param propertyName the property name (e.g. "version")
* @param parameters the parameters
* @param dataType the property's data type (e.g. "text")
* @param value the property value
* @throws IllegalStateException if there are no open components (
* {@link #writeStartComponent(String)} must be called first) or if the last
* method called was {@link #writeEndComponent()}.
* @throws IOException if there's an I/O problem
*/
public void writeProperty(String propertyName, ICalParameters parameters, ICalDataType dataType, JCalValue value) throws IOException {
if (stack.isEmpty()) {
throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(2));
}
if (componentEnded) {
throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(3));
}
generator.setCurrentValue(JCalPrettyPrinter.PROPERTY_VALUE);
generator.writeStartArray();
//write the property name
generator.writeString(propertyName);
//write parameters
generator.writeStartObject();
for (Map.Entry<String, List<String>> entry : parameters) {
String name = entry.getKey().toLowerCase();
List<String> values = entry.getValue();
if (values.isEmpty()) {
continue;
}
if (values.size() == 1) {
generator.writeStringField(name, values.get(0));
} else {
generator.writeArrayFieldStart(name);
for (String paramValue : values) {
generator.writeString(paramValue);
}
generator.writeEndArray();
}
}
generator.writeEndObject();
//write data type
generator.writeString((dataType == null) ? "unknown" : dataType.getName().toLowerCase());
//write value
for (JsonValue jsonValue : value.getValues()) {
writeValue(jsonValue);
}
generator.writeEndArray();
generator.setCurrentValue(null);
}
private void writeValue(JsonValue jsonValue) throws IOException {
if (jsonValue.isNull()) {
generator.writeNull();
return;
}
Object val = jsonValue.getValue();
if (val != null) {
if (val instanceof Byte) {
generator.writeNumber((Byte) val);
} else if (val instanceof Short) {
generator.writeNumber((Short) val);
} else if (val instanceof Integer) {
generator.writeNumber((Integer) val);
} else if (val instanceof Long) {
generator.writeNumber((Long) val);
} else if (val instanceof Float) {
generator.writeNumber((Float) val);
} else if (val instanceof Double) {
generator.writeNumber((Double) val);
} else if (val instanceof Boolean) {
generator.writeBoolean((Boolean) val);
} else {
generator.writeString(val.toString());
}
return;
}
List<JsonValue> array = jsonValue.getArray();
if (array != null) {
generator.writeStartArray();
for (JsonValue element : array) {
writeValue(element);
}
generator.writeEndArray();
return;
}
Map<String, JsonValue> object = jsonValue.getObject();
if (object != null) {
generator.writeStartObject();
for (Map.Entry<String, JsonValue> entry : object.entrySet()) {
generator.writeFieldName(entry.getKey());
writeValue(entry.getValue());
}
generator.writeEndObject();
return;
}
}
/**
* Flushes the JSON stream.
* @throws IOException if there's a problem flushing the stream
*/
public void flush() throws IOException {
if (generator == null) {
return;
}
generator.flush();
}
/**
* Finishes writing the JSON document so that it is syntactically correct.
* No more data can be written once this method is called.
* @throws IOException if there's a problem closing the stream
*/
public void closeJsonStream() throws IOException {
if (generator == null) {
return;
}
while (!stack.isEmpty()) {
writeEndComponent();
}
if (wrapInArray) {
generator.writeEndArray();
}
if (closeGenerator) {
generator.close();
}
}
/**
* Finishes writing the JSON document and closes the underlying
* {@link Writer}.
* @throws IOException if there's a problem closing the stream
*/
public void close() throws IOException {
if (generator == null) {
return;
}
closeJsonStream();
if (writer != null) {
writer.close();
}
}
private void init() throws IOException {
JsonFactory factory = new JsonFactory();
factory.configure(Feature.AUTO_CLOSE_TARGET, false);
generator = factory.createGenerator(writer);
if (prettyPrint) {
if (prettyPrinter == null) {
prettyPrinter = new JCalPrettyPrinter();
}
generator.setPrettyPrinter(prettyPrinter);
}
if (wrapInArray) {
generator.writeStartArray();
}
}
private static class Info {
public boolean wroteEndPropertiesArray = false;
public boolean wroteStartSubComponentsArray = false;
}
}

@ -1,241 +0,0 @@
package biweekly.io.json;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.io.CannotParseException;
import biweekly.io.ParseWarning;
import biweekly.io.SkipMeException;
import biweekly.io.StreamReader;
import biweekly.io.json.JCalRawReader.JCalDataStreamListener;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.component.ICalendarScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.io.scribe.property.RawPropertyScribe;
import biweekly.parameter.ICalParameters;
import biweekly.property.ICalProperty;
import biweekly.property.RawProperty;
import biweekly.property.Version;
import biweekly.util.Utf8Reader;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Parses {@link ICalendar} objects from a jCal data stream (JSON).
* </p>
* <p>
* <b>Example:</b>
* </p>
*
* <pre class="brush:java">
* File file = new File("icals.json");
* JCalReader reader = null;
* try {
* reader = new JCalReader(file);
* ICalendar ical;
* while ((ical = reader.readNext()) != null) {
* //...
* }
* } finally {
* if (reader != null) reader.close();
* }
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc7265">RFC 7265</a>
*/
public class JCalReader extends StreamReader {
private static final ICalendarScribe icalScribe = ScribeIndex.getICalendarScribe();
private final JCalRawReader reader;
/**
* @param json the JSON string to read from
*/
public JCalReader(String json) {
this(new StringReader(json));
}
/**
* @param in the input stream to read from
*/
public JCalReader(InputStream in) {
this(new Utf8Reader(in));
}
/**
* @param file the file to read from
* @throws FileNotFoundException if the file doesn't exist
*/
public JCalReader(File file) throws FileNotFoundException {
this(new BufferedReader(new Utf8Reader(file)));
}
/**
* @param reader the reader to read from
*/
public JCalReader(Reader reader) {
this.reader = new JCalRawReader(reader);
}
/**
* @param parser the parser to read from
*/
public JCalReader(JsonParser parser) {
this.reader = new JCalRawReader(parser, true);
}
/**
* Reads the next iCalendar object from the JSON data stream.
* @return the iCalendar object or null if there are no more
* @throws JCalParseException if the jCal syntax is incorrect (the JSON
* syntax may be valid, but it is not in the correct jCal format).
* @throws JsonParseException if the JSON syntax is incorrect
* @throws IOException if there is a problem reading from the data stream
*/
@Override
public ICalendar _readNext() throws IOException {
if (reader.eof()) {
return null;
}
context.setVersion(ICalVersion.V2_0);
JCalDataStreamListenerImpl listener = new JCalDataStreamListenerImpl();
reader.readNext(listener);
return listener.getICalendar();
}
//@Override
public void close() throws IOException {
reader.close();
}
private class JCalDataStreamListenerImpl implements JCalDataStreamListener {
private final Map<List<String>, ICalComponent> components = new HashMap<List<String>, ICalComponent>();
public void readProperty(List<String> componentHierarchy, String propertyName, ICalParameters parameters, ICalDataType dataType, JCalValue value) {
context.getWarnings().clear();
context.setLineNumber(reader.getLineNum());
context.setPropertyName(propertyName);
//get the component that the property belongs to
ICalComponent parent = components.get(componentHierarchy);
//unmarshal the property
ICalPropertyScribe<? extends ICalProperty> scribe = index.getPropertyScribe(propertyName, ICalVersion.V2_0);
try {
ICalProperty property = scribe.parseJson(value, dataType, parameters, context);
warnings.addAll(context.getWarnings());
//set "ICalendar.version" if the value of the VERSION property is recognized
//otherwise, unmarshal VERSION like a normal property
if (parent instanceof ICalendar && property instanceof Version) {
Version version = (Version) property;
ICalVersion icalVersion = version.toICalVersion();
if (icalVersion != null) {
context.setVersion(icalVersion);
return;
}
}
parent.addProperty(property);
} catch (SkipMeException e) {
//@formatter:off
warnings.add(new ParseWarning.Builder(context)
.message(0, e.getMessage())
.build()
);
//@formatter:on
} catch (CannotParseException e) {
RawProperty property = new RawPropertyScribe(propertyName).parseJson(value, dataType, parameters, context);
parent.addProperty(property);
//@formatter:off
warnings.add(new ParseWarning.Builder(context)
.message(e)
.build()
);
//@formatter:on
}
}
public void readComponent(List<String> parentHierarchy, String componentName) {
ICalComponentScribe<? extends ICalComponent> scribe = index.getComponentScribe(componentName, ICalVersion.V2_0);
ICalComponent component = scribe.emptyInstance();
ICalComponent parent = components.get(parentHierarchy);
if (parent != null) {
parent.addComponent(component);
}
List<String> hierarchy = new ArrayList<String>(parentHierarchy);
hierarchy.add(componentName);
components.put(hierarchy, component);
}
public ICalendar getICalendar() {
if (components.isEmpty()) {
//EOF
return null;
}
ICalComponent component = components.get(Collections.singletonList(icalScribe.getComponentName().toLowerCase()));
if (component == null) {
//should never happen because the parser always looks for a "vcalendar" component
return null;
}
if (component instanceof ICalendar) {
//should happen every time
return (ICalendar) component;
}
//this will only happen if the user decides to override the ICalendarScribe for some reason
ICalendar ical = icalScribe.emptyInstance();
ical.addComponent(component);
return ical;
}
}
}

@ -1,116 +0,0 @@
package biweekly.io.json;
import java.io.IOException;
import biweekly.ICalendar;
import biweekly.io.TimezoneAssignment;
import biweekly.io.scribe.ScribeIndex;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.property.ICalProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Serializes jCals within the jackson-databind framework.
* @author Buddy Gorven
* @author Michael Angstadt
*/
@JsonFormat
public class JCalSerializer extends StdSerializer<ICalendar> {
private static final long serialVersionUID = 8964681078186049817L;
private ScribeIndex index = new ScribeIndex();
private TimezoneAssignment globalTimezone;
public JCalSerializer() {
super(ICalendar.class);
}
@Override
public void serialize(ICalendar value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
@SuppressWarnings("resource")
JCalWriter writer = new JCalWriter(gen);
writer.setScribeIndex(getScribeIndex());
writer.setGlobalTimezone(globalTimezone);
writer.write(value);
}
/**
* <p>
* Registers a property scribe. This is the same as calling:
* </p>
* <p>
* {@code getScribeIndex().register(scribe)}
* </p>
* @param scribe the scribe to register
*/
public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) {
index.register(scribe);
}
/**
* Gets the scribe index.
* @return the scribe index
*/
public ScribeIndex getScribeIndex() {
return index;
}
/**
* Sets the scribe index.
* @param index the scribe index
*/
public void setScribeIndex(ScribeIndex index) {
this.index = index;
}
/**
* Gets the timezone that all date/time property values will be formatted
* in. If set, this setting will override the timezone information
* associated with each {@link ICalendar} object.
* @return the global timezone or null if not set (defaults to null)
*/
public TimezoneAssignment getGlobalTimezone() {
return globalTimezone;
}
/**
* Sets the timezone that all date/time property values will be formatted
* in. This is a convenience method that overrides the timezone information
* associated with each {@link ICalendar} object that is passed into this
* writer.
* @param globalTimezone the global timezone or null not to set a global
* timezone (defaults to null)
*/
public void setGlobalTimezone(TimezoneAssignment globalTimezone) {
this.globalTimezone = globalTimezone;
}
}

@ -1,360 +0,0 @@
package biweekly.io.json;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import biweekly.util.ListMultimap;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Holds the value of a jCal property.
* @author Michael Angstadt
*/
public class JCalValue {
private final List<JsonValue> values;
/**
* Creates a new jCal value.
* @param values the values
*/
public JCalValue(List<JsonValue> values) {
this.values = Collections.unmodifiableList(values);
}
/**
* Creates a new jCal value.
* @param values the values
*/
public JCalValue(JsonValue... values) {
this.values = Arrays.asList(values); //unmodifiable
}
/**
* Creates a single-valued value.
* @param value the value
* @return the jCal value
*/
public static JCalValue single(Object value) {
return new JCalValue(new JsonValue(value));
}
/**
* Creates a multi-valued value.
* @param values the values
* @return the jCal value
*/
public static JCalValue multi(Object... values) {
return multi(Arrays.asList(values));
}
/**
* Creates a multi-valued value.
* @param values the values
* @return the jCal value
*/
public static JCalValue multi(List<?> values) {
List<JsonValue> multiValues = new ArrayList<JsonValue>(values.size());
for (Object value : values) {
multiValues.add(new JsonValue(value));
}
return new JCalValue(multiValues);
}
/**
* <p>
* Creates a structured value.
* </p>
* <p>
* This method accepts a vararg of {@link Object} instances. {@link List}
* objects will be treated as multi-valued components. All other objects.
* Null values will be treated as empty components.
* </p>
* @param values the values
* @return the jCal value
*/
public static JCalValue structured(Object... values) {
List<List<?>> valuesList = new ArrayList<List<?>>(values.length);
for (Object value : values) {
List<?> list = (value instanceof List) ? (List<?>) value : Collections.singletonList(value);
valuesList.add(list);
}
return structured(valuesList);
}
/**
* Creates a structured value.
* @param values the values
* @return the jCal value
*/
public static JCalValue structured(List<List<?>> values) {
List<JsonValue> array = new ArrayList<JsonValue>(values.size());
for (List<?> list : values) {
if (list.isEmpty()) {
array.add(new JsonValue(""));
continue;
}
if (list.size() == 1) {
Object value = list.get(0);
if (value == null) {
value = "";
}
array.add(new JsonValue(value));
continue;
}
List<JsonValue> subArray = new ArrayList<JsonValue>(list.size());
for (Object value : list) {
if (value == null) {
value = "";
}
subArray.add(new JsonValue(value));
}
array.add(new JsonValue(subArray));
}
return new JCalValue(new JsonValue(array));
}
/**
* Creates an object value.
* @param value the object
* @return the jCal value
*/
public static JCalValue object(ListMultimap<String, Object> value) {
Map<String, JsonValue> object = new LinkedHashMap<String, JsonValue>();
for (Map.Entry<String, List<Object>> entry : value) {
String key = entry.getKey();
List<Object> list = entry.getValue();
JsonValue v;
if (list.size() == 1) {
v = new JsonValue(list.get(0));
} else {
List<JsonValue> array = new ArrayList<JsonValue>(list.size());
for (Object element : list) {
array.add(new JsonValue(element));
}
v = new JsonValue(array);
}
object.put(key, v);
}
return new JCalValue(new JsonValue(object));
}
/**
* Gets the raw JSON values. Use one of the "{@code as*}" methods to parse
* the values as one of the standard jCal values.
* @return the JSON values
*/
public List<JsonValue> getValues() {
return values;
}
/**
* Parses this jCal value as a single-valued property value.
* @return the value or empty string if not found
*/
public String asSingle() {
if (values.isEmpty()) {
return "";
}
JsonValue first = values.get(0);
if (first.isNull()) {
return "";
}
Object obj = first.getValue();
if (obj != null) {
return obj.toString();
}
//get the first element of the array
List<JsonValue> array = first.getArray();
if (array != null && !array.isEmpty()) {
obj = array.get(0).getValue();
if (obj != null) {
return obj.toString();
}
}
return "";
}
/**
* Parses this jCal value as a structured property value.
* @return the structured values or empty list if not found
*/
public List<List<String>> asStructured() {
if (values.isEmpty()) {
return Collections.emptyList();
}
JsonValue first = values.get(0);
//["request-status", {}, "text", ["2.0", "Success"] ]
List<JsonValue> array = first.getArray();
if (array != null) {
List<List<String>> components = new ArrayList<List<String>>(array.size());
for (JsonValue value : array) {
if (value.isNull()) {
components.add(Collections.<String>emptyList());
continue;
}
Object obj = value.getValue();
if (obj != null) {
String s = obj.toString();
List<String> component = s.isEmpty() ? Collections.<String>emptyList() : Collections.singletonList(s);
components.add(component);
continue;
}
List<JsonValue> subArray = value.getArray();
if (subArray != null) {
List<String> component = new ArrayList<String>(subArray.size());
for (JsonValue subArrayValue : subArray) {
if (subArrayValue.isNull()) {
component.add("");
continue;
}
obj = subArrayValue.getValue();
if (obj != null) {
component.add(obj.toString());
continue;
}
}
if (component.size() == 1 && component.get(0).isEmpty()) {
component.clear();
}
components.add(component);
}
}
return components;
}
//get the first value if it's not enclosed in an array
//["request-status", {}, "text", "2.0"]
Object obj = first.getValue();
if (obj != null) {
List<List<String>> components = new ArrayList<List<String>>(1);
String s = obj.toString();
List<String> component = s.isEmpty() ? Collections.<String>emptyList() : Collections.singletonList(s);
components.add(component);
return components;
}
//["request-status", {}, "text", null]
if (first.isNull()) {
List<List<String>> components = new ArrayList<List<String>>(1);
components.add(Collections.<String>emptyList());
return components;
}
return Collections.emptyList();
}
/**
* Parses this jCal value as a multi-valued property value.
* @return the values or empty list if not found
*/
public List<String> asMulti() {
if (values.isEmpty()) {
return Collections.emptyList();
}
List<String> multi = new ArrayList<String>(values.size());
for (JsonValue value : values) {
if (value.isNull()) {
multi.add("");
continue;
}
Object obj = value.getValue();
if (obj != null) {
multi.add(obj.toString());
continue;
}
}
return multi;
}
/**
* Parses this jCal value as an object property value.
* @return the object or an empty map if not found
*/
public ListMultimap<String, String> asObject() {
if (values.isEmpty()) {
return new ListMultimap<String, String>(0);
}
Map<String, JsonValue> map = values.get(0).getObject();
if (map == null) {
return new ListMultimap<String, String>(0);
}
ListMultimap<String, String> values = new ListMultimap<String, String>();
for (Map.Entry<String, JsonValue> entry : map.entrySet()) {
String key = entry.getKey();
JsonValue value = entry.getValue();
if (value.isNull()) {
values.put(key, "");
continue;
}
Object obj = value.getValue();
if (obj != null) {
values.put(key, obj.toString());
continue;
}
List<JsonValue> array = value.getArray();
if (array != null) {
for (JsonValue element : array) {
if (element.isNull()) {
values.put(key, "");
continue;
}
obj = element.getValue();
if (obj != null) {
values.put(key, obj.toString());
}
}
}
}
return values;
}
}

@ -1,250 +0,0 @@
package biweekly.io.json;
import java.io.File;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.component.VTimezone;
import biweekly.io.SkipMeException;
import biweekly.io.StreamWriter;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.parameter.ICalParameters;
import biweekly.property.ICalProperty;
import biweekly.property.Version;
import biweekly.util.Utf8Writer;
import com.fasterxml.jackson.core.JsonGenerator;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Writes {@link ICalendar} objects to a JSON data stream (jCal).
* </p>
* <p>
* <b>Example:</b>
* </p>
*
* <pre class="brush:java">
* ICalendar ical1 = ...
* ICalendar ical2 = ...
* File file = new File("icals.json");
* JCalWriter writer = null;
* try {
* writer = new JCalWriter(file);
* writer.write(ical1);
* writer.write(ical2);
* } finally {
* if (writer != null) writer.close();
* }
* </pre>
* @author Michael Angstadt
* @see <a href="http://tools.ietf.org/html/rfc7265">RFC 7265</a>
*/
public class JCalWriter extends StreamWriter implements Flushable {
private final JCalRawWriter writer;
private final ICalVersion targetVersion = ICalVersion.V2_0;
/**
* @param out the output stream to write to (UTF-8 encoding will be used)
*/
public JCalWriter(OutputStream out) {
this(new Utf8Writer(out));
}
/**
* @param out the output stream to write to (UTF-8 encoding will be used)
* @param wrapInArray true to wrap all iCalendar objects in a parent array,
* false not to (useful when writing more than one iCalendar object)
*/
public JCalWriter(OutputStream out, boolean wrapInArray) {
this(new Utf8Writer(out), wrapInArray);
}
/**
* @param file the file to write to (UTF-8 encoding will be used)
* @throws IOException if the file cannot be written to
*/
public JCalWriter(File file) throws IOException {
this(new Utf8Writer(file));
}
/**
* @param file the file to write to (UTF-8 encoding will be used)
* @param wrapInArray true to wrap all iCalendar objects in a parent array,
* false not to (useful when writing more than one iCalendar object)
* @throws IOException if the file cannot be written to
*/
public JCalWriter(File file, boolean wrapInArray) throws IOException {
this(new Utf8Writer(file), wrapInArray);
}
/**
* @param writer the writer to write to
*/
public JCalWriter(Writer writer) {
this(writer, false);
}
/**
* @param writer the writer to write to
* @param wrapInArray true to wrap all iCalendar objects in a parent array,
* false not to (useful when writing more than one iCalendar object)
*/
public JCalWriter(Writer writer, boolean wrapInArray) {
this.writer = new JCalRawWriter(writer, wrapInArray);
}
/**
* @param generator the generator to write to
*/
public JCalWriter(JsonGenerator generator) {
this.writer = new JCalRawWriter(generator);
}
/**
* Gets whether or not the JSON will be pretty-printed.
* @return true if it will be pretty-printed, false if not (defaults to
* false)
*/
public boolean isPrettyPrint() {
return writer.isPrettyPrint();
}
/**
* Sets whether or not to pretty-print the JSON.
* @param prettyPrint true to pretty-print it, false not to (defaults to
* false)
*/
public void setPrettyPrint(boolean prettyPrint) {
writer.setPrettyPrint(prettyPrint);
}
@Override
protected void _write(ICalendar ical) throws IOException {
writeComponent(ical);
}
@Override
protected ICalVersion getTargetVersion() {
return targetVersion;
}
/**
* Writes a component to the data stream.
* @param component the component to write
* @throws IllegalArgumentException if the scribe class for a component or
* property object cannot be found (only happens when an experimental
* property/component scribe is not registered with the
* {@code registerScribe} method.)
* @throws IOException if there's a problem writing to the data stream
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void writeComponent(ICalComponent component) throws IOException {
ICalComponentScribe componentScribe = index.getComponentScribe(component);
writer.writeStartComponent(componentScribe.getComponentName().toLowerCase());
List propertyObjs = componentScribe.getProperties(component);
if (component instanceof ICalendar && component.getProperty(Version.class) == null) {
propertyObjs.add(0, new Version(targetVersion));
}
//write properties
for (Object propertyObj : propertyObjs) {
context.setParent(component); //set parent here incase a scribe resets the parent
ICalProperty property = (ICalProperty) propertyObj;
ICalPropertyScribe propertyScribe = index.getPropertyScribe(property);
//marshal property
ICalParameters parameters;
JCalValue value;
try {
parameters = propertyScribe.prepareParameters(property, context);
value = propertyScribe.writeJson(property, context);
} catch (SkipMeException e) {
continue;
}
//write property
String propertyName = propertyScribe.getPropertyName(targetVersion).toLowerCase();
ICalDataType dataType = propertyScribe.dataType(property, targetVersion);
writer.writeProperty(propertyName, parameters, dataType, value);
}
//write sub-components
List subComponents = componentScribe.getComponents(component);
if (component instanceof ICalendar) {
//add the VTIMEZONE components that were auto-generated by TimezoneOptions
Collection<VTimezone> tzs = getTimezoneComponents();
for (VTimezone tz : tzs) {
if (!subComponents.contains(tz)) {
subComponents.add(0, tz);
}
}
}
for (Object subComponentObj : subComponents) {
ICalComponent subComponent = (ICalComponent) subComponentObj;
writeComponent(subComponent);
}
writer.writeEndComponent();
}
/**
* Flushes the stream.
* @throws IOException if there's a problem flushing the stream
*/
public void flush() throws IOException {
writer.flush();
}
/**
* Finishes writing the JSON document and closes the underlying
* {@link Writer} object.
* @throws IOException if there's a problem closing the stream
*/
public void close() throws IOException {
writer.close();
}
/**
* Finishes writing the JSON document so that it is syntactically correct.
* No more iCalendar objects can be written once this method is called.
* @throws IOException if there's a problem writing to the data stream
*/
public void closeJsonStream() throws IOException {
writer.closeJsonStream();
}
}

@ -1,166 +0,0 @@
package biweekly.io.json;
import java.util.List;
import java.util.Map;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Represents a JSON value, array, or object.
* @author Michael Angstadt
*/
public class JsonValue {
private final boolean isNull;
private final Object value;
private final List<JsonValue> array;
private final Map<String, JsonValue> object;
/**
* Creates a JSON value (such as a string or integer).
* @param value the value
*/
public JsonValue(Object value) {
this.value = value;
array = null;
object = null;
isNull = (value == null);
}
/**
* Creates a JSON array.
* @param array the array elements
*/
public JsonValue(List<JsonValue> array) {
this.array = array;
value = null;
object = null;
isNull = (array == null);
}
/**
* Creates a JSON object.
* @param object the object fields
*/
public JsonValue(Map<String, JsonValue> object) {
this.object = object;
value = null;
array = null;
isNull = (object == null);
}
/**
* Gets the JSON value.
* @return the value or null if it's not a JSON value
*/
public Object getValue() {
return value;
}
/**
* Gets the JSON array elements.
* @return the array elements or null if it's not a JSON array
*/
public List<JsonValue> getArray() {
return array;
}
/**
* Gets the JSON object.
* @return the object or null if it's not a JSON object
*/
public Map<String, JsonValue> getObject() {
return object;
}
/**
* Determines if the value is "null" or not.
* @return true if the value is "null", false if not
*/
public boolean isNull() {
return isNull;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((array == null) ? 0 : array.hashCode());
result = prime * result + (isNull ? 1231 : 1237);
result = prime * result + ((object == null) ? 0 : object.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JsonValue other = (JsonValue) obj;
if (array == null) {
if (other.array != null)
return false;
} else if (!array.equals(other.array))
return false;
if (isNull != other.isNull)
return false;
if (object == null) {
if (other.object != null)
return false;
} else if (!object.equals(other.object))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
@Override
public String toString() {
if (isNull) {
return "NULL";
}
if (value != null) {
return "VALUE = " + value;
}
if (array != null) {
return "ARRAY = " + array;
}
if (object != null) {
return "OBJECT = " + object;
}
return "";
}
}

@ -1,4 +0,0 @@
/**
* Contains classes for reading and writing jCals (JSON-encoded iCalendar objects).
*/
package biweekly.io.json;

@ -1,4 +0,0 @@
/**
* Contains I/O related classes.
*/
package biweekly.io;

@ -1,451 +0,0 @@
package biweekly.io.scribe;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import biweekly.ICalVersion;
import biweekly.ICalendar;
import biweekly.component.ICalComponent;
import biweekly.component.RawComponent;
import biweekly.io.scribe.component.DaylightSavingsTimeScribe;
import biweekly.io.scribe.component.ICalComponentScribe;
import biweekly.io.scribe.component.ICalendarScribe;
import biweekly.io.scribe.component.RawComponentScribe;
import biweekly.io.scribe.component.StandardTimeScribe;
import biweekly.io.scribe.component.VAlarmScribe;
import biweekly.io.scribe.component.VEventScribe;
import biweekly.io.scribe.component.VFreeBusyScribe;
import biweekly.io.scribe.component.VJournalScribe;
import biweekly.io.scribe.component.VTimezoneScribe;
import biweekly.io.scribe.component.VTodoScribe;
import biweekly.io.scribe.property.ActionScribe;
import biweekly.io.scribe.property.AttachmentScribe;
import biweekly.io.scribe.property.AttendeeScribe;
import biweekly.io.scribe.property.AudioAlarmScribe;
import biweekly.io.scribe.property.CalendarScaleScribe;
import biweekly.io.scribe.property.CategoriesScribe;
import biweekly.io.scribe.property.ClassificationScribe;
import biweekly.io.scribe.property.ColorScribe;
import biweekly.io.scribe.property.CommentScribe;
import biweekly.io.scribe.property.CompletedScribe;
import biweekly.io.scribe.property.ConferenceScribe;
import biweekly.io.scribe.property.ContactScribe;
import biweekly.io.scribe.property.CreatedScribe;
import biweekly.io.scribe.property.DateDueScribe;
import biweekly.io.scribe.property.DateEndScribe;
import biweekly.io.scribe.property.DateStartScribe;
import biweekly.io.scribe.property.DateTimeStampScribe;
import biweekly.io.scribe.property.DaylightScribe;
import biweekly.io.scribe.property.DescriptionScribe;
import biweekly.io.scribe.property.DisplayAlarmScribe;
import biweekly.io.scribe.property.DurationPropertyScribe;
import biweekly.io.scribe.property.EmailAlarmScribe;
import biweekly.io.scribe.property.ExceptionDatesScribe;
import biweekly.io.scribe.property.ExceptionRuleScribe;
import biweekly.io.scribe.property.FreeBusyScribe;
import biweekly.io.scribe.property.GeoScribe;
import biweekly.io.scribe.property.ICalPropertyScribe;
import biweekly.io.scribe.property.ImageScribe;
import biweekly.io.scribe.property.LastModifiedScribe;
import biweekly.io.scribe.property.LocationScribe;
import biweekly.io.scribe.property.MethodScribe;
import biweekly.io.scribe.property.NameScribe;
import biweekly.io.scribe.property.OrganizerScribe;
import biweekly.io.scribe.property.PercentCompleteScribe;
import biweekly.io.scribe.property.PriorityScribe;
import biweekly.io.scribe.property.ProcedureAlarmScribe;
import biweekly.io.scribe.property.ProductIdScribe;
import biweekly.io.scribe.property.RawPropertyScribe;
import biweekly.io.scribe.property.RecurrenceDatesScribe;
import biweekly.io.scribe.property.RecurrenceIdScribe;
import biweekly.io.scribe.property.RecurrenceRuleScribe;
import biweekly.io.scribe.property.RefreshIntervalScribe;
import biweekly.io.scribe.property.RelatedToScribe;
import biweekly.io.scribe.property.RepeatScribe;
import biweekly.io.scribe.property.RequestStatusScribe;
import biweekly.io.scribe.property.ResourcesScribe;
import biweekly.io.scribe.property.SequenceScribe;
import biweekly.io.scribe.property.SourceScribe;
import biweekly.io.scribe.property.StatusScribe;
import biweekly.io.scribe.property.SummaryScribe;
import biweekly.io.scribe.property.TimezoneIdScribe;
import biweekly.io.scribe.property.TimezoneNameScribe;
import biweekly.io.scribe.property.TimezoneOffsetFromScribe;
import biweekly.io.scribe.property.TimezoneOffsetToScribe;
import biweekly.io.scribe.property.TimezoneScribe;
import biweekly.io.scribe.property.TimezoneUrlScribe;
import biweekly.io.scribe.property.TransparencyScribe;
import biweekly.io.scribe.property.TriggerScribe;
import biweekly.io.scribe.property.UidScribe;
import biweekly.io.scribe.property.UrlScribe;
import biweekly.io.scribe.property.VersionScribe;
import biweekly.io.scribe.property.XmlScribe;
import biweekly.io.xml.XCalNamespaceContext;
import biweekly.property.ICalProperty;
import biweekly.property.RawProperty;
import biweekly.property.Xml;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* <p>
* Manages a listing of component and property scribes. This is useful for
* injecting the scribes of any experimental components or properties you have
* defined into a reader or writer object. The same ScribeIndex instance can be
* reused and injected into multiple reader/writer classes.
* </p>
* <p>
* <b>Example:</b>
* </p>
*
* <pre class="brush:java">
* //init the index
* ScribeIndex index = new ScribeIndex();
* index.register(new CustomPropertyScribe());
* index.register(new AnotherCustomPropertyScribe());
* index.register(new CustomComponentScribe());
*
* //inject into a reader class
* ICalReader reader = new ICalReader(...);
* reader.setScribeIndex(index);
* List&lt;ICalendar&gt; icals = new ArrayList&lt;ICalendar&gt;();
* ICalendar ical;
* while ((ical = reader.readNext()) != null) {
* icals.add(ical);
* }
*
* //inject the same instance in another reader/writer class
* JCalWriter writer = new JCalWriter(...);
* writer.setScribeIndex(index);
* for (ICalendar ical : icals) {
* writer.write(ical);
* }
* </pre>
* @author Michael Angstadt
*/
public class ScribeIndex {
//define standard component scribes
private static final Map<String, ICalComponentScribe<? extends ICalComponent>> standardCompByName = new HashMap<String, ICalComponentScribe<? extends ICalComponent>>();
private static final Map<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>> standardCompByClass = new HashMap<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>>();
static {
registerStandard(new ICalendarScribe());
registerStandard(new VAlarmScribe());
registerStandard(new VEventScribe());
registerStandard(new VFreeBusyScribe());
registerStandard(new VJournalScribe());
registerStandard(new VTodoScribe());
registerStandard(new VTimezoneScribe());
registerStandard(new StandardTimeScribe());
registerStandard(new DaylightSavingsTimeScribe());
}
//define standard property scribes
private static final Map<String, ICalPropertyScribe<? extends ICalProperty>> standardPropByName = new HashMap<String, ICalPropertyScribe<? extends ICalProperty>>();
private static final Map<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>> standardPropByClass = new HashMap<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>>();
private static final Map<QName, ICalPropertyScribe<? extends ICalProperty>> standardPropByQName = new HashMap<QName, ICalPropertyScribe<? extends ICalProperty>>();
static {
//RFC 5545
registerStandard(new ActionScribe());
registerStandard(new AttachmentScribe());
registerStandard(new AttendeeScribe());
registerStandard(new CalendarScaleScribe());
registerStandard(new CategoriesScribe());
registerStandard(new ClassificationScribe());
registerStandard(new CommentScribe());
registerStandard(new CompletedScribe());
registerStandard(new ContactScribe());
registerStandard(new CreatedScribe());
registerStandard(new DateDueScribe());
registerStandard(new DateEndScribe());
registerStandard(new DateStartScribe());
registerStandard(new DateTimeStampScribe());
registerStandard(new DescriptionScribe());
registerStandard(new DurationPropertyScribe());
registerStandard(new ExceptionDatesScribe());
registerStandard(new FreeBusyScribe());
registerStandard(new GeoScribe());
registerStandard(new LastModifiedScribe());
registerStandard(new LocationScribe());
registerStandard(new MethodScribe());
registerStandard(new OrganizerScribe());
registerStandard(new PercentCompleteScribe());
registerStandard(new PriorityScribe());
registerStandard(new ProductIdScribe());
registerStandard(new RecurrenceDatesScribe());
registerStandard(new RecurrenceIdScribe());
registerStandard(new RecurrenceRuleScribe());
registerStandard(new RelatedToScribe());
registerStandard(new RepeatScribe());
registerStandard(new RequestStatusScribe());
registerStandard(new ResourcesScribe());
registerStandard(new SequenceScribe());
registerStandard(new StatusScribe());
registerStandard(new SummaryScribe());
registerStandard(new TimezoneIdScribe());
registerStandard(new TimezoneNameScribe());
registerStandard(new TimezoneOffsetFromScribe());
registerStandard(new TimezoneOffsetToScribe());
registerStandard(new TimezoneUrlScribe());
registerStandard(new TransparencyScribe());
registerStandard(new TriggerScribe());
registerStandard(new UidScribe());
registerStandard(new UrlScribe());
registerStandard(new VersionScribe());
//RFC 6321
registerStandard(new XmlScribe());
//RFC 2445
registerStandard(new ExceptionRuleScribe());
//vCal
registerStandard(new AudioAlarmScribe());
registerStandard(new DaylightScribe());
registerStandard(new DisplayAlarmScribe());
registerStandard(new EmailAlarmScribe());
registerStandard(new ProcedureAlarmScribe());
registerStandard(new TimezoneScribe());
//draft-ietf-calext-extensions-01
registerStandard(new ColorScribe());
registerStandard(new ConferenceScribe());
registerStandard(new ImageScribe());
registerStandard(new NameScribe());
registerStandard(new SourceScribe());
registerStandard(new RefreshIntervalScribe());
}
private final Map<String, ICalComponentScribe<? extends ICalComponent>> experimentalCompByName = new HashMap<String, ICalComponentScribe<? extends ICalComponent>>(0);
private final Map<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>> experimentalCompByClass = new HashMap<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>>(0);
private final Map<String, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByName = new HashMap<String, ICalPropertyScribe<? extends ICalProperty>>(0);
private final Map<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByClass = new HashMap<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>>(0);
private final Map<QName, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByQName = new HashMap<QName, ICalPropertyScribe<? extends ICalProperty>>(0);
/**
* Gets a component scribe by name.
* @param componentName the component name (e.g. "VEVENT")
* @param version the version of the iCalendar object being parsed
* @return the component scribe or a {@link RawComponentScribe} if not found
*/
public ICalComponentScribe<? extends ICalComponent> getComponentScribe(String componentName, ICalVersion version) {
componentName = componentName.toUpperCase();
ICalComponentScribe<? extends ICalComponent> scribe = experimentalCompByName.get(componentName);
if (scribe == null) {
scribe = standardCompByName.get(componentName);
}
if (scribe == null) {
return new RawComponentScribe(componentName);
}
if (version != null && !scribe.getSupportedVersions().contains(version)) {
//treat the component as a raw component if the current iCal version doesn't support it
return new RawComponentScribe(componentName);
}
return scribe;
}
/**
* Gets a property scribe by name.
* @param propertyName the property name (e.g. "UID")
* @param version the version of the iCalendar object being parsed
* @return the property scribe or a {@link RawPropertyScribe} if not found
*/
public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(String propertyName, ICalVersion version) {
propertyName = propertyName.toUpperCase();
String key = propertyNameKey(propertyName, version);
ICalPropertyScribe<? extends ICalProperty> scribe = experimentalPropByName.get(key);
if (scribe == null) {
scribe = standardPropByName.get(key);
}
if (scribe == null) {
return new RawPropertyScribe(propertyName);
}
if (version != null && !scribe.getSupportedVersions().contains(version)) {
//treat the property as a raw property if the current iCal version doesn't support it
return new RawPropertyScribe(propertyName);
}
return scribe;
}
/**
* Gets a component scribe by class.
* @param clazz the component class
* @return the component scribe or null if not found
*/
public ICalComponentScribe<? extends ICalComponent> getComponentScribe(Class<? extends ICalComponent> clazz) {
ICalComponentScribe<? extends ICalComponent> scribe = experimentalCompByClass.get(clazz);
if (scribe != null) {
return scribe;
}
return standardCompByClass.get(clazz);
}
/**
* Gets a property scribe by class.
* @param clazz the property class
* @return the property scribe or null if not found
*/
public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(Class<? extends ICalProperty> clazz) {
ICalPropertyScribe<? extends ICalProperty> scribe = experimentalPropByClass.get(clazz);
if (scribe != null) {
return scribe;
}
return standardPropByClass.get(clazz);
}
/**
* Gets the appropriate component scribe for a given component instance.
* @param component the component instance
* @return the component scribe or null if not found
*/
public ICalComponentScribe<? extends ICalComponent> getComponentScribe(ICalComponent component) {
if (component instanceof RawComponent) {
RawComponent raw = (RawComponent) component;
return new RawComponentScribe(raw.getName());
}
return getComponentScribe(component.getClass());
}
/**
* Gets the appropriate property scribe for a given property instance.
* @param property the property instance
* @return the property scribe or null if not found
*/
public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(ICalProperty property) {
if (property instanceof RawProperty) {
RawProperty raw = (RawProperty) property;
return new RawPropertyScribe(raw.getName());
}
return getPropertyScribe(property.getClass());
}
/**
* Gets a property scribe by XML local name and namespace.
* @param qname the XML local name and namespace
* @return the property scribe or a {@link XmlScribe} if not found
*/
public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(QName qname) {
ICalPropertyScribe<? extends ICalProperty> scribe = experimentalPropByQName.get(qname);
if (scribe == null) {
scribe = standardPropByQName.get(qname);
}
if (scribe == null || !scribe.getSupportedVersions().contains(ICalVersion.V2_0)) {
if (XCalNamespaceContext.XCAL_NS.equals(qname.getNamespaceURI())) {
return new RawPropertyScribe(qname.getLocalPart().toUpperCase());
}
return getPropertyScribe(Xml.class);
}
return scribe;
}
/**
* Registers a component scribe.
* @param scribe the scribe to register
*/
public void register(ICalComponentScribe<? extends ICalComponent> scribe) {
experimentalCompByName.put(scribe.getComponentName().toUpperCase(), scribe);
experimentalCompByClass.put(scribe.getComponentClass(), scribe);
}
/**
* Registers a property scribe.
* @param scribe the scribe to register
*/
public void register(ICalPropertyScribe<? extends ICalProperty> scribe) {
for (ICalVersion version : ICalVersion.values()) {
experimentalPropByName.put(propertyNameKey(scribe, version), scribe);
}
experimentalPropByClass.put(scribe.getPropertyClass(), scribe);
experimentalPropByQName.put(scribe.getQName(), scribe);
}
/**
* Unregisters a component scribe.
* @param scribe the scribe to unregister
*/
public void unregister(ICalComponentScribe<? extends ICalComponent> scribe) {
experimentalCompByName.remove(scribe.getComponentName().toUpperCase());
experimentalCompByClass.remove(scribe.getComponentClass());
}
/**
* Unregisters a property scribe
* @param scribe the scribe to unregister
*/
public void unregister(ICalPropertyScribe<? extends ICalProperty> scribe) {
for (ICalVersion version : ICalVersion.values()) {
experimentalPropByName.remove(propertyNameKey(scribe, version));
}
experimentalPropByClass.remove(scribe.getPropertyClass());
experimentalPropByQName.remove(scribe.getQName());
}
/**
* Convenience method for getting the scribe of the root iCalendar component
* ("VCALENDAR").
* @return the scribe
*/
public static ICalendarScribe getICalendarScribe() {
return (ICalendarScribe) standardCompByClass.get(ICalendar.class);
}
private static void registerStandard(ICalComponentScribe<? extends ICalComponent> scribe) {
standardCompByName.put(scribe.getComponentName().toUpperCase(), scribe);
standardCompByClass.put(scribe.getComponentClass(), scribe);
}
private static void registerStandard(ICalPropertyScribe<? extends ICalProperty> scribe) {
for (ICalVersion version : ICalVersion.values()) {
standardPropByName.put(propertyNameKey(scribe, version), scribe);
}
standardPropByClass.put(scribe.getPropertyClass(), scribe);
standardPropByQName.put(scribe.getQName(), scribe);
}
private static String propertyNameKey(ICalPropertyScribe<? extends ICalProperty> scribe, ICalVersion version) {
return propertyNameKey(scribe.getPropertyName(version), version);
}
private static String propertyNameKey(String propertyName, ICalVersion version) {
return version.ordinal() + propertyName.toUpperCase();
}
}

@ -1,42 +0,0 @@
package biweekly.io.scribe.component;
import biweekly.component.DaylightSavingsTime;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class DaylightSavingsTimeScribe extends ObservanceScribe<DaylightSavingsTime> {
public DaylightSavingsTimeScribe() {
super(DaylightSavingsTime.class, "DAYLIGHT");
}
@Override
protected DaylightSavingsTime _newInstance() {
return new DaylightSavingsTime();
}
}

@ -1,146 +0,0 @@
package biweekly.io.scribe.component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.ICalComponent;
import biweekly.io.DataModelConversionException;
import biweekly.property.ICalProperty;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Base class for iCalendar component scribes.
* @param <T> the component class
* @author Michael Angstadt
*/
public abstract class ICalComponentScribe<T extends ICalComponent> {
private static final Set<ICalVersion> allVersions = Collections.unmodifiableSet(EnumSet.allOf(ICalVersion.class));
protected final Class<T> clazz;
protected final String componentName;
/**
* Creates a new component scribe.
* @param clazz the component's class
* @param componentName the component's name (e.g. "VEVENT")
*/
public ICalComponentScribe(Class<T> clazz, String componentName) {
this.clazz = clazz;
this.componentName = componentName;
}
/**
* Gets the iCalendar versions that support this component. This method
* returns all iCalendar versions unless overridden by the child scribe.
* @return the iCalendar versions
*/
public Set<ICalVersion> getSupportedVersions() {
return allVersions;
}
/**
* Gets the component class.
* @return the component class.
*/
public Class<T> getComponentClass() {
return clazz;
}
/**
* Gets the component's name.
* @return the compent's name (e.g. "VEVENT")
*/
public String getComponentName() {
return componentName;
}
/**
* Creates a new instance of the component class that doesn't have any
* properties or sub-components.
* @return the new instance
*/
public T emptyInstance() {
T component = _newInstance();
//remove any properties/components that were created in the constructor
component.getProperties().clear();
component.getComponents().clear();
return component;
}
/**
* Creates a new instance of the component class.
* @return the new instance
*/
protected abstract T _newInstance();
/**
* Gets the sub-components to marshal. Child classes can override this for
* better control over which components are marshalled.
* @param component the component
* @return the sub-components to marshal
*/
public List<ICalComponent> getComponents(T component) {
return new ArrayList<ICalComponent>(component.getComponents().values());
}
/**
* Gets the properties to marshal. Child classes can override this for
* better control over which properties are marshalled.
* @param component the component
* @return the properties to marshal
*/
public List<ICalProperty> getProperties(T component) {
return new ArrayList<ICalProperty>(component.getProperties().values());
}
/**
* <p>
* Checks this component to see if it needs to be converted to a different
* data model before writing it out, throwing a
* {@link DataModelConversionException} if it does.
* </p>
* <p>
* Child classes should override this method if the component requires
* any such conversion. The default implementation of this method does
* nothing.
* </p>
* @param component the component being written
* @param parent the component's parent or null if it has no parent
* @param version the version iCalendar object being written
* @throws DataModelConversionException if the component needs to be
* converted
*/
public void checkForDataModelConversions(T component, ICalComponent parent, ICalVersion version) {
//empty
}
}

@ -1,70 +0,0 @@
package biweekly.io.scribe.component;
import java.util.ArrayList;
import java.util.List;
import biweekly.ICalendar;
import biweekly.property.ICalProperty;
import biweekly.property.ProductId;
import biweekly.property.Version;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class ICalendarScribe extends ICalComponentScribe<ICalendar> {
public ICalendarScribe() {
super(ICalendar.class, "VCALENDAR");
}
@Override
protected ICalendar _newInstance() {
return new ICalendar();
}
@Override
public List<ICalProperty> getProperties(ICalendar component) {
List<ICalProperty> properties = new ArrayList<ICalProperty>(component.getProperties().values());
/*
* Move VERSION properties to the front (if any are present), followed
* by PRODID properties. This is not required by the specs, but may help
* with interoperability because all the examples in the specs put the
* VERSION and PRODID at the very beginning of the iCalendar.
*/
moveToFront(ProductId.class, component, properties);
moveToFront(Version.class, component, properties);
return properties;
}
private <T extends ICalProperty> void moveToFront(Class<T> clazz, ICalendar component, List<ICalProperty> properties) {
List<T> toMove = component.getProperties(clazz);
properties.removeAll(toMove);
properties.addAll(0, toMove);
}
}

@ -1,46 +0,0 @@
package biweekly.io.scribe.component;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.Observance;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public abstract class ObservanceScribe<T extends Observance> extends ICalComponentScribe<T> {
protected ObservanceScribe(Class<T> clazz, String componentName) {
super(clazz, componentName);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,46 +0,0 @@
package biweekly.io.scribe.component;
import biweekly.component.RawComponent;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class RawComponentScribe extends ICalComponentScribe<RawComponent> {
/**
* Creates a new raw component scribe.
* @param componentName the component's name (e.g. "X-PARTY")
*/
public RawComponentScribe(String componentName) {
super(RawComponent.class, componentName);
}
@Override
protected RawComponent _newInstance() {
return new RawComponent(componentName);
}
}

@ -1,42 +0,0 @@
package biweekly.io.scribe.component;
import biweekly.component.StandardTime;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class StandardTimeScribe extends ObservanceScribe<StandardTime> {
public StandardTimeScribe() {
super(StandardTime.class, "STANDARD");
}
@Override
protected StandardTime _newInstance() {
return new StandardTime();
}
}

@ -1,230 +0,0 @@
package biweekly.io.scribe.component;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.ICalComponent;
import biweekly.component.VAlarm;
import biweekly.io.DataModelConversionException;
import biweekly.parameter.Related;
import biweekly.property.Action;
import biweekly.property.Attachment;
import biweekly.property.Attendee;
import biweekly.property.AudioAlarm;
import biweekly.property.DateEnd;
import biweekly.property.DateStart;
import biweekly.property.Description;
import biweekly.property.DisplayAlarm;
import biweekly.property.DurationProperty;
import biweekly.property.EmailAlarm;
import biweekly.property.ProcedureAlarm;
import biweekly.property.Repeat;
import biweekly.property.Trigger;
import biweekly.property.VCalAlarmProperty;
import biweekly.property.ValuedProperty;
import biweekly.util.Duration;
import biweekly.util.StringUtils;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VAlarmScribe extends ICalComponentScribe<VAlarm> {
public VAlarmScribe() {
super(VAlarm.class, "VALARM");
}
@Override
protected VAlarm _newInstance() {
return new VAlarm(null, null);
}
@Override
public void checkForDataModelConversions(VAlarm component, ICalComponent parent, ICalVersion version) {
if (version != ICalVersion.V1_0) {
return;
}
VCalAlarmProperty vcalAlarm = convert(component, parent);
if (vcalAlarm == null) {
return;
}
DataModelConversionException e = new DataModelConversionException(null);
e.getProperties().add(vcalAlarm);
throw e;
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
/**
* Converts a {@link VAlarm} component to a vCal alarm property.
* @param valarm the component
* @param parent the component's parent
* @return the vCal alarm property or null if it cannot be converted
*/
private static VCalAlarmProperty convert(VAlarm valarm, ICalComponent parent) {
VCalAlarmProperty property = create(valarm);
if (property == null) {
return null;
}
property.setStart(determineStartDate(valarm, parent));
DurationProperty duration = valarm.getDuration();
if (duration != null) {
property.setSnooze(duration.getValue());
}
Repeat repeat = valarm.getRepeat();
if (repeat != null) {
property.setRepeat(repeat.getValue());
}
return property;
}
/**
* Creates a new {@link VCalAlarmProperty} based on the given {@link VAlarm}
* component, setting fields that are common to all
* {@link VCalAlarmProperty} classes.
* @param valarm the source component
* @return the property or null if it cannot be created
*/
private static VCalAlarmProperty create(VAlarm valarm) {
Action action = valarm.getAction();
if (action == null) {
return null;
}
if (action.isAudio()) {
AudioAlarm aalarm = new AudioAlarm();
List<Attachment> attaches = valarm.getAttachments();
if (!attaches.isEmpty()) {
Attachment attach = attaches.get(0);
String formatType = attach.getFormatType();
aalarm.setParameter("TYPE", formatType);
byte[] data = attach.getData();
if (data != null) {
aalarm.setData(data);
}
String uri = attach.getUri();
if (uri != null) {
String contentId = StringUtils.afterPrefixIgnoreCase(uri, "cid:");
if (contentId == null) {
aalarm.setUri(uri);
} else {
aalarm.setContentId(contentId);
}
}
}
return aalarm;
}
if (action.isDisplay()) {
Description description = valarm.getDescription();
String text = ValuedProperty.getValue(description);
return new DisplayAlarm(text);
}
if (action.isEmail()) {
List<Attendee> attendees = valarm.getAttendees();
String email = attendees.isEmpty() ? null : attendees.get(0).getEmail();
EmailAlarm malarm = new EmailAlarm(email);
Description description = valarm.getDescription();
String note = ValuedProperty.getValue(description);
malarm.setNote(note);
return malarm;
}
if (action.isProcedure()) {
Description description = valarm.getDescription();
String path = ValuedProperty.getValue(description);
return new ProcedureAlarm(path);
}
return null;
}
/**
* Determines what the alarm property's start date should be.
* @param valarm the component that is being converted to a vCal alarm
* property
* @param parent the component's parent
* @return the start date or null if it cannot be determined
*/
private static Date determineStartDate(VAlarm valarm, ICalComponent parent) {
Trigger trigger = valarm.getTrigger();
if (trigger == null) {
return null;
}
Date triggerStart = trigger.getDate();
if (triggerStart != null) {
return triggerStart;
}
Duration triggerDuration = trigger.getDuration();
if (triggerDuration == null) {
return null;
}
if (parent == null) {
return null;
}
Related related = trigger.getRelated();
Date date = null;
if (related == Related.START) {
date = ValuedProperty.getValue(parent.getProperty(DateStart.class));
} else if (related == Related.END) {
date = ValuedProperty.getValue(parent.getProperty(DateEnd.class));
if (date == null) {
Date dateStart = ValuedProperty.getValue(parent.getProperty(DateStart.class));
Duration duration = ValuedProperty.getValue(parent.getProperty(DurationProperty.class));
if (duration != null && dateStart != null) {
date = duration.add(dateStart);
}
}
}
return (date == null) ? null : triggerDuration.add(date);
}
}

@ -1,42 +0,0 @@
package biweekly.io.scribe.component;
import biweekly.component.VEvent;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VEventScribe extends ICalComponentScribe<VEvent> {
public VEventScribe() {
super(VEvent.class, "VEVENT");
}
@Override
protected VEvent _newInstance() {
return new VEvent();
}
}

@ -1,118 +0,0 @@
package biweekly.io.scribe.component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.VFreeBusy;
import biweekly.property.FreeBusy;
import biweekly.property.ICalProperty;
import biweekly.util.Period;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VFreeBusyScribe extends ICalComponentScribe<VFreeBusy> {
public VFreeBusyScribe() {
super(VFreeBusy.class, "VFREEBUSY");
}
@Override
public List<ICalProperty> getProperties(VFreeBusy component) {
List<ICalProperty> properties = super.getProperties(component);
List<FreeBusy> fb = new ArrayList<FreeBusy>(component.getFreeBusy());
if (fb.isEmpty()) {
return properties;
}
//sort FREEBUSY properties by start date (p.100)
Collections.sort(fb, new Comparator<FreeBusy>() {
public int compare(FreeBusy one, FreeBusy two) {
Date oneStart = getEarliestStartDate(one);
Date twoStart = getEarliestStartDate(two);
if (oneStart == null && twoStart == null) {
return 0;
}
if (oneStart == null) {
return 1;
}
if (twoStart == null) {
return -1;
}
return oneStart.compareTo(twoStart);
}
private Date getEarliestStartDate(FreeBusy fb) {
Date date = null;
for (Period tp : fb.getValues()) {
if (tp.getStartDate() == null) {
continue;
}
if (date == null || date.compareTo(tp.getStartDate()) > 0) {
date = tp.getStartDate();
}
}
return date;
}
});
//find index of first FREEBUSY instance
int index = 0;
for (ICalProperty prop : properties) {
if (prop instanceof FreeBusy) {
break;
}
index++;
}
//remove and re-add the FREEBUSY instances in sorted order
properties = new ArrayList<ICalProperty>(properties);
for (FreeBusy f : fb) {
properties.remove(f);
properties.add(index++, f);
}
return properties;
}
@Override
protected VFreeBusy _newInstance() {
return new VFreeBusy();
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,51 +0,0 @@
package biweekly.io.scribe.component;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.VJournal;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VJournalScribe extends ICalComponentScribe<VJournal> {
public VJournalScribe() {
super(VJournal.class, "VJOURNAL");
}
@Override
protected VJournal _newInstance() {
return new VJournal();
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,51 +0,0 @@
package biweekly.io.scribe.component;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.component.VTimezone;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VTimezoneScribe extends ICalComponentScribe<VTimezone> {
public VTimezoneScribe() {
super(VTimezone.class, "VTIMEZONE");
}
@Override
protected VTimezone _newInstance() {
return new VTimezone((String) null);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,42 +0,0 @@
package biweekly.io.scribe.component;
import biweekly.component.VTodo;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* @author Michael Angstadt
*/
public class VTodoScribe extends ICalComponentScribe<VTodo> {
public VTodoScribe() {
super(VTodo.class, "VTODO");
}
@Override
protected VTodo _newInstance() {
return new VTodo();
}
}

@ -1,4 +0,0 @@
/**
* Contains classes that marshal and unmarshal components.
*/
package biweekly.io.scribe.component;

@ -1,4 +0,0 @@
/**
* Contains classes that marshal and unmarshal components and properties in various formats.
*/
package biweekly.io.scribe;

@ -1,52 +0,0 @@
package biweekly.io.scribe.property;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.property.Action;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Action} properties.
* @author Michael Angstadt
*/
public class ActionScribe extends TextPropertyScribe<Action> {
public ActionScribe() {
super(Action.class, "ACTION");
}
@Override
protected Action newInstance(String value, ICalVersion version) {
return new Action(value);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,137 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.property.Attachment;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Attachment} properties.
* @author Michael Angstadt
*/
public class AttachmentScribe extends BinaryPropertyScribe<Attachment> {
public AttachmentScribe() {
super(Attachment.class, "ATTACH");
}
@Override
protected ICalDataType _dataType(Attachment property, ICalVersion version) {
if (property.getContentId() != null) {
return (version == ICalVersion.V1_0) ? ICalDataType.CONTENT_ID : ICalDataType.URI;
}
return super._dataType(property, version);
}
@Override
protected Attachment newInstance(byte[] data) {
/*
* Note: "formatType" will be set when the parameters are assigned to
* the property object.
*/
return new Attachment(null, data);
}
@Override
protected Attachment newInstance(String value, ICalDataType dataType) {
/*
* Note: "formatType" will be set when the parameters are assigned to
* the property object.
*/
if (dataType == ICalDataType.CONTENT_ID) {
String contentId = getCidUriValue(value);
if (contentId == null) {
contentId = value;
}
Attachment attach = new Attachment(null, (String) null);
attach.setContentId(contentId);
return attach;
}
String contentId = getCidUriValue(value);
if (contentId != null) {
Attachment attach = new Attachment(null, (String) null);
attach.setContentId(contentId);
return attach;
}
return new Attachment(null, value);
}
@Override
protected String _writeText(Attachment property, WriteContext context) {
String contentId = property.getContentId();
if (contentId != null) {
return (context.getVersion() == ICalVersion.V1_0) ? '<' + contentId + '>' : "cid:" + contentId;
}
return super._writeText(property, context);
}
@Override
protected void _writeXml(Attachment property, XCalElement element, WriteContext context) {
String contentId = property.getContentId();
if (contentId != null) {
element.append(ICalDataType.URI, "cid:" + contentId);
return;
}
super._writeXml(property, element, context);
}
@Override
protected JCalValue _writeJson(Attachment property, WriteContext context) {
String contentId = property.getContentId();
if (contentId != null) {
return JCalValue.single("cid:" + contentId);
}
return super._writeJson(property, context);
}
/**
* Gets the value of the given "cid" URI.
* @param uri the "cid" URI
* @return the URI value or null if the given string is not a "cid" URI
*/
private static String getCidUriValue(String uri) {
int colon = uri.indexOf(':');
if (colon == 3) {
String scheme = uri.substring(0, colon);
return "cid".equalsIgnoreCase(scheme) ? uri.substring(colon + 1) : null;
}
if (uri.length() > 0 && uri.charAt(0) == '<' && uri.charAt(uri.length() - 1) == '>') {
return uri.substring(1, uri.length() - 1);
}
return null;
}
}

@ -1,340 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Iterator;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.io.DataModelConversionException;
import biweekly.io.ParseContext;
import biweekly.io.WriteContext;
import biweekly.parameter.ICalParameters;
import biweekly.parameter.ParticipationLevel;
import biweekly.parameter.ParticipationStatus;
import biweekly.parameter.Role;
import biweekly.property.Attendee;
import biweekly.property.Organizer;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Attendee} properties.
* @author Michael Angstadt
*/
public class AttendeeScribe extends ICalPropertyScribe<Attendee> {
public AttendeeScribe() {
super(Attendee.class, "ATTENDEE");
}
@Override
protected ICalDataType _defaultDataType(ICalVersion version) {
switch (version) {
case V1_0:
return null;
default:
return ICalDataType.CAL_ADDRESS;
}
}
@Override
protected ICalDataType _dataType(Attendee property, ICalVersion version) {
if (version == ICalVersion.V1_0 && property.getUri() != null) {
return ICalDataType.URL;
}
return defaultDataType(version);
}
@Override
protected ICalParameters _prepareParameters(Attendee property, WriteContext context) {
/*
* Note: Parameter values are assigned using "put()" instead of the
* appropriate "setter" methods so that any existing parameter values
* are not overwritten.
*/
ICalParameters copy = new ICalParameters(property.getParameters());
//RSVP parameter
//1.0 - Uses the values "YES" and "NO"
//2.0 - Uses the values "TRUE" and "FALSE"
Boolean rsvp = property.getRsvp();
if (rsvp != null) {
String value;
switch (context.getVersion()) {
case V1_0:
value = rsvp ? "YES" : "NO";
break;
default:
value = rsvp ? "TRUE" : "FALSE";
break;
}
copy.put(ICalParameters.RSVP, value);
}
//ROLE and EXPECT parameters
//1.0 - Uses ROLE and EXPECT
//2.0 - Uses only ROLE
Role role = property.getRole();
ParticipationLevel level = property.getParticipationLevel();
switch (context.getVersion()) {
case V1_0:
if (role != null) {
copy.put(ICalParameters.ROLE, role.getValue());
}
if (level != null) {
copy.put(ICalParameters.EXPECT, level.getValue(context.getVersion()));
}
break;
default:
String value = null;
if (role == Role.CHAIR) {
value = role.getValue();
} else if (level != null) {
value = level.getValue(context.getVersion());
} else if (role != null) {
value = role.getValue();
}
if (value != null) {
copy.put(ICalParameters.ROLE, value);
}
break;
}
//PARTSTAT vs STATUS
//1.0 - Calls the parameter "STATUS"
//2.0 - Calls the parameter "PARTSTAT"
ParticipationStatus partStat = property.getParticipationStatus();
if (partStat != null) {
String paramName;
String paramValue;
switch (context.getVersion()) {
case V1_0:
paramName = ICalParameters.STATUS;
paramValue = (partStat == ParticipationStatus.NEEDS_ACTION) ? "NEEDS ACTION" : partStat.getValue();
break;
default:
paramName = ICalParameters.PARTSTAT;
paramValue = partStat.getValue();
break;
}
copy.put(paramName, paramValue);
}
//CN parameter
String name = property.getCommonName();
if (name != null && context.getVersion() != ICalVersion.V1_0) {
copy.put(ICalParameters.CN, name);
}
//EMAIL parameter
String uri = property.getUri();
String email = property.getEmail();
if (uri != null && email != null && context.getVersion() != ICalVersion.V1_0) {
copy.put(ICalParameters.EMAIL, email);
}
return copy;
}
@Override
protected Attendee _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
String uri = null, name = null, email = null;
Boolean rsvp = null;
Role role = null;
ParticipationLevel participationLevel = null;
ParticipationStatus participationStatus = null;
switch (context.getVersion()) {
case V1_0:
Iterator<String> it = parameters.get(ICalParameters.RSVP).iterator();
while (it.hasNext()) {
String rsvpStr = it.next();
if ("YES".equalsIgnoreCase(rsvpStr)) {
rsvp = Boolean.TRUE;
it.remove();
break;
}
if ("NO".equalsIgnoreCase(rsvpStr)) {
rsvp = Boolean.FALSE;
it.remove();
break;
}
}
String roleStr = parameters.first(ICalParameters.ROLE);
if (roleStr != null) {
role = Role.get(roleStr);
parameters.remove(ICalParameters.ROLE, roleStr);
}
String expectStr = parameters.getExpect();
if (expectStr != null) {
participationLevel = ParticipationLevel.get(expectStr);
parameters.remove(ICalParameters.EXPECT, expectStr);
}
String statusStr = parameters.getStatus();
if (statusStr != null) {
participationStatus = ParticipationStatus.get(statusStr);
parameters.remove(ICalParameters.STATUS, statusStr);
}
int bracketStart = value.lastIndexOf('<');
int bracketEnd = value.lastIndexOf('>');
if (bracketStart >= 0 && bracketEnd >= 0 && bracketStart < bracketEnd) {
name = value.substring(0, bracketStart).trim();
email = value.substring(bracketStart + 1, bracketEnd).trim();
} else if (dataType == ICalDataType.URL) {
uri = value;
} else {
email = value;
}
break;
default:
it = parameters.get(ICalParameters.RSVP).iterator();
while (it.hasNext()) {
String rsvpStr = it.next();
if ("TRUE".equalsIgnoreCase(rsvpStr)) {
rsvp = Boolean.TRUE;
it.remove();
break;
}
if ("FALSE".equalsIgnoreCase(rsvpStr)) {
rsvp = Boolean.FALSE;
it.remove();
break;
}
}
roleStr = parameters.first(ICalParameters.ROLE);
if (roleStr != null) {
if (roleStr.equalsIgnoreCase(Role.CHAIR.getValue())) {
role = Role.CHAIR;
} else {
ParticipationLevel l = ParticipationLevel.find(roleStr);
if (l == null) {
role = Role.get(roleStr);
} else {
participationLevel = l;
}
}
parameters.remove(ICalParameters.ROLE, roleStr);
}
String participationStatusStr = parameters.getParticipationStatus();
if (participationStatusStr != null) {
participationStatus = ParticipationStatus.get(participationStatusStr);
parameters.remove(ICalParameters.PARTSTAT, participationStatusStr);
}
name = parameters.getCommonName();
if (name != null) {
parameters.remove(ICalParameters.CN, name);
}
email = parameters.getEmail();
if (email == null) {
int colon = value.indexOf(':');
if (colon == 6) {
String scheme = value.substring(0, colon);
if (scheme.equalsIgnoreCase("mailto")) {
email = value.substring(colon + 1);
} else {
uri = value;
}
} else {
uri = value;
}
} else {
uri = value;
parameters.remove(ICalParameters.EMAIL, email);
}
break;
}
Attendee attendee = new Attendee(name, email, uri);
attendee.setParticipationStatus(participationStatus);
attendee.setParticipationLevel(participationLevel);
attendee.setRole(role);
attendee.setRsvp(rsvp);
if (context.getVersion() == ICalVersion.V1_0 && attendee.getRole() == Role.ORGANIZER) {
Organizer organizer = new Organizer(attendee.getCommonName(), attendee.getEmail());
organizer.setUri(attendee.getUri());
organizer.setParameters(parameters);
attendee.setParameters(parameters);
DataModelConversionException conversionException = new DataModelConversionException(attendee);
conversionException.getProperties().add(organizer);
throw conversionException;
}
return attendee;
}
@Override
protected String _writeText(Attendee property, WriteContext context) {
String uri = property.getUri();
if (uri != null) {
return uri;
}
String name = property.getCommonName();
String email = property.getEmail();
switch (context.getVersion()) {
case V1_0:
if (email != null) {
String value = (name == null) ? email : name + " <" + email + ">";
return VObjectPropertyValues.escape(value);
}
break;
default:
if (email != null) {
return "mailto:" + email;
}
break;
}
return "";
}
}

@ -1,145 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Collections;
import java.util.List;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.component.VAlarm;
import biweekly.property.Action;
import biweekly.property.Attachment;
import biweekly.property.AudioAlarm;
import biweekly.util.org.apache.commons.codec.binary.Base64;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues.SemiStructuredValueIterator;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link AudioAlarm} properties.
* @author Michael Angstadt
*/
public class AudioAlarmScribe extends VCalAlarmPropertyScribe<AudioAlarm> {
public AudioAlarmScribe() {
super(AudioAlarm.class, "AALARM");
}
@Override
protected ICalDataType _dataType(AudioAlarm property, ICalVersion version) {
if (property.getUri() != null) {
return ICalDataType.URL;
}
if (property.getData() != null) {
return ICalDataType.BINARY;
}
if (property.getContentId() != null) {
return ICalDataType.CONTENT_ID;
}
return null;
}
@Override
protected List<String> writeData(AudioAlarm property) {
String uri = property.getUri();
if (uri != null) {
return Collections.singletonList(uri);
}
byte[] data = property.getData();
if (data != null) {
String base64Str = Base64.encodeBase64String(data);
return Collections.singletonList(base64Str);
}
String contentId = property.getContentId();
if (contentId != null) {
return Collections.singletonList(contentId);
}
return Collections.emptyList();
}
@Override
protected AudioAlarm create(ICalDataType dataType, SemiStructuredValueIterator it) {
AudioAlarm aalarm = new AudioAlarm();
String next = it.next();
if (next == null) {
return aalarm;
}
if (dataType == ICalDataType.BINARY) {
byte[] data = Base64.decodeBase64(next);
aalarm.setData(data);
} else if (dataType == ICalDataType.URL) {
aalarm.setUri(next);
} else if (dataType == ICalDataType.CONTENT_ID) {
aalarm.setContentId(next);
} else {
aalarm.setUri(next);
}
return aalarm;
}
@Override
protected void toVAlarm(VAlarm valarm, AudioAlarm property) {
Attachment attach = buildAttachment(property);
if (attach != null) {
valarm.addAttachment(attach);
}
}
private static Attachment buildAttachment(AudioAlarm aalarm) {
String type = aalarm.getType();
String contentType = (type == null) ? null : "audio/" + type.toLowerCase();
Attachment attach = new Attachment(contentType, (String) null);
byte[] data = aalarm.getData();
if (data != null) {
attach.setData(data);
return attach;
}
String contentId = aalarm.getContentId();
if (contentId != null) {
attach.setContentId(contentId);
return attach;
}
String uri = aalarm.getUri();
if (uri != null) {
attach.setUri(uri);
return attach;
}
return null;
}
@Override
protected Action action() {
return Action.audio();
}
}

@ -1,175 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.io.ParseContext;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.parameter.Encoding;
import biweekly.parameter.ICalParameters;
import biweekly.property.BinaryProperty;
import biweekly.util.org.apache.commons.codec.binary.Base64;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link BinaryProperty} properties.
* @author Michael Angstadt
*/
public abstract class BinaryPropertyScribe<T extends BinaryProperty> extends ICalPropertyScribe<T> {
public BinaryPropertyScribe(Class<T> clazz, String propertyName) {
super(clazz, propertyName, ICalDataType.URI);
}
@Override
protected ICalParameters _prepareParameters(T property, WriteContext context) {
ICalParameters copy = new ICalParameters(property.getParameters());
if (property.getUri() != null) {
copy.setEncoding(null);
} else if (property.getData() != null) {
copy.setEncoding(Encoding.BASE64);
}
return copy;
}
@Override
protected ICalDataType _dataType(T property, ICalVersion version) {
if (property.getUri() != null) {
return (version == ICalVersion.V1_0) ? ICalDataType.URL : ICalDataType.URI;
}
if (property.getData() != null) {
return ICalDataType.BINARY;
}
return defaultDataType(version);
}
@Override
protected String _writeText(T property, WriteContext context) {
String uri = property.getUri();
if (uri != null) {
return uri;
}
byte[] data = property.getData();
if (data != null) {
return Base64.encodeBase64String(data);
}
return "";
}
@Override
protected T _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
value = VObjectPropertyValues.unescape(value);
if (dataType == ICalDataType.BINARY || parameters.getEncoding() == Encoding.BASE64) {
byte[] data = Base64.decodeBase64(value);
return newInstance(data);
}
return newInstance(value, dataType);
}
@Override
protected void _writeXml(T property, XCalElement element, WriteContext context) {
String uri = property.getUri();
if (uri != null) {
element.append(ICalDataType.URI, uri);
return;
}
byte[] data = property.getData();
if (data != null) {
element.append(ICalDataType.BINARY, Base64.encodeBase64String(data));
return;
}
element.append(defaultDataType(context.getVersion()), "");
}
@Override
protected T _parseXml(XCalElement element, ICalParameters parameters, ParseContext context) {
String uri = element.first(ICalDataType.URI);
if (uri != null) {
return newInstance(uri, ICalDataType.URI);
}
String base64Data = element.first(ICalDataType.BINARY);
if (base64Data != null) {
byte[] data = Base64.decodeBase64(base64Data);
return newInstance(data);
}
throw missingXmlElements(ICalDataType.URI, ICalDataType.BINARY);
}
@Override
protected JCalValue _writeJson(T property, WriteContext context) {
String uri = property.getUri();
if (uri != null) {
return JCalValue.single(uri);
}
byte[] data = property.getData();
if (data != null) {
return JCalValue.single(Base64.encodeBase64String(data));
}
return JCalValue.single("");
}
@Override
protected T _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
String valueStr = value.asSingle();
if (dataType == ICalDataType.BINARY) {
byte[] data = Base64.decodeBase64(valueStr);
return newInstance(data);
}
return newInstance(valueStr, dataType);
}
/**
* Creates a property object from the given binary data.
* @param data the data
* @return the property object
*/
protected abstract T newInstance(byte[] data);
/**
* Creates a property object from the given string value.
* @param value the string value
* @param dataType the data type
* @return the property object
*/
protected abstract T newInstance(String value, ICalDataType dataType);
}

@ -1,52 +0,0 @@
package biweekly.io.scribe.property;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.property.CalendarScale;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link CalendarScale} properties.
* @author Michael Angstadt
*/
public class CalendarScaleScribe extends TextPropertyScribe<CalendarScale> {
public CalendarScaleScribe() {
super(CalendarScale.class, "CALSCALE");
}
@Override
protected CalendarScale newInstance(String value, ICalVersion version) {
return new CalendarScale(value);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,45 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.parameter.ICalParameters;
import biweekly.property.Categories;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Categories} properties.
* @author Michael Angstadt
*/
public class CategoriesScribe extends TextListPropertyScribe<Categories> {
public CategoriesScribe() {
super(Categories.class, "CATEGORIES");
}
@Override
public Categories newInstance(ICalDataType dataType, ICalParameters parameters) {
return new Categories();
}
}

@ -1,44 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalVersion;
import biweekly.property.Classification;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Classification} properties.
* @author Michael Angstadt
*/
public class ClassificationScribe extends TextPropertyScribe<Classification> {
public ClassificationScribe() {
super(Classification.class, "CLASS");
}
@Override
protected Classification newInstance(String value, ICalVersion version) {
return new Classification(value);
}
}

@ -1,44 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalVersion;
import biweekly.property.Color;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Color} properties.
* @author Michael Angstadt
*/
public class ColorScribe extends TextPropertyScribe<Color> {
public ColorScribe() {
super(Color.class, "COLOR");
}
@Override
protected Color newInstance(String value, ICalVersion version) {
return new Color(value);
}
}

@ -1,52 +0,0 @@
package biweekly.io.scribe.property;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.property.Comment;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Comment} properties.
* @author Michael Angstadt
*/
public class CommentScribe extends TextPropertyScribe<Comment> {
public CommentScribe() {
super(Comment.class, "COMMENT");
}
@Override
protected Comment newInstance(String value, ICalVersion version) {
return new Comment(value);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,46 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Date;
import biweekly.property.Completed;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Completed} properties.
* @author Michael Angstadt
*/
public class CompletedScribe extends DateTimePropertyScribe<Completed> {
public CompletedScribe() {
super(Completed.class, "COMPLETED");
}
@Override
protected Completed newInstance(Date date) {
return new Completed(date);
}
}

@ -1,114 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.io.ParseContext;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.parameter.ICalParameters;
import biweekly.property.Conference;
import biweekly.util.DataUri;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Conference} properties.
* @author Michael Angstadt
*/
public class ConferenceScribe extends ICalPropertyScribe<Conference> {
public ConferenceScribe() {
super(Conference.class, "CONFERENCE", ICalDataType.URI);
}
@Override
protected String _writeText(Conference property, WriteContext context) {
return write(property);
}
@Override
protected Conference _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
value = VObjectPropertyValues.unescape(value);
return parse(value);
}
@Override
protected void _writeXml(Conference property, XCalElement element, WriteContext context) {
element.append(ICalDataType.URI, write(property));
}
@Override
protected Conference _parseXml(XCalElement element, ICalParameters parameters, ParseContext context) {
String uri = element.first(ICalDataType.URI);
if (uri != null) {
return parse(uri);
}
throw missingXmlElements(ICalDataType.URI);
}
@Override
protected JCalValue _writeJson(Conference property, WriteContext context) {
return JCalValue.single(write(property));
}
@Override
protected Conference _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
String uri = value.asSingle();
return parse(uri);
}
private static String write(Conference property) {
String uri = property.getUri();
if (uri != null) {
return uri;
}
String text = property.getText();
if (text != null) {
return new DataUri(text).toString();
}
return "";
}
private static Conference parse(String value) {
try {
DataUri uri = DataUri.parse(value);
String text = uri.getText();
if (text != null) {
Conference property = new Conference((String) null);
property.setText(text);
return property;
}
} catch (IllegalArgumentException e) {
//not a data URI
}
return new Conference(value);
}
}

@ -1,52 +0,0 @@
package biweekly.io.scribe.property;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.property.Contact;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Contact} properties.
* @author Michael Angstadt
*/
public class ContactScribe extends TextPropertyScribe<Contact> {
public ContactScribe() {
super(Contact.class, "CONTACT");
}
@Override
protected Contact newInstance(String value, ICalVersion version) {
return new Contact(value);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
}

@ -1,51 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Date;
import biweekly.ICalVersion;
import biweekly.property.Created;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link Created} properties.
* @author Michael Angstadt
*/
public class CreatedScribe extends DateTimePropertyScribe<Created> {
public CreatedScribe() {
super(Created.class, "CREATED");
}
@Override
public String getPropertyName(ICalVersion version) {
return (version == ICalVersion.V1_0) ? "DCREATED" : super.getPropertyName(version);
}
@Override
protected Created newInstance(Date date) {
return new Created(date);
}
}

@ -1,44 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.property.DateDue;
import biweekly.util.ICalDate;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link DateDue} properties.
* @author Michael Angstadt
*/
public class DateDueScribe extends DateOrDateTimePropertyScribe<DateDue> {
public DateDueScribe() {
super(DateDue.class, "DUE");
}
@Override
protected DateDue newInstance(ICalDate date) {
return new DateDue(date);
}
}

@ -1,44 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.property.DateEnd;
import biweekly.util.ICalDate;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link DateEnd} properties.
* @author Michael Angstadt
*/
public class DateEndScribe extends DateOrDateTimePropertyScribe<DateEnd> {
public DateEndScribe() {
super(DateEnd.class, "DTEND");
}
@Override
protected DateEnd newInstance(ICalDate date) {
return new DateEnd(date);
}
}

@ -1,137 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.ICalVersion;
import biweekly.io.CannotParseException;
import biweekly.io.ParseContext;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.parameter.ICalParameters;
import biweekly.property.DateOrDateTimeProperty;
import biweekly.util.ICalDate;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals properties that have either "date" or "date-time" values.
* @param <T> the property class
* @author Michael Angstadt
*/
public abstract class DateOrDateTimePropertyScribe<T extends DateOrDateTimeProperty> extends ICalPropertyScribe<T> {
public DateOrDateTimePropertyScribe(Class<T> clazz, String propertyName) {
super(clazz, propertyName, ICalDataType.DATE_TIME);
}
@Override
protected ICalParameters _prepareParameters(T property, WriteContext context) {
ICalDate value = property.getValue();
if (value == null) {
return property.getParameters();
}
return handleTzidParameter(property, value.hasTime(), context);
}
@Override
protected ICalDataType _dataType(T property, ICalVersion version) {
ICalDate value = property.getValue();
if (value == null) {
return ICalDataType.DATE_TIME;
}
return value.hasTime() ? ICalDataType.DATE_TIME : ICalDataType.DATE;
}
@Override
protected String _writeText(T property, WriteContext context) {
ICalDate value = property.getValue();
return date(value, property, context).extended(false).write();
}
@Override
protected T _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
value = VObjectPropertyValues.unescape(value);
return parse(value, parameters, context);
}
@Override
protected void _writeXml(T property, XCalElement element, WriteContext context) {
ICalDataType dataType = dataType(property, null);
ICalDate value = property.getValue();
String dateStr = date(value, property, context).extended(true).write();
element.append(dataType, dateStr);
}
@Override
protected T _parseXml(XCalElement element, ICalParameters parameters, ParseContext context) {
String value = element.first(ICalDataType.DATE_TIME);
if (value == null) {
value = element.first(ICalDataType.DATE);
}
if (value != null) {
return parse(value, parameters, context);
}
throw missingXmlElements(ICalDataType.DATE_TIME, ICalDataType.DATE);
}
@Override
protected JCalValue _writeJson(T property, WriteContext context) {
ICalDate value = property.getValue();
String dateStr = date(value, property, context).extended(true).write();
return JCalValue.single(dateStr);
}
@Override
protected T _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
String valueStr = value.asSingle();
return parse(valueStr, parameters, context);
}
protected abstract T newInstance(ICalDate date);
private T parse(String value, ICalParameters parameters, ParseContext context) {
if (value == null) {
return newInstance(null);
}
ICalDate date;
try {
date = date(value).parse();
} catch (IllegalArgumentException e) {
throw new CannotParseException(17);
}
T property = newInstance(date);
context.addDate(date, property, parameters);
return property;
}
}

@ -1,90 +0,0 @@
package biweekly.io.scribe.property;
import biweekly.ICalDataType;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.parameter.ICalParameters;
import biweekly.property.DateStart;
import biweekly.util.ICalDate;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link DateStart} properties.
* @author Michael Angstadt
*/
public class DateStartScribe extends DateOrDateTimePropertyScribe<DateStart> {
public DateStartScribe() {
super(DateStart.class, "DTSTART");
}
@Override
protected ICalParameters _prepareParameters(DateStart property, WriteContext context) {
if (isInObservance(context)) {
return property.getParameters();
}
return super._prepareParameters(property, context);
}
@Override
protected String _writeText(DateStart property, WriteContext context) {
if (isInObservance(context)) {
return write(property, false);
}
return super._writeText(property, context);
}
@Override
protected void _writeXml(DateStart property, XCalElement element, WriteContext context) {
if (isInObservance(context)) {
String dateStr = write(property, true);
ICalDataType dataType = dataType(property, null);
element.append(dataType, dateStr);
return;
}
super._writeXml(property, element, context);
}
@Override
protected JCalValue _writeJson(DateStart property, WriteContext context) {
if (isInObservance(context)) {
return JCalValue.single(write(property, true));
}
return super._writeJson(property, context);
}
private String write(DateStart property, boolean extended) {
ICalDate value = property.getValue();
return date(value).observance(true).extended(extended).write();
}
@Override
protected DateStart newInstance(ICalDate date) {
return new DateStart(date);
}
}

@ -1,112 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Date;
import biweekly.ICalDataType;
import biweekly.io.CannotParseException;
import biweekly.io.ParseContext;
import biweekly.io.WriteContext;
import biweekly.io.json.JCalValue;
import biweekly.io.xml.XCalElement;
import biweekly.parameter.ICalParameters;
import biweekly.property.DateTimeProperty;
import biweekly.util.ICalDate;
import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals properties that have "date-time" values. These values will always be
* formatted in the UTC timezone.
* @param <T> the property class
* @author Michael Angstadt
*/
public abstract class DateTimePropertyScribe<T extends DateTimeProperty> extends ICalPropertyScribe<T> {
public DateTimePropertyScribe(Class<T> clazz, String propertyName) {
super(clazz, propertyName, ICalDataType.DATE_TIME);
}
@Override
protected String _writeText(T property, WriteContext context) {
Date value = property.getValue();
return date(value).utc(true).extended(false).write();
}
@Override
protected T _parseText(String value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
value = VObjectPropertyValues.unescape(value);
return parse(value, parameters, context);
}
@Override
protected void _writeXml(T property, XCalElement element, WriteContext context) {
ICalDataType dataType = dataType(property, null);
Date value = property.getValue();
String dateStr = date(value).utc(true).extended(true).write();
element.append(dataType, dateStr);
}
@Override
protected T _parseXml(XCalElement element, ICalParameters parameters, ParseContext context) {
ICalDataType dataType = defaultDataType(context.getVersion());
String value = element.first(dataType);
if (value != null) {
return parse(value, parameters, context);
}
throw missingXmlElements(dataType);
}
@Override
protected JCalValue _writeJson(T property, WriteContext context) {
Date value = property.getValue();
String dateStr = date(value).utc(true).extended(true).write();
return JCalValue.single(dateStr);
}
@Override
protected T _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, ParseContext context) {
String valueStr = value.asSingle();
return parse(valueStr, parameters, context);
}
private T parse(String value, ICalParameters parameters, ParseContext context) {
ICalDate date;
try {
date = date(value).parse();
} catch (IllegalArgumentException e) {
throw new CannotParseException(17);
}
T property = newInstance(date);
context.addDate(date, property, parameters);
return property;
}
protected abstract T newInstance(Date date);
}

@ -1,63 +0,0 @@
package biweekly.io.scribe.property;
import java.util.Date;
import java.util.EnumSet;
import java.util.Set;
import biweekly.ICalVersion;
import biweekly.io.SkipMeException;
import biweekly.io.WriteContext;
import biweekly.property.DateTimeStamp;
/*
Copyright (c) 2013-2023, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
*/
/**
* Marshals {@link DateTimeStamp} properties.
* @author Michael Angstadt
*/
public class DateTimeStampScribe extends DateTimePropertyScribe<DateTimeStamp> {
public DateTimeStampScribe() {
super(DateTimeStamp.class, "DTSTAMP");
}
@Override
protected DateTimeStamp newInstance(Date date) {
return new DateTimeStamp(date);
}
@Override
public Set<ICalVersion> getSupportedVersions() {
return EnumSet.of(ICalVersion.V2_0_DEPRECATED, ICalVersion.V2_0);
}
@Override
protected String _writeText(DateTimeStamp property, WriteContext context) {
if (context.getVersion() == ICalVersion.V1_0){
throw new SkipMeException("This property is not used in vCal 1.0.");
}
return super._writeText(property, context);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save