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