package jfxtras.icalendarfx.properties; import java.net.URI; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Period; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import jfxtras.icalendarfx.VElement; import jfxtras.icalendarfx.properties.component.recurrence.rrule.RecurrenceRuleValue; import jfxtras.icalendarfx.utilities.DateTimeUtilities; import jfxtras.icalendarfx.utilities.DefaultStringConverter; import jfxtras.icalendarfx.utilities.DoubleStringConverter; import jfxtras.icalendarfx.utilities.IntegerStringConverter; import jfxtras.icalendarfx.utilities.StringConverter; import jfxtras.icalendarfx.utilities.StringConverters; /** * Allowed value types for calendar properties' value * * @author David Bal * */ public enum ValueType { BINARY ("BINARY", Arrays.asList(String.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) new DefaultStringConverter(); } }, BOOLEAN ("BOOLEAN", Arrays.asList(Boolean.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return object.toString().toUpperCase(); } @Override public T fromString(String string) { return (T) (Boolean) Boolean.parseBoolean(string); } }; } }, CALENDAR_USER_ADDRESS ("CAL-ADDRESS", Arrays.asList(URI.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) StringConverters.uriConverterNoQuotes(); } }, DATE ("DATE", Arrays.asList(LocalDate.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return DateTimeUtilities.temporalToString((LocalDate) object); } @Override public T fromString(String string) { return (T) LocalDate.parse(string, DateTimeUtilities.LOCAL_DATE_FORMATTER); } }; } }, DATE_TIME ("DATE-TIME", Arrays.asList(LocalDateTime.class, ZonedDateTime.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return DateTimeUtilities.temporalToString((Temporal) object); } @Override public T fromString(String string) { return (T) DateTimeUtilities.temporalFromString(string); } }; } }, DURATION ("DURATION", Arrays.asList(Duration.class, Period.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { String s = object.toString(); // move minus sign to front if (s.contains("-")) { s = "-" + s.replaceFirst("-", ""); } return s; } @Override public T fromString(String string) { if (string.contains("T")) { return (T) Duration.parse(string); } else { return (T) Period.parse(string); } } }; } }, // Based on ISO.8601.2004 (but Y and M for years and months is not supported by iCalendar) FLOAT ("FLOAT", Arrays.asList(Double.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) new DoubleStringConverter(); } }, INTEGER ("INTEGER", Arrays.asList(Integer.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) new IntegerStringConverter(); } }, PERIOD ("PERIOD", Arrays.asList(List.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return object.toString(); } @Override public T fromString(String string) { throw new RuntimeException("not implemented"); // return (T) string; } }; } }, RECURRENCE_RULE ("RECUR", Arrays.asList(RecurrenceRuleValue.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return ((VElement) object).toString(); } @Override public T fromString(String string) { return (T) RecurrenceRuleValue.parse(string); } }; } @Override public <T> List<String> createErrorList(T value) { if (value != null) { return ((RecurrenceRuleValue) value).errors(); } else { return Collections.emptyList(); } } }, /* Note: This string converter is only acceptable for values converted to Stings * without any additional processing. For properties with TEXT value that is stored * as any type other than String, this converter MUST be replaced. (Use setConverter in * Property). * For example, the value type for TimeZoneIdentifier is TEXT, but the Java object is * ZoneId. A different converter is required to make the conversion to ZoneId. */ TEXT ("TEXT", Arrays.asList(String.class, ZoneId.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { if (object == null) return ""; // Add escape characters String line = object.toString(); StringBuilder builder = new StringBuilder(line.length()+20); for (int i=0; i<line.length(); i++) { char myChar = line.charAt(i); for (int j=0;j<REPLACEMENT_CHARACTERS.length; j++) { if (myChar == REPLACEMENT_CHARACTERS[j]) { builder.append('\\'); myChar = SPECIAL_CHARACTERS[j]; break; } } builder.append(myChar); } return builder.toString(); } @Override public T fromString(String string) { // Remove escape characters \ , ; \n (newline) StringBuilder builder = new StringBuilder(string.length()); for (int i=0; i<string.length(); i++) { char charToAdd = string.charAt(i); if (string.charAt(i) == '\\') { char nextChar = string.charAt(i+1); for (int j=0;j<SPECIAL_CHARACTERS.length; j++) { if (nextChar == SPECIAL_CHARACTERS[j]) { charToAdd = REPLACEMENT_CHARACTERS[j]; if (charToAdd == '\n' && IS_WINDOWS) builder.append('\r'); i++; break; } } } builder.append(charToAdd); } return (T) builder.toString(); } }; // @Override // public String toString(T object) // { // if (object == null) return ""; // // Add escape characters // String line = object.toString(); // StringBuilder builder = new StringBuilder(line.length()+20); // for (int i=0; i<line.length(); i++) // { //// String myChar1 = line.substring(i); // String myChar = line.substring(i, Math.min(i+2, line.length())); //// char myChar = line.charAt(i); // for (int j=0;j<REPLACEMENT_CHARACTERS.length; j++) // { // if (myChar.startsWith(REPLACEMENT_CHARACTERS[j])) //// if (myChar == REPLACEMENT_CHARACTERS[j]) // { // builder.append('\\'); // myChar = Character.toString(SPECIAL_CHARACTERS[j]); // break; // } // } // builder.append(myChar); // } // return builder.toString(); // } // // @Override // public T fromString(String string) // { // // Remove escape characters \ , ; \n (newline) // StringBuilder builder = new StringBuilder(string.length()); // for (int i=0; i<string.length(); i++) // { // String charToAdd = string.substring(i); //// char charToAdd = string.charAt(i); // if (string.charAt(i) == '\\') // { // char nextChar = string.charAt(i+1); // for (int j=0;j<SPECIAL_CHARACTERS.length; j++) // { // if (nextChar == SPECIAL_CHARACTERS[j]) // { // charToAdd = REPLACEMENT_CHARACTERS[j]; // i++; // break; // } // } // } // builder.append(charToAdd); // } // return (T) builder.toString(); // } // }; } }, TIME ("TIME", Arrays.asList(LocalTime.class)) { @Override public <T> StringConverter<T> getConverter() { throw new RuntimeException("not implemented"); } }, UNIFORM_RESOURCE_IDENTIFIER ("URI", Arrays.asList(URI.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) StringConverters.uriConverterNoQuotes(); } }, UTC_OFFSET ("UTC-OFFSET", Arrays.asList(ZoneOffset.class)) { @Override public <T> StringConverter<T> getConverter() { return new StringConverter<T>() { @Override public String toString(T object) { return ZONE_OFFSET_FORMATTER.format((TemporalAccessor) object); } @Override public T fromString(String string) { return (T) ZoneOffset.of(string); } }; } }, UNKNOWN ("UNKNOWN", Arrays.asList(Object.class)) { @Override public <T> StringConverter<T> getConverter() { return (StringConverter<T>) new DefaultStringConverter(); } } ; final private static boolean IS_WINDOWS = System.getProperty("os.name").equals("Windows"); final private static char[] SPECIAL_CHARACTERS = new char[] {',' , ';' , '\\' , 'n', 'N' }; final private static char[] REPLACEMENT_CHARACTERS = new char[] {',' , ';' , '\\' , '\n', '\n'}; private final static DateTimeFormatter ZONE_OFFSET_FORMATTER = new DateTimeFormatterBuilder() .appendOffset("+HHMMss", "+0000") .toFormatter(); private static Map<String, ValueType> enumFromNameMap = makeEnumFromNameMap(); private static Map<String, ValueType> makeEnumFromNameMap() { Map<String, ValueType> map = new HashMap<>(); ValueType[] values = ValueType.values(); for (int i=0; i<values.length; i++) { map.put(values[i].toString(), values[i]); } return map; } /** get enum from name */ public static ValueType enumFromName(String propertyName) { ValueType type = enumFromNameMap.get(propertyName.toUpperCase()); return (type == null) ? UNKNOWN : type; } private String name; @Override public String toString() { return name; } List<Class<?>> allowedClasses; public List<Class<?>> allowedClasses() { return allowedClasses; } ValueType(String name, List<Class<?>> allowedClasses) { this.name = name; this.allowedClasses = allowedClasses; } /** return default String converter associated with property value type */ abstract public <T> StringConverter<T> getConverter(); public <T> List<String> createErrorList(T value) { // most values are valid by default, which is denoted by an empty list. RRULE is an exception and needs to call its errors() method. return null; } }