001package biweekly.io.scribe; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Set; 009 010import javax.xml.namespace.QName; 011 012import biweekly.ICalendar; 013import biweekly.component.ICalComponent; 014import biweekly.component.RawComponent; 015import biweekly.io.scribe.component.DaylightSavingsTimeScribe; 016import biweekly.io.scribe.component.ICalComponentScribe; 017import biweekly.io.scribe.component.ICalendarScribe; 018import biweekly.io.scribe.component.RawComponentScribe; 019import biweekly.io.scribe.component.StandardTimeScribe; 020import biweekly.io.scribe.component.VAlarmScribe; 021import biweekly.io.scribe.component.VEventScribe; 022import biweekly.io.scribe.component.VFreeBusyScribe; 023import biweekly.io.scribe.component.VJournalScribe; 024import biweekly.io.scribe.component.VTimezoneScribe; 025import biweekly.io.scribe.component.VTodoScribe; 026import biweekly.io.scribe.property.ActionScribe; 027import biweekly.io.scribe.property.AttachmentScribe; 028import biweekly.io.scribe.property.AttendeeScribe; 029import biweekly.io.scribe.property.CalendarScaleScribe; 030import biweekly.io.scribe.property.CategoriesScribe; 031import biweekly.io.scribe.property.ClassificationScribe; 032import biweekly.io.scribe.property.CommentScribe; 033import biweekly.io.scribe.property.CompletedScribe; 034import biweekly.io.scribe.property.ContactScribe; 035import biweekly.io.scribe.property.CreatedScribe; 036import biweekly.io.scribe.property.DateDueScribe; 037import biweekly.io.scribe.property.DateEndScribe; 038import biweekly.io.scribe.property.DateStartScribe; 039import biweekly.io.scribe.property.DateTimeStampScribe; 040import biweekly.io.scribe.property.DescriptionScribe; 041import biweekly.io.scribe.property.DurationPropertyScribe; 042import biweekly.io.scribe.property.ExceptionDatesScribe; 043import biweekly.io.scribe.property.ExceptionRuleScribe; 044import biweekly.io.scribe.property.FreeBusyScribe; 045import biweekly.io.scribe.property.GeoScribe; 046import biweekly.io.scribe.property.ICalPropertyScribe; 047import biweekly.io.scribe.property.LastModifiedScribe; 048import biweekly.io.scribe.property.LocationScribe; 049import biweekly.io.scribe.property.MethodScribe; 050import biweekly.io.scribe.property.OrganizerScribe; 051import biweekly.io.scribe.property.PercentCompleteScribe; 052import biweekly.io.scribe.property.PriorityScribe; 053import biweekly.io.scribe.property.ProductIdScribe; 054import biweekly.io.scribe.property.RawPropertyScribe; 055import biweekly.io.scribe.property.RecurrenceDatesScribe; 056import biweekly.io.scribe.property.RecurrenceIdScribe; 057import biweekly.io.scribe.property.RecurrenceRuleScribe; 058import biweekly.io.scribe.property.RelatedToScribe; 059import biweekly.io.scribe.property.RepeatScribe; 060import biweekly.io.scribe.property.RequestStatusScribe; 061import biweekly.io.scribe.property.ResourcesScribe; 062import biweekly.io.scribe.property.SequenceScribe; 063import biweekly.io.scribe.property.StatusScribe; 064import biweekly.io.scribe.property.SummaryScribe; 065import biweekly.io.scribe.property.TimezoneIdScribe; 066import biweekly.io.scribe.property.TimezoneNameScribe; 067import biweekly.io.scribe.property.TimezoneOffsetFromScribe; 068import biweekly.io.scribe.property.TimezoneOffsetToScribe; 069import biweekly.io.scribe.property.TimezoneUrlScribe; 070import biweekly.io.scribe.property.TransparencyScribe; 071import biweekly.io.scribe.property.TriggerScribe; 072import biweekly.io.scribe.property.UidScribe; 073import biweekly.io.scribe.property.UrlScribe; 074import biweekly.io.scribe.property.VersionScribe; 075import biweekly.io.scribe.property.XmlScribe; 076import biweekly.io.xml.XCalNamespaceContext; 077import biweekly.property.ICalProperty; 078import biweekly.property.RawProperty; 079import biweekly.property.Xml; 080 081/* 082 Copyright (c) 2013, Michael Angstadt 083 All rights reserved. 084 085 Redistribution and use in source and binary forms, with or without 086 modification, are permitted provided that the following conditions are met: 087 088 1. Redistributions of source code must retain the above copyright notice, this 089 list of conditions and the following disclaimer. 090 2. Redistributions in binary form must reproduce the above copyright notice, 091 this list of conditions and the following disclaimer in the documentation 092 and/or other materials provided with the distribution. 093 094 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 095 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 096 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 097 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 098 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 099 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 100 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 101 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 102 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 103 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 104 */ 105 106/** 107 * <p> 108 * Manages a listing of component and property scribes. This is useful for 109 * injecting the scribes of any experimental components or properties you have 110 * defined into a reader or writer object. The same object instance can be 111 * reused and injected into multiple reader/writer classes. 112 * </p> 113 * <p> 114 * <b>Example:</b> 115 * 116 * <pre class="brush:java"> 117 * //init the index 118 * ScribeIndex index = new ScribeIndex(); 119 * index.register(new CustomPropertyScribe()); 120 * index.register(new AnotherCustomPropertyScribe()); 121 * index.register(new CustomComponentScribe()); 122 * 123 * //inject into a reader class 124 * ICalReader textReader = new ICalReader(...); 125 * textReader.setRegistrar(index); 126 * List<ICalendar> icals = new ArrayList<ICalendar>(); 127 * ICalendar ical; 128 * while ((ical = textReader.readNext()) != null){ 129 * icals.add(ical); 130 * } 131 * 132 * //inject the same instance in another reader/writer class 133 * JCalWriter writer = new JCalWriter(...); 134 * writer.setRegistrar(index); 135 * for (ICalendar ical : icals){ 136 * writer.write(ical); 137 * } 138 * </pre> 139 * 140 * </p> 141 * @author Michael Angstadt 142 */ 143public class ScribeIndex { 144 //define standard component scribes 145 private static final Map<String, ICalComponentScribe<? extends ICalComponent>> standardCompByName = new HashMap<String, ICalComponentScribe<? extends ICalComponent>>(); 146 private static final Map<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>> standardCompByClass = new HashMap<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>>(); 147 static { 148 registerStandard(new ICalendarScribe()); 149 registerStandard(new VAlarmScribe()); 150 registerStandard(new VEventScribe()); 151 registerStandard(new VFreeBusyScribe()); 152 registerStandard(new VJournalScribe()); 153 registerStandard(new VTodoScribe()); 154 registerStandard(new VTimezoneScribe()); 155 registerStandard(new StandardTimeScribe()); 156 registerStandard(new DaylightSavingsTimeScribe()); 157 } 158 159 //define standard property scribes 160 private static final Map<String, ICalPropertyScribe<? extends ICalProperty>> standardPropByName = new HashMap<String, ICalPropertyScribe<? extends ICalProperty>>(); 161 private static final Map<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>> standardPropByClass = new HashMap<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>>(); 162 private static final Map<QName, ICalPropertyScribe<? extends ICalProperty>> standardPropByQName = new HashMap<QName, ICalPropertyScribe<? extends ICalProperty>>(); 163 static { 164 //RFC 5545 165 registerStandard(new ActionScribe()); 166 registerStandard(new AttachmentScribe()); 167 registerStandard(new AttendeeScribe()); 168 registerStandard(new CalendarScaleScribe()); 169 registerStandard(new CategoriesScribe()); 170 registerStandard(new ClassificationScribe()); 171 registerStandard(new CommentScribe()); 172 registerStandard(new CompletedScribe()); 173 registerStandard(new ContactScribe()); 174 registerStandard(new CreatedScribe()); 175 registerStandard(new DateDueScribe()); 176 registerStandard(new DateEndScribe()); 177 registerStandard(new DateStartScribe()); 178 registerStandard(new DateTimeStampScribe()); 179 registerStandard(new DescriptionScribe()); 180 registerStandard(new DurationPropertyScribe()); 181 registerStandard(new ExceptionDatesScribe()); 182 registerStandard(new FreeBusyScribe()); 183 registerStandard(new GeoScribe()); 184 registerStandard(new LastModifiedScribe()); 185 registerStandard(new LocationScribe()); 186 registerStandard(new MethodScribe()); 187 registerStandard(new OrganizerScribe()); 188 registerStandard(new PercentCompleteScribe()); 189 registerStandard(new PriorityScribe()); 190 registerStandard(new ProductIdScribe()); 191 registerStandard(new RecurrenceDatesScribe()); 192 registerStandard(new RecurrenceIdScribe()); 193 registerStandard(new RecurrenceRuleScribe()); 194 registerStandard(new RelatedToScribe()); 195 registerStandard(new RepeatScribe()); 196 registerStandard(new RequestStatusScribe()); 197 registerStandard(new ResourcesScribe()); 198 registerStandard(new SequenceScribe()); 199 registerStandard(new StatusScribe()); 200 registerStandard(new SummaryScribe()); 201 registerStandard(new TimezoneIdScribe()); 202 registerStandard(new TimezoneNameScribe()); 203 registerStandard(new TimezoneOffsetFromScribe()); 204 registerStandard(new TimezoneOffsetToScribe()); 205 registerStandard(new TimezoneUrlScribe()); 206 registerStandard(new TransparencyScribe()); 207 registerStandard(new TriggerScribe()); 208 registerStandard(new UidScribe()); 209 registerStandard(new UrlScribe()); 210 registerStandard(new VersionScribe()); 211 212 //RFC 6321 213 registerStandard(new XmlScribe()); 214 215 //RFC 2445 216 registerStandard(new ExceptionRuleScribe()); 217 } 218 219 private final Map<String, ICalComponentScribe<? extends ICalComponent>> experimentalCompByName = new HashMap<String, ICalComponentScribe<? extends ICalComponent>>(0); 220 private final Map<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>> experimentalCompByClass = new HashMap<Class<? extends ICalComponent>, ICalComponentScribe<? extends ICalComponent>>(0); 221 222 private final Map<String, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByName = new HashMap<String, ICalPropertyScribe<? extends ICalProperty>>(0); 223 private final Map<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByClass = new HashMap<Class<? extends ICalProperty>, ICalPropertyScribe<? extends ICalProperty>>(0); 224 private final Map<QName, ICalPropertyScribe<? extends ICalProperty>> experimentalPropByQName = new HashMap<QName, ICalPropertyScribe<? extends ICalProperty>>(0); 225 226 /** 227 * Gets a component scribe by name. 228 * @param componentName the component name (e.g. "VEVENT") 229 * @return the component scribe or a {@link RawComponentScribe} if not found 230 */ 231 public ICalComponentScribe<? extends ICalComponent> getComponentScribe(String componentName) { 232 componentName = componentName.toUpperCase(); 233 234 ICalComponentScribe<? extends ICalComponent> marshaller = experimentalCompByName.get(componentName); 235 if (marshaller != null) { 236 return marshaller; 237 } 238 239 marshaller = standardCompByName.get(componentName); 240 if (marshaller != null) { 241 return marshaller; 242 } 243 244 return new RawComponentScribe(componentName); 245 } 246 247 /** 248 * Gets a property scribe by name. 249 * @param propertyName the property name (e.g. "VERSION") 250 * @return the property scribe or a {@link RawPropertyScribe} if not found 251 */ 252 public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(String propertyName) { 253 propertyName = propertyName.toUpperCase(); 254 255 ICalPropertyScribe<? extends ICalProperty> marshaller = experimentalPropByName.get(propertyName); 256 if (marshaller != null) { 257 return marshaller; 258 } 259 260 marshaller = standardPropByName.get(propertyName); 261 if (marshaller != null) { 262 return marshaller; 263 } 264 265 return new RawPropertyScribe(propertyName); 266 } 267 268 /** 269 * Gets a component scribe by class. 270 * @param clazz the component class 271 * @return the component scribe or null if not found 272 */ 273 public ICalComponentScribe<? extends ICalComponent> getComponentScribe(Class<? extends ICalComponent> clazz) { 274 ICalComponentScribe<? extends ICalComponent> marshaller = experimentalCompByClass.get(clazz); 275 if (marshaller != null) { 276 return marshaller; 277 } 278 279 return standardCompByClass.get(clazz); 280 } 281 282 /** 283 * Gets a property scribe by class. 284 * @param clazz the property class 285 * @return the property scribe or null if not found 286 */ 287 public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(Class<? extends ICalProperty> clazz) { 288 ICalPropertyScribe<? extends ICalProperty> marshaller = experimentalPropByClass.get(clazz); 289 if (marshaller != null) { 290 return marshaller; 291 } 292 293 return standardPropByClass.get(clazz); 294 } 295 296 /** 297 * Gets the appropriate component scribe for a given component instance. 298 * @param component the component instance 299 * @return the component scribe or null if not found 300 */ 301 public ICalComponentScribe<? extends ICalComponent> getComponentScribe(ICalComponent component) { 302 if (component instanceof RawComponent) { 303 RawComponent raw = (RawComponent) component; 304 return new RawComponentScribe(raw.getName()); 305 } 306 307 return getComponentScribe(component.getClass()); 308 } 309 310 /** 311 * Gets the appropriate property scribe for a given property instance. 312 * @param property the property instance 313 * @return the property scribe or null if not found 314 */ 315 public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(ICalProperty property) { 316 if (property instanceof RawProperty) { 317 RawProperty raw = (RawProperty) property; 318 return new RawPropertyScribe(raw.getName()); 319 } 320 321 return getPropertyScribe(property.getClass()); 322 } 323 324 /** 325 * Gets a property scribe by XML local name and namespace. 326 * @param qname the XML local name and namespace 327 * @return the property scribe or a {@link XmlScribe} if not found 328 */ 329 public ICalPropertyScribe<? extends ICalProperty> getPropertyScribe(QName qname) { 330 ICalPropertyScribe<? extends ICalProperty> marshaller = experimentalPropByQName.get(qname); 331 if (marshaller != null) { 332 return marshaller; 333 } 334 335 marshaller = standardPropByQName.get(qname); 336 if (marshaller != null) { 337 return marshaller; 338 } 339 340 if (XCalNamespaceContext.XCAL_NS.equals(qname.getNamespaceURI())) { 341 return new RawPropertyScribe(qname.getLocalPart().toUpperCase()); 342 } 343 344 return getPropertyScribe(Xml.class); 345 } 346 347 /** 348 * Registers a component scribe. 349 * @param scribe the scribe to register 350 */ 351 public void register(ICalComponentScribe<? extends ICalComponent> scribe) { 352 experimentalCompByName.put(scribe.getComponentName().toUpperCase(), scribe); 353 experimentalCompByClass.put(scribe.getComponentClass(), scribe); 354 } 355 356 /** 357 * Registers a property scribe. 358 * @param scribe the scribe to register 359 */ 360 public void register(ICalPropertyScribe<? extends ICalProperty> scribe) { 361 experimentalPropByName.put(scribe.getPropertyName().toUpperCase(), scribe); 362 experimentalPropByClass.put(scribe.getPropertyClass(), scribe); 363 experimentalPropByQName.put(scribe.getQName(), scribe); 364 } 365 366 /** 367 * Unregisters a component scribe. 368 * @param scribe the scribe to unregister 369 */ 370 public void unregister(ICalComponentScribe<? extends ICalComponent> scribe) { 371 experimentalCompByName.remove(scribe.getComponentName().toUpperCase()); 372 experimentalCompByClass.remove(scribe.getComponentClass()); 373 } 374 375 /** 376 * Unregisters a property scribe 377 * @param scribe the scribe to unregister 378 */ 379 public void unregister(ICalPropertyScribe<? extends ICalProperty> scribe) { 380 experimentalPropByName.remove(scribe.getPropertyName().toUpperCase()); 381 experimentalPropByClass.remove(scribe.getPropertyClass()); 382 experimentalPropByQName.remove(scribe.getQName()); 383 } 384 385 /** 386 * Checks to see if this scribe index has scribes registered for all of the 387 * components/properties in an iCalendar object. 388 * @param ical the iCalendar object 389 * @throws IllegalArgumentException if the scribe index is missing scribes 390 * for one or more properties/components. 391 */ 392 public void hasScribesFor(ICalendar ical) { 393 Set<Class<? extends Object>> unregistered = new HashSet<Class<? extends Object>>(); 394 List<ICalComponent> components = new ArrayList<ICalComponent>(); 395 components.add(ical); 396 397 while (!components.isEmpty()) { 398 ICalComponent component = components.remove(components.size() - 1); 399 400 Class<? extends ICalComponent> componentClass = component.getClass(); 401 if (componentClass != RawComponent.class && getComponentScribe(componentClass) == null) { 402 unregistered.add(componentClass); 403 } 404 405 for (Map.Entry<Class<? extends ICalProperty>, List<ICalProperty>> entry : component.getProperties()) { 406 List<ICalProperty> properties = entry.getValue(); 407 if (properties.isEmpty()) { 408 continue; 409 } 410 411 Class<? extends ICalProperty> clazz = entry.getKey(); 412 if (clazz != RawProperty.class && getPropertyScribe(clazz) == null) { 413 unregistered.add(clazz); 414 } 415 } 416 417 components.addAll(component.getComponents().values()); 418 } 419 420 if (!unregistered.isEmpty()) { 421 //all code that calls this method needs to throw an exception, so an exception is thrown here instead of returning some value 422 throw new IllegalArgumentException("No scribes were found the following component/property classes: " + unregistered); 423 } 424 } 425 426 /** 427 * Convenience method for getting the scribe of the root iCalendar component 428 * ("VCALENDAR"). 429 * @return the scribe 430 */ 431 public static ICalendarScribe getICalendarScribe() { 432 return (ICalendarScribe) standardCompByClass.get(ICalendar.class); 433 } 434 435 private static void registerStandard(ICalComponentScribe<? extends ICalComponent> marshaller) { 436 standardCompByName.put(marshaller.getComponentName().toUpperCase(), marshaller); 437 standardCompByClass.put(marshaller.getComponentClass(), marshaller); 438 } 439 440 private static void registerStandard(ICalPropertyScribe<? extends ICalProperty> marshaller) { 441 standardPropByName.put(marshaller.getPropertyName().toUpperCase(), marshaller); 442 standardPropByClass.put(marshaller.getPropertyClass(), marshaller); 443 standardPropByQName.put(marshaller.getQName(), marshaller); 444 } 445}