/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.netbeans.parsing.spi.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.RunnableScheduledFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.JTextComponent;
import org.antlr.netbeans.editor.text.DocumentSnapshot;
import org.antlr.netbeans.editor.text.VersionedDocument;
import org.antlr.netbeans.parsing.spi.ParseContext;
import org.antlr.netbeans.parsing.spi.ParserData;
import org.antlr.netbeans.parsing.spi.ParserDataDefinition;
import org.antlr.netbeans.parsing.spi.ParserDataEvent;
import org.antlr.netbeans.parsing.spi.ParserDataListener;
import org.antlr.netbeans.parsing.spi.ParserDataOptions;
import org.antlr.netbeans.parsing.spi.ParserResultHandler;
import org.antlr.netbeans.parsing.spi.ParserTask;
import org.antlr.netbeans.parsing.spi.ParserTaskDefinition;
import org.antlr.netbeans.parsing.spi.ParserTaskManager;
import org.antlr.netbeans.parsing.spi.ParserTaskProvider;
import org.antlr.netbeans.parsing.spi.ParserTaskScheduler;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.lib.editor.util.ListenerList;
import org.openide.filesystems.FileObject;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;
import org.openide.util.lookup.ServiceProvider;
/**
*
* @author Sam Harwell
*/
@NbBundle.Messages({
"taskFailedException=Task execution failed with exception.",
"taskFailedError=Task execution failed with error.",
})
@ServiceProvider(service=ParserTaskManager.class)
public class ParserTaskManagerImpl implements ParserTaskManager {
// -J-Dorg.antlr.netbeans.parsing.spi.impl.ParserTaskManagerImpl.level=FINE
private static final Logger LOGGER = Logger.getLogger(ParserTaskManagerImpl.class.getName());
private static final Long DEFAULT_DELAY = 500L;
private static final TimeUnit DEFAULT_TIMEUNIT = TimeUnit.MILLISECONDS;
private static final int HIGH_THREAD_PRIORITY_VALUE = Thread.NORM_PRIORITY;
private static final int LOW_THREAD_PRIORITY_VALUE = Thread.NORM_PRIORITY - 2;
private final ListenerList<ParserDataListener<Object>> globalListeners = new ListenerList<>();
private final Map<ParserDataDefinition<?>, ListenerList<ParserDataListener<?>>> dataListeners =
new HashMap<>();
private final Map<String, Collection<? extends ParserTaskProvider>> taskProviders =
new HashMap<>();
private static final String COMPONENT_PROPERTIES_KEY = ParserTaskManagerImpl.class.getName() + "-comp-properties";
private static final String DOCUMENT_PROPERTIES_KEY = ParserTaskManagerImpl.class.getName() + "-properties";
private final RejectionHandler rejectionHandler;
private final ScheduledThreadPoolExecutor highPriorityExecutor;
private final ScheduledThreadPoolExecutor lowPriorityExecutor;
public ParserTaskManagerImpl() {
rejectionHandler = new RejectionHandler();
int highPriorityPoolSize = 2;
highPriorityExecutor = new PriorityInsertionScheduledThreadPoolExecutor(highPriorityPoolSize, new ParserThreadFactory(HIGH_THREAD_PRIORITY_VALUE), rejectionHandler);
int lowPriorityPoolSize = 2;//Math.max(2, Runtime.getRuntime().availableProcessors());
lowPriorityExecutor = new PriorityInsertionScheduledThreadPoolExecutor(lowPriorityPoolSize, new ParserThreadFactory(LOW_THREAD_PRIORITY_VALUE), rejectionHandler);
}
@Override
public <T> Future<ParserData<T>> getData(DocumentSnapshot snapshot, ParserDataDefinition<T> definition) {
return getData(snapshot, null, definition);
}
@Override
public Future<ParserData<?>>[] getData(DocumentSnapshot snapshot, Collection<? extends ParserDataDefinition<?>> definitions) {
return getData(snapshot, null, definitions);
}
@Override
public <T> Future<ParserData<T>> getData(final DocumentSnapshot snapshot, final ParserDataDefinition<T> definition, Collection<ParserDataOptions> options) {
return getData(snapshot, null, definition, options);
}
@Override
public Future<ParserData<?>>[] getData(DocumentSnapshot snapshot, Collection<? extends ParserDataDefinition<?>> definitions, Collection<ParserDataOptions> options) {
return getData(snapshot, null, definitions, options);
}
@Override
public <T> Future<ParserData<T>> getData(DocumentSnapshot snapshot, JTextComponent component, ParserDataDefinition<T> definition) {
return getData(snapshot, definition, EnumSet.noneOf(ParserDataOptions.class));
}
@Override
public Future<ParserData<?>>[] getData(DocumentSnapshot snapshot, JTextComponent component, Collection<? extends ParserDataDefinition<?>> definitions) {
return getData(snapshot, definitions, EnumSet.noneOf(ParserDataOptions.class));
}
@Override
public <T> Future<ParserData<T>> getData(final DocumentSnapshot snapshot, JTextComponent component, final ParserDataDefinition<T> definition, Collection<ParserDataOptions> options) {
Parameters.notNull("snapshot", snapshot);
Parameters.notNull("definition", definition);
Parameters.notNull("options", options);
@SuppressWarnings("unchecked")
ParserData<T> cachedData = getCachedData(snapshot.getVersionedDocument(), component, definition);
boolean useCached = options.contains(ParserDataOptions.NO_UPDATE);
boolean allowStale = options.contains(ParserDataOptions.ALLOW_STALE);
if (!useCached && cachedData != null) {
if (allowStale) {
useCached = true;
} else if (cachedData.getSnapshot().equals(snapshot)) {
useCached = true;
}
}
if (useCached) {
if (!allowStale && cachedData != null && !cachedData.getSnapshot().equals(snapshot)) {
cachedData = null;
}
return new CompletedFuture<>(cachedData, null);
}
ParseContext context = new ParseContext(ParserTaskScheduler.MANUAL_TASK_SCHEDULER, snapshot, component);
Callable<ParserData<T>> callable = createCallable(context, definition);
if (options.contains(ParserDataOptions.SYNCHRONOUS) || isParserThread()) {
try {
return new CompletedFuture<>(callable.call(), null);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "An exception occurred while handling a parse request.", ex);
return new CompletedFuture<>(null, ex);
}
}
callable = decorateCallable(callable);
return lowPriorityExecutor.schedule(callable, 0, TimeUnit.NANOSECONDS);
}
@Override
public Future<ParserData<?>>[] getData(DocumentSnapshot snapshot, JTextComponent component, Collection<? extends ParserDataDefinition<?>> definitions, Collection<ParserDataOptions> options) {
Parameters.notNull("snapshot", snapshot);
Parameters.notNull("definitions", definitions);
Parameters.notNull("options", options);
throw new UnsupportedOperationException("Not supported yet.");
// List<ParserData<?>> data = new ArrayList<ParserData<?>>();
// for (ParserDataDefinition<?> definition : definitions) {
// data.add(getData(snapshot, definition, options));
// }
//
// return data.toArray(new ParserData<?>[0]);
}
@Override
public void reschedule(VersionedDocument document, Class<? extends ParserTaskScheduler> schedulerClass) {
reschedule(document, null, schedulerClass);
}
@Override
public void reschedule(VersionedDocument document, JTextComponent component, Class<? extends ParserTaskScheduler> schedulerClass) {
ParserTaskScheduler scheduler = TaskSchedulers.getScheduler(schedulerClass);
if (scheduler != null) {
@SuppressWarnings("rawtypes")
Collection<? extends ParserDataDefinition> data = MimeLookup.getLookup(document.getMimeType()).lookupAll(ParserDataDefinition.class);
for (ParserDataDefinition<?> definition : data) {
if (definition.getScheduler() == schedulerClass && definition.isCacheable()) {
clearCachedData(document, definition);
}
}
ParseContext context = new ParseContext(scheduler.getClass(), document, component);
scheduler.schedule(context);
}
}
@Override
public void reschedule(VersionedDocument document, JTextComponent component, long delay, TimeUnit timeUnit, Class<? extends ParserTaskScheduler> schedulerClass) {
ParserTaskScheduler scheduler = TaskSchedulers.getScheduler(schedulerClass);
if (scheduler != null) {
@SuppressWarnings("rawtypes")
Collection<? extends ParserDataDefinition> data = MimeLookup.getLookup(document.getMimeType()).lookupAll(ParserDataDefinition.class);
for (ParserDataDefinition<?> definition : data) {
if (definition.getScheduler() == schedulerClass && definition.isCacheable()) {
clearCachedData(document, definition);
}
}
ParseContext context = new ParseContext(scheduler.getClass(), document, component);
scheduler.schedule(context, delay, timeUnit);
}
}
@Override
public <T> ScheduledFuture<ParserData<T>> scheduleData(ParseContext context, ParserDataDefinition<T> data) {
return scheduleData(context, data, DEFAULT_DELAY, DEFAULT_TIMEUNIT);
}
@Override
public Map<ParserDataDefinition<?>, ScheduledFuture<ParserData<?>>> scheduleData(ParseContext context, Collection<? extends ParserDataDefinition<?>> data) {
return scheduleData(context, data, DEFAULT_DELAY, DEFAULT_TIMEUNIT);
}
@Override
public <T> ScheduledFuture<ParserData<T>> scheduleData(ParseContext context, ParserDataDefinition<T> data, long delay, TimeUnit timeUnit) {
Callable<ParserData<T>> callable = createCallable(context, data);
callable = decorateCallable(callable);
return lowPriorityExecutor.schedule(callable, delay, timeUnit);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<ParserDataDefinition<?>, ScheduledFuture<ParserData<?>>> scheduleData(ParseContext context, @NonNull Collection<? extends ParserDataDefinition<?>> data, long delay, TimeUnit timeUnit) {
if (data.isEmpty()) {
return Collections.emptyMap();
}
Map<ParserDataDefinition<?>, ScheduledFuture<ParserData<?>>> futures = new HashMap<>();
for (ParserDataDefinition dataDefinition : data) {
futures.put(dataDefinition, (ScheduledFuture<ParserData<?>>)scheduleData(context, dataDefinition, delay, timeUnit));
}
return futures;
}
@Override
public ScheduledFuture<Collection<? extends ParserData<?>>> scheduleTask(@NonNull ParseContext context, @NonNull ParserTaskProvider provider) {
return scheduleTask(context, provider, DEFAULT_DELAY, DEFAULT_TIMEUNIT);
}
@Override
public Map<ParserTaskProvider, ScheduledFuture<Collection<? extends ParserData<?>>>> scheduleTask(@NonNull ParseContext context, @NonNull Collection<? extends ParserTaskProvider> providers) {
return scheduleTask(context, providers, DEFAULT_DELAY, DEFAULT_TIMEUNIT);
}
@Override
public ScheduledFuture<Collection<? extends ParserData<?>>> scheduleTask(@NonNull ParseContext context, @NonNull ParserTaskProvider provider, long delay, @NonNull TimeUnit timeUnit) {
Callable<Collection<? extends ParserData<?>>> callable = createCallable(context, provider);
callable = decorateCallable(callable);
return lowPriorityExecutor.schedule(callable, delay, timeUnit);
}
@Override
public Map<ParserTaskProvider, ScheduledFuture<Collection<? extends ParserData<?>>>> scheduleTask(@NonNull ParseContext context, @NonNull Collection<? extends ParserTaskProvider> providers, long delay, @NonNull TimeUnit timeUnit) {
if (providers.isEmpty()) {
return Collections.emptyMap();
}
Map<ParserTaskProvider, ScheduledFuture<Collection<? extends ParserData<?>>>> result = new HashMap<>();
for (ParserTaskProvider provider : providers) {
result.put(provider, scheduleTask(context, provider, delay, timeUnit));
}
return result;
}
@Override
public <T> ScheduledFuture<T> scheduleLowPriority(Callable<T> callable) {
Parameters.notNull("callable", callable);
callable = decorateCallable(callable);
return lowPriorityExecutor.schedule(callable, 0, TimeUnit.MILLISECONDS);
}
@Override
public <T> ScheduledFuture<T> scheduleHighPriority(Callable<T> callable) {
Parameters.notNull("callable", callable);
callable = decorateCallable(callable);
return highPriorityExecutor.schedule(callable, 0, TimeUnit.MILLISECONDS);
}
protected <T> Callable<T> decorateCallable(@NonNull final Callable<T> callable) {
if (callable instanceof UpdateCallable) {
return callable;
}
return new Callable<T>() {
@Override
public T call() throws Exception {
try {
return callable.call();
} catch (Exception | Error ex) {
LOGGER.log(Level.WARNING, Bundle.taskFailedException(), ex);
throw ex;
}
}
};
}
@Override
public void addDataListener(ParserDataListener<Object> listener) {
Parameters.notNull("listener", listener);
synchronized (globalListeners) {
globalListeners.add(listener);
}
}
@Override
public void removeDataListener(ParserDataListener<Object> listener) {
Parameters.notNull("listener", listener);
synchronized (globalListeners) {
globalListeners.remove(listener);
}
}
@Override
public <T> void addDataListener(ParserDataDefinition<T> definition, ParserDataListener<? super T> listener) {
Parameters.notNull("definition", definition);
Parameters.notNull("listener", listener);
synchronized (dataListeners) {
ListenerList<ParserDataListener<?>> listeners = dataListeners.get(definition);
if (listeners == null) {
listeners = new ListenerList<>();
dataListeners.put(definition, listeners);
}
listeners.add(listener);
}
}
@Override
public <T> void removeDataListener(ParserDataDefinition<T> definition, ParserDataListener<? super T> listener) {
Parameters.notNull("definition", definition);
Parameters.notNull("listener", listener);
synchronized (dataListeners) {
ListenerList<ParserDataListener<?>> listeners = dataListeners.get(definition);
if (listeners == null) {
return;
}
listeners.remove(listener);
if (listeners.getListenerCount() == 0) {
dataListeners.remove(definition);
}
}
}
private static boolean isParserThread() {
return Thread.currentThread() instanceof ParserThread;
}
private <T> Callable<ParserData<T>> createCallable(ParseContext context, ParserDataDefinition<T> data) {
Callable<ParserData<T>> callable = new UpdateDataCallable<>(this, context, data);
return callable;
}
private Callable<Collection<? extends ParserData<?>>> createCallable(ParseContext context, ParserTaskProvider provider) {
Callable<Collection<? extends ParserData<?>>> callable = new UpdateTaskCallable(this, context, provider);
return callable;
}
private <T> void fireDataChanged(ParserDataDefinition<T> definition, ParserData<T> data) {
ListenerList<ParserDataListener<?>> listeners;
synchronized (dataListeners) {
listeners = dataListeners.get(definition);
}
if (listeners == null && globalListeners.getListenerCount() == 0) {
return;
}
ParserDataEvent<T> event = new ParserDataEvent<>(this, definition, data);
if (listeners != null) {
for (ParserDataListener<?> listener : listeners.getListeners()) {
@SuppressWarnings("unchecked")
ParserDataListener<T> typedListener = (ParserDataListener<T>)listener;
typedListener.dataChanged(event);
}
}
for (ParserDataListener<Object> listener : globalListeners.getListeners()) {
listener.dataChanged(event);
}
}
private ParserTaskProvider getTaskProvider(VersionedDocument versionedDocument, ParserDataDefinition<?> definition) {
Collection<? extends ParserTaskProvider> providers = getTaskProviders(versionedDocument);
for (ParserTaskProvider provider : providers) {
ParserTaskDefinition taskDefinition = provider.getDefinition();
boolean hasOutput = taskDefinition.getOutputs().contains(definition);
if (hasOutput) {
return provider;
}
}
return null;
}
private Collection<? extends ParserTaskProvider> getTaskProviders(VersionedDocument versionedDocument) {
String mimeType = versionedDocument.getMimeType();
synchronized (taskProviders) {
Collection<? extends ParserTaskProvider> providers = taskProviders.get(mimeType);
if (providers == null) {
providers = MimeLookup.getLookup(mimeType).lookupAll(ParserTaskProvider.class);
taskProviders.put(mimeType, providers);
}
return providers;
}
}
@SuppressWarnings("unchecked")
private synchronized <T> ParserData<T> getCachedData(VersionedDocument versionedDocument, JTextComponent component, ParserDataDefinition<T> definition) {
if (!definition.isCacheable()) {
return null;
} else if (component == null && definition.isComponentSpecific()) {
return null;
}
if (definition.isComponentSpecific()) {
Map<ParserDataDefinition<?>, Map<JTextComponent, ParserData<?>>> documentProperties = (Map<ParserDataDefinition<?>, Map<JTextComponent, ParserData<?>>>)versionedDocument.getProperty(COMPONENT_PROPERTIES_KEY);
if (documentProperties != null) {
Map<JTextComponent, ParserData<?>> componentProperties = documentProperties.get(definition);
if (componentProperties != null) {
return (ParserData<T>)componentProperties.get(component);
}
}
} else {
Map<ParserDataDefinition<?>, ParserData<?>> documentProperties = (Map<ParserDataDefinition<?>, ParserData<?>>)versionedDocument.getProperty(DOCUMENT_PROPERTIES_KEY);
if (documentProperties != null) {
return (ParserData<T>)documentProperties.get(definition);
}
}
return null;
}
private synchronized boolean clearCachedData(VersionedDocument versionedDocument, ParserDataDefinition<?> definition) {
if (definition.isComponentSpecific()) {
Map<?, ?> componentProperties = (Map<?, ?>)versionedDocument.getProperty(COMPONENT_PROPERTIES_KEY);
if (componentProperties != null) {
return componentProperties.remove(definition) != null;
}
} else {
Map<?, ?> documentProperties = (Map<?, ?>)versionedDocument.getProperty(DOCUMENT_PROPERTIES_KEY);
if (documentProperties != null) {
return documentProperties.remove(definition) != null;
}
}
return false;
}
private synchronized boolean updateCachedData(ParseContext context, ParserDataDefinition<?> definition, ParserData<?> data) {
if (data == null) {
return false;
} else if (definition.isComponentSpecific() && data.getContext().getComponent() == null) {
return false;
}
VersionedDocument versionedDocument = context.getDocument();
if (definition.isComponentSpecific()) {
ComponentPropertiesMap documentProperties = (ComponentPropertiesMap)versionedDocument.getProperty(COMPONENT_PROPERTIES_KEY);
if (documentProperties == null) {
documentProperties = new ComponentPropertiesMap();
versionedDocument.putProperty(COMPONENT_PROPERTIES_KEY, documentProperties);
}
ComponentDataMap componentProperties = (ComponentDataMap)documentProperties.get(definition);
if (componentProperties == null) {
componentProperties = new ComponentDataMap();
}
ParserData<?> previousData = componentProperties.get(data.getContext().getComponent());
//ParserData<?> previousData = previousDataRef != null ?
if (previousData == data || (previousData != null && previousData.equals(data))) {
return false;
}
else if (previousData != null && !isCurrentNewer(context, previousData, data)) {
// don't replace new data with old
return false;
}
componentProperties.put(data.getContext().getComponent(), data);
return true;
} else {
DocumentPropertiesMap documentProperties = (DocumentPropertiesMap)versionedDocument.getProperty(DOCUMENT_PROPERTIES_KEY);
if (documentProperties == null) {
documentProperties = new DocumentPropertiesMap();
versionedDocument.putProperty(DOCUMENT_PROPERTIES_KEY, documentProperties);
}
ParserData<?> previousData = documentProperties.get(definition);
if (previousData == data || (previousData != null && previousData.equals(data))) {
return false;
}
else if (previousData != null && !isCurrentNewer(context, previousData, data)) {
// don't replace new data with old
return false;
}
documentProperties.put(definition, data);
return true;
}
}
private static boolean isCurrentNewer(ParseContext context, ParserData<?> original, ParserData<?> current) {
if (original.getSnapshot().getVersion().getVersionNumber() > current.getSnapshot().getVersion().getVersionNumber()) {
return false;
}
return true;
}
private static class ComponentPropertiesMap extends HashMap<ParserDataDefinition<?>, Map<JTextComponent, ParserData<?>>> {
}
private static class ComponentDataMap extends WeakHashMap<JTextComponent, ParserData<?>> {
}
private static class DocumentPropertiesMap extends HashMap<ParserDataDefinition<?>, ParserData<?>> {
}
private static class RejectionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
private static class ParserThread extends Thread {
public ParserThread(Runnable target) {
super(target);
}
}
private static class ParserThreadFactory implements ThreadFactory {
private final int priority;
private int threadCount;
public ParserThreadFactory(int priority) {
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
throw new IllegalArgumentException("Invalid thread priority.");
}
this.priority = priority;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new ParserThread(r);
thread.setPriority(priority);
String priorityName = priority >= HIGH_THREAD_PRIORITY_VALUE ? "Foreground" : "Background";
String name = String.format("Parse (%s) #%d", priorityName, ++threadCount);
thread.setName(name);
return thread;
}
}
private static abstract class UpdateCallable<Result> implements Callable<Result> {
protected final ParserTaskManagerImpl outer;
protected final ParseContext context;
protected UpdateCallable(ParserTaskManagerImpl outer, ParseContext context) {
this.outer = outer;
this.context = context;
}
@Override
public final Result call() throws Exception {
try {
return callImpl();
} catch (Exception | Error ex) {
LOGGER.log(Level.WARNING, Bundle.taskFailedException(), ex);
throw ex;
}
}
protected abstract Result callImpl() throws Exception;
}
private static class UpdateDataCallable<T> extends UpdateCallable<ParserData<T>> {
private final ParserDataDefinition<T> data;
public UpdateDataCallable(ParserTaskManagerImpl outer, ParseContext context, ParserDataDefinition<T> data) {
super(outer, context);
this.data = data;
}
@Override
@SuppressWarnings("unchecked")
protected ParserData<T> callImpl() throws Exception {
VersionedDocument document = context.getDocument();
DocumentSnapshot snapshot = context.getSnapshot();
if (snapshot == null) {
snapshot = document.getCurrentSnapshot();
}
if (data.isCacheable()) {
ParserData<T> cachedData = outer.getCachedData(context.getDocument(), context.getComponent(), data);
if (cachedData != null && cachedData.getSnapshot().equals(snapshot)) {
return cachedData;
}
}
ParserTaskProvider provider = outer.getTaskProvider(document, data);
if (provider == null) {
LOGGER.log(Level.WARNING, "No provider found for parser data \"{0}\".", data.getName());
return null;
} else if (LOGGER.isLoggable(Level.FINE)) {
Object[] args = { provider.getDefinition().getName(), data.getName() };
LOGGER.log(Level.FINE, "Using provider \"{0}\" for data \"{1}\".", args);
}
final ParserTask task = provider.createTask(document);
if (LOGGER.isLoggable(Level.FINE)) {
String threadName = Thread.currentThread().getName();
String messageFormat = "{0}: Updating data \"{1}\" with task \"{2}\" for {3}#{4}";
FileObject fileObject = document.getFileObject();
String path = fileObject != null ? fileObject.getPath() : "";
LOGGER.log(Level.FINE, messageFormat, new Object[] { threadName, data.getName(), task.getDefinition().getName(), path, snapshot.getVersion().getVersionNumber() });
}
ResultAggregator handler = new ResultAggregator(outer, context);
task.parse(outer, context, snapshot, Collections.<ParserDataDefinition<?>>singleton(data), handler);
for (ParserData<?> result : handler.getUpdatedResults()) {
outer.fireDataChanged((ParserDataDefinition)result.getDefinition(), result);
}
for (ParserData<?> result : handler.getResults()) {
if (result.getDefinition().equals(data)) {
return (ParserData<T>)result;
}
}
return null;
}
}
private static class UpdateTaskCallable extends UpdateCallable<Collection<? extends ParserData<?>>> {
private final ParserTaskProvider provider;
public UpdateTaskCallable(ParserTaskManagerImpl outer, ParseContext context, ParserTaskProvider provider) {
super(outer, context);
this.provider = provider;
}
@Override
@SuppressWarnings("unchecked")
protected Collection<? extends ParserData<?>> callImpl() throws Exception {
VersionedDocument document = context.getDocument();
final ParserTask task = provider.createTask(document);
DocumentSnapshot snapshot = context.getSnapshot();
if (snapshot == null) {
snapshot = document.getCurrentSnapshot();
}
if (LOGGER.isLoggable(Level.FINE)) {
String messageFormat = "{0}: Updating task \"{1}\" for {2}#{3}";
FileObject fileObject = document.getFileObject();
String path = fileObject != null ? fileObject.getPath() : "";
Object[] args =
{
Thread.currentThread().getName(),
task.getDefinition().getName(),
path,
snapshot.getVersion().getVersionNumber()
};
LOGGER.log(Level.FINE, messageFormat, args);
}
ResultAggregator handler = new ResultAggregator(outer, context);
task.parse(outer, context, snapshot, provider.getDefinition().getOutputs(), handler);
for (ParserData<?> result : handler.getUpdatedResults()) {
outer.fireDataChanged((ParserDataDefinition)result.getDefinition(), result);
}
return handler.getResults();
}
}
private static class ResultAggregator implements ParserResultHandler {
private final List<ParserData<?>> results = new ArrayList<>();
private final List<ParserData<?>> updatedResults = new ArrayList<>();
private final ParserTaskManagerImpl outer;
private final ParseContext context;
public ResultAggregator(@NonNull ParserTaskManagerImpl outer, @NonNull ParseContext context) {
Parameters.notNull("outer", outer);
Parameters.notNull("context", context);
this.outer = outer;
this.context = context;
}
@Override
public void addResult(@NonNull ParserData<?> result) {
Parameters.notNull("result", result);
results.add(result);
boolean cacheable = result.getDefinition().isCacheable();
boolean updated = !cacheable;
if (cacheable) {
updated |= outer.updateCachedData(context, result.getDefinition(), result);
}
if (updated) {
updatedResults.add(result);
}
}
public List<? extends ParserData<?>> getResults() {
return results;
}
public List<? extends ParserData<?>> getUpdatedResults() {
return updatedResults;
}
}
private static class PriorityInsertionScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
public PriorityInsertionScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, threadFactory, handler);
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
int priority = PRIORITY_INITIAL;
if (task.getDelay(DEFAULT_TIMEUNIT) <= 0) {
priority += PRIORITY_IMMEDIATE_OFFSET;
}
if (callable instanceof UpdateCallable<?>) {
UpdateCallable<?> updateCallable = (UpdateCallable<?>)callable;
if (updateCallable.context.getDocument().getDocument() != null) {
priority += PRIORITY_FOREGROUND_OFFSET;
}
}
return new PriorityInsertionRunnableScheduledFuture<>(task, priority);
}
@Override
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
int priority = PRIORITY_INITIAL;
if (task.getDelay(DEFAULT_TIMEUNIT) <= 0) {
priority += PRIORITY_IMMEDIATE_OFFSET;
}
if (runnable instanceof UpdateCallable<?>) {
UpdateCallable<?> updateCallable = (UpdateCallable<?>)runnable;
if (updateCallable.context.getDocument().getDocument() != null) {
priority += PRIORITY_FOREGROUND_OFFSET;
}
}
return new PriorityInsertionRunnableScheduledFuture<>(task, priority);
}
}
private static final int PRIORITY_INITIAL = 2;
private static final int PRIORITY_IMMEDIATE_OFFSET = -1;
private static final int PRIORITY_FOREGROUND_OFFSET = -2;
private static class PriorityInsertionRunnableScheduledFuture<V> implements RunnableScheduledFuture<V> {
private final RunnableScheduledFuture<V> wrappedTask;
private final int priority;
public PriorityInsertionRunnableScheduledFuture(@NonNull RunnableScheduledFuture<V> wrappedTask, int priority) {
Parameters.notNull("wrappedTask", wrappedTask);
this.wrappedTask = wrappedTask;
this.priority = priority;
}
@Override
public boolean isPeriodic() {
return wrappedTask.isPeriodic();
}
@Override
public void run() {
wrappedTask.run();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return wrappedTask.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return wrappedTask.isCancelled();
}
@Override
public boolean isDone() {
return wrappedTask.isDone();
}
@Override
public V get() throws InterruptedException, ExecutionException {
return wrappedTask.get();
}
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return wrappedTask.get(timeout, unit);
}
@Override
public long getDelay(TimeUnit unit) {
return wrappedTask.getDelay(unit);
}
@Override
public int compareTo(Delayed o) {
if (o instanceof PriorityInsertionRunnableScheduledFuture<?>) {
PriorityInsertionRunnableScheduledFuture<?> other = (PriorityInsertionRunnableScheduledFuture<?>)o;
if (this.priority != other.priority) {
return this.priority - other.priority;
}
return wrappedTask.compareTo(((PriorityInsertionRunnableScheduledFuture<?>)o).wrappedTask);
}
return -1;
}
}
}