diff --git a/tdrules-store-loader/pom.xml b/tdrules-store-loader/pom.xml index 98a0516..4e665a3 100644 --- a/tdrules-store-loader/pom.xml +++ b/tdrules-store-loader/pom.xml @@ -62,6 +62,10 @@ junit junit + + pl.pragmatists + JUnitParams + io.github.javiertuya portable-java diff --git a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/EntityLoader.java b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/EntityLoader.java index 846f43c..85df515 100644 --- a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/EntityLoader.java +++ b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/EntityLoader.java @@ -104,7 +104,7 @@ String loadValues(String entity, String[] attrNames, String[] attrValues) { initializeGeneratedAttribute(null, gattrs, attr, currentEntity); // Configures the attributes with specified values or symbols - setSpecifiedValues(gattrs, attrNames, attrValues); + setSpecifiedValues(gattrs, entity, attrNames, attrValues); if (isArray) setArrayKeyAttributes(gattrs); @@ -138,18 +138,18 @@ private void initializeGeneratedAttribute(GeneratedAttribute parent, List gattrs, String[] attrNames, String[] attrValues) { + private void setSpecifiedValues(List gattrs, String entity, String[] attrNames, String[] attrValues) { for (int i = 0; i < attrNames.length; i++) { if (!attrNames[i].trim().equals("")) { log.trace("setSpecifiedValues: attrName={} attrValue={}", attrNames[i], attrValues[i]); GeneratedAttribute gattr = findGeneratedAttribute(gattrs, attrNames[i]); log.trace("setSpecifiedValues: before: {}", gattr); - setSpecifiedValuesForAttribute(gattr, attrValues[i]); + setSpecifiedValuesForAttribute(gattr, entity, attrValues[i]); log.trace("setSpecifiedValues: after: {}", gattr); } } } - private void setSpecifiedValuesForAttribute(GeneratedAttribute gattr, String attrValue) { + private void setSpecifiedValuesForAttribute(GeneratedAttribute gattr, String entity, String attrValue) { gattr.specValue = attrValue; if (symbols.isSymbol(attrValue)) { // The specified value is symbolic, it is a uid that will store its value in a symbol @@ -162,7 +162,8 @@ private void setSpecifiedValuesForAttribute(GeneratedAttribute gattr, String att gattr.genType = GenType.SPEC_FKSYM; // rid with symbolic value of a uid } else { gattr.genType = GenType.SPEC_USER; - gattr.genValue = attrValue; + // Even if specified, when using dictionaries it is possible some additional change because of collisions + gattr.genValue = config.attrGen.transformSpecValue(entity, gattr.attr.getName(), attrValue); } // If composite, as a value has been specified, remove descendants if (!gattr.isPrimitive()) diff --git a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/IAttrGen.java b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/IAttrGen.java index fa9a329..8d1d0bc 100644 --- a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/IAttrGen.java +++ b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/IAttrGen.java @@ -17,7 +17,7 @@ public interface IAttrGen { void reset(); /** - * Generates a string with the specified maximum lenght + * Generates a string with the specified maximum length */ String generateString(String entityName, String attrName, int maxLength); @@ -51,6 +51,16 @@ public interface IAttrGen { */ String generateCheckInConstraint(String[] allowedValues); + /** + * Specified values are not generated, but this method must be invoked + * because some times these values require some kind of transformation + * and override this default implementation + * (e.g. the case of dictionaries when the specified value matches any in the dictionary) + */ + default String transformSpecValue(String entityName, String attrName, String value) { + return value; + } + /** * Determines if a null value must be genrated for a given atribute with a given * probability. diff --git a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/gen/DictionaryAttrGen.java b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/gen/DictionaryAttrGen.java index a819f65..aeaa1af 100644 --- a/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/gen/DictionaryAttrGen.java +++ b/tdrules-store-loader/src/main/java/giis/tdrules/store/loader/gen/DictionaryAttrGen.java @@ -1,10 +1,15 @@ package giis.tdrules.store.loader.gen; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import giis.tdrules.store.loader.IConstraint; /** @@ -19,6 +24,7 @@ * desired configuration. All configuration methods are fluent. */ public class DictionaryAttrGen extends DeterministicAttrGen { + private static final Logger log=LoggerFactory.getLogger(DictionaryAttrGen.class); private SortedMap containers = new TreeMap<>(); private DictionaryContainer currentConfiguringContainer; @@ -26,6 +32,9 @@ public class DictionaryAttrGen extends DeterministicAttrGen { //Stores all configurations for a given coordinate public class DictionaryContainer { private String[] values; + // forbidden because some has been user specified + private Set blacklist; + private int lastIndex = -1; private String mask = ""; @@ -38,10 +47,11 @@ public class DictionaryContainer { public void reset() { lastIndex = -1; + blacklist= new HashSet<>(); } public boolean hasValues() { - return values != null; + return values != null && this.values.length > 0; } public boolean hasMask() { @@ -59,6 +69,15 @@ private String padValue(String value) { return hasPad() ? String.format(padFormat, value).replace(' ', padChar) : value; } + private int indexOf(String value) { + if (!this.hasValues()) + return -1; + for (int i = 0; i < this.values.length; i++) + if (this.values[i].equals(value)) + return i; + return -1; + } + @Override public String toString() { return Arrays.asList(values).toString(); @@ -91,6 +110,7 @@ public DictionaryAttrGen with(String entityName, String attrName) { */ public DictionaryAttrGen dictionary(String... values) { currentConfiguringContainer.values = values; + currentConfiguringContainer.blacklist = new HashSet<>(); return this; } @@ -132,12 +152,17 @@ private DictionaryContainer getDictionary(String entityName, String attrName) { } private String getNewStringFromDictionary(DictionaryContainer container) { - if (container.values == null) + if (!container.hasValues()) return null; container.lastIndex++; + skipBlacklist(container); + return getStringFromDictionary(container, container.lastIndex); + } + + private String getStringFromDictionary(DictionaryContainer container, int index) { // if all strings have been generated, recycles the dictionary - int actualIndex = container.lastIndex % container.values.length; - int actualCycle = container.lastIndex / container.values.length; + int actualIndex = index % container.values.length; + int actualCycle = index / container.values.length; String value = container.values[actualIndex]; if (actualCycle > 0) //to generate different values even if recycled @@ -145,6 +170,14 @@ private String getNewStringFromDictionary(DictionaryContainer container) { return value; } + private void skipBlacklist(DictionaryContainer container) { + for (int i=container.lastIndex; i= 0) { // collision detected, the dictionary items is blackliste + container.blacklist.add(container.values[index]); + log.warn("Collision between specified value '{}' and an item in the dictionary, removing this item", value); + } + + return value; + } } diff --git a/tdrules-store-loader/src/test/java/test4giis/tdrules/store/loader/oa/TestOaDictionaryCollisions.java b/tdrules-store-loader/src/test/java/test4giis/tdrules/store/loader/oa/TestOaDictionaryCollisions.java new file mode 100644 index 0000000..74a4af6 --- /dev/null +++ b/tdrules-store-loader/src/test/java/test4giis/tdrules/store/loader/oa/TestOaDictionaryCollisions.java @@ -0,0 +1,67 @@ +package test4giis.tdrules.store.loader.oa; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import giis.tdrules.openapi.model.TdAttribute; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.gen.DictionaryAttrGen; +import giis.tdrules.store.loader.oa.OaLocalAdapter; +import giis.tdrules.openapi.model.TdEntity; + +/** + * Test data generation both with and without dictionaries is in + * TestOaLocalGeneration; here generation when there are collisions between + * specified values for a string attribute and values in the dictionary is + * tested (this causes these items removed from the dictionary) + */ +@RunWith(JUnitParamsRunner.class) +public class TestOaDictionaryCollisions extends Base { + + protected TdSchema getDataTypesModel() { + TdEntity entity = new TdEntity().name("ent") + .addAttributesItem(new TdAttribute().name("pk").datatype("integer").uid("true").notnull("true")) + .addAttributesItem(new TdAttribute().name("str").datatype("string").notnull("true")); + return new TdSchema().storetype("openapi").addEntitiesItem(entity); + } + + protected DataLoader getGenerator(TdSchema model) { + return new DataLoader(model, new OaLocalAdapter()) + .setAttrGen(new DictionaryAttrGen().with("ent", "str").dictionary("aa", "bb", "cc")); + } + + @Test + @Parameters({ "_;xx;_;_;_;_ , aa;xx;bb;cc;aa-1;bb-1, no collision", + "bb;_;_;_;_;_ , bb;aa;cc;aa-1;bb-1;cc-1, spec before collision index", + "_;bb;_;_;_;_ , aa;bb;cc;aa-1;bb-1;cc-1, spec at collision index", + "_;aa;_;_;_;_ , aa;aa;bb;cc;aa-1;bb-1, spec after collision index (can't avoid)", + "_;_;_;aa;_;_ , aa;bb;cc;aa;aa-1;bb-1, spec after collision index at recycle position", + + "bb;_;bb;_;_;_ , bb;aa;bb;cc;aa-1;bb-1, multiple spec before collision index", + "_;bb;_;bb;_;_ , aa;bb;cc;bb;aa-1;bb-1, multiple spec at collision index", + + "aa;_;_;_;_;_ , aa;bb;cc;aa-1;bb-1;cc-1, collision at first index", + "_;_;cc;_;_;_ , aa;bb;cc;aa-1;bb-1;cc-1, collision at last index", + + "aa-1;_;_;_;_;_ , aa-1;aa;bb;cc;aa-1;bb-1, exclude collision with recycled items", + "_;_;_;aa-1;_;_ , aa;bb;cc;aa-1;aa-1;bb-1, exclude collision with recycled items", + }) + public void testDictionaryCollisionWithSpecValue(String specs, String outs, String message) { + String[] spec = specs.split(";"); + String[] out = outs.split(";"); + DataLoader dtg = getGenerator(getDataTypesModel()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Math.max(spec.length, out.length); i++) { + spec[i] = spec[i].trim(); + out[i] = out[i].trim(); + dtg.load("ent", ("_".equals(spec[i]) ? "" : "str=" + spec[i])); + sb.append("\"ent\":{\"pk\":" + (i * 100 + 1) + ",\"str\":\"" + out[i] + "\"}\n"); + } + assertEquals(message, sb.toString().trim(), dtg.getDataAdapter().getAllAsString()); + } + +}