diff --git a/common/src/main/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraint.java b/common/src/main/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraint.java new file mode 100644 index 000000000..b686162a5 --- /dev/null +++ b/common/src/main/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraint.java @@ -0,0 +1,74 @@ +/* + * 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.common.profile.constraints.atomic; + +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.fieldspecs.whitelist.DistributedList; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class IsInMapConstraint implements AtomicConstraint { + public final Field field; + public final DistributedList legalValues; + + public IsInMapConstraint(Field field, DistributedList legalValues) { + this.field = field; + this.legalValues = legalValues; + + if (legalValues.distributedList().isEmpty()) { + throw new IllegalArgumentException("Cannot create an IsInMapConstraint for field '" + + field.name + "' with an empty set."); + } + + if (legalValues.list().contains(null)) { + throw new IllegalArgumentException("Cannot create an IsInMapConstraint for field '" + + field.name + "' with a list containing null."); + } + } + + @Override + public Field getField() { + return field; + } + + public String toString(){ + boolean overLimit = legalValues.list().size() > 3; + return String.format("%s in [%s%s](%d values)", + field.name, + legalValues.stream().limit(3).map(Object::toString).collect(Collectors.joining(", ")), + overLimit ? ", ..." : "", + legalValues.list().size()); + } + + @Override + public boolean equals(Object o){ + if (this == o) return true; + if (o instanceof ViolatedAtomicConstraint) { + return o.equals(this); + } + if (o == null || getClass() != o.getClass()) return false; + IsInMapConstraint constraint = (IsInMapConstraint) o; + return Objects.equals(field, constraint.field) && Objects.equals(legalValues, constraint.legalValues); + } + + @Override + public int hashCode(){ + return Objects.hash(field, legalValues); + } +} diff --git a/common/src/test/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraintTests.java b/common/src/test/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraintTests.java new file mode 100644 index 000000000..921a19fe3 --- /dev/null +++ b/common/src/test/java/com/scottlogic/deg/common/profile/constraints/atomic/IsInMapConstraintTests.java @@ -0,0 +1,53 @@ +/* + * 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.common.profile.constraints.atomic; + +import com.scottlogic.deg.common.profile.Field; +import com.scottlogic.deg.generator.fieldspecs.whitelist.DistributedList; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static com.scottlogic.deg.common.profile.FieldBuilder.createField; + +public class IsInMapConstraintTests { + + @Test + public void testConstraintThrowsIfGivenEmptySet(){ + Field field1 = createField("TestField"); + + Assertions.assertThrows( + IllegalArgumentException.class, + () -> new IsInMapConstraint(field1, DistributedList.empty())); + } + + @Test + public void testConstraintThrowsIfGivenNullInASet(){ + Field field1 = createField("TestField"); + + Assertions.assertThrows( + IllegalArgumentException.class, + () -> new IsInMapConstraint(field1, DistributedList.singleton(null))); + } + + @Test + public void testConstraintThrowsNothingIfGivenAValidSet(){ + Field field1 = createField("TestField"); + Assertions.assertDoesNotThrow( + () -> new IsInMapConstraint(field1, DistributedList.singleton("foo"))); + } + +} diff --git a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpecFactory.java b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpecFactory.java index 1c08248dd..e08efeff2 100644 --- a/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpecFactory.java +++ b/generator/src/main/java/com/scottlogic/deg/generator/fieldspecs/FieldSpecFactory.java @@ -52,6 +52,8 @@ private FieldSpec construct(AtomicConstraint constraint, boolean negate) { return construct(((NotConstraint) constraint).negatedConstraint, !negate); } else if (constraint instanceof IsInSetConstraint) { return construct((IsInSetConstraint) constraint, negate); + } else if (constraint instanceof IsInMapConstraint) { + return construct((IsInMapConstraint) constraint, negate); } else if (constraint instanceof EqualToConstraint) { return construct((EqualToConstraint) constraint, negate); } else if (constraint instanceof IsGreaterThanConstantConstraint) { @@ -101,6 +103,14 @@ private FieldSpec construct(IsInSetConstraint constraint, boolean negate) { return FieldSpec.fromList(constraint.legalValues); } + private FieldSpec construct(IsInMapConstraint constraint, boolean negate) { + if (negate) { + throw new UnsupportedOperationException("negation of inMap not supported"); + } + + return FieldSpec.fromList(constraint.legalValues); + } + private FieldSpec construct(EqualToConstraint constraint, boolean negate) { if (negate) { return FieldSpec.empty().withBlacklist(Collections.singleton(constraint.value)); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintFactory.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintFactory.java index 5ba21cb5d..d060ba6ff 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintFactory.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintFactory.java @@ -22,6 +22,8 @@ public static Constraint create(AtomicConstraintType type, Field field, Object v return new EqualToConstraint(field, value); case IS_IN_SET: return new IsInSetConstraint(field, (DistributedList)value); + case IS_IN_MAP: + return new IsInMapConstraint(field, (DistributedList)value); case IS_NULL: return new IsNullConstraint(field); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintValueReader.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintValueReader.java index c4ae761bb..d3f6feb65 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintValueReader.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/AtomicConstraintValueReader.java @@ -44,7 +44,7 @@ private Object tryGetValue(ConstraintDTO dto, Types type){ } if (dto.file != null && dto.is.equals(AtomicConstraintType.IS_IN_MAP.getText())){ - throw new UnsupportedOperationException("inMap is unsupported"); + return fromFileReader.listFromMapFile(dto.file, dto.key); } return getValue(dto.value, type); diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/FromFileReader.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/FromFileReader.java index 06d2ccde2..043b22ed5 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/FromFileReader.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/atomic/FromFileReader.java @@ -35,6 +35,18 @@ public DistributedList setFromFile(String file) { .collect(Collectors.toList())); } + public DistributedList listFromMapFile(String file, String key) { + InputStream streamFromPath = createStreamFromPath(appendPath(file)); + + DistributedList names = CsvInputStreamReader.retrieveLines(streamFromPath, key); + closeStream(streamFromPath); + + return new DistributedList<>( + names.distributedList().stream() + .map(holder -> new WeightedElement<>((Object) holder.element(), holder.weight())) + .collect(Collectors.toList())); + } + private String appendPath(String path) { return fromFilePath + path; } diff --git a/profile/src/main/java/com/scottlogic/deg/profile/reader/file/CsvInputStreamReader.java b/profile/src/main/java/com/scottlogic/deg/profile/reader/file/CsvInputStreamReader.java index d11ed8d96..d98ed7cae 100644 --- a/profile/src/main/java/com/scottlogic/deg/profile/reader/file/CsvInputStreamReader.java +++ b/profile/src/main/java/com/scottlogic/deg/profile/reader/file/CsvInputStreamReader.java @@ -16,6 +16,7 @@ package com.scottlogic.deg.profile.reader.file; +import com.scottlogic.deg.common.ValidationException; import com.scottlogic.deg.generator.fieldspecs.whitelist.DistributedList; import com.scottlogic.deg.generator.fieldspecs.whitelist.WeightedElement; import org.apache.commons.csv.CSVFormat; @@ -26,7 +27,7 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.charset.Charset; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; public final class CsvInputStreamReader { @@ -38,16 +39,44 @@ private CsvInputStreamReader() { public static DistributedList retrieveLines(InputStream stream) { List records = parse(stream); return new DistributedList<>(records.stream() - .map(CsvInputStreamReader::createWeightedElement) + .map(CsvInputStreamReader::createWeightedElementFromRecord) .collect(Collectors.toList())); } - private static WeightedElement createWeightedElement(CSVRecord record) { - if (record.size() > 1) { - return new WeightedElement<>(record.get(0), Double.parseDouble(record.get(1))); - } else { - return WeightedElement.withDefaultWeight(record.get(0)); + public static DistributedList retrieveLines(InputStream stream, String key) { + List records = parse(stream); + + int index = getIndexForKey(records.get(0), key); + + //Remove the header + records.remove(0); + + return new DistributedList<>(records.stream() + .map(record -> record.get(index)) + .map(record -> createWeightedElement(record, Optional.empty())) + .collect(Collectors.toList())); + } + + private static int getIndexForKey(CSVRecord header, String key) { + int index = 0; + for (String title : header) { + if (title.equals(key)) { + return index; + } + index++; } + throw new ValidationException("unable to find data for key " + key); + } + + private static WeightedElement createWeightedElementFromRecord(CSVRecord record) { + return createWeightedElement(record.get(0), + record.size() == 1 ? Optional.empty() : Optional.of(Double.parseDouble(record.get(1)))); + } + + + private static WeightedElement createWeightedElement(String element, Optional weight) { + return weight.map(integer -> new WeightedElement<>(element, integer)) + .orElseGet(() -> WeightedElement.withDefaultWeight(element)); } private static List parse(InputStream stream) {