| 1 | package com.renomad.minum.templating; | |
| 2 | ||
| 3 | import java.util.List; | |
| 4 | import java.util.Map; | |
| 5 | import java.util.Objects; | |
| 6 | ||
| 7 | import static com.renomad.minum.templating.TemplateType.DYNAMIC_TEXT; | |
| 8 | import static com.renomad.minum.templating.TemplateType.STATIC_TEXT; | |
| 9 | import static com.renomad.minum.utils.SerializationUtils.tokenizer; | |
| 10 | ||
| 11 | /** | |
| 12 | * Represents one item in the list that will eventually be cooked | |
| 13 | * up into a fully-rendered string. This record is the magic | |
| 14 | * ingredient to an easy templating system. If it has a key, | |
| 15 | * then this object will be getting replaced during final string rendering. If it has a substring, | |
| 16 | * then the substring gets concatenated unchanged when the final string | |
| 17 | * is rendered. | |
| 18 | */ | |
| 19 | final class TemplateSection { | |
| 20 | ||
| 21 | final String key; | |
| 22 | private final String subString; | |
| 23 | private final int indent; | |
| 24 | public final TemplateType templateType; | |
| 25 | ||
| 26 | ||
| 27 | /** | |
| 28 | * @param indent the column number, measured from the left, of the first character of this template key. This is used | |
| 29 | * to indent the subsequent lines of text when there are newlines in the replacement | |
| 30 | * text. For example, if the indent is 5, and the value is "a", then it should indent like this: | |
| 31 | * <br> | |
| 32 | * <pre>{@code | |
| 33 | * 12345 | |
| 34 | * a | |
| 35 | * }</pre> | |
| 36 | * @param key the name of the key, e.g. "name", or "color" | |
| 37 | * @param subString the template content around the keys. For example, in the text | |
| 38 | * of "my favorite color is {{ color }} and I like it", | |
| 39 | * it would generate three template sections - "my favorite color is" would be | |
| 40 | * the first subString, then a key of "color", then a third subString of "and I like it" | |
| 41 | */ | |
| 42 | public TemplateSection(String key, String subString, int indent, TemplateType templateType) { | |
| 43 | this.key = key; | |
| 44 | this.subString = subString; | |
| 45 | this.indent = indent; | |
| 46 | this.templateType = templateType; | |
| 47 |
3
1. <init> : negated conditional → KILLED 2. <init> : negated conditional → KILLED 3. <init> : negated conditional → KILLED |
if ((templateType.equals(STATIC_TEXT) && (key != null || subString == null)) || |
| 48 |
3
1. <init> : negated conditional → KILLED 2. <init> : negated conditional → KILLED 3. <init> : negated conditional → KILLED |
(templateType.equals(DYNAMIC_TEXT) && (key == null || subString != null))) { |
| 49 | throw new TemplateRenderException("Invalid templateSection: " + this); | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * It would be absurd to send a million lines to a browser, much | |
| 55 | * less ten million. This is here to set an upper limit on | |
| 56 | * the tokenizer loop, to prevent certain attacks. | |
| 57 | */ | |
| 58 | static final int MAXIMUM_LINES_ALLOWED = 10_000_000; | |
| 59 | ||
| 60 | public void render(Map<String, String> myMap, StringBuilder stringBuilder) { | |
| 61 |
1
1. render : negated conditional → KILLED |
if (templateType.equals(STATIC_TEXT)) { |
| 62 | stringBuilder.append(subString); | |
| 63 | } else { | |
| 64 | String value = myMap.get(key); | |
| 65 | List<String> lines = tokenizer(value, '\n', MAXIMUM_LINES_ALLOWED); | |
| 66 | ||
| 67 | // if, after splitting on newlines, we have more than one line, we'll indent the remaining | |
| 68 | // lines so that they end up at the same column as the first line. | |
| 69 | stringBuilder.append(lines.getFirst()); | |
| 70 |
2
1. render : changed conditional boundary → KILLED 2. render : negated conditional → KILLED |
for (int i = 1; i < lines.size(); i++) { |
| 71 |
1
1. render : negated conditional → KILLED |
if (lines.get(i).isEmpty()) { |
| 72 | stringBuilder.append('\n'); | |
| 73 | } else { | |
| 74 |
1
1. render : Replaced integer subtraction with addition → KILLED |
stringBuilder.append('\n').append(" ".repeat(indent - 1)).append(lines.get(i)); |
| 75 | } | |
| 76 | } | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | @Override | |
| 81 | public boolean equals(Object o) { | |
| 82 |
3
1. equals : replaced boolean return with true for com/renomad/minum/templating/TemplateSection::equals → TIMED_OUT 2. equals : negated conditional → TIMED_OUT 3. equals : negated conditional → KILLED |
if (o == null || getClass() != o.getClass()) return false; |
| 83 | TemplateSection that = (TemplateSection) o; | |
| 84 |
5
1. equals : replaced boolean return with true for com/renomad/minum/templating/TemplateSection::equals → TIMED_OUT 2. equals : negated conditional → TIMED_OUT 3. equals : negated conditional → KILLED 4. equals : negated conditional → KILLED 5. equals : negated conditional → KILLED |
return indent == that.indent && Objects.equals(key, that.key) && Objects.equals(subString, that.subString) && templateType == that.templateType; |
| 85 | } | |
| 86 | ||
| 87 | @Override | |
| 88 | public int hashCode() { | |
| 89 |
1
1. hashCode : replaced int return with 0 for com/renomad/minum/templating/TemplateSection::hashCode → TIMED_OUT |
return Objects.hash(key, subString, indent, templateType); |
| 90 | } | |
| 91 | ||
| 92 | @Override | |
| 93 | public String toString() { | |
| 94 |
1
1. toString : replaced return value with "" for com/renomad/minum/templating/TemplateSection::toString → KILLED |
return "TemplateSection{" + |
| 95 | "key='" + key + '\'' + | |
| 96 | ", subString='" + subString + '\'' + | |
| 97 | ", indent=" + indent + | |
| 98 | ", templateType=" + templateType + | |
| 99 | '}'; | |
| 100 | } | |
| 101 | } | |
Mutations | ||
| 47 |
1.1 2.2 3.3 |
|
| 48 |
1.1 2.2 3.3 |
|
| 61 |
1.1 |
|
| 70 |
1.1 2.2 |
|
| 71 |
1.1 |
|
| 74 |
1.1 |
|
| 82 |
1.1 2.2 3.3 |
|
| 84 |
1.1 2.2 3.3 4.4 5.5 |
|
| 89 |
1.1 |
|
| 94 |
1.1 |