// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.debug.ui.liveedit;
import static org.chromium.debug.ui.DialogUtils.createErrorOptional;
import static org.chromium.debug.ui.DialogUtils.createOptional;
import org.chromium.debug.core.model.PushChangesPlan;
import org.chromium.debug.ui.DialogUtils.Message;
import org.chromium.debug.ui.DialogUtils.MessagePriority;
import org.chromium.debug.ui.DialogUtils.Optional;
import org.chromium.debug.ui.DialogUtils.Scope;
import org.chromium.debug.ui.DialogUtils.Updater;
import org.chromium.debug.ui.DialogUtils.ValueConsumer;
import org.chromium.debug.ui.DialogUtils.ValueSource;
import org.chromium.sdk.UpdatableScript;
import org.chromium.sdk.UpdatableScript.ChangeDescription;
import org.eclipse.osgi.util.NLS;
/**
* An asynchronous loader of LiveEdit update preview data. It deals with outer world in terms of
* {@link Updater} sources/consumers.
* It implements {@link ValueSource} interface that provides a loaded result, or null data
* if the result is not loaded yet. Updater gets notified whenever result is delivered.
* <p>The input parameter may be changed at any moment.
* <p>The loader may be in active or passive state. Since the preview data this class loads
* is optional and is not required for wizard's work, the loader should be kept passive until
* user actually needs its result (turns the page).
*/
class PreviewLoader implements ValueSource<Optional<PreviewLoader.Data>> {
private final Updater updater;
private final ValueSource<PushChangesPlan> inputParameterSource;
private boolean active = false;
private final Monitor dataMonitor = new Monitor();
PreviewLoader(Updater updater, ValueSource<PushChangesPlan> inputParameterSource) {
this.updater = updater;
this.inputParameterSource = inputParameterSource;
}
void registerSelf(Scope scope) {
updater.addSource(scope, this);
updater.addConsumer(scope, parametersConsumer);
updater.addDependency(parametersConsumer, inputParameterSource);
}
void setActive(boolean active) {
this.active = active;
if (active) {
requestPreview();
}
}
private void requestPreview() {
final PushChangesPlan plan = inputParameterSource.getValue();
boolean inputIsNew = dataMonitor.updateInputAndStarted(plan);
if (!inputIsNew) {
return;
}
// Report about our value becoming empty.
updater.reportChanged(this);
UpdatableScript.UpdateCallback callback = new UpdatableScript.UpdateCallback() {
public void failure(String message) {
Optional<Data> error = createErrorOptional(
new Message(NLS.bind(Messages.PreviewLoader_FAILED_TO_GET, message),
MessagePriority.WARNING));
done(error);
}
public void success(Object report,
final UpdatableScript.ChangeDescription changeDescription) {
Optional<Data> result;
if (changeDescription == null) {
result = EMPTY_DATA;
} else {
Data data = new Data() {
@Override public PushChangesPlan getChangesPlan() {
return plan;
}
@Override public ChangeDescription getChangeDescription() {
return changeDescription;
}
};
result = createOptional(data);
}
done(result);
}
private void done(Optional<Data> result) {
boolean resultTaken = dataMonitor.updateResult(result, plan);
if (resultTaken) {
updater.reportChanged(PreviewLoader.this);
updater.updateAsync();
}
}
};
plan.execute(true, callback, null);
}
public interface Data {
UpdatableScript.ChangeDescription getChangeDescription();
PushChangesPlan getChangesPlan();
}
public Optional<Data> getValue() {
return dataMonitor.getValue();
}
// A consumer that receive the actual input parameter (ScriptTargetMapping).
private final ValueConsumer parametersConsumer = new ValueConsumer() {
public void update(Updater updater) {
PushChangesPlan inputPlan = inputParameterSource.getValue();
boolean updated = dataMonitor.updateInput(inputPlan);
if (updated && active) {
requestPreview();
}
}
};
// Makes sure that some fields are only accessed in synchronized fashion.
private static class Monitor {
private PushChangesPlan input = null;
private boolean alreadyStarted = false;
private Optional<Data> result = NO_DATA;
synchronized boolean updateInputAndStarted(PushChangesPlan inputPlan) {
if (inputPlan == input) {
if (alreadyStarted) {
return false;
} else {
alreadyStarted = true;
return true;
}
} else {
input = inputPlan;
result = NO_DATA;
alreadyStarted = true;
return true;
}
}
synchronized boolean updateInput(PushChangesPlan inputPlan) {
if (inputPlan == input) {
return false;
} else {
input = inputPlan;
result = NO_DATA;
alreadyStarted = false;
return true;
}
}
synchronized Optional<Data> getValue() {
return result;
}
synchronized boolean updateResult(Optional<Data> result,
PushChangesPlan inputPlan) {
if (inputPlan != input) {
return false;
}
this.result = result;
return true;
}
}
private static final Optional<Data> NO_DATA = createErrorOptional(
new Message(Messages.PreviewLoader_WAITING_FOR_DIFF, MessagePriority.NONE));
private static final Optional<Data> EMPTY_DATA =
createErrorOptional(new Message(Messages.PreviewLoader_FAILED_TO_LOAD, MessagePriority.NONE));
}