/*
* Copyright 2000-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 com.jetbrains.lang.dart.ide.refactoring;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.dart.server.GetRefactoringConsumer;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import com.jetbrains.lang.dart.ide.refactoring.status.RefactoringStatus;
import com.jetbrains.lang.dart.ide.refactoring.status.RefactoringStatusEntry;
import com.jetbrains.lang.dart.ide.refactoring.status.RefactoringStatusSeverity;
import org.dartlang.analysis.server.protocol.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* The LTK wrapper around an Analysis Server refactoring.
*/
public abstract class ServerRefactoring {
@NotNull private final Project myProject;
@NotNull private final String refactoringName;
@NotNull private final String kind;
@NotNull private final VirtualFile file;
private final int offset;
private final int length;
private final Set<Integer> pendingRequestIds = Sets.newHashSet();
@Nullable private RefactoringStatus serverErrorStatus;
@Nullable private RefactoringStatus initialStatus;
@Nullable private RefactoringStatus optionsStatus;
@Nullable private RefactoringStatus finalStatus;
@Nullable private SourceChange change;
@NotNull private final Set<String> potentialEdits = Sets.newHashSet();
private int lastId = 0;
@Nullable private ServerRefactoringListener listener;
public ServerRefactoring(@NotNull final Project project,
@NotNull final String refactoringName,
@NotNull final String kind,
@NotNull final VirtualFile file,
final int offset,
final int length) {
myProject = project;
this.refactoringName = refactoringName;
this.kind = kind;
this.file = file;
this.offset = offset;
this.length = length;
}
@NotNull
protected Project getProject() {
return myProject;
}
@NotNull
protected VirtualFile getFile() {
return file;
}
@Nullable
public RefactoringStatus checkFinalConditions() {
ProgressManager.getInstance().run(new Task.Modal(null, refactoringName, true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Validating the specified parameters.");
indicator.setIndeterminate(true);
setOptions(false, indicator);
}
});
if (serverErrorStatus != null) {
return serverErrorStatus;
}
if (finalStatus == null) {
return null;
}
RefactoringStatus result = new RefactoringStatus();
result.merge(optionsStatus);
result.merge(finalStatus);
return result;
}
@Nullable
public RefactoringStatus checkInitialConditions() {
ProgressManager.getInstance().run(new Task.Modal(null, refactoringName, true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText("Checking availability at the selection.");
indicator.setIndeterminate(true);
setOptions(true, indicator);
}
});
if (serverErrorStatus != null) {
return serverErrorStatus;
}
return initialStatus;
}
@Nullable
public SourceChange getChange() {
return change;
}
/**
* Returns this {@link RefactoringOptions} subclass instance.
*/
@Nullable
protected abstract RefactoringOptions getOptions();
@NotNull
public Set<String> getPotentialEdits() {
return potentialEdits;
}
/**
* Sets the received {@link RefactoringFeedback}.
*/
protected abstract void setFeedback(@NotNull RefactoringFeedback feedback);
public void setListener(@Nullable ServerRefactoringListener listener) {
this.listener = listener;
}
protected void setOptions(boolean validateOnly, @Nullable ProgressIndicator indicator) {
// add a new pending request ID
final int id;
synchronized (pendingRequestIds) {
id = ++lastId;
pendingRequestIds.add(id);
}
// do request
serverErrorStatus = null;
final CountDownLatch latch = new CountDownLatch(1);
RefactoringOptions options = getOptions();
DartAnalysisServerService.getInstance(myProject).updateFilesContent();
final boolean success = DartAnalysisServerService.getInstance(myProject)
.edit_getRefactoring(kind, file, offset, length, validateOnly, options, new GetRefactoringConsumer() {
@Override
public void computedRefactorings(List<RefactoringProblem> initialProblems,
List<RefactoringProblem> optionsProblems,
List<RefactoringProblem> finalProblems,
RefactoringFeedback feedback,
SourceChange _change,
List<String> _potentialEdits) {
if (feedback != null) {
setFeedback(feedback);
}
initialStatus = toRefactoringStatus(initialProblems);
optionsStatus = toRefactoringStatus(optionsProblems);
finalStatus = toRefactoringStatus(finalProblems);
change = _change;
potentialEdits.clear();
if (_potentialEdits != null) {
potentialEdits.addAll(_potentialEdits);
}
latch.countDown();
requestDone(id);
}
@Override
public void onError(RequestError requestError) {
String message = "Server error: " + requestError.getMessage();
serverErrorStatus = RefactoringStatus.createFatalErrorStatus(message);
latch.countDown();
requestDone(id);
}
private void requestDone(final int id) {
synchronized (pendingRequestIds) {
pendingRequestIds.remove(id);
notifyListener();
}
}
});
if (!success) return;
// wait for completion
if (indicator != null) {
while (true) {
if (indicator.isCanceled()) {
throw new ProcessCanceledException();
}
boolean done = Uninterruptibles.awaitUninterruptibly(latch, 10, TimeUnit.MILLISECONDS);
if (done) {
return;
}
}
}
else {
// Wait a very short time, just in case if it can be done fast,
// so that we don't have to disable UI and re-enable it 2 milliseconds later.
Uninterruptibles.awaitUninterruptibly(latch, 10, TimeUnit.MILLISECONDS);
notifyListener();
}
}
private void notifyListener() {
if (listener != null) {
boolean hasPendingRequests = !pendingRequestIds.isEmpty();
RefactoringStatus status = optionsStatus != null ? optionsStatus : new RefactoringStatus();
listener.requestStateChanged(hasPendingRequests, status);
}
}
private static RefactoringStatusSeverity toProblemSeverity(@NotNull String severity) {
if (RefactoringProblemSeverity.FATAL.equals(severity)) {
return RefactoringStatusSeverity.FATAL;
}
if (RefactoringProblemSeverity.ERROR.equals(severity)) {
return RefactoringStatusSeverity.ERROR;
}
if (RefactoringProblemSeverity.WARNING.equals(severity)) {
return RefactoringStatusSeverity.WARNING;
}
return RefactoringStatusSeverity.OK;
}
private static RefactoringStatus toRefactoringStatus(@NotNull List<RefactoringProblem> problems) {
RefactoringStatus status = new RefactoringStatus();
for (RefactoringProblem problem : problems) {
final String serverSeverity = problem.getSeverity();
final RefactoringStatusSeverity problemSeverity = toProblemSeverity(serverSeverity);
final String message = problem.getMessage();
status.addEntry(new RefactoringStatusEntry(problemSeverity, message));
}
return status;
}
public interface ServerRefactoringListener {
void requestStateChanged(boolean hasPendingRequests, @NotNull RefactoringStatus optionsStatus);
}
}