CompatSong.java

1
package com.github.dakusui.symfonion.song;
2
3
import com.github.dakusui.logias.Logias;
4
import com.github.dakusui.logias.lisp.Context;
5
import com.github.dakusui.symfonion.compat.exceptions.ExceptionContext;
6
import com.github.dakusui.symfonion.compat.exceptions.SymfonionException;
7
import com.github.dakusui.symfonion.compat.json.CompatJsonException;
8
import com.github.dakusui.symfonion.compat.json.CompatJsonUtils;
9
import com.github.dakusui.symfonion.utils.Utils;
10
import com.github.valid8j.classic.Requires;
11
import com.github.valid8j.pcond.forms.Predicates;
12
import com.google.gson.JsonArray;
13
import com.google.gson.JsonElement;
14
import com.google.gson.JsonObject;
15
16
import java.util.*;
17
import java.util.function.Predicate;
18
19
import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.ContextKey.JSON_ELEMENT_ROOT;
20
import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.exceptionContext;
21
import static com.github.dakusui.symfonion.compat.exceptions.CompatExceptionThrower.typeMismatchException;
22
import static com.github.dakusui.symfonion.compat.exceptions.ExceptionContext.entry;
23
import static com.github.dakusui.symfonion.compat.exceptions.SymfonionTypeMismatchException.ARRAY;
24
import static com.github.dakusui.symfonion.compat.exceptions.SymfonionTypeMismatchException.OBJECT;
25
import static com.github.valid8j.classic.Requires.requireNonNull;
26
import static java.util.Collections.unmodifiableList;
27
import static java.util.Collections.unmodifiableSet;
28
29
/**
30
 * //@formatter:off
31
 * This class models the logical aspect of the entire musical piece, described by the application syntax.
32
 *
33
 * Entries that directly matter in this class are as follows:
34
 *
35
 * .Entries `(Compat)Song` class processes
36
 * ----
37
 * {
38
 *   "$noteMaps": { "<noteMapName>": "<NoteMap>"},
39
 *   "parts": { "<partName>": "<Part>" },
40
 *   "grooves": { "<grooveName>": ["<Groove>", "<Groove>", "..."] },
41
 *   "sequence": [ "<Bar>", "<Bar>", "..." ]
42
 * }
43
 * ----
44
 *
45
 * Each element in `$sequence` array is a {@link Bar} object.
46
 * A bar contains notes and its related information played during a certain period of time.
47
 *
48
 * //@formatter:on
49
 */
