blob: ce04bf616391bc5b3fe38be729310a74dd8ba0ce [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2018, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.jcommons.status;
import java.util.Objects;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
@NonNullByDefault
public class BasicProgressMonitor implements ProgressMonitor {
private static final int ROOT_FLAG= 1 << 31;
private static final int INHERITED_FLAGS= (SUPPRESS_BEGINTASK_NAME | SUPPRESS_ISCANCELED);
public static class ProgressData {
protected static final byte MAIN_TASK_NAME= 1 << 1;
protected static final byte SUB_TASK_NAME= 1 << 2;
protected static final byte PROGRESS_UNKNOWN= 1 << 3;
protected static final byte PROGRESS_RATE= 1 << 4;
protected static final byte CANCELED= 1 << 5;
protected static final byte BLOCKED= 1 << 6;
private String mainTaskName= ""; //$NON-NLS-1$
private @Nullable String subTaskName;
private boolean progressUnknown;
/** Progress: from 1 to 0 */
private double progress= 1;
private volatile boolean isCanceled;
private @Nullable Status blocked;
public ProgressData() {
}
public String getMainTaskName() {
return this.mainTaskName;
}
protected final void setMainTaskName(final String name) {
if (!Objects.equals(this.mainTaskName, name)) {
this.mainTaskName= name;
this.subTaskName= null;
onDataChanged(MAIN_TASK_NAME);
}
else if (this.subTaskName != null) {
this.subTaskName= null;
onDataChanged(SUB_TASK_NAME);
}
}
public @Nullable String getSubTaskName() {
return this.subTaskName;
}
private final void setSubTaskName(@Nullable String name) {
if (name != null && name.isEmpty()) {
name= null;
}
if (!Objects.equals(this.subTaskName, name)) {
this.subTaskName= name;
onDataChanged(SUB_TASK_NAME);
}
}
public boolean isProgressUnknown() {
return this.progressUnknown;
}
private void setProgressUnknown(final boolean enabled) {
if (this.progressUnknown != enabled) {
this.progressUnknown= enabled;
onDataChanged(PROGRESS_UNKNOWN);
}
}
public double getProgress() {
return this.progress;
}
private void setProgress(final double progress) {
if (progress < this.progress) {
this.progress= progress;
onDataChanged(PROGRESS_RATE);
}
}
public boolean isCanceled() {
return this.isCanceled;
}
protected void setCanceled(final boolean state) {
if (this.isCanceled != state) {
this.isCanceled= state;
onDataChanged(CANCELED);
}
}
public @Nullable Status getBlocked() {
return this.blocked;
}
protected void setBlocked(final Status reason) {
if (!Objects.equals(this.blocked, reason)) {
this.blocked= reason;
onDataChanged(BLOCKED);
}
}
protected void clearBlocked() {
if (this.blocked != null) {
this.blocked= null;
onDataChanged(BLOCKED);
}
}
protected void onDataChanged(final byte data) {
}
}
protected final ProgressData data;
private final int flags;
private String taskName;
/** Bound (this.workRemaining == 0) for data.progress */
private final double progressDataDone;
/** Current work remaining: from x to 0 */
private int workRemaining;
/** Current factor: work -> progress */
private double workProgressFactor;
private @Nullable BasicProgressMonitor currentSub;
private BasicProgressMonitor(final ProgressData data, final double progressDone,
final int flags) {
this.data= data;
this.flags= flags;
this.taskName= data.mainTaskName;
this.progressDataDone= progressDone;
}
public BasicProgressMonitor(final ProgressData data, final int flags) {
this(data, 0, ROOT_FLAG | flags);
}
public BasicProgressMonitor(final ProgressData data) {
this(data, 0, ROOT_FLAG);
}
@Override
public void beginTask(final String name, final int totalWork) {
if ((this.flags & SUPPRESS_BEGINTASK_NAME) == 0 && name != null) {
this.taskName= name;
this.data.setMainTaskName(name);
}
setWorkRemaining(totalWork);
}
@Override
public void beginSubTask(final @Nullable String name) {
checkProgress();
this.data.setSubTaskName(name);
}
@Override
public ProgressMonitor setWorkRemaining(final int work) {
checkProgress();
if (work >= 0) {
if ((this.flags & ROOT_FLAG) != 0) {
this.data.setProgressUnknown(false);
}
final double progressRemaining= (this.data.progress - this.progressDataDone);
if (work == 0 || progressRemaining <= 0) {
this.workProgressFactor= 0;
this.workRemaining= 0;
this.data.setProgress(this.progressDataDone);
}
else {
this.workRemaining= work;
this.workProgressFactor= progressRemaining / work;
}
}
else {
if (work == UNKNOWN) {
if ((this.flags & ROOT_FLAG) != 0) {
this.data.setProgressUnknown(true);
}
}
this.workProgressFactor= 0;
}
return this;
}
@Override
public void addWorked(final int work) {
checkProgress();
this.data.setProgress(consumeWork(work));
}
@Override
public @NonNull ProgressMonitor newSubMonitor(final int work, final int flags) {
checkProgress();
return this.currentSub= new BasicProgressMonitor(this.data, consumeWork(work),
(this.flags & INHERITED_FLAGS) | flags );
}
private double consumeWork(final int work) {
if (work > 0 && this.workProgressFactor != 0) {
this.workRemaining= Math.max(this.workRemaining - work, 0);
}
return this.progressDataDone + this.workRemaining * this.workProgressFactor;
}
private void checkProgress() {
final BasicProgressMonitor currentSub= this.currentSub;
if (currentSub != null) {
this.currentSub= null;
this.data.setMainTaskName(this.taskName);
this.data.setProgress(currentSub.progressDataDone);
}
}
@Override
public boolean isCanceled() {
if ((this.flags & SUPPRESS_ISCANCELED) == 0) {
return this.data.isCanceled();
}
return false;
}
@Override
public void setCanceled(final boolean state) {
this.data.setCanceled(state);
}
@Override
public void setBlocked(final Status reason) {
this.data.setBlocked(reason);
}
@Override
public void clearBlocked() {
this.data.clearBlocked();
}
}