Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,12 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @hidden
* @morphia.internal
*/
@MorphiaInternal
public class InstanceCreatorFactoryImpl implements InstanceCreatorFactory {
private static final Logger LOG = LoggerFactory.getLogger(InstanceCreatorFactoryImpl.class);

private final EntityModel model;
private Function<Conversions, MorphiaInstanceCreator> creator;

Expand All @@ -45,15 +40,10 @@ public MorphiaInstanceCreator create(Conversions conversions) {
ConstructorCreator.Plan plan = new ConstructorCreator.Plan(model, constructor);
creator = (c) -> new ConstructorCreator(plan, c);
} else {
LOG.info("using old creator approach: " + model.getType().getName());
try {
Constructor<?> declared = model.getType().getDeclaredConstructor();
creator = (c) -> new NoArgCreator(declared);
} catch (NoSuchMethodException e) {
Constructor<?> full = ConstructorCreator.getFullConstructor(model);
ConstructorCreator.Plan plan = new ConstructorCreator.Plan(model, full);
creator = (c) -> new ConstructorCreator(plan, c);
}
// bestConstructor already picks a no-arg constructor when there is one
Constructor<?> full = ConstructorCreator.getFullConstructor(model);
ConstructorCreator.Plan plan = new ConstructorCreator.Plan(model, full);
creator = (c) -> new ConstructorCreator(plan, c);
}
}

Expand Down
57 changes: 0 additions & 57 deletions core/src/main/java/dev/morphia/mapping/NoArgCreator.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.regex.Pattern;

import com.mongodb.lang.Nullable;

Expand Down Expand Up @@ -39,11 +40,17 @@
*/
@MorphiaInternal
public class ConstructorCreator implements MorphiaInstanceCreator {
private static final Pattern UNNAMED_PARAMETER = Pattern.compile("arg[0-9]+");
private static final Object[] NO_PARAMETERS = new Object[0];

private final Plan plan;
private final Conversions conversions;
private final Object[] parameters;
private final List<PropertyModel> pendingModels = new ArrayList<>();
private final List<Object> pendingValues = new ArrayList<>();
// properties the constructor doesn't take, set once it has run; allocated only if a value arrives before then
@Nullable
private List<PropertyModel> pendingModels;
@Nullable
private List<Object> pendingValues;
private Object instance;

/**
Expand All @@ -63,7 +70,7 @@ public ConstructorCreator(EntityModel model, Constructor<?> constructor, Convers
public ConstructorCreator(Plan plan, Conversions conversions) {
this.plan = plan;
this.conversions = conversions;
this.parameters = plan.zeroValues.clone();
this.parameters = plan.types.length == 0 ? NO_PARAMETERS : plan.zeroValues.clone();
}

/**
Expand Down Expand Up @@ -93,7 +100,7 @@ public Plan(EntityModel model, Constructor<?> constructor) {
types[i] = parameter.getType();
zeroValues[i] = zeroValue(parameter);
String name = getParameterName(parameter);
if (name.matches("arg[0-9]+")) {
if (UNNAMED_PARAMETER.matcher(name).matches()) {
throw new MappingException(Sofia.unnamedConstructorParameter(model.getType().getName()));
}
if (positions.put(name, i) != null) {
Expand Down Expand Up @@ -138,8 +145,10 @@ public Object getInstance() {
if (instance == null) {
try {
instance = plan.constructor.newInstance(parameters);
for (int i = 0; i < pendingModels.size(); i++) {
pendingModels.get(i).setValue(instance, pendingValues.get(i));
if (pendingModels != null) {
for (int i = 0; i < pendingModels.size(); i++) {
pendingModels.get(i).setValue(instance, pendingValues.get(i));
}
}
} catch (Exception e) {
throw new MappingException(Sofia.cannotInstantiate(plan.model.getType().getName(), e.getMessage()), e);
Expand Down Expand Up @@ -193,13 +202,21 @@ private static boolean namesMatchProperties(EntityModel model, Constructor<?> co
public void set(@Nullable Object value, PropertyModel model) {
if (instance != null) {
model.setValue(instance, value);
} else if (plan.types.length == 0) {
// a no-arg constructor needs none of the values, so construct now and set each value as it arrives instead
// of buffering them all
model.setValue(getInstance(), value);
} else {
Integer position = plan.positions.get(model.getName());
if (position != null) {
parameters[position] = conversions.convert(value, plan.types[position]);
} else {
// only properties the constructor doesn't take need setting after construction. re-setting the others would
// overwrite whatever the constructor did with the value and, for final fields, write them reflectively (JEP 500).
if (pendingModels == null) {
pendingModels = new ArrayList<>();
pendingValues = new ArrayList<>();
}
pendingModels.add(model);
pendingValues.add(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ public void testBestConstructor() {
Assertions.assertEquals(0, constructor.getParameterCount());
}

@Test
public void noArgConstructorSetsValuesAsTheyArrive() {
EntityModel model = getMapper().map(Default.class).get(0);
MorphiaInstanceCreator creator = model.getInstanceCreator(getMapper().getConversions());
Assertions.assertTrue(creator.isEagerInstanceSafe());

creator.set("named", model.getProperty("name"));
Default instance = (Default) creator.getInstance();
Assertions.assertEquals("named", instance.name);

// a value that needs converting still goes through Conversions on this path
creator.set(42.0, model.getProperty("count"));
Assertions.assertSame(instance, creator.getInstance());
Assertions.assertEquals(42, instance.count);
}

@Test
public void creatorsDoNotShareState() {
EntityModel model = getMapper().map(Address.class).get(0);
Expand Down
2 changes: 1 addition & 1 deletion design/mapping-game-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ JMH numbers on both mappers.
- Optional: send a known-rejected type straight to the fallback, avoiding a throw and stack trace for each drifted
field.

### 3.2 No-arg creator fast path (#2, A2/B4)
### ~~3.2 No-arg creator fast path (#2, A2/B4)~~
- When the chosen constructor has no parameters, construct the instance first and set values through
`model.setValue`, which keeps `Conversions`. Allocate pending lists lazily.
- Delete the dead `NoArgCreator` branch and its `LOG.info`. Delete `NoArgCreator` itself, which also skips
Expand Down
Loading