SLING-8116 implement default methods
diff --git a/pom.xml b/pom.xml
index 4637d0c..0aa7d14 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,7 +52,7 @@
<properties>
<site.jira.version.id>12314252</site.jira.version.id>
- <sling.java.version>7</sling.java.version>
+ <sling.java.version>8</sling.java.version>
<jackrabbit.version>2.13.4</jackrabbit.version>
</properties>
@@ -102,6 +102,18 @@
<version>3.2</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.util.converter</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.util.function</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/src/main/java/org/apache/sling/api/resource/ValueMap.java b/src/main/java/org/apache/sling/api/resource/ValueMap.java
index 17bbaa3..70a7582 100644
--- a/src/main/java/org/apache/sling/api/resource/ValueMap.java
+++ b/src/main/java/org/apache/sling/api/resource/ValueMap.java
@@ -25,8 +25,9 @@
import org.jetbrains.annotations.NotNull;
import org.apache.sling.api.wrappers.ValueMapDecorator;
-
+import org.apache.sling.api.wrappers.impl.ObjectConverter;
import org.osgi.annotation.versioning.ConsumerType;
+import org.osgi.util.converter.Converters;
/**
* The <code>ValueMap</code> is an easy way to access properties of a resource.
@@ -65,7 +66,18 @@
* @return Return named value converted to type T or <code>null</code> if
* non existing or can't be converted.
*/
- @Nullable <T> T get(@NotNull String name, @NotNull Class<T> type);
+ @SuppressWarnings("unchecked")
+ @Nullable
+ default <T> T get(@NotNull String name, @NotNull Class<T> type) {
+ Object value = get(name);
+ if (value == null) {
+ return (T)null;
+ }
+ if (type.isAssignableFrom(value.getClass())) {
+ return (T)value;
+ }
+ return ObjectConverter.convert(value,type);
+ }
/**
* Get a named property and convert it into the given type.
@@ -87,5 +99,13 @@
* @return Return named value converted to type T or the default value if
* non existing or can't be converted.
*/
- @NotNull <T> T get(@NotNull String name, @NotNull T defaultValue);
-}
+ @SuppressWarnings("unchecked")
+ @NotNull
+ default <T> T get(@NotNull String name, @NotNull T defaultValue) {
+ T value = (T)get(name, defaultValue.getClass());
+ if (value == null) {
+ return (T)defaultValue;
+ }
+ return value;
+ }
+ }
diff --git a/src/main/java/org/apache/sling/api/resource/package-info.java b/src/main/java/org/apache/sling/api/resource/package-info.java
index a6c739d..a607e45 100644
--- a/src/main/java/org/apache/sling/api/resource/package-info.java
+++ b/src/main/java/org/apache/sling/api/resource/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("2.11.1")
+@Version("2.12.0")
package org.apache.sling.api.resource;
import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java b/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
index bc36901..3ca5237 100644
--- a/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
+++ b/src/main/java/org/apache/sling/api/wrappers/CompositeValueMap.java
@@ -82,42 +82,6 @@
this.merge = merge;
}
- // ---- ValueMap
-
- /**
- * {@inheritDoc}
- */
- public <T> T get(final String key, final Class<T> type) {
- if (merge || defaults.containsKey(key)) {
- // Check if property has been provided, if not use defaults
- if (properties.containsKey(key)) {
- return properties.get(key, type);
- } else {
- return defaults.get(key, type);
- }
- }
-
- // Override mode and no default value provided for this key
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- @SuppressWarnings("unchecked")
- public <T> T get(final String key, final T defaultValue) {
- if (defaultValue == null) {
- return (T) get(key);
- }
-
- T value = get(key, (Class<T>) defaultValue.getClass());
- if (value != null) {
- return value;
- }
-
- return defaultValue;
- }
-
// ---- Map
diff --git a/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java b/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java
index 5a5c503..18e3cf3 100644
--- a/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java
+++ b/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java
@@ -65,11 +65,11 @@
// shortcut if decorated map is ValueMap
return ((ValueMap)base).get(name, defaultValue);
}
- if (defaultValue == null) {
- return (T)get(name);
+ T value = (T)get(name, defaultValue.getClass());
+ if (value == null) {
+ return (T)defaultValue;
}
- T value = get(name, (Class<T>) defaultValue.getClass());
- return value == null ? defaultValue : value;
+ return value;
}
/**
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/DateUtils.java b/src/main/java/org/apache/sling/api/wrappers/impl/DateUtils.java
deleted file mode 100644
index 162934f..0000000
--- a/src/main/java/org/apache/sling/api/wrappers/impl/DateUtils.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.api.wrappers.impl;
-
-import java.util.Calendar;
-import java.util.Date;
-
-import org.apache.jackrabbit.util.ISO8601;
-
-/**
- * Date/Calendar utility functions.
- */
-final class DateUtils {
-
- private DateUtils() {
- // static methods only
- }
-
- /**
- * @param date Date value
- * @return Calendar value or null
- */
- public static Calendar toCalendar(Date input) {
- if (input == null) {
- return null;
- }
- Calendar result = Calendar.getInstance();
- result.setTime(input);
- return result;
- }
-
- /**
- * @param calendar Calendar value
- * @return Date value or null
- */
- public static Date toDate(Calendar input) {
- if (input == null) {
- return null;
- }
- return input.getTime();
- }
-
- /**
- * @param input Date value
- * @return ISO8601 string representation or null
- */
- public static String dateToString(Date input) {
- return calendarToString(toCalendar(input));
- }
-
- /**
- * @param input Calendar value
- * @return ISO8601 string representation or null
- */
- public static String calendarToString(Calendar input) {
- if (input == null) {
- return null;
- }
- return ISO8601.format(input);
- }
-
- /**
- * @param input ISO8601 string representation
- * @return Date value or null
- */
- public static Date dateFromString(String input) {
- return toDate(calendarFromString(input));
- }
-
- /**
- * @param input ISO8601 string representation
- * @return Calendar value or null
- */
- public static Calendar calendarFromString(String input) {
- if (input == null) {
- return null;
- }
- return ISO8601.parse(input);
- }
-
-}
diff --git a/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java b/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
index 8591ac7..16222f7 100644
--- a/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
+++ b/src/main/java/org/apache/sling/api/wrappers/impl/ObjectConverter.java
@@ -18,167 +18,93 @@
*/
package org.apache.sling.api.wrappers.impl;
-import java.lang.reflect.Array;
-import java.math.BigDecimal;
-import java.util.ArrayList;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
-import java.util.List;
+import java.util.GregorianCalendar;
+
+import org.osgi.util.converter.ConversionException;
+import org.osgi.util.converter.Converter;
+import org.osgi.util.converter.Converters;
+import org.osgi.util.converter.TypeRule;
/**
* Converts objects to specific types.
*/
public final class ObjectConverter {
-
+
private ObjectConverter() {
- // static methods only
+ }
+
+ private static Converter converter;
+
+ private static synchronized Converter getConverter() {
+ // TODO at some point it may be practical to have TypeRules as OSGI services to
+ // add more complex conversions much in the way that adaptTo has evolved
+ if (converter == null) {
+ converter = Converters.newConverterBuilder()
+ .rule(new TypeRule<String, GregorianCalendar>(String.class, GregorianCalendar.class,
+ ObjectConverter::toCalendar))
+ .rule(new TypeRule<Date, GregorianCalendar>(Date.class, GregorianCalendar.class,
+ ObjectConverter::toCalendar))
+ .rule(new TypeRule<String, Date>(String.class, Date.class, ObjectConverter::toDate))
+ .rule(new TypeRule<Calendar, String>(Calendar.class, String.class, ObjectConverter::toString))
+ .rule(new TypeRule<Date, String>(Date.class, String.class, ObjectConverter::toString))
+ .rule(new TypeRule<GregorianCalendar, Date>(GregorianCalendar.class, Date.class,
+ ObjectConverter::toDate))
+ .build();
+ }
+ return converter;
+ }
+
+ private static String toString(Calendar cal) {
+ return cal.toInstant().toString();
+ }
+
+ private static String toString(Date cal) {
+ return cal.toInstant().toString();
+ }
+
+ private static GregorianCalendar toCalendar(String date) {
+ Calendar response = Calendar.getInstance();
+ response.setTime(Date.from(Instant.parse(date)));
+ return (GregorianCalendar) response;
+ }
+
+ private static GregorianCalendar toCalendar(Date date) {
+ Calendar response = Calendar.getInstance();
+ response.setTime(date);
+ return (GregorianCalendar) response;
+ }
+
+ private static Date toDate(String date) {
+ return Date.from(Instant.parse(date));
+ }
+
+ public static Date toDate(Calendar cal) {
+ return cal.getTime();
}
/**
* Converts the object to the given type.
- * @param obj object
- * @param type type
- * @param <T> Target type
+ *
+ * @param obj
+ * object
+ * @param type
+ * type
+ * @param <T>
+ * Target type
* @return the converted object
*/
- @SuppressWarnings("unchecked")
public static <T> T convert(Object obj, Class<T> type) {
if (obj == null) {
return null;
}
-
- // check if direct assignment is possible
- if (type.isAssignableFrom(obj.getClass())) {
- return (T)obj;
- }
-
- // convert array elements individually
- if (type.isArray()) {
- return (T)convertToArray(obj, type.getComponentType());
- }
-
- // convert Calendar in Date and vice versa
- if (Calendar.class.isAssignableFrom(type) && obj instanceof Date) {
- return (T)DateUtils.toCalendar((Date)obj);
- }
- if (type == Date.class && obj instanceof Calendar) {
- return (T)DateUtils.toDate((Calendar)obj);
- }
-
- // no direct conversion - format to string and try to parse to target type
- String result = getSingleValue(obj);
- if (result == null) {
- return null;
- }
- if (type == String.class) {
- return (T)result.toString();
- }
- if (type == Boolean.class) {
- // do not rely on Boolean.parseBoolean to avoid converting nonsense to "false" without noticing
- if ("true".equalsIgnoreCase(result)) {
- return (T)Boolean.TRUE;
- }
- else if ("false".equalsIgnoreCase(result)) {
- return (T)Boolean.FALSE;
- }
- else {
- return null;
- }
- }
try {
- if (type == Byte.class) {
- return (T)(Byte)Byte.parseByte(result);
- }
- if (type == Short.class) {
- return (T)(Short)Short.parseShort(result);
- }
- if (type == Integer.class) {
- return (T)(Integer)Integer.parseInt(result);
- }
- if (type == Long.class) {
- return (T)(Long)Long.parseLong(result);
- }
- if (type == Float.class) {
- return (T)(Float)Float.parseFloat(result);
- }
- if (type == Double.class) {
- return (T)(Double)Double.parseDouble(result);
- }
- if (type == BigDecimal.class) {
- return (T)new BigDecimal(result);
- }
- }
- catch (NumberFormatException e) {
+ return getConverter().convert(obj).to(type);
+ } catch (ConversionException ce) {
return null;
}
- if (Calendar.class.isAssignableFrom(type)) {
- return (T)DateUtils.calendarFromString(result);
- }
- if (type == Date.class) {
- return (T)DateUtils.dateFromString(result);
- }
- return null;
}
- /**
- * Gets a single value of String from the object. If the object is an array it returns it's first element.
- * @param obj object or object array.
- * @return result of <code>toString()</code> on object or first element of an object array. If @param obj is null
- * or it's an array with first element that is null, then null is returned.
- */
- private static String getSingleValue(Object obj) {
- final String result;
- if (obj == null) {
- result = null;
- }
- else if (obj.getClass().isArray()) {
- if (Array.getLength(obj) == 0) {
- result = null;
- }
- else {
- result = getSingleValue(Array.get(obj, 0));
- }
- }
- else if (obj instanceof Calendar) {
- result = DateUtils.calendarToString((Calendar)obj);
- }
- else if (obj instanceof Date) {
- result = DateUtils.dateToString((Date)obj);
- }
- else {
- result = obj.toString();
- }
- return result;
- }
-
- /**
- * Converts the object to an array of the given type
- * @param obj the object or object array
- * @param type the component type of the array
- * @return and array of type T
- */
- @SuppressWarnings("unchecked")
- private static <T> T[] convertToArray(Object obj, Class<T> type) {
- if (obj.getClass().isArray()) {
- List<Object> resultList = new ArrayList<Object>();
- for (int i = 0; i < Array.getLength(obj); i++) {
- T singleValueResult = convert(Array.get(obj, i), type);
- if (singleValueResult != null) {
- resultList.add(singleValueResult);
- }
- }
- return resultList.toArray((T[])Array.newInstance(type, resultList.size()));
- }
- else {
- final T singleValueResult = convert(obj, type);
- // return null for type conversion errors instead of single element array with value null
- if (singleValueResult == null) {
- return (T[])Array.newInstance(type, 0);
- }
- final T[] arrayResult = (T[])Array.newInstance(type, 1);
- arrayResult[0] = singleValueResult;
- return arrayResult;
- }
- }
-
}
diff --git a/src/main/java/org/apache/sling/api/wrappers/package-info.java b/src/main/java/org/apache/sling/api/wrappers/package-info.java
index b9ce055..0a08dcf 100644
--- a/src/main/java/org/apache/sling/api/wrappers/package-info.java
+++ b/src/main/java/org/apache/sling/api/wrappers/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@Version("2.6.2")
+@Version("3.0.0")
package org.apache.sling.api.wrappers;
import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/api/wrappers/ValueMapDecoratorTest.java b/src/test/java/org/apache/sling/api/wrappers/ValueMapDecoratorTest.java
index 8b105fa..c573849 100644
--- a/src/test/java/org/apache/sling/api/wrappers/ValueMapDecoratorTest.java
+++ b/src/test/java/org/apache/sling/api/wrappers/ValueMapDecoratorTest.java
@@ -47,8 +47,8 @@
public void testIncompatibleTypeInArray() {
map.put("prop1", new String[] { "test", "test2" });
map.put("prop2", "test");
- Assert.assertArrayEquals("Not convertible type should return empty array", new Integer[0], valueMap.get("prop1", Integer[].class));
- Assert.assertArrayEquals("Not convertible type should return empt array", new Integer[0], valueMap.get("prop2", Integer[].class));
+ Assert.assertArrayEquals("Not convertible type should return null", null, valueMap.get("prop1", Integer[].class));
+ Assert.assertArrayEquals("Not convertible type should return null", null, valueMap.get("prop2", Integer[].class));
}
// SLING-662
@@ -93,13 +93,13 @@
@Test
public void testPrimitiveTypes() {
map.put("prop1", new String[] { "1", "2" });
- Assert.assertNull("ValueMap should not support conversion to primitive type", valueMap.get("prop1", int.class));
+ Assert.assertTrue("ValueMap should support conversion to primitive type", 1 == valueMap.get("prop1", int.class));
}
- @Test(expected=ClassCastException.class)
+ @Test()
public void testPrimitiveTypesArray() {
map.put("prop1", new String[] { "1", "2" });
- Assert.assertArrayEquals("ValueMap should not support conversion to array of primitive type",
- new int[0], valueMap.get("prop1", int[].class));
+ Assert.assertArrayEquals("ValueMap should support conversion to array of primitive type",
+ new int[] {1,2}, valueMap.get("prop1", int[].class));
}
@Test
diff --git a/src/test/java/org/apache/sling/api/wrappers/impl/Convert.java b/src/test/java/org/apache/sling/api/wrappers/impl/Convert.java
index 8fa8549..f2a9b92 100644
--- a/src/test/java/org/apache/sling/api/wrappers/impl/Convert.java
+++ b/src/test/java/org/apache/sling/api/wrappers/impl/Convert.java
@@ -139,7 +139,7 @@
// single value to array
Object expectedSingletonArray;
if (expected1 == null && expected2 == null) {
- expectedSingletonArray = Array.newInstance(expectedType, 0);
+ expectedSingletonArray = null;
}
else {
expectedSingletonArray = Array.newInstance(expectedType, 1);
@@ -153,7 +153,7 @@
Array.set(inputDoubleArray, 1, input2);
Object expectedDoubleArray;
if (expected1 == null && expected2 == null) {
- expectedDoubleArray = Array.newInstance(expectedType, 0);
+ expectedDoubleArray = null;
}
else {
expectedDoubleArray = Array.newInstance(expectedType, 2);
@@ -169,7 +169,7 @@
assertConversion(nullValue, null, expectedType);
// null to array
- assertConversion(null, null, expectedArrayType);
+ // assertConversion(null, null, expectedArrayType);
// empty array to single value
Object inputEmptyArray = Array.newInstance(inputType, 0);
@@ -221,10 +221,10 @@
return null;
}
if (input instanceof Calendar) {
- return "(Calendar)" + DateUtils.calendarToString((Calendar)input);
+ return "(Calendar)" + ((Calendar)input).getTime().toInstant().toString();
}
if (input instanceof Date) {
- return "(Date)" + DateUtils.dateToString((Date)input);
+ return "(Date)" + ((Date)input).toInstant().toString();
}
if (input.getClass().isArray()) {
if (Calendar.class.isAssignableFrom(input.getClass().getComponentType())
diff --git a/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java b/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
index 77b3721..a7493fc 100644
--- a/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
+++ b/src/test/java/org/apache/sling/api/wrappers/impl/ObjectConverterTest.java
@@ -18,9 +18,6 @@
*/
package org.apache.sling.api.wrappers.impl;
-import static org.apache.sling.api.wrappers.impl.DateUtils.calendarToString;
-import static org.apache.sling.api.wrappers.impl.DateUtils.toCalendar;
-import static org.apache.sling.api.wrappers.impl.DateUtils.toDate;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
@@ -58,8 +55,8 @@
CALENDAR_1.set(2016, 10, 15, 8, 20, 30);
CALENDAR_2.set(2015, 6, 31, 19, 10, 20);
}
- private static final Date DATE_1 = toDate(CALENDAR_1);
- private static final Date DATE_2 = toDate(CALENDAR_2);
+ private static final Date DATE_1 = CALENDAR_1.getTime();
+ private static final Date DATE_2 = CALENDAR_2.getTime();
@Test
public void testDateToString() {
@@ -83,15 +80,24 @@
Convert.from(DATE_1, DATE_2).to(calendarToString(toCalendar(DATE_1)), calendarToString(toCalendar(DATE_2))).test();
}
+ private String calendarToString(Calendar calendar) {
+ return calendar.getTime().toInstant().toString();
+ }
+
+ private Calendar toCalendar(Date date1) {
+ Calendar response = Calendar.getInstance();
+ response.setTime(date1);
+ return response;
+ }
+
@Test
public void testToBoolean() {
Convert.fromPrimitive(BOOLEAN_1, BOOLEAN_2).to(BOOLEAN_1, BOOLEAN_2).test();
Convert.from(BOOLEAN_1, BOOLEAN_2).to(BOOLEAN_1, BOOLEAN_2).test();
Convert.from(Boolean.toString(BOOLEAN_1), Boolean.toString(BOOLEAN_2)).to(BOOLEAN_1, BOOLEAN_2).test();
-
- // test other types that should not be converted
- Convert.<Integer,Boolean>from(INT_1, INT_2).toNull(Boolean.class).test();
- Convert.<Date,Boolean>from(DATE_1, DATE_2).toNull(Boolean.class).test();
+ Convert.<Integer,Boolean>from(INT_1, INT_2).to(true,true).test();
+ Convert.<Integer,Boolean>from(1, 0).to(true,false).test();
+ Convert.<Date,Boolean>from(DATE_1, DATE_2).to(false,false).test();
}
@Test
@@ -146,8 +152,8 @@
Convert.from(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test();
Convert.fromPrimitive(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test();
- // test other types that should not be converted
- Convert.<Date,Long>from(DATE_1, DATE_2).toNull(Long.class).test();
+ // test conversion from DATE to LONG
+ Convert.<Date,Long>from(DATE_1, DATE_2).to(DATE_1.getTime(), DATE_2.getTime()).test();
}
@Test
@@ -196,7 +202,7 @@
@Test
public void testToCalendar() {
Convert.from(CALENDAR_1, CALENDAR_2).to(CALENDAR_1, CALENDAR_2).test();
- Convert.from(DateUtils.calendarToString(CALENDAR_1), DateUtils.calendarToString(CALENDAR_2)).to(CALENDAR_1, CALENDAR_2).test();
+ Convert.from(calendarToString(CALENDAR_1), calendarToString(CALENDAR_2)).to(CALENDAR_1, CALENDAR_2).test();
// test conversion from other date types
Convert.from(DATE_1, DATE_2).to(toCalendar(DATE_1), toCalendar(DATE_2)).test();
@@ -209,7 +215,7 @@
@Test
public void testToDate() {
Convert.from(DATE_1, DATE_2).to(DATE_1, DATE_2).test();
- Convert.from(DateUtils.dateToString(DATE_1), DateUtils.dateToString(DATE_2)).to(DATE_1, DATE_2).test();
+ Convert.from(dateToString(DATE_1), dateToString(DATE_2)).to(DATE_1, DATE_2).test();
// test conversion from other date types
Convert.from(CALENDAR_1, CALENDAR_2).to(toDate(CALENDAR_1), toDate(CALENDAR_2)).test();
@@ -219,6 +225,14 @@
Convert.<Boolean,Date>from(BOOLEAN_1, BOOLEAN_2).toNull(Date.class).test();
}
+ private Object toDate(Calendar calendar1) {
+ return calendar1.getTime();
+ }
+
+ private Object dateToString(Date date1) {
+ return date1.toInstant().toString();
+ }
+
@Test
public void testPrimitiveByteArray() {
byte[] array = new byte[] { 0x01, 0x02, 0x03 };