Bar.java

1
package com.github.dakusui.symfonion.song;
2
3
import com.github.dakusui.symfonion.compat.exceptions.FractionFormatException;
4
import com.github.dakusui.symfonion.compat.exceptions.SymfonionException;
5
import com.github.dakusui.symfonion.compat.json.CompatJsonException;
6
import com.github.dakusui.symfonion.compat.json.CompatJsonUtils;
7
import com.github.dakusui.symfonion.utils.Fraction;
8
import com.google.gson.JsonArray;
9
import com.google.gson.JsonElement;
10
import com.google.gson.JsonObject;
11
12
import java.util.*;
13
import java.util.function.Predicate;
14
import java.util.stream.StreamSupport;
15
16
import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.*;
17
import static com.github.dakusui.symfonion.compat.exceptions.SymfonionIllegalFormatException.FRACTION_EXAMPLE;
18
import static com.github.dakusui.symfonion.compat.exceptions.SymfonionTypeMismatchException.OBJECT;
19
import static com.github.dakusui.symfonion.compat.json.CompatJsonUtils.asJsonElement;
20
import static com.github.dakusui.symfonion.compat.json.JsonUtils.findJsonArray;
21
import static com.github.dakusui.symfonion.compat.json.JsonUtils.path;
22
import static com.github.dakusui.symfonion.utils.Fraction.parseFraction;
23
import static com.github.valid8j.classic.Requires.requireNonNull;
24
import static java.util.Collections.*;
25
26
/**
27
 * // @formatter:off
28
 * A class that models a "bar" in a musical score.
29
 *
30
 * // @formatter:on
31
 */
