001package biweekly.io; 002 003import java.io.Closeable; 004import java.io.IOException; 005import java.util.Collection; 006import java.util.HashSet; 007import java.util.LinkedList; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011 012import biweekly.ICalVersion; 013import biweekly.ICalendar; 014import biweekly.component.ICalComponent; 015import biweekly.component.RawComponent; 016import biweekly.io.scribe.ScribeIndex; 017import biweekly.io.scribe.component.ICalComponentScribe; 018import biweekly.io.scribe.property.ICalPropertyScribe; 019import biweekly.property.ICalProperty; 020import biweekly.property.RawProperty; 021 022/* 023 Copyright (c) 2013-2015, 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 * Writes iCalendar objects to a data stream. 049 * @author Michael Angstadt 050 */ 051public abstract class StreamWriter implements Closeable { 052 protected ScribeIndex index = new ScribeIndex(); 053 protected WriteContext context; 054 protected TimezoneInfo tzinfo = new TimezoneInfo(); 055 056 /** 057 * Writes an iCalendar object to the data stream. 058 * @param ical the iCalendar object to write 059 * @throws IllegalArgumentException if the scribe class for a component or 060 * property object cannot be found (only happens when an experimental 061 * property/component scribe is not registered with the 062 * {@code registerScribe} method.) 063 * @throws IOException if there's a problem writing to the data stream 064 */ 065 public void write(ICalendar ical) throws IOException { 066 Collection<Class<? extends Object>> unregistered = findScribeless(ical); 067 if (!unregistered.isEmpty()) { 068 throw new IllegalArgumentException("No scribes were found for the following component/property classes: " + unregistered); 069 } 070 071 context = new WriteContext(getTargetVersion(), tzinfo); 072 _write(ical); 073 } 074 075 /** 076 * Gets the version that the next iCalendar object will be written as. 077 * @return the version 078 */ 079 protected abstract ICalVersion getTargetVersion(); 080 081 /** 082 * Writes an iCalendar object to the data stream. 083 * @param ical the iCalendar object to write 084 * @throws IOException if there's a problem writing to the data stream 085 */ 086 protected abstract void _write(ICalendar ical) throws IOException; 087 088 /** 089 * Gets the timezone-related info for this writer. 090 * @return the timezone-related info 091 */ 092 public TimezoneInfo getTimezoneInfo() { 093 return tzinfo; 094 } 095 096 /** 097 * Sets the timezone-related info for this writer. 098 * @param tzinfo the timezone-related info 099 */ 100 public void setTimezoneInfo(TimezoneInfo tzinfo) { 101 this.tzinfo = tzinfo; 102 } 103 104 /** 105 * <p> 106 * Registers an experimental property scribe. Can also be used to override 107 * the scribe of a standard property (such as DTSTART). Calling this method 108 * is the same as calling: 109 * </p> 110 * <p> 111 * {@code getScribeIndex().register(scribe)}. 112 * </p> 113 * @param scribe the scribe to register 114 */ 115 public void registerScribe(ICalPropertyScribe<? extends ICalProperty> scribe) { 116 index.register(scribe); 117 } 118 119 /** 120 * <p> 121 * Registers an experimental component scribe. Can also be used to override 122 * the scribe of a standard component (such as VEVENT). Calling this method 123 * is the same as calling: 124 * </p> 125 * <p> 126 * {@code getScribeIndex().register(scribe)}. 127 * </p> 128 * @param scribe the scribe to register 129 */ 130 public void registerScribe(ICalComponentScribe<? extends ICalComponent> scribe) { 131 index.register(scribe); 132 } 133 134 /** 135 * Gets the object that manages the component/property scribes. 136 * @return the scribe index 137 */ 138 public ScribeIndex getScribeIndex() { 139 return index; 140 } 141 142 /** 143 * Sets the object that manages the component/property scribes. 144 * @param scribe the scribe index 145 */ 146 public void setScribeIndex(ScribeIndex scribe) { 147 this.index = scribe; 148 } 149 150 /** 151 * Gets the component/property classes that don't have scribes associated 152 * with them. 153 * @param ical the iCalendar object 154 * @return the component/property classes 155 */ 156 private Collection<Class<?>> findScribeless(ICalendar ical) { 157 Set<Class<?>> unregistered = new HashSet<Class<?>>(); 158 LinkedList<ICalComponent> components = new LinkedList<ICalComponent>(); 159 components.add(ical); 160 161 while (!components.isEmpty()) { 162 ICalComponent component = components.removeLast(); 163 164 Class<? extends ICalComponent> componentClass = component.getClass(); 165 if (componentClass != RawComponent.class && index.getComponentScribe(componentClass) == null) { 166 unregistered.add(componentClass); 167 } 168 169 for (Map.Entry<Class<? extends ICalProperty>, List<ICalProperty>> entry : component.getProperties()) { 170 List<ICalProperty> properties = entry.getValue(); 171 if (properties.isEmpty()) { 172 continue; 173 } 174 175 Class<? extends ICalProperty> clazz = entry.getKey(); 176 if (clazz != RawProperty.class && index.getPropertyScribe(clazz) == null) { 177 unregistered.add(clazz); 178 } 179 } 180 181 components.addAll(component.getComponents().values()); 182 } 183 184 return unregistered; 185 } 186}