001package biweekly.io.json; 002 003import static biweekly.util.IOUtils.utf8Writer; 004 005import java.io.File; 006import java.io.Flushable; 007import java.io.IOException; 008import java.io.OutputStream; 009import java.io.Writer; 010import java.util.Collection; 011import java.util.List; 012 013import biweekly.ICalDataType; 014import biweekly.ICalVersion; 015import biweekly.ICalendar; 016import biweekly.component.ICalComponent; 017import biweekly.component.VTimezone; 018import biweekly.io.SkipMeException; 019import biweekly.io.StreamWriter; 020import biweekly.io.scribe.component.ICalComponentScribe; 021import biweekly.io.scribe.property.ICalPropertyScribe; 022import biweekly.parameter.ICalParameters; 023import biweekly.property.ICalProperty; 024import biweekly.property.Version; 025 026/* 027 Copyright (c) 2013-2015, Michael Angstadt 028 All rights reserved. 029 030 Redistribution and use in source and binary forms, with or without 031 modification, are permitted provided that the following conditions are met: 032 033 1. Redistributions of source code must retain the above copyright notice, this 034 list of conditions and the following disclaimer. 035 2. Redistributions in binary form must reproduce the above copyright notice, 036 this list of conditions and the following disclaimer in the documentation 037 and/or other materials provided with the distribution. 038 039 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 040 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 041 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 042 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 043 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 044 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 045 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 046 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 047 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 048 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 049 */ 050 051/** 052 * <p> 053 * Writes {@link ICalendar} objects to a JSON data stream (jCal). 054 * </p> 055 * <p> 056 * <b>Example:</b> 057 * 058 * <pre class="brush:java"> 059 * ICalendar ical1 = ... 060 * ICalendar ical2 = ... 061 * File file = new File("icals.json"); 062 * JCalWriter writer = null; 063 * try { 064 * writer = new JCalWriter(file); 065 * writer.write(ical1); 066 * writer.write(ical2); 067 * } finally { 068 * if (writer != null) writer.close(); 069 * } 070 * </pre> 071 * 072 * </p> 073 * 074 * <p> 075 * <b>Changing the timezone settings:</b> 076 * 077 * <pre class="brush:java"> 078 * JCalWriter writer = new JCalWriter(...); 079 * 080 * //format all date/time values in a specific timezone instead of UTC 081 * //note: this makes an HTTP call to the "tzurl.org" website 082 * writer.getTimezoneInfo().setDefaultTimeZone(TimeZone.getDefault()); 083 * 084 * //format the value of a particular date/time property in a specific timezone instead of UTC 085 * //note: this makes an HTTP call to the "tzurl.org" website 086 * DateStart dtstart = ... 087 * writer.getTimezoneInfo().setTimeZone(dtstart, TimeZone.getDefault()); 088 * 089 * //generate Outlook-friendly VTIMEZONE components: 090 * writer.getTimezoneInfo().setGenerator(new TzUrlDotOrgGenerator(true)); 091 * </pre> 092 * 093 * </p> 094 * @author Michael Angstadt 095 * @see <a href="http://tools.ietf.org/html/rfc7265">RFC 7265</a> 096 */ 097public class JCalWriter extends StreamWriter implements Flushable { 098 private final JCalRawWriter writer; 099 private final ICalVersion targetVersion = ICalVersion.V2_0; 100 101 /** 102 * @param out the output stream to write to (UTF-8 encoding will be used) 103 */ 104 public JCalWriter(OutputStream out) { 105 this(utf8Writer(out)); 106 } 107 108 /** 109 * @param out the output stream to write to (UTF-8 encoding will be used) 110 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 111 * false not to (useful when writing more than one iCalendar object) 112 */ 113 public JCalWriter(OutputStream out, boolean wrapInArray) { 114 this(utf8Writer(out), wrapInArray); 115 } 116 117 /** 118 * @param file the file to write to (UTF-8 encoding will be used) 119 * @throws IOException if the file cannot be written to 120 */ 121 public JCalWriter(File file) throws IOException { 122 this(utf8Writer(file)); 123 } 124 125 /** 126 * @param file the file to write to (UTF-8 encoding will be used) 127 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 128 * false not to (useful when writing more than one iCalendar object) 129 * @throws IOException if the file cannot be written to 130 */ 131 public JCalWriter(File file, boolean wrapInArray) throws IOException { 132 this(utf8Writer(file), wrapInArray); 133 } 134 135 /** 136 * @param writer the writer to write to 137 */ 138 public JCalWriter(Writer writer) { 139 this(writer, false); 140 } 141 142 /** 143 * @param writer the writer to write to 144 * @param wrapInArray true to wrap all iCalendar objects in a parent array, 145 * false not to (useful when writing more than one iCalendar object) 146 */ 147 public JCalWriter(Writer writer, boolean wrapInArray) { 148 this.writer = new JCalRawWriter(writer, wrapInArray); 149 } 150 151 /** 152 * Gets whether or not the JSON will be pretty-printed. 153 * @return true if it will be pretty-printed, false if not (defaults to 154 * false) 155 */ 156 public boolean isIndent() { 157 return writer.isIndent(); 158 } 159 160 /** 161 * Sets whether or not to pretty-print the JSON. 162 * @param indent true to pretty-print it, false not to (defaults to false) 163 */ 164 public void setIndent(boolean indent) { 165 writer.setIndent(indent); 166 } 167 168 @Override 169 protected void _write(ICalendar ical) throws IOException { 170 writeComponent(ical); 171 } 172 173 @Override 174 protected ICalVersion getTargetVersion() { 175 return targetVersion; 176 } 177 178 /** 179 * Writes a component to the data stream. 180 * @param component the component to write 181 * @throws IllegalArgumentException if the scribe class for a component or 182 * property object cannot be found (only happens when an experimental 183 * property/component scribe is not registered with the 184 * {@code registerScribe} method.) 185 * @throws IOException if there's a problem writing to the data stream 186 */ 187 @SuppressWarnings({ "rawtypes", "unchecked" }) 188 private void writeComponent(ICalComponent component) throws IOException { 189 ICalComponentScribe componentScribe = index.getComponentScribe(component); 190 writer.writeStartComponent(componentScribe.getComponentName().toLowerCase()); 191 192 List propertyObjs = componentScribe.getProperties(component); 193 if (component instanceof ICalendar && component.getProperty(Version.class) == null) { 194 propertyObjs.add(0, new Version(targetVersion)); 195 } 196 197 //write properties 198 for (Object propertyObj : propertyObjs) { 199 context.setParent(component); //set parent here incase a scribe resets the parent 200 ICalProperty property = (ICalProperty) propertyObj; 201 ICalPropertyScribe propertyScribe = index.getPropertyScribe(property); 202 203 //marshal property 204 ICalParameters parameters; 205 JCalValue value; 206 try { 207 parameters = propertyScribe.prepareParameters(property, context); 208 value = propertyScribe.writeJson(property, context); 209 } catch (SkipMeException e) { 210 continue; 211 } 212 213 //write property 214 String propertyName = propertyScribe.getPropertyName().toLowerCase(); 215 ICalDataType dataType = propertyScribe.dataType(property, targetVersion); 216 writer.writeProperty(propertyName, parameters, dataType, value); 217 } 218 219 //write sub-components 220 Collection subComponents = componentScribe.getComponents(component); 221 if (component instanceof ICalendar) { 222 //add the VTIMEZONE components that were auto-generated by TimezoneOptions 223 Collection<VTimezone> tzs = tzinfo.getComponents(); 224 for (VTimezone tz : tzs) { 225 if (!subComponents.contains(tz)) { 226 subComponents.add(tz); 227 } 228 } 229 } 230 for (Object subComponentObj : subComponents) { 231 ICalComponent subComponent = (ICalComponent) subComponentObj; 232 writeComponent(subComponent); 233 } 234 235 writer.writeEndComponent(); 236 } 237 238 /** 239 * Flushes the stream. 240 * @throws IOException if there's a problem flushing the stream 241 */ 242 public void flush() throws IOException { 243 writer.flush(); 244 } 245 246 /** 247 * Finishes writing the JSON document and closes the underlying 248 * {@link Writer} object. 249 * @throws IOException if there's a problem closing the stream 250 */ 251 public void close() throws IOException { 252 writer.close(); 253 } 254 255 /** 256 * Finishes writing the JSON document so that it is syntactically correct. 257 * No more iCalendar objects can be written once this method is called. 258 * @throws IOException if there's a problem writing to the data stream 259 */ 260 public void closeJsonStream() throws IOException { 261 writer.closeJsonStream(); 262 } 263}