50
public class CompatSong {
51
  private final Map<String, Part>   parts;
52
  private final Map<String, Groove> grooves;
53
  private final List<Bar>            bars;
54
  private final JsonObject           rootJsonObject;
55
56
  /**
57
   * Creates an object of this class.
58
   *
59
   * @param parts          Parts of a musical work.
60
   * @param grooves        Grooves referenced from `bars`.
61
   * @param bars           Bars of a musical work.
62
   * @param rootJsonObject A root JSON object that `barJsonObject` belongs to.
63
   */
64
  public CompatSong(Map<String, Part> parts,
65
                    Map<String, Groove> grooves,
66
                    List<Bar> bars,
67
                    JsonObject rootJsonObject) {
68
    this.parts          = Requires.requireNonNull(parts);
69
    this.grooves        = requireNonNull(grooves);
70
    this.bars           = requireNonNull(bars);
71
    this.rootJsonObject = requireNonNull(rootJsonObject);
72
  }
73
74
  /**
75
   * Returns bars.
76
   *
77
   * @return Bars.
78
   */
79
  public List<Bar> bars() {
80 1 1. bars : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/CompatSong::bars → KILLED
    return unmodifiableList(this.bars);
81
  }
82
83
  /**
84
   * Returns all known part names.
85
   *
86
   * @return A list of part names.
87
   */
88
  public Set<String> partNames() {
89 1 1. partNames : replaced return value with Collections.emptySet for com/github/dakusui/symfonion/song/CompatSong::partNames → KILLED
    return unmodifiableSet(this.parts.keySet());
90
  }
91
92
  public Part part(String name) {
93 1 1. part : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong::part → KILLED
    return this.parts.get(name);
94
  }
95
96
  public Groove groove(String grooveName) {
97 1 1. groove : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong::groove → NO_COVERAGE
    return this.grooves.get(grooveName);
98
  }
99
100
  /**
101
   * Returns a root JSON object to which this bar belongs.
102
   *
103
   * @return A root JSON object.
104
   */
105
  public JsonObject rootJsonObject() {
106 1 1. rootJsonObject : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong::rootJsonObject → KILLED
    return this.rootJsonObject;
107
  }
108
109
  public static class Builder {
110
    private final JsonObject json;
111
112
    private Predicate<Bar>    barFilter  = Predicates.alwaysTrue();
113
    private Predicate<String> partFilter = Predicates.alwaysTrue();
114
115
    public Builder(JsonObject jsonObject) {
116
      this.json = requireNonNull(jsonObject);
117
    }
118
119
    public Builder barFilter(Predicate<Bar> barFilter) {
120
      this.barFilter = requireNonNull(barFilter);
121 1 1. barFilter : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::barFilter → KILLED
      return this;
122
    }
123
124
    public Builder partFilter(Predicate<String> partFilter) {
125
      this.partFilter = requireNonNull(partFilter);
126 1 1. partFilter : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::partFilter → KILLED
      return this;
127
    }
128
129
    public CompatSong build() throws CompatJsonException, SymfonionException {
130
      try (ExceptionContext ignored = exceptionContext(entry(JSON_ELEMENT_ROOT, json))) {
131
        Map<String, NoteMap> noteMaps = initNoteMaps(json);
132
        Map<String, Groove>  grooves  = initGrooves(json);
133 1 1. build : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::build → KILLED
        return new CompatSong(initParts(this.json),
134
                              grooves,
135
                              initBars(json,
136
                                       grooves,
137
                                       noteMaps,
138
                                       this.barFilter,
139
                                       this.partFilter),
140
                              json);
141
      }
142
    }
143
144
    public static Context loadMidiDeviceProfile(JsonObject json, Context logiasContext) throws SymfonionException, CompatJsonException {
145
      JsonElement tmp = CompatJsonUtils.asJsonObjectWithDefault(json, new JsonObject(), Keyword.settings);
146 1 1. loadMidiDeviceProfile : negated conditional → KILLED
      if (!tmp.isJsonObject()) {
147
        throw typeMismatchException(tmp, OBJECT);
148
      }
149
      String profileName = CompatJsonUtils.asStringWithDefault(tmp.getAsJsonObject(), "", Keyword.mididevice);
150
      Logias logias      = new Logias(logiasContext);
151 1 1. loadMidiDeviceProfile : negated conditional → KILLED
      if (!"".equals(profileName)) {
152
        JsonObject       deviceDef = CompatJsonUtils.toJson(Utils.loadResource(profileName + ".json")).getAsJsonObject();
153
        Iterator<String> i         = CompatJsonUtils.keyIterator(deviceDef);
154 1 1. loadMidiDeviceProfile : negated conditional → NO_COVERAGE
        while (i.hasNext()) {
155
          String      k = i.next();
156
          JsonElement v = deviceDef.get(k);
157
          logiasContext.bind(k, logias.run(logias.buildSexp(v)));
158
        }
159
      }
160 1 1. loadMidiDeviceProfile : replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::loadMidiDeviceProfile → SURVIVED
      return logiasContext;
161
    }
162
163
    private static List<Bar> initBars(
164
        JsonObject json,
165
        Map<String, Groove> grooves,
166
        Map<String, NoteMap> noteMaps,
167
        Predicate<Bar> barFilter,
168
        Predicate<String> partFilter) throws SymfonionException, CompatJsonException {
169
      List<Bar> bars = new LinkedList<>();
170
      try (ExceptionContext ignored = exceptionContext(entry(JSON_ELEMENT_ROOT, json))) {
171
        JsonElement tmp = CompatJsonUtils.asJsonElement(json, Keyword.sequence);
172 1 1. initBars : negated conditional → KILLED
        if (!tmp.isJsonArray()) {
173
          throw typeMismatchException(tmp, ARRAY);
174
        }
175
        JsonArray seqJson = tmp.getAsJsonArray();
176
        int       len     = seqJson.getAsJsonArray().size();
177 2 1. initBars : changed conditional boundary → KILLED
2. initBars : negated conditional → KILLED
        for (int i = 0; i < len; i++) {
178
          JsonElement barJson = seqJson.get(i);
179 1 1. initBars : negated conditional → KILLED
          if (!barJson.isJsonObject()) {
180
            throw typeMismatchException(seqJson, OBJECT);
181
          }
182
          Bar bar = new Bar(barJson.getAsJsonObject(),
183
                            grooves,
184
                            noteMaps,
185
                            partFilter);
186 1 1. initBars : negated conditional → KILLED
          if (barFilter.test(bar))
187
            bars.add(bar);
188
        }
189
      }
190 1 1. initBars : replaced return value with Collections.emptyList for com/github/dakusui/symfonion/song/CompatSong$Builder::initBars → KILLED
      return bars;
191
    }
192
193
    /**
194
     * Returns a map of note-map name to note-map.
195
     *
196
     * @param json A JSON object that defines note-maps.
197
     * @return A map of note-map name to note-map.
198
     * @throws SymfonionException  An error found in {@code json} argument.
199
     * @throws CompatJsonException An error found in {@code json} argument.
200
     */
201
    static Map<String, NoteMap> initNoteMaps(JsonObject json) throws SymfonionException, CompatJsonException {
202
      Map<String, NoteMap> noteMaps     = new HashMap<>();
203
      final JsonObject     noteMapsJSON = CompatJsonUtils.asJsonObjectWithDefault(json, new JsonObject(), Keyword.notemaps);
204
205
      Iterator<String> i = CompatJsonUtils.keyIterator(noteMapsJSON);
206
      noteMaps.put(Keyword.normal.toString(), NoteMap.defaultNoteMap);
207
      noteMaps.put(Keyword.percussion.toString(), NoteMap.defaultPercussionMap);
208 1 1. initNoteMaps : negated conditional → KILLED
      while (i.hasNext()) {
209
        String  noteMapName = i.next();
210
        NoteMap cur         = new NoteMap(CompatJsonUtils.asJsonObject(noteMapsJSON, noteMapName));
211
        noteMaps.put(noteMapName, cur);
212
      }
213 1 1. initNoteMaps : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initNoteMaps → KILLED
      return noteMaps;
214
    }
215
216
    static Map<String, Part> initParts(JsonObject json) throws SymfonionException, CompatJsonException {
217
      Map<String, Part> parts = new HashMap<>();
218 1 1. initParts : negated conditional → KILLED
      if (CompatJsonUtils.hasPath(json, Keyword.parts)) {
219
        JsonObject       instrumentsJSON = CompatJsonUtils.asJsonObject(json, Keyword.parts);
220
        Iterator<String> i               = CompatJsonUtils.keyIterator(instrumentsJSON);
221 1 1. initParts : negated conditional → KILLED
        while (i.hasNext()) {
222
          String name = i.next();
223
          Part   cur  = new Part(name, CompatJsonUtils.asJsonObject(instrumentsJSON, name));
224
          parts.put(name, cur);
225
        }
226
      }
227 1 1. initParts : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initParts → KILLED
      return parts;
228
    }
229
230
    static Map<String, Groove> initGrooves(JsonObject json) throws SymfonionException, CompatJsonException {
231
      Map<String, Groove> grooves = new HashMap<>();
232 1 1. initGrooves : negated conditional → KILLED
      if (CompatJsonUtils.hasPath(json, Keyword.grooves)) {
233
        JsonObject groovesJSON = CompatJsonUtils.asJsonObject(json, Keyword.grooves);
234
235
        Iterator<String> i = CompatJsonUtils.keyIterator(groovesJSON);
236 1 1. initGrooves : negated conditional → KILLED
        while (i.hasNext()) {
237
          String name = i.next();
238
          Groove cur  = Groove.createGroove(CompatJsonUtils.asJsonArray(groovesJSON, name));
239
          grooves.put(name, cur);
240
        }
241
      }
242 1 1. initGrooves : replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initGrooves → KILLED
      return grooves;
243
    }
244
  }
245
}

