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