Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow object instantiation without calling the default constructor #133

Merged
merged 5 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions confectory-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<json-path.version>2.8.0</json-path.version>
<json-smart.version>2.4.10</json-smart.version>
<performetrics.version>2.4.0</performetrics.version>
<objenesis.version>3.3</objenesis.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -82,6 +83,13 @@
<artifactId>jsonmerge-core</artifactId>
<version>${jsonmerge.version}</version>
</dependency>

<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>${objenesis.version}</version>
<optional>true</optional>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static ConfigurationContainer container()
*/
public static ConfectorySettings settings()
{
return ConfectorySettings.getInstance();
return ConfectorySettings.instance();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public ConfigurationContainer(Configuration<?>... configs)
public ConfigurationContainer(DataFetchStrategy dataFetchStrategy, Configuration<?>... configs)
{
ConfectorySettings settings = Confectory.settings();
setDataFetchStrategy(ObjectUtils.defaultIfNull(dataFetchStrategy, settings.getDefaultDataFetchStrategy()));
setDataFetchStrategy(ObjectUtils.defaultIfNull(dataFetchStrategy, settings.getDataFetchStrategy()));

Arrays.stream(configs).forEach(this::add);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Objects;

import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import net.obvj.confectory.ConfigurationException;
import net.obvj.confectory.internal.helper.BeanConfigurationHelper;
import net.obvj.confectory.internal.helper.ConfigurationHelper;
import net.obvj.confectory.settings.ConfectorySettings;
import net.obvj.confectory.util.*;

/**
Expand Down Expand Up @@ -66,15 +67,31 @@ public class INIToObjectMapper<T> extends AbstractINIMapper<T> implements Mapper
private static final String MSG_UNPARSABLE_PROPERTY_VALUE = "Unable to parse the value of the property %s into a field of type '%s'";

private final Class<T> targetType;
private final ObjectFactory objectFactory;

/**
* Builds a new {@code INIToObjectMapper} with the specified target type.
*
* @param targetType the target type to be produced by this {@code Mapper}
*/
public INIToObjectMapper(Class<T> targetType)
{
this(targetType, ConfectorySettings.instance().getObjectFactory());
}

/**
* Builds a new {@code INIToObjectMapper} with the specified target type and a custom
* object factory.
*
* @param targetType the target type to be produced by this {@code Mapper}
* @param objectFactory the {@link ObjectFactory} to produce objects; not null
* @since 2.5.0
*/
public INIToObjectMapper(Class<T> targetType, ObjectFactory objectFactory)
{
this.targetType = targetType;
this.objectFactory = Objects.requireNonNull(objectFactory,
"the ObjectFactory must not be null");
}

@Override
Expand All @@ -90,7 +107,7 @@ Object newObject(Context context)
Class<?> type = getCurrentType(context);
try
{
return type != null ? ConstructorUtils.invokeConstructor(type) : null;
return type != null ? objectFactory.newObject(type) : null;
}
catch (ReflectiveOperationException exception)
{
Expand All @@ -105,7 +122,7 @@ Object parseValue(Context context, String value)
Field field = PropertyUtils.findFieldByPropertyKeyOrName(currentType, context.currentKey);
try
{
return field != null ? PropertyUtils.parseValue(value, field) : null;
return field != null ? PropertyUtils.parseValue(value, field, objectFactory) : null;
}
catch (ParseException | ReflectiveOperationException exception)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.Properties;

import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import net.obvj.confectory.ConfigurationException;
import net.obvj.confectory.internal.helper.BeanConfigurationHelper;
import net.obvj.confectory.internal.helper.ConfigurationHelper;
import net.obvj.confectory.util.ParseException;
import net.obvj.confectory.util.Property;
import net.obvj.confectory.util.PropertyUtils;
import net.obvj.confectory.util.ReflectionUtils;
import net.obvj.confectory.settings.ConfectorySettings;
import net.obvj.confectory.util.*;

/**
* A specialized {@code Mapper} that loads the contents of a {@code Source} (e.g.: file,
Expand Down Expand Up @@ -67,15 +65,31 @@ public class PropertiesToObjectMapper<T> implements Mapper<T>
private static final String MSG_UNABLE_TO_PARSE_PROPERTY = "Unable to parse the value of the property '%s' into a field of type '%s'";

private final Class<T> targetType;
private final ObjectFactory objectFactory;

/**
* Builds a new Properties Mapper with the specified target type.
*
* @param targetType the target type to be produced by this {@code Mapper}
*/
public PropertiesToObjectMapper(Class<T> targetType)
{
this(targetType, ConfectorySettings.instance().getObjectFactory());
}

/**
* Builds a new Properties Mapper with the specified target type and a custom object
* factory.
*
* @param targetType the target type to be produced by this {@code Mapper}
* @param objectFactory the {@link ObjectFactory} to produce objects; not null
* @since 2.5.0
*/
public PropertiesToObjectMapper(Class<T> targetType, ObjectFactory objectFactory)
{
this.targetType = targetType;
this.objectFactory = Objects.requireNonNull(objectFactory,
"the ObjectFactory must not be null");
}

@Override
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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;
}
Expand All @@ -70,7 +78,7 @@ public static ConfectorySettings getInstance()
*
* @return the default {@link DataFetchStrategy} to be applied
*/
public DataFetchStrategy getDefaultDataFetchStrategy()
public DataFetchStrategy getDataFetchStrategy()
{
return defaultDataFetchStrategy;
}
Expand All @@ -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");
}

}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* It's safer but requires the existence of a public, default constructor available in the
* class to allow the instantiation.
*/
CLASSIC
{
@Override
public <T> T newObject(Class<T> 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.
* <p>
* Final fields are assigned with
* <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">
* default values</a>.
* <p>
* <b>Note:</b> This is the default strategy since 2.5.0.
*/
UNSAFE
{
@Override
@SuppressWarnings("restriction")
public <T> T newObject(Class<T> 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.
* <p>
* <b>IMPORTANT:</b> This strategy requires the <b>optional dependency</b>
* {@code org.objenesis:objenesis} in the class path.
*/
OBJENESIS
{
@Override
public <T> T newObject(Class<T> type)
{
return new ObjenesisStd().newInstance(type);
}
};

/**
* Creates a new instance of the specified class.
*
* @param <T> 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> T newObject(Class<T> type) throws ReflectiveOperationException;

}
Loading