diff --git a/common/src/main/java/com/scottlogic/deg/common/profile/Field.java b/common/src/main/java/com/scottlogic/deg/common/profile/Field.java index 7770f5240..3220b7861 100644 --- a/common/src/main/java/com/scottlogic/deg/common/profile/Field.java +++ b/common/src/main/java/com/scottlogic/deg/common/profile/Field.java @@ -25,14 +25,16 @@ public class Field { private final String formatting; private final boolean internal; private final boolean nullable; + private final String generator; - public Field(String name, SpecificFieldType type, boolean unique, String formatting, boolean internal, boolean nullable) { + public Field(String name, SpecificFieldType type, boolean unique, String formatting, boolean internal, boolean nullable, String generator) { this.name = name; this.type = type; this.unique = unique; this.formatting = formatting; this.internal = internal; this.nullable = nullable; + this.generator = generator; } public FieldType getType() { @@ -60,6 +62,9 @@ public boolean isNullable() return nullable; } + public boolean usesCustomGenerator() { return generator != null; } + + public String getCustomGeneratorName() { return generator; } @Override public String toString() { @@ -75,12 +80,13 @@ public boolean equals(Object o) { && Objects.equals(unique, field.unique) && Objects.equals(type, field.type) && Objects.equals(formatting, field.formatting) - && Objects.equals(nullable, field.nullable); + && Objects.equals(nullable, field.nullable) + && Objects.equals(generator, field.generator); } @Override public int hashCode() { - return Objects.hash(name, unique, formatting, type, nullable); + return Objects.hash(name, unique, formatting, type, nullable, generator); } public String getName() diff --git a/common/src/test/java/com/scottlogic/deg/common/profile/FieldBuilder.java b/common/src/test/java/com/scottlogic/deg/common/profile/FieldBuilder.java index fa801939d..d4d717ede 100644 --- a/common/src/test/java/com/scottlogic/deg/common/profile/FieldBuilder.java +++ b/common/src/test/java/com/scottlogic/deg/common/profile/FieldBuilder.java @@ -24,9 +24,9 @@ public static Field createInternalField(String name) { return createInternalField(name, SpecificFieldType.STRING); } public static Field createField(String name, SpecificFieldType type) { - return new Field(name, type, false, null, false, false); + return new Field(name, type, false, null, false, false, null); } public static Field createInternalField(String name, SpecificFieldType type) { - return new Field(name, type, false, null, true, false); + return new Field(name, type, false, null, true, false, null); } } diff --git a/custom/.gitignore b/custom/.gitignore new file mode 100644 index 000000000..6e4170320 --- /dev/null +++ b/custom/.gitignore @@ -0,0 +1,3 @@ +build/ +out/ +bin/ \ No newline at end of file diff --git a/custom/build.gradle b/custom/build.gradle new file mode 100644 index 000000000..e93e712c3 --- /dev/null +++ b/custom/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java' +} + +group 'com.scottlogic.deg' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/CustomGenerator.java b/custom/src/main/java/com/scottlogic/deg/custom/CustomGenerator.java new file mode 100644 index 000000000..0f2e0febb --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/CustomGenerator.java @@ -0,0 +1,87 @@ +/* + * 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.deg.custom; + +import java.util.stream.Stream; + +public interface CustomGenerator { + + /*** + * REQUIRED! used in profile reading + * @return the name of the custom generator + */ + String generatorName(); + + /** + * STRING for Strings + * NUMERIC for BigDecimals + * DATETIME for OffsetDateTimes + * @return accepted field type + */ + CustomGeneratorFieldType fieldType(); + + /** + * the part of the generator to be used during random generation + * + * Required if you want your custom generator to support random generation + * + * @return your stream of random values + */ + Stream generateRandom(); + + /** + * the part of the generator to be used when the generator constraint is negated during random generation + * - this should be implemented as the values that your regular generator should not be outputting + * + * Required if you want your custom generator to support being negated in random mode + * Required if you want your custom generator to support used in the IF part of IF THEN constraints + * + * @return your stream of random values that are not what would be produced by the generator if it were not negated + */ + Stream generateNegatedRandom(); + + /** + * the part of the generator to be used during sequential generation + * + * Required if you want your custom generator to support sequential generation + * Required if you want your custom generator to support unique keys generation + * + * @return your stream of random values + */ + Stream generateSequential(); + + /** + * the part of the generator to be used when the generator constraint is negated during sequential generation + * + * Required if you want your custom generator to support being negated in sequential mode + * should not be used with unique keys generation + * + * @return your stream of sequential values that are not what would be produced by the generator if it were not negated + */ + Stream generateNegatedSequential(); + + /** + * The function to check whether a value from a set can be generated from this generator + * + * Required if you want your custom generator to support being combined with inSet and equalTo constraints + * + * @param value the value from the set to be tested against + * + * @return true if the generator would be able to produce the value + */ + boolean setMatchingFunction(T value); +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorFieldType.java b/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorFieldType.java new file mode 100644 index 000000000..e15317293 --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorFieldType.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.deg.custom; + +public enum CustomGeneratorFieldType { + NUMERIC, + STRING, + DATETIME +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorList.java b/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorList.java new file mode 100644 index 000000000..707543ff1 --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/CustomGeneratorList.java @@ -0,0 +1,30 @@ +/* + * 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.deg.custom; + +import com.scottlogic.deg.custom.example.LoremIpsumGeneratorCreator; + +import java.util.Arrays; +import java.util.List; + +public class CustomGeneratorList { + public List get() { + return Arrays.asList( + LoremIpsumGeneratorCreator.create() + ); + } +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/builder/BuiltCustomGenerator.java b/custom/src/main/java/com/scottlogic/deg/custom/builder/BuiltCustomGenerator.java new file mode 100644 index 000000000..24237f731 --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/builder/BuiltCustomGenerator.java @@ -0,0 +1,87 @@ +/* + * 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.deg.custom.builder; + +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.custom.CustomGeneratorFieldType; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class BuiltCustomGenerator implements CustomGenerator { + + private final CustomGeneratorFieldType fieldType; + private final String name; + private final Function matchingFunction; + private final Supplier> randomGenerator; + private final Supplier> negatedRandomGenerator; + private final Supplier> sequentialGenerator; + private final Supplier> negatedSequentialGenerator; + + public BuiltCustomGenerator(CustomGeneratorFieldType fieldType, + String name, + Function matchingFunction, + Supplier> randomGenerator, + Supplier> negatedRandomGenerator, + Supplier> sequentialGenerator, + Supplier> negatedSequentialGenerator) { + this.fieldType = fieldType; + this.name = name; + this.matchingFunction = matchingFunction; + this.randomGenerator = randomGenerator; + this.negatedRandomGenerator = negatedRandomGenerator; + this.sequentialGenerator = sequentialGenerator; + this.negatedSequentialGenerator = negatedSequentialGenerator; + } + + @Override + public String generatorName() { + return name; + } + + @Override + public CustomGeneratorFieldType fieldType() { + return fieldType; + } + + @Override + public Stream generateRandom() { + return randomGenerator.get(); + } + + @Override + public Stream generateNegatedRandom() { + return negatedRandomGenerator.get(); + } + + @Override + public Stream generateSequential() { + return sequentialGenerator.get(); + } + + @Override + public Stream generateNegatedSequential() { + return negatedSequentialGenerator.get(); + } + + @Override + public boolean setMatchingFunction(T value) { + return matchingFunction.apply(value); + } +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorBuilder.java b/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorBuilder.java new file mode 100644 index 000000000..34c0f3c4c --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorBuilder.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.deg.custom.builder; + +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.custom.CustomGeneratorFieldType; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class CustomGeneratorBuilder { + + private CustomGeneratorFieldType fieldType; + private String name; + + private Function matchingFunction; + + private Supplier> randomGenerator; + private Supplier> negatedRandomGenerator; + private Supplier> sequentialGenerator; + private Supplier> negatedSequentialGenerator; + + private CustomGeneratorBuilder(CustomGeneratorFieldType fieldType, String name) { + this.fieldType = fieldType; + this.name = name; + matchingFunction = (value) -> { throw new CustomGeneratorNotImplementedException(name + " custom generator does not support being used with inSet or equalsTo constraints"); }; + randomGenerator = () -> { throw new CustomGeneratorNotImplementedException(name + " custom generator does not support random mode"); }; + negatedRandomGenerator = () -> { throw new CustomGeneratorNotImplementedException(name + " custom generator does not support being negated in random mode"); }; + sequentialGenerator = () -> { throw new CustomGeneratorNotImplementedException(name + " custom generator does not support sequential mode"); }; + negatedSequentialGenerator = () -> { throw new CustomGeneratorNotImplementedException(name + " custom generator does not support being negated in sequential mode"); }; + } + + public static CustomGeneratorBuilder createStringGenerator(String name){ + return new CustomGeneratorBuilder(CustomGeneratorFieldType.STRING, name); + } + + public static CustomGeneratorBuilder createNumericGenerator(String name){ + return new CustomGeneratorBuilder(CustomGeneratorFieldType.NUMERIC, name); + } + + public static CustomGeneratorBuilder createDateTimeGenerator(String name){ + return new CustomGeneratorBuilder(CustomGeneratorFieldType.DATETIME, name); + } + + /** + * the part of the generator to be used during random generation + * + * Required if you want your custom generator to support random generation + * @param supplier Supplier for a random value + * @return + */ + public CustomGeneratorBuilder withRandomGenerator(Supplier supplier){ + this.randomGenerator = () -> Stream.generate(supplier); + return this; + } + + /** + * the part of the generator to be used when the generator constraint is negated during random generation + * - this should be implemented as the values that your regular generator should not be outputting + * + * Required if you want your custom generator to support being negated in random mode + * Required if you want your custom generator to support used in the IF part of IF THEN constraints + * @param supplier Supplier for a random value that would not be produced by the non negated generator + * @return + */ + public CustomGeneratorBuilder withNegatedRandomGenerator(Supplier supplier){ + this.negatedRandomGenerator = () -> Stream.generate(supplier); + return this; + } + + /** + * the part of the generator to be used during sequential generation + * + * Required if you want your custom generator to support sequential generation + * Required if you want your custom generator to support unique keys generation + * @param sequentialGenerator the supplier for a stream of sequential values + * @return + */ + public CustomGeneratorBuilder withSequentialGenerator(Supplier> sequentialGenerator) { + this.sequentialGenerator = sequentialGenerator; + return this; + } + + /** + * the part of the generator to be used when the generator constraint is negated during sequential generation + * + * Required if you want your custom generator to support being negated in sequential mode + * should not be used with unique keys generation + * + * @param negatedSequentialGenerator the supplier for a stream of sequential values that would not be produced by the non negated generator + * @return + */ + public CustomGeneratorBuilder withNegatedSequentialGenerator(Supplier> negatedSequentialGenerator) { + this.negatedSequentialGenerator = negatedSequentialGenerator; + return this; + } + + /** + * The function to check whether a value from a set can be generated from this generator + * + * Required if you want your custom generator to support being combined wit + * @param matchingFunction a function that takes a value and says whether it can be produced by the generator + * @return + */ + public CustomGeneratorBuilder withMatchingFunction(Function matchingFunction) { + this.matchingFunction = matchingFunction; + return this; + } + + public CustomGenerator build(){ + if (name == null){ + throw new UnsupportedOperationException("Custom Generators must be named"); + } + + return new BuiltCustomGenerator<>(fieldType, name, matchingFunction, randomGenerator, negatedRandomGenerator, sequentialGenerator, negatedSequentialGenerator); + } +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorNotImplementedException.java b/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorNotImplementedException.java new file mode 100644 index 000000000..246e382c2 --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/builder/CustomGeneratorNotImplementedException.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.deg.custom.builder; + +public class CustomGeneratorNotImplementedException extends RuntimeException{ + public CustomGeneratorNotImplementedException(String message){ + super(message); + } +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/example/LoremIpsumGeneratorCreator.java b/custom/src/main/java/com/scottlogic/deg/custom/example/LoremIpsumGeneratorCreator.java new file mode 100644 index 000000000..ec3fb0a8f --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/example/LoremIpsumGeneratorCreator.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.deg.custom.example; + +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.custom.builder.CustomGeneratorBuilder; + +import java.util.stream.Stream; + +public class LoremIpsumGeneratorCreator { + + public static CustomGenerator create(){ + RandomLoremIpsum randomLoremIpsum = new RandomLoremIpsum(); + + return CustomGeneratorBuilder + .createStringGenerator("lorem ipsum") + .withRandomGenerator(() -> randomLoremIpsum.generateString()) + .withSequentialGenerator(() -> Stream.generate(() -> loremIpsumText)) + .build(); + } + + private static String loremIpsumText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; +} diff --git a/custom/src/main/java/com/scottlogic/deg/custom/example/RandomLoremIpsum.java b/custom/src/main/java/com/scottlogic/deg/custom/example/RandomLoremIpsum.java new file mode 100644 index 000000000..4f9a58528 --- /dev/null +++ b/custom/src/main/java/com/scottlogic/deg/custom/example/RandomLoremIpsum.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.deg.custom.example; + +import java.util.Random; + +public class RandomLoremIpsum { + + private Random random = new Random(); + + public String generateString(){ + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Lorem ipsum"); + + int length = random.nextInt(950); + + while (stringBuilder.length() < length){ + stringBuilder.append(getRandomPunctuation()); + stringBuilder.append(getRandomWord()); + } + + return stringBuilder.toString(); + } + + private String getRandomWord() { + return "lorem ipsum"; + } + + private String getRandomPunctuation() { + int i = random.nextInt(20); + switch (i) { + case 1: + case 2: + return ", "; + case 3: + return ". "; + default: + return " "; + } + } +} diff --git a/docs/UserGuide.md b/docs/UserGuide.md index f71e40023..0a758d569 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -53,7 +53,11 @@ 3. [allOf](#allOf) 4. [if](#if) -7. [Running a Profile](#Running-a-Profile) +7. [Custom Generators](#custom-generator) + 1. [adding](#adding-custom-generator) + 2. [using](#using-custom-generator) + +8. [Running a Profile](#Running-a-Profile) 1. [Command Line Arguments](#Command-Line-Arguments) 1. [Command Line Arguments for Generate Mode](#Command-Line-Arguments-for-Generate-Mode) 2. [Generation Strategies](#Generation-strategies) @@ -64,7 +68,7 @@ 2. [Exhaustive](#Exhaustive) 3. [Pinning](#Pinning) -8. [Visualising Decision Trees](#Visualising-Decision-Trees) +9. [Visualising Decision Trees](#Visualising-Decision-Trees) # Introduction @@ -663,6 +667,43 @@ Is satisfied if either: While it's not prohibited, wrapping conditional constraints in any other kind of constraint (eg, a `not`) may cause unintuitive results. + + +# Custom Generators +
+ +You can add your own custom java generators to the project with the following instructions. + +## Adding Custom Generators +
+ +To add a custom generator you will need to + - clone the datahelix source code + - go to the "custom" package + - either + - implement the CustomGenerator.java interface + - use the CustomGeneratorBuilder.java to build a custom generator + - add your custom generator to the list in the CustomGeneratorList.java class + +There is an example folder in the "custom" package which shows an example using the CustomGeneratorBuilder to build a generator called "lorem ipsum" + +## using Custom Generators +
+ +To use your custom generator you add it to the field definition in your profile like this + +```javascript +{ + "name": "field1", + "type": "string", + "generator": "lorem ipsum" +} +``` + +This will use the "lorem ipsum" example custom generator. + +To use your own, put the name of your generator instead of "lorem ipsum" + # Running a Profile
diff --git a/examples/a-valid-isin/profile.json b/examples/a-valid-isin/profile.json index a74660da9..630561deb 100644 --- a/examples/a-valid-isin/profile.json +++ b/examples/a-valid-isin/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "isin", diff --git a/examples/actor-names/profile.json b/examples/actor-names/profile.json index f88688198..0f62dc469 100644 --- a/examples/actor-names/profile.json +++ b/examples/actor-names/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "forename", diff --git a/examples/anyOf/profile.json b/examples/anyOf/profile.json index c609d850d..b24feea53 100644 --- a/examples/anyOf/profile.json +++ b/examples/anyOf/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "rules": [ { "rule": "rule 1", diff --git a/examples/date-after/profile.json b/examples/date-after/profile.json index f6380ed28..e75163333 100644 --- a/examples/date-after/profile.json +++ b/examples/date-after/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "date", diff --git a/examples/datetime-after-dynamic/profile.json b/examples/datetime-after-dynamic/profile.json index 1ae0725c3..1ab56923a 100644 --- a/examples/datetime-after-dynamic/profile.json +++ b/examples/datetime-after-dynamic/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "field1", diff --git a/examples/datetime-after/profile.json b/examples/datetime-after/profile.json index a770ab247..d36777c44 100644 --- a/examples/datetime-after/profile.json +++ b/examples/datetime-after/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "date", diff --git a/examples/datetime-equal-to-dynamic-offset/profile.json b/examples/datetime-equal-to-dynamic-offset/profile.json index a8e96f098..d0b723c8b 100644 --- a/examples/datetime-equal-to-dynamic-offset/profile.json +++ b/examples/datetime-equal-to-dynamic-offset/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first", diff --git a/examples/datetime-equal-to-dynamic/profile.json b/examples/datetime-equal-to-dynamic/profile.json index 42b6a1e0a..240f2fd32 100644 --- a/examples/datetime-equal-to-dynamic/profile.json +++ b/examples/datetime-equal-to-dynamic/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first", diff --git a/examples/datetime-equal-to/profile.json b/examples/datetime-equal-to/profile.json index 1258b2edc..9d9009c7e 100644 --- a/examples/datetime-equal-to/profile.json +++ b/examples/datetime-equal-to/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "date", diff --git a/examples/datetime-offset-working-days/profile.json b/examples/datetime-offset-working-days/profile.json index f2a922710..e2753824e 100644 --- a/examples/datetime-offset-working-days/profile.json +++ b/examples/datetime-offset-working-days/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "foo", diff --git a/examples/demo-trades-smaller/profile.json b/examples/demo-trades-smaller/profile.json index 5c1b9f2db..a6b0655dc 100644 --- a/examples/demo-trades-smaller/profile.json +++ b/examples/demo-trades-smaller/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "tradeId", diff --git a/examples/demo-trades/profile.json b/examples/demo-trades/profile.json index 93a9ae405..6c9c937b3 100644 --- a/examples/demo-trades/profile.json +++ b/examples/demo-trades/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "tradeId", diff --git a/examples/duplicates/profile.json b/examples/duplicates/profile.json index e3216c693..d965544b2 100644 --- a/examples/duplicates/profile.json +++ b/examples/duplicates/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "a", diff --git a/examples/formatting/profile.json b/examples/formatting/profile.json index b8834673d..e1b7589b5 100644 --- a/examples/formatting/profile.json +++ b/examples/formatting/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "decimal", diff --git a/examples/from-file-weighted/profile.json b/examples/from-file-weighted/profile.json index f1af04ecd..187f1b67e 100644 --- a/examples/from-file-weighted/profile.json +++ b/examples/from-file-weighted/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "name", diff --git a/examples/from-file/profile.json b/examples/from-file/profile.json index f1af04ecd..187f1b67e 100644 --- a/examples/from-file/profile.json +++ b/examples/from-file/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "name", diff --git a/examples/hard-contradiction-null-validation/profile.json b/examples/hard-contradiction-null-validation/profile.json index 9be671ae0..c956d3cc7 100644 --- a/examples/hard-contradiction-null-validation/profile.json +++ b/examples/hard-contradiction-null-validation/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "Column 1", diff --git a/examples/hard-contradiction-range-and-granular/profile.json b/examples/hard-contradiction-range-and-granular/profile.json index 6921ab434..5fafe7801 100644 --- a/examples/hard-contradiction-range-and-granular/profile.json +++ b/examples/hard-contradiction-range-and-granular/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "Column 1", diff --git a/examples/integer-range-with-blacklist/profile.json b/examples/integer-range-with-blacklist/profile.json index b81632e03..3263c5358 100644 --- a/examples/integer-range-with-blacklist/profile.json +++ b/examples/integer-range-with-blacklist/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "an_integer", diff --git a/examples/integer-range/profile.json b/examples/integer-range/profile.json index 6f7df8448..01d416ba8 100644 --- a/examples/integer-range/profile.json +++ b/examples/integer-range/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "an_integer", diff --git a/examples/multiple-fields/profile.json b/examples/multiple-fields/profile.json index 419969b9e..220179436 100644 --- a/examples/multiple-fields/profile.json +++ b/examples/multiple-fields/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "field1", diff --git a/examples/names/profile.json b/examples/names/profile.json index bcc356bfc..d84067435 100644 --- a/examples/names/profile.json +++ b/examples/names/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "forename", diff --git a/examples/partial-contradictions/profile.json b/examples/partial-contradictions/profile.json index bdf0abc57..bd5b8260b 100644 --- a/examples/partial-contradictions/profile.json +++ b/examples/partial-contradictions/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "Column 1", diff --git a/examples/partitioning/profile.json b/examples/partitioning/profile.json index 9188c5fd0..1c37d7e74 100644 --- a/examples/partitioning/profile.json +++ b/examples/partitioning/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "p1f1", diff --git a/examples/real-number-range/profile.json b/examples/real-number-range/profile.json index 749559b24..c2a62a4b9 100644 --- a/examples/real-number-range/profile.json +++ b/examples/real-number-range/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "a_number", diff --git a/examples/regex-contains/profile.json b/examples/regex-contains/profile.json index 3e60d5a27..6013a0794 100644 --- a/examples/regex-contains/profile.json +++ b/examples/regex-contains/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first_name", diff --git a/examples/regex-intersect-with-blacklist/profile.json b/examples/regex-intersect-with-blacklist/profile.json index 755a743ba..4cf46bde1 100644 --- a/examples/regex-intersect-with-blacklist/profile.json +++ b/examples/regex-intersect-with-blacklist/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first_name", diff --git a/examples/regex-intersect/profile.json b/examples/regex-intersect/profile.json index 4aebfd158..d0863ee02 100644 --- a/examples/regex-intersect/profile.json +++ b/examples/regex-intersect/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first_name", diff --git a/examples/regex/profile.json b/examples/regex/profile.json index c8b449467..011cb0182 100644 --- a/examples/regex/profile.json +++ b/examples/regex/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first_name", diff --git a/examples/setwise-combination/profile.json b/examples/setwise-combination/profile.json index df72c1385..17d08e5b4 100644 --- a/examples/setwise-combination/profile.json +++ b/examples/setwise-combination/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "a", diff --git a/examples/shorter-than/profile.json b/examples/shorter-than/profile.json index e9c455182..627e7f5e7 100644 --- a/examples/shorter-than/profile.json +++ b/examples/shorter-than/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "first_name", diff --git a/examples/string/profile.json b/examples/string/profile.json index 19eaff563..1901e15d5 100644 --- a/examples/string/profile.json +++ b/examples/string/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "rules": [], "fields": [ { diff --git a/examples/unique/profile.json b/examples/unique/profile.json index e206126a1..00b09ebfe 100644 --- a/examples/unique/profile.json +++ b/examples/unique/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "id", diff --git a/examples/user-account/profile.json b/examples/user-account/profile.json index e19ea5dd9..c4ed1a5d3 100644 --- a/examples/user-account/profile.json +++ b/examples/user-account/profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "user_id", diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java index 53d39751f..7b697296b 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/AfterDateRelationTest.java @@ -33,8 +33,8 @@ public class AfterDateRelationTest { - private final Field a = new Field("a", SpecificFieldType.DATETIME, false, "", false, false); - private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false); + private final Field a = new Field("a", SpecificFieldType.DATETIME, false, "", false, false, null); + private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false, null); @Test public void testReduceToFieldSpec_withNotNull_reducesToSpec() { diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java index 034093f5a..e7952c296 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/BeforeDateRelationTest.java @@ -33,8 +33,8 @@ public class BeforeDateRelationTest { - private final Field a = new Field("a", SpecificFieldType.DATETIME, false, "", false, false); - private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false); + private final Field a = new Field("a", SpecificFieldType.DATETIME, false, "", false, false, null); + private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false, null); @Test public void testReduceToFieldSpec_withNotNull_reducesToSpec() { diff --git a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToDateRelationTest.java b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToDateRelationTest.java index c44dbfbe8..6f0aeddcf 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToDateRelationTest.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/fieldspecs/relations/EqualToDateRelationTest.java @@ -31,8 +31,8 @@ class EqualToDateRelationTest { - private final Field a = new Field("a", SpecificFieldType.DATETIME, false ,"", false, false); - private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false); + private final Field a = new Field("a", SpecificFieldType.DATETIME, false ,"", false, false, null); + private final Field b = new Field("b", SpecificFieldType.DATETIME, false, "", false, false, null); private final FieldSpecRelation equalToDateRelations = new EqualToRelation(a, b); @Test diff --git a/generator/src/test/java/com/scottlogic/deg/generator/generation/FieldSpecValueGeneratorTests.java b/generator/src/test/java/com/scottlogic/deg/generator/generation/FieldSpecValueGeneratorTests.java index d6fc53c9f..99a1ade6b 100644 --- a/generator/src/test/java/com/scottlogic/deg/generator/generation/FieldSpecValueGeneratorTests.java +++ b/generator/src/test/java/com/scottlogic/deg/generator/generation/FieldSpecValueGeneratorTests.java @@ -117,7 +117,7 @@ void generateRandom_uniqueFieldSpec_returnsAllValues() { randomNumberGenerator ); - fieldSpecFulfiller.generate(new Field(null, SpecificFieldType.STRING, true, null, false, false), fieldSpec).collect(Collectors.toSet()); + fieldSpecFulfiller.generate(new Field(null, SpecificFieldType.STRING, true, null, false, false, null), fieldSpec).collect(Collectors.toSet()); verify(fieldValueSource, times(1)).generateAllValues(); verify(fieldValueSource, times(0)).generateInterestingValues(); @@ -145,7 +145,7 @@ void generateInteresting_uniqueFieldSpec_returnsAllValues() { randomNumberGenerator ); - fieldSpecFulfiller.generate(new Field(null, SpecificFieldType.STRING, true, null, false, false), fieldSpec).collect(Collectors.toSet()); + fieldSpecFulfiller.generate(new Field(null, SpecificFieldType.STRING, true, null, false, false, null), fieldSpec).collect(Collectors.toSet()); verify(fieldValueSource, times(1)).generateAllValues(); verify(fieldValueSource, times(0)).generateInterestingValues(); diff --git a/orchestrator/build.gradle b/orchestrator/build.gradle index 63f55aa10..dfd954185 100644 --- a/orchestrator/build.gradle +++ b/orchestrator/build.gradle @@ -32,6 +32,7 @@ dependencies { compile project(":generator") compile project(":output") compile project(":common") + compile project(":custom") compile group: "info.picocli", name: "picocli", version: "${PICOCLI_VERSION}" compile group: "com.google.code.gson", name: "gson", version: "${GSON_VERSION}" diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberProfileReader.java b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberProfileReader.java index da02cbde5..bc2985d3c 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberProfileReader.java +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/cucumber/testframework/utils/CucumberProfileReader.java @@ -20,6 +20,7 @@ import com.scottlogic.deg.common.commands.CommandBus; import com.scottlogic.deg.common.util.FileUtils; import com.scottlogic.deg.generator.profile.Profile; +import com.scottlogic.deg.profile.custom.CustomConstraintFactory; import com.scottlogic.deg.profile.dtos.ProfileDTO; import com.scottlogic.deg.profile.dtos.RuleDTO; import com.scottlogic.deg.profile.validators.ConfigValidator; diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/loadfiletest.profile.json b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/loadfiletest.profile.json index 1b335d27e..5f208dd8b 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/loadfiletest.profile.json +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/loadfiletest.profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "fields": [ { "name": "foo", diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/testprofile.profile.json b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/testprofile.profile.json index 1212c7e49..fcf59c6af 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/testprofile.profile.json +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/endtoend/testprofile.profile.json @@ -1,5 +1,5 @@ { - "schemaVersion": "0.14", + "schemaVersion": "0.15", "description": "Testing Profile", "fields": [ { diff --git a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/validator/ProfileValidationTests.java b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/validator/ProfileValidationTests.java index 9d8635763..bbf9099e0 100644 --- a/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/validator/ProfileValidationTests.java +++ b/orchestrator/src/test/java/com/scottlogic/deg/orchestrator/validator/ProfileValidationTests.java @@ -17,6 +17,8 @@ package com.scottlogic.deg.orchestrator.validator; import com.scottlogic.deg.common.util.FileUtils; +import com.scottlogic.deg.custom.CustomGeneratorList; +import com.scottlogic.deg.profile.custom.CustomConstraintFactory; import com.scottlogic.deg.profile.reader.FileReader; import com.scottlogic.deg.profile.reader.JsonProfileReader; import com.scottlogic.deg.profile.reader.ProfileCommandBus; @@ -50,7 +52,8 @@ Collection shouldAllValidateWithoutErrors() throws IOException { DynamicTest test = DynamicTest.dynamicTest( dir.getName(), () -> new JsonProfileReader(profileFile, new ConfigValidator(new FileUtils()),new FileReader(profileFile.getParent()), - new ProfileCommandBus(new FieldService(), new RuleService(new ConstraintService()), + new ProfileCommandBus(new FieldService(), new RuleService(new ConstraintService(), + new CustomConstraintFactory(new CustomGeneratorList())), new CreateProfileValidator(new ProfileValidator(null)))).read()); dynamicTests.add(test); } diff --git a/output/src/test/java/com/scottlogic/deg/output/writer/csv/CsvDataSetWriterTest.java b/output/src/test/java/com/scottlogic/deg/output/writer/csv/CsvDataSetWriterTest.java index 6f78aa2ce..9ce5cc7e0 100644 --- a/output/src/test/java/com/scottlogic/deg/output/writer/csv/CsvDataSetWriterTest.java +++ b/output/src/test/java/com/scottlogic/deg/output/writer/csv/CsvDataSetWriterTest.java @@ -42,8 +42,8 @@ public class CsvDataSetWriterTest { private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - private Field fieldOne = new Field("one", SpecificFieldType.STRING,false,null,false, false); - private Field fieldTwo = new Field("two", SpecificFieldType.STRING,false,null,false, false); + private Field fieldOne = new Field("one", SpecificFieldType.STRING,false,null,false, false, null); + private Field fieldTwo = new Field("two", SpecificFieldType.STRING,false,null,false, false, null); private Fields fields = new Fields(new ArrayList<>(Arrays.asList( fieldOne, fieldTwo diff --git a/profile/build.gradle b/profile/build.gradle index 2bbe010c3..de4434760 100644 --- a/profile/build.gradle +++ b/profile/build.gradle @@ -19,6 +19,7 @@ */ dependencies { compile project(":common") + compile project(":custom") compile project(":generator") compile group: "org.apache.commons", name: "commons-csv", version: "${COMMONS_CSV_VERSION}" compile group: "commons-io", name: "commons-io", version: "${COMMONS_IO_VERSION}" diff --git a/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraint.java b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraint.java new file mode 100644 index 000000000..85201d5cc --- /dev/null +++ b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraint.java @@ -0,0 +1,81 @@ +/* + * 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.deg.profile.custom; + +import com.scottlogic.deg.common.ValidationException; +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.common.profile.FieldType; +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.generator.fieldspecs.FieldSpec; +import com.scottlogic.deg.generator.fieldspecs.FieldSpecFactory; +import com.scottlogic.deg.generator.profile.constraints.atomic.AtomicConstraint; + +public class CustomConstraint implements AtomicConstraint { + private final Field field; + private final CustomGenerator customGenerator; + private final boolean negated; + + public CustomConstraint(Field field, CustomGenerator customGenerator){ + this(field, customGenerator, false); + } + + private CustomConstraint(Field field, CustomGenerator customGenerator, boolean negated) { + this.field = field; + this.customGenerator = customGenerator; + this.negated = negated; + + if (!correctType()) { + throw new ValidationException( + String.format("Custom generator %s requires type %s, but field %s is typed %s", + customGenerator.generatorName(), + customGenerator.fieldType(), + field.getName(), + field.getType())); + } + } + + @Override + public Field getField() { + return field; + } + + @Override + public AtomicConstraint negate() { + return new CustomConstraint(field, customGenerator, !negated); + } + + @Override + public FieldSpec toFieldSpec() { + return FieldSpecFactory + .fromGenerator( + new CustomFieldValueSource(customGenerator, negated), + customGenerator::setMatchingFunction); + } + + private boolean correctType() { + switch (customGenerator.fieldType()) { + case STRING: + return field.getType() == FieldType.STRING; + case DATETIME: + return field.getType() == FieldType.DATETIME; + case NUMERIC: + return field.getType() == FieldType.NUMERIC; + default: + return false; + } + } +} diff --git a/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraintFactory.java b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraintFactory.java new file mode 100644 index 000000000..56fb62cd8 --- /dev/null +++ b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomConstraintFactory.java @@ -0,0 +1,45 @@ +/* + * 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.deg.profile.custom; + +import com.google.inject.Inject; +import com.scottlogic.deg.common.ValidationException; +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.custom.CustomGeneratorList; +import com.scottlogic.deg.generator.profile.constraints.atomic.AtomicConstraint; + +import java.util.List; + +public class CustomConstraintFactory { + + private final List customGenerators; + + @Inject + public CustomConstraintFactory(CustomGeneratorList customGeneratorList) { + customGenerators = customGeneratorList.get(); + } + + public AtomicConstraint create(Field field, String generatorName){ + CustomGenerator customGenerator = customGenerators.stream() + .filter(cg -> cg.generatorName().equals(generatorName)) + .findFirst() + .orElseThrow(() -> new ValidationException("Custom generator " + generatorName + " does not exist it needs to be created and added to the CustomGeneratorList class")); + + return new CustomConstraint(field, customGenerator); + } +} diff --git a/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomFieldValueSource.java b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomFieldValueSource.java new file mode 100644 index 000000000..f1377f167 --- /dev/null +++ b/profile/src/main/java/com/scottlogic/deg/profile/custom/CustomFieldValueSource.java @@ -0,0 +1,54 @@ +/* + * 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.deg.profile.custom; + +import com.scottlogic.deg.custom.CustomGenerator; +import com.scottlogic.deg.generator.generation.fieldvaluesources.FieldValueSource; +import com.scottlogic.deg.generator.utils.RandomNumberGenerator; + +import java.util.stream.Stream; + +public class CustomFieldValueSource implements FieldValueSource { + private final CustomGenerator customGenerator; + private final boolean negated; + + public CustomFieldValueSource(CustomGenerator customGenerator, boolean negated) { + this.customGenerator = customGenerator; + this.negated = negated; + } + + @Override + public Stream generateInterestingValues() { + return generateAllValues().limit(2); + } + + @Override + public Stream generateAllValues() { + if (negated){ + return customGenerator.generateNegatedSequential(); + } + return customGenerator.generateSequential(); + } + + @Override + public Stream generateRandomValues(RandomNumberGenerator randomNumberGenerator) { + if (negated){ + return customGenerator.generateNegatedRandom(); + } + return customGenerator.generateRandom(); + } +} diff --git a/profile/src/main/java/com/scottlogic/deg/profile/dtos/FieldDTO.java b/profile/src/main/java/com/scottlogic/deg/profile/dtos/FieldDTO.java index b9cc0abfc..20cda5af1 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/dtos/FieldDTO.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/dtos/FieldDTO.java @@ -21,6 +21,7 @@ public class FieldDTO { public String name; public SpecificFieldType type; + public String generator; public String formatting; public boolean unique; public boolean nullable = false; diff --git a/profile/src/main/java/com/scottlogic/deg/profile/services/FieldService.java b/profile/src/main/java/com/scottlogic/deg/profile/services/FieldService.java index 50976e3d9..d8769e8b1 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/services/FieldService.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/services/FieldService.java @@ -44,13 +44,22 @@ public Fields createFields(List fieldDTOs, List ruleDTOs) private Field createRegularField(FieldDTO fieldDTO) { - String formatting = fieldDTO.formatting != null ? fieldDTO.formatting : fieldDTO.type.getDefaultFormatting(); - return new Field(fieldDTO.name, fieldDTO.type, fieldDTO.unique,formatting, false, fieldDTO.nullable); + String formatting = fieldDTO.formatting != null + ? fieldDTO.formatting + : fieldDTO.type.getDefaultFormatting(); + return new Field( + fieldDTO.name, + fieldDTO.type, + fieldDTO.unique, + formatting, + false, + fieldDTO.nullable, + fieldDTO.generator); } private Field createInMapField(String inMapFile) { - return new Field(inMapFile, SpecificFieldType.INTEGER, false, null, true, false); + return new Field(inMapFile, SpecificFieldType.INTEGER, false, null, true, false, null); } private List getInMapFieldNames(List ruleDTOs) diff --git a/profile/src/main/java/com/scottlogic/deg/profile/services/RuleService.java b/profile/src/main/java/com/scottlogic/deg/profile/services/RuleService.java index 042b5cfdc..df1ec4f50 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/services/RuleService.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/services/RuleService.java @@ -21,6 +21,7 @@ import com.scottlogic.deg.generator.profile.Rule; import com.scottlogic.deg.generator.profile.constraints.Constraint; import com.scottlogic.deg.generator.profile.constraints.atomic.IsNullConstraint; +import com.scottlogic.deg.profile.custom.CustomConstraintFactory; import com.scottlogic.deg.profile.dtos.RuleDTO; import java.util.List; @@ -30,11 +31,15 @@ public class RuleService { private final ConstraintService constraintService; + private final CustomConstraintFactory customConstraintFactory; @Inject - public RuleService(ConstraintService constraintService){ - + public RuleService( + ConstraintService constraintService, + CustomConstraintFactory customConstraintFactory) + { this.constraintService = constraintService; + this.customConstraintFactory = customConstraintFactory; } public List createRules(List ruleDTOs, Fields fields) @@ -45,6 +50,7 @@ public List createRules(List ruleDTOs, Fields fields) createNotNullableRule(fields).ifPresent(rules::add); createSpecificTypeRule(fields).ifPresent(rules::add); + createCustomGeneratorRule(fields, customConstraintFactory).ifPresent(rules::add); return rules; } @@ -73,4 +79,18 @@ private Optional createSpecificTypeRule(Fields fields) ? Optional.empty() : Optional.of(new Rule("specific-types", specificTypeConstraints)); } + + private Optional createCustomGeneratorRule( + Fields fields, + CustomConstraintFactory customConstraintFactory) + { + List customGeneratorConstraints = fields.stream() + .filter(f -> f.usesCustomGenerator()) + .map(f -> customConstraintFactory.create(f, f.getCustomGeneratorName())) + .collect(Collectors.toList()); + + return customGeneratorConstraints.isEmpty() + ? Optional.empty() + : Optional.of(new Rule("generators", customGeneratorConstraints)); + } } diff --git a/profile/src/main/resources/profileschema/datahelix.schema.json b/profile/src/main/resources/profileschema/datahelix.schema.json index 809c6f481..888186b37 100644 --- a/profile/src/main/resources/profileschema/datahelix.schema.json +++ b/profile/src/main/resources/profileschema/datahelix.schema.json @@ -56,7 +56,7 @@ "definitions": { "schemaVersion": { "title": "The version of the DataHelix profile schema", - "const": "0.14" + "const": "0.15" }, "dataType": { "oneOf": [ @@ -148,7 +148,8 @@ "type": "object", "additionalProperties": false, "required": [ - "name" + "name", + "type" ], "default": { "name": "fieldName", @@ -186,6 +187,9 @@ }, "nullable": { "type": "boolean" + }, + "generator": { + "type": "string" } } }, diff --git a/profile/src/test/java/com/scottlogic/deg/profile/FieldDeserialiserTests.java b/profile/src/test/java/com/scottlogic/deg/profile/FieldDeserialiserTests.java index cbea7651c..6b28d9b10 100644 --- a/profile/src/test/java/com/scottlogic/deg/profile/FieldDeserialiserTests.java +++ b/profile/src/test/java/com/scottlogic/deg/profile/FieldDeserialiserTests.java @@ -150,7 +150,7 @@ public void shouldDeserialiseFieldAndThrowInvalidFieldNameException() throws IOE deserialiseJsonString(json); Assert.fail("should have thrown an exception"); } catch (UnrecognizedPropertyException e) { - String expectedMessage = "Unrecognized field \"tpe\" (class com.scottlogic.deg.profile.dtos.FieldDTO), not marked as ignorable (5 known properties: \"unique\", \"type\", \"name\", \"formatting\", \"nullable\"])\n at [Source: (String)\"{ \"name\": \"id\", \"tpe\": \"integer\" }\"; line: 1, column: 25] (through reference chain: com.scottlogic.deg.profile.dtos.FieldDTO[\"tpe\"])"; + String expectedMessage = "Unrecognized field \"tpe\" (class com.scottlogic.deg.profile.dtos.FieldDTO), not marked as ignorable (6 known properties: \"unique\", \"generator\", \"formatting\", \"type\", \"name\", \"nullable\"])\n at [Source: (String)\"{ \"name\": \"id\", \"tpe\": \"integer\" }\"; line: 1, column: 25] (through reference chain: com.scottlogic.deg.profile.dtos.FieldDTO[\"tpe\"])"; assertThat(e.getMessage(), sameBeanAs(expectedMessage)); } } diff --git a/profile/src/test/java/com/scottlogic/deg/profile/ProfileSchemaImmutabilityTests.java b/profile/src/test/java/com/scottlogic/deg/profile/ProfileSchemaImmutabilityTests.java index 1eb867639..883ca85aa 100644 --- a/profile/src/test/java/com/scottlogic/deg/profile/ProfileSchemaImmutabilityTests.java +++ b/profile/src/test/java/com/scottlogic/deg/profile/ProfileSchemaImmutabilityTests.java @@ -116,7 +116,10 @@ private static Set versionToHash() { "3b812a8dfe5b2a77e2b427f343ffc237e3e31c0364197e820f2b7702fa6ef260")); versionToHash.add(new VersionHash( "0.14", - "26ef27557c4b966b786bed79a99e0184b3e4a3c50b98256725a1000250d6fcd0")); + "757551740cdb5d6c368ba33fed4989e494064399cd0af8d852f776e038b4b229")); + versionToHash.add(new VersionHash( + "0.15", + "5f2ecc76e7db9055be42e959005f169e03c4baa86d98ac8d3df363d867e7bbac")); return versionToHash; } diff --git a/profile/src/test/java/com/scottlogic/deg/profile/reader/JsonProfileReaderTests.java b/profile/src/test/java/com/scottlogic/deg/profile/reader/JsonProfileReaderTests.java index c3b108a97..610109ea6 100644 --- a/profile/src/test/java/com/scottlogic/deg/profile/reader/JsonProfileReaderTests.java +++ b/profile/src/test/java/com/scottlogic/deg/profile/reader/JsonProfileReaderTests.java @@ -22,6 +22,7 @@ import com.scottlogic.deg.common.profile.Field; import com.scottlogic.deg.common.profile.FieldType; import com.scottlogic.deg.common.profile.NumericGranularity; +import com.scottlogic.deg.custom.CustomGeneratorList; import com.scottlogic.deg.common.util.FileUtils; import com.scottlogic.deg.generator.fieldspecs.whitelist.DistributedList; import com.scottlogic.deg.generator.profile.Profile; @@ -31,6 +32,8 @@ import com.scottlogic.deg.generator.profile.constraints.grammatical.AndConstraint; import com.scottlogic.deg.generator.profile.constraints.grammatical.ConditionalConstraint; import com.scottlogic.deg.generator.profile.constraints.grammatical.OrConstraint; +import com.scottlogic.deg.profile.custom.CustomConstraint; +import com.scottlogic.deg.profile.custom.CustomConstraintFactory; import com.scottlogic.deg.profile.services.ConstraintService; import com.scottlogic.deg.profile.services.FieldService; import com.scottlogic.deg.profile.services.RuleService; @@ -91,8 +94,10 @@ public DistributedList listFromMapFile(String file, String Key) new MockFromFileReader(), new ProfileCommandBus( new FieldService(), - new RuleService(new ConstraintService()), new CreateProfileValidator(new ProfileValidator(null)))); - + new RuleService( + new ConstraintService(), + new CustomConstraintFactory(new CustomGeneratorList())), + new CreateProfileValidator(new ProfileValidator(null)))); private void givenJson(String json) { this.json = json; @@ -1129,4 +1134,51 @@ public void formatting_withDateTypeAndFormatting_shouldSetCorrectFormatting() th field -> Assert.assertEquals("%tD",field.getFormatting()) ); } + + @Test + public void addsConstraintForGenerator() throws IOException { + givenJson( + "{" + + " \"schemaVersion\": " + schemaVersion + "," + + " \"fields\": [ { " + + " \"name\": \"foo\" ," + + " \"type\": \"string\"," + + " \"generator\": \"lorem ipsum\"," + + " \"nullable\": true" + + " }]," + + " \"rules\": []" + + "}"); + + expectRules( + ruleWithConstraints( + typedConstraint( + CustomConstraint.class, + c -> { + Assert.assertEquals( + c.getField().getName(), + "foo"); + } + ) + ) + ); + } + + + @Test + public void exceptionWhenGeneratorDoesNotExist() throws IOException { + givenJson( + "{" + + " \"schemaVersion\": " + schemaVersion + "," + + " \"fields\": [ { " + + " \"name\": \"foo\" ," + + " \"type\": \"string\"," + + " \"generator\": \"INCORRECT\"," + + " \"nullable\": true" + + " }]," + + " \"rules\": []" + + "}"); + + expectValidationException("Custom generator INCORRECT does not exist it needs to be created and added to the CustomGeneratorList class"); + } + } diff --git a/settings.gradle b/settings.gradle index d61dad644..3fa8afe10 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,4 +25,5 @@ include(':output') include 'common' include 'orchestrator' include 'output' +include 'custom'