/*
* 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.works.editor.antlr4.semantics;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyledDocument;
import org.antlr.netbeans.editor.text.DocumentSnapshot;
import org.antlr.netbeans.editor.text.OffsetRegion;
import org.antlr.netbeans.editor.text.SnapshotPositionRegion;
import org.antlr.netbeans.editor.text.TrackingPositionRegion;
import org.antlr.netbeans.editor.text.TrackingPositionRegion.Bias;
import org.antlr.netbeans.editor.text.VersionedDocument;
import org.antlr.netbeans.editor.text.VersionedDocumentUtilities;
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.ParserTaskManager;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.Tuple;
import org.antlr.v4.runtime.misc.Tuple2;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.editor.settings.FontColorSettings;
import org.netbeans.spi.editor.highlighting.HighlightsChangeEvent;
import org.netbeans.spi.editor.highlighting.HighlightsChangeListener;
import org.netbeans.spi.editor.highlighting.HighlightsLayer;
import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
import org.netbeans.spi.editor.highlighting.HighlightsSequence;
import org.netbeans.spi.editor.highlighting.ZOrder;
import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.openide.util.Lookup;
import org.openide.util.Parameters;
import org.openide.util.WeakSet;
/**
*
* @author Sam Harwell
* @param <SemanticData>
*/
public abstract class AbstractSemanticHighlighter<SemanticData> extends AbstractHighlightsContainer {
// -J-Dorg.antlr.works.editor.antlr4.semantics.AbstractSemanticHighlighter.level=FINE
private static final Logger LOGGER = Logger.getLogger(AbstractSemanticHighlighter.class.getName());
private final StyledDocument document;
private final ParserDataDefinition<? extends SemanticData> semanticDataDefinition;
private final ParserTaskManager taskManager;
private final VersionedDocument versionedDocument;
private final DataListener dataListener;
private final OffsetsBag container;
private final Set<JTextComponent> components = new WeakSet<>();
protected AbstractSemanticHighlighter(@NonNull StyledDocument document, @NonNull ParserDataDefinition<? extends SemanticData> semanticDataDefinition) {
Parameters.notNull("document", document);
Parameters.notNull("semanticDataDefinition", semanticDataDefinition);
this.document = document;
this.semanticDataDefinition = semanticDataDefinition;
this.taskManager = Lookup.getDefault().lookup(ParserTaskManager.class);
this.versionedDocument = VersionedDocumentUtilities.getVersionedDocument(document);
this.dataListener = new DataListener();
this.container = new OffsetsBag(document, true);
this.container.addHighlightsChangeListener(new HighlightsChangeListener() {
@Override
public void highlightChanged(HighlightsChangeEvent event) {
fireHighlightsChange(event.getStartOffset(), event.getEndOffset());
}
});
}
protected StyledDocument getDocument() {
return document;
}
protected ParserTaskManager getTaskManager() {
return taskManager;
}
protected VersionedDocument getVersionedDocument() {
return versionedDocument;
}
protected OffsetsBag getContainer() {
return container;
}
protected static AttributeSet getFontAndColors(FontColorSettings settings, String category) {
AttributeSet attributes = settings.getTokenFontColors(category);
if (attributes == null) {
LOGGER.log(Level.WARNING, "No font attributes found for category {0}.", category);
return SimpleAttributeSet.EMPTY;
}
return attributes;
}
@Override
public HighlightsSequence getHighlights(int startOffset, int endOffset) {
return container.getHighlights(startOffset, endOffset);
}
protected void initialize() {
}
protected void addComponent(JTextComponent component) {
components.add(component);
if (components.size() == 1) {
taskManager.addDataListener(semanticDataDefinition, new WeakDataListener<>(semanticDataDefinition, dataListener));
}
}
public static abstract class AbstractLayerFactory implements HighlightsLayerFactory {
private final Class<? extends AbstractSemanticHighlighter<?>> highlighterClass;
public AbstractLayerFactory(@NonNull Class<? extends AbstractSemanticHighlighter<?>> highlighterClass) {
Parameters.notNull("highlighterClass", highlighterClass);
this.highlighterClass = highlighterClass;
}
@Override
public HighlightsLayer[] createLayers(Context context) {
Document document = context.getDocument();
if (!(document instanceof StyledDocument)) {
return new HighlightsLayer[0];
}
AbstractSemanticHighlighter<?> highlighter = (AbstractSemanticHighlighter<?>)document.getProperty(highlighterClass);
if (highlighter == null) {
highlighter = createHighlighter(context);
highlighter.initialize();
document.putProperty(highlighterClass, highlighter);
}
highlighter.addComponent(context.getComponent());
return new HighlightsLayer[] { HighlightsLayer.create(highlighterClass.getName(), getPosition(), true, highlighter) };
}
protected abstract AbstractSemanticHighlighter<?> createHighlighter(Context context);
protected ZOrder getPosition() {
return ZOrder.SYNTAX_RACK.forPosition(3);
}
}
protected abstract Callable<Void> createAnalyzerTask(final ParserData<? extends SemanticData> parserData);
protected void addHighlights(List<Tuple2<OffsetRegion, AttributeSet>> intermediateContainer, DocumentSnapshot sourceSnapshot, DocumentSnapshot currentSnapshot, Collection<Token> tokens, AttributeSet attributes) {
for (Token token : tokens) {
int startIndex = token.getStartIndex();
int stopIndex = token.getStopIndex();
if (startIndex < 0 || stopIndex < 0 || (startIndex > stopIndex + 1)) {
continue;
}
TrackingPositionRegion trackingRegion = sourceSnapshot.createTrackingRegion(OffsetRegion.fromBounds(startIndex, stopIndex + 1), Bias.Forward);
SnapshotPositionRegion region = trackingRegion.getRegion(currentSnapshot);
intermediateContainer.add(Tuple.create(region.getRegion(), attributes));
}
}
protected void fillHighlights(OffsetsBag targetContainer, List<Tuple2<OffsetRegion, AttributeSet>> highlights) {
Collections.sort(highlights, new Comparator<Tuple2<OffsetRegion, AttributeSet>>() {
@Override
public int compare(Tuple2<OffsetRegion, AttributeSet> o1, Tuple2<OffsetRegion, AttributeSet> o2) {
int diff = o1.getItem1().getStart() - o2.getItem1().getStart();
if (diff != 0) {
return diff;
}
return o1.getItem1().getEnd() - o2.getItem1().getEnd();
}
});
for (Tuple2<OffsetRegion, AttributeSet> highlight : highlights) {
targetContainer.addHighlight(highlight.getItem1().getStart(), highlight.getItem1().getEnd(), highlight.getItem2());
}
}
protected class DataListener implements ParserDataListener<SemanticData> {
@Override
public void dataChanged(ParserDataEvent<? extends SemanticData> event) {
final ParserData<? extends SemanticData> parserData = event.getData();
if (parserData == null) {
return;
}
final DocumentSnapshot snapshot = parserData.getSnapshot();
if (!versionedDocument.equals(snapshot.getVersionedDocument())) {
return;
}
Callable<Void> task = createAnalyzerTask(parserData);
if (task != null) {
taskManager.scheduleHighPriority(task);
}
}
}
protected static class WeakDataListener<T> implements ParserDataListener<T> {
@NonNull
private final WeakReference<ParserDataListener<T>> _listener;
@NonNull
private final ParserDataDefinition<? extends T> dataDefinition;
public WeakDataListener(@NonNull ParserDataDefinition<? extends T> dataDefinition, @NonNull ParserDataListener<T> listener) {
this._listener = new WeakReference<>(listener);
this.dataDefinition = dataDefinition;
}
@Override
public void dataChanged(ParserDataEvent<? extends T> event) {
ParserDataListener<T> listener = getListener();
if (listener == null) {
ParserTaskManager taskManager = Lookup.getDefault().lookup(ParserTaskManager.class);
taskManager.removeDataListener(dataDefinition, this);
return;
}
listener.dataChanged(event);
}
protected ParserDataListener<T> getListener() {
return _listener.get();
}
}
}