/*
* 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.collect.Lists;
import com.google.common.collect.Maps;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.source.Source;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.internal.model.DartIgnoreManager;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Helper for updating the order in which sources are analyzed in contexts associated with editors.
* This is called once per instantiated editor on startup and then once for each editor as it
* becomes active. For example, if there are 2 of 7 editors visible on startup, then this will be
* called for the 2 visible editors.
*
* @coverage dart.editor.ui.text
*/
public class DartPrioritySourcesHelper {
private static class PriorityOrderRequest {
final AnalysisContext context;
final List<Source> sources;
public PriorityOrderRequest(AnalysisContext context, List<Source> sources) {
this.context = context;
this.sources = sources;
}
public void perform() {
context.setAnalysisPriorityOrder(sources);
}
}
private class PriorityOrderThread extends Thread {
public PriorityOrderThread() {
setName("DartPrioritySourcesHelper-PriorityOrderThread");
setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
PriorityOrderRequest request = requestQueue.take();
if (request == SHUTDOWN_REQUEST) {
return;
}
request.perform();
} catch (InterruptedException e) {
}
}
}
}
private static final PriorityOrderRequest SHUTDOWN_REQUEST = new PriorityOrderRequest(null, null);
private final IWorkbench workbench;
private final DartIgnoreManager ignoreManager;
private final BlockingQueue<PriorityOrderRequest> requestQueue = new LinkedBlockingQueue<PriorityOrderRequest>();
public DartPrioritySourcesHelper(IWorkbench workbench) {
this(workbench, DartCore.getIgnoreManager());
}
public DartPrioritySourcesHelper(IWorkbench workbench, DartIgnoreManager ignoreManager) {
this.workbench = workbench;
this.ignoreManager = ignoreManager;
}
/**
* Schedules helper start, once {@link IWorkbenchPage} is created.
*/
public void start() {
workbench.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
internalStart(page);
}
}
}
});
new PriorityOrderThread().start();
}
public void stop() {
requestQueue.offer(SHUTDOWN_REQUEST);
}
public void test_waitForQueueEmpty() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
requestQueue.add(new PriorityOrderRequest(null, null) {
@Override
public void perform() {
latch.countDown();
}
});
latch.await();
}
/**
* @return the {@link DartPriorityFileEditor} that corresponds to the given {@link IWorkbenchPart}
* , maybe {@code null}.
*/
private DartPriorityFileEditor getPrioritySourceEditor(IWorkbenchPart part) {
if (part != null) {
Object maybeEditor = part.getAdapter(DartPriorityFileEditor.class);
if (maybeEditor instanceof DartPriorityFileEditor) {
return (DartPriorityFileEditor) maybeEditor;
}
}
return null;
}
/**
* Answer the visible {@link DartPriorityFileEditor}s.
*
* @param context the context (not {@code null})
* @return a list of sources (not {@code null}, contains no {@code null}s)
*/
private List<DartPriorityFileEditor> getVisibleEditors() {
List<DartPriorityFileEditor> editors = Lists.newArrayList();;
for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
for (IWorkbenchPage page : window.getPages()) {
for (IEditorReference editorRef : page.getEditorReferences()) {
IEditorPart part = editorRef.getEditor(false);
DartPriorityFileEditor editor = getPrioritySourceEditor(part);
if (editor != null) {
if (editor.isVisible()) {
editors.add(editor);
}
}
}
}
}
return editors;
}
/**
* Answer the visible editors displaying source for the given context. This must be called on the
* UI thread because it accesses windows, pages, and editors.
*
* @param context the context (not {@code null})
* @return a list of sources (not {@code null}, contains no {@code null}s)
*/
private List<Source> getVisibleSourcesForContext(AnalysisContext context) {
List<Source> sources = Lists.newArrayList();
List<DartPriorityFileEditor> editors = getVisibleEditors();
for (DartPriorityFileEditor editor : editors) {
if (editor.getInputAnalysisContext() == context) {
Source source = editor.getInputSource();
if (source != null && ignoreManager.isAnalyzed(source.getFullName())) {
sources.add(source);
}
}
}
return sources;
}
private void handlePartActivated(IWorkbenchPart part) {
DartPriorityFileEditor editor = getPrioritySourceEditor(part);
if (editor != null) {
updateAnalysisPriorityOrderOnUiThread(editor, true);
}
}
private void handlePartDeactivated(IWorkbenchPart part) {
DartPriorityFileEditor editor = getPrioritySourceEditor(part);
if (editor != null) {
updateAnalysisPriorityOrderOnUiThread(editor, false);
}
}
/**
* Starts listening for {@link IWorkbenchPage} and adding/removing sources of the visible editors.
*/
private void internalStart(IWorkbenchPage activePage) {
// make source of the currently visible editors a priority ones
// but exclude those sources that are marked as do-not-analyze
{
Map<AnalysisContext, List<Source>> contextMap = Maps.newHashMap();
List<DartPriorityFileEditor> editors = getVisibleEditors();
for (DartPriorityFileEditor editor : editors) {
AnalysisContext context = editor.getInputAnalysisContext();
if (context != null && !contextMap.containsKey(context)) {
List<Source> sources = getVisibleSourcesForContext(context);
contextMap.put(context, sources);
}
}
// schedule priority sources setting
for (Entry<AnalysisContext, List<Source>> entry : contextMap.entrySet()) {
AnalysisContext context = entry.getKey();
List<Source> prioritySources = entry.getValue();
updateAnalysisPriorityOrderInBackground(context, prioritySources);
}
}
// track visible editors
activePage.addPartListener(new IPartListener2() {
@Override
public void partActivated(IWorkbenchPartReference partRef) {
}
@Override
public void partBroughtToTop(IWorkbenchPartReference partRef) {
}
@Override
public void partClosed(IWorkbenchPartReference partRef) {
}
@Override
public void partDeactivated(IWorkbenchPartReference partRef) {
}
@Override
public void partHidden(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part != null) {
handlePartDeactivated(part);
}
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
}
@Override
public void partOpened(IWorkbenchPartReference partRef) {
}
@Override
public void partVisible(IWorkbenchPartReference partRef) {
IWorkbenchPart part = partRef.getPart(false);
if (part != null) {
handlePartActivated(part);
}
}
});
}
/**
* Schedules {@link AnalysisContext#setAnalysisPriorityOrder(List)} execution in background.
*/
private void updateAnalysisPriorityOrderInBackground(AnalysisContext context, List<Source> sources) {
try {
requestQueue.add(new PriorityOrderRequest(context, sources));
} catch (IllegalStateException e) {
// Should never happen, "requestQueue" has a very high capacity.
}
}
/**
* Update the order in which sources are analyzed in the context associated with the editor. This
* is called once per instantiated editor on startup and then once for each editor as it becomes
* active. For example, if there are 2 of 7 editors visible on startup, then this will be called
* for the 2 visible editors.
* <p>
* MUST be called on the UI thread.
*
* @param isOpen {@code true} if the editor is open and the source should be the first source
* analyzed or {@code false} if the editor is closed and the source should be removed
* from the priority list.
*/
private void updateAnalysisPriorityOrderOnUiThread(DartPriorityFileEditor editor, boolean isOpen) {
AnalysisContext context = editor.getInputAnalysisContext();
Source source = editor.getInputSource();
if (context != null && source != null) {
List<Source> sources = getVisibleSourcesForContext(context);
sources.remove(source);
if (isOpen) {
if (ignoreManager.isAnalyzed(source.getFullName())) {
sources.add(0, source);
}
}
updateAnalysisPriorityOrderInBackground(context, sources);
}
}
}