| 1 | package com.renomad.minum.templating; | |
| 2 | ||
| 3 | import java.util.*; | |
| 4 | import java.util.stream.Collectors; | |
| 5 | ||
| 6 | import static com.renomad.minum.utils.SerializationUtils.tokenizer; | |
| 7 | ||
| 8 | /** | |
| 9 | * This class provides methods for working with templates. | |
| 10 | * <p> | |
| 11 | * The first step is to write a template. Here is an example: | |
| 12 | * </p> | |
| 13 | * <pre> | |
| 14 | * Hello, my name is {{name}} | |
| 15 | * </pre> | |
| 16 | * <p> | |
| 17 | * Then, feed that string into the {@link #buildProcessor} method, like | |
| 18 | * this: | |
| 19 | * </p> | |
| 20 | * <pre> | |
| 21 | * {@code | |
| 22 | * String input = "Hello, my name is {{name}}" | |
| 23 | * TemplateProcessor helloProcessor = TemplateProcessor.buildProcessor(input); | |
| 24 | * } | |
| 25 | * </pre> | |
| 26 | * <p> | |
| 27 | * The returned value ("helloProcessor") can be rendered with different values. For | |
| 28 | * example: | |
| 29 | * </p> | |
| 30 | * <pre> | |
| 31 | * {@code | |
| 32 | * Map<String,String> myMap = Map.of("name", "Susanne"); | |
| 33 | * String fullyRenderedString = helloProcessor.renderTemplate(myMap); | |
| 34 | * } | |
| 35 | * </pre> | |
| 36 | * <p> | |
| 37 | * The result is: | |
| 38 | * </p> | |
| 39 | * <pre> | |
| 40 | * {@code Hello, my name is Susanne} | |
| 41 | * </pre> | |
| 42 | */ | |
| 43 | public final class TemplateProcessor { | |
| 44 | ||
| 45 | /** | |
| 46 | * template sections by indentation | |
| 47 | */ | |
| 48 | private Map<Integer, List<TemplateSection>> templatesSectionsByIndent; | |
| 49 | private List<Map<String, String>> dataList = new ArrayList<>(); | |
| 50 | private final Set<String> keysFoundInTemplate; | |
| 51 | private final Set<String> keysRegisteredForInnerTemplates; | |
| 52 | private final String originalText; | |
| 53 | /** | |
| 54 | * This value is used to calculate a quick estimate of how many | |
| 55 | * bytes of memory we will need for the buffer holding our generated string | |
| 56 | */ | |
| 57 | private final static double SIZE_ESTIMATE_MODIFIER = 1.1; | |
| 58 | private final int estimatedSize; | |
| 59 | private Map<String, TemplateProcessor> innerTemplates; | |
| 60 | ||
| 61 | /** | |
| 62 | * Instantiate a new object with a list of {@link TemplateSection}. | |
| 63 | */ | |
| 64 | private TemplateProcessor(List<TemplateSection> templateSections, String originalText) { | |
| 65 | this.templatesSectionsByIndent = new HashMap<>(); | |
| 66 | this.templatesSectionsByIndent.put(0, templateSections); | |
| 67 | keysFoundInTemplate = new HashSet<>(); | |
| 68 | keysRegisteredForInnerTemplates = new HashSet<>(); | |
| 69 | this.originalText = originalText; | |
| 70 | this.innerTemplates = new HashMap<>(); | |
| 71 |
1
1. <init> : Replaced double multiplication with division → SURVIVED |
estimatedSize = (int) Math.round(originalText.length() * SIZE_ESTIMATE_MODIFIER); |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Given a map of key names -> value, render a template. | |
| 76 | */ | |
| 77 | public String renderTemplate(Map<String, String> myMap) { | |
| 78 |
1
1. renderTemplate : removed call to com/renomad/minum/templating/TemplateProcessor::registerData → KILLED |
registerData(List.of(myMap)); |
| 79 | ||
| 80 |
1
1. renderTemplate : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::renderTemplate → KILLED |
return internalRender(true).toString(); |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Given a list of maps of key names -> value, render a template | |
| 85 | * multiple times. | |
| 86 | */ | |
| 87 | public String renderTemplate(List<Map<String, String>> myMap) { | |
| 88 |
1
1. renderTemplate : removed call to com/renomad/minum/templating/TemplateProcessor::registerData → KILLED |
registerData(myMap); |
| 89 | ||
| 90 |
1
1. renderTemplate : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::renderTemplate → KILLED |
return internalRender(true).toString(); |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Recursively assembles the template and sub-templates | |
| 95 | */ | |
| 96 | public String renderTemplate() { | |
| 97 |
1
1. renderTemplate : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::renderTemplate → KILLED |
return internalRender(true).toString(); |
| 98 | } | |
| 99 | ||
| 100 | /** | |
| 101 | * Render the template and any nested sub-templates. All templates | |
| 102 | * must have data registered before running this method. | |
| 103 | * @param runWithChecks Default: true. Check that there is a 1-to-1 correspondence between | |
| 104 | * the keys provided and keys in the template and sub-templates, throwing | |
| 105 | * an exception if there are any errors. Also check that the maps | |
| 106 | * of data are consistent. This should be set true unless there is a reason | |
| 107 | * to aim for maximum performance, which is actually not | |
| 108 | * valuable in most cases, since the bottleneck is the business algorithms, database, | |
| 109 | * and HTTP processing. | |
| 110 | */ | |
| 111 | public String renderTemplate(boolean runWithChecks) { | |
| 112 |
1
1. renderTemplate : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::renderTemplate → KILLED |
return internalRender(runWithChecks).toString(); |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Assign data. Keys must match to template. | |
| 117 | */ | |
| 118 | public void registerData(List<Map<String, String>> dataList) { | |
| 119 |
1
1. registerData : negated conditional → KILLED |
if (dataList == null){ |
| 120 | throw new TemplateRenderException("provided data cannot be null"); | |
| 121 |
1
1. registerData : negated conditional → KILLED |
} else if (dataList.isEmpty()) { |
| 122 | throw new TemplateRenderException("No data provided in registerData call"); | |
| 123 | } | |
| 124 | ||
| 125 | this.dataList = dataList; | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Builds a {@link TemplateProcessor} from a string | |
| 130 | * containing a proper template. Templated values | |
| 131 | * are surrounded by double-curly-braces, i.e. {{foo}} or {{ foo }} | |
| 132 | */ | |
| 133 | public static TemplateProcessor buildProcessor(String template) { | |
| 134 |
2
1. buildProcessor : negated conditional → KILLED 2. buildProcessor : negated conditional → KILLED |
if (template == null || template.isEmpty()) { |
| 135 | throw new TemplateRenderException("The input to building a template must be a non-empty string"); | |
| 136 | } | |
| 137 | var tp = new TemplateProcessor(new ArrayList<>(), template); | |
| 138 | List<TemplateSection> tSections = tp.renderToTemplateSections(template); | |
| 139 | Set<String> keysFound = tSections.stream() | |
| 140 |
2
1. lambda$buildProcessor$0 : replaced boolean return with true for com/renomad/minum/templating/TemplateProcessor::lambda$buildProcessor$0 → KILLED 2. lambda$buildProcessor$0 : replaced boolean return with false for com/renomad/minum/templating/TemplateProcessor::lambda$buildProcessor$0 → KILLED |
.filter(x -> x.templateType.equals(TemplateType.DYNAMIC_TEXT)) |
| 141 |
1
1. lambda$buildProcessor$1 : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::lambda$buildProcessor$1 → KILLED |
.map(x -> x.key) |
| 142 | .collect(Collectors.toSet()); | |
| 143 | tp.keysFoundInTemplate.addAll(keysFound); | |
| 144 | tp.templatesSectionsByIndent.put(0, tSections); | |
| 145 | ||
| 146 |
1
1. buildProcessor : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::buildProcessor → KILLED |
return tp; |
| 147 | } | |
| 148 | ||
| 149 | private List<TemplateSection> renderToTemplateSections(String template) { | |
| 150 | // this value holds the entire template after processing, comprised | |
| 151 | // of an ordered list of TemplateSections | |
| 152 | var tSections = new ArrayList<TemplateSection>(); | |
| 153 | ||
| 154 | // these values are used for logging and setting proper indentation | |
| 155 | int rowNumber = 1; | |
| 156 | int columnNumber = 1; | |
| 157 | // this value records the indent of the beginning of template keys, | |
| 158 | // so we can properly indent the values later. | |
| 159 | int startOfKey = 0; | |
| 160 | ||
| 161 | StringBuilder builder = new StringBuilder(); | |
| 162 | // this flag is to help us understand whether we are currently reading the | |
| 163 | // name of a template literal. | |
| 164 | // e.g. in the case of hello {{ name }}, "name" is the literal. | |
| 165 | boolean isInsideTemplateKeyLiteral = false; | |
| 166 |
2
1. renderToTemplateSections : negated conditional → KILLED 2. renderToTemplateSections : changed conditional boundary → KILLED |
for (int i = 0; i < template.length(); i++) { |
| 167 | char charAtCursor = template.charAt(i); | |
| 168 | ||
| 169 |
6
1. renderToTemplateSections : negated conditional → KILLED 2. renderToTemplateSections : Replaced integer addition with subtraction → KILLED 3. renderToTemplateSections : negated conditional → KILLED 4. renderToTemplateSections : Replaced integer addition with subtraction → KILLED 5. renderToTemplateSections : negated conditional → KILLED 6. renderToTemplateSections : changed conditional boundary → KILLED |
if (charAtCursor == '{' && (i + 1) < template.length() && template.charAt(i + 1) == '{') { |
| 170 | isInsideTemplateKeyLiteral = true; | |
| 171 |
1
1. renderToTemplateSections : Replaced integer subtraction with addition → KILLED |
startOfKey = columnNumber - 1; |
| 172 |
1
1. renderToTemplateSections : Changed increment from 1 to -1 → TIMED_OUT |
i += 1; |
| 173 | builder = processSectionInside(builder, tSections); | |
| 174 |
7
1. renderToTemplateSections : Replaced integer addition with subtraction → KILLED 2. renderToTemplateSections : negated conditional → KILLED 3. renderToTemplateSections : negated conditional → KILLED 4. renderToTemplateSections : negated conditional → KILLED 5. renderToTemplateSections : Replaced integer addition with subtraction → KILLED 6. renderToTemplateSections : changed conditional boundary → KILLED 7. renderToTemplateSections : negated conditional → KILLED |
} else if (isInsideTemplateKeyLiteral && charAtCursor == '}' && (i + 1) < template.length() && template.charAt(i + 1) == '}') { |
| 175 | isInsideTemplateKeyLiteral = false; | |
| 176 |
1
1. renderToTemplateSections : Changed increment from 1 to -1 → KILLED |
i += 1; |
| 177 | builder = processSectionOutside(builder, tSections, startOfKey); | |
| 178 | startOfKey = 0; | |
| 179 | } else { | |
| 180 | builder.append(charAtCursor); | |
| 181 | ||
| 182 | /* | |
| 183 | if we're at the end of the template, it's our last chance to | |
| 184 | add a substring (we can't be adding to a key, since if we're | |
| 185 | at the end, and it's not a closing brace, it's a malformed | |
| 186 | template. | |
| 187 | */ | |
| 188 |
2
1. renderToTemplateSections : Replaced integer subtraction with addition → KILLED 2. renderToTemplateSections : negated conditional → KILLED |
if (i == template.length() - 1) { |
| 189 |
1
1. renderToTemplateSections : negated conditional → KILLED |
if (isInsideTemplateKeyLiteral) { |
| 190 | // if we're exiting this string while inside a template literal, then | |
| 191 | // we're reading a corrupted input, and we should make that clear | |
| 192 | // to our caller. | |
| 193 |
2
1. renderToTemplateSections : changed conditional boundary → KILLED 2. renderToTemplateSections : negated conditional → KILLED |
String templateSample = template.length() > 10 ? template.substring(0, 10) + "..." : template; |
| 194 | throw new TemplateParseException( | |
| 195 | "parsing failed for string starting with \"" + templateSample + "\" at line " + rowNumber + " and column " + columnNumber); | |
| 196 | } | |
| 197 | tSections.add(new TemplateSection(null, builder.toString(), null, TemplateType.STATIC_TEXT, 0)); | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 |
1
1. renderToTemplateSections : negated conditional → KILLED |
if (charAtCursor == '\n') { |
| 202 |
1
1. renderToTemplateSections : Changed increment from 1 to -1 → KILLED |
rowNumber += 1; |
| 203 | columnNumber = 1; | |
| 204 | } else { | |
| 205 |
1
1. renderToTemplateSections : Changed increment from 1 to -1 → KILLED |
columnNumber += 1; |
| 206 | } | |
| 207 | ||
| 208 | } | |
| 209 |
1
1. renderToTemplateSections : replaced return value with Collections.emptyList for com/renomad/minum/templating/TemplateProcessor::renderToTemplateSections → KILLED |
return tSections; |
| 210 | } | |
| 211 | ||
| 212 | private static StringBuilder processSectionInside(StringBuilder builder, List<TemplateSection> tSections) { | |
| 213 |
1
1. processSectionInside : negated conditional → KILLED |
if (!builder.isEmpty()) { |
| 214 | tSections.add(new TemplateSection(null, builder.toString(), null, TemplateType.STATIC_TEXT, 0)); | |
| 215 | builder = new StringBuilder(); | |
| 216 | } | |
| 217 |
1
1. processSectionInside : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::processSectionInside → KILLED |
return builder; |
| 218 | } | |
| 219 | ||
| 220 | private static StringBuilder processSectionOutside(StringBuilder builder, | |
| 221 | List<TemplateSection> tSections, | |
| 222 | int indent) { | |
| 223 | String key = builder.toString().trim(); | |
| 224 | tSections.add(new TemplateSection(key, "", null, TemplateType.DYNAMIC_TEXT, indent)); | |
| 225 | builder = new StringBuilder(); | |
| 226 |
1
1. processSectionOutside : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::processSectionOutside → KILLED |
return builder; |
| 227 | } | |
| 228 | ||
| 229 | /** | |
| 230 | * Binds an inner template to a key of this template. | |
| 231 | */ | |
| 232 | public TemplateProcessor registerInnerTemplate(String key, TemplateProcessor innerTemplate) { | |
| 233 |
2
1. registerInnerTemplate : negated conditional → KILLED 2. registerInnerTemplate : negated conditional → KILLED |
if (key == null || key.isBlank()) { |
| 234 | throw new TemplateRenderException("The key must be a valid non-blank string"); | |
| 235 | } | |
| 236 |
1
1. registerInnerTemplate : negated conditional → KILLED |
if (innerTemplate == null) { |
| 237 | throw new TemplateRenderException("The template must not be null"); | |
| 238 | } | |
| 239 |
1
1. registerInnerTemplate : negated conditional → KILLED |
if (this.equals(innerTemplate)) { |
| 240 | throw new TemplateRenderException("Disallowed to register a template to itself as an inner template"); | |
| 241 | } | |
| 242 |
1
1. registerInnerTemplate : negated conditional → KILLED |
if (keysRegisteredForInnerTemplates.contains(key)) { |
| 243 | throw new TemplateRenderException("key is already registered for use in another template: " + key); | |
| 244 | } | |
| 245 | ||
| 246 | // get the indent we should apply to each line after the first | |
| 247 | // by seeing what indent exists in the template sections and | |
| 248 | // creating a separate indented version for each one | |
| 249 | Set<Integer> necessaryIndentations = this.templatesSectionsByIndent.get(0).stream() | |
| 250 |
2
1. lambda$registerInnerTemplate$2 : replaced boolean return with true for com/renomad/minum/templating/TemplateProcessor::lambda$registerInnerTemplate$2 → SURVIVED 2. lambda$registerInnerTemplate$2 : replaced boolean return with false for com/renomad/minum/templating/TemplateProcessor::lambda$registerInnerTemplate$2 → KILLED |
.filter(x -> key.equals(x.key)) |
| 251 |
1
1. lambda$registerInnerTemplate$3 : replaced Integer return value with 0 for com/renomad/minum/templating/TemplateProcessor::lambda$registerInnerTemplate$3 → KILLED |
.map(x -> x.indent).collect(Collectors.toSet()); |
| 252 | ||
| 253 | // make sure we have one for zero as well. | |
| 254 | necessaryIndentations.add(0); | |
| 255 | ||
| 256 | ||
| 257 | var copyOfInnerTemplate = new TemplateProcessor(innerTemplate.templatesSectionsByIndent.get(0), innerTemplate.getOriginalText()); | |
| 258 | copyOfInnerTemplate.keysFoundInTemplate.addAll(innerTemplate.keysFoundInTemplate); | |
| 259 | copyOfInnerTemplate.keysRegisteredForInnerTemplates.addAll(innerTemplate.keysRegisteredForInnerTemplates); | |
| 260 | copyOfInnerTemplate.innerTemplates = new HashMap<>(innerTemplate.innerTemplates); | |
| 261 | this.innerTemplates.put(key, copyOfInnerTemplate); | |
| 262 | ||
| 263 |
1
1. registerInnerTemplate : removed call to java/util/Map::clear → SURVIVED |
copyOfInnerTemplate.templatesSectionsByIndent.clear(); |
| 264 | ||
| 265 | // a non-configurable ceiling limit to avoid runaway loops | |
| 266 | int MAXIMUM_LINES_ALLOWED = 10_000_000; | |
| 267 | String originalText = copyOfInnerTemplate.getOriginalText(); | |
| 268 | List<String> lines = tokenizer(originalText, '\n', MAXIMUM_LINES_ALLOWED); | |
| 269 | ||
| 270 | // if, after splitting on newlines, we have more than one line, we'll indent the remaining | |
| 271 | // lines so that they end up at the same column as the first line. | |
| 272 | for (int indentation : necessaryIndentations) { | |
| 273 | var indentedInnerTemplateText = new StringBuilder(lines.getFirst()); | |
| 274 |
2
1. registerInnerTemplate : negated conditional → KILLED 2. registerInnerTemplate : changed conditional boundary → KILLED |
for (int i = 1; i < lines.size(); i++) { |
| 275 |
1
1. registerInnerTemplate : negated conditional → KILLED |
if (lines.get(i).isEmpty()) { |
| 276 | indentedInnerTemplateText.append('\n'); | |
| 277 | } else { | |
| 278 | indentedInnerTemplateText.append('\n').append(" ".repeat(indentation)).append(lines.get(i)); | |
| 279 | } | |
| 280 | } | |
| 281 | List<TemplateSection> tSections = renderToTemplateSections(indentedInnerTemplateText.toString()); | |
| 282 | copyOfInnerTemplate.templatesSectionsByIndent.put(indentation, tSections); | |
| 283 | ||
| 284 | // now, loop through all the template sections, replacing them appropriately with | |
| 285 | // new data labeled as INNER_TEMPLATE. | |
| 286 | Map<Integer, List<TemplateSection>> revisedTemplateSectionsByIndent = new HashMap<>(); | |
| 287 | for (var templateSectionsByIndent : templatesSectionsByIndent.entrySet()) { | |
| 288 | List<TemplateSection> revisedList = new ArrayList<>(); | |
| 289 | for (TemplateSection templateSection : templateSectionsByIndent.getValue()) { | |
| 290 |
1
1. registerInnerTemplate : negated conditional → KILLED |
if (key.equals(templateSection.key)) { |
| 291 | revisedList.add(new TemplateSection(templateSection.key, | |
| 292 | templateSection.staticData, | |
| 293 | copyOfInnerTemplate, | |
| 294 | TemplateType.INNER_TEMPLATE, | |
| 295 | templateSection.indent)); | |
| 296 | } else { | |
| 297 | revisedList.add(templateSection); | |
| 298 | } | |
| 299 | } | |
| 300 | revisedTemplateSectionsByIndent.put(templateSectionsByIndent.getKey(), revisedList); | |
| 301 | } | |
| 302 | templatesSectionsByIndent = revisedTemplateSectionsByIndent; | |
| 303 | ||
| 304 | this.keysRegisteredForInnerTemplates.add(key); | |
| 305 | } | |
| 306 | ||
| 307 |
1
1. registerInnerTemplate : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::registerInnerTemplate → KILLED |
return copyOfInnerTemplate; |
| 308 | } | |
| 309 | ||
| 310 | /** | |
| 311 | * Returns the original unchanged template string | |
| 312 | */ | |
| 313 | public String getOriginalText() { | |
| 314 |
1
1. getOriginalText : replaced return value with "" for com/renomad/minum/templating/TemplateProcessor::getOriginalText → KILLED |
return originalText; |
| 315 | } | |
| 316 | ||
| 317 | /** | |
| 318 | * now, loop through the lists of data we were given, with the | |
| 319 | * internal template sections in hand | |
| 320 | */ | |
| 321 | private StringBuilder internalRender(boolean runWithChecks) { | |
| 322 |
1
1. internalRender : negated conditional → KILLED |
if (runWithChecks) { |
| 323 |
1
1. internalRender : removed call to com/renomad/minum/templating/TemplateProcessor::correctnessCheck → KILLED |
correctnessCheck(); |
| 324 | } | |
| 325 | int capacity = calculateEstimatedSize(); | |
| 326 | StringBuilder parts = new StringBuilder(capacity); | |
| 327 |
1
1. internalRender : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::internalRender → KILLED |
return internalRender(0, parts); |
| 328 | } | |
| 329 | ||
| 330 | /** | |
| 331 | * This examines the currently registered data lists and template keys | |
| 332 | * and confirms they are aligned. It will throw an exception if they | |
| 333 | * are not perfectly correlated. | |
| 334 | * <br> | |
| 335 | * | |
| 336 | */ | |
| 337 | private void correctnessCheck() { | |
| 338 | HashSet<String> copyOfKeysInTemplate = new HashSet<>(keysFoundInTemplate); | |
| 339 | copyOfKeysInTemplate.removeAll(this.innerTemplates.keySet()); | |
| 340 | ||
| 341 |
1
1. correctnessCheck : negated conditional → KILLED |
if (this.dataList.isEmpty()) { |
| 342 |
1
1. correctnessCheck : negated conditional → KILLED |
if (!copyOfKeysInTemplate.isEmpty()) { |
| 343 | // at this point we know there is no data provided but the template | |
| 344 | // requires data, so throw an exception. | |
| 345 | ||
| 346 | throw new TemplateRenderException("No data was provided for these keys: " + copyOfKeysInTemplate); | |
| 347 | } | |
| 348 | } else { | |
| 349 | ||
| 350 | // check for inconsistencies between maps in the data list | |
| 351 | Set<String> keysInFirstMap = this.dataList.getFirst().keySet(); | |
| 352 | for (Map<String, String> data : this.dataList) { | |
| 353 |
1
1. correctnessCheck : negated conditional → KILLED |
if (!data.keySet().equals(keysInFirstMap)) { |
| 354 | Set<String> result = differenceBetweenSets(data.keySet(), keysInFirstMap); | |
| 355 | throw new TemplateRenderException("In registered data, the maps were inconsistent on these keys: " + result); | |
| 356 | } | |
| 357 | } | |
| 358 | ||
| 359 | // ensure consistency between the registered data and the template keys | |
| 360 | HashSet<String> copyOfTemplateKeys = new HashSet<>(copyOfKeysInTemplate); | |
| 361 | copyOfTemplateKeys.removeAll(keysInFirstMap); | |
| 362 |
1
1. correctnessCheck : negated conditional → KILLED |
if (!copyOfTemplateKeys.isEmpty()) { |
| 363 | throw new TemplateRenderException("These keys in the template were not provided data: " + copyOfTemplateKeys); | |
| 364 | } | |
| 365 | ||
| 366 | HashSet<String> copyOfDataKeys = new HashSet<>(keysInFirstMap); | |
| 367 | copyOfDataKeys.removeAll(copyOfKeysInTemplate); | |
| 368 |
1
1. correctnessCheck : negated conditional → KILLED |
if (!copyOfDataKeys.isEmpty()) { |
| 369 | throw new TemplateRenderException("These keys in the data did not match anything in the template: " + copyOfDataKeys); | |
| 370 | } | |
| 371 | } | |
| 372 | ||
| 373 | for (TemplateProcessor tp : this.innerTemplates.values()) { | |
| 374 |
1
1. correctnessCheck : removed call to com/renomad/minum/templating/TemplateProcessor::correctnessCheck → KILLED |
tp.correctnessCheck(); |
| 375 | } | |
| 376 | ||
| 377 | } | |
| 378 | ||
| 379 | private static Set<String> differenceBetweenSets(Set<String> set1, Set<String> set2) { | |
| 380 | Set<String> union = new HashSet<>(set2); | |
| 381 | union.addAll(set1); | |
| 382 | Set<String> intersection = new HashSet<>(set2); | |
| 383 | intersection.retainAll(set1); | |
| 384 | ||
| 385 | Set<String> result = new HashSet<>(union); | |
| 386 | result.removeAll(intersection); | |
| 387 |
1
1. differenceBetweenSets : replaced return value with Collections.emptySet for com/renomad/minum/templating/TemplateProcessor::differenceBetweenSets → KILLED |
return result; |
| 388 | } | |
| 389 | ||
| 390 | /** | |
| 391 | * build up a calculated size estimate for this and all | |
| 392 | * nested templates. | |
| 393 | */ | |
| 394 | private int calculateEstimatedSize() { | |
| 395 | // the size of the datalist specifies how many times we will render ourselves. | |
| 396 |
1
1. calculateEstimatedSize : negated conditional → KILLED |
int sizeMultiplier = this.dataList.isEmpty() ? 1 : this.dataList.size(); |
| 397 |
1
1. calculateEstimatedSize : Replaced integer multiplication with division → KILLED |
int fullCalculatedSize = sizeMultiplier * estimatedSize; |
| 398 | for (TemplateProcessor innerProcessor : this.innerTemplates.values()) { | |
| 399 |
1
1. calculateEstimatedSize : Replaced integer addition with subtraction → KILLED |
fullCalculatedSize += innerProcessor.calculateEstimatedSize(); |
| 400 | } | |
| 401 |
1
1. calculateEstimatedSize : replaced int return with 0 for com/renomad/minum/templating/TemplateProcessor::calculateEstimatedSize → SURVIVED |
return fullCalculatedSize; |
| 402 | } | |
| 403 | ||
| 404 | private StringBuilder internalRender(int indent, StringBuilder parts) { | |
| 405 | Map<String, String> myDataMap = Map.of(); | |
| 406 | List<TemplateSection> templateSections = templatesSectionsByIndent.get(indent); | |
| 407 | int templateSectionsSize = templateSections.size(); | |
| 408 | int dataListIndex = 0; | |
| 409 |
1
1. internalRender : negated conditional → KILLED |
if (!dataList.isEmpty()) { |
| 410 | myDataMap = dataList.get(dataListIndex); | |
| 411 | } | |
| 412 | ||
| 413 | // build ourself out for each map of data given | |
| 414 | while (true) { | |
| 415 |
2
1. internalRender : changed conditional boundary → KILLED 2. internalRender : negated conditional → KILLED |
for (int i = 0; i < templateSectionsSize; i++) { |
| 416 | TemplateSection templateSection = templateSections.get(i); | |
| 417 | switch (templateSection.templateType) { | |
| 418 | case STATIC_TEXT -> parts.append(templateSection.staticData); | |
| 419 | case DYNAMIC_TEXT -> parts.append(myDataMap.get(templateSection.key)); | |
| 420 | default -> templateSection.templateProcessor.internalRender(templateSection.indent, parts); | |
| 421 | } | |
| 422 | ||
| 423 | } | |
| 424 |
1
1. internalRender : Changed increment from 1 to -1 → KILLED |
dataListIndex += 1; |
| 425 |
3
1. internalRender : changed conditional boundary → KILLED 2. internalRender : negated conditional → KILLED 3. internalRender : negated conditional → KILLED |
if (!dataList.isEmpty() && dataListIndex < dataList.size()) { |
| 426 | myDataMap = dataList.get(dataListIndex); | |
| 427 | parts.append("\n").repeat(" ", indent); | |
| 428 | } else { | |
| 429 |
1
1. internalRender : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::internalRender → KILLED |
return parts; |
| 430 | } | |
| 431 | } | |
| 432 | } | |
| 433 | ||
| 434 | /** | |
| 435 | * Returns the reference to an inner template, to enable registering | |
| 436 | * data and sub-templates. | |
| 437 | */ | |
| 438 | public TemplateProcessor getInnerTemplate(String innerTemplateKey) { | |
| 439 |
1
1. getInnerTemplate : replaced return value with null for com/renomad/minum/templating/TemplateProcessor::getInnerTemplate → KILLED |
return this.innerTemplates.get(innerTemplateKey); |
| 440 | } | |
| 441 | } | |
| 442 | ||
Mutations | ||
| 71 |
1.1 |
|
| 78 |
1.1 |
|
| 80 |
1.1 |
|
| 88 |
1.1 |
|
| 90 |
1.1 |
|
| 97 |
1.1 |
|
| 112 |
1.1 |
|
| 119 |
1.1 |
|
| 121 |
1.1 |
|
| 134 |
1.1 2.2 |
|
| 140 |
1.1 2.2 |
|
| 141 |
1.1 |
|
| 146 |
1.1 |
|
| 166 |
1.1 2.2 |
|
| 169 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 171 |
1.1 |
|
| 172 |
1.1 |
|
| 174 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 |
|
| 176 |
1.1 |
|
| 188 |
1.1 2.2 |
|
| 189 |
1.1 |
|
| 193 |
1.1 2.2 |
|
| 201 |
1.1 |
|
| 202 |
1.1 |
|
| 205 |
1.1 |
|
| 209 |
1.1 |
|
| 213 |
1.1 |
|
| 217 |
1.1 |
|
| 226 |
1.1 |
|
| 233 |
1.1 2.2 |
|
| 236 |
1.1 |
|
| 239 |
1.1 |
|
| 242 |
1.1 |
|
| 250 |
1.1 2.2 |
|
| 251 |
1.1 |
|
| 263 |
1.1 |
|
| 274 |
1.1 2.2 |
|
| 275 |
1.1 |
|
| 290 |
1.1 |
|
| 307 |
1.1 |
|
| 314 |
1.1 |
|
| 322 |
1.1 |
|
| 323 |
1.1 |
|
| 327 |
1.1 |
|
| 341 |
1.1 |
|
| 342 |
1.1 |
|
| 353 |
1.1 |
|
| 362 |
1.1 |
|
| 368 |
1.1 |
|
| 374 |
1.1 |
|
| 387 |
1.1 |
|
| 396 |
1.1 |
|
| 397 |
1.1 |
|
| 399 |
1.1 |
|
| 401 |
1.1 |
|
| 409 |
1.1 |
|
| 415 |
1.1 2.2 |
|
| 424 |
1.1 |
|
| 425 |
1.1 2.2 3.3 |
|
| 429 |
1.1 |
|
| 439 |
1.1 |