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.IOException; 008 import java.io.OutputStream; 009 import java.io.Writer; 010 011 import biweekly.ICalDataType; 012 import biweekly.ICalendar; 013 import biweekly.component.ICalComponent; 014 import biweekly.component.marshaller.ICalComponentMarshaller; 015 import biweekly.io.ICalMarshallerRegistrar; 016 import biweekly.io.SkipMeException; 017 import biweekly.parameter.ICalParameters; 018 import biweekly.property.ICalProperty; 019 import biweekly.property.marshaller.ICalPropertyMarshaller; 020 021 /* 022 Copyright (c) 2013, Michael Angstadt 023 All rights reserved. 024 025 Redistribution and use in source and binary forms, with or without 026 modification, are permitted provided that the following conditions are met: 027 028 1. Redistributions of source code must retain the above copyright notice, this 029 list of conditions and the following disclaimer. 030 2. Redistributions in binary form must reproduce the above copyright notice, 031 this list of conditions and the following disclaimer in the documentation 032 and/or other materials provided with the distribution. 033 034 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 035 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 036 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 037 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 038 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 039 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 040 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 041 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 042 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 043 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 044 */ 045 046 /** 047 * <p> 048 * Writes {@link ICalendar} objects to a JSON data stream (jCal). 049 * </p> 050 * <p> 051 * <b>Example:</b> 052 * 053 * <pre class="brush:java"> 054 * List<ICalendar> icals = ... 055 * OutputStream out = ... 056 * JCalWriter jcalWriter = new JCalWriter(out); 057 * for (ICalendar ical : icals){ 058 * jcalWriter.write(ical); 059 * } 060 * jcalWriter.close(); 061 * </pre> 062 * 063 * </p> 064 * @author Michael Angstadt 065 * @see <a href="http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-05">jCal 066 * draft</a> 067 */ 068 public class JCalWriter implements Closeable { 069 private ICalMarshallerRegistrar registrar = new ICalMarshallerRegistrar(); 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 marshaller. Can also be used to 131 * override the marshaller of a standard property (such as DTSTART). Calling 132 * this method is the same as calling: 133 * </p> 134 * <p> 135 * {@code getRegistrar().register(marshaller)}. 136 * </p> 137 * @param marshaller the marshaller to register 138 */ 139 public void registerMarshaller(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 140 registrar.register(marshaller); 141 } 142 143 /** 144 * <p> 145 * Registers an experimental component marshaller. Can also be used to 146 * override the marshaller of a standard component (such as VEVENT). Calling 147 * this method is the same as calling: 148 * </p> 149 * <p> 150 * {@code getRegistrar().register(marshaller)}. 151 * </p> 152 * @param marshaller the marshaller to register 153 */ 154 public void registerMarshaller(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 155 registrar.register(marshaller); 156 } 157 158 /** 159 * Gets the object that manages the component/property marshaller objects. 160 * @return the marshaller registrar 161 */ 162 public ICalMarshallerRegistrar getRegistrar() { 163 return registrar; 164 } 165 166 /** 167 * Sets the object that manages the component/property marshaller objects. 168 * @param registrar the marshaller registrar 169 */ 170 public void setRegistrar(ICalMarshallerRegistrar registrar) { 171 this.registrar = registrar; 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 marshaller class for a component 195 * or property object cannot be found (only happens when an experimental 196 * property/component marshaller is not registered with the 197 * {@code registerMarshaller} method.) 198 * @throws IOException if there's a problem writing to the data stream 199 */ 200 public void write(ICalendar ical) throws IOException { 201 writeComponent(ical); 202 } 203 204 /** 205 * Writes a component to the data stream. 206 * @param component the component to write 207 * @throws IllegalArgumentException if the marshaller class for a component 208 * or property object cannot be found (only happens when an experimental 209 * property/component marshaller is not registered with the 210 * {@code registerMarshaller} method.) 211 * @throws IOException if there's a problem writing to the data stream 212 */ 213 @SuppressWarnings({ "rawtypes", "unchecked" }) 214 private void writeComponent(ICalComponent component) throws IOException { 215 ICalComponentMarshaller compMarshaller = registrar.getComponentMarshaller(component); 216 if (compMarshaller == null) { 217 throw new IllegalArgumentException("No marshaller found for component class \"" + component.getClass().getName() + "\"."); 218 } 219 220 writer.writeStartComponent(compMarshaller.getComponentName().toLowerCase()); 221 222 //write properties 223 for (Object obj : compMarshaller.getProperties(component)) { 224 ICalProperty property = (ICalProperty) obj; 225 ICalPropertyMarshaller propMarshaller = registrar.getPropertyMarshaller(property); 226 if (propMarshaller == null) { 227 throw new IllegalArgumentException("No marshaller found for property class \"" + property.getClass().getName() + "\"."); 228 } 229 230 //marshal property 231 String propertyName = propMarshaller.getPropertyName().toLowerCase(); 232 ICalParameters parameters; 233 JCalValue value; 234 try { 235 parameters = propMarshaller.prepareParameters(property); 236 value = propMarshaller.writeJson(property); 237 } catch (SkipMeException e) { 238 continue; 239 } 240 241 //get the data type 242 ICalDataType dataType = propMarshaller.dataType(property); 243 244 //write property 245 writer.writeProperty(propertyName, parameters, dataType, value); 246 } 247 248 //write sub-components 249 for (Object obj : compMarshaller.getComponents(component)) { 250 ICalComponent subComponent = (ICalComponent) obj; 251 writeComponent(subComponent); 252 } 253 254 writer.writeEndComponent(); 255 } 256 257 /** 258 * Finishes writing the JSON document and closes the underlying 259 * {@link Writer}. 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 }