org.netbeans.modules.progress.spi.InternalHandle Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.progress.spi;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.progress.module.*;
import org.openide.modules.PatchedPublic;
import org.openide.util.Cancellable;
import org.openide.util.Exceptions;
/**
* Instances provided by the ProgressHandleFactory allow the users of the API to
* notify the progress bar UI about changes in the state of the running task.
* @author Milos Kleint ([email protected])
* @since org.netbeans.api.progress/1 1.18
*/
public class InternalHandle {
private static final Logger LOG = Logger.getLogger(InternalHandle.class.getName());
private String displayName;
private int state;
private int totalUnits;
private int currentUnit;
private long initialEstimate;
private long timeStarted;
private long timeLastProgress;
private long timeSleepy = 0;
private String lastMessage;
private final Cancellable cancelable;
private final boolean userInitiated;
private int initialDelay = Controller.INITIAL_DELAY;
private Controller controller;
private ProgressHandle handle;
private boolean customPlaced;
public static final int STATE_INITIALIZED = 0;
public static final int STATE_RUNNING = 1;
public static final int STATE_FINISHED = 2;
public static final int STATE_REQUEST_STOP = 3;
public static final int NO_INCREASE = -2;
/** For compatibility only, not used for new clients, package access */
InternalHandle del;
/** Creates a new instance of ProgressHandle */
@PatchedPublic
protected InternalHandle(String displayName,
Cancellable cancel,
boolean userInitiated) {
this.displayName = displayName;
this.userInitiated = userInitiated;
state = STATE_INITIALIZED;
totalUnits = 0;
lastMessage = null;
cancelable = cancel;
compatInit();
}
/**
* Creates a {@link ProgressHandle} instance, which works with this SPI.
* @return a fresh instance of ProgressHandle
* @since 1.40
*/
public final ProgressHandle createProgressHandle() {
synchronized (this) {
if (handle != null) {
return handle;
}
}
ProgressHandle h;
if (del != null) {
h = ProgressApiAccessor.getInstance().create(del);
} else {
h = ProgressApiAccessor.getInstance().create(this);
}
synchronized (this) {
if (handle == null) {
handle = h;
}
return handle;
}
}
public String getDisplayName() {
if (del != null) {
return del.getDisplayName();
}
return displayName;
}
/**
* XXX - called from UI, threading
*/
public synchronized int getState() {
if (del != null) {
return del.getState();
}
return state;
}
public boolean isAllowCancel() {
if (del != null) {
return del.isAllowCancel();
}
return cancelable != null;
}
public boolean isAllowView() {
if (del != null) {
return del.isAllowView();
}
return false;
}
public boolean isCustomPlaced() {
if (del != null) {
return del.isCustomPlaced();
}
return customPlaced;
}
public final boolean isUserInitialized() {
return userInitiated;
}
private int getCurrentUnit() {
return currentUnit;
}
public int getTotalUnits() {
if (del != null) {
return del.getTotalUnits();
}
return totalUnits;
}
public void setInitialDelay(int millis) {
if (del != null) {
del.setInitialDelay(millis);
return;
}
if (state != STATE_INITIALIZED) {
LOG.log(Level.WARNING, "Setting ProgressHandle.setInitialDelay() after the task is started has no effect at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
initialDelay = millis;
}
public int getInitialDelay() {
if (del != null) {
return del.getInitialDelay();
}
return initialDelay;
}
public synchronized void toSilent(String message) {
if (del != null) {
del.toSilent(message);
return;
}
if (state != STATE_RUNNING && state != STATE_REQUEST_STOP) {
LOG.log(Level.WARNING, "Cannot switch to silent mode when not running at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
timeLastProgress = System.currentTimeMillis();
timeSleepy = timeLastProgress;
if (message != null) {
lastMessage = message;
}
controller.toSilent(this, message);
}
public boolean isInSleepMode() {
if (del != null) {
return del.isInSleepMode();
}
return timeSleepy == timeLastProgress;
}
public synchronized void toIndeterminate() {
if (del != null) {
del.toIndeterminate();
return;
}
if (state != STATE_RUNNING && state != STATE_REQUEST_STOP) {
LOG.log(Level.WARNING, "Cannot switch to indeterminate mode when not running at {0}", LoggingUtils.findCaller());
return;
}
totalUnits = 0;
currentUnit = 0;
initialEstimate = -1;
timeLastProgress = System.currentTimeMillis();
controller.toIndeterminate(this);
}
public synchronized void toDeterminate(int workunits, long estimate) {
if (del != null) {
del.toDeterminate(workunits, estimate);
}
if (state != STATE_RUNNING && state != STATE_REQUEST_STOP) {
LOG.log(Level.WARNING, "Cannot switch to determinate mode when not running at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
if (workunits < 0) {
throw new IllegalArgumentException("number of workunits cannot be negative");
}
totalUnits = workunits;
currentUnit = 0;
initialEstimate = estimate;
timeLastProgress = System.currentTimeMillis();
controller.toDeterminate(this);
}
protected final void setController(Controller ctrl) {
assert this.controller == null : "Controller can be set just once"; // NOI18N
this.controller = ctrl;
}
/**
* start the progress indication for a task with known number of steps and known
* time estimate for completing the task.
*
* @param message
* @param workunits
* @param estimate estimated time to process the task in seconds
*/
public synchronized void start(String message, int workunits, long estimate) {
if (del != null) {
del.start(message, workunits, estimate);
return;
}
if (state != STATE_INITIALIZED) {
LOG.log(Level.WARNING, "Cannot call start twice on a handle at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
if (workunits < 0) {
throw new IllegalArgumentException("number of workunits cannot be negative");
}
totalUnits = workunits;
currentUnit = 0;
if (message != null) {
lastMessage = message;
}
if (controller == null) {
controller = Controller.getDefault();
}
state = STATE_RUNNING;
initialEstimate = estimate;
timeStarted = System.currentTimeMillis();
timeLastProgress = timeStarted;
controller.start(this);
}
/**
* finish the task, remove the task's component from the progress bar UI.
*/
public synchronized void finish() {
if (del != null) {
del.finish();
return;
}
if (state == STATE_INITIALIZED) {
LOG.log(Level.WARNING, "Cannot finish a task that was never started at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
// handle is already finished, just return
if (state == STATE_FINISHED) {
return;
}
state = STATE_FINISHED;
currentUnit = totalUnits;
controller.finish(this);
}
/**
*
* @param message
* @param workunit
*/
public synchronized void progress(String message, int workunit) {
if (del != null) {
del.progress(message, workunit);
return;
}
if (state != STATE_RUNNING && state != STATE_REQUEST_STOP) {
LOG.log(Level.WARNING, "Cannot call progress on a task that was never started at {0}", LoggingUtils.findCaller()); //NOI18N
return;
}
if (workunit != NO_INCREASE) {
if (workunit < currentUnit) {
throw new IllegalArgumentException("Cannot decrease processed workunit count (" + workunit + ") to lower value than before (" + currentUnit + ")");
}
if (workunit > totalUnits) {
// seems to be the by far most frequently abused contract. Record it to log file and safely handle the case
//#96921 - WARNING -> INFO to prevent users reporting the problem automatically.
LOG.log(Level.INFO,
"Cannot process more work than scheduled. " +
"Progress handle with name \"" + getDisplayName() + "\" has requested progress to workunit no." + workunit +
" but the total number of workunits is " + totalUnits + ". That means the progress bar UI will not display real progress and will stay at 100%.",
new IllegalArgumentException()
);
workunit = totalUnits;
}
currentUnit = workunit;
}
if (message != null) {
lastMessage = message;
}
timeLastProgress = System.currentTimeMillis();
controller.progress(this, message, currentUnit,
totalUnits > 0 ? getPercentageDone() : -1,
(initialEstimate == -1 ? -1 : calculateFinishEstimate()));
}
// XXX - called from UI, threading
public void requestCancel() {
if (del != null) {
del.requestCancel();
return;
}
if (!isAllowCancel()) {
return;
}
synchronized (this) {
state = STATE_REQUEST_STOP;
}
// do not call in synchronized block because it can take a long time to process,
/// and it could slow down UI.
//TODO - call in some other thread, not AWT? what is the cancel() contract?
cancelable.cancel();
synchronized (this) {
requestStateSnapshot();
}
}
///XXX - called from UI, threading
public void requestView() {
if (del != null) {
del.requestView();
}
}
// XXX - called from UI, threading
public synchronized void requestExplicitSelection() {
if (del != null) {
del.requestExplicitSelection();
return;
}
if (!isInSleepMode()) {
timeLastProgress = System.currentTimeMillis();
}
controller.explicitSelection(this);
}
/**
* Request a interaction callback to be attached to the Handle. The
* implementation decides if the callback is permitted and desirable. One command,
* {@link ProgressHandle#ACTION_VIEW} is defined as a default command (action) for
* the progress handle presentation. Implementations are free to ignore request
* for adding actions.
* @param actionCommand command to bind the action for.
* @param action action instance
* @return true, if the handle agrees to support the action.
* @since 1.59
*/
public boolean requestAction(String actionCommand, Action action) {
return false;
}
public synchronized void requestDisplayNameChange(String newDisplayName) {
if (del != null) {
del.requestDisplayNameChange(newDisplayName);
return;
}
displayName = newDisplayName;
if (state == STATE_INITIALIZED) {
return;
}
timeLastProgress = System.currentTimeMillis();
controller.displayNameChange(this, currentUnit,
totalUnits > 0 ? getPercentageDone() : -1,
(initialEstimate == -1 ? -1 : calculateFinishEstimate()), newDisplayName);
}
// XXX - called from UI, threading
public synchronized ProgressEvent requestStateSnapshot() {
if (del != null) {
// TODO - event.getSource() exposes the delegate InternalHandle
return del.requestStateSnapshot();
}
if (!isInSleepMode()) {
timeLastProgress = System.currentTimeMillis();
}
return controller.snapshot(this, lastMessage, currentUnit,
totalUnits > 0 ? getPercentageDone() : -1,
(initialEstimate == -1 ? -1 : calculateFinishEstimate()));
}
long calculateFinishEstimate() {
// we are interested in seconds only
double durationSoFar = ((double)(System.currentTimeMillis() - timeStarted)) / 1000;
if (initialEstimate == -1) {
// we don't have an initial estimate, calculate by real-life data only
return (long)(durationSoFar * (totalUnits - currentUnit) / totalUnits);
} else {
// in the begining give the initial estimate more weight than in the end.
// should give us more smooth estimates
long remainingUnits = (totalUnits - currentUnit);
double remainingPortion = (double)remainingUnits / (double)totalUnits;
double currentEstimate = durationSoFar / (double)currentUnit * totalUnits;
long retValue = (long)(((initialEstimate * remainingUnits * remainingPortion)
+ (currentEstimate * remainingUnits * (1 - remainingPortion)))
/ totalUnits);
return retValue;
}
}
/**
*public because of tests.
*/
public double getPercentageDone() {
if (del != null) {
return del.getPercentageDone();
}
return ((double)currentUnit * 100 / (double)totalUnits);
}
/**
* Returns the last time the progress was updated. he timestamp
* is updated on start, stop, every progress report and determinate / indeterminate
* switch. Generally every time the ProgressHandle client publishes some information.
*
* @return timestamp of last update, in milliseconds.
* @since 1.45
*/
public long getLastPingTime() {
if (del != null) {
return del.getLastPingTime();
}
return timeLastProgress;
}
public long getTimeStampStarted() {
if (del != null) {
return del.getTimeStampStarted();
}
return timeStarted;
}
static final Method compatInit;
private void compatInit() {
if (compatInit == null) {
return;
}
try {
compatInit.invoke(this, displayName, cancelable, userInitiated);
} catch (IllegalAccessException ex) {
Exceptions.printStackTrace(ex);
} catch (IllegalArgumentException ex) {
Exceptions.printStackTrace(ex);
} catch (InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
}
static {
Method m = null;
try {
m = InternalHandle.class.getSuperclass().getDeclaredMethod("compatInit",
String.class, Cancellable.class, Boolean.TYPE); // NOI18N
} catch (NoSuchMethodException ex) {
// OK
} catch (SecurityException ex) {
Exceptions.printStackTrace(ex);
}
compatInit = m;
}
/**
* Marks this handle as custom-placed. Handle should be marked as custom-placed
* if some controller overtakes (part of) handle's presentation.
* @since 1.59
*/
protected final void markCustomPlaced() {
if (getState() != STATE_INITIALIZED) {
throw new IllegalStateException();
}
customPlaced = true;
}
@Override
public String toString() {
return "H@" + Integer.toHexString(System.identityHashCode(this)) +
"[\"" + getDisplayName() + "\", state: " + state + "]";
}
}