//----------------------------------------------------------------------------//
// //
// S t e p p i n g //
// //
//----------------------------------------------------------------------------//
// <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.step;
import omr.score.Score;
import omr.score.entity.Page;
import omr.score.ui.ScoreActions;
import omr.script.StepTask;
import omr.sheet.Sheet;
import omr.sheet.SystemInfo;
import omr.sheet.ui.SheetsController;
import static omr.step.Steps.*;
import omr.util.OmrExecutors;
import omr.util.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import javax.swing.SwingUtilities;
/**
* Class {@code Stepping} handles the scheduling of step(s) on a score
* or a sheet, with notification to the user interface when running in
* interactive mode.
*
* @author Hervé Bitteur
*/
public class Stepping
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(Stepping.class);
/** Related progress monitor when used in interactive mode. */
private static volatile StepMonitor monitor;
//~ Constructors -----------------------------------------------------------
/**
* Not meant to be instantiated.
*/
private Stepping ()
{
}
//~ Methods ----------------------------------------------------------------
//
//---------------//
// createMonitor //
//---------------//
/**
* Allows to couple the steps with a UI.
*
* @return the monitor to deal with steps
*/
public static StepMonitor createMonitor ()
{
return monitor = new StepMonitor();
}
//----------------//
// doOneSheetStep //
//----------------//
/**
* At sheet level, do just one specified step, synchronously, with
* display of related UI and recording of the step into the script.
*
* @param step the step to perform
* @param sheet the sheet to be processed
* @throws StepException
*/
private static void doOneSheetStep (final Step step,
Sheet sheet,
Collection<SystemInfo> systems)
throws StepException
{
long startTime = System.currentTimeMillis();
logger.debug("{}{} starting", sheet.getLogPrefix(), step);
// Standard processing on an existing sheet
step.doStep(systems, sheet);
final long stopTime = System.currentTimeMillis();
final long duration = stopTime - startTime;
logger.debug("{}{} completed in {} ms",
sheet.getLogPrefix(), step, duration);
// Record this in sheet->score bench
sheet.getBench().recordStep(step, duration);
}
//------------------------//
// getLatestMandatoryStep //
//------------------------//
/**
* Report the latest mandatory step done so far with the related sheet.
*
* @return the latest mandatory step done, or null
*/
public static Step getLatestMandatoryStep (Sheet sheet)
{
Step latest = null;
for (Step step : Steps.values()) {
if (step.isMandatory() && step.isDone(sheet)) {
latest = step;
} else {
break;
}
}
return latest;
}
//---------------//
// getLatestStep //
//---------------//
/**
* Report the latest step done so far with the related sheet.
*
* @return the latest step done, or null
*/
public static Step getLatestStep (Sheet sheet)
{
for (ListIterator<Step> it = Steps.values().listIterator(Steps.values().
size());
it.hasPrevious();) {
Step step = it.previous();
if (step.isDone(sheet)) {
return step;
}
}
return null;
}
//------------//
// notifyStep //
//------------//
/**
* Notify the UI part that the provided step has started or stopped
* in the provided sheet.
*
* @param sheet the sheet concerned
* @param step the step notified
*/
static void notifyStep (final Sheet sheet,
final Step step)
{
if (monitor != null) {
final boolean finished = sheet.getCurrentStep() == null;
SwingUtilities.invokeLater(
new Runnable()
{
@Override
public void run ()
{
// Update sheet view for this step?
if (finished) {
step.displayUI(sheet);
sheet.getAssembly().selectViewTab(step);
}
// Call attention to this sheet (only if displayed),
// so that score-dependent actions can get enabled.
SheetsController ctrl = SheetsController.
getInstance();
Sheet currentSheet = ctrl.getSelectedSheet();
if (currentSheet == sheet) {
ctrl.callAboutSheet(sheet);
}
}
});
}
}
//----------------//
// notifyProgress //
//----------------//
/**
* When running interactively, move slightly the progress bar
* animation.
*/
public static void notifyProgress ()
{
if (monitor != null) {
monitor.animate();
}
}
//--------------//
// processScore //
//--------------//
/**
* At score level, perform the desired steps (as well as all needed
* intermediate steps).
* <p>This method is used from the CLI, from a script or from the Step menu
* (via the StepTask), and from the drag&drop handler.
*
* @param desiredSteps the desired steps
* @param pages specific set of pages, if any
* @param score the processed score (and its sheets)
*/
public static void processScore (Set<Step> desiredSteps,
SortedSet<Integer> pages,
Score score)
{
logger.debug("processScore {} on {}", desiredSteps, score);
// Sanity checks
if (score == null) {
throw new IllegalArgumentException("Score is null");
}
// Determine the precise ordered collection of steps to perform
SortedSet<Step> orderedSteps = new TreeSet<>(comparator);
orderedSteps.addAll(desiredSteps);
try {
// Determine starting step and stopping step
final Step loadStep = Steps.valueOf(Steps.LOAD);
final Step start;
final Step stop;
if (score.getPages().isEmpty()) {
// Create score pages if not yet done
score.createPages(pages);
start = first;
stop = orderedSteps.isEmpty() ? first : orderedSteps.last();
} else {
// Use a score sheet to retrieve the latest mandatory step
Sheet sheet = score.getFirstPage().getSheet();
Step latest = getLatestMandatoryStep(sheet);
Step firstDesired = orderedSteps.first();
start = (latest == null) ? first
: ((latest == firstDesired) ? firstDesired : next(
latest));
stop = (Steps.compare(latest, orderedSteps.last()) >= 0)
? latest : orderedSteps.last();
}
// Add all intermediate mandatory steps
for (Step step : range(start, stop)) {
if (step.isMandatory()) {
orderedSteps.add(step);
}
}
// Remove the LOAD step (unless it is explicitly desired)
// LOAD step may appear only in reprocessSheet()
if (!desiredSteps.contains(loadStep)) {
orderedSteps.remove(loadStep);
}
// Schedule the steps on each sheet
scheduleScoreStepSet(orderedSteps, score);
// Record the step tasks to script
for (Step step : desiredSteps) {
score.getScript().addTask(new StepTask(step));
}
} catch (ProcessingCancellationException pce) {
throw pce;
} catch (Exception ex) {
logger.warn("Error in performing " + orderedSteps, ex);
}
}
//-----------------//
// ensureScoreStep //
//-----------------//
/**
* Make sure the provided step has been reached on the score at hand
*
* @param step the step to check
* @param score the score to process, if so needed
*/
public static void ensureScoreStep (Step step,
Score score)
{
if (!score.getFirstPage().getSheet().isDone(step)) {
processScore(Collections.singleton(step), null, score);
}
}
//----------------//
// reprocessSheet //
//----------------//
/**
* For just a given sheet, update the steps already done, starting
* from the provided step.
* This method will try to minimize the systems to rebuild in each step, by
* processing only the provided "impacted" systems.
*
* @param step the step to restart from
* @param impactedSystems the ordered set of systems to rebuild, or null
* if all systems must be rebuilt
* @param imposed flag to indicate that update is imposed
*/
public static void reprocessSheet (Step step,
Sheet sheet,
Collection<SystemInfo> impactedSystems,
boolean imposed)
{
reprocessSheet(step, sheet, impactedSystems, imposed, true);
}
//----------------//
// reprocessSheet //
//----------------//
/**
* For just a given sheet, update the steps already done, starting
* from the provided step.
* This method will try to minimize the systems to rebuild in each step, by
* processing only the provided "impacted" systems.
*
* @param step the step to restart from
* @param impactedSystems the ordered set of systems to rebuild, or null
* if all systems must be rebuilt
* @param imposed flag to indicate that update is imposed
* @param merge true if step SCORE (merge of pages) is allowed
*/
public static void reprocessSheet (Step step,
Sheet sheet,
Collection<SystemInfo> impactedSystems,
boolean imposed,
boolean merge)
{
logger.debug("reprocessSheet {} on {}", step, sheet);
// Sanity checks
if (SwingUtilities.isEventDispatchThread()) {
logger.error("Method reprocessSheet should not run on EDT!");
}
if (step == null) {
return;
}
// Check whether the update must really be done
if (!imposed && !ScoreActions.getInstance().isRebuildAllowed()) {
return;
}
// A null set of systems means all of them
if (impactedSystems == null) {
impactedSystems = sheet.getSystems();
}
logger.debug("{}Rebuild launched from {} on {}",
sheet.getLogPrefix(),
step,
SystemInfo.toString(impactedSystems));
// Rebuild from specified step, if needed
Step latest = getLatestMandatoryStep(sheet);
// Avoid SCORE step?
Step scoreStep = Steps.valueOf(Steps.SCORE);
if (!merge && latest == scoreStep) {
latest = Steps.previous(latest);
}
if ((latest == null) || (compare(latest, step) >= 0)) {
// The range of steps to re-perform
SortedSet<Step> stepRange = range(step, latest);
notifyStart();
try {
doSheetStepSet(stepRange, sheet, impactedSystems);
} catch (ProcessingCancellationException pce) {
throw pce;
} catch (Exception ex) {
logger.warn("Error in re-processing from " + step, ex);
} finally {
notifyStop();
}
}
}
//----------------//
// doOneScoreStep //
//----------------//
/**
* At score level, do just one specified step, synchronously, with
* display of related UI and recording of the step into the script.
*
* @param step the step to perform
* @param score the score to be processed
* @throws StepException
*/
private static void doOneScoreStep (final Step step,
final Score score)
throws StepException
{
long startTime = System.currentTimeMillis();
logger.debug("{} Starting", step);
// Standard processing (using first sheet)
Sheet sheet = score.getFirstPage().getSheet();
step.doStep(null, sheet);
final long stopTime = System.currentTimeMillis();
final long duration = stopTime - startTime;
logger.debug("{} completed in {} ms", step, duration);
// Record this in score bench
score.getBench().recordStep(step, duration);
}
//----------------//
// doScoreStepSet //
//----------------//
/**
* At score level, perform a set of steps, with online display of a
* progress monitor.
*
* <p>We can perform all the pages in parallel or in sequence, depending on
* the value of constant 'pagesInParallel'.</p>
*
* @param stepSet the set of steps
* @param score the score to be processed
* @throws StepException
*/
private static void doScoreStepSet (final SortedSet<Step> stepSet,
final Score score)
{
if (score.isMultiPage()) {
if (OmrExecutors.defaultParallelism.getTarget() == true) {
// Process all sheets in parallel
List<Callable<Void>> tasks = new ArrayList<>();
for (TreeNode pn : new ArrayList<>(score.getPages())) {
final Page page = (Page) pn;
tasks.add(
new Callable<Void>()
{
@Override
public Void call ()
throws StepException
{
doSheetStepSet(
stepSet,
page.getSheet(),
null);
return null;
}
});
}
try {
List<Future<Void>> futures = OmrExecutors.
getCachedLowExecutor().invokeAll(tasks);
} catch (InterruptedException ex) {
logger.warn("Error in parallel doScoreStepSet", ex);
}
} else {
// Process one sheet after the other
for (TreeNode pn : new ArrayList<>(score.getPages())) {
Page page = (Page) pn;
doSheetStepSet(stepSet, page.getSheet(), null);
}
}
} else {
// Process the single sheet
doSheetStepSet(stepSet, score.getFirstPage().getSheet(), null);
}
}
//----------------//
// doSheetStepSet //
//----------------//
/**
* At sheet level, perform a set of steps, with online progress monitor.
* If any step in the step set throws {@link StepException} the processing
* is stopped.
*
* @param stepSet the set of steps
* @param sheet the sheet to be processed
* @params systems the impacted systems (null for all of them)
* @throws StepException if processing must stop
*/
private static void doSheetStepSet (SortedSet<Step> stepSet,
Sheet sheet,
Collection<SystemInfo> systems)
{
try {
for (Step step : stepSet) {
notifyMsg(sheet.getLogPrefix() + step);
doOneSheetStep(step, sheet, systems);
}
} catch (StepException se) {
logger.info("{}Processing stopped. {}",
sheet.getLogPrefix(), se.getMessage());
}
}
//-----------//
// notifyMsg //
//-----------//
/**
* Notify a simple message, which may be not related to any step.
*
* @param msg the message to display on the UI window, or to write in the
* log if there is no UI.
*/
private static void notifyMsg (String msg)
{
if (monitor != null) {
monitor.notifyMsg(msg);
} else {
logger.info(msg);
}
}
//-------------//
// notifyStart //
//-------------//
/**
* When running interactively, start the progress bar animation
*/
private static void notifyStart ()
{
// "Activate" the progress bar
if (monitor != null) {
monitor.displayAnimation(true);
}
}
//------------//
// notifyStop //
//------------//
/**
* When running interactively, stop the progress bar animation
*/
private static void notifyStop ()
{
// Reset the progress bar?
if (monitor != null) {
notifyMsg("");
monitor.displayAnimation(false);
}
}
//----------------------//
// scheduleScoreStepSet //
//----------------------//
/**
* Organize the scheduling of steps at score level among the sheets,
* since some steps have specific requirements
*
* @param orderedSet the sequence of steps
* @param score the score to process
*/
private static void scheduleScoreStepSet (SortedSet<Step> orderedSet,
Score score)
{
// Make a copy, so that we can modify the step set locally
SortedSet<Step> stepSet = new TreeSet<>(orderedSet);
if (stepSet.isEmpty()) {
return;
}
logger.info("{}scheduling {}", score.getLogPrefix(), stepSet);
long startTime = System.currentTimeMillis();
notifyStart();
try {
// SCALE step, if present, is always the first step
// We perform this step on all sheets, to allow early filtering
Step scaleStep = Steps.valueOf(Steps.SCALE);
if (stepSet.contains(scaleStep)) {
SortedSet<Step> single = new TreeSet<>(comparator);
single.add(scaleStep);
stepSet.remove(scaleStep);
doScoreStepSet(single, score);
if (!score.isMultiPage()
&& (score.getFirstPage().getSheet().getScale() == null)) {
throw new StepException("No scale available");
}
}
// Perform the remaining steps at sheet level, if any
SortedSet<Step> sheetSet = new TreeSet<>(comparator);
for (Step step : stepSet) {
if (!step.isScoreLevel()) {
sheetSet.add(step);
}
}
stepSet.removeAll(sheetSet);
doScoreStepSet(sheetSet, score);
// Finally, perform steps that must be done at score level
// SCORE step if present, must be done first, and in case of failure
// must prevent the following score-level steps to run.
Step scoreStep = Steps.valueOf(Steps.SCORE);
if (stepSet.contains(scoreStep)) {
stepSet.remove(scoreStep);
doOneScoreStep(scoreStep, score);
}
// Perform the other score-level steps, if any
for (Step step : stepSet) {
try {
doOneScoreStep(step, score);
} catch (StepException ignored) {
}
}
} catch (StepException se) {
logger.info("Processing stopped. {}", se.getMessage());
} finally {
notifyStop();
}
long stopTime = System.currentTimeMillis();
logger.debug("End of step set in {} ms.", (stopTime - startTime));
}
}