001package biweekly.io.json; 002 003import static biweekly.util.IOUtils.utf8Writer; 004 005import java.io.Closeable; 006import java.io.File; 007import java.io.Flushable; 008import java.io.IOException; 009import java.io.OutputStream; 010import java.io.Writer; 011 012import biweekly.ICalDataType; 013import biweekly.ICalendar; 014import biweekly.component.ICalComponent; 015import biweekly.io.SkipMeException; 016import biweekly.io.scribe.ScribeIndex; 017import biweekly.io.scribe.component.ICalComponentScribe; 018import biweekly.io.scribe.property.ICalPropertyScribe; 019import biweekly.parameter.ICalParameters; 020import biweekly.property.ICalProperty; 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/rfc7265">RFC 7265</a> 067 */ 068public class JCalWriter implements Closeable, Flushable { 069 private ScribeIndex index = new ScribeIndex(); 070 private final JCalRawWriter writer; 071 072 /** 073 * Creates a jCal writer that writes to an output stream. 074 * @param outputStream the output stream to write to 075 */ 076 public JCalWriter(OutputStream outputStream) { 077 this(utf8Writer(outputStream)); 078 } 079 080 /** 081 * Creates a jCal writer that writes to an output stream. 082 * @param outputStream the output stream to write to 083 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 084 * false not to (useful when writing more than one iCalendar object) 085 */ 086 public JCalWriter(OutputStream outputStream, boolean wrapInArray) { 087 this(utf8Writer(outputStream), wrapInArray); 088 } 089 090 /** 091 * Creates a jCal writer that writes to a file. 092 * @param file the file to write to 093 * @throws IOException if the file cannot be written to 094 */ 095 public JCalWriter(File file) throws IOException { 096 this(utf8Writer(file)); 097 } 098 099 /** 100 * Creates a jCal writer that writes to a file. 101 * @param file the file to write to 102 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 103 * false not to (useful when writing more than one iCalendar object) 104 * @throws IOException if the file cannot be written to 105 */ 106 public JCalWriter(File file, boolean wrapInArray) throws IOException { 107 this(utf8Writer(file), wrapInArray); 108 } 109 110 /** 111 * Creates a jCal writer that writes to a writer. 112 * @param writer the writer to the data stream 113 */ 114 public JCalWriter(Writer writer) { 115 this(writer, false); 116 } 117 118 /** 119 * Creates a jCal writer that writes to a writer. 120 * @param writer the writer to the data stream 121 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 122 * false not to (useful when writing more than one iCalendar object) 123 */ 124 public JCalWriter(Writer writer, boolean wrapInArray) { 125 this.writer = new JCalRawWriter(writer, wrapInArray); 126 } 127 128 /** 129 * <p> 130 * Registers an experimental property scribe. Can also be used to override 131 * the scribe of a standard property (such as DTSTART). Calling this method 132 * is the same as calling: 133 * </p> 134 * <p> 135 * {@code getScribeIndex().register(scribe)}. 136 * </p> 137 * @param scribe the scribe to register 138 */ 139 public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) { 140 index.register(scribe); 141 } 142 143 /** 144 * <p> 145 * Registers an experimental component scribe. Can also be used to override 146 * the scribe of a standard component (such as VEVENT). Calling this method 147 * is the same as calling: 148 * </p> 149 * <p> 150 * {@code getScribeIndex().register(scribe)}. 151 * </p> 152 * @param scribe the scribe to register 153 */ 154 public void registerScribe(ICalComponentScribe<? extends ICalComponent> scribe) { 155 index.register(scribe); 156 } 157 158 /** 159 * Gets the object that manages the component/property scribes. 160 * @return the scribe index 161 */ 162 public ScribeIndex getScribeIndex() { 163 return index; 164 } 165 166 /** 167 * Sets the object that manages the component/property scribes. 168 * @param index the scribe index 169 */ 170 public void setScribeIndex(ScribeIndex index) { 171 this.index = index; 172 } 173 174 /** 175 * Gets whether or not the JSON will be pretty-printed. 176 * @return true if it will be pretty-printed, false if not (defaults to 177 * false) 178 */ 179 public boolean isIndent() { 180 return writer.isIndent(); 181 } 182 183 /** 184 * Sets whether or not to pretty-print the JSON. 185 * @param indent true to pretty-print it, false not to (defaults to false) 186 */ 187 public void setIndent(boolean indent) { 188 writer.setIndent(indent); 189 } 190 191 /** 192 * Writes an iCalendar object to the data stream. 193 * @param ical the iCalendar object to write 194 * @throws IllegalArgumentException if the scribe class for a component or 195 * property object cannot be found (only happens when an experimental 196 * property/component scribe is not registered with the 197 * {@code registerScribe} method.) 198 * @throws IOException if there's a problem writing to the data stream 199 */ 200 public void write(ICalendar ical) throws IOException { 201 index.hasScribesFor(ical); 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 scribe class for a component or 209 * property object cannot be found (only happens when an experimental 210 * property/component scribe is not registered with the 211 * {@code registerScribe} 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 ICalComponentScribe componentScribe = index.getComponentScribe(component); 217 writer.writeStartComponent(componentScribe.getComponentName().toLowerCase()); 218 219 //write properties 220 for (Object propertyObj : componentScribe.getProperties(component)) { 221 ICalProperty property = (ICalProperty) propertyObj; 222 ICalPropertyScribe propertyScribe = index.getPropertyScribe(property); 223 224 //marshal property 225 ICalParameters parameters; 226 JCalValue value; 227 try { 228 parameters = propertyScribe.prepareParameters(property); 229 value = propertyScribe.writeJson(property); 230 } catch (SkipMeException e) { 231 continue; 232 } 233 234 //write property 235 String propertyName = propertyScribe.getPropertyName().toLowerCase(); 236 ICalDataType dataType = propertyScribe.dataType(property); 237 writer.writeProperty(propertyName, parameters, dataType, value); 238 } 239 240 //write sub-components 241 for (Object subComponentObj : componentScribe.getComponents(component)) { 242 ICalComponent subComponent = (ICalComponent) subComponentObj; 243 writeComponent(subComponent); 244 } 245 246 writer.writeEndComponent(); 247 } 248 249 /** 250 * Flushes the stream. 251 * @throws IOException if there's a problem flushing the stream 252 */ 253 public void flush() throws IOException { 254 writer.flush(); 255 } 256 257 /** 258 * Finishes writing the JSON document and closes the underlying 259 * {@link Writer} object. 260 * @throws IOException if there's a problem closing the stream 261 */ 262 public void close() throws IOException { 263 writer.close(); 264 } 265 266 /** 267 * Finishes writing the JSON document so that it is syntactically correct. 268 * No more iCalendar objects can be written once this method is called. 269 * @throws IOException if there's a problem writing to the data stream 270 */ 271 public void closeJsonStream() throws IOException { 272 writer.closeJsonStream(); 273 } 274}