/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* Portions Copyrighted 2007 Sun Microsystems, Inc.
*/
package org.netbeans.modules.ruby.hints.infrastructure;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.Set;
import org.jrubyparser.ast.Node;
import org.jrubyparser.ast.NodeType;
import org.jrubyparser.IRubyWarnings.ID;
import org.jrubyparser.ast.CaseNode;
import org.jrubyparser.ast.WhenNode;
import org.netbeans.modules.csl.api.Error;
import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintsProvider;
import org.netbeans.modules.csl.api.HintsProvider.HintsManager;
import org.netbeans.modules.csl.api.Rule;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.ruby.AstPath;
import org.netbeans.modules.ruby.AstUtilities;
import org.netbeans.modules.ruby.RubyParser.RubyError;
/**
* Class which acts on the rules and suggestions by iterating the
* AST and invoking applicable rules
*
*
* @author Tor Norbye
*/
public class RubyHintsProvider implements HintsProvider {
private boolean cancelled;
public RubyHintsProvider() {
}
public RuleContext createRuleContext() {
return new RubyRuleContext();
}
@SuppressWarnings("unchecked")
public void computeErrors(HintsManager manager, RuleContext context, List<Hint> result, List<Error> unhandled) {
ParserResult parserResult = context.parserResult;
if (parserResult == null) {
return;
}
List<? extends Error> errors = parserResult.getDiagnostics();
if (errors == null || errors.size() == 0) {
return;
}
cancelled = false;
@SuppressWarnings("unchecked")
Map<ID,List<RubyErrorRule>> hints = (Map)manager.getErrors();
if (hints.isEmpty() || isCancelled()) {
unhandled.addAll(errors);
return;
}
try {
context.doc.readLock();
for (Error error : errors) {
if (error instanceof RubyError) {
if (!applyRules(manager, (RubyError)error, context, hints, result)) {
unhandled.add(error);
}
}
}
} finally {
context.doc.readUnlock();
}
}
public List<Rule> getBuiltinRules() {
return null;
}
@SuppressWarnings("unchecked")
public void computeSelectionHints(HintsManager manager, RuleContext context, List<Hint> result, int start, int end) {
cancelled = false;
ParserResult parserResult = context.parserResult;
if (parserResult == null) {
return;
}
Node root = AstUtilities.getRoot(parserResult);
if (root == null) {
return;
}
@SuppressWarnings("unchecked")
List<RubySelectionRule> hints = (List<RubySelectionRule>)manager.getSelectionHints();
if (hints.isEmpty()) {
return;
}
if (isCancelled()) {
return;
}
try {
context.doc.readLock();
applyRules(manager, context, hints, result);
} finally {
context.doc.readUnlock();
}
}
public void computeHints(HintsManager manager, RuleContext context, List<Hint> result) {
cancelled = false;
ParserResult parserResult = context.parserResult;
if (parserResult == null) {
return;
}
Node root = AstUtilities.getRoot(parserResult);
if (root == null) {
return;
}
@SuppressWarnings("unchecked")
Map<NodeType,List<RubyAstRule>> hints = (Map)manager.getHints(false, context);
if (hints.isEmpty()) {
return;
}
if (isCancelled()) {
return;
}
AstPath path = new AstPath();
path.descend(root);
try {
context.doc.readLock();
applyRules(manager, context, NodeType.ROOTNODE, root, path, hints, result);
scan(manager, context, root, path, hints, result);
} finally {
context.doc.readUnlock();
}
path.ascend();
}
@SuppressWarnings("unchecked")
public void computeSuggestions(HintsManager manager, RuleContext context, List<Hint> result, int caretOffset) {
cancelled = false;
ParserResult parserResult = context.parserResult;
if (parserResult == null) {
return;
}
Node root = AstUtilities.getRoot(parserResult);
if (root == null) {
return;
}
Map<NodeType, List<RubyAstRule>> suggestions = new HashMap<NodeType, List<RubyAstRule>>();
Map<NodeType,List<RubyAstRule>> hintsMap = (Map)manager.getHints(true, context);
suggestions.putAll(hintsMap);
Set<Entry<NodeType, List<RubyAstRule>>> suggestionsSet = (Set)manager.getSuggestions().entrySet();
for (Entry<NodeType, List<RubyAstRule>> e : suggestionsSet) {
List<RubyAstRule> rules = suggestions.get(e.getKey());
if (rules != null) {
List<RubyAstRule> res = new LinkedList<RubyAstRule>();
res.addAll(rules);
res.addAll(e.getValue());
suggestions.put(e.getKey(), res);
} else {
suggestions.put(e.getKey(), e.getValue());
}
}
if (suggestions.isEmpty()) {
return;
}
if (isCancelled()) {
return;
}
ParserResult info = context.parserResult;
int astOffset = AstUtilities.getAstOffset(info, caretOffset);
AstPath path = new AstPath(root, astOffset);
try {
context.doc.readLock();
Iterator<Node> it = path.leafToRoot();
while (it.hasNext()) {
if (isCancelled()) {
return;
}
Node node = it.next();
applyRules(manager, context, node.getNodeType(), node, path, suggestions, result);
}
} finally {
context.doc.readUnlock();
}
//applyRules(NodeType.ROOTNODE, path, info, suggestions, caretOffset, result);
}
private void applyRules(HintsManager manager, RuleContext context, NodeType nodeType, Node node, AstPath path, Map<NodeType,List<RubyAstRule>> hints,
List<Hint> result) {
List<RubyAstRule> rules = hints.get(nodeType);
if (rules != null) {
RubyRuleContext rubyContext = (RubyRuleContext)context;
rubyContext.node = node;
rubyContext.path = path;
for (RubyAstRule rule : rules) {
if (manager.isEnabled(rule)) {
rule.run(rubyContext, result);
}
}
}
}
/** Apply error rules and return true iff somebody added an error description for it */
private boolean applyRules(HintsManager manager, RubyError error, RuleContext context, Map<ID,List<RubyErrorRule>> hints,
List<Hint> result) {
ID code = error.getId();
if (code != null) {
List<RubyErrorRule> rules = hints.get(code);
if (rules != null) {
int countBefore = result.size();
RubyRuleContext rubyContext = (RubyRuleContext)context;
for (RubyErrorRule rule : rules) {
if (!manager.isEnabled(rule)) {
continue;
}
if (!rule.appliesTo(context)) {
continue;
}
rule.run(rubyContext, error, result);
}
return countBefore < result.size();
}
}
return false;
}
private void applyRules(HintsManager manager, RuleContext context, List<RubySelectionRule> rules, List<Hint> result) {
RubyRuleContext rubyContext = (RubyRuleContext)context;
for (RubySelectionRule rule : rules) {
if (!rule.appliesTo(context)) {
continue;
}
if (!manager.isEnabled(rule)) {
continue;
}
rule.run(rubyContext, result);
}
}
private void scan(HintsManager manager, RuleContext context, Node node, AstPath path, Map<NodeType,List<RubyAstRule>> hints,
List<Hint> result) {
applyRules(manager, context, node.getNodeType(), node, path, hints, result);
List<Node> list = childNodes(node);
for (Node child : list) {
if (child.isInvisible()) {
continue;
}
if (isCancelled()) {
return;
}
path.descend(child);
scan(manager, context, child, path, hints, result);
path.ascend();
}
}
private List<Node> childNodes(Node node) {
if (node.getNodeType() == NodeType.WHENNODE) {
// skip the next case nodes for when nodes, the
// new parser includes them in child nodes which
// confuses our hints
// XXX: change this behaviour in the parser?
WhenNode whenNode = (WhenNode) node;
return nodeList(whenNode.getExpressionNodes(), whenNode.getBodyNode());
} else if (node.getNodeType() == NodeType.CASENODE) {
CaseNode caseNode = (CaseNode) node;
// include the else node for case nodes
return nodeList(caseNode.getCaseNode(), caseNode.getCases(), caseNode.getElseNode());
}
return node.childNodes();
}
private List<Node> nodeList(Node... nodes) {
List<Node> result = new ArrayList<Node>(nodes.length);
for (Node node : nodes) {
if (node != null) {
result.add(node);
}
}
return result;
}
public void cancel() {
cancelled = true;
}
private boolean isCancelled() {
return cancelled;
}
}