Mutations

80

1.1
Location : bars
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/CompatSong::bars → KILLED

89

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:#3]
replaced return value with Collections.emptySet for com/github/dakusui/symfonion/song/CompatSong::partNames → KILLED

93

1.1
Location : part
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:#3]
replaced return value with null for com/github/dakusui/symfonion/song/CompatSong::part → KILLED

97

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

106

1.1
Location : rootJsonObject
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/CompatSong::rootJsonObject → KILLED

121

1.1
Location : barFilter
Killed by : com.github.dakusui.symfonion.tests.InvalidJsonErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.InvalidJsonErrorTest]/[method:invalid_01()]
replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::barFilter → KILLED

126

1.1
Location : partFilter
Killed by : com.github.dakusui.symfonion.tests.InvalidJsonErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.InvalidJsonErrorTest]/[method:invalid_01()]
replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::partFilter → KILLED

133

1.1
Location : build
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/CompatSong$Builder::build → KILLED

146

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

151

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

154

1.1
Location : loadMidiDeviceProfile
Killed by : none
negated conditional → NO_COVERAGE

160

1.1
Location : loadMidiDeviceProfile
Killed by : none
replaced return value with null for com/github/dakusui/symfonion/song/CompatSong$Builder::loadMidiDeviceProfile → SURVIVED
Covering tests

172

1.1
Location : initBars
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

177

1.1
Location : initBars
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]
changed conditional boundary → KILLED

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

179

1.1
Location : initBars
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

186

1.1
Location : initBars
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]
negated conditional → KILLED

190

1.1
Location : initBars
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/CompatSong$Builder::initBars → KILLED

208

1.1
Location : initNoteMaps
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

213

1.1
Location : initNoteMaps
Killed by : com.github.dakusui.symfonion.tests.InvalidDataErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.InvalidDataErrorTest]/[method:illegalNoteLength_02()]
replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initNoteMaps → KILLED

218

1.1
Location : initParts
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:#3]
negated conditional → KILLED

221

1.1
Location : initParts
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

227

1.1
Location : initParts
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:#3]
replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initParts → KILLED

232

1.1
Location : initGrooves
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

236

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

242

1.1
Location : initGrooves
Killed by : com.github.dakusui.symfonion.tests.ReferenceErrorTest.[engine:junit-jupiter]/[class:com.github.dakusui.symfonion.tests.ReferenceErrorTest]/[method:missingNoteMap()]
replaced return value with Collections.emptyMap for com/github/dakusui/symfonion/song/CompatSong$Builder::initGrooves → KILLED

Active mutators

Tests examined


Report generated by PIT 1.19.1