001    package biweekly.property.marshaller;
002    
003    import java.util.ArrayList;
004    import java.util.Date;
005    import java.util.List;
006    
007    import biweekly.io.xml.XCalElement;
008    import biweekly.parameter.ICalParameters;
009    import biweekly.parameter.Value;
010    import biweekly.property.RecurrenceDates;
011    import biweekly.util.Duration;
012    import biweekly.util.Period;
013    import biweekly.util.StringUtils;
014    import biweekly.util.StringUtils.JoinCallback;
015    
016    /*
017     Copyright (c) 2013, Michael Angstadt
018     All rights reserved.
019    
020     Redistribution and use in source and binary forms, with or without
021     modification, are permitted provided that the following conditions are met: 
022    
023     1. Redistributions of source code must retain the above copyright notice, this
024     list of conditions and the following disclaimer. 
025     2. Redistributions in binary form must reproduce the above copyright notice,
026     this list of conditions and the following disclaimer in the documentation
027     and/or other materials provided with the distribution. 
028    
029     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
030     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
031     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
032     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
033     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
034     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
035     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
036     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
037     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
038     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
039     */
040    
041    /**
042     * Marshals {@link RecurrenceDates} properties.
043     * @author Michael Angstadt
044     */
045    public class RecurrenceDatesMarshaller extends ICalPropertyMarshaller<RecurrenceDates> {
046            public RecurrenceDatesMarshaller() {
047                    super(RecurrenceDates.class, "RDATE");
048            }
049    
050            @Override
051            protected void _prepareParameters(RecurrenceDates property, ICalParameters copy) {
052                    Value value = null;
053                    if (property.getDates() != null) {
054                            if (!property.hasTime()) {
055                                    value = Value.DATE;
056                            }
057                    } else if (property.getPeriods() != null) {
058                            value = Value.PERIOD;
059                    }
060                    copy.setValue(value);
061            }
062    
063            @Override
064            protected String _writeText(final RecurrenceDates property) {
065                    if (property.getDates() != null) {
066                            return StringUtils.join(property.getDates(), ",", new JoinCallback<Date>() {
067                                    public void handle(StringBuilder sb, Date date) {
068                                            String value = date(date).time(property.hasTime()).write();
069                                            sb.append(value);
070                                    }
071                            });
072                    } else if (property.getPeriods() != null) {
073                            return StringUtils.join(property.getPeriods(), ",", new JoinCallback<Period>() {
074                                    public void handle(StringBuilder sb, Period period) {
075                                            if (period.getStartDate() != null) {
076                                                    String value = date(period.getStartDate()).write();
077                                                    sb.append(value);
078                                            }
079    
080                                            sb.append('/');
081    
082                                            if (period.getEndDate() != null) {
083                                                    String value = date(period.getEndDate()).write();
084                                                    sb.append(value);
085                                            } else if (period.getDuration() != null) {
086                                                    sb.append(period.getDuration());
087                                            }
088                                    }
089                            });
090                    }
091                    return "";
092            }
093    
094            @Override
095            protected RecurrenceDates _parseText(String value, ICalParameters parameters, List<String> warnings) {
096                    String split[] = parseList(value);
097    
098                    Value valueParam = parameters.getValue();
099                    if (valueParam == Value.PERIOD) {
100                            //parse as periods
101                            List<Period> periods = new ArrayList<Period>(split.length);
102                            for (String timePeriodStr : split) {
103                                    String timePeriodStrSplit[] = timePeriodStr.split("/");
104    
105                                    if (timePeriodStrSplit.length < 2) {
106                                            warnings.add("No end date or duration found, skipping time period: " + timePeriodStr);
107                                            continue;
108                                    }
109    
110                                    String startStr = timePeriodStrSplit[0];
111                                    Date start;
112                                    try {
113                                            start = date(startStr).tzid(parameters.getTimezoneId(), warnings).parse();
114                                    } catch (IllegalArgumentException e) {
115                                            warnings.add("Could not parse start date, skipping time period: " + timePeriodStr);
116                                            continue;
117                                    }
118    
119                                    String endStr = timePeriodStrSplit[1];
120                                    try {
121                                            Date end = date(endStr).tzid(parameters.getTimezoneId(), warnings).parse();
122                                            periods.add(new Period(start, end));
123                                    } catch (IllegalArgumentException e) {
124                                            //must be a duration
125                                            try {
126                                                    Duration duration = Duration.parse(endStr);
127                                                    periods.add(new Period(start, duration));
128                                            } catch (IllegalArgumentException e2) {
129                                                    warnings.add("Could not parse end date or duration value, skipping time period: " + timePeriodStr);
130                                                    continue;
131                                            }
132                                    }
133                            }
134                            return new RecurrenceDates(periods);
135                    } else {
136                            //parse as dates
137                            boolean hasTime = (valueParam == null || valueParam == Value.DATE_TIME);
138                            List<Date> dates = new ArrayList<Date>(split.length);
139                            for (String s : split) {
140                                    try {
141                                            Date date = date(s).tzid(parameters.getTimezoneId(), warnings).parse();
142                                            dates.add(date);
143                                    } catch (IllegalArgumentException e) {
144                                            warnings.add("Skipping unparsable date: " + s);
145                                    }
146                            }
147                            return new RecurrenceDates(dates, hasTime);
148                    }
149            }
150    
151            @Override
152            protected void _writeXml(RecurrenceDates property, XCalElement element) {
153                    if (property.getDates() != null) {
154                            Value dataType = property.hasTime() ? Value.DATE_TIME : Value.DATE;
155                            for (Date date : property.getDates()) {
156                                    String dateStr = date(date).time(property.hasTime()).extended(true).write();
157                                    element.append(dataType, dateStr);
158                            }
159                    } else if (property.getPeriods() != null) {
160                            for (Period period : property.getPeriods()) {
161                                    XCalElement periodElement = element.append(Value.PERIOD);
162    
163                                    Date start = period.getStartDate();
164                                    if (start != null) {
165                                            periodElement.append("start", date(start).extended(true).write());
166                                    }
167    
168                                    Date end = period.getEndDate();
169                                    if (end != null) {
170                                            periodElement.append("end", date(end).extended(true).write());
171                                    }
172    
173                                    Duration duration = period.getDuration();
174                                    if (duration != null) {
175                                            periodElement.append("duration", duration.toString());
176                                    }
177                            }
178                    }
179            }
180    
181            @Override
182            protected RecurrenceDates _parseXml(XCalElement element, ICalParameters parameters, List<String> warnings) {
183                    List<XCalElement> periodElements = element.children(Value.PERIOD);
184                    if (!periodElements.isEmpty()) {
185                            //parse as periods
186                            List<Period> periods = new ArrayList<Period>(periodElements.size());
187                            for (XCalElement periodElement : periodElements) {
188                                    Date start = null;
189                                    String startStr = periodElement.first("start");
190                                    if (startStr != null) {
191                                            try {
192                                                    start = date(startStr).tzid(parameters.getTimezoneId(), warnings).parse();
193                                            } catch (IllegalArgumentException e) {
194                                                    warnings.add("Could not parse start date, skipping time period: " + startStr);
195                                                    continue;
196                                            }
197                                    }
198    
199                                    String endStr = periodElement.first("end");
200                                    if (endStr != null) {
201                                            try {
202                                                    Date end = date(endStr).tzid(parameters.getTimezoneId(), warnings).parse();
203                                                    periods.add(new Period(start, end));
204                                            } catch (IllegalArgumentException e) {
205                                                    warnings.add("Could not parse end date, skipping time period: " + endStr);
206                                            }
207                                            continue;
208                                    }
209    
210                                    String durationStr = periodElement.first("duration");
211                                    if (durationStr != null) {
212                                            try {
213                                                    Duration duration = Duration.parse(durationStr);
214                                                    periods.add(new Period(start, duration));
215                                            } catch (IllegalArgumentException e) {
216                                                    warnings.add("Could not parse duration, skipping time period: " + durationStr);
217                                            }
218                                            continue;
219                                    }
220                            }
221                            return new RecurrenceDates(periods);
222                    } else {
223                            //parse as dates
224    
225                            //be lenient and accept a combination of <date> and <date-time> values
226                            List<String> dateStrs = element.all(Value.DATE_TIME);
227                            boolean hasTime = !dateStrs.isEmpty();
228                            dateStrs.addAll(element.all(Value.DATE));
229    
230                            List<Date> dates = new ArrayList<Date>(dateStrs.size());
231                            for (String dateStr : dateStrs) {
232                                    try {
233                                            Date date = date(dateStr).tzid(parameters.getTimezoneId(), warnings).parse();
234                                            dates.add(date);
235                                    } catch (IllegalArgumentException e) {
236                                            warnings.add("Skipping unparsable date: " + dateStr);
237                                    }
238                            }
239                            return new RecurrenceDates(dates, hasTime);
240                    }
241            }
242    }