From 53425fc1b687d039e330a7621e82416c5e9e4c46 Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 07:37:20 +0100 Subject: [PATCH 01/12] fix(#841): Relational data --- .../Example_generation__relational_.xml | 10 ++ .../common/output/GeneratedObject.java | 1 + .../common/output}/OutputFormat.java | 2 +- .../output/RelationalGeneratedObject.java | 23 +++ .../common/output/SubGeneratedObject.java | 27 +++ .../generation/DecisionTreeDataGenerator.java | 13 +- .../core/generation/databags/DataBag.java | 7 +- .../relationships/ExtentAugmentedFields.java | 48 +++++ .../GeneratedRelationalData.java | 58 ++++++ .../relationships/OneToManyRange.java | 64 +++++++ .../relationships/OneToManyRangeResolver.java | 147 +++++++++++++++ .../OneToManyRelationshipProcessor.java | 88 +++++++++ .../OneToOneRelationshipProcessor.java | 55 ++++++ .../relationships/RelationshipProcessor.java | 25 +++ .../relationships/RelationshipsProcessor.java | 71 ++++++++ .../generator/core/profile/Profile.java | 21 ++- .../profile/relationships/Relationship.java | 56 ++++++ .../DecisionTreeFactoryTests.java | 31 ++-- .../decisiontree/RowSpecTreeSolverTests.java | 2 +- .../DecisionTreeDataGeneratorTests.java | 18 +- docs/RelationalData.md | 170 ++++++++++++++++++ examples/relational/dependants.profile.json | 10 ++ examples/relational/profile.json | 47 +++++ .../generate/GenerateCommandLine.java | 4 +- .../utils/CucumberGenerationConfigSource.java | 2 +- .../utils/CucumberTestState.java | 2 +- .../endtoend/JarExecuteTests.java | 62 ++++++- .../profile-referenced-relationship.json | 36 ++++ .../orchestrator/relational/profile.json | 47 +++++ .../subfolder/dependants.profile.json | 28 +++ .../relational/subfolder/testfile.csv | 1 + .../validator/ProfileValidationTests.java | 5 +- .../output/guice/OutputConfigSource.java | 2 + .../generator/output/guice/OutputModule.java | 5 + .../output/writer/json/JsonDataSetWriter.java | 59 +++++- .../profile/commands/CreateProfile.java | 6 +- .../profile/commands/ReadRelationships.java | 37 ++++ .../profile/dtos/RelationalProfileDTO.java | 23 +++ .../profile/dtos/RelationshipDTO.java | 29 +++ .../profile/guice/ProfileModule.java | 3 + .../handlers/CreateProfileHandler.java | 23 ++- .../handlers/ReadRelationshipsHandler.java | 44 +++++ .../profile/reader/JsonProfileReader.java | 6 +- .../profile/reader/ProfileCommandBus.java | 20 ++- .../serialisation/ProfileDeserialiser.java | 8 +- .../profile/services/RelationshipService.java | 118 ++++++++++++ .../ReadRelationshipsValidator.java | 29 +++ .../reader/JsonProfileReaderTests.java | 7 +- 48 files changed, 1527 insertions(+), 73 deletions(-) create mode 100644 .idea/runConfigurations/Example_generation__relational_.xml rename {output/src/main/java/com/scottlogic/datahelix/generator/output/guice => common/src/main/java/com/scottlogic/datahelix/generator/common/output}/OutputFormat.java (91%) create mode 100644 common/src/main/java/com/scottlogic/datahelix/generator/common/output/RelationalGeneratedObject.java create mode 100644 common/src/main/java/com/scottlogic/datahelix/generator/common/output/SubGeneratedObject.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/GeneratedRelationalData.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRelationshipProcessor.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToOneRelationshipProcessor.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipProcessor.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java create mode 100644 core/src/main/java/com/scottlogic/datahelix/generator/core/profile/relationships/Relationship.java create mode 100644 docs/RelationalData.md create mode 100644 examples/relational/dependants.profile.json create mode 100644 examples/relational/profile.json create mode 100644 orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile-referenced-relationship.json create mode 100644 orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile.json create mode 100644 orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/dependants.profile.json create mode 100644 orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/testfile.csv create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/ReadRelationships.java create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationalProfileDTO.java create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationshipDTO.java create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/ReadRelationshipsHandler.java create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/RelationshipService.java create mode 100644 profile/src/main/java/com/scottlogic/datahelix/generator/profile/validators/ReadRelationshipsValidator.java diff --git a/.idea/runConfigurations/Example_generation__relational_.xml b/.idea/runConfigurations/Example_generation__relational_.xml new file mode 100644 index 000000000..ff3da57cc --- /dev/null +++ b/.idea/runConfigurations/Example_generation__relational_.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/common/src/main/java/com/scottlogic/datahelix/generator/common/output/GeneratedObject.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/GeneratedObject.java index 32105f4a3..770a3f4d7 100644 --- a/common/src/main/java/com/scottlogic/datahelix/generator/common/output/GeneratedObject.java +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/GeneratedObject.java @@ -21,4 +21,5 @@ /** A set of values representing one complete, discrete output (eg, this could be used to make a full CSV row) */ public interface GeneratedObject { Object getFormattedValue(Field field); + Object getValue(Field field); } diff --git a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputFormat.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/OutputFormat.java similarity index 91% rename from output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputFormat.java rename to common/src/main/java/com/scottlogic/datahelix/generator/common/output/OutputFormat.java index a62d4e305..b312e7055 100644 --- a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputFormat.java +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/OutputFormat.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.scottlogic.datahelix.generator.output.guice; +package com.scottlogic.datahelix.generator.common.output; public enum OutputFormat { CSV, diff --git a/common/src/main/java/com/scottlogic/datahelix/generator/common/output/RelationalGeneratedObject.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/RelationalGeneratedObject.java new file mode 100644 index 000000000..5c954b21d --- /dev/null +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/RelationalGeneratedObject.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.common.output; + +import java.util.Map; + +public interface RelationalGeneratedObject { + Map getSubObjects(); +} \ No newline at end of file diff --git a/common/src/main/java/com/scottlogic/datahelix/generator/common/output/SubGeneratedObject.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/SubGeneratedObject.java new file mode 100644 index 000000000..587903d50 --- /dev/null +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/output/SubGeneratedObject.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.common.output; + +import com.scottlogic.datahelix.generator.common.profile.Field; + +import java.util.List; + +public interface SubGeneratedObject { + List getFields(); + List getData(); + boolean isArray(); +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java index 987096c41..049b620cc 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java @@ -25,6 +25,7 @@ import com.scottlogic.datahelix.generator.core.decisiontree.treepartitioning.TreePartitioner; import com.scottlogic.datahelix.generator.core.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.datahelix.generator.core.generation.databags.DataBag; +import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsProcessor; import com.scottlogic.datahelix.generator.core.generation.visualiser.Visualiser; import com.scottlogic.datahelix.generator.core.generation.visualiser.VisualiserFactory; import com.scottlogic.datahelix.generator.core.profile.Profile; @@ -44,6 +45,7 @@ public class DecisionTreeDataGenerator implements DataGenerator { private final CombinationStrategy partitionCombiner; private final UpfrontTreePruner upfrontTreePruner; private final VisualiserFactory visualiserFactory; + private final RelationshipsProcessor relationshipsProcessor; @Inject public DecisionTreeDataGenerator( @@ -54,7 +56,8 @@ public DecisionTreeDataGenerator( DataGeneratorMonitor monitor, CombinationStrategy combinationStrategy, UpfrontTreePruner upfrontTreePruner, - VisualiserFactory visualiserFactory) { + VisualiserFactory visualiserFactory, + RelationshipsProcessor relationshipsProcessor) { this.decisionTreeGenerator = decisionTreeGenerator; this.treePartitioner = treePartitioner; this.treeOptimiser = optimiser; @@ -63,6 +66,7 @@ public DecisionTreeDataGenerator( this.partitionCombiner = combinationStrategy; this.upfrontTreePruner = upfrontTreePruner; this.visualiserFactory = visualiserFactory; + this.relationshipsProcessor = relationshipsProcessor; } @Override @@ -81,9 +85,12 @@ public Stream generateData(Profile profile) { .map(treeOptimiser::optimiseTree) .map(tree -> () -> treeWalker.walk(tree)); - //noinspection RedundantCast return partitionCombiner.permute(partitionedDataBags) - .map(d-> (GeneratedObject)d); + .map(generatedObject -> relationshipsProcessor.produceRelationalObjects( + profile.getFields(), + generatedObject, + profile.getRelationships(), + this)); } private void visualiseTree(DecisionTree decisionTree, String title) { diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/databags/DataBag.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/databags/DataBag.java index c9ace4aa1..4049268e5 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/databags/DataBag.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/databags/DataBag.java @@ -34,9 +34,14 @@ public DataBag(Map fieldToValue) { this.fieldToValue = fieldToValue; } + @Override + public Object getValue(Field field) { + return getDataBagValue(field).getValue(); + } + @Override public Object getFormattedValue(Field field) { - Object value = getDataBagValue(field).getValue(); + Object value = getValue(field); String formatting = field.getFormatting(); if (formatting == null || value == null) { diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java new file mode 100644 index 000000000..b5e99fc1d --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.common.profile.FieldType; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.SpecificFieldType; + +public class ExtentAugmentedFields extends Fields { + private static final SpecificFieldType integer = new SpecificFieldType("integer", FieldType.NUMERIC, null); + + public static final String minField = "min"; + public static final String maxField = "max"; + public static final Field min = new Field(minField, integer, false, null, false, true, null); + public static final Field max = new Field(maxField, integer, false, null, false, true, null); + + public ExtentAugmentedFields(Fields fields) { + super(fields.asList()); + } + + @Override + public Field getByName(String fieldName) { + if (fieldName.equals(minField)) { + return min; + } + + if (fieldName.equals(maxField)) { + return max; + } + + return super.getByName(fieldName); + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/GeneratedRelationalData.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/GeneratedRelationalData.java new file mode 100644 index 000000000..ec8e1792b --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/GeneratedRelationalData.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.output.RelationalGeneratedObject; +import com.scottlogic.datahelix.generator.common.output.SubGeneratedObject; +import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; + +import java.util.HashMap; +import java.util.Map; + +public class GeneratedRelationalData implements GeneratedObject, RelationalGeneratedObject { + private final GeneratedObject underlyingObject; + private final Map subObjects = new HashMap<>(); + + public GeneratedRelationalData(GeneratedObject underlyingObject) { + this.underlyingObject = underlyingObject; + } + + @Override + public Object getFormattedValue(Field field) { + return underlyingObject.getFormattedValue(field); + } + + @Override + public Object getValue(Field field) { + return underlyingObject.getValue(field); + } + + @Override + public Map getSubObjects() { + return subObjects; + } + + public void addSubObject(Relationship relationship, SubGeneratedObject subObject) { + if (!subObjects.containsKey(relationship.getName())) { + subObjects.put(relationship.getName(), subObject); + } else { + throw new RuntimeException("Sub-object for this relationship already exists"); + } + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java new file mode 100644 index 000000000..4e4e0f929 --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +public class OneToManyRange { + private final int min; + private final Integer max; + + public OneToManyRange(int min, Integer max) { + this.min = min; + this.max = max; + } + + public int getMin() { + return min; + } + + public Integer getMax() { + return max; + } + + public OneToManyRange withMin(int min) { + if (min > this.min) { + return new OneToManyRange(min, max); + } + + return this; + } + + public OneToManyRange withMax(int max) { + if (this.max == null || max < this.max) { + return new OneToManyRange(min, max); + } + + return this; + } + + public boolean isEmpty() { + return this.max != null && this.min >= this.max; + } + + @Override + public String toString() { + if (max == null) { + return min + ".."; + } + + return min + ".." + max; + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java new file mode 100644 index 000000000..83ed5425d --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.google.inject.Inject; +import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNode; +import com.scottlogic.datahelix.generator.core.decisiontree.DecisionTree; +import com.scottlogic.datahelix.generator.core.decisiontree.DecisionTreeFactory; +import com.scottlogic.datahelix.generator.core.generation.databags.DataBagValue; +import com.scottlogic.datahelix.generator.core.profile.Profile; +import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; +import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.AtomicConstraint; +import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.EqualToConstraint; +import com.scottlogic.datahelix.generator.core.walker.pruner.Merged; +import com.scottlogic.datahelix.generator.core.walker.pruner.TreePruner; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +public class OneToManyRangeResolver { + private final DecisionTreeFactory factory; + private final TreePruner treePruner; + + private static final Field min = ExtentAugmentedFields.min; + private static final Field max = ExtentAugmentedFields.max; + + @Inject + public OneToManyRangeResolver( + DecisionTreeFactory factory, + TreePruner treePruner) { + this.factory = factory; + this.treePruner = treePruner; + } + + public OneToManyRange getRange(Fields profileFields, Collection constraints, GeneratedObject generatedObject) { + OneToManyRange range = new OneToManyRange(0, null); + + DecisionTree tree = factory.analyse(new Profile( + getFields(profileFields), + new ArrayList<>(constraints), + Collections.emptyList()) + ); + + //apply each value of generatedObject to the tree + ConstraintNode rootNode = applyGeneratedData(profileFields, tree.getRootNode(), generatedObject); + + if (rootNode == null) { + throw new RuntimeException("There are conditions that result in an unresolved extent"); + } + + if (!rootNode.getDecisions().isEmpty()) { + throw new RuntimeException("There are conditional constraints that haven't been resolved"); + } + + //read the root-level properties for and if there are any decisions + range = rootNode.getAtomicConstraints() + .stream() + .filter(ac -> ac.getField().equals(min) || ac.getField().equals(max)) + .reduce( + range, + OneToManyRangeResolver::applyAtomicConstraint, + (a, b) -> null); + + return range; + } + + private static OneToManyRange applyAtomicConstraint(OneToManyRange range, AtomicConstraint atomicConstraint) { + Field field = atomicConstraint.getField(); + + if (field.equals(min)) { + return range.withMin(getExtent(atomicConstraint)); + } else if (field.equals(max)) { + return range.withMax(getExtent(atomicConstraint)); + } + + return range; + } + + private static int getExtent(AtomicConstraint atomicConstraint) { + if (atomicConstraint instanceof EqualToConstraint) { + return getIntegerValue(((EqualToConstraint) atomicConstraint).value); + } + + throw new RuntimeException("Unsure how to extract an extent from a " + atomicConstraint.getClass().getName()); + } + + private static int getIntegerValue(Object value) { + if (value instanceof Integer){ + return (int) value; + } + + if (value instanceof BigDecimal){ + return ((BigDecimal) value).intValue(); + } + + throw new RuntimeException("Unable to extract an integer value from a " + value.getClass().getName()); + } + + private ConstraintNode applyGeneratedData(Fields profileFields, ConstraintNode rootNode, GeneratedObject generatedObject) { + return profileFields.stream().reduce( + rootNode, + (nextRootNode, field) -> { + if (nextRootNode == null) { + return null; + } + + Object value = generatedObject.getValue(field); + Merged constraintNodeMerged = treePruner.pruneConstraintNode(nextRootNode, field, new DataBagValue(value)); + if (constraintNodeMerged.isContradictory()){ + return null; + } + + return constraintNodeMerged.get(); + }, + (a, b) -> null + ); + } + + private List getFields(Fields profileFields) { + return Stream.concat( + profileFields.asList().stream(), + Stream.of(min, max)) + .collect(Collectors.toList()); + + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRelationshipProcessor.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRelationshipProcessor.java new file mode 100644 index 000000000..8107907fb --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRelationshipProcessor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.google.inject.Inject; +import com.scottlogic.datahelix.generator.common.RandomNumberGenerator; +import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.output.SubGeneratedObject; +import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.generation.DataGenerator; +import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; +import com.scottlogic.datahelix.generator.core.utils.JavaUtilRandomNumberGenerator; + +import java.util.List; +import java.util.stream.Collectors; + +public class OneToManyRelationshipProcessor implements RelationshipProcessor { + private final RandomNumberGenerator randomNumberGenerator; + private final OneToManyRangeResolver rangeResolver; + + @Inject + public OneToManyRelationshipProcessor( + JavaUtilRandomNumberGenerator randomNumberGenerator, + OneToManyRangeResolver rangeResolver) { + this.randomNumberGenerator = randomNumberGenerator; + this.rangeResolver = rangeResolver; + } + + @Override + public void processRelationship(Fields profileFields, Relationship relationship, GeneratedRelationalData generatedObject, DataGenerator dataGenerator) { + List extents = relationship.getExtents(); + OneToManyRange range = this.rangeResolver.getRange(profileFields, extents, generatedObject); + + if (range.isEmpty()) { + return; + } + + int numberOfObjects = getNumberOfObjectsToProduce(range.getMin(), range.getMax()); + + generatedObject.addSubObject(relationship, new SubGeneratedObject() { + @Override + public List getFields() { + return relationship.getProfile().getFields().asList(); + } + + @Override + public List getData() { + return dataGenerator.generateData(relationship.getProfile()) + .limit(numberOfObjects) + .collect(Collectors.toList()); + } + + @Override + public boolean isArray() { + return true; + } + }); + } + + private Integer getNumberOfObjectsToProduce(int min, Integer max) { + if (max == null) { + throw new RuntimeException("Unable to produce sub-objects, range is unbounded"); + } + + int range = max - min; + if (range == 0){ + return min; + } + + return randomNumberGenerator.nextInt(range + 1) + min; + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToOneRelationshipProcessor.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToOneRelationshipProcessor.java new file mode 100644 index 000000000..d921f0b69 --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToOneRelationshipProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.output.SubGeneratedObject; +import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.generation.DataGenerator; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class OneToOneRelationshipProcessor implements RelationshipProcessor { + @Override + public void processRelationship(Fields profileFields, Relationship relationship, GeneratedRelationalData generatedObject, DataGenerator dataGenerator) { + Optional subObject = dataGenerator.generateData(relationship.getProfile()).limit(1).findFirst(); + if (!subObject.isPresent()) { + return; + } + + generatedObject.addSubObject(relationship, new SubGeneratedObject() { + @Override + public List getFields() { + return relationship.getProfile().getFields().asList(); + } + + @Override + public List getData() { + return Collections.singletonList(subObject.get()); + } + + @Override + public boolean isArray() { + return false; + } + }); + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipProcessor.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipProcessor.java new file mode 100644 index 000000000..30598ca83 --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipProcessor.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.generation.DataGenerator; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; + +interface RelationshipProcessor { + void processRelationship(Fields profileFields, Relationship relationship, GeneratedRelationalData generatedObject, DataGenerator dataGenerator); +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java new file mode 100644 index 000000000..ba9c56293 --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.output.OutputFormat; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.generation.DataGenerator; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; + +import java.util.Collection; + +public class RelationshipsProcessor { + private final OutputFormat outputFormat; + private final RelationshipProcessor oneToOne; + private final RelationshipProcessor oneToMany; + + @Inject + public RelationshipsProcessor( + OutputFormat outputFormat, + OneToOneRelationshipProcessor oneToOne, + OneToManyRelationshipProcessor oneToMany) { + this.outputFormat = outputFormat; + this.oneToOne = oneToOne; + this.oneToMany = oneToMany; + } + + public GeneratedObject produceRelationalObjects(Fields profileFields, GeneratedObject generatedObject, Collection relationships, DataGenerator dataGenerator) { + if (relationships == null || relationships.isEmpty()){ + return generatedObject; + } + + if (outputFormat != OutputFormat.JSON) { + throw new RuntimeException("Unable to produce relational data except in JSON format, please supply the --output-format=JSON command line argument"); + } + + GeneratedRelationalData generatedRelationalData = new GeneratedRelationalData(generatedObject); + + relationships.forEach(relationship -> processRelationship(profileFields, relationship, generatedRelationalData, dataGenerator)); + + return generatedRelationalData; + } + + private void processRelationship(Fields profileFields, Relationship relationship, GeneratedRelationalData generatedObject, DataGenerator dataGenerator) { + if (relationship.getProfile() == null) { + throw new RuntimeException("Profile must be supplied for the relationship"); + } + + if (relationship.getExtents().isEmpty()) { + oneToOne.processRelationship(profileFields, relationship, generatedObject, dataGenerator); + } else { + oneToMany.processRelationship(profileFields, relationship, generatedObject, dataGenerator); + } + } +} diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java index 7715b52f1..60f616d8e 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java @@ -19,6 +19,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; import java.util.Collection; import java.util.List; @@ -27,23 +28,25 @@ public class Profile { private final Fields fields; private final Collection constraints; private final String description; + private final Collection relationships; - public Profile(List fields, Collection constraints) { - this(null, new Fields(fields), constraints); + public Profile(List fields, Collection constraints, Collection relationships) { + this(null, new Fields(fields), constraints, relationships); } - public Profile(List fields, Collection constraints, String description) { - this(description, new Fields(fields), constraints); + public Profile(List fields, Collection constraints, Collection relationships, String description) { + this(description, new Fields(fields), constraints, relationships); } - public Profile(Fields fields, Collection constraints) { - this(null, fields, constraints); + public Profile(Fields fields, Collection constraints, Collection relationships) { + this(null, fields, constraints, relationships); } - public Profile(String description, Fields fields, Collection constraints) { + public Profile(String description, Fields fields, Collection constraints, Collection relationships) { this.fields = fields; this.constraints = constraints; this.description = description; + this.relationships = relationships; } public Fields getFields() { @@ -57,4 +60,8 @@ public Collection getConstraints() { public String getDescription() { return description; } + + public Collection getRelationships() { + return relationships; + } } diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/relationships/Relationship.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/relationships/Relationship.java new file mode 100644 index 000000000..1707abc69 --- /dev/null +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/relationships/Relationship.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.profile.relationships; + +import com.scottlogic.datahelix.generator.core.profile.Profile; +import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; + +import java.util.List; + +public class Relationship { + private final String name; + private final String description; + private final Profile profile; + private final List extents; + + public Relationship( + String name, + String description, + Profile profile, + List extents) { + this.name = name; + this.description = description; + this.profile = profile; + this.extents = extents; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Profile getProfile() { + return profile; + } + + public List getExtents() { + return extents; + } +} diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java index f40d02e42..b2e7d5f68 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java @@ -75,7 +75,8 @@ private DecisionTree getActualOutput() { Profile testInput = new Profile( new Fields( Arrays.asList(this.fieldA, this.fieldB, this.fieldC)), - this.constraints); + this.constraints, + Collections.emptyList()); DecisionTreeFactory testObject = new DecisionTreeFactory(); @@ -91,7 +92,7 @@ private ConstraintNode getResultingRootOption() { @Test void shouldReturnAnalysedProfileWithNoAnalysedRules_IfProfileHasNoRules() { - Profile testInput = new Profile(new ArrayList<>(), new ArrayList<>()); + Profile testInput = new Profile(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree testOutput = testObject.analyse(testInput); @@ -106,7 +107,7 @@ void shouldReturnAnalysedProfileWithNoAnalysedRules_IfProfileHasNoRules() { @Test void shouldReturnAnalysedProfileWithCorrectFields() { List inputFieldList = Arrays.asList(createField("one"), createField("two"), createField("three")); - Profile testInput = new Profile(inputFieldList, new ArrayList<>()); + Profile testInput = new Profile(inputFieldList, new ArrayList<>(), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree testOutput = testObject.analyse(testInput); @@ -124,7 +125,7 @@ void shouldReturnAnalysedRuleWithNoDecisions_IfProfileContainsOnlyAtomicConstrai new DistributedList<>(Collections.singletonList(new WeightedElement<>(10, 1.0F)))); GreaterThanConstraint constraint1 = new GreaterThanConstraint(inputFieldList.get(0), NumberUtils.coerceToBigDecimal(0)); MatchesRegexConstraint constraint2 = new MatchesRegexConstraint(inputFieldList.get(1), Pattern.compile("start.*end")); - Profile testInput = new Profile(inputFieldList, Arrays.asList(constraint0, constraint1, constraint2)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(constraint0, constraint1, constraint2), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); @@ -146,7 +147,7 @@ void shouldReturnAnalysedRuleWithAllConstraintsInAtomicConstraintsCollection_IfP GreaterThanConstraint constraint1 = new GreaterThanConstraint(inputFieldList.get(0), NumberUtils.coerceToBigDecimal(0)); MatchesRegexConstraint constraint2 = new MatchesRegexConstraint(inputFieldList.get(1), Pattern.compile("start.*end")); List inputConstraints = Arrays.asList(constraint0, constraint1, constraint2); - Profile testInput = new Profile(inputFieldList, inputConstraints); + Profile testInput = new Profile(inputFieldList, inputConstraints, new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -168,7 +169,7 @@ void shouldReturnAnalysedRuleWithNoDecisions_IfProfileContainsOnlyAtomicConstrai GreaterThanConstraint constraint1 = new GreaterThanConstraint(inputFieldList.get(0), NumberUtils.coerceToBigDecimal(0)); AndConstraint andConstraint0 = new AndConstraint(Arrays.asList(constraint0, constraint1)); MatchesRegexConstraint constraint2 = new MatchesRegexConstraint(inputFieldList.get(1), Pattern.compile("start.*end")); - Profile testInput = new Profile(inputFieldList, Arrays.asList(andConstraint0, constraint2)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(andConstraint0, constraint2), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -187,7 +188,7 @@ void shouldReturnAnalysedRuleWithAllAtomicConstraintsInAtomicConstraintsCollecti GreaterThanConstraint constraint1 = new GreaterThanConstraint(inputFieldList.get(0), NumberUtils.coerceToBigDecimal(0)); AndConstraint andConstraint0 = new AndConstraint(Arrays.asList(constraint0, constraint1)); MatchesRegexConstraint constraint2 = new MatchesRegexConstraint(inputFieldList.get(1), Pattern.compile("start.*end")); - Profile testInput = new Profile(inputFieldList, Arrays.asList(andConstraint0, constraint2)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(andConstraint0, constraint2), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); @@ -217,7 +218,7 @@ void shouldReturnAnalysedRuleWithDecisionForEachOrConstraint() { inputFieldList.get(1), new DistributedList<>(Collections.singletonList(new WeightedElement<>("diesel", 1.0F)))); OrConstraint orConstraint1 = new OrConstraint(Arrays.asList(constraint2, constraint3)); - Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -241,7 +242,7 @@ void shouldReturnAnalysedRuleWithNoAtomicConstraints_IfAllAtomicConstraintsInPro inputFieldList.get(1), new DistributedList<>(Collections.singletonList(new WeightedElement<>("diesel", 1.0F)))); OrConstraint orConstraint1 = new OrConstraint(Arrays.asList(constraintC, constraintD)); - Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -265,7 +266,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfAllAtomicConstraints inputFieldList.get(1), new DistributedList<>(Collections.singletonList(new WeightedElement<>("diesel", 1.0F)))); OrConstraint orConstraint1 = new OrConstraint(Arrays.asList(constraintC, constraintD)); - Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -302,7 +303,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfAllAtomicConstraints inputFieldList.get(1), new DistributedList<>(Collections.singletonList(new WeightedElement<>("diesel", 1.0F)))); OrConstraint orConstraint1 = new OrConstraint(Arrays.asList(constraintD, constraintE)); - Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1)); + Profile testInput = new Profile(inputFieldList, Arrays.asList(orConstraint0, orConstraint1), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -331,7 +332,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfConditionalConstrain GreaterThanConstraint constraintB = new GreaterThanConstraint(inputFieldList.get(1), NumberUtils.coerceToBigDecimal(10)); GreaterThanConstraint constraintC = new GreaterThanConstraint(inputFieldList.get(1), NumberUtils.coerceToBigDecimal(20)); ConditionalConstraint conditionalConstraint = new ConditionalConstraint(constraintA, constraintB, constraintC); - Profile testInput = new Profile(inputFieldList, Collections.singletonList(conditionalConstraint)); + Profile testInput = new Profile(inputFieldList, Collections.singletonList(conditionalConstraint), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -393,7 +394,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfNegatedConditionalCo GreaterThanConstraint constraintC = new GreaterThanConstraint(inputFieldList.get(1), NumberUtils.coerceToBigDecimal(10)); ConditionalConstraint conditionalConstraint = new ConditionalConstraint(constraintA, constraintB, constraintC); Constraint notConstraint = conditionalConstraint.negate(); - Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint)); + Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -446,7 +447,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfDoubleNegationIsPres InSetConstraint constraintA = new InSetConstraint(inputFieldList.get(0), new DistributedList<>(Collections.singletonList(new WeightedElement<>(10, 1.0F)))); Constraint notConstraint0 = constraintA.negate(); Constraint notConstraint1 = notConstraint0.negate(); - Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint1)); + Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint1), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); @@ -468,7 +469,7 @@ void shouldReturnAnalysedRuleWithCorrectDecisionStructure_IfNegatedAndIsPresent( InSetConstraint constraintA = new InSetConstraint(inputFieldList.get(0), new DistributedList<>(Collections.singletonList(new WeightedElement<>(10, 1.0F)))); GreaterThanConstraint constraintB = new GreaterThanConstraint(inputFieldList.get(1), NumberUtils.coerceToBigDecimal(5)); NegatedGrammaticalConstraint notConstraint = (NegatedGrammaticalConstraint) new AndConstraint(Arrays.asList(constraintA, constraintB)).negate(); - Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint)); + Profile testInput = new Profile(inputFieldList, Collections.singletonList(notConstraint), new ArrayList<>()); DecisionTreeFactory testObject = new DecisionTreeFactory(); DecisionTree outputRule = testObject.analyse(testInput); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java index 1767adb3d..631a50ca3 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java @@ -129,7 +129,7 @@ void test() new DistributedList<>(Collections.singletonList(new WeightedElement<>("GBP", 1.0F))) ))); - Profile profile = new Profile(fields, constraints); + Profile profile = new Profile(fields, constraints, new ArrayList<>()); final DecisionTree merged = this.dTreeGenerator.analyse(profile); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java index 5aa137c7f..625b5c855 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java @@ -17,7 +17,6 @@ package com.scottlogic.datahelix.generator.core.generation; import com.scottlogic.datahelix.generator.common.output.GeneratedObject; -import com.scottlogic.datahelix.generator.core.profile.Profile; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNode; import com.scottlogic.datahelix.generator.core.decisiontree.DecisionTree; import com.scottlogic.datahelix.generator.core.decisiontree.DecisionTreeFactory; @@ -25,15 +24,16 @@ import com.scottlogic.datahelix.generator.core.decisiontree.treepartitioning.TreePartitioner; import com.scottlogic.datahelix.generator.core.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.datahelix.generator.core.generation.databags.DataBag; +import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsProcessor; import com.scottlogic.datahelix.generator.core.generation.visualiser.Visualiser; import com.scottlogic.datahelix.generator.core.generation.visualiser.VisualiserFactory; +import com.scottlogic.datahelix.generator.core.profile.Profile; import com.scottlogic.datahelix.generator.core.walker.DecisionTreeWalker; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import java.io.IOException; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,32 +46,30 @@ class DecisionTreeDataGeneratorTests { private DecisionTreeDataGenerator generator; private DecisionTreeFactory factory; - private DataGeneratorMonitor monitor; private TreePartitioner treePartitioner; private CombinationStrategy combinationStrategy; private DecisionTreeOptimiser optimiser; - private DecisionTreeWalker treeWalker; private UpfrontTreePruner upfrontTreePruner; private VisualiserFactory visualiserFactory; + @BeforeEach void setup() { factory = Mockito.mock(DecisionTreeFactory.class); - treeWalker = Mockito.mock(DecisionTreeWalker.class); treePartitioner = Mockito.mock(TreePartitioner.class); optimiser = Mockito.mock(DecisionTreeOptimiser.class); - monitor = Mockito.mock(DataGeneratorMonitor.class); combinationStrategy = Mockito.mock(CombinationStrategy.class); upfrontTreePruner = Mockito.mock(UpfrontTreePruner.class); visualiserFactory = Mockito.mock(VisualiserFactory.class); generator = new DecisionTreeDataGenerator( factory, - treeWalker, + Mockito.mock(DecisionTreeWalker.class), treePartitioner, optimiser, - monitor, + Mockito.mock(DataGeneratorMonitor.class), combinationStrategy, upfrontTreePruner, - visualiserFactory + visualiserFactory, + Mockito.mock(RelationshipsProcessor.class) ); } @@ -82,7 +80,7 @@ class upfrontContradictionChecking { private Profile profile; private Visualiser visualiser; @BeforeEach - void setup() throws IOException { + void setup() { tree = Mockito.mock(DecisionTree.class); rootNode = Mockito.mock(ConstraintNode.class); profile = Mockito.mock(Profile.class); diff --git a/docs/RelationalData.md b/docs/RelationalData.md new file mode 100644 index 000000000..424d039d2 --- /dev/null +++ b/docs/RelationalData.md @@ -0,0 +1,170 @@ +## Beta Feature - Relational data + +**Relational data** can only be produced when the output format is JSON. Output to console (streaming) and output to file are both supported. An attempt to produce CSV data where there is a relationship will result in an error. Profiles without relationships can still produce CSV data. + +This feature introduces the following **additional functionality**. The changes are fully backward compatible. A new property can be added to the profile called `relationships`, this is a collection of 'relationship descriptions'. + +For example + +``` +{ + "fields": [ ... ], + "constraints": [ ... ], + "relationships": [ + ... + ] +} +``` + +A relationship represents additional data that should be included in the output data as a sub-object (one-to-one) or sub-array (one-to-many). Each relationship is considered to be one-to-one, unless extents (the number of sub-objects to produce) are included. + +## Relationship description +There can be any number of relationships within each profile. Sub profiles can also contain relationships. + +A relationship description can have the following properties + +| property | type | requirement | description | +| ---- | ---- | ---- | ---- | +| `name` | string | required | The name of the relationship, this is the name of the property in which the related data will be embedded | +| `description` | string | optional | A description for the relationship, this is for documentation only - it has no bearing on the generation process | +| `profileFile` | string | optional* | A relative path to a profile file, that describes the data that should be generated within this relationship | +| `profile` | object | optional* | that describes the data that should be generated within this relationship | +| `extents` | array of `extents` | optional | the min and max number of records within a one-to-many relationship, if absent will be treated as a one-to-one relationship | + +For example: +``` +{ + ... + "relationships": [ + { + "name": "name", + "profileFile": "profile-filename", + "extents": [ + { "field": "min", "equalTo": 1 }, + { "field": "max", "equalTo": 3 } + ], + } + ] +} +``` + +\* One of `profile` or `profileFile` must be supplied, `profile` takes precedence over `profileFile`. + +**NOTE**: If `profileFile` is used, it is possible to create a circular/recursive 'dependency' on profiles. No current strategy has been implemented to prevent/detect this, however it should be simple to integrate. + +An `extent` is a description of the limit on the number of rows that must be produced, it is described as a regular - numeric - constraint, but for a virtual field called `min` or `max`, as in the example above. Any constraint that results in a integer value can be used. + +**If any extents are supplied** then a constraint for the `max` extent must be supplied. The `min` extent is 0 by default, but can be overridden with any >= 0 value. Conditional constraints are supported. + +For one-to-many relationships a random number of records will be produced between `min` and `max`. Other strategies can be introduced in the fullness of time, this is the only approach at present. + +## Example profile: + +``` +{ + "fields": [ + { + "name": "shortName", + "type": "faker.name.firstName", + "nullable": false + } + ], + "constraints": [ + { + "field": "shortName", + "shorterThan": 6 + }, + { + "field": "shortName", + "matchingRegex": "J.*" + } + ], + "relationships": + [ + { + "name": "dependants", + "description": "presence of min/max indicates that it is a collection (one-to-many)", + "profileFile": "dependants.profile.json", + "extents": [ + { "field": "min", "equalTo": 0 }, + { + "if": { "field": "shortName", "matchingRegex": "J[ae].*" }, + "then": { "field": "min", "equalTo": 2 }, + "else": { "field": "max", "equalTo": 3 } + }, + { "field": "max", "equalTo": 3 }, + { + "if": { "field": "shortName", "matchingRegex": "J[iou].*" }, + "then": { "field": "max", "equalTo": 1 } + } + ] + }, + { + "name": "mother", + "description": "absence of min/max indicates that it is a sub-object (one-to-one)", + "profile": { + "fields": [ + { + "name": "name", + "type": "faker.name.firstName", + "nullable": false + }, + { + "name": "age", + "type": "integer", + "nullable": false + } + ], + "constraints": [ + { + "field": "age", + "greaterThanOrEqualTo": 16 + }, + { + "field": "age", + "lessThanOrEqualTo": 100 + } + ] + } + } + ] +} +``` + +This will produce data that looks like below: + +``` +{"mother":{"age":52,"name":"Vivien"},"shortName":"Jeri","dependants":[{"age":9,"name":"Ferne"},{"age":4,"name":"Eleonor"},{"age":8,"name":"Virgilio"}]} +{"mother":{"age":61,"name":"Dante"},"shortName":"James","dependants":[{"age":0,"name":"Elli"},{"age":2,"name":"Jewel"}]} +{"mother":{"age":24,"name":"Lana"},"shortName":"Jame","dependants":[{"age":3,"name":"Long"},{"age":9,"name":"Vergie"}]} +{"mother":{"age":67,"name":"Angelica"},"shortName":"Jan","dependants":[{"age":14,"name":"Wyatt"},{"age":3,"name":"Charline"}]} +{"mother":{"age":17,"name":"Hal"},"shortName":"Jamel","dependants":[{"age":5,"name":"Aurora"},{"age":3,"name":"Elisa"}]} +{"mother":{"age":38,"name":"Connie"},"shortName":"John","dependants":[{"age":14,"name":"Antonio"}]} +{"mother":{"age":88,"name":"Shae"},"shortName":"Jamey","dependants":[{"age":12,"name":"Enola"},{"age":6,"name":"Dania"}]} +{"mother":{"age":50,"name":"Aja"},"shortName":"Jodi","dependants":[]} +{"mother":{"age":24,"name":"Sherley"},"shortName":"James","dependants":[{"age":13,"name":"Thanh"},{"age":14,"name":"Stacey"},{"age":16,"name":"Dewayne"}]} +{"mother":{"age":66,"name":"Rosetta"},"shortName":"Joana","dependants":[]} +``` + +Note that data within `mother` is: +- A sub-object - because no `extents` were provided in the relationship +- Is embedded within the property `mother` as that is the `name` of the relationship + +Note that data within `dependants` is: +- A sub-array - because there are `extents` provided in the relationship +- Is embedded within the property `dependants` as that is the `name` of the relationship +- Sometimes an empty array, as the minimum extent is 0 +- Various sizes - the number of records to produce is a random number between `min` and `max` at generation time. + +### What it can do +- Represent sub-objects (one-to-one) +- Represent sub-arrays (one-to-many) +- Represent n-levels of sub-objects and sub-arrays + +### What it cannot do (yet) +- Constrain objects in one relationship by values in another, i.e. + - if `children` sub-objects have some `age`s > 18 then `placesLived` must contain a `number-of-rooms` >= 3 +- Constrain the parent object by values in a relationship, i.e. + - if `children` sub-objects have all `age`s > 18 then `councilTaxRateBand` = `A` +- Constrain circular relationships to a maximum depth, i.e. + - Generate a tree of ancestors (or conversely children) from a given person (aka genealogical data) \ No newline at end of file diff --git a/examples/relational/dependants.profile.json b/examples/relational/dependants.profile.json new file mode 100644 index 000000000..d79f460e1 --- /dev/null +++ b/examples/relational/dependants.profile.json @@ -0,0 +1,10 @@ +{ + "fields": [ + { "name": "name", "type": "faker.name.firstName", "nullable": false }, + { "name": "age", "type": "integer", "nullable": false } + ], + "constraints": [ + { "field": "age", "greaterThanOrEqualTo": 0 }, + { "field": "age", "lessThan": 18 } + ] +} \ No newline at end of file diff --git a/examples/relational/profile.json b/examples/relational/profile.json new file mode 100644 index 000000000..581e7cae4 --- /dev/null +++ b/examples/relational/profile.json @@ -0,0 +1,47 @@ +{ + "fields": [ + { "name": "id", "type": "integer", "nullable": false, "unique": true }, + { "name": "shortName", "type": "faker.name.firstName", "nullable": false } + ], + "constraints": [ + { "field": "id", "greaterThanOrEqualTo": 1 }, + { "field": "id", "lessThanOrEqualTo": 100 }, + { "field": "shortName", "shorterThan": 6 }, + { "field": "shortName", "matchingRegex": "J.*" } + ], + "relationships": + [ + { + "name": "dependants", + "description": "presence of min/max indicates that it is a collection (one-to-many)", + "profileFile": "dependants.profile.json", + "extents": [ + { "field": "min", "equalTo": 0 }, + { + "if": { "field": "shortName", "matchingRegex": "J[ae].*" }, + "then": { "field": "min", "equalTo": 2 }, + "else": { "field": "max", "equalTo": 3 } + }, + { "field": "max", "equalTo": 3 }, + { + "if": { "field": "shortName", "matchingRegex": "J[iou].*" }, + "then": { "field": "max", "equalTo": 1 } + } + ] + }, + { + "name": "mother", + "description": "absence of min/max indicates that it is a sub-object (one-to-one)", + "profile": { + "fields": [ + { "name": "name", "type": "faker.name.firstName", "nullable": false }, + { "name": "age", "type": "integer", "nullable": false } + ], + "constraints": [ + { "field": "age", "greaterThanOrEqualTo": 16 }, + { "field": "age", "lessThanOrEqualTo": 100 } + ] + } + } + ] +} diff --git a/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java b/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java index d7e97deb7..a72943462 100644 --- a/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java +++ b/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java @@ -26,7 +26,7 @@ import com.scottlogic.datahelix.generator.orchestrator.CommonOptionInfo; import com.scottlogic.datahelix.generator.orchestrator.guice.AllConfigSource; import com.scottlogic.datahelix.generator.orchestrator.guice.AllModule; -import com.scottlogic.datahelix.generator.output.guice.OutputFormat; +import com.scottlogic.datahelix.generator.common.output.OutputFormat; import com.scottlogic.datahelix.generator.profile.ProfileConfiguration; import picocli.CommandLine; @@ -37,7 +37,7 @@ import static com.scottlogic.datahelix.generator.common.util.Defaults.DEFAULT_MAX_ROWS; import static com.scottlogic.datahelix.generator.core.config.detail.CombinationStrategyType.MINIMAL; import static com.scottlogic.datahelix.generator.core.config.detail.DataGenerationType.RANDOM; -import static com.scottlogic.datahelix.generator.output.guice.OutputFormat.CSV; +import static com.scottlogic.datahelix.generator.common.output.OutputFormat.CSV; /** * This class holds the generate specific command line options. diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java index 04ba08e9f..645a3179c 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java @@ -22,7 +22,7 @@ import com.scottlogic.datahelix.generator.core.config.detail.MonitorType; import com.scottlogic.datahelix.generator.core.config.detail.VisualiserLevel; import com.scottlogic.datahelix.generator.orchestrator.guice.AllConfigSource; -import com.scottlogic.datahelix.generator.output.guice.OutputFormat; +import com.scottlogic.datahelix.generator.common.output.OutputFormat; import java.io.File; import java.nio.file.Path; diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberTestState.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberTestState.java index b5ef397a0..3cac47a4e 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberTestState.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberTestState.java @@ -45,7 +45,7 @@ import com.scottlogic.datahelix.generator.profile.dtos.constraints.grammatical.ConditionalConstraintDTO; import com.scottlogic.datahelix.generator.profile.dtos.constraints.grammatical.NotConstraintDTO; import com.scottlogic.datahelix.generator.profile.dtos.constraints.relations.*; -import com.scottlogic.datahelix.generator.profile.serialisation.ConstraintDeserializer; +import com.scottlogic.datahelix.generator.profile.serialisation.ConstraintDeserializerFactory; import com.scottlogic.datahelix.generator.profile.serialisation.ConstraintDeserializerFactory; import java.io.IOException; diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java index ca786d2a2..05e3fde32 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java @@ -18,13 +18,17 @@ import org.junit.jupiter.api.Test; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; public class JarExecuteTests { @Test @@ -33,7 +37,7 @@ void generateSuccessfullyFromJar() throws Exception { List collectedOutput = collectOutputAndCloseProcess(p); - assertOnOutputs(collectedOutput, "Generation successful", ""); + assertCsvOutputs(collectedOutput, "Generation successful", ""); } @Test @@ -42,7 +46,7 @@ void generateSuccessfullyFromJarAndLoadFile() throws Exception { List collectedOutput = collectOutputAndCloseProcess(p); - assertOnOutputs(collectedOutput, + assertCsvOutputs(collectedOutput, "Generated successfully from file", "Either load from file no longer works, or "); } @@ -53,31 +57,73 @@ void generateSuccessfullyFromJarAndLoadFileWithinSubDirectory() throws Exception List collectedOutput = collectOutputAndCloseProcess(p); - assertOnOutputs(collectedOutput, + assertCsvOutputs(collectedOutput, "Generated successfully from file", "Either load from file no longer works, or "); } - private void assertOnOutputs(List outputs, String expectedFinalMessage, String extraErrorMessage) { + @Test + void generateRelationalDataSuccessfullyFromJar() throws Exception { + Process p = setupProcess("-p=src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile.json", "JSON"); + + List collectedOutput = collectOutputAndCloseProcess(p); + + assertJsonOutputs(collectedOutput, + "shortName", + null); + } + + @Test + void generateRelationalDataSuccessfullyFromJarAndLoadFileWithinSubDirectory() throws Exception { + Process p = setupProcess("-p=src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile-referenced-relationship.json", "JSON"); + + List collectedOutput = collectOutputAndCloseProcess(p); + + assertJsonOutputs(collectedOutput, + "age", + "\"Generated successfully from file\""); + } + + private void assertCsvOutputs(List outputs, String expectedFinalMessage, String extraErrorMessage) { String errorMessageOnFailure = "Jar test failed. This may have been caused by one of the following:" + "1) You have not built the jar. \n Try running Gradle Build. \n" + "2) System.out is being printed to (which interferes with streaming output) e.g. using 'printStackTrace'.\n" + "3) There is a bug in code conditional on whether it is running inside the JAR, e.g. in SupportedVersionsGetter. \n" + outputs.stream().limit(5).collect(Collectors.joining("\n")); String fullErrorMessage = extraErrorMessage + errorMessageOnFailure; - assertTrue(outputs.size() >= 2, fullErrorMessage); + assertThat(fullErrorMessage, outputs.size(), is(greaterThanOrEqualTo(2))); assertEquals("foo", outputs.get(outputs.size() - 2), fullErrorMessage); assertEquals(expectedFinalMessage, outputs.get(outputs.size() - 1), fullErrorMessage); } + private void assertJsonOutputs(List outputs, String expectedJsonPropertyName, String expectedData) { + String errorMessageOnFailure = "Jar test failed. This may have been caused by one of the following:" + + "1) You have not built the jar. \n Try running Gradle Build. \n" + + "2) System.out is being printed to (which interferes with streaming output) e.g. using 'printStackTrace'.\n" + + "3) There is a bug in code conditional on whether it is running inside the JAR, e.g. in SupportedVersionsGetter. \n" + + outputs.stream().limit(5).collect(Collectors.joining("\n")); + assertThat(errorMessageOnFailure, outputs.size(), is(greaterThanOrEqualTo(1))); + assertThat(errorMessageOnFailure, outputs.get(outputs.size() - 1), containsString("\"" + expectedJsonPropertyName + "\":")); + if (expectedData != null){ + assertThat(errorMessageOnFailure, outputs.get(outputs.size() - 1), containsString(":" + expectedData)); + } + } + private Process setupProcess(final String profile) throws IOException { + return setupProcess(profile, null); + } + + private Process setupProcess(final String profile, final String outputFormat) throws IOException { ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "build/libs/datahelix.jar", profile, "--max-rows=1", - "--quiet"); + "--quiet", + outputFormat == null + ? "" + : "--output-format=" + outputFormat); pb.redirectErrorStream(true); return pb.start(); } diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile-referenced-relationship.json b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile-referenced-relationship.json new file mode 100644 index 000000000..0a5a2665f --- /dev/null +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile-referenced-relationship.json @@ -0,0 +1,36 @@ +{ + "fields": [ + { + "name": "shortName", + "type": "faker.name.firstName", + "nullable": false + } + ], + "constraints": [ + { + "field": "shortName", + "shorterThan": 6 + }, + { + "field": "shortName", + "matchingRegex": "J.*" + } + ], + "relationships": [ { + "name": "dependants", + "description": "presence of min/max indicates that it is a collection (one-to-many)", + "extents": [ + { "field": "min", "equalTo": 1 }, + { + "if": { "field": "shortName", "matchingRegex": "J[ae].*" }, + "then": { "field": "min", "equalTo": 2 } + }, + { "field": "max", "equalTo": 3 }, + { + "if": { "field": "shortName", "matchingRegex": "J[iou].*" }, + "then": { "field": "max", "equalTo": 1 } + } + ], + "profileFile": "subfolder/dependants.profile.json" + } ] +} diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile.json b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile.json new file mode 100644 index 000000000..4c9b118a4 --- /dev/null +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/profile.json @@ -0,0 +1,47 @@ +{ + "fields": [ + { + "name": "shortName", + "type": "faker.name.firstName", + "nullable": false + } + ], + "constraints": [ + { + "field": "shortName", + "shorterThan": 6 + }, + { + "field": "shortName", + "matchingRegex": "J.*" + } + ], + "relationships": [ { + "name": "mother", + "description": "absence of min/max indicates that it is a sub-object (one-to-one)", + "profile": { + "fields": [ + { + "name": "name", + "type": "faker.name.firstName", + "nullable": false + }, + { + "name": "age", + "type": "integer", + "nullable": false + } + ], + "constraints": [ + { + "field": "age", + "greaterThanOrEqualTo": 16 + }, + { + "field": "age", + "lessThanOrEqualTo": 100 + } + ] + } + } ] +} diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/dependants.profile.json b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/dependants.profile.json new file mode 100644 index 000000000..2ba7049c1 --- /dev/null +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/dependants.profile.json @@ -0,0 +1,28 @@ +{ + "fields": [ + { + "name": "name", + "type": "string", + "nullable": false + }, + { + "name": "age", + "type": "integer", + "nullable": false + } + ], + "constraints": [ + { + "field": "name", + "inSet": "testfile.csv" + }, + { + "field": "age", + "greaterThanOrEqualTo": 0 + }, + { + "field": "age", + "lessThan": 18 + } + ] +} \ No newline at end of file diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/testfile.csv b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/testfile.csv new file mode 100644 index 000000000..320eed6fe --- /dev/null +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/relational/subfolder/testfile.csv @@ -0,0 +1 @@ +Generated successfully from file \ No newline at end of file diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/validator/ProfileValidationTests.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/validator/ProfileValidationTests.java index 9f4a36c40..0207615c9 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/validator/ProfileValidationTests.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/validator/ProfileValidationTests.java @@ -32,6 +32,7 @@ import com.scottlogic.datahelix.generator.profile.services.NameRetrievalService; import com.scottlogic.datahelix.generator.profile.validators.ConfigValidator; import com.scottlogic.datahelix.generator.profile.validators.CreateProfileValidator; +import com.scottlogic.datahelix.generator.profile.validators.ReadRelationshipsValidator; import com.scottlogic.datahelix.generator.profile.validators.profile.ProfileValidator; import org.junit.Assert; import org.junit.jupiter.api.DynamicTest; @@ -69,7 +70,9 @@ Collection shouldAllValidateWithoutErrors() throws IOException { new FieldService(), constraintService, customConstraintFactory, - new CreateProfileValidator(new ProfileValidator(null))); + new CreateProfileValidator(new ProfileValidator(null)), + new ReadRelationshipsValidator(), + profileDeserialiser); JsonProfileReader profileReader = new JsonProfileReader(commandBus, profileDeserialiser); diff --git a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputConfigSource.java b/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputConfigSource.java index edb8fa7c8..7f14176c8 100644 --- a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputConfigSource.java +++ b/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputConfigSource.java @@ -16,6 +16,8 @@ package com.scottlogic.datahelix.generator.output.guice; +import com.scottlogic.datahelix.generator.common.output.OutputFormat; + import java.nio.file.Path; public interface OutputConfigSource { diff --git a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputModule.java b/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputModule.java index 76da796e4..7ebe6ee11 100644 --- a/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputModule.java +++ b/output/src/main/java/com/scottlogic/datahelix/generator/output/guice/OutputModule.java @@ -18,6 +18,8 @@ import com.google.inject.AbstractModule; import com.google.inject.name.Names; +import com.google.inject.util.Providers; +import com.scottlogic.datahelix.generator.common.output.OutputFormat; import com.scottlogic.datahelix.generator.output.OutputPath; import com.scottlogic.datahelix.generator.output.outputtarget.SingleDatasetOutputTarget; import com.scottlogic.datahelix.generator.output.writer.OutputWriterFactory; @@ -45,5 +47,8 @@ protected void configure() { bind(boolean.class) .annotatedWith(Names.named("config:streamOutput")) .toInstance(outputConfigSource.useStdOut()); + + bind(OutputFormat.class) + .toProvider(Providers.of(outputConfigSource.getOutputFormat())); } } diff --git a/output/src/main/java/com/scottlogic/datahelix/generator/output/writer/json/JsonDataSetWriter.java b/output/src/main/java/com/scottlogic/datahelix/generator/output/writer/json/JsonDataSetWriter.java index a75813c0b..e27304014 100644 --- a/output/src/main/java/com/scottlogic/datahelix/generator/output/writer/json/JsonDataSetWriter.java +++ b/output/src/main/java/com/scottlogic/datahelix/generator/output/writer/json/JsonDataSetWriter.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.SequenceWriter; import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.output.RelationalGeneratedObject; +import com.scottlogic.datahelix.generator.common.output.SubGeneratedObject; import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.output.writer.DataSetWriter; @@ -27,28 +29,75 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; class JsonDataSetWriter implements DataSetWriter { private static final DateTimeFormatter standardDateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; private final SequenceWriter writer; - private final Fields fields; + private final List fields; JsonDataSetWriter(SequenceWriter writer, Fields fields) { this.writer = writer; + this.fields = fields.getExternalStream().collect(Collectors.toList()); + } + + private JsonDataSetWriter(List fields) { this.fields = fields; + this.writer = null; } @Override public void writeRow(GeneratedObject row) throws IOException { + Map jsonObject = convertRow(row); + + writer.write(jsonObject); + } + + private Map convertRow(GeneratedObject row) { Map jsonObject = new HashMap<>(); - fields.getExternalStream() - .forEach(field -> jsonObject - .put(field , convertValue(row.getFormattedValue(field)))); + fields + .forEach(field -> + jsonObject.put(field, convertValue(row.getFormattedValue(field)))); - writer.write(jsonObject); + if (row instanceof RelationalGeneratedObject) { + writeRelatedObjects(jsonObject, (RelationalGeneratedObject)row); + } + + return jsonObject; + } + + private void writeRelatedObjects(Map jsonObject, RelationalGeneratedObject relationalGeneratedObject) { + relationalGeneratedObject.getSubObjects() + .forEach((key, value) -> writeRelatedObject(jsonObject, key, value)); + } + + private void writeRelatedObject(Map jsonObject, String key, SubGeneratedObject value) { + JsonDataSetWriter subWriter = new JsonDataSetWriter(value.getFields()); + Field fieldForRelationship = new Field(key, null, false, null, false, true, null); + + if (value.isArray()) { + writeRelatedArray(jsonObject, value, subWriter, fieldForRelationship); + } else { + writeRelatedObject(jsonObject, value, subWriter, fieldForRelationship); + } + } + + private void writeRelatedObject(Map jsonObject, SubGeneratedObject value, JsonDataSetWriter subWriter, Field fieldForRelationship) { + GeneratedObject singleSubObject = value.getData().get(0); + Map subObject = subWriter.convertRow(singleSubObject); + jsonObject.put(fieldForRelationship, subObject); + } + + private void writeRelatedArray(Map jsonObject, SubGeneratedObject value, JsonDataSetWriter subWriter, Field fieldForRelationship) { + List> subObjectsForRelationship = value.getData() + .stream() + .map(subWriter::convertRow) + .collect(Collectors.toList()); + jsonObject.put(fieldForRelationship, subObjectsForRelationship); } @Override diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/CreateProfile.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/CreateProfile.java index 6c6fcb58b..9baa5d465 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/CreateProfile.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/CreateProfile.java @@ -18,16 +18,16 @@ import com.scottlogic.datahelix.generator.common.commands.Command; import com.scottlogic.datahelix.generator.core.profile.Profile; -import com.scottlogic.datahelix.generator.profile.dtos.ProfileDTO; +import com.scottlogic.datahelix.generator.profile.dtos.RelationalProfileDTO; import java.nio.file.Path; public class CreateProfile extends Command { - public final ProfileDTO profileDTO; + public final RelationalProfileDTO profileDTO; public final Path profileDirectory; - public CreateProfile(Path profileDirectory, ProfileDTO profileDTO) + public CreateProfile(Path profileDirectory, RelationalProfileDTO profileDTO) { this.profileDirectory = profileDirectory; this.profileDTO = profileDTO; diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/ReadRelationships.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/ReadRelationships.java new file mode 100644 index 000000000..29432e6fa --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/commands/ReadRelationships.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.commands; + +import com.scottlogic.datahelix.generator.common.commands.Command; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; +import com.scottlogic.datahelix.generator.profile.dtos.RelationshipDTO; + +import java.nio.file.Path; +import java.util.List; + +public class ReadRelationships extends Command> { + public final Fields fields; + public final List relationships; + public final Path profileDirectory; + + public ReadRelationships(Path profileDirectory, Fields fields, List relationships) { + this.profileDirectory = profileDirectory; + this.fields = fields; + this.relationships = relationships; + } +} diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationalProfileDTO.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationalProfileDTO.java new file mode 100644 index 000000000..cfe87b4da --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationalProfileDTO.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.dtos; + +import java.util.List; + +public class RelationalProfileDTO extends ProfileDTO { + public List relationships; +} diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationshipDTO.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationshipDTO.java new file mode 100644 index 000000000..88f4b21e6 --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/dtos/RelationshipDTO.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.dtos; + +import com.scottlogic.datahelix.generator.profile.dtos.constraints.ConstraintDTO; + +import java.util.List; + +public class RelationshipDTO { + public String name; + public String description; + public String profileFile; + public RelationalProfileDTO profile; + public List extents; +} diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/guice/ProfileModule.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/guice/ProfileModule.java index e636fb14a..64cb8404c 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/guice/ProfileModule.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/guice/ProfileModule.java @@ -23,8 +23,10 @@ import com.scottlogic.datahelix.generator.common.commands.CommandBus; import com.scottlogic.datahelix.generator.common.validators.Validator; import com.scottlogic.datahelix.generator.profile.commands.CreateProfile; +import com.scottlogic.datahelix.generator.profile.commands.ReadRelationships; import com.scottlogic.datahelix.generator.profile.dtos.ProfileDTO; import com.scottlogic.datahelix.generator.profile.validators.CreateProfileValidator; +import com.scottlogic.datahelix.generator.profile.validators.ReadRelationshipsValidator; import com.scottlogic.datahelix.generator.profile.validators.profile.ProfileValidator; import com.scottlogic.datahelix.generator.profile.reader.*; @@ -53,6 +55,7 @@ protected void configure() { bind(Key.get(new TypeLiteral>(){})).to(ProfileValidator.class); bind(Key.get(new TypeLiteral>(){})).to(CreateProfileValidator.class); + bind(Key.get(new TypeLiteral>(){})).to(ReadRelationshipsValidator.class); bind(CommandBus.class).to(ProfileCommandBus.class); } } diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/CreateProfileHandler.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/CreateProfileHandler.java index 758d3e132..b266dde5f 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/CreateProfileHandler.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/CreateProfileHandler.java @@ -17,6 +17,7 @@ package com.scottlogic.datahelix.generator.profile.handlers; import com.google.inject.Inject; +import com.scottlogic.datahelix.generator.common.commands.CommandBus; import com.scottlogic.datahelix.generator.common.commands.CommandHandler; import com.scottlogic.datahelix.generator.common.commands.CommandResult; import com.scottlogic.datahelix.generator.common.profile.Field; @@ -25,7 +26,9 @@ import com.scottlogic.datahelix.generator.core.profile.Profile; import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.NotNullConstraint; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; import com.scottlogic.datahelix.generator.profile.commands.CreateProfile; +import com.scottlogic.datahelix.generator.profile.commands.ReadRelationships; import com.scottlogic.datahelix.generator.profile.custom.CustomConstraintFactory; import com.scottlogic.datahelix.generator.profile.services.ConstraintService; import com.scottlogic.datahelix.generator.profile.services.FieldService; @@ -39,15 +42,21 @@ public class CreateProfileHandler extends CommandHandler private final FieldService fieldService; private final ConstraintService constraintService; private final CustomConstraintFactory customConstraintFactory; + private final CommandBus commandBus; @Inject - public CreateProfileHandler(FieldService fieldService, ConstraintService constraintService, - CustomConstraintFactory customConstraintFactory, Validator validator) + public CreateProfileHandler( + FieldService fieldService, + ConstraintService constraintService, + CustomConstraintFactory customConstraintFactory, + Validator validator, + CommandBus commandBus) { super(validator); this.fieldService = fieldService; this.constraintService = constraintService; this.customConstraintFactory = customConstraintFactory; + this.commandBus = commandBus; } @Override @@ -55,12 +64,20 @@ public CommandResult handleCommand(CreateProfile command) { Fields fields = fieldService.createFields(command.profileDTO); List constraints = constraintService.createConstraints(command.profileDTO.constraints, fields); + CommandResult> relationships = commandBus.send( + new ReadRelationships( + command.profileDirectory, + fields, + command.profileDTO.relationships)); + if (!relationships.isSuccess) { + throw new RuntimeException("Unable to read relationships"); + } constraints.addAll(createNullableConstraints(fields)); constraints.addAll(createSpecificTypeConstraints(fields)); constraints.addAll(createCustomGeneratorConstraints(fields)); - return CommandResult.success(new Profile(command.profileDTO.description, fields, constraints)); + return CommandResult.success(new Profile(command.profileDTO.description, fields, constraints, relationships.value)); } private List createNullableConstraints(Fields fields) diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/ReadRelationshipsHandler.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/ReadRelationshipsHandler.java new file mode 100644 index 000000000..6cfe9e35b --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/handlers/ReadRelationshipsHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.handlers; + +import com.google.inject.Inject; +import com.scottlogic.datahelix.generator.common.commands.CommandHandler; +import com.scottlogic.datahelix.generator.common.commands.CommandResult; +import com.scottlogic.datahelix.generator.common.validators.Validator; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; +import com.scottlogic.datahelix.generator.profile.commands.ReadRelationships; +import com.scottlogic.datahelix.generator.profile.services.RelationshipService; + +import java.util.List; + +public class ReadRelationshipsHandler extends CommandHandler> { + private final RelationshipService relationshipService; + + @Inject + public ReadRelationshipsHandler( + RelationshipService relationshipService, + Validator validator) { + super(validator); + this.relationshipService = relationshipService; + } + + @Override + public CommandResult> handleCommand(ReadRelationships command) { + return CommandResult.success(relationshipService.createRelationships(command.profileDirectory, command.fields, command.relationships)); + } +} diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReader.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReader.java index e1099088d..38119f5b6 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReader.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReader.java @@ -22,7 +22,7 @@ import com.scottlogic.datahelix.generator.common.commands.CommandResult; import com.scottlogic.datahelix.generator.core.profile.Profile; import com.scottlogic.datahelix.generator.profile.commands.CreateProfile; -import com.scottlogic.datahelix.generator.profile.dtos.ProfileDTO; +import com.scottlogic.datahelix.generator.profile.dtos.RelationalProfileDTO; import com.scottlogic.datahelix.generator.profile.serialisation.ProfileDeserialiser; import java.io.File; @@ -53,11 +53,11 @@ public Profile read(File profileFile) throws IOException { } public Profile read(Path profileDirectory, String profileJson) { - ProfileDTO profileDTO = profileDeserialiser.deserialise(profileDirectory, profileJson); + RelationalProfileDTO profileDTO = profileDeserialiser.deserialise(profileDirectory, profileJson); return createFromDto(profileDirectory, profileDTO); } - private Profile createFromDto(Path profileDirectory, ProfileDTO profileDTO) { + private Profile createFromDto(Path profileDirectory, RelationalProfileDTO profileDTO) { CommandResult createProfileResult = commandBus.send(new CreateProfile(profileDirectory, profileDTO)); if (!createProfileResult.isSuccess){ diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/ProfileCommandBus.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/ProfileCommandBus.java index a22faa5b4..82513c5cd 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/ProfileCommandBus.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/reader/ProfileCommandBus.java @@ -19,10 +19,14 @@ import com.scottlogic.datahelix.generator.common.commands.CommandBus; import com.scottlogic.datahelix.generator.common.validators.Validator; import com.scottlogic.datahelix.generator.profile.commands.CreateProfile; +import com.scottlogic.datahelix.generator.profile.commands.ReadRelationships; import com.scottlogic.datahelix.generator.profile.custom.CustomConstraintFactory; import com.scottlogic.datahelix.generator.profile.handlers.CreateProfileHandler; +import com.scottlogic.datahelix.generator.profile.handlers.ReadRelationshipsHandler; +import com.scottlogic.datahelix.generator.profile.serialisation.ProfileDeserialiser; import com.scottlogic.datahelix.generator.profile.services.ConstraintService; import com.scottlogic.datahelix.generator.profile.services.FieldService; +import com.scottlogic.datahelix.generator.profile.services.RelationshipService; public class ProfileCommandBus extends CommandBus { @@ -31,7 +35,9 @@ public ProfileCommandBus( FieldService fieldService, ConstraintService constraintService, CustomConstraintFactory customConstraintFactory, - Validator validator) + Validator validator, + Validator relationshipValidator, + ProfileDeserialiser profileDeserialiser) { register( CreateProfile.class, @@ -39,6 +45,16 @@ public ProfileCommandBus( fieldService, constraintService, customConstraintFactory, - validator)); + validator, + this)); + + register( + ReadRelationships.class, + new ReadRelationshipsHandler( + new RelationshipService( + this, + constraintService, + profileDeserialiser), + relationshipValidator)); } } diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/serialisation/ProfileDeserialiser.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/serialisation/ProfileDeserialiser.java index cb8a23ea7..6a0ed5fb8 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/serialisation/ProfileDeserialiser.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/serialisation/ProfileDeserialiser.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.inject.Inject; import com.scottlogic.datahelix.generator.common.ValidationException; -import com.scottlogic.datahelix.generator.profile.dtos.ProfileDTO; +import com.scottlogic.datahelix.generator.profile.dtos.RelationalProfileDTO; import com.scottlogic.datahelix.generator.profile.dtos.constraints.ConstraintDTO; import com.scottlogic.datahelix.generator.profile.validators.ConfigValidator; @@ -44,14 +44,14 @@ public ProfileDeserialiser(ConfigValidator configValidator, ConstraintDeserializ this.constraintDeserializerFactory = constraintDeserializerFactory; } - public ProfileDTO deserialise(File profileFile) throws IOException { + public RelationalProfileDTO deserialise(File profileFile) throws IOException { configValidator.validate(profileFile); byte[] encoded = Files.readAllBytes(profileFile.toPath()); String profileJson = new String(encoded, StandardCharsets.UTF_8); return deserialise(profileFile.getParentFile().toPath(), profileJson); } - public ProfileDTO deserialise(Path profileDirectory, String json) { + public RelationalProfileDTO deserialise(Path profileDirectory, String json) { if (profileDirectory == null) { throw new IllegalArgumentException("profileDirectory must be supplied"); } @@ -66,7 +66,7 @@ public ProfileDTO deserialise(Path profileDirectory, String json) { try { return mapper - .readerFor(ProfileDTO.class) + .readerFor(RelationalProfileDTO.class) .readValue(json); } catch (Exception e) { StringWriter stackTrace = new StringWriter(); diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/RelationshipService.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/RelationshipService.java new file mode 100644 index 000000000..d5ca3c5b7 --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/RelationshipService.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.services; + +import com.google.inject.Inject; +import com.scottlogic.datahelix.generator.common.ValidationException; +import com.scottlogic.datahelix.generator.common.commands.CommandBus; +import com.scottlogic.datahelix.generator.common.commands.CommandResult; +import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.core.generation.relationships.ExtentAugmentedFields; +import com.scottlogic.datahelix.generator.core.profile.Profile; +import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; +import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; +import com.scottlogic.datahelix.generator.profile.commands.CreateProfile; +import com.scottlogic.datahelix.generator.profile.dtos.RelationalProfileDTO; +import com.scottlogic.datahelix.generator.profile.dtos.RelationshipDTO; +import com.scottlogic.datahelix.generator.profile.dtos.constraints.ConstraintDTO; +import com.scottlogic.datahelix.generator.profile.serialisation.ProfileDeserialiser; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class RelationshipService { + private final CommandBus commandBus; + private final ConstraintService constraintService; + private final ProfileDeserialiser profileDeserialiser; + + @Inject + public RelationshipService( + CommandBus commandBus, + ConstraintService constraintService, + ProfileDeserialiser profileDeserialiser) { + this.commandBus = commandBus; + this.constraintService = constraintService; + this.profileDeserialiser = profileDeserialiser; + } + + public List createRelationships(Path profileDirectory, Fields fields, List relationships) { + if (relationships == null) { + return Collections.emptyList(); + } + + return relationships.stream() + .map(relationshipDTO -> createRelationship(profileDirectory, fields, relationshipDTO)) + .collect(Collectors.toList()); + } + + private Relationship createRelationship(Path profileDirectory, Fields fields, RelationshipDTO relationshipDTO) { + return new Relationship( + relationshipDTO.name, + relationshipDTO.description, + relationshipDTO.profile != null + ? createProfile(profileDirectory, relationshipDTO.profile) + : readProfile(profileDirectory, relationshipDTO.profileFile), + mapConstraints(fields, relationshipDTO.extents) + ); + } + + private List mapConstraints(Fields fields, List extents) { + if (extents == null || extents.isEmpty()){ + return Collections.emptyList(); + } + + return extents + .stream() + .map(c -> createConstraint(fields, c)) + .collect(Collectors.toList()); + } + + private Constraint createConstraint(Fields fields, ConstraintDTO extent) { + return constraintService.createConstraints( + Collections.singletonList(extent), + new ExtentAugmentedFields(fields)).get(0); + } + + private Profile readProfile(Path profileDirectory, String profileFile) { + if (profileFile == null || profileFile.equals("")) { + throw new ValidationException("Relationships must have a `profile` (sub profile) or a `profileFile` (file path) property populated"); + } + + //read file from disk. + File file = Paths.get(profileDirectory.toString(), profileFile).toFile(); + try { + RelationalProfileDTO profileDTO = profileDeserialiser.deserialise(file); + return createProfile(profileDirectory, profileDTO); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private Profile createProfile(Path profileDirectory, RelationalProfileDTO profile) { + CommandResult createProfileResult = commandBus.send(new CreateProfile(profileDirectory, profile)); + if (!createProfileResult.isSuccess){ + throw new ValidationException(createProfileResult.errors); + } + return createProfileResult.value; + } +} diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/validators/ReadRelationshipsValidator.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/validators/ReadRelationshipsValidator.java new file mode 100644 index 000000000..33e32fc16 --- /dev/null +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/validators/ReadRelationshipsValidator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.profile.validators; + +import com.scottlogic.datahelix.generator.common.validators.ValidationResult; +import com.scottlogic.datahelix.generator.common.validators.Validator; +import com.scottlogic.datahelix.generator.profile.commands.ReadRelationships; + +public class ReadRelationshipsValidator implements Validator +{ + @Override + public ValidationResult validate(ReadRelationships obj) { + return ValidationResult.success(); + } +} diff --git a/profile/src/test/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReaderTests.java b/profile/src/test/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReaderTests.java index 568c06c2d..400822494 100644 --- a/profile/src/test/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReaderTests.java +++ b/profile/src/test/java/com/scottlogic/datahelix/generator/profile/reader/JsonProfileReaderTests.java @@ -40,6 +40,7 @@ import com.scottlogic.datahelix.generator.profile.services.NameRetrievalService; import com.scottlogic.datahelix.generator.profile.validators.ConfigValidator; import com.scottlogic.datahelix.generator.profile.validators.CreateProfileValidator; +import com.scottlogic.datahelix.generator.profile.validators.ReadRelationshipsValidator; import com.scottlogic.datahelix.generator.profile.validators.profile.ProfileValidator; import org.junit.Assert; import org.junit.jupiter.api.Assertions; @@ -97,9 +98,11 @@ public DistributedList listFromMapFile(File file, String Key) new ProfileCommandBus( new FieldService(), constraintService, - new CustomConstraintFactory(new CustomGeneratorList()), + new CustomConstraintFactory(new CustomGeneratorList()), new CreateProfileValidator( - new ProfileValidator(null))), + new ProfileValidator(null)), + new ReadRelationshipsValidator(), + profileDeserialiser), profileDeserialiser); private void givenJson(String json) { From a7cee658e0d1e79c1907f0d1c0b2a65031dc8061 Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 07:37:22 +0100 Subject: [PATCH 02/12] fix(#1534): Refactor Fields to an interface (composition over inheritance) --- .../generator/common/profile/Fields.java | 63 ++-------------- .../common/profile/ProfileFields.java | 75 +++++++++++++++++++ ...eldsTests.java => ProfileFieldsTests.java} | 30 ++++---- .../treepartitioning/TreePartitioner.java | 5 +- .../relationships/ExtentAugmentedFields.java | 60 +++++++++++++-- .../relationships/OneToManyRangeResolver.java | 30 ++------ .../generator/core/profile/Profile.java | 5 +- .../DecisionTreeFactoryTests.java | 5 +- .../DecisionTreeOptimiserTest.java | 5 +- .../DecisionTreeSimplifierTests.java | 18 +++-- .../decisiontree/RowSpecTreeSolverTests.java | 3 +- .../ConstraintToFieldMapperTests.java | 5 +- .../TreePartitionerTests.java | 3 +- .../core/fieldspecs/RowSpecMergerTest.java | 3 +- .../generation/UpfrontTreePrunerTests.java | 53 ++++++------- .../RowSpecDataBagGeneratorTests.java | 5 +- .../grouped/RowSpecGrouperTest.java | 15 ++-- .../decisionbased/RowSpecTreeSolverTests.java | 3 +- .../writer/csv/CsvDataSetWriterTest.java | 7 +- .../csv/CsvOutputWriterFactoryTests.java | 5 +- .../json/JsonOutputWriterFactoryTest.java | 7 +- .../profile/services/FieldService.java | 7 +- 22 files changed, 239 insertions(+), 173 deletions(-) create mode 100644 common/src/main/java/com/scottlogic/datahelix/generator/common/profile/ProfileFields.java rename common/src/test/java/com/scottlogic/datahelix/generator/common/profile/{FieldsTests.java => ProfileFieldsTests.java} (89%) diff --git a/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/Fields.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/Fields.java index 3c93912b0..1f7ab3822 100644 --- a/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/Fields.java +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/Fields.java @@ -16,62 +16,13 @@ package com.scottlogic.datahelix.generator.common.profile; - - -import java.util.Iterator; import java.util.List; import java.util.stream.Stream; -public class Fields implements Iterable { - private final List fields; - - public Fields(List fields) { - this.fields = fields; - } - - public Field getByName(String fieldName) { - return this.fields.stream() - .filter(f -> f.getName().equals(fieldName)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Profile fields do not contain " + fieldName)); - } - - public int size() { - return this.fields.size(); - } - - @Override - public Iterator iterator() { - return fields.iterator(); - } - - public Stream stream() { - return this.fields.stream(); - } - - public Stream getExternalStream() { - return this.stream().filter(f -> !f.isInternal()); - } - - public List asList() { - return fields; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj.getClass() != getClass()) { - return false; - } - - Fields fields = (Fields) obj; - return this.fields.equals(fields.fields); - } - - @Override - public int hashCode() { - return fields.hashCode(); - } -} +public interface Fields extends Iterable { + Field getByName(String fieldName); + int size(); + Stream stream(); + Stream getExternalStream(); + List asList(); +} \ No newline at end of file diff --git a/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/ProfileFields.java b/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/ProfileFields.java new file mode 100644 index 000000000..6c8819fe5 --- /dev/null +++ b/common/src/main/java/com/scottlogic/datahelix/generator/common/profile/ProfileFields.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.common.profile; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +public class ProfileFields implements Fields { + private final List fields; + + public ProfileFields(List fields) { + this.fields = fields; + } + + public Field getByName(String fieldName) { + return this.fields.stream() + .filter(f -> f.getName().equals(fieldName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Profile fields do not contain " + fieldName)); + } + + public int size() { + return this.fields.size(); + } + + @Override + public Iterator iterator() { + return fields.iterator(); + } + + public Stream stream() { + return this.fields.stream(); + } + + public Stream getExternalStream() { + return this.stream().filter(f -> !f.isInternal()); + } + + public List asList() { + return fields; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj.getClass() != getClass()) { + return false; + } + + ProfileFields fields = (ProfileFields) obj; + return this.fields.equals(fields.fields); + } + + @Override + public int hashCode() { + return fields.hashCode(); + } +} diff --git a/common/src/test/java/com/scottlogic/datahelix/generator/common/profile/FieldsTests.java b/common/src/test/java/com/scottlogic/datahelix/generator/common/profile/ProfileFieldsTests.java similarity index 89% rename from common/src/test/java/com/scottlogic/datahelix/generator/common/profile/FieldsTests.java rename to common/src/test/java/com/scottlogic/datahelix/generator/common/profile/ProfileFieldsTests.java index f8948d527..fae39271b 100644 --- a/common/src/test/java/com/scottlogic/datahelix/generator/common/profile/FieldsTests.java +++ b/common/src/test/java/com/scottlogic/datahelix/generator/common/profile/ProfileFieldsTests.java @@ -23,11 +23,11 @@ import java.util.Arrays; import static com.scottlogic.datahelix.generator.common.profile.FieldBuilder.createField; -class FieldsTests +class ProfileFieldsTests { @Test void equals_objIsNull_returnsFalse() { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("Test") ) @@ -43,7 +43,7 @@ void equals_objIsNull_returnsFalse() { @Test void equals_objTypeIsNotProfileFields_returnsFalse() { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("Test") ) @@ -59,7 +59,7 @@ void equals_objTypeIsNotProfileFields_returnsFalse() { @Test void equals_rowSpecFieldsLengthNotEqualToOtherObjectFieldsLength_returnsFalse() { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") @@ -67,7 +67,7 @@ void equals_rowSpecFieldsLengthNotEqualToOtherObjectFieldsLength_returnsFalse() ); boolean result = fields.equals( - new Fields( + new ProfileFields( Arrays.asList( createField("First Field") ) @@ -82,7 +82,7 @@ void equals_rowSpecFieldsLengthNotEqualToOtherObjectFieldsLength_returnsFalse() @Test void equals_rowSpecFieldsLengthEqualToOterObjectFieldsLengthButValuesDiffer_returnsFalse() { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") @@ -90,7 +90,7 @@ void equals_rowSpecFieldsLengthEqualToOterObjectFieldsLengthButValuesDiffer_retu ); boolean result = fields.equals( - new Fields( + new ProfileFields( Arrays.asList( createField("First Field"), createField("Third Field") @@ -106,7 +106,7 @@ void equals_rowSpecFieldsLengthEqualToOterObjectFieldsLengthButValuesDiffer_retu @Test void equals_rowSpecFieldsAreEqualToTheFieldsOfTheOtherObject_returnsTrue() { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") @@ -114,7 +114,7 @@ void equals_rowSpecFieldsAreEqualToTheFieldsOfTheOtherObject_returnsTrue() { ); boolean result = fields.equals( - new Fields( + new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") @@ -130,13 +130,13 @@ void equals_rowSpecFieldsAreEqualToTheFieldsOfTheOtherObject_returnsTrue() { @Test void hashCode_valuesinFieldsDifferInSize_returnsDifferentHashCodes() { - Fields firstFields = new Fields( + Fields firstFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") ) ); - Fields secondFields = new Fields( + Fields secondFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field"), @@ -156,13 +156,13 @@ void hashCode_valuesinFieldsDifferInSize_returnsDifferentHashCodes() { @Test void hashCode_valuesInFieldsAreEqualSizeButValuesDiffer_returnsDifferentHashCodes() { - Fields firstFields = new Fields( + Fields firstFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") ) ); - Fields secondFields = new Fields( + Fields secondFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Third Field") @@ -181,13 +181,13 @@ void hashCode_valuesInFieldsAreEqualSizeButValuesDiffer_returnsDifferentHashCode @Test void hashCode_valuesInFieldsAreEqual_identicalHashCodesAreReturned() { - Fields firstFields = new Fields( + Fields firstFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") ) ); - Fields secondFields = new Fields( + Fields secondFields = new ProfileFields( Arrays.asList( createField("First Field"), createField("Second Field") diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitioner.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitioner.java index 7614794d2..d65131356 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitioner.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitioner.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNodeBuilder; import com.scottlogic.datahelix.generator.core.decisiontree.DecisionNode; import com.scottlogic.datahelix.generator.core.fieldspecs.relations.FieldSpecRelation; @@ -88,12 +89,12 @@ public Stream splitTreeIntoPartitions(DecisionTree decisionTree) { .addRelations(partition.getRelations()) .setDecisions(partition.getDecisionNodes()) .build(), - new Fields(new ArrayList<>(partition.fields)) + new ProfileFields(new ArrayList<>(partition.fields)) )), unpartitionedFields .map(field -> new DecisionTree( new ConstraintNodeBuilder().build(), - new Fields(Collections.singletonList(field)) + new ProfileFields(Collections.singletonList(field)) )) ); } diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java index b5e99fc1d..5571de763 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/ExtentAugmentedFields.java @@ -21,16 +21,21 @@ import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.common.profile.SpecificFieldType; -public class ExtentAugmentedFields extends Fields { +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +public class ExtentAugmentedFields implements Fields { private static final SpecificFieldType integer = new SpecificFieldType("integer", FieldType.NUMERIC, null); + private static final String minField = "min"; + private static final String maxField = "max"; + private static final Field min = new Field(minField, integer, false, null, false, true, null); + private static final Field max = new Field(maxField, integer, false, null, false, true, null); - public static final String minField = "min"; - public static final String maxField = "max"; - public static final Field min = new Field(minField, integer, false, null, false, true, null); - public static final Field max = new Field(maxField, integer, false, null, false, true, null); + private final Fields underlying; public ExtentAugmentedFields(Fields fields) { - super(fields.asList()); + this.underlying = fields; } @Override @@ -43,6 +48,47 @@ public Field getByName(String fieldName) { return max; } - return super.getByName(fieldName); + return underlying.getByName(fieldName); + } + + @Override + public int size() { + return underlying.size(); + } + + @Override + public Stream stream() { + return Stream.concat( + underlying.stream(), + Stream.of(min, max)); + } + + @Override + public Stream getExternalStream() { + return underlying.getExternalStream(); + } + + @Override + public List asList() { + return underlying.asList(); + } + + @Override + public Iterator iterator() { + return underlying.iterator(); + } + + public boolean isExtentField(Field field) { + return field.equals(min) || field.equals(max); + } + + public OneToManyRange applyExtent(OneToManyRange range, Field field, int extent) { + if (field.equals(min)) { + return range.withMin(extent); + } else if (field.equals(max)) { + return range.withMax(extent); + } + + return range; } } diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java index 83ed5425d..408858aa9 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeResolver.java @@ -35,16 +35,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; public class OneToManyRangeResolver { private final DecisionTreeFactory factory; private final TreePruner treePruner; - private static final Field min = ExtentAugmentedFields.min; - private static final Field max = ExtentAugmentedFields.max; - @Inject public OneToManyRangeResolver( DecisionTreeFactory factory, @@ -56,8 +51,9 @@ public OneToManyRangeResolver( public OneToManyRange getRange(Fields profileFields, Collection constraints, GeneratedObject generatedObject) { OneToManyRange range = new OneToManyRange(0, null); + ExtentAugmentedFields extentAugmentedFields = new ExtentAugmentedFields(profileFields); DecisionTree tree = factory.analyse(new Profile( - getFields(profileFields), + extentAugmentedFields, new ArrayList<>(constraints), Collections.emptyList()) ); @@ -76,25 +72,19 @@ public OneToManyRange getRange(Fields profileFields, Collection cons //read the root-level properties for and if there are any decisions range = rootNode.getAtomicConstraints() .stream() - .filter(ac -> ac.getField().equals(min) || ac.getField().equals(max)) + .filter(ac -> extentAugmentedFields.isExtentField(ac.getField())) .reduce( range, - OneToManyRangeResolver::applyAtomicConstraint, + (r, atomicConstraint) -> applyAtomicConstraint(r, atomicConstraint, extentAugmentedFields), (a, b) -> null); return range; } - private static OneToManyRange applyAtomicConstraint(OneToManyRange range, AtomicConstraint atomicConstraint) { + private static OneToManyRange applyAtomicConstraint(OneToManyRange range, AtomicConstraint atomicConstraint, ExtentAugmentedFields extentAugmentedFields) { Field field = atomicConstraint.getField(); - if (field.equals(min)) { - return range.withMin(getExtent(atomicConstraint)); - } else if (field.equals(max)) { - return range.withMax(getExtent(atomicConstraint)); - } - - return range; + return extentAugmentedFields.applyExtent(range, field, getExtent(atomicConstraint)); } private static int getExtent(AtomicConstraint atomicConstraint) { @@ -136,12 +126,4 @@ private ConstraintNode applyGeneratedData(Fields profileFields, ConstraintNode r (a, b) -> null ); } - - private List getFields(Fields profileFields) { - return Stream.concat( - profileFields.asList().stream(), - Stream.of(min, max)) - .collect(Collectors.toList()); - - } } diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java index 60f616d8e..0dc292ea0 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/profile/Profile.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; import com.scottlogic.datahelix.generator.core.profile.relationships.Relationship; @@ -31,11 +32,11 @@ public class Profile { private final Collection relationships; public Profile(List fields, Collection constraints, Collection relationships) { - this(null, new Fields(fields), constraints, relationships); + this(null, new ProfileFields(fields), constraints, relationships); } public Profile(List fields, Collection constraints, Collection relationships, String description) { - this(description, new Fields(fields), constraints, relationships); + this(description, new ProfileFields(fields), constraints, relationships); } public Profile(Fields fields, Collection constraints, Collection relationships) { diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java index b2e7d5f68..36f9f8cd6 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeFactoryTests.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.common.util.NumberUtils; import com.scottlogic.datahelix.generator.common.whitelist.DistributedList; import com.scottlogic.datahelix.generator.common.whitelist.WeightedElement; @@ -73,7 +74,7 @@ private void givenConstraints(Constraint... constraints) { private DecisionTree getActualOutput() { if (this.actualOutput == null) { Profile testInput = new Profile( - new Fields( + new ProfileFields( Arrays.asList(this.fieldA, this.fieldB, this.fieldC)), this.constraints, Collections.emptyList()); @@ -113,7 +114,7 @@ void shouldReturnAnalysedProfileWithCorrectFields() { DecisionTree testOutput = testObject.analyse(testInput); Fields actualFields = testOutput.getFields(); - Fields expected = new Fields(inputFieldList); + Fields expected = new ProfileFields(inputFieldList); assertThat(actualFields, sameBeanAs(expected)); } diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeOptimiserTest.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeOptimiserTest.java index 7f633cc69..c06524eb2 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeOptimiserTest.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeOptimiserTest.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import org.junit.jupiter.api.Test; import java.util.Collections; @@ -53,7 +54,7 @@ public void optimise_circularDependency() { .where(B).isNotNull()) .build(); - ConstraintNode actual = optimiser.optimiseTree(new DecisionTree(original, new Fields(Collections.EMPTY_LIST))) + ConstraintNode actual = optimiser.optimiseTree(new DecisionTree(original, new ProfileFields(Collections.EMPTY_LIST))) .getRootNode(); assertThat(actual, sameBeanAs(original)); @@ -86,7 +87,7 @@ public void optimise_oneCommonIf(){ .where(A).isNotInSet("a1")) .build(); - ConstraintNode actual = optimiser.optimiseTree(new DecisionTree(original, new Fields(Collections.EMPTY_LIST))) + ConstraintNode actual = optimiser.optimiseTree(new DecisionTree(original, new ProfileFields(Collections.EMPTY_LIST))) .getRootNode(); assertThat(actual, sameBeanAs(original)); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeSimplifierTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeSimplifierTests.java index bbdc21f15..acc7dba14 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeSimplifierTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/DecisionTreeSimplifierTests.java @@ -16,20 +16,24 @@ package com.scottlogic.datahelix.generator.core.decisiontree; +import com.scottlogic.datahelix.generator.common.SetUtils; import com.scottlogic.datahelix.generator.common.profile.Field; -import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; +import com.scottlogic.datahelix.generator.common.whitelist.DistributedList; +import com.scottlogic.datahelix.generator.common.whitelist.WeightedElement; import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.AtomicConstraint; import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.InSetConstraint; import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.IsNullConstraint; -import com.scottlogic.datahelix.generator.common.whitelist.DistributedList; -import com.scottlogic.datahelix.generator.common.whitelist.WeightedElement; -import com.scottlogic.datahelix.generator.common.SetUtils; import org.junit.Assert; import org.junit.jupiter.api.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; + import static com.scottlogic.datahelix.generator.common.profile.FieldBuilder.createField; class DecisionTreeSimplifierTests { @@ -56,7 +60,7 @@ void simplify_decisionContainsSingleOptiontWithMatchingConstraintOnRootNode_does ) ) )).build(), - new Fields( + new ProfileFields( new ArrayList() {{ add(createField("Field 1")); }} ) ); @@ -83,7 +87,7 @@ void simplify_decisionContainsSingleOptionWithDifferingConstraintOnRootNode_simp ) ) )).build(), - new Fields( + new ProfileFields( new ArrayList() {{ add(createField("Field 1")); }} ) ); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java index 631a50ca3..b2d33aee2 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/RowSpecTreeSolverTests.java @@ -17,6 +17,7 @@ package com.scottlogic.datahelix.generator.core.decisiontree; import com.scottlogic.datahelix.generator.common.profile.Field; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.profile.Profile; import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.core.profile.constraints.Constraint; @@ -85,7 +86,7 @@ void test() final Field currency = createField("currency"); final Field city = createField("city"); - Fields fields = new Fields(Arrays.asList(country, currency, city)); + Fields fields = new ProfileFields(Arrays.asList(country, currency, city)); List constraints = Arrays.asList( new ConditionalConstraint( diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/ConstraintToFieldMapperTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/ConstraintToFieldMapperTests.java index 562df3820..4ae5f2975 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/ConstraintToFieldMapperTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/ConstraintToFieldMapperTests.java @@ -19,6 +19,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.FieldBuilder; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNodeBuilder; import com.scottlogic.datahelix.generator.core.decisiontree.DecisionNode; import com.scottlogic.datahelix.generator.core.profile.constraints.atomic.InSetConstraint; @@ -116,7 +117,7 @@ void shouldMapTopLevelConstraintsToNestedFields() { void beforeEach() { constraintsSet = new HashSet<>(); decisionsSet = new HashSet<>(); - fields = new Fields(Collections.emptyList()); + fields = new ProfileFields(Collections.emptyList()); mappings = null; } @@ -134,7 +135,7 @@ private void givenDecisions(DecisionNode... decisions) { } private void givenFields(String... fieldNames) { - fields = new Fields( + fields = new ProfileFields( Arrays.stream(fieldNames) .map(FieldBuilder::createField) .collect(Collectors.toList())); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitionerTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitionerTests.java index eac5566bd..88754be63 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitionerTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/decisiontree/treepartitioning/TreePartitionerTests.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.FieldBuilder; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.common.whitelist.DistributedList; import com.scottlogic.datahelix.generator.common.whitelist.WeightedElement; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNode; @@ -289,7 +290,7 @@ private DecisionNode decision(ConstraintNode... constraints) { } private Fields fields(String... fieldNames) { - return new Fields( + return new ProfileFields( Stream.of(fieldNames) .map(FieldBuilder::createField) .collect(Collectors.toList())); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/fieldspecs/RowSpecMergerTest.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/fieldspecs/RowSpecMergerTest.java index 1eaf64a10..033ca4b53 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/fieldspecs/RowSpecMergerTest.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/fieldspecs/RowSpecMergerTest.java @@ -19,6 +19,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.common.profile.FieldType; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -35,7 +36,7 @@ class RowSpecMergerTest { FieldSpec notNull = FieldSpecFactory.fromType(FieldType.STRING).withNotNull(); Field A = createField("A"); Field B = createField("B"); - Fields fields = new Fields(Arrays.asList(A, B)); + Fields fields = new ProfileFields(Arrays.asList(A, B)); @Test void merge_notContradictoryForField() { diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/UpfrontTreePrunerTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/UpfrontTreePrunerTests.java index a633a9df9..c111c3b15 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/UpfrontTreePrunerTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/UpfrontTreePrunerTests.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.builders.TestConstraintNodeBuilder; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNode; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNodeBuilder; @@ -63,10 +64,10 @@ void runUpfrontPrune_withOneField_returnsPrunedTree() { fieldSpecs.put(fieldA, FieldSpecFactory.fromType(fieldA.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); DecisionTree treeMarkedWithContradictions = new DecisionTree( new ConstraintNodeBuilder().build().builder().markNode(NodeMarking.CONTRADICTORY).build(), - new Fields(fields)); + new ProfileFields(fields)); Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.of(prunedRoot)); Mockito.when(contradictionValidator.markContradictions(tree)).thenReturn(treeMarkedWithContradictions); @@ -88,11 +89,11 @@ void runUpfrontPrune_withTwoFields_returnsPrunedTree() { fieldSpecs.put(fieldB, FieldSpecFactory.fromType(fieldB.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); DecisionTree treeMarkedWithContradictions = new DecisionTree( new ConstraintNodeBuilder().build().builder().markNode(NodeMarking.CONTRADICTORY).build(), - new Fields(fields)); + new ProfileFields(fields)); Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.of(prunedRoot)); Mockito.when(contradictionValidator.markContradictions(tree)).thenReturn(treeMarkedWithContradictions); @@ -112,7 +113,7 @@ void runUpfrontPrune_whenTreeWhollyContradictory_returnsPrunedTree() { fieldSpecs.put(fieldA, FieldSpecFactory.fromType(fieldA.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); //Act Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.contradictory()); @@ -131,10 +132,10 @@ void runUpfrontPrune_whenTreeNotContradictory_reportsNothing() { fieldSpecs.put(fieldA, FieldSpecFactory.fromType(fieldA.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); DecisionTree completelyUnmarkedTree = new DecisionTree( new ConstraintNodeBuilder().build(), - new Fields(fields)); + new ProfileFields(fields)); //Act Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.of(unPrunedRoot)); @@ -154,13 +155,13 @@ void runUpfrontPrune_whenTreePartiallyContradictory_reportsPartialContradiction( fieldSpecs.put(fieldA, FieldSpecFactory.fromType(fieldA.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); ConstraintNode root = constraintNode() .withDecision( constraintNode().markNode(NodeMarking.CONTRADICTORY)).build(); DecisionTree treeMarkedWithContradictions = new DecisionTree( root, - new Fields(fields)); + new ProfileFields(fields)); //Act Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.of(root)); @@ -182,10 +183,10 @@ void runUpfrontPrune_whenTreeWhollyContradictory_reportsFullContradiction() { fieldSpecs.put(fieldA, FieldSpecFactory.fromType(fieldA.getType())); ConstraintNode unPrunedRoot = Mockito.mock(ConstraintNode.class); - DecisionTree tree = new DecisionTree(unPrunedRoot, new Fields(fields)); + DecisionTree tree = new DecisionTree(unPrunedRoot, new ProfileFields(fields)); DecisionTree treeMarkedWithContradictions = new DecisionTree( new ConstraintNodeBuilder().build().builder().markNode(NodeMarking.CONTRADICTORY).build(), - new Fields(fields)); + new ProfileFields(fields)); //Act Mockito.when(treePruner.pruneConstraintNode(unPrunedRoot, fieldSpecs)).thenReturn(Merged.contradictory()); @@ -228,7 +229,7 @@ public void runUpfrontPrune_forNonContradictoryTreeWithOneNode_reportsNoContradi .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -252,7 +253,7 @@ public void runUpfrontPrune_forNonContradictoryTreeWithTwoNonContradictoryChildr .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -285,7 +286,7 @@ public void runUpfrontPrune_forNonContradictoryTreeWithContradictionsThatAreNotR .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -322,7 +323,7 @@ public void runUpfrontPrune_forPartiallyContradictoryTreeWithTwoContradictionsIn .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -348,7 +349,7 @@ public void runUpfrontPrune_forNonContradictoryTreeWithContradictionInOneBranch_ .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -373,7 +374,7 @@ public void runUpfrontPrune_forPartiallyContradictoryTreeWithOneContradictoryChi .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -404,7 +405,7 @@ public void runUpfrontPrune_forPartiallyContradictoryTreeWithRootContradictingWi .where(fieldB).isNull())) .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -441,7 +442,7 @@ public void runUpfrontPrune_forPartiallyContradictoryTreeWithOneContradictionDee .where(fieldB).isNull())) .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -480,7 +481,7 @@ public void runUpfrontPrune_forPartiallyContradictoryTreeWithTwoSelfContradictin ) .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -503,7 +504,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithOnlyRoot_reportsFul .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -531,7 +532,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithContradictoryRoot_r .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -561,7 +562,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithEveryNodeContradict .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -592,7 +593,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithContradictionDeepIn .where(fieldA).isNotNull()))) .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -622,7 +623,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithAllContradictingNod .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); @@ -652,7 +653,7 @@ public void runUpfrontPrune_forWhollyContradictoryProfileWithNonContradictingRoo .build(); - DecisionTree tree = new DecisionTree(root, new Fields(fields)); + DecisionTree tree = new DecisionTree(root, new ProfileFields(fields)); //Act upfrontPruner.runUpfrontPrune(tree, monitor); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/databags/RowSpecDataBagGeneratorTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/databags/RowSpecDataBagGeneratorTests.java index c79b74d17..1ab792f24 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/databags/RowSpecDataBagGeneratorTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/databags/RowSpecDataBagGeneratorTests.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.builders.DataBagBuilder; import com.scottlogic.datahelix.generator.core.fieldspecs.FieldSpec; import com.scottlogic.datahelix.generator.core.fieldspecs.RowSpec; @@ -43,7 +44,7 @@ class RowSpecDataBagGeneratorTests { private Field field = createField("Field1"); Field field2 = createField("field2"); Field field3 = createField("field3"); - private Fields fields = new Fields(Collections.singletonList(field)); + private Fields fields = new ProfileFields(Collections.singletonList(field)); private FieldSpec fieldSpec = mock(FieldSpec.class); private FieldSpec fieldSpec2 = mock(FieldSpec.class); private FieldSpec fieldSpec3 = mock(FieldSpec.class); @@ -80,7 +81,7 @@ void factoryIsCalledForEachField() { put(field2, fieldSpec2); put(field3, fieldSpec3); }}; RowSpec rowSpec = new RowSpec( - new Fields(Arrays.asList(field2, field, field3)), + new ProfileFields(Arrays.asList(field2, field, field3)), map, Collections.emptyList()); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/grouped/RowSpecGrouperTest.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/grouped/RowSpecGrouperTest.java index 8e97fbd74..faedd1af1 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/grouped/RowSpecGrouperTest.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/grouped/RowSpecGrouperTest.java @@ -19,6 +19,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.fieldspecs.FieldSpec; import com.scottlogic.datahelix.generator.core.fieldspecs.FieldSpecFactory; import com.scottlogic.datahelix.generator.core.fieldspecs.FieldSpecGroup; @@ -40,7 +41,7 @@ class RowSpecGrouperTest { void createGroups_withTwoRelatedFields_givesOneGroupOfSizeOne() { Field first = createField("first"); Field second = createField("second"); - Fields fields = new Fields(Arrays.asList(first, second)); + Fields fields = new ProfileFields(Arrays.asList(first, second)); Map fieldSpecMap = fieldSpecMapOf(first, second); @@ -59,7 +60,7 @@ void createGroups_withTwoAndOneFields_givesTwoGroups() { Field first = createField("first"); Field second = createField("second"); Field third = createField("third"); - Fields fields = new Fields(Arrays.asList(first, second, third)); + Fields fields = new ProfileFields(Arrays.asList(first, second, third)); Map fieldSpecMap = fieldSpecMapOf(first, second, third); @@ -78,7 +79,7 @@ void createGroups_withThreeIndependentFields_givesThreeGroups() { Field first = createField("first"); Field second = createField("second"); Field third = createField("third"); - Fields fields = new Fields(Arrays.asList(first, second, third)); + Fields fields = new ProfileFields(Arrays.asList(first, second, third)); Map fieldSpecMap = fieldSpecMapOf(first, second, third); @@ -96,7 +97,7 @@ void createGroups_withThreeCodependentFields_givesOneGroup() { Field first = createField("first"); Field second = createField("second"); Field third = createField("third"); - Fields fields = new Fields(Arrays.asList(first, second, third)); + Fields fields = new ProfileFields(Arrays.asList(first, second, third)); Map fieldSpecMap = fieldSpecMapOf(first, second, third); @@ -114,7 +115,7 @@ void createGroups_withThreeRelatedFieldsWithACircularLink_givesOneGroup() { Field first = createField("first"); Field second = createField("second"); Field third = createField("third"); - Fields fields = new Fields(Arrays.asList(first, second, third)); + Fields fields = new ProfileFields(Arrays.asList(first, second, third)); Map fieldSpecMap = fieldSpecMapOf(first, second, third); @@ -138,7 +139,7 @@ void createGroups_withFiveFields_correctlyGroups() { Field fourth = createField("fourth"); Field fifth = createField("fifth"); - Fields fields = new Fields(Arrays.asList(first, second, third, fourth, fifth)); + Fields fields = new ProfileFields(Arrays.asList(first, second, third, fourth, fifth)); Map fieldSpecMap = fieldSpecMapOf(first, second, third, fourth, fifth); @@ -159,7 +160,7 @@ void createGroups_withMultipleLinksBetweenTwoFields_givesOneGroup() { Field first = createField("first"); Field second = createField("second"); - Fields fields = new Fields(Arrays.asList(first, second)); + Fields fields = new ProfileFields(Arrays.asList(first, second)); Map fieldSpecMap = fieldSpecMapOf(first, second); diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/walker/decisionbased/RowSpecTreeSolverTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/walker/decisionbased/RowSpecTreeSolverTests.java index d4d7146cb..0b3526c05 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/walker/decisionbased/RowSpecTreeSolverTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/walker/decisionbased/RowSpecTreeSolverTests.java @@ -17,6 +17,7 @@ import com.scottlogic.datahelix.generator.common.profile.Field; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.core.builders.TestConstraintNodeBuilder; import com.scottlogic.datahelix.generator.core.decisiontree.ConstraintNode; import com.scottlogic.datahelix.generator.core.decisiontree.DecisionTree; @@ -37,7 +38,7 @@ class RowSpecTreeSolverTests { private Field fieldA = createField("A"); private Field fieldB = createField("B"); - private Fields fields = new Fields(Arrays.asList(fieldA, fieldB)); + private Fields fields = new ProfileFields(Arrays.asList(fieldA, fieldB)); private FieldSpecMerger fieldSpecMerger = new FieldSpecMerger(); private ConstraintReducer constraintReducer = new ConstraintReducer(fieldSpecMerger); private TreePruner pruner = new TreePruner(fieldSpecMerger, constraintReducer, new FieldSpecHelper()); diff --git a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvDataSetWriterTest.java b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvDataSetWriterTest.java index 2ce42d532..b31ea5c46 100644 --- a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvDataSetWriterTest.java +++ b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvDataSetWriterTest.java @@ -18,10 +18,7 @@ package com.scottlogic.datahelix.generator.output.writer.csv; import com.scottlogic.datahelix.generator.common.output.GeneratedObject; -import com.scottlogic.datahelix.generator.common.profile.Field; -import com.scottlogic.datahelix.generator.common.profile.Fields; -import com.scottlogic.datahelix.generator.common.profile.SpecificFieldType; -import com.scottlogic.datahelix.generator.common.profile.StandardSpecificFieldType; +import com.scottlogic.datahelix.generator.common.profile.*; import com.scottlogic.datahelix.generator.output.writer.DataSetWriter; import org.junit.Assert; import org.junit.Test; @@ -45,7 +42,7 @@ public class CsvDataSetWriterTest { private Field fieldOne = new Field("one", StandardSpecificFieldType.STRING.toSpecificFieldType(),false,null,false, false, null); private Field fieldTwo = new Field("two", StandardSpecificFieldType.STRING.toSpecificFieldType(),false,null,false, false, null); - private Fields fields = new Fields(new ArrayList<>(Arrays.asList( + private Fields fields = new ProfileFields(new ArrayList<>(Arrays.asList( fieldOne, fieldTwo ))); diff --git a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvOutputWriterFactoryTests.java b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvOutputWriterFactoryTests.java index 2c305893d..9f1b8adfc 100644 --- a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvOutputWriterFactoryTests.java +++ b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/csv/CsvOutputWriterFactoryTests.java @@ -19,6 +19,7 @@ import com.scottlogic.datahelix.generator.common.profile.FieldBuilder; import com.scottlogic.datahelix.generator.common.profile.Fields; import com.scottlogic.datahelix.generator.common.output.GeneratedObject; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.output.writer.DataSetWriter; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -98,7 +99,7 @@ void writeRow_withDateTimeGranularToAMillisecondAndNoFormat_shouldFormatDateUsin @Test void writeRow_withInternalFields_shouldNotWriteInternalFields() throws IOException { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("External"), createInternalField("Internal") @@ -112,7 +113,7 @@ void writeRow_withInternalFields_shouldNotWriteInternalFields() throws IOExcepti } private static Fields fields(String ...names) { - return new Fields( + return new ProfileFields( Arrays.stream(names) .map(FieldBuilder::createField) .collect(Collectors.toList())); diff --git a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/json/JsonOutputWriterFactoryTest.java b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/json/JsonOutputWriterFactoryTest.java index 5a4bb8447..a9228453a 100644 --- a/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/json/JsonOutputWriterFactoryTest.java +++ b/output/src/test/java/com/scottlogic/datahelix/generator/output/writer/json/JsonOutputWriterFactoryTest.java @@ -18,6 +18,7 @@ import com.scottlogic.datahelix.generator.common.output.GeneratedObject; import com.scottlogic.datahelix.generator.common.profile.FieldBuilder; import com.scottlogic.datahelix.generator.common.profile.Fields; +import com.scottlogic.datahelix.generator.common.profile.ProfileFields; import com.scottlogic.datahelix.generator.output.writer.DataSetWriter; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -39,7 +40,7 @@ class JsonOutputWriterFactoryTest { @Test void writer_whereStreamingJson__shouldOutputNewLineDelimiterRows() throws IOException { - Fields fields = new Fields(Collections.singletonList(FieldBuilder.createField("my_field"))); + Fields fields = new ProfileFields(Collections.singletonList(FieldBuilder.createField("my_field"))); expectJson( fields, @@ -49,7 +50,7 @@ void writer_whereStreamingJson__shouldOutputNewLineDelimiterRows() throws IOExce @Test void writer_whereNotStreamingJson__shouldOutputRowsWrappedInAnArray() throws IOException { - Fields fields = new Fields(Collections.singletonList(FieldBuilder.createField("my_field"))); + Fields fields = new ProfileFields(Collections.singletonList(FieldBuilder.createField("my_field"))); expectJson( fields, @@ -59,7 +60,7 @@ void writer_whereNotStreamingJson__shouldOutputRowsWrappedInAnArray() throws IOE @Test void writeRow_withInternalFields_shouldNotWriteInternalFields() throws IOException { - Fields fields = new Fields( + Fields fields = new ProfileFields( Arrays.asList( createField("External"), createInternalField("Internal") diff --git a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/FieldService.java b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/FieldService.java index 528833cef..9fcc3c942 100644 --- a/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/FieldService.java +++ b/profile/src/main/java/com/scottlogic/datahelix/generator/profile/services/FieldService.java @@ -16,10 +16,7 @@ package com.scottlogic.datahelix.generator.profile.services; -import com.scottlogic.datahelix.generator.common.profile.Field; -import com.scottlogic.datahelix.generator.common.profile.Fields; -import com.scottlogic.datahelix.generator.common.profile.SpecificFieldType; -import com.scottlogic.datahelix.generator.common.profile.StandardSpecificFieldType; +import com.scottlogic.datahelix.generator.common.profile.*; import com.scottlogic.datahelix.generator.profile.dtos.FieldDTO; import com.scottlogic.datahelix.generator.profile.dtos.ProfileDTO; import com.scottlogic.datahelix.generator.profile.dtos.constraints.ConstraintDTO; @@ -39,7 +36,7 @@ public class FieldService { public Fields createFields(ProfileDTO dto) { List fields = dto.fields.stream().map(this::createRegularField).collect(Collectors.toList()); getInMapFieldNames(dto.constraints).stream().map(this::createInMapField).forEach(fields::add); - return new Fields(fields); + return new ProfileFields(fields); } public SpecificFieldType specificFieldTypeFromString(String type, String formatting) { From 266d2ff310255973c7387cc52283ef73de74f34b Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 07:37:25 +0100 Subject: [PATCH 03/12] fix(#841): Refactor data generator provider --- .../generator/core/guice/DataGeneratorProvider.java | 12 +++++++++++- .../generator/core/guice/GeneratorModule.java | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java index 0420c17cd..8f8692259 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java @@ -19,27 +19,37 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; +import com.scottlogic.datahelix.generator.core.config.detail.MonitorType; import com.scottlogic.datahelix.generator.core.generation.*; public class DataGeneratorProvider implements Provider { private final DataGenerator coreGenerator; private final long maxRows; + private final MonitorType monitorType; private final DataGeneratorMonitor monitor; @Inject public DataGeneratorProvider( DecisionTreeDataGenerator coreGenerator, @Named("config:maxRows") long maxRows, + MonitorType monitorType, DataGeneratorMonitor monitor) { this.coreGenerator = coreGenerator; this.maxRows = maxRows; + this.monitorType = monitorType; this.monitor = monitor; } @Override public DataGenerator get() { + DataGenerator limitingGenerator = new LimitingDataGenerator(coreGenerator, maxRows); + + if (monitorType == MonitorType.QUIET){ + return limitingGenerator; + } + return new MonitoringDataGenerator( - new LimitingDataGenerator(coreGenerator, maxRows), + limitingGenerator, monitor); } } diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java index 51c0afa97..d14af8ef2 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java @@ -21,6 +21,7 @@ import com.google.inject.name.Names; import com.scottlogic.datahelix.generator.core.config.detail.CombinationStrategyType; import com.scottlogic.datahelix.generator.core.config.detail.DataGenerationType; +import com.scottlogic.datahelix.generator.core.config.detail.MonitorType; import com.scottlogic.datahelix.generator.core.generation.*; import com.scottlogic.datahelix.generator.core.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.datahelix.generator.core.utils.JavaUtilRandomNumberGenerator; @@ -59,6 +60,9 @@ protected void configure() { .annotatedWith(Names.named("config:maxRows")) .toInstance(generationConfigSource.getMaxRows()); + bind(MonitorType.class) + .toInstance(generationConfigSource.getMonitorType()); + // Bind known implementations - no user input required bind(DataGeneratorMonitor.class).to(AbstractDataGeneratorMonitor.class); bind(DataGenerator.class).toProvider(DataGeneratorProvider.class); From f158133b1538ef8a64bec11ce0bd9848b798fbcc Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 07:37:29 +0100 Subject: [PATCH 04/12] fix(#841): Support infinite generation --- .../generation/GenerationConfigSource.java | 3 ++- .../guice/CombinationStrategyProvider.java | 2 +- .../core/guice/DataGeneratorProvider.java | 10 +++++--- .../generator/core/guice/GeneratorModule.java | 23 +++++++++++++++++-- .../generate/GenerateCommandLine.java | 16 ++++++++++--- .../utils/CucumberGenerationConfigSource.java | 7 +++++- 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/GenerationConfigSource.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/GenerationConfigSource.java index e33a3b9c4..571075743 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/GenerationConfigSource.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/GenerationConfigSource.java @@ -26,7 +26,8 @@ public interface GenerationConfigSource { DataGenerationType getGenerationType(); CombinationStrategyType getCombinationStrategyType(); - long getMaxRows(); + Long getMaxRows(); + boolean getInfiniteOutput(); MonitorType getMonitorType(); diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java index bf318b2b4..dabe5fc15 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java @@ -35,7 +35,7 @@ public CombinationStrategy get() { if (config.getGenerationType() == DataGenerationType.RANDOM){ // The minimal combination strategy doesn't reuse values for fields. // This is required to get truly random data. - return new MinimalCombinationStrategy(); + return new ExhaustiveCombinationStrategy(); } switch(config.getCombinationStrategyType()){ diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java index 8f8692259..586f1703f 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/DataGeneratorProvider.java @@ -22,16 +22,18 @@ import com.scottlogic.datahelix.generator.core.config.detail.MonitorType; import com.scottlogic.datahelix.generator.core.generation.*; +import javax.annotation.Nullable; + public class DataGeneratorProvider implements Provider { private final DataGenerator coreGenerator; - private final long maxRows; + private final Long maxRows; private final MonitorType monitorType; private final DataGeneratorMonitor monitor; @Inject public DataGeneratorProvider( DecisionTreeDataGenerator coreGenerator, - @Named("config:maxRows") long maxRows, + @Nullable @Named("config:maxRows") Long maxRows, MonitorType monitorType, DataGeneratorMonitor monitor) { this.coreGenerator = coreGenerator; @@ -42,7 +44,9 @@ public DataGeneratorProvider( @Override public DataGenerator get() { - DataGenerator limitingGenerator = new LimitingDataGenerator(coreGenerator, maxRows); + DataGenerator limitingGenerator = maxRows == null + ? coreGenerator + : new LimitingDataGenerator(coreGenerator, maxRows); if (monitorType == MonitorType.QUIET){ return limitingGenerator; diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java index d14af8ef2..2a4c905d1 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/GeneratorModule.java @@ -19,6 +19,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Singleton; import com.google.inject.name.Names; +import com.google.inject.util.Providers; import com.scottlogic.datahelix.generator.core.config.detail.CombinationStrategyType; import com.scottlogic.datahelix.generator.core.config.detail.DataGenerationType; import com.scottlogic.datahelix.generator.core.config.detail.MonitorType; @@ -30,6 +31,8 @@ import java.time.OffsetDateTime; +import static com.scottlogic.datahelix.generator.common.util.Defaults.DEFAULT_MAX_ROWS; + /** * Class to define default bindings for Guice injection. Utilises the generation config source to determine which * 'generate' classes should be bound for this execution run. @@ -56,9 +59,9 @@ protected void configure() { bind(DataGenerationType.class).toInstance(generationConfigSource.getGenerationType()); bind(CombinationStrategyType.class).toInstance(generationConfigSource.getCombinationStrategyType()); - bind(long.class) + bind(Long.class) .annotatedWith(Names.named("config:maxRows")) - .toInstance(generationConfigSource.getMaxRows()); + .toProvider(Providers.of(getMaxRows(generationConfigSource))); bind(MonitorType.class) .toInstance(generationConfigSource.getMonitorType()); @@ -73,4 +76,20 @@ protected void configure() { .annotatedWith(Names.named("config:internalRandomRowSpecStorage")) .toInstance(256); } + + private static Long getMaxRows(GenerationConfigSource generationConfigSource) { + Long requestedMaxRows = generationConfigSource.getMaxRows(); + + return requestedMaxRows == null + ? getDefaultMaxRows(generationConfigSource) + : requestedMaxRows; + } + + private static Long getDefaultMaxRows(GenerationConfigSource generationConfigSource) { + if (generationConfigSource.getInfiniteOutput()){ + return null; + } + + return DEFAULT_MAX_ROWS; + } } diff --git a/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java b/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java index a72943462..da810512f 100644 --- a/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java +++ b/orchestrator/src/main/java/com/scottlogic/datahelix/generator/orchestrator/generate/GenerateCommandLine.java @@ -34,7 +34,6 @@ import java.nio.file.Path; import java.util.concurrent.Callable; -import static com.scottlogic.datahelix.generator.common.util.Defaults.DEFAULT_MAX_ROWS; import static com.scottlogic.datahelix.generator.core.config.detail.CombinationStrategyType.MINIMAL; import static com.scottlogic.datahelix.generator.core.config.detail.DataGenerationType.RANDOM; import static com.scottlogic.datahelix.generator.common.output.OutputFormat.CSV; @@ -116,7 +115,13 @@ public Integer call() throws Exception { @CommandLine.Option( names = {"-n", "--max-rows"}, description = "Defines the maximum number of rows that should be generated") - private long maxRows = DEFAULT_MAX_ROWS; + private Long maxRows = null; + + @SuppressWarnings("FieldCanBeLocal") + @CommandLine.Option( + names = {"--infinite"}, + description = "Permits infinite generation of data") + private boolean infiniteGeneration = false; @SuppressWarnings("FieldCanBeLocal") @CommandLine.Option( @@ -194,10 +199,15 @@ public MonitorType getMonitorType() { } @Override - public long getMaxRows() { + public Long getMaxRows() { return maxRows; } + @Override + public boolean getInfiniteOutput() { + return infiniteGeneration; + } + public OutputFormat getOutputFormat() { return outputFormat; } diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java index 645a3179c..8ac007910 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java @@ -51,10 +51,15 @@ public MonitorType getMonitorType() { } @Override - public long getMaxRows() { + public Long getMaxRows() { return state.maxRows; } + @Override + public boolean getInfiniteOutput() { + return false; + } + @Override public Path getOutputPath() { return new File("mockFilePath").toPath(); From bc75e0621e3fd6ae92379556b77d2d5c8b5a40f7 Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 08:28:13 +0100 Subject: [PATCH 05/12] chore(#841): Update documentation to include use cases --- docs/RelationalData.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/RelationalData.md b/docs/RelationalData.md index 424d039d2..8b6f7814d 100644 --- a/docs/RelationalData.md +++ b/docs/RelationalData.md @@ -28,8 +28,8 @@ A relationship description can have the following properties | `name` | string | required | The name of the relationship, this is the name of the property in which the related data will be embedded | | `description` | string | optional | A description for the relationship, this is for documentation only - it has no bearing on the generation process | | `profileFile` | string | optional* | A relative path to a profile file, that describes the data that should be generated within this relationship | -| `profile` | object | optional* | that describes the data that should be generated within this relationship | -| `extents` | array of `extents` | optional | the min and max number of records within a one-to-many relationship, if absent will be treated as a one-to-one relationship | +| `profile` | object | optional* | A profile that describes the data that should be generated within this relationship | +| `extents` | array of `extent` | optional | The _min_ and _max_ number of records within a one-to-many relationship, if absent will be treated as a one-to-one relationship | For example: ``` @@ -167,4 +167,31 @@ Note that data within `dependants` is: - Constrain the parent object by values in a relationship, i.e. - if `children` sub-objects have all `age`s > 18 then `councilTaxRateBand` = `A` - Constrain circular relationships to a maximum depth, i.e. - - Generate a tree of ancestors (or conversely children) from a given person (aka genealogical data) \ No newline at end of file + - Generate a tree of ancestors (or conversely children) from a given person (aka genealogical data) + +## How to use the feature +To be able to use the feature, you first need to work out how to build the profile. The generator needs a place to start producing data, this will depend on your relationships. Consider the following scenarios: + +### 1. One-to-many (Customers and their orders) +Two entities related with a simple foreign-key relationship, i.e. + +- A customer entity with customer details +- An order entity with order details and the id of the customer + +Create a profile that represents the customer initially, then embed the profile for the orders entity as a one-to-many relationship within this. You'll then get a collection of orders for each customer. A process of repeating each order (and grebbing the parent customer-id) will permit a collection of orders that can be injected into a database. + +### 2. Many-to-many (Parts and their orders) +Two entities, orders and parts (ignoring customer for simplicity), i.e. + +- An order entity which represents an order with some related parts +- A part entity which represents a purchasable item in a catalogue (ignoring stock quantity) + +Each order can have many parts and each part can be on many orders. Therefore, naturally, in a relational database there would be a Cross-reference table that contains the keys of the order and part in a row. + +To be able to use this feature to produce the data you need, you need to start at the Cross-Reference table, as this is the only unique 'entity' in this model. Therefore your profile would be constructed as follows: + +- Cross-Reference: an empty object (unless you want any additional data, e.g. requested-quantity), except for it's two one-to-many relationships + - Part: one-to-many collection of parts + - Order: one-to-many collection of orders + +From the output data you can derive all the orders that exist, all the parts that exist. Equally it is possible to dervive the orders that contain a part and conversely the parts that are contained in an order. \ No newline at end of file From 4c71acf93ba0304c64968f1a86dbb619704f494d Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 08:38:24 +0100 Subject: [PATCH 06/12] fix(#841): Fix OutputFormat for cucumber tests --- .../testframework/utils/CucumberGenerationConfigSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java index 8ac007910..e6604749e 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/cucumber/testframework/utils/CucumberGenerationConfigSource.java @@ -82,7 +82,7 @@ public boolean useStdOut() { @Override public OutputFormat getOutputFormat() { - return null; + return OutputFormat.JSON; } @Override From 76538bbc6cd28f022e90234e9e098c67dfcd84ac Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 08:58:33 +0100 Subject: [PATCH 07/12] fix(#641): Revert to using MinimalCombinationStrategy --- .../generator/core/guice/CombinationStrategyProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java index dabe5fc15..bf318b2b4 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/guice/CombinationStrategyProvider.java @@ -35,7 +35,7 @@ public CombinationStrategy get() { if (config.getGenerationType() == DataGenerationType.RANDOM){ // The minimal combination strategy doesn't reuse values for fields. // This is required to get truly random data. - return new ExhaustiveCombinationStrategy(); + return new MinimalCombinationStrategy(); } switch(config.getCombinationStrategyType()){ From 6a67c330293be1afc77f157f551d42007deb5d73 Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 09:31:04 +0100 Subject: [PATCH 08/12] chore(#841): Include commandline in error output --- .../endtoend/JarExecuteTests.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java index 05e3fde32..0601e4945 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java @@ -22,7 +22,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; @@ -31,13 +33,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class JarExecuteTests { + private static final Map commandLineMap = new HashMap<>(); + @Test void generateSuccessfullyFromJar() throws Exception { Process p = setupProcess("-p=src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/testprofile.profile.json"); List collectedOutput = collectOutputAndCloseProcess(p); - assertCsvOutputs(collectedOutput, "Generation successful", ""); + assertCsvOutputs( + collectedOutput, + "Generation successful", + "", + p); } @Test @@ -48,7 +56,8 @@ void generateSuccessfullyFromJarAndLoadFile() throws Exception { assertCsvOutputs(collectedOutput, "Generated successfully from file", - "Either load from file no longer works, or "); + "Either load from file no longer works, or ", + p); } @Test @@ -59,7 +68,8 @@ void generateSuccessfullyFromJarAndLoadFileWithinSubDirectory() throws Exception assertCsvOutputs(collectedOutput, "Generated successfully from file", - "Either load from file no longer works, or "); + "Either load from file no longer works, or ", + p); } @Test @@ -70,7 +80,8 @@ void generateRelationalDataSuccessfullyFromJar() throws Exception { assertJsonOutputs(collectedOutput, "shortName", - null); + null, + p); } @Test @@ -81,14 +92,18 @@ void generateRelationalDataSuccessfullyFromJarAndLoadFileWithinSubDirectory() th assertJsonOutputs(collectedOutput, "age", - "\"Generated successfully from file\""); + "\"Generated successfully from file\"", + p); } - private void assertCsvOutputs(List outputs, String expectedFinalMessage, String extraErrorMessage) { + private void assertCsvOutputs(List outputs, String expectedFinalMessage, String extraErrorMessage, Process process) { + String commandLine = commandLineMap.get(process); + String errorMessageOnFailure = "Jar test failed. This may have been caused by one of the following:" + "1) You have not built the jar. \n Try running Gradle Build. \n" + "2) System.out is being printed to (which interferes with streaming output) e.g. using 'printStackTrace'.\n" + "3) There is a bug in code conditional on whether it is running inside the JAR, e.g. in SupportedVersionsGetter. \n" + + "Command line used: '" + commandLine + "'\n" + outputs.stream().limit(5).collect(Collectors.joining("\n")); String fullErrorMessage = extraErrorMessage + errorMessageOnFailure; assertThat(fullErrorMessage, outputs.size(), is(greaterThanOrEqualTo(2))); @@ -96,11 +111,14 @@ private void assertCsvOutputs(List outputs, String expectedFinalMessage, assertEquals(expectedFinalMessage, outputs.get(outputs.size() - 1), fullErrorMessage); } - private void assertJsonOutputs(List outputs, String expectedJsonPropertyName, String expectedData) { + private void assertJsonOutputs(List outputs, String expectedJsonPropertyName, String expectedData, Process process) { + String commandLine = commandLineMap.get(process); + String errorMessageOnFailure = "Jar test failed. This may have been caused by one of the following:" + "1) You have not built the jar. \n Try running Gradle Build. \n" + "2) System.out is being printed to (which interferes with streaming output) e.g. using 'printStackTrace'.\n" + "3) There is a bug in code conditional on whether it is running inside the JAR, e.g. in SupportedVersionsGetter. \n" + + "Command line used: '" + commandLine + "'\n" + outputs.stream().limit(5).collect(Collectors.joining("\n")); assertThat(errorMessageOnFailure, outputs.size(), is(greaterThanOrEqualTo(1))); assertThat(errorMessageOnFailure, outputs.get(outputs.size() - 1), containsString("\"" + expectedJsonPropertyName + "\":")); @@ -124,8 +142,14 @@ private Process setupProcess(final String profile, final String outputFormat) th outputFormat == null ? "" : "--output-format=" + outputFormat); + pb.redirectErrorStream(true); - return pb.start(); + Process process = pb.start(); + + String commandLine = String.join(" ", pb.command()); + commandLineMap.put(process, commandLine); + + return process; } private List collectOutputAndCloseProcess(Process process) throws IOException, InterruptedException { From 7f65179ba1c8ecf0f8f9d5c62d4f085719b6d5fe Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 09:43:46 +0100 Subject: [PATCH 09/12] fix(#841): Update command format to exclude empty argument --- .../generator/orchestrator/endtoend/JarExecuteTests.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java index 0601e4945..2880ee117 100644 --- a/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java +++ b/orchestrator/src/test/java/com/scottlogic/datahelix/generator/orchestrator/endtoend/JarExecuteTests.java @@ -138,10 +138,11 @@ private Process setupProcess(final String profile, final String outputFormat) th "build/libs/datahelix.jar", profile, "--max-rows=1", - "--quiet", - outputFormat == null - ? "" - : "--output-format=" + outputFormat); + "--quiet"); + + if (outputFormat != null) { + pb.command().add("--output-format=" + outputFormat); + } pb.redirectErrorStream(true); Process process = pb.start(); From 51c9998dc3b18dff5269cfa75770d5d4d193300c Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 10:29:46 +0100 Subject: [PATCH 10/12] fix(#841): Fix isEmpty on a relationship range --- .../generator/core/generation/relationships/OneToManyRange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java index 4e4e0f929..b357edbda 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java @@ -50,7 +50,7 @@ public OneToManyRange withMax(int max) { } public boolean isEmpty() { - return this.max != null && this.min >= this.max; + return this.max != null && this.min > this.max; } @Override From e27f30310ff58017a3e8f899096f7c1f5174348c Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Fri, 15 May 2020 10:50:39 +0100 Subject: [PATCH 11/12] fix(#841): Add tests for OneToManyRange --- .../relationships/OneToManyRange.java | 2 +- .../relationships/OneToManyRangeTests.java | 134 ++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeTests.java diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java index b357edbda..24d430c75 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRange.java @@ -50,7 +50,7 @@ public OneToManyRange withMax(int max) { } public boolean isEmpty() { - return this.max != null && this.min > this.max; + return this.max != null && (this.min > this.max || (this.min == 0 && this.max == 0)); } @Override diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeTests.java new file mode 100644 index 000000000..f6cf6ea5a --- /dev/null +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/relationships/OneToManyRangeTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Scott Logic Ltd + * + * 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 com.scottlogic.datahelix.generator.core.generation.relationships; + +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class OneToManyRangeTests { + @Test + public void isEmpty_whenMinAndMaxAreEqual_returnsFalse(){ + OneToManyRange range = new OneToManyRange(1, 1); + + assertThat(range.isEmpty(), is(false)); + } + + @Test + public void isEmpty_whenMinIsGreaterThanMax_returnsTrue(){ + OneToManyRange range = new OneToManyRange(2, 1); + + assertThat(range.isEmpty(), is(true)); + } + + @Test + public void isEmpty_whenMaxIsNull_returnsFalse(){ + OneToManyRange range = new OneToManyRange(1, null); + + assertThat(range.isEmpty(), is(false)); + } + + @Test + public void isEmpty_whenMinAndMaxAre0_returnsTrue(){ + OneToManyRange range = new OneToManyRange(0, 0); + + assertThat(range.isEmpty(), is(true)); + } + + @Test + public void withMin_whereValueIsLessThanExistingMin_doesNotRetrictFurther() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMin(1); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(3)); + } + + @Test + public void withMin_whereValueIsEqualToExistingMin_doesNotRetrictFurther() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMin(2); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(3)); + } + + @Test + public void withMin_whereValueIsGreaterThanExistingMin_restrictsRangeToNewValue() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMin(3); + + assertThat(newRange.getMin(), is(3)); + assertThat(newRange.getMax(), is(3)); + } + + @Test + public void withMin_whereValueIsGreaterThanExistingMax_restrictsRangeToEmptyRange() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMin(4); + + assertThat(newRange.getMin(), is(4)); + assertThat(newRange.getMax(), is(3)); + assertThat(newRange.isEmpty(), is(true)); + } + + @Test + public void withMax_whereValueIsGreaterThanExistingMax_doesNotRetrictFurther() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMax(4); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(3)); + } + + @Test + public void withMax_whereValueIsEqualToExistingMax_doesNotRetrictFurther() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMax(3); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(3)); + } + + @Test + public void withMax_whereValueIsLessThanExistingMax_restrictsRangeToNewValue() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMax(2); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(2)); + } + + @Test + public void withMax_whereValueIsLessThanExistingMin_restrictsRangeEmptyRange() { + OneToManyRange range = new OneToManyRange(2, 3); + + OneToManyRange newRange = range.withMax(1); + + assertThat(newRange.getMin(), is(2)); + assertThat(newRange.getMax(), is(1)); + assertThat(newRange.isEmpty(), is(true)); + } +} From 577c27b6a720e53771c42a12c9fa566b4fe42cee Mon Sep 17 00:00:00 2001 From: Simon Laing Date: Thu, 28 May 2020 17:09:50 +0100 Subject: [PATCH 12/12] fix(#841): Rename class to remove ambiguity --- .../core/generation/DecisionTreeDataGenerator.java | 10 +++++----- ...sProcessor.java => RelationshipsDataGenerator.java} | 5 ++--- .../generation/DecisionTreeDataGeneratorTests.java | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) rename core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/{RelationshipsProcessor.java => RelationshipsDataGenerator.java} (96%) diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java index 049b620cc..ad401b2f6 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGenerator.java @@ -25,7 +25,7 @@ import com.scottlogic.datahelix.generator.core.decisiontree.treepartitioning.TreePartitioner; import com.scottlogic.datahelix.generator.core.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.datahelix.generator.core.generation.databags.DataBag; -import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsProcessor; +import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsDataGenerator; import com.scottlogic.datahelix.generator.core.generation.visualiser.Visualiser; import com.scottlogic.datahelix.generator.core.generation.visualiser.VisualiserFactory; import com.scottlogic.datahelix.generator.core.profile.Profile; @@ -45,7 +45,7 @@ public class DecisionTreeDataGenerator implements DataGenerator { private final CombinationStrategy partitionCombiner; private final UpfrontTreePruner upfrontTreePruner; private final VisualiserFactory visualiserFactory; - private final RelationshipsProcessor relationshipsProcessor; + private final RelationshipsDataGenerator relationshipsDataGenerator; @Inject public DecisionTreeDataGenerator( @@ -57,7 +57,7 @@ public DecisionTreeDataGenerator( CombinationStrategy combinationStrategy, UpfrontTreePruner upfrontTreePruner, VisualiserFactory visualiserFactory, - RelationshipsProcessor relationshipsProcessor) { + RelationshipsDataGenerator relationshipsDataGenerator) { this.decisionTreeGenerator = decisionTreeGenerator; this.treePartitioner = treePartitioner; this.treeOptimiser = optimiser; @@ -66,7 +66,7 @@ public DecisionTreeDataGenerator( this.partitionCombiner = combinationStrategy; this.upfrontTreePruner = upfrontTreePruner; this.visualiserFactory = visualiserFactory; - this.relationshipsProcessor = relationshipsProcessor; + this.relationshipsDataGenerator = relationshipsDataGenerator; } @Override @@ -86,7 +86,7 @@ public Stream generateData(Profile profile) { .map(tree -> () -> treeWalker.walk(tree)); return partitionCombiner.permute(partitionedDataBags) - .map(generatedObject -> relationshipsProcessor.produceRelationalObjects( + .map(generatedObject -> relationshipsDataGenerator.produceRelationalObjects( profile.getFields(), generatedObject, profile.getRelationships(), diff --git a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsDataGenerator.java similarity index 96% rename from core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java rename to core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsDataGenerator.java index ba9c56293..f970a9dc8 100644 --- a/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsProcessor.java +++ b/core/src/main/java/com/scottlogic/datahelix/generator/core/generation/relationships/RelationshipsDataGenerator.java @@ -17,7 +17,6 @@ package com.scottlogic.datahelix.generator.core.generation.relationships; import com.google.inject.Inject; -import com.google.inject.name.Named; import com.scottlogic.datahelix.generator.common.output.GeneratedObject; import com.scottlogic.datahelix.generator.common.output.OutputFormat; import com.scottlogic.datahelix.generator.common.profile.Fields; @@ -26,13 +25,13 @@ import java.util.Collection; -public class RelationshipsProcessor { +public class RelationshipsDataGenerator { private final OutputFormat outputFormat; private final RelationshipProcessor oneToOne; private final RelationshipProcessor oneToMany; @Inject - public RelationshipsProcessor( + public RelationshipsDataGenerator( OutputFormat outputFormat, OneToOneRelationshipProcessor oneToOne, OneToManyRelationshipProcessor oneToMany) { diff --git a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java index 625b5c855..5a0609c4f 100644 --- a/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java +++ b/core/src/test/java/com/scottlogic/datahelix/generator/core/generation/DecisionTreeDataGeneratorTests.java @@ -24,7 +24,7 @@ import com.scottlogic.datahelix.generator.core.decisiontree.treepartitioning.TreePartitioner; import com.scottlogic.datahelix.generator.core.generation.combinationstrategies.CombinationStrategy; import com.scottlogic.datahelix.generator.core.generation.databags.DataBag; -import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsProcessor; +import com.scottlogic.datahelix.generator.core.generation.relationships.RelationshipsDataGenerator; import com.scottlogic.datahelix.generator.core.generation.visualiser.Visualiser; import com.scottlogic.datahelix.generator.core.generation.visualiser.VisualiserFactory; import com.scottlogic.datahelix.generator.core.profile.Profile; @@ -69,7 +69,7 @@ void setup() { combinationStrategy, upfrontTreePruner, visualiserFactory, - Mockito.mock(RelationshipsProcessor.class) + Mockito.mock(RelationshipsDataGenerator.class) ); }