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&lt;ICalendar&gt; icals = new ArrayList&lt;ICalendar&gt;();
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}