/*
* 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.navigation;
import java.awt.Image;
import java.util.List;
import java.util.concurrent.Callable;
import org.antlr.netbeans.editor.text.OffsetRegion;
import org.antlr.v4.runtime.InterpreterRuleContext;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.antlr.works.editor.antlr4.parsing.ParseTrees;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.annotations.common.StaticResource;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
/**
*
* @author Sam Harwell
*/
public class ParseTreeNode extends AbstractNode implements OffsetProvider {
@StaticResource
private static final String RULE_IMAGE_PATH = "org/antlr/works/editor/antlr4/navigation/ui/rule.png";
public static final Image RULE_IMAGE = ImageUtilities.loadImage(RULE_IMAGE_PATH);
@StaticResource
private static final String TERMINAL_IMAGE_PATH = "org/antlr/works/editor/antlr4/navigation/ui/terminal.png";
public static final Image TERMINAL_IMAGE = ImageUtilities.loadImage(TERMINAL_IMAGE_PATH);
@StaticResource
private static final String ERROR_IMAGE_PATH = "org/antlr/works/editor/antlr4/navigation/ui/error.png";
public static final Image ERROR_IMAGE = ImageUtilities.loadImage(ERROR_IMAGE_PATH);
@NonNull
private final ParseTree _tree;
@NullAllowed
private final List<String> _ruleNames;
@Deprecated
public ParseTreeNode(@NonNull ParseTree tree) {
this(tree, null);
}
public ParseTreeNode(@NonNull ParseTree tree, @NullAllowed List<String> ruleNames) {
super(Children.LEAF);
_tree = tree;
_ruleNames = ruleNames;
if (tree.getChildCount() > 0) {
setChildren(Children.createLazy(new ChildrenOfParseTreeNodeCreator()));
}
if (tree instanceof RuleNode) {
RuleNode ruleNode = (RuleNode)tree;
RuleContext ruleContext = ruleNode.getRuleContext();
if (ruleContext instanceof ParserRuleContext && ruleContext.getClass() != ParserRuleContext.class && ruleContext.getClass() != InterpreterRuleContext.class) {
String contextName = ruleContext.getClass().getSimpleName();
if (!"Context".equals(contextName) && contextName.endsWith("Context")) {
contextName = contextName.substring(0, contextName.length() - "Context".length());
}
contextName = Character.toLowerCase(contextName.charAt(0)) + contextName.substring(1);
setDisplayName(contextName);
} else {
String displayName = null;
if (ruleNames != null && ruleContext.getRuleIndex() >= 0 && ruleContext.getRuleIndex() < ruleNames.size()) {
displayName = ruleNames.get(ruleContext.getRuleIndex());
}
if (displayName == null || displayName.isEmpty()) {
displayName = "Rule Node";
}
setDisplayName(displayName);
}
} else if (tree instanceof ErrorNode) {
setDisplayName("Error Node");
} else if (tree instanceof TerminalNode) {
String nodeText = tree.getText();
if (nodeText != null && !nodeText.isEmpty()) {
nodeText = nodeText.substring(0, Math.min(nodeText.length(), 100));
nodeText = nodeText.replace("\\", "\\\\");
nodeText = nodeText.replace("\r", "\\r");
nodeText = nodeText.replace("\n", "\\n");
nodeText = nodeText.replace("\t", "\\t");
nodeText = nodeText.replace("'", "\\'");
setDisplayName("'" + nodeText + "'");
} else {
setDisplayName("Terminal Node");
}
}
}
@NonNull
public ParseTree getTree() {
return _tree;
}
@CheckForNull
public List<String> getRuleNames() {
return _ruleNames;
}
@Override
public Image getIcon(int type) {
if (_tree instanceof RuleNode) {
return RULE_IMAGE;
} else if (_tree instanceof ErrorNode) {
return ERROR_IMAGE;
} else if (_tree instanceof TerminalNode) {
return TERMINAL_IMAGE;
}
return super.getIcon(type);
}
@Override
public Image getOpenedIcon(int type) {
return getIcon(type);
}
@Override
public OffsetRegion getSpan() {
TerminalNode startNode = ParseTrees.getStartNode(_tree);
if (startNode == null) {
return null;
}
TerminalNode stopNode = ParseTrees.getStopNode(_tree);
if (stopNode == null) {
// rule matched epsilon
return new OffsetRegion(startNode.getSymbol().getStartIndex(), 0);
}
return OffsetRegion.fromBounds(startNode.getSymbol().getStartIndex(), stopNode.getSymbol().getStopIndex() + 1);
}
@Override
public OffsetRegion getSeek() {
OffsetRegion span = getSpan();
if (span == null) {
return null;
}
return new OffsetRegion(span.getStart(), 0);
}
@Override
public OffsetRegion getEmphasis() {
return null;
}
protected ParseTreeNode createChildNode(ParseTree tree) {
return new ParseTreeNode(tree, _ruleNames);
}
private final class ChildrenOfParseTreeNodeCreator implements Callable<Children> {
@Override
public Children call() throws Exception {
Node[] childrenNodes = new Node[_tree.getChildCount()];
for (int i = 0; i < childrenNodes.length; i++) {
childrenNodes[i] = createChildNode(_tree.getChild(i));
}
return new NodeChildren(childrenNodes);
}
}
private static final class NodeChildren extends Children.Keys<Node> {
public NodeChildren(Node[] nodes) {
setKeys(nodes);
}
@Override
protected Node[] createNodes(Node key) {
return new Node[] { key };
}
}
}