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-2026, 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 protected 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 boolean extended = (version == VCardVersion.V3_0); 092 093 Temporal date = property.getDate(); 094 if (date != null) { 095 return date(date).extended(extended).write(); 096 } 097 098 /* 099 * Allow partial dates to be written to non-4.0 vCards for leniency and 100 * round-tripping 101 */ 102 PartialDate partialDate = property.getPartialDate(); 103 if (partialDate != null) { 104 return partialDate.toISO8601(extended); 105 } 106 107 if (version == VCardVersion.V4_0) { 108 String text = property.getText(); 109 if (text != null) { 110 return VObjectPropertyValues.escape(text); 111 } 112 } 113 114 return ""; 115 } 116 117 @Override 118 protected T _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 119 value = VObjectPropertyValues.unescape(value); 120 if (context.getVersion() == VCardVersion.V4_0 && dataType == VCardDataType.TEXT) { 121 return newInstance(value); 122 } 123 124 return parse(value, context); 125 } 126 127 @Override 128 protected void _writeXml(T property, XCardElement parent) { 129 Temporal date = property.getDate(); 130 if (date != null) { 131 String value = date(date).extended(false).write(); 132 VCardDataType dataType = VCardDateFormat.hasTime(date) ? VCardDataType.DATE_TIME : VCardDataType.DATE; 133 parent.append(dataType, value); 134 return; 135 } 136 137 PartialDate partialDate = property.getPartialDate(); 138 if (partialDate != null) { 139 VCardDataType dataType; 140 if (partialDate.hasTimeComponent() && partialDate.hasDateComponent()) { 141 dataType = VCardDataType.DATE_TIME; 142 } else if (partialDate.hasTimeComponent()) { 143 dataType = VCardDataType.TIME; 144 } else if (partialDate.hasDateComponent()) { 145 dataType = VCardDataType.DATE; 146 } else { 147 dataType = VCardDataType.DATE_AND_OR_TIME; 148 } 149 150 parent.append(dataType, partialDate.toISO8601(false)); 151 return; 152 } 153 154 String text = property.getText(); 155 if (text != null) { 156 parent.append(VCardDataType.TEXT, text); 157 return; 158 } 159 160 parent.append(VCardDataType.DATE_AND_OR_TIME, ""); 161 } 162 163 @Override 164 protected T _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) { 165 String value = element.first(VCardDataType.DATE, VCardDataType.DATE_TIME, VCardDataType.DATE_AND_OR_TIME); 166 if (value != null) { 167 return parse(value, context); 168 } 169 170 value = element.first(VCardDataType.TEXT); 171 if (value != null) { 172 return newInstance(value); 173 } 174 175 throw missingXmlElements(VCardDataType.DATE, VCardDataType.DATE_TIME, VCardDataType.DATE_AND_OR_TIME, VCardDataType.TEXT); 176 } 177 178 @Override 179 protected T _parseHtml(HCardElement element, ParseContext context) { 180 String value = null; 181 if ("time".equals(element.tagName())) { 182 String datetime = element.attr("datetime"); 183 if (!datetime.isEmpty()) { 184 value = datetime; 185 } 186 } 187 if (value == null) { 188 value = element.value(); 189 } 190 return parse(value, context); 191 } 192 193 @Override 194 protected JCardValue _writeJson(T property) { 195 Temporal date = property.getDate(); 196 if (date != null) { 197 String value = date(date).extended(true).write(); 198 return JCardValue.single(value); 199 } 200 201 PartialDate partialDate = property.getPartialDate(); 202 if (partialDate != null) { 203 String value = partialDate.toISO8601(true); 204 return JCardValue.single(value); 205 } 206 207 String text = property.getText(); 208 if (text != null) { 209 return JCardValue.single(text); 210 } 211 212 return JCardValue.single(""); 213 } 214 215 @Override 216 protected T _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 217 String valueStr = value.asSingle(); 218 if (dataType == VCardDataType.TEXT) { 219 return newInstance(valueStr); 220 } 221 222 return parse(valueStr, context); 223 } 224 225 private T parse(String value, ParseContext context) { 226 try { 227 return newInstance(date(value)); 228 } catch (IllegalArgumentException e) { 229 try { 230 //allow partial dates to be parsed from non-4.0 vCards 231 //https://github.com/mangstadt/ez-vcard/issues/155 232 return newInstance(PartialDate.parse(value)); 233 } catch (IllegalArgumentException e2) { 234 if (context.getVersion() == VCardVersion.V2_1 || context.getVersion() == VCardVersion.V3_0) { 235 throw new CannotParseException(5); 236 } 237 context.addWarning(6); 238 return newInstance(value); 239 } 240 } 241 } 242 243 protected abstract T newInstance(String text); 244 245 protected abstract T newInstance(Temporal date); 246 247 protected abstract T newInstance(PartialDate partialDate); 248}