32
public class Bar {
33
  private final Map<String, NoteMap>    noteMaps;
34
  private final Fraction                beats;
35
  private final Map<String, List<Pattern>> patterns;
36
  private final Groove                  groove;
37
  private final List<String>            labels;
38
  private final JsonObject              barJsonObject;
39
  private final JsonArray               partsJsonArray;
40
41
42
  /**
43
   * Creates a `Bar` object.
44
   *
45
   * // @formatter:off
46
   * `barJsonObject` stores content of the bar.
47
   *
48
   * [source, JSON]
49
   * .barJsonObject
50
   * ----
51
   * {
52
   *   "beats": "<beatsDefiningString>",
53
   *   "parts": [
54
   *     { "name": "<partName>", "<inline pattern definition>" },
55
   *     ...
56
   *   ],
57
   *   "groove": "<grooveName>",
58
   *   "$noteMap": "<noteMapName>",
59
   *   "labels": ["<label1>", "<label2>", "..."]
60
   * }
61
   * ----
62
   *
63
   * `root` is used only for composing messages on errors.
64
   * // @formatter:on
65
   *
66
   * @param barJsonObject A JSON object from which a bar is created.
67
   * @param grooves       A map that defines a groove on which this bar should be played.
68
   * @param noteMaps      A note map with which this bar should be played.
69
   * @param partFilter    A predicate that filters parts to be played in this bar.
70
   * @throws SymfonionException  Data processing was failed.
71
   * @throws CompatJsonException Invalid JSON is given.
72
   */
73
  public Bar(JsonObject barJsonObject,
74
             Map<String, Groove> grooves,
75
             Map<String, NoteMap> noteMaps,
76
             Predicate<String> partFilter) throws SymfonionException,
77
                                                  CompatJsonException {
78
    this.noteMaps      = requireNonNull(noteMaps);
79
    this.barJsonObject = requireNonNull(barJsonObject);
80
    this.beats         = extractBeatsFractionFrom(barJsonObject);
81
    this.groove        = resolveGrooveForBar(barJsonObject, this.beats, grooves);
82
    this.labels        = resolveLabelsForBar(barJsonObject);
83
    this.partsJsonArray = getPartsInBarAsJsonArray(barJsonObject);
84
    this.patterns = composePatternsMap(this, partFilter, this.partsJsonArray);
85
  }
86
87
  /**
88
   * Returns labels attached to this bar object.
89
   *
90
   * @return A list of labels.
91
   */
92
  public List<String> labels() {
93 1 1. labels : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::labels → SURVIVED
    return this.labels;
94
  }
95
96
  /**
97
   * Returns a set of part names.
98
   *
99
   * @return A set of part names.
100
   */
101
  public Set<String> partNames() {
102 1 1. partNames : replaced return value with Collections.emptySet for com/github/dakusui/symfonion/song/Bar::partNames → KILLED
    return unmodifiableSet(this.patterns.keySet());
103
  }
104
105
  /**
106
   * Returns the list of stacked `Pattern` objects for the given `partName`.
107
   * Multiple entries with the same `$name` in `$parts` are played simultaneously.
108
   *
109
   * @param partName A part name for which the patterns should be returned.
110
   * @return A list of `Pattern` objects (stacked).
111
   */
112
  public List<Pattern> patternsForPart(String partName) {
113 1 1. patternsForPart : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::patternsForPart → KILLED
    return this.patterns.get(partName);
114
  }
115
116
  /**
117
   * Returns `Beats` that determine the duration of this object.
118
   *
119
   * @return Beats
120
   */
121
  public Fraction beats() {
122 1 1. beats : replaced return value with null for com/github/dakusui/symfonion/song/Bar::beats → KILLED
    return this.beats;
123
  }
124
125
  /**
126
   * Returns a `Groove` object that determines "groove" of this bar
127
   *
128
   * @return A `Groove` object.
129
   * @see Groove
130
   */
131
  public Groove groove() {
132 1 1. groove : replaced return value with null for com/github/dakusui/symfonion/song/Bar::groove → KILLED
    return this.groove;
133
  }
134
135
  /**
136
   * Find up a `JsonElement` in `$parts` array that has `$name` matching `partName`.
137
   * Returns the first matching element, or the `$parts` array itself if not found.
138
   *
139
   * @param partName partName used for searching.
140
   * @return A matching JSON element, or the array if not found.
141
   */
142
  public JsonElement lookUpJsonNode(String partName) {
143
    for (JsonElement elem : this.partsJsonArray) {
144 1 1. lookUpJsonNode : negated conditional → KILLED
      if (elem.isJsonObject()) {
145
        JsonObject obj = elem.getAsJsonObject();
146 2 1. lookUpJsonNode : negated conditional → KILLED
2. lookUpJsonNode : negated conditional → KILLED
        if (obj.has(Keyword.name.name()) && partName.equals(CompatJsonUtils.asString(obj, Keyword.name))) {
147 1 1. lookUpJsonNode : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → KILLED
          return elem;
148
        }
149
      }
150
    }
151 1 1. lookUpJsonNode : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → NO_COVERAGE
    return this.partsJsonArray;
152
  }
153
154
  /**
155
   * // @formatter:off
156
   * Composes a map from part name to list of stacked patterns.
157
   *
158
   * [source, JSON]
159
   * .partsJsonArrayInBar
160
   * ----
161
   * [
162
   *   { "name": "<partName>", "<inline pattern definition>" },
163
   *   { "name": "<partName>", "<inline pattern definition>" }
164
   * ]
165
   * ----
166
   * // @formatter:on
167
   *
168
   * @param bar                 A bar object from which the pattern map is created.
169
   * @param partFilter          A predicate that filters a part to be rendered.
170
   * @param partsJsonArrayInBar A JSON array associated with `$parts` key in the bar JSON object.
171
   * @return A map from part name to a list of patterns (stacked).
172
   */
173
  private static Map<String, List<Pattern>> composePatternsMap(Bar bar,
174
                                                               Predicate<String> partFilter,
175
                                                               JsonArray partsJsonArrayInBar) {
176
    Map<String, List<Pattern>> ret = new LinkedHashMap<>();
177
    for (JsonElement elem : partsJsonArrayInBar) {
178 1 1. composePatternsMap : negated conditional → KILLED
      if (!elem.isJsonObject()) {
179
        throw typeMismatchException(elem, OBJECT);
180
      }
181
      JsonObject partObj  = elem.getAsJsonObject();
182
      String     partName = CompatJsonUtils.asString(partObj, Keyword.name);
183 1 1. composePatternsMap : negated conditional → KILLED
      if (!partFilter.test(partName))
184
        continue;
185 1 1. lambda$composePatternsMap$0 : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::lambda$composePatternsMap$0 → KILLED
      ret.computeIfAbsent(partName, k -> new ArrayList<>())
186
         .add(Pattern.createPattern(partObj, bar.noteMaps));
187
    }
188 1 1. composePatternsMap : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/Bar::composePatternsMap → KILLED
    return ret;
189
  }
190
191
  static List<String> resolveLabelsForBar(JsonObject barJsonObject) {
192 1 1. resolveLabelsForBar : negated conditional → KILLED
    if (CompatJsonUtils.hasPath(barJsonObject, Keyword.labels)) {
193 1 1. resolveLabelsForBar : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::resolveLabelsForBar → NO_COVERAGE
      return StreamSupport.stream(CompatJsonUtils.asJsonArray(barJsonObject, Keyword.labels).spliterator(), false)
194 1 1. lambda$resolveLabelsForBar$1 : replaced return value with "" for com/github/dakusui/symfonion/song/Bar::lambda$resolveLabelsForBar$1 → NO_COVERAGE
                          .map(CompatJsonUtils::asString)
195
                          .toList();
196
    }
197
    return emptyList();
198
  }
199
200
  private static Groove resolveGrooveForBar(JsonObject barJsonObject, Fraction barLength, Map<String, Groove> grooves) {
201
    Groove g = Groove.defaultGrooveOf(barLength);
202 1 1. resolveGrooveForBar : negated conditional → KILLED
    if (CompatJsonUtils.hasPath(barJsonObject, Keyword.groove)) {
203
      String grooveName = CompatJsonUtils.asString(barJsonObject, Keyword.groove.name());
204
      g = grooves.get(grooveName);
205 1 1. resolveGrooveForBar : negated conditional → KILLED
      if (g == null) {
206
        throw grooveNotDefinedException(asJsonElement(barJsonObject, Keyword.groove), grooveName);
207
      }
208
    }
209 1 1. resolveGrooveForBar : replaced return value with null for com/github/dakusui/symfonion/song/Bar::resolveGrooveForBar → KILLED
    return g;
210
  }
211
212
  static Fraction extractBeatsFractionFrom(JsonObject barJsonObject) {
213
    Fraction beats;
214
    try {
215
      beats = parseFraction(CompatJsonUtils.asString(barJsonObject, Keyword.beats));
216
    } catch (FractionFormatException e) {
217
      throw illegalFormatException(asJsonElement(barJsonObject, Keyword.beats), FRACTION_EXAMPLE);
218
    }
219 1 1. extractBeatsFractionFrom : negated conditional → SURVIVED
    beats = beats == null ? Fraction.ONE : beats;
220 1 1. extractBeatsFractionFrom : replaced return value with null for com/github/dakusui/symfonion/song/Bar::extractBeatsFractionFrom → KILLED
    return beats;
221
  }
222
223
  /**
224
   * @param barJsonObject A JSON object that models a bar in a musical score.
225
   * @return JSON array under `$parts` element
226
   */
227
  private static JsonArray getPartsInBarAsJsonArray(JsonObject barJsonObject) {
228 2 1. lambda$getPartsInBarAsJsonArray$2 : replaced return value with null for com/github/dakusui/symfonion/song/Bar::lambda$getPartsInBarAsJsonArray$2 → NO_COVERAGE
2. getPartsInBarAsJsonArray : replaced return value with null for com/github/dakusui/symfonion/song/Bar::getPartsInBarAsJsonArray → KILLED
    return findJsonArray(barJsonObject, path(Keyword.parts)).orElseThrow(() -> requiredElementMissingException(barJsonObject,
229
                                                                                                               Keyword.parts));
230
  }
231
232
}

