001package ezvcard.io.scribe;
002
003import java.util.Date;
004
005import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
006
007import ezvcard.VCardDataType;
008import ezvcard.VCardVersion;
009import ezvcard.io.CannotParseException;
010import ezvcard.io.ParseContext;
011import ezvcard.io.html.HCardElement;
012import ezvcard.io.json.JCardValue;
013import ezvcard.io.text.WriteContext;
014import ezvcard.io.xml.XCardElement;
015import ezvcard.parameter.VCardParameters;
016import ezvcard.property.DateOrTimeProperty;
017import ezvcard.util.PartialDate;
018
019/*
020 Copyright (c) 2012-2018, Michael Angstadt
021 All rights reserved.
022
023 Redistribution and use in source and binary forms, with or without
024 modification, are permitted provided that the following conditions are met: 
025
026 1. Redistributions of source code must retain the above copyright notice, this
027 list of conditions and the following disclaimer. 
028 2. Redistributions in binary form must reproduce the above copyright notice,
029 this list of conditions and the following disclaimer in the documentation
030 and/or other materials provided with the distribution. 
031
032 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
033 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
034 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
035 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
036 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
037 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
038 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
039 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
040 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
041 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042 */
043
044/**
045 * Marshals properties with date-time values.
046 * @author Michael Angstadt
047 * @param <T> the property class
048 */
049public abstract class DateOrTimePropertyScribe<T extends DateOrTimeProperty> extends VCardPropertyScribe<T> {
050        public DateOrTimePropertyScribe(Class<T> clazz, String propertyName) {
051                super(clazz, propertyName);
052        }
053
054        @Override
055        protected VCardDataType _defaultDataType(VCardVersion version) {
056                switch (version) {
057                case V2_1:
058                case V3_0:
059                        return null;
060                case V4_0:
061                        return VCardDataType.DATE_AND_OR_TIME;
062                }
063                return null;
064        }
065
066        @Override
067        protected VCardDataType _dataType(T property, VCardVersion version) {
068                switch (version) {
069                case V2_1:
070                case V3_0:
071                        return null;
072                case V4_0:
073                        if (property.getText() != null) {
074                                return VCardDataType.TEXT;
075                        }
076                        if (property.getDate() != null || property.getPartialDate() != null) {
077                                return property.hasTime() ? VCardDataType.DATE_TIME : VCardDataType.DATE;
078                        }
079                        return VCardDataType.DATE_AND_OR_TIME;
080                }
081                return null;
082        }
083
084        @Override
085        protected String _writeText(T property, WriteContext context) {
086                VCardVersion version = context.getVersion();
087                Date date = property.getDate();
088                if (date != null) {
089                        boolean extended = (version == VCardVersion.V3_0);
090                        return date(date).time(property.hasTime()).extended(extended).utc(false).write();
091                }
092
093                if (version == VCardVersion.V4_0) {
094                        String text = property.getText();
095                        if (text != null) {
096                                return VObjectPropertyValues.escape(text);
097                        }
098
099                        PartialDate partialDate = property.getPartialDate();
100                        if (partialDate != null) {
101                                return partialDate.toISO8601(false);
102                        }
103                }
104
105                return "";
106        }
107
108        @Override
109        protected T _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
110                value = VObjectPropertyValues.unescape(value);
111                if (context.getVersion() == VCardVersion.V4_0 && dataType == VCardDataType.TEXT) {
112                        return newInstance(value);
113                }
114
115                return parse(value, context);
116        }
117
118        @Override
119        protected void _writeXml(T property, XCardElement parent) {
120                Date date = property.getDate();
121                if (date != null) {
122                        boolean hasTime = property.hasTime();
123                        String value = date(date).time(hasTime).extended(false).utc(false).write();
124
125                        VCardDataType dataType = hasTime ? VCardDataType.DATE_TIME : VCardDataType.DATE;
126
127                        parent.append(dataType, value);
128                        return;
129                }
130
131                PartialDate partialDate = property.getPartialDate();
132                if (partialDate != null) {
133                        VCardDataType dataType;
134                        if (partialDate.hasTimeComponent() && partialDate.hasDateComponent()) {
135                                dataType = VCardDataType.DATE_TIME;
136                        } else if (partialDate.hasTimeComponent()) {
137                                dataType = VCardDataType.TIME;
138                        } else if (partialDate.hasDateComponent()) {
139                                dataType = VCardDataType.DATE;
140                        } else {
141                                dataType = VCardDataType.DATE_AND_OR_TIME;
142                        }
143
144                        parent.append(dataType, partialDate.toISO8601(false));
145                        return;
146                }
147
148                String text = property.getText();
149                if (text != null) {
150                        parent.append(VCardDataType.TEXT, text);
151                        return;
152                }
153
154                parent.append(VCardDataType.DATE_AND_OR_TIME, "");
155        }
156
157        @Override
158        protected T _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) {
159                String value = element.first(VCardDataType.DATE, VCardDataType.DATE_TIME, VCardDataType.DATE_AND_OR_TIME);
160                if (value != null) {
161                        return parse(value, context);
162                }
163
164                value = element.first(VCardDataType.TEXT);
165                if (value != null) {
166                        return newInstance(value);
167                }
168
169                throw missingXmlElements(VCardDataType.DATE, VCardDataType.DATE_TIME, VCardDataType.DATE_AND_OR_TIME, VCardDataType.TEXT);
170        }
171
172        @Override
173        protected T _parseHtml(HCardElement element, ParseContext context) {
174                String value = null;
175                if ("time".equals(element.tagName())) {
176                        String datetime = element.attr("datetime");
177                        if (datetime.length() > 0) {
178                                value = datetime;
179                        }
180                }
181                if (value == null) {
182                        value = element.value();
183                }
184                return parse(value, context);
185        }
186
187        @Override
188        protected JCardValue _writeJson(T property) {
189                Date date = property.getDate();
190                if (date != null) {
191                        boolean hasTime = property.hasTime();
192                        String value = date(date).time(hasTime).extended(true).utc(false).write();
193                        return JCardValue.single(value);
194                }
195
196                PartialDate partialDate = property.getPartialDate();
197                if (partialDate != null) {
198                        String value = partialDate.toISO8601(true);
199                        return JCardValue.single(value);
200                }
201
202                String text = property.getText();
203                if (text != null) {
204                        return JCardValue.single(text);
205                }
206
207                return JCardValue.single("");
208        }
209
210        @Override
211        protected T _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
212                String valueStr = value.asSingle();
213                if (dataType == VCardDataType.TEXT) {
214                        return newInstance(valueStr);
215                }
216
217                return parse(valueStr, context);
218        }
219
220        private T parse(String value, ParseContext context) {
221                try {
222                        boolean hasTime = value.contains("T");
223                        return newInstance(date(value), hasTime);
224                } catch (IllegalArgumentException e) {
225                        if (context.getVersion() == VCardVersion.V2_1 || context.getVersion() == VCardVersion.V3_0) {
226                                throw new CannotParseException(5);
227                        }
228
229                        try {
230                                return newInstance(PartialDate.parse(value));
231                        } catch (IllegalArgumentException e2) {
232                                context.addWarning(6);
233                                return newInstance(value);
234                        }
235                }
236        }
237
238        protected abstract T newInstance(String text);
239
240        protected abstract T newInstance(Date date, boolean hasTime);
241
242        protected abstract T newInstance(PartialDate partialDate);
243}