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}