/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.ui.internal.refactoring;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.dart.server.GetRefactoringConsumer;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.internal.corext.refactoring.base.StringStatusContext;
import static com.google.dart.tools.ui.internal.refactoring.ServiceUtils_NEW.toLTK;
import static com.google.dart.tools.ui.internal.refactoring.ServiceUtils_NEW.toRefactoringStatus;
import org.apache.commons.lang3.StringUtils;
import org.dartlang.analysis.server.protocol.RefactoringFeedback;
import org.dartlang.analysis.server.protocol.RefactoringOptions;
import org.dartlang.analysis.server.protocol.RefactoringProblem;
import org.dartlang.analysis.server.protocol.RequestError;
import org.dartlang.analysis.server.protocol.SourceChange;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* LTK wrapper around Analysis Server refactoring.
*
* @coverage dart.editor.ui.refactoring.ui
*/
public abstract class ServerRefactoring extends Refactoring {
public interface ServerRefactoringListener {
void requestStateChanged(boolean hasPendingRequests, RefactoringStatus optionsStatus);
}
protected static final RefactoringStatus TIMEOUT_STATUS = RefactoringStatus.createFatalErrorStatus("Timeout");
public static String[] toStringArray(List<String> list) {
return list.toArray(new String[list.size()]);
}
protected final String kind;
private final String name;
private final String file;
private final int offset;
private final int length;
protected RefactoringStatus serverErrorStatus;
protected RefactoringStatus initialStatus;
protected RefactoringStatus optionsStatus;
protected RefactoringStatus finalStatus;
private Change change;
private final List<String> externalFiles = Lists.newArrayList();
private int lastId = 0;
private final Set<Integer> pendingRequestIds = Sets.newHashSet();
private ServerRefactoringListener listener;
public ServerRefactoring(String kind, String name, String file, int offset, int length) {
this.kind = kind;
this.name = name;
this.file = file;
this.offset = offset;
this.length = length;
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) {
setOptions(false, pm);
if (serverErrorStatus != null) {
return serverErrorStatus;
}
// done if already fatal
if (finalStatus.hasFatalError()) {
return finalStatus;
}
// check for external files
if (!externalFiles.isEmpty()) {
finalStatus.addError(
"The following files are external and cannot be updated",
new StringStatusContext(null, StringUtils.join(externalFiles, "\n")));
}
// done
return finalStatus;
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) {
setOptions(true, pm);
if (serverErrorStatus != null) {
return serverErrorStatus;
}
return initialStatus;
}
@Override
public Change createChange(IProgressMonitor pm) {
setOptions(false, pm);
return change;
}
@Override
public String getName() {
return name;
}
/**
* @return {@code true} if the {@link Change} created by refactoring may be unsafe, so we want
* user to review the change to ensure that he understand it.
*/
public boolean requiresPreview() {
return false;
}
public void setListener(ServerRefactoringListener listener) {
this.listener = listener;
}
/**
* Returns this {@link RefactoringOptions} subclass instance.
*/
protected abstract RefactoringOptions getOptions();
/**
* Sets the received {@link RefactoringFeedback}.
*/
protected abstract void setFeedback(RefactoringFeedback feedback);
protected void setOptions(boolean validateOnly) {
setOptions(validateOnly, null);
}
protected void setOptions(boolean validateOnly, IProgressMonitor pm) {
// 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();
DartCore.getAnalysisServer().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);
}
externalFiles.clear();
initialStatus = toRefactoringStatus(initialProblems);
optionsStatus = toRefactoringStatus(optionsProblems);
finalStatus = toRefactoringStatus(finalProblems);
change = toLTK(_change, externalFiles);
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();
}
}
});
// wait for completion
if (pm != null) {
while (true) {
if (pm.isCanceled()) {
throw new OperationCanceledException();
}
boolean done = Uninterruptibles.awaitUninterruptibly(latch, 10, TimeUnit.MILLISECONDS);
if (done) {
return;
}
}
} else {
// Wait a very short time, just in case it 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();
listener.requestStateChanged(hasPendingRequests, optionsStatus);
}
}
}