//----------------------------------------------------------------------------//
// //
// S c o r e B e n c h //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.score;
import omr.WellKnowns;
import omr.sheet.Bench;
import omr.step.Step;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Class {@code ScoreBench} is in charge of recording all important information
* related to the processing of a music score, and producing an output formatted
* as "key = value" lines of text.
*
* <p>In order to cope with possible multiple recordings with the same radix, we
* always add to a temporary property set, using numbered suffixes (.01, .02,
* etc) so that no data is ever overwritten. The temporary set contains only
* lines formatted as "radix.suffix = value".</p>
*
* <p>When the recordings are to be flushed, the temporary set is used to
* produce a clean set of external properties, according to the following
* rules:<ul>
*
* <li>When only the .01 suffix exists for a given radix, then the externals
* just contains the "radix = value" line, and the .01 suffix is not transferred
* to the output.</li>
*
* <li>When more than the .01 suffix exist for a given radix, then these
* intermediate "radix.suffix = value" pairs are copied to the externals as they
* are. The last key/value pair is also used to set the "radix = value" pair in
* the externals (so that the latest value is always accessible through its
* simple radix)</li>
*
* <li>For a special kind of keys (step.[name].duration), the "radix = value"
* line does not contain the latest intermediate value, but rather the sum of
* all intermediate values</li></ul>
*
* <p>The recorded data can be flushed to disk on specific occasions, to make
* sure that no data ever get lost even in the case of step cancellation or
* program interruption. <br/>In case of step cancellation the line
* "whole.cancelled = true" is added to the externals. <br/>In case of program
* interruption the line "whole.interrupted = true" is kept in the
* externals.</p>
*
* @author Hervé Bitteur
*/
public class ScoreBench
extends Bench
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(ScoreBench.class);
/** Special key which indicates that an interruption has occurred */
private static final String INTERRUPTION_KEY = "whole.interrupted";
//~ Instance fields --------------------------------------------------------
/** The related score */
private final Score score;
/** Time stamp when this instance was created */
private final long startTime = System.currentTimeMillis();
/** Starting date */
private final Date date = new Date(startTime);
//~ Constructors -----------------------------------------------------------
//------------//
// ScoreBench //
//------------//
/**
* Creates a new ScoreBench object.
*
* @param score the related score
*/
public ScoreBench (Score score)
{
this.score = score;
// To be later removed, but only at normal completion point
addProp(INTERRUPTION_KEY, "true");
addProp("date", date.toString());
addProp("program", WellKnowns.TOOL_NAME);
addProp("version", WellKnowns.TOOL_REF);
addProp("image", score.getImagePath());
flushBench();
}
//~ Methods ----------------------------------------------------------------
//------------//
// flushBench //
//------------//
/**
* Flush the current content of bench to disk
*/
@Override
public final synchronized void flushBench ()
{
ScoresManager.getInstance()
.storeBench(this, null, false);
}
//----------//
// getScore //
//----------//
/**
* @return the score
*/
public Score getScore ()
{
return score;
}
//--------------------//
// recordCancellation //
//--------------------//
public synchronized void recordCancellation ()
{
addProp("whole.cancelled", "true");
}
//------------//
// recordStep //
//------------//
public synchronized void recordStep (Step step,
long duration)
{
addProp(
"step." + step.getName().toLowerCase() + ".duration",
"" + duration);
flushBench();
}
//-------//
// store //
//-------//
/**
* Store this bench into an output stream
*
* @param output the output stream to be written
* @param complete true if bench data must be finalized
* @throws IOException
*/
public synchronized void store (OutputStream output,
boolean complete)
throws IOException
{
// Build external properties
Properties externals = cleanupProps();
// What do we do with the script data? and with app constants?
// TBD
// Insert global duration (up till now)
long wholeDuration = System.currentTimeMillis() - startTime;
externals.setProperty("whole.duration", "" + wholeDuration);
// Finalize this bench?
if (complete) {
Object obj = externals.remove(INTERRUPTION_KEY);
}
// Sort and store to file
SortedSet<String> keys = new TreeSet<>();
for (Object obj : externals.keySet()) {
String key = (String) obj;
keys.add(key);
}
PrintWriter writer = new PrintWriter(output);
for (String key : keys) {
writer.println(key + " = " + externals.getProperty(key));
}
writer.flush();
}
//--------------//
// cleanupProps //
//--------------//
/**
* Build the externals properties, radix by radix, playing with the key
* suffixes
*/
private Properties cleanupProps ()
{
Properties externals = new Properties();
// Retrieve key radices
Set<String> radices = new HashSet<>();
for (Object obj : props.keySet()) {
String key = (String) obj;
int dot = key.lastIndexOf('.');
String radix = key.substring(0, dot);
radices.add(radix);
}
// Browse radices
for (String radix : radices) {
if (!props.containsKey(keyOf(radix, 2))) {
// We have just 1 property: we rename it as the radix
String key1 = keyOf(radix, 1);
externals.setProperty(radix, props.getProperty(key1));
} else {
// We have several properties, so we keep all the intermediate values
// Special case for step radix: we sum up the durations
// Standard radix case: we use the latest value
boolean isStep = radix.startsWith("step.")
&& radix.endsWith(".duration");
int sum = 0;
for (int index = 1;; index++) {
String key = keyOf(radix, index);
String str = props.getProperty(key);
if (str == null) {
break;
} else {
// Keep intermediate value
externals.setProperty(key, str);
if (isStep) {
// Sum up step durations
sum += Integer.parseInt(str);
} else {
// Overwrite the radix value
externals.setProperty(radix, str);
}
}
}
if (isStep) {
// Write the total sum as the radix value
externals.setProperty(radix, "" + sum);
}
}
}
return externals;
}
}