targetType, ObjectFactory objectFactory)
{
this.targetType = targetType;
+ this.objectFactory = Objects.requireNonNull(objectFactory,
+ "the ObjectFactory must not be null");
}
@Override
@@ -97,7 +111,7 @@ private T asObject(Properties properties)
Field[] fields = FieldUtils.getAllFields(targetType);
try
{
- T targetObject = ConstructorUtils.invokeConstructor(targetType);
+ T targetObject = objectFactory.newObject(targetType);
for (Field field : fields)
{
writeField(targetObject, field, properties);
@@ -136,7 +150,7 @@ private void writeField(T targetObject, Field field, Properties properties)
Class> fieldType = field.getType();
try
{
- Object parsedValue = PropertyUtils.parseValue(propertyValue, fieldType, annotation);
+ Object parsedValue = PropertyUtils.parseValue(propertyValue, fieldType, annotation, objectFactory);
FieldUtils.writeDeclaredField(targetObject, field.getName(), parsedValue, true);
}
catch (ParseException exception)
diff --git a/confectory-core/src/main/java/net/obvj/confectory/settings/ConfectorySettings.java b/confectory-core/src/main/java/net/obvj/confectory/settings/ConfectorySettings.java
index 4671697c..0c970db0 100644
--- a/confectory-core/src/main/java/net/obvj/confectory/settings/ConfectorySettings.java
+++ b/confectory-core/src/main/java/net/obvj/confectory/settings/ConfectorySettings.java
@@ -19,6 +19,7 @@
import java.util.Objects;
import net.obvj.confectory.DataFetchStrategy;
+import net.obvj.confectory.util.ObjectFactory;
/**
* An object that defines the global settings for the {@code Confectory} project.
@@ -34,11 +35,17 @@ public class ConfectorySettings
*/
static final DataFetchStrategy INITIAL_DATA_FETCH_STRATEGY = DataFetchStrategy.LENIENT;
+ /**
+ * The initial {@link ObjectFactory} applied by default
+ */
+ static final ObjectFactory INITIAL_OBJECT_FACTORY = ObjectFactory.UNSAFE;
+
private static final ConfectorySettings INSTANCE = new ConfectorySettings();
// Settings - start
private DataFetchStrategy defaultDataFetchStrategy;
+ private ObjectFactory objectFactory;
/*
* Private constructor to hide the default, implicit one
@@ -54,12 +61,13 @@ private ConfectorySettings()
public void reset()
{
defaultDataFetchStrategy = INITIAL_DATA_FETCH_STRATEGY;
+ objectFactory = INITIAL_OBJECT_FACTORY;
}
/**
* @return a reference to the the current {@link ConfectorySettings} instance.
*/
- public static ConfectorySettings getInstance()
+ public static ConfectorySettings instance()
{
return INSTANCE;
}
@@ -70,7 +78,7 @@ public static ConfectorySettings getInstance()
*
* @return the default {@link DataFetchStrategy} to be applied
*/
- public DataFetchStrategy getDefaultDataFetchStrategy()
+ public DataFetchStrategy getDataFetchStrategy()
{
return defaultDataFetchStrategy;
}
@@ -82,10 +90,32 @@ public DataFetchStrategy getDefaultDataFetchStrategy()
* @param strategy the default {@link DataFetchStrategy} to set; not null
* @throws NullPointerException if the specified strategy is null
*/
- public void setDefaultDataFetchStrategy(DataFetchStrategy strategy)
+ public void setDataFetchStrategy(DataFetchStrategy strategy)
{
this.defaultDataFetchStrategy = Objects.requireNonNull(strategy,
"the default DataFetchStrategy must not be null");
}
+ /**
+ * @return the {@link ObjectFactory} to be produce new objects
+ * @since 2.5.0
+ */
+ public ObjectFactory getObjectFactory()
+ {
+ return objectFactory;
+ }
+
+ /**
+ * Defines the {@link ObjectFactory} to produce new objects.
+ *
+ * @param objectFactory the {@link ObjectFactory} to set; not null
+ * @throws NullPointerException if the specified {@link ObjectFactory} is null
+ * @since 2.5.0
+ */
+ public void setObjectFactory(ObjectFactory objectFactory)
+ {
+ this.objectFactory = Objects.requireNonNull(objectFactory,
+ "the ObjectFactory must not be null");
+ }
+
}
diff --git a/confectory-core/src/main/java/net/obvj/confectory/util/ObjectFactory.java b/confectory-core/src/main/java/net/obvj/confectory/util/ObjectFactory.java
new file mode 100644
index 00000000..b129709b
--- /dev/null
+++ b/confectory-core/src/main/java/net/obvj/confectory/util/ObjectFactory.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023 obvj.net
+ *
+ * Licensed 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 net.obvj.confectory.util;
+
+import org.apache.commons.lang3.reflect.ConstructorUtils;
+import org.objenesis.ObjenesisStd;
+
+/**
+ * A factory that encapsulates the logic to reflectively produce new objects depending on
+ * the desired strategy.
+ *
+ * @author oswaldo.bapvic.jr
+ * @since 2.5.0
+ */
+public enum ObjectFactory
+{
+ /**
+ * Constructor-based object factory.
+ *
+ * It's safer but requires the existence of a public, default constructor available in the
+ * class to allow the instantiation.
+ */
+ CLASSIC
+ {
+ @Override
+ public T newObject(Class type) throws ReflectiveOperationException
+ {
+ return ConstructorUtils.invokeConstructor(type);
+ }
+ },
+
+ /**
+ * Object factory that builds objects by allocating an instance directly on the heap,
+ * without any constructor being called.
+ *
+ * Final fields are assigned with
+ *
+ * default values.
+ *
+ * Note: This is the default strategy since 2.5.0.
+ */
+ UNSAFE
+ {
+ @Override
+ @SuppressWarnings("restriction")
+ public T newObject(Class type) throws ReflectiveOperationException
+ {
+ return type.cast(UnsafeAccessor.UNSAFE.allocateInstance(type));
+ }
+ },
+
+ /**
+ * Alternative object factory that uses a variety of approaches to attempt to instantiate
+ * the object, depending on the type of object, JVM version, JVM vendor and Security
+ * Manager present.
+ *
+ * IMPORTANT: This strategy requires the optional dependency
+ * {@code org.objenesis:objenesis} in the class path.
+ */
+ OBJENESIS
+ {
+ @Override
+ public T newObject(Class type)
+ {
+ return new ObjenesisStd().newInstance(type);
+ }
+ };
+
+ /**
+ * Creates a new instance of the specified class.
+ *
+ * @param the target type
+ * @param type the target type
+ * @return a new instance of the specified class
+ *
+ * @throws ReflectiveOperationException in case of failure during the instantiation
+ */
+ public abstract T newObject(Class type) throws ReflectiveOperationException;
+
+}
diff --git a/confectory-core/src/main/java/net/obvj/confectory/util/PropertyUtils.java b/confectory-core/src/main/java/net/obvj/confectory/util/PropertyUtils.java
index 63756265..5151cf90 100644
--- a/confectory-core/src/main/java/net/obvj/confectory/util/PropertyUtils.java
+++ b/confectory-core/src/main/java/net/obvj/confectory/util/PropertyUtils.java
@@ -16,13 +16,12 @@
package net.obvj.confectory.util;
-import static net.obvj.confectory.util.StringUtils.*;
+import static net.obvj.confectory.util.StringUtils.defaultIfEmpty;
import java.lang.reflect.Field;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
/**
@@ -130,18 +129,20 @@ public static boolean isFieldAnnotated(final Field field, final String name)
* class configured in the {@link Property} annotation (if not null), or standard
* conversion by using the {@link TypeFactory} by default.
*
- * @param string the string to be parsed
- * @param field the target field (not null)
+ * @param string the string to be parsed
+ * @param field the target field (not null)
+ * @param objectFactory the {@link ObjectFactory} to produce a new instance of the custom
+ * converter class, if specified in the annotation (not null)
*
* @return the object resulting from the parse operation
*
* @throws ReflectiveOperationException if an error occurs in the reflective operation
* @throws ParseException if an error is encountered while parsing
*/
- public static Object parseValue(final String string, final Field field)
- throws ReflectiveOperationException, ParseException
+ public static Object parseValue(final String string, final Field field,
+ final ObjectFactory objectFactory) throws ReflectiveOperationException, ParseException
{
- return parseValue(string, field.getType(), field.getAnnotation(Property.class));
+ return parseValue(string, field.getType(), field.getAnnotation(Property.class), objectFactory);
}
/**
@@ -151,9 +152,11 @@ public static Object parseValue(final String string, final Field field)
* class configured in the {@link Property} annotation (if not null), or standard
* conversion by using the {@link TypeFactory} by default.
*
- * @param string the string to be parsed
- * @param targetType the target type (not null)
- * @param property the {@link Property} annotation to be evaluated (null is allowed)
+ * @param string the string to be parsed
+ * @param targetType the target type (not null)
+ * @param property the {@link Property} annotation to be evaluated (null is allowed)
+ * @param objectFactory the {@link ObjectFactory} to produce a new instance of the custom
+ * converter class, if specified in the annotation (not null)
*
* @return the object resulting from the parse operation
*
@@ -161,15 +164,17 @@ public static Object parseValue(final String string, final Field field)
* @throws ParseException if an error is encountered while parsing
*/
public static Object parseValue(final String string, final Class> targetType,
- final Property property) throws ReflectiveOperationException, ParseException
+ final Property property, ObjectFactory objectFactory)
+ throws ReflectiveOperationException, ParseException
{
if (property != null && property.converter().length > 0)
{
// Apply custom converter specified in the annotation
- TypeConverter> converter = ConstructorUtils.invokeConstructor(property.converter()[0]);
+ TypeConverter> converter = objectFactory.newObject(property.converter()[0]);
return converter.convert(string);
}
// Apply standard/default conversion
return TypeFactory.parse(targetType, string);
}
+
}
diff --git a/confectory-core/src/main/java/net/obvj/confectory/util/UnsafeAccessor.java b/confectory-core/src/main/java/net/obvj/confectory/util/UnsafeAccessor.java
new file mode 100644
index 00000000..5b9077db
--- /dev/null
+++ b/confectory-core/src/main/java/net/obvj/confectory/util/UnsafeAccessor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 obvj.net
+ *
+ * Licensed 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 net.obvj.confectory.util;
+
+import java.lang.reflect.Constructor;
+
+import sun.misc.Unsafe;
+
+/**
+ * A class that allows to get access to {@code sun.misc.Unsafe}.
+ *
+ * @author oswaldo.bapvic.jr
+ * @since 2.5.0
+ */
+@SuppressWarnings("restriction")
+public final class UnsafeAccessor
+{
+ public static final Unsafe UNSAFE = getUnsafe();
+
+ private static Unsafe getUnsafe()
+ {
+ try
+ {
+ Constructor constructor = Unsafe.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ return constructor.newInstance();
+ }
+ catch (ReflectiveOperationException exception)
+ {
+ throw new UnsupportedOperationException(
+ "Unable to get hold of an instance of sun.misc.Unsafe", exception);
+ }
+ }
+
+ private UnsafeAccessor()
+ {
+ throw new UnsupportedOperationException("Instantiation not allowed");
+ }
+
+}
diff --git a/confectory-core/src/test/java/net/obvj/confectory/ConfectoryTest.java b/confectory-core/src/test/java/net/obvj/confectory/ConfectoryTest.java
index 1b177ef7..6fbb6dd3 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/ConfectoryTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/ConfectoryTest.java
@@ -48,7 +48,7 @@ void ensure_global_default_configuration_exists()
@Test
void settings_sameInstanceDefaultConfiguration()
{
- assertThat(Confectory.settings(), equalTo(ConfectorySettings.getInstance()));
+ assertThat(Confectory.settings(), equalTo(ConfectorySettings.instance()));
}
}
diff --git a/confectory-core/src/test/java/net/obvj/confectory/ConfigurationContainerTest.java b/confectory-core/src/test/java/net/obvj/confectory/ConfigurationContainerTest.java
index f6520d74..b2d0a0af 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/ConfigurationContainerTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/ConfigurationContainerTest.java
@@ -88,7 +88,7 @@ private static String join(String... lines)
private void assertDefaultDataFetchStrategy()
{
assertThat(container.getDataFetchStrategy(),
- equalTo(ConfectorySettings.getInstance().getDefaultDataFetchStrategy()));
+ equalTo(ConfectorySettings.instance().getDataFetchStrategy()));
}
@Test
diff --git a/confectory-core/src/test/java/net/obvj/confectory/TypeSafeConfigurationContainerTest.java b/confectory-core/src/test/java/net/obvj/confectory/TypeSafeConfigurationContainerTest.java
index 77b0e6e9..e50a4ced 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/TypeSafeConfigurationContainerTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/TypeSafeConfigurationContainerTest.java
@@ -61,7 +61,7 @@ void constructor_empty_default()
container = new TypeSafeConfigurationContainer<>();
assertThat(container.size(), equalTo(0L));
assertThat(container.getInternal().getDataFetchStrategy(),
- equalTo(Confectory.settings().getDefaultDataFetchStrategy()));
+ equalTo(Confectory.settings().getDataFetchStrategy()));
}
@Test
diff --git a/confectory-core/src/test/java/net/obvj/confectory/mapper/INIToObjectMapperTest.java b/confectory-core/src/test/java/net/obvj/confectory/mapper/INIToObjectMapperTest.java
index b1d170ea..cec40cc0 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/mapper/INIToObjectMapperTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/mapper/INIToObjectMapperTest.java
@@ -42,6 +42,7 @@
import net.obvj.confectory.mapper.PropertiesToObjectMapperTest.MyIntsConverter;
import net.obvj.confectory.mapper.PropertiesToObjectMapperTest.MyPairConverter;
import net.obvj.confectory.mapper.model.MyIni;
+import net.obvj.confectory.util.ObjectFactory;
import net.obvj.confectory.util.ParseException;
import net.obvj.confectory.util.Property;
@@ -116,6 +117,9 @@ class INIToObjectMapperTest
static class MyBeanPrivateConstructor
{
+ @Property("rootProperty")
+ String myString;
+
private MyBeanPrivateConstructor() {}
}
@@ -125,7 +129,10 @@ static class MyBeanPrivateSection
static class Section
{
- private Section() {} // unsupported
+ @Property("section_string")
+ String myString;
+
+ private Section() {} // unsupported in ObjectFactory.CLASSIC
}
}
@@ -133,8 +140,6 @@ static class MyIniDate
{
@Property("my_date")
LocalDate myDate;
-
- public MyIniDate() {}
}
static class MyIniOtherTypes
@@ -143,8 +148,6 @@ static class MyIniOtherTypes
Class extends Exception> myClass;
@Property("my_ip")
InetAddress myIp;
-
- public MyIniOtherTypes() {}
}
static class MyBeanCustomConverters
@@ -156,26 +159,24 @@ static class MyBeanCustomConverters
@Property("section1")
Section section;
- public MyBeanCustomConverters() {}
-
static class Section
{
@Property("myBigDecimal")
BigDecimal myDecimal;
-
- public Section() {}
}
}
private Mapper mapper = new INIToObjectMapper<>(MyIni.class);
+ private Mapper mapperClassic = new INIToObjectMapper<>(MyIni.class, ObjectFactory.CLASSIC);
+ private Mapper mapperUnsafe = new INIToObjectMapper<>(MyIni.class, ObjectFactory.UNSAFE);
private ByteArrayInputStream toInputStream(String content)
{
return new ByteArrayInputStream(content.getBytes());
}
- private MyIni testWithString(String string)
+ private MyIni applyString(Mapper mapper, String string)
{
try
{
@@ -189,9 +190,26 @@ private MyIni testWithString(String string)
}
@Test
- void apply_validIni_validJSONObject() throws IOException
+ void apply_validIniAndObjectFactoryClassic_validObject() throws IOException
{
- MyIni result = testWithString(VALID_INI_1);
+ MyIni result = applyString(mapperClassic, VALID_INI_1);
+ assertThat(result.getRootProperty(), is(equalTo("myRootValue")));
+
+ assertThat(result.getSection1().getSectionString(), is(equalTo("mySection1Value")));
+ assertThat(result.getSection1().getSectionNumber(), is(equalTo(1)));
+ assertThat(result.getSection1().isSectionBoolean(), is(equalTo(false)));
+ assertThat(result.getSection1().getTransientField(), is(equalTo(null)));
+
+ assertThat(result.getSection2().getSectionString(), is(equalTo("mySection2Value")));
+ assertThat(result.getSection2().getSectionNumber(), is(equalTo(2)));
+ assertThat(result.getSection2().isSectionBoolean(), is(equalTo(true)));
+ assertThat(result.getSection2().getTransientField(), is(equalTo(null)));
+ }
+
+ @Test
+ void apply_validIniAndObjectFactoryUnsafe_validObject() throws IOException
+ {
+ MyIni result = applyString(mapperUnsafe, VALID_INI_1);
assertThat(result.getRootProperty(), is(equalTo("myRootValue")));
assertThat(result.getSection1().getSectionString(), is(equalTo("mySection1Value")));
@@ -208,28 +226,32 @@ void apply_validIni_validJSONObject() throws IOException
@Test
void apply_missingTokenInSectionDeclaration_exception() throws IOException
{
- assertThat(() -> testWithString(INVALID_INI_1), throwsException(ConfigurationSourceException.class)
+ assertThat(() -> applyString(mapper, INVALID_INI_1),
+ throwsException(ConfigurationSourceException.class)
.withMessage(equalTo("Malformed INI: expected token ']' at line 2: \"[section1\"")));
}
@Test
void apply_sectionDeclarationNoName_exception() throws IOException
{
- assertThat(() -> testWithString(INVALID_INI_2), throwsException(ConfigurationSourceException.class)
+ assertThat(() -> applyString(mapper, INVALID_INI_2),
+ throwsException(ConfigurationSourceException.class)
.withMessage(equalTo("Malformed INI: expected section name at line 2: \"[]\"")));
}
@Test
void apply_valueWithoutProperty_exception() throws IOException
{
- assertThat(() -> testWithString(INVALID_INI_3), throwsException(ConfigurationSourceException.class)
+ assertThat(() -> applyString(mapper, INVALID_INI_3),
+ throwsException(ConfigurationSourceException.class)
.withMessage(equalTo("Malformed INI: expected property key at line 1: \"=value\"")));
}
@Test
void apply_invalidLine_exception() throws IOException
{
- assertThat(() -> testWithString(INVALID_INI_4), throwsException(ConfigurationSourceException.class)
+ assertThat(() -> applyString(mapper, INVALID_INI_4),
+ throwsException(ConfigurationSourceException.class)
.withMessage(equalTo("Malformed INI: expected property at line 1: \"invalid line\"")));
}
@@ -237,7 +259,7 @@ void apply_invalidLine_exception() throws IOException
void apply_invalidType_exception() throws IOException
{
ConfigurationException exception = assertThrows(ConfigurationException.class,
- () -> testWithString(INVALID_INI_5));
+ () -> applyString(mapper, INVALID_INI_5));
assertThat(exception.getMessage(), equalTo(
"Unable to parse the value of the property ['number'] into a field of type 'double'"));
@@ -255,7 +277,7 @@ void apply_invalidType_exception() throws IOException
void apply_invalidTypeInsideSection_exception() throws IOException
{
ConfigurationException exception = assertThrows(ConfigurationException.class,
- () -> testWithString(INVALID_INI_6));
+ () -> applyString(mapper, INVALID_INI_6));
assertThat(exception.getMessage(), equalTo(
"Unable to parse the value of the property ['section1']['section_number'] into a field of type 'int'"));
@@ -272,7 +294,7 @@ void apply_invalidTypeInsideSection_exception() throws IOException
@Test
void apply_sectionNotMapped_sectionSkipped() throws IOException
{
- MyIni result = testWithString(VALID_INI_2);
+ MyIni result = applyString(mapper, VALID_INI_2);
assertThat(result.getRootProperty(), is(equalTo("myRootValue")));
assertThat(result.getSection1(), is(equalTo(null)));
assertThat(result.getSection2().getSectionString(), is(equalTo("mySection2Value")));
@@ -281,25 +303,41 @@ void apply_sectionNotMapped_sectionSkipped() throws IOException
}
@Test
- void apply_beanWithPrivateConstructor_configurationException()
+ void apply_beanWithPrivateConstructorAndClassicFactory_configurationException()
{
assertThat(
- () -> new INIToObjectMapper<>(MyBeanPrivateConstructor.class)
+ () -> new INIToObjectMapper<>(MyBeanPrivateConstructor.class, ObjectFactory.CLASSIC)
.apply(toInputStream(VALID_INI_1)),
throwsException(ConfigurationException.class)
.withCause(ReflectiveOperationException.class));
}
@Test
- void apply_beanWithPrivateConstructorInSection_configurationException()
+ void apply_beanWithPrivateConstructorAndEnhancedFactory_success() throws IOException
+ {
+ MyBeanPrivateConstructor bean = new INIToObjectMapper<>(MyBeanPrivateConstructor.class,
+ ObjectFactory.UNSAFE).apply(toInputStream(VALID_INI_1));
+ assertThat(bean.myString, equalTo("myRootValue"));
+ }
+
+ @Test
+ void apply_beanWithPrivateConstructorInSectionAndClassicFactory_configurationException()
{
assertThat(
- () -> new INIToObjectMapper<>(MyBeanPrivateSection.class)
+ () -> new INIToObjectMapper<>(MyBeanPrivateSection.class, ObjectFactory.CLASSIC)
.apply(toInputStream(VALID_INI_1)),
throwsException(ConfigurationException.class)
.withCause(ReflectiveOperationException.class));
}
+ @Test
+ void apply_beanWithPrivateConstructorInSectionAndEnhancedFactory_success() throws IOException
+ {
+ MyBeanPrivateSection bean = new INIToObjectMapper<>(MyBeanPrivateSection.class,
+ ObjectFactory.UNSAFE).apply(toInputStream(VALID_INI_1));
+ assertThat(bean.section1.myString, equalTo("mySection1Value"));
+ }
+
@Test
void apply_validIniDate_validObject() throws IOException
{
diff --git a/confectory-core/src/test/java/net/obvj/confectory/mapper/PropertiesToObjectMapperTest.java b/confectory-core/src/test/java/net/obvj/confectory/mapper/PropertiesToObjectMapperTest.java
index b004bbdb..6397e221 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/mapper/PropertiesToObjectMapperTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/mapper/PropertiesToObjectMapperTest.java
@@ -40,6 +40,7 @@
import net.obvj.confectory.ConfigurationException;
import net.obvj.confectory.TestUtils;
import net.obvj.confectory.internal.helper.BeanConfigurationHelper;
+import net.obvj.confectory.util.ObjectFactory;
import net.obvj.confectory.util.ParseException;
import net.obvj.confectory.util.Property;
import net.obvj.confectory.util.TypeConverter;
@@ -74,8 +75,6 @@ static class MyBeanNoExplicitMapping
Boolean booleanValue; // implicit mapping
String stringValue; // implicit mapping
Integer intValue; // implicit mapping
-
- public MyBeanNoExplicitMapping() {}
}
static class MyBeanExplicitMapping
@@ -83,8 +82,6 @@ static class MyBeanExplicitMapping
@Property("booleanValue") boolean b; // explicit mapping
@Property("stringValue") String s; // explicit mapping
@Property("intValue") int i; // explicit mapping
-
- public MyBeanExplicitMapping() {}
}
static class MyBeanHybrid
@@ -93,8 +90,6 @@ static class MyBeanHybrid
@Property String stringValue; // implicit mapping
int intValue; // implicit mapping
double unknownDouble; // invalid property
-
- public MyBeanHybrid() {}
}
static class MyBeanAllFieldsTransient
@@ -102,12 +97,18 @@ static class MyBeanAllFieldsTransient
transient boolean booleanValue; // implicit, but transient
transient String stringValue; // implicit, but transient
transient int intValue; // implicit, but transient
-
- public MyBeanAllFieldsTransient() {}
}
static class MyBeanPrivateConstructor
{
+ boolean booleanValue;
+ String stringValue;
+ int intValue;
+
+ // since this value is assigned during constructor,
+ // it will not happen when using the ObjectFactor.UNSAFE
+ double undefined = -1.0;
+
private MyBeanPrivateConstructor() {}
}
@@ -217,15 +218,37 @@ void apply_beanWithAllFieldsTransient_noDataCopied() throws IOException
}
@Test
- void apply_beanWithPrivateConstructor_configurationException()
+ void apply_beanWithPrivateConstructorAndClassicObjectFactory_configurationException()
{
assertThat(
- () -> new PropertiesToObjectMapper<>(MyBeanPrivateConstructor.class)
+ () -> new PropertiesToObjectMapper<>(MyBeanPrivateConstructor.class, ObjectFactory.CLASSIC)
.apply(newInputStream()),
throwsException(ConfigurationException.class)
.withCause(ReflectiveOperationException.class));
}
+ @Test
+ void apply_beanWithPrivateConstructorAndUnsafedObjectFactory_success() throws IOException
+ {
+ MyBeanPrivateConstructor bean = new PropertiesToObjectMapper<>(
+ MyBeanPrivateConstructor.class, ObjectFactory.UNSAFE).apply(newInputStream());
+ assertThat(bean.booleanValue, equalTo(true));
+ assertThat(bean.stringValue, equalTo("string1"));
+ assertThat(bean.intValue, equalTo(1910));
+ assertThat(bean.undefined, equalTo(0.0)); // Default value assigned
+ }
+
+ @Test
+ void apply_beanWithPrivateConstructorAndObjenesisObjectFactory_success() throws IOException
+ {
+ MyBeanPrivateConstructor bean = new PropertiesToObjectMapper<>(
+ MyBeanPrivateConstructor.class, ObjectFactory.OBJENESIS).apply(newInputStream());
+ assertThat(bean.booleanValue, equalTo(true));
+ assertThat(bean.stringValue, equalTo("string1"));
+ assertThat(bean.intValue, equalTo(1910));
+ assertThat(bean.undefined, equalTo(0.0)); // Default value assigned
+ }
+
@Test
void apply_propertiesWithDate_success() throws IOException
{
diff --git a/confectory-core/src/test/java/net/obvj/confectory/settings/ConfectorySettingsTest.java b/confectory-core/src/test/java/net/obvj/confectory/settings/ConfectorySettingsTest.java
index 8090f97f..d57f3850 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/settings/ConfectorySettingsTest.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/settings/ConfectorySettingsTest.java
@@ -16,7 +16,7 @@
package net.obvj.confectory.settings;
-import static net.obvj.confectory.settings.ConfectorySettings.INITIAL_DATA_FETCH_STRATEGY;
+import static net.obvj.confectory.settings.ConfectorySettings.*;
import static net.obvj.junit.utils.matchers.AdvancedMatchers.throwsException;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -28,6 +28,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import net.obvj.confectory.DataFetchStrategy;
+import net.obvj.confectory.util.ObjectFactory;
/**
* Unit tests for the {@link ConfectorySettings}.
@@ -40,8 +41,10 @@ class ConfectorySettingsTest
{
@Mock
private DataFetchStrategy dataFetchStrategy;
+ @Mock
+ private ObjectFactory objectFactory;
- private ConfectorySettings settings = ConfectorySettings.getInstance();
+ private ConfectorySettings settings = ConfectorySettings.instance();
@AfterEach
void reset()
@@ -50,20 +53,39 @@ void reset()
}
@Test
- void setDefaultDataFetchStrategy_null_exceptionAndNoChangePerformed()
+ void setDataFetchStrategy_null_exceptionAndNoChangePerformed()
+ {
+ assertThat(settings.getDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
+ assertThat(() -> settings.setDataFetchStrategy(null),
+ throwsException(NullPointerException.class)
+ .withMessageContaining("DataFetchStrategy must not be null"));
+ assertThat(settings.getDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
+ }
+
+ @Test
+ void setDataFetchStrategy_valid_success()
+ {
+ assertThat(settings.getDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
+ settings.setDataFetchStrategy(dataFetchStrategy);
+ assertThat(settings.getDataFetchStrategy(), is(dataFetchStrategy));
+ }
+
+ @Test
+ void setDefaultObjectFactory_null_exceptionAndNoChangePerformed()
{
- assertThat(settings.getDefaultDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
- assertThat(() -> settings.setDefaultDataFetchStrategy(null), throwsException(NullPointerException.class)
- .withMessageContaining("DataFetchStrategy must not be null"));
- assertThat(settings.getDefaultDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
+ assertThat(settings.getObjectFactory(), is(INITIAL_OBJECT_FACTORY));
+ assertThat(() -> settings.setObjectFactory(null),
+ throwsException(NullPointerException.class)
+ .withMessageContaining("ObjectFactory must not be null"));
+ assertThat(settings.getObjectFactory(), is(INITIAL_OBJECT_FACTORY));
}
@Test
- void setDefaultDataFetchStrategy_valid_success()
+ void setObjectFactory_valid_success()
{
- assertThat(settings.getDefaultDataFetchStrategy(), is(INITIAL_DATA_FETCH_STRATEGY));
- settings.setDefaultDataFetchStrategy(dataFetchStrategy);
- assertThat(settings.getDefaultDataFetchStrategy(), is(dataFetchStrategy));
+ assertThat(settings.getObjectFactory(), is(INITIAL_OBJECT_FACTORY));
+ settings.setObjectFactory(objectFactory);
+ assertThat(settings.getObjectFactory(), is(objectFactory));
}
}
diff --git a/confectory-core/src/test/java/net/obvj/confectory/testdrive/ConfectoryTestDriveContainerLenient.java b/confectory-core/src/test/java/net/obvj/confectory/testdrive/ConfectoryTestDriveContainerLenient.java
index d4725492..52bf7431 100644
--- a/confectory-core/src/test/java/net/obvj/confectory/testdrive/ConfectoryTestDriveContainerLenient.java
+++ b/confectory-core/src/test/java/net/obvj/confectory/testdrive/ConfectoryTestDriveContainerLenient.java
@@ -29,7 +29,7 @@ public class ConfectoryTestDriveContainerLenient
{
public static void main(String[] args)
{
- ConfectorySettings.getInstance().setDefaultDataFetchStrategy(DataFetchStrategy.LENIENT);
+ ConfectorySettings.instance().setDataFetchStrategy(DataFetchStrategy.LENIENT);
Configuration config1 = Configuration.builder()
.namespace("test")
diff --git a/confectory-core/src/test/java/net/obvj/confectory/util/UnsafeAccessorTest.java b/confectory-core/src/test/java/net/obvj/confectory/util/UnsafeAccessorTest.java
new file mode 100644
index 00000000..bbd7000a
--- /dev/null
+++ b/confectory-core/src/test/java/net/obvj/confectory/util/UnsafeAccessorTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 obvj.net
+ *
+ * Licensed 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 net.obvj.confectory.util;
+
+import static net.obvj.junit.utils.matchers.AdvancedMatchers.instantiationNotAllowed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for the {@link UnsafeAccessor} class.
+ *
+ * @author oswaldo.bapvic.jr
+ * @since 2.5.0
+ */
+class UnsafeAccessorTest
+{
+ @Test
+ void constructor_instantiationNotAllowed()
+ {
+ assertThat(UnsafeAccessor.class,
+ instantiationNotAllowed().throwing(UnsupportedOperationException.class));
+ }
+
+}