001package biweekly;
002
003import java.util.ArrayList;
004import java.util.Iterator;
005import java.util.List;
006
007import biweekly.ValidationWarnings.WarningsGroup;
008import biweekly.component.ICalComponent;
009import biweekly.property.ICalProperty;
010import biweekly.util.StringUtils;
011import biweekly.util.StringUtils.JoinCallback;
012
013/*
014 Copyright (c) 2013-2015, Michael Angstadt
015 All rights reserved.
016
017 Redistribution and use in source and binary forms, with or without
018 modification, are permitted provided that the following conditions are met: 
019
020 1. Redistributions of source code must retain the above copyright notice, this
021 list of conditions and the following disclaimer. 
022 2. Redistributions in binary form must reproduce the above copyright notice,
023 this list of conditions and the following disclaimer in the documentation
024 and/or other materials provided with the distribution. 
025
026 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
027 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
028 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
029 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
030 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
031 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
032 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
033 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
034 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036 */
037
038/**
039 * <p>
040 * Holds the validation warnings of an iCalendar object.
041 * </p>
042 * <p>
043 * <b>Examples:</b>
044 * 
045 * <pre class="brush:java">
046 * //validate an iCalendar object
047 * ValidationWarnings warnings = ical.validate();
048 * 
049 * //print all warnings to a string:
050 * System.out.println(warnings.toString());
051 * //sample output:
052 * //[ICalendar]: ProductId is not set (it is a required property).
053 * //[ICalendar &gt; VEvent &gt; DateStart]: DateStart must come before DateEnd.
054 * //[ICalendar &gt; VEvent &gt; VAlarm]: The trigger must specify which date field its duration is relative to.
055 * 
056 * //iterate over each warnings group
057 * //this gives you access to the property/component object and its parent components
058 * for (WarningsGroup group : warnings) {
059 *      ICalProperty prop = group.getProperty();
060 *      if (prop == null) {
061 *              //then it was a component that caused the warnings
062 *              ICalComponent comp = group.getComponent();
063 *      }
064 * 
065 *      //get parent components
066 *      List&lt;ICalComponent&gt; hierarchy = group.getComponentHierarchy();
067 * 
068 *      //get warning messages
069 *      List&lt;String&gt; messages = group.getMessages();
070 * }
071 * 
072 * //you can also get the warnings of specific properties/components
073 * List&lt;WarningsGroup&gt; dtstartWarnings = warnings.getByProperty(DateStart.class);
074 * List&lt;WarningsGroup&gt; veventWarnings = warnings.getByComponent(VEvent.class);
075 * </pre>
076 * 
077 * </p>
078 * @author Michael Angstadt
079 * @see ICalendar#validate(ICalVersion)
080 */
081public class ValidationWarnings implements Iterable<WarningsGroup> {
082        private final List<WarningsGroup> warnings;
083
084        /**
085         * Creates a new validation warnings list.
086         * @param warnings the validation warnings
087         */
088        public ValidationWarnings(List<WarningsGroup> warnings) {
089                this.warnings = warnings;
090        }
091
092        /**
093         * Gets all validation warnings of a given property.
094         * @param propertyClass the property (e.g. {@code DateStart.class})
095         * @return the validation warnings
096         */
097        public List<WarningsGroup> getByProperty(Class<? extends ICalProperty> propertyClass) {
098                List<WarningsGroup> warnings = new ArrayList<WarningsGroup>();
099                for (WarningsGroup group : this.warnings) {
100                        ICalProperty property = group.getProperty();
101                        if (property == null) {
102                                continue;
103                        }
104
105                        if (propertyClass == property.getClass()) {
106                                warnings.add(group);
107                        }
108                }
109                return warnings;
110        }
111
112        /**
113         * Gets all validation warnings of a given component.
114         * @param componentClass the component (e.g. {@code VEvent.class})
115         * @return the validation warnings
116         */
117        public List<WarningsGroup> getByComponent(Class<? extends ICalComponent> componentClass) {
118                List<WarningsGroup> warnings = new ArrayList<WarningsGroup>();
119                for (WarningsGroup group : this.warnings) {
120                        ICalComponent component = group.getComponent();
121                        if (component == null) {
122                                continue;
123                        }
124
125                        if (componentClass == component.getClass()) {
126                                warnings.add(group);
127                        }
128                }
129                return warnings;
130        }
131
132        /**
133         * Gets all the validation warnings.
134         * @return the validation warnings
135         */
136        public List<WarningsGroup> getWarnings() {
137                return warnings;
138        }
139
140        /**
141         * Determines whether there are any validation warnings.
142         * @return true if there are none, false if there are one or more
143         */
144        public boolean isEmpty() {
145                return warnings.isEmpty();
146        }
147
148        /**
149         * <p>
150         * Outputs all validation warnings as a newline-delimited string. For
151         * example:
152         * </p>
153         * 
154         * <pre>
155         * [ICalendar]: ProductId is not set (it is a required property).
156         * [ICalendar > VEvent > DateStart]: DateStart must come before DateEnd.
157         * [ICalendar > VEvent > VAlarm]: The trigger must specify which date field its duration is relative to.
158         * </pre>
159         */
160        @Override
161        public String toString() {
162                return StringUtils.join(warnings, StringUtils.NEWLINE);
163        }
164
165        /**
166         * Iterates over each warning group (same as calling
167         * {@code getWarnings().iterator()}).
168         * @return the iterator
169         */
170        public Iterator<WarningsGroup> iterator() {
171                return warnings.iterator();
172        }
173
174        /**
175         * Holds the validation warnings of a property or component.
176         * @author Michael Angstadt
177         */
178        public static class WarningsGroup {
179                private final ICalProperty property;
180                private final ICalComponent component;
181                private final List<ICalComponent> componentHierarchy;
182                private final List<Warning> warnings;
183
184                /**
185                 * Creates a new set of validation warnings for a property.
186                 * @param property the property that caused the warnings
187                 * @param componentHierarchy the hierarchy of components that the
188                 * property belongs to
189                 * @param warning the warnings
190                 */
191                public WarningsGroup(ICalProperty property, List<ICalComponent> componentHierarchy, List<Warning> warning) {
192                        this(null, property, componentHierarchy, warning);
193                }
194
195                /**
196                 * Creates a new set of validation warnings for a component.
197                 * @param component the component that caused the warnings
198                 * @param componentHierarchy the hierarchy of components that the
199                 * component belongs to
200                 * @param warning the warnings
201                 */
202                public WarningsGroup(ICalComponent component, List<ICalComponent> componentHierarchy, List<Warning> warning) {
203                        this(component, null, componentHierarchy, warning);
204                }
205
206                private WarningsGroup(ICalComponent component, ICalProperty property, List<ICalComponent> componentHierarchy, List<Warning> warning) {
207                        this.component = component;
208                        this.property = property;
209                        this.componentHierarchy = componentHierarchy;
210                        this.warnings = warning;
211                }
212
213                /**
214                 * Gets the property object that caused the validation warnings.
215                 * @return the property object or null if a component caused the
216                 * warnings.
217                 */
218                public ICalProperty getProperty() {
219                        return property;
220                }
221
222                /**
223                 * Gets the component object that caused the validation warnings.
224                 * @return the component object or null if a property caused the
225                 * warnings.
226                 */
227                public ICalComponent getComponent() {
228                        return component;
229                }
230
231                /**
232                 * Gets the hierarchy of components that the property or component
233                 * belongs to.
234                 * @return the component hierarchy
235                 */
236                public List<ICalComponent> getComponentHierarchy() {
237                        return componentHierarchy;
238                }
239
240                /**
241                 * Gets the warnings.
242                 * @return the warnings
243                 */
244                public List<Warning> getWarnings() {
245                        return warnings;
246                }
247
248                /**
249                 * <p>
250                 * Outputs each message in this warnings group as a newline-delimited
251                 * string. Each line includes the component hierarchy and the name of
252                 * the property/component. For example:
253                 * </p>
254                 * 
255                 * <pre>
256                 * [ICalendar > VEvent > VAlarm]: Email alarms must have at least one attendee.
257                 * [ICalendar > VEvent > VAlarm]: The trigger must specify which date field its duration is relative to.
258                 * </pre>
259                 */
260                @Override
261                public String toString() {
262                        final String prefix = "[" + buildPath() + "]: ";
263                        return StringUtils.join(warnings, StringUtils.NEWLINE, new JoinCallback<Warning>() {
264                                public void handle(StringBuilder sb, Warning warning) {
265                                        sb.append(prefix).append(warning);
266                                }
267                        });
268                }
269
270                private String buildPath() {
271                        StringBuilder sb = new StringBuilder();
272
273                        if (!componentHierarchy.isEmpty()) {
274                                String delimitor = " > ";
275
276                                StringUtils.join(componentHierarchy, delimitor, sb, new JoinCallback<ICalComponent>() {
277                                        public void handle(StringBuilder sb, ICalComponent component) {
278                                                sb.append(component.getClass().getSimpleName());
279                                        }
280                                });
281                                sb.append(delimitor);
282                        }
283
284                        Object obj = (property == null) ? component : property;
285                        sb.append(obj.getClass().getSimpleName());
286
287                        return sb.toString();
288                }
289        }
290}