001 package biweekly.io.json;
002
003 import static biweekly.util.IOUtils.utf8Writer;
004
005 import java.io.Closeable;
006 import java.io.File;
007 import java.io.Flushable;
008 import java.io.IOException;
009 import java.io.OutputStream;
010 import java.io.Writer;
011
012 import biweekly.ICalDataType;
013 import biweekly.ICalendar;
014 import biweekly.component.ICalComponent;
015 import biweekly.component.marshaller.ICalComponentMarshaller;
016 import biweekly.io.ICalMarshallerRegistrar;
017 import biweekly.io.SkipMeException;
018 import biweekly.parameter.ICalParameters;
019 import biweekly.property.ICalProperty;
020 import biweekly.property.marshaller.ICalPropertyMarshaller;
021
022 /*
023 Copyright (c) 2013, Michael Angstadt
024 All rights reserved.
025
026 Redistribution and use in source and binary forms, with or without
027 modification, are permitted provided that the following conditions are met:
028
029 1. Redistributions of source code must retain the above copyright notice, this
030 list of conditions and the following disclaimer.
031 2. Redistributions in binary form must reproduce the above copyright notice,
032 this list of conditions and the following disclaimer in the documentation
033 and/or other materials provided with the distribution.
034
035 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
036 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
037 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
039 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
040 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
041 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
043 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
044 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045 */
046
047 /**
048 * <p>
049 * Writes {@link ICalendar} objects to a JSON data stream (jCal).
050 * </p>
051 * <p>
052 * <b>Example:</b>
053 *
054 * <pre class="brush:java">
055 * List<ICalendar> icals = ...
056 * OutputStream out = ...
057 * JCalWriter jcalWriter = new JCalWriter(out);
058 * for (ICalendar ical : icals){
059 * jcalWriter.write(ical);
060 * }
061 * jcalWriter.close();
062 * </pre>
063 *
064 * </p>
065 * @author Michael Angstadt
066 * @see <a href="http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-05">jCal
067 * draft</a>
068 */
069 public class JCalWriter implements Closeable, Flushable {
070 private ICalMarshallerRegistrar registrar = new ICalMarshallerRegistrar();
071 private final JCalRawWriter writer;
072
073 /**
074 * Creates a jCal writer that writes to an output stream.
075 * @param outputStream the output stream to write to
076 */
077 public JCalWriter(OutputStream outputStream) {
078 this(utf8Writer(outputStream));
079 }
080
081 /**
082 * Creates a jCal writer that writes to an output stream.
083 * @param outputStream the output stream to write to
084 * @param wrapInArray true to wrap all iCalendar objects in a parent array,
085 * false not to (useful when writing more than one iCalendar object)
086 */
087 public JCalWriter(OutputStream outputStream, boolean wrapInArray) {
088 this(utf8Writer(outputStream), wrapInArray);
089 }
090
091 /**
092 * Creates a jCal writer that writes to a file.
093 * @param file the file to write to
094 * @throws IOException if the file cannot be written to
095 */
096 public JCalWriter(File file) throws IOException {
097 this(utf8Writer(file));
098 }
099
100 /**
101 * Creates a jCal writer that writes to a file.
102 * @param file the file to write to
103 * @param wrapInArray true to wrap all iCalendar objects in a parent array,
104 * false not to (useful when writing more than one iCalendar object)
105 * @throws IOException if the file cannot be written to
106 */
107 public JCalWriter(File file, boolean wrapInArray) throws IOException {
108 this(utf8Writer(file), wrapInArray);
109 }
110
111 /**
112 * Creates a jCal writer that writes to a writer.
113 * @param writer the writer to the data stream
114 */
115 public JCalWriter(Writer writer) {
116 this(writer, false);
117 }
118
119 /**
120 * Creates a jCal writer that writes to a writer.
121 * @param writer the writer to the data stream
122 * @param wrapInArray true to wrap all iCalendar objects in a parent array,
123 * false not to (useful when writing more than one iCalendar object)
124 */
125 public JCalWriter(Writer writer, boolean wrapInArray) {
126 this.writer = new JCalRawWriter(writer, wrapInArray);
127 }
128
129 /**
130 * <p>
131 * Registers an experimental property marshaller. Can also be used to
132 * override the marshaller of a standard property (such as DTSTART). Calling
133 * this method is the same as calling:
134 * </p>
135 * <p>
136 * {@code getRegistrar().register(marshaller)}.
137 * </p>
138 * @param marshaller the marshaller to register
139 */
140 public void registerMarshaller(ICalPropertyMarshaller<? extends ICalProperty> marshaller) {
141 registrar.register(marshaller);
142 }
143
144 /**
145 * <p>
146 * Registers an experimental component marshaller. Can also be used to
147 * override the marshaller of a standard component (such as VEVENT). Calling
148 * this method is the same as calling:
149 * </p>
150 * <p>
151 * {@code getRegistrar().register(marshaller)}.
152 * </p>
153 * @param marshaller the marshaller to register
154 */
155 public void registerMarshaller(ICalComponentMarshaller<? extends ICalComponent> marshaller) {
156 registrar.register(marshaller);
157 }
158
159 /**
160 * Gets the object that manages the component/property marshaller objects.
161 * @return the marshaller registrar
162 */
163 public ICalMarshallerRegistrar getRegistrar() {
164 return registrar;
165 }
166
167 /**
168 * Sets the object that manages the component/property marshaller objects.
169 * @param registrar the marshaller registrar
170 */
171 public void setRegistrar(ICalMarshallerRegistrar registrar) {
172 this.registrar = registrar;
173 }
174
175 /**
176 * Gets whether or not the JSON will be pretty-printed.
177 * @return true if it will be pretty-printed, false if not (defaults to
178 * false)
179 */
180 public boolean isIndent() {
181 return writer.isIndent();
182 }
183
184 /**
185 * Sets whether or not to pretty-print the JSON.
186 * @param indent true to pretty-print it, false not to (defaults to false)
187 */
188 public void setIndent(boolean indent) {
189 writer.setIndent(indent);
190 }
191
192 /**
193 * Writes an iCalendar object to the data stream.
194 * @param ical the iCalendar object to write
195 * @throws IllegalArgumentException if the marshaller class for a component
196 * or property object cannot be found (only happens when an experimental
197 * property/component marshaller is not registered with the
198 * {@code registerMarshaller} method.)
199 * @throws IOException if there's a problem writing to the data stream
200 */
201 public void write(ICalendar ical) throws IOException {
202 writeComponent(ical);
203 }
204
205 /**
206 * Writes a component to the data stream.
207 * @param component the component to write
208 * @throws IllegalArgumentException if the marshaller class for a component
209 * or property object cannot be found (only happens when an experimental
210 * property/component marshaller is not registered with the
211 * {@code registerMarshaller} method.)
212 * @throws IOException if there's a problem writing to the data stream
213 */
214 @SuppressWarnings({ "rawtypes", "unchecked" })
215 private void writeComponent(ICalComponent component) throws IOException {
216 ICalComponentMarshaller compMarshaller = registrar.getComponentMarshaller(component);
217 if (compMarshaller == null) {
218 throw new IllegalArgumentException("No marshaller found for component class \"" + component.getClass().getName() + "\".");
219 }
220
221 writer.writeStartComponent(compMarshaller.getComponentName().toLowerCase());
222
223 //write properties
224 for (Object obj : compMarshaller.getProperties(component)) {
225 ICalProperty property = (ICalProperty) obj;
226 ICalPropertyMarshaller propMarshaller = registrar.getPropertyMarshaller(property);
227 if (propMarshaller == null) {
228 throw new IllegalArgumentException("No marshaller found for property class \"" + property.getClass().getName() + "\".");
229 }
230
231 //marshal property
232 String propertyName = propMarshaller.getPropertyName().toLowerCase();
233 ICalParameters parameters;
234 JCalValue value;
235 try {
236 parameters = propMarshaller.prepareParameters(property);
237 value = propMarshaller.writeJson(property);
238 } catch (SkipMeException e) {
239 continue;
240 }
241
242 //get the data type
243 ICalDataType dataType = propMarshaller.dataType(property);
244
245 //write property
246 writer.writeProperty(propertyName, parameters, dataType, value);
247 }
248
249 //write sub-components
250 for (Object obj : compMarshaller.getComponents(component)) {
251 ICalComponent subComponent = (ICalComponent) obj;
252 writeComponent(subComponent);
253 }
254
255 writer.writeEndComponent();
256 }
257
258 /**
259 * Flushes the stream.
260 * @throws IOException if there's a problem flushing the stream
261 */
262 public void flush() throws IOException {
263 writer.flush();
264 }
265
266 /**
267 * Finishes writing the JSON document and closes the underlying
268 * {@link Writer} object.
269 * @throws IOException if there's a problem closing the stream
270 */
271 public void close() throws IOException {
272 writer.close();
273 }
274
275 /**
276 * Finishes writing the JSON document so that it is syntactically correct.
277 * No more iCalendar objects can be written once this method is called.
278 * @throws IOException if there's a problem writing to the data stream
279 */
280 public void closeJsonStream() throws IOException {
281 writer.closeJsonStream();
282 }
283 }