001    package biweekly;
002    
003    import java.util.ArrayList;
004    import java.util.Iterator;
005    import java.util.List;
006    
007    import biweekly.ValidationWarnings.WarningsGroup;
008    import biweekly.component.ICalComponent;
009    import biweekly.property.ICalProperty;
010    import biweekly.util.StringUtils;
011    import biweekly.util.StringUtils.JoinCallback;
012    
013    /*
014     Copyright (c) 2013, 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()
080     */
081    public 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<String> messages;
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 messages the warning messages
190                     */
191                    public WarningsGroup(ICalProperty property, List<ICalComponent> componentHierarchy, List<String> messages) {
192                            this(null, property, componentHierarchy, messages);
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 messages the warning messages
201                     */
202                    public WarningsGroup(ICalComponent component, List<ICalComponent> componentHierarchy, List<String> messages) {
203                            this(component, null, componentHierarchy, messages);
204                    }
205    
206                    private WarningsGroup(ICalComponent component, ICalProperty property, List<ICalComponent> componentHierarchy, List<String> messages) {
207                            this.component = component;
208                            this.property = property;
209                            this.componentHierarchy = componentHierarchy;
210                            this.messages = messages;
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 warning messages.
242                     * @return the warning messages
243                     */
244                    public List<String> getMessages() {
245                            return messages;
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(messages, StringUtils.NEWLINE, new JoinCallback<String>() {
264                                    public void handle(StringBuilder sb, String message) {
265                                            sb.append(prefix).append(message);
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                            if (property != null) {
285                                    sb.append(property.getClass().getSimpleName());
286                            } else {
287                                    sb.append(component.getClass().getSimpleName());
288                            }
289    
290                            return sb.toString();
291                    }
292            }
293    }