/*
* 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.text.dart;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.source.Source;
import com.google.dart.tools.core.analysis.model.ContextManager;
import com.google.dart.tools.core.internal.builder.AnalysisManager;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Helper for updating in-memory content of {@link Source}s in an {@link AnalysisContext}.
*/
public class DartUpdateSourceHelper {
private class DelayThread extends Thread {
public DelayThread() {
setName("DartUpdateSourceHelper-DelayThread");
setDaemon(true);
}
@Override
public void run() {
while (true) {
// prepare worker
DelayWorker worker;
try {
worker = delayDeque.take();
// if this worker is not ready yet, return it into the queue
if (!worker.isReady()) {
delayDeque.putFirst(worker);
Uninterruptibles.sleepUninterruptibly(worker.getDelay(), TimeUnit.MILLISECONDS);
continue;
}
// continue execution
} catch (InterruptedException e) {
continue;
}
// execute worker
worker.perform();
delayDequeEmpty = delayDeque.isEmpty();
}
}
}
private class DelayWorker {
private final ContextManager manager;
private final AnalysisContext context;
private final Source source;
private String content;
private long contentTime;
public DelayWorker(ContextManager manager, AnalysisContext context, Source source,
String content) {
this.manager = manager;
this.context = context;
this.source = source;
setContent(content);
}
public void setContent(String content) {
this.content = content;
this.contentTime = System.currentTimeMillis();
}
/**
* Return how many more milliseconds should be waited until this worker should be executed.
*/
long getDelay() {
long ageOfContent = System.currentTimeMillis() - contentTime;
return DELAY_TIME - ageOfContent;
}
/**
* Return {@code true} if enough time passed since last update, so that this worker should be
* executed.
*/
boolean isReady() {
return getDelay() <= 0;
}
void perform() {
context.setContents(source, content);
AnalysisManager.getInstance().performAnalysisInBackground(manager, context);
}
}
private class FastThread extends Thread {
public FastThread() {
setName("DartUpdateSourceHelper-FastThread");
setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
FastWorker worker = fastQueue.take();
worker.perform();
} catch (InterruptedException e) {
}
fastQueueEmpty = fastQueue.isEmpty();
}
}
}
private class FastWorker {
private final AnalysisManager analysisManager;
private final ContextManager manager;
private final AnalysisContext context;
private final Source source;
private final String content;
private final int offset;
private final int oldLength;
private final int newLength;
public FastWorker(AnalysisManager analysisManager, ContextManager manager,
AnalysisContext context, Source source, String content, int offset, int oldLength,
int newLength) {
this.analysisManager = analysisManager;
this.manager = manager;
this.context = context;
this.source = source;
this.content = content;
this.offset = offset;
this.oldLength = oldLength;
this.newLength = newLength;
}
void perform() {
if (offset != -1) {
context.setChangedContents(source, content, offset, oldLength, newLength);
} else {
context.setContents(source, content);
}
analysisManager.performAnalysisInBackground(manager, context);
}
}
private static long DELAY_TIME = 250;
private static DartUpdateSourceHelper INSTANCE = new DartUpdateSourceHelper();
/**
* Returns the unique instance of the {@link DartUpdateSourceHelper}.
*/
public static DartUpdateSourceHelper getInstance() {
return INSTANCE;
}
private final BlockingQueue<FastWorker> fastQueue = new LinkedBlockingQueue<FastWorker>();
private final BlockingDeque<DelayWorker> delayDeque = new LinkedBlockingDeque<DelayWorker>();
private boolean fastQueueEmpty = true;
private boolean delayDequeEmpty = true;
private DartUpdateSourceHelper() {
new FastThread().start();
new DelayThread().start();
}
/**
* Schedules update of the given {@link Source} in background and subsequent analysis.
* <p>
* Operation is executed ASAP, without any additional delay (but still it background).
*
* @param manager the manager containing the context to be analyzed (not {@code null})
* @param context the context to be analyzed (not {@code null})
* @param source the source whose contents are being overridden
* @param content the text to replace the range in the current contents
*/
public void updateFast(AnalysisManager analysisManager, ContextManager manager,
AnalysisContext context, Source source, String content) {
updateFast(analysisManager, manager, context, source, content, -1, 0, 0);
}
/**
* Schedules update of the given {@link Source} in background and subsequent analysis.
* <p>
* Operation is executed ASAP, without any additional delay (but still it background).
*
* @param manager the manager containing the context to be analyzed (not {@code null})
* @param context the context to be analyzed (not {@code null})
* @param source the source whose contents are being overridden
* @param content the text to replace the range in the current contents
* @param offset the offset into the current contents, if {@code -1} then the whole source content
* is changes
* @param oldLength the number of characters in the original contents that were replaced
* @param newLength the number of characters in the replacement text
*/
public void updateFast(AnalysisManager analysisManager, ContextManager manager,
AnalysisContext context, Source source, String content, int offset, int oldLength,
int newLength) {
try {
fastQueueEmpty = false;
fastQueue.add(new FastWorker(
analysisManager,
manager,
context,
source,
content,
offset,
oldLength,
newLength));
} catch (IllegalStateException e) {
// Should never happen, "fastQueue" has a very high capacity.
}
}
/**
* Schedules update of the given {@link Source} in background and subsequent analysis.
* <p>
* Operation execution is delayed for some reasonable time.
*/
public void updateWithDelay(ContextManager manager, AnalysisContext context, Source source,
String content) {
// if the same Source, update worker
{
DelayWorker lastWorker = delayDeque.peekLast();
if (lastWorker != null && lastWorker.manager == manager && lastWorker.context == context
&& Objects.equal(lastWorker.source, source)) {
lastWorker.setContent(content);
return;
}
}
// new Source - new worker
delayDequeEmpty = false;
delayDeque.add(new DelayWorker(manager, context, source, content));
}
/**
* Waits for execution queue is empty.
*/
@VisibleForTesting
public void waitForEmptyQueue() {
while (!fastQueueEmpty || !delayDequeEmpty) {
Uninterruptibles.sleepUninterruptibly(5, TimeUnit.MILLISECONDS);
}
}
}