/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed 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.jetbrains.kotlin.idea.util;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class LongRunningReadTask<RequestInfo, ResultData> {
enum State {
NOT_INITIALIZED,
INITIALIZED,
STARTED,
FINISHED,
FINISHED_WITH_ACTUAL_DATA
}
private ProgressIndicator progressIndicator = null;
private RequestInfo requestInfo = null;
private State currentState = State.NOT_INITIALIZED;
protected LongRunningReadTask() {}
/** Should be executed in GUI thread */
public boolean shouldStart(@Nullable LongRunningReadTask<RequestInfo, ResultData> previousTask) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (currentState != State.INITIALIZED) {
throw new IllegalStateException("Task should be initialized state. Call init() method.");
}
// Cancel previous task if necessary
if (previousTask != null && previousTask.currentState == State.STARTED) {
if (requestInfo == null || !requestInfo.equals(previousTask.requestInfo)) {
previousTask.progressIndicator.cancel();
}
}
if (requestInfo == null) {
if (previousTask != null && (previousTask.currentState == State.FINISHED_WITH_ACTUAL_DATA || previousTask.currentState == State.FINISHED)) {
previousTask.hideResultOnInvalidLocation();
}
return false;
}
if (previousTask != null) {
if (previousTask.currentState == State.STARTED) {
// Start new task only if previous isn't working on similar request
return !requestInfo.equals(previousTask.requestInfo);
}
else if (previousTask.currentState == State.FINISHED_WITH_ACTUAL_DATA) {
if (requestInfo.equals(previousTask.requestInfo)) {
return false;
}
}
}
return true;
}
/** Should be executed in GUI thread */
public final void run() {
ApplicationManager.getApplication().assertIsDispatchThread();
if (currentState != State.INITIALIZED) {
throw new IllegalStateException("Task should be initialized with init() method");
}
if (requestInfo == null) {
throw new IllegalStateException("Invalid request for task beginning");
}
currentState = State.STARTED;
beforeRun();
progressIndicator = new ProgressIndicatorBase();
final RequestInfo requestInfoCopy = cloneRequestInfo(requestInfo);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
runWithWriteActionPriority(progressIndicator, new Runnable() {
@Override
public void run() {
ResultData resultData = null;
try {
resultData = processRequest(requestInfoCopy);
}
finally {
// Back to GUI thread for submitting result
final ResultData finalResult = resultData;
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
resultReady(finalResult);
}
});
}
}
});
}
});
}
/**
* Executed in GUI Thread.
*
* @return true if new request was successfully created, false if request is invalid and shouldn't be started
*/
public final boolean init() {
ApplicationManager.getApplication().assertIsDispatchThread();
requestInfo = prepareRequestInfo();
currentState = State.INITIALIZED;
return requestInfo != null;
}
private void resultReady(ResultData resultData) {
ApplicationManager.getApplication().assertIsDispatchThread();
currentState = State.FINISHED;
if (resultData != null) {
RequestInfo actualInfo = prepareRequestInfo();
if (requestInfo.equals(actualInfo)) {
currentState = State.FINISHED_WITH_ACTUAL_DATA;
onResultReady(actualInfo, resultData);
}
}
}
/**
* This method should prepare a copy of request object that will be used during the processing of the
* request in thread pool. If RequestInfo class is thread safe this method can return
* a reference to already constructed object.
*
* By default this method will reconstruct request object with prepareRequestInfo method.
*
* Executed in GUI Thread.
*/
@SuppressWarnings("UnusedParameters")
@NotNull
protected RequestInfo cloneRequestInfo(@NotNull RequestInfo requestInfo) {
RequestInfo cloneRequestInfo = prepareRequestInfo();
if (cloneRequestInfo == null) {
throw new IllegalStateException("Cloned request object can't be null");
}
return cloneRequestInfo;
}
/**
* Executed in GUI Thread.
*
* @return null if current request is invalid and task shouldn't be executed.
*/
@Nullable
protected abstract RequestInfo prepareRequestInfo();
/**
* Executed in GUI Thread.
*/
protected void hideResultOnInvalidLocation() {}
/**
* Executed in GUI Thread right before task run. Do nothing by default.
*/
protected void beforeRun() {}
/**
* Executed in thread pool under read lock with write priority.
*/
@Nullable
protected abstract ResultData processRequest(@NotNull RequestInfo requestInfo);
/**
* Executed in GUI Thread. Do nothing by default.
*/
protected void onResultReady(@NotNull RequestInfo requestInfo, @Nullable ResultData resultData) {}
/**
* Execute action with immediate stop when write lock is required.
*
* {@link ProgressIndicatorUtils#runWithWriteActionPriority(Runnable)}
*
* @param indicator
* @param action
*/
public static void runWithWriteActionPriority(@NotNull final ProgressIndicator indicator, @NotNull final Runnable action) {
ApplicationAdapter listener = new ApplicationAdapter() {
@Override
public void beforeWriteActionStart(Object action) {
indicator.cancel();
}
};
final Application application = ApplicationManager.getApplication();
try {
application.addApplicationListener(listener);
ProgressManager.getInstance().runProcess(new Runnable() {
@Override
public void run() {
application.runReadAction(action);
}
}, indicator);
}
finally {
application.removeApplicationListener(listener);
}
}
}