Mutations

93

1.1
Location : labels
Killed by : none
replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::labels → SURVIVED
Covering tests

102

1.1
Location : partNames
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with Collections.emptySet for com/github/dakusui/symfonion/song/Bar::partNames → KILLED

113

1.1
Location : patternsForPart
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::patternsForPart → KILLED

122

1.1
Location : beats
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#2]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::beats → KILLED

132

1.1
Location : groove
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::groove → KILLED

144

1.1
Location : lookUpJsonNode
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingPart()]
negated conditional → KILLED

146

1.1
Location : lookUpJsonNode
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingPart()]
negated conditional → KILLED

2.2
Location : lookUpJsonNode
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingPart()]
negated conditional → KILLED

147

1.1
Location : lookUpJsonNode
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingPart()]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → KILLED

151

1.1
Location : lookUpJsonNode
Killed by : none
replaced return value with null for com/github/dakusui/symfonion/song/Bar::lookUpJsonNode → NO_COVERAGE

178

1.1
Location : composePatternsMap
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingNoteMap()]
negated conditional → KILLED

183

1.1
Location : composePatternsMap
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingNoteMap()]
negated conditional → KILLED

185

1.1
Location : lambda$composePatternsMap$0
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::lambda$composePatternsMap$0 → KILLED

188

1.1
Location : composePatternsMap
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/Bar::composePatternsMap → KILLED

192

1.1
Location : resolveLabelsForBar
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#2]
negated conditional → KILLED

193

1.1
Location : resolveLabelsForBar
Killed by : none
replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/Bar::resolveLabelsForBar → NO_COVERAGE

194

1.1
Location : lambda$resolveLabelsForBar$1
Killed by : none
replaced return value with "" for com/github/dakusui/symfonion/song/Bar::lambda$resolveLabelsForBar$1 → NO_COVERAGE

202

1.1
Location : resolveGrooveForBar
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#2]
negated conditional → KILLED

205

1.1
Location : resolveGrooveForBar
Killed by : com.github.dakusui.symfonion.tests.InvalidJsonErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.InvalidJsonErrorTest]/[method:missingSection_groove()]
negated conditional → KILLED

209

1.1
Location : resolveGrooveForBar
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#4]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::resolveGrooveForBar → KILLED

219

1.1
Location : extractBeatsFractionFrom
Killed by : none
negated conditional → SURVIVED
Covering tests

220

1.1
Location : extractBeatsFractionFrom
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#2]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::extractBeatsFractionFrom → KILLED

228

1.1
Location : getPartsInBarAsJsonArray
Killed by : com.github.dakusui.symfonion.tests.MidiCompilerTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.MidiCompilerTest]/[test-template:exercise(com.github.dakusui.symfonion.testutils.SymfonionTestCase)]/[test-template-invocation:#2]
replaced return value with null for com/github/dakusui/symfonion/song/Bar::getPartsInBarAsJsonArray → KILLED

2.2
Location : lambda$getPartsInBarAsJsonArray$2
Killed by : none
replaced return value with null for com/github/dakusui/symfonion/song/Bar::lambda$getPartsInBarAsJsonArray$2 → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1