/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ca.weblite.netbeans.mirah.lexer;
import ca.weblite.netbeans.mirah.ClassIndex;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import mirah.lang.ast.ClassDefinition;
import mirah.lang.ast.ClosureDefinition;
import mirah.lang.ast.MethodDefinition;
import mirah.lang.ast.Node;
import mirah.lang.ast.Package;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.parsing.spi.Parser;
import org.netbeans.modules.parsing.spi.ParserResultTask;
import org.netbeans.modules.parsing.spi.Scheduler;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.editor.hints.Severity;
import org.openide.filesystems.FileObject;
/**
*
* @author shannah
*/
class CodeHintsTask extends ParserResultTask {
private static final Logger LOG = Logger.getLogger(CodeHintsTask.class.getCanonicalName());
private List<ErrorDescription> errorsOut;
public CodeHintsTask() {
this.errorsOut = new ArrayList<ErrorDescription>();
}
public List<ErrorDescription> getErrors(){
return errorsOut;
}
@Override
public void run(Parser.Result t, SchedulerEvent se) {
processClosures(t, se);
processClasses(t, se);
}
public void processClasses(Parser.Result t, SchedulerEvent se){
FileObject sourceFile = t.getSnapshot().getSource().getFileObject();
Document doc = t.getSnapshot().getSource().getDocument(false);
SourceQuery q = new SourceQuery(doc);
SourceQuery classes = q.findClass(null);
List<ErrorDescription> errors = new ArrayList<ErrorDescription>();
DocumentQuery dq = new DocumentQuery(doc);
for ( Node node : classes){
List<Fix> fixList = new ArrayList<Fix>();
SourceQuery $ = new SourceQuery(doc, node);
ClassDefinition clazz = (ClassDefinition)node;
String fqn = String.valueOf(clazz.name().identifier());
String packageName = dq.getPackageName(clazz.position().startChar());
if ( packageName != null ){
fqn = packageName+"."+fqn;
}
ClassQuery clsQuery = new ClassQuery(fqn, sourceFile, false);
Set<Method> missingMethods = clsQuery.getUnimplementedMethodsRequiredByInterfaces();
missingMethods.addAll(clsQuery.getAbstractMethods());
if ( !missingMethods.isEmpty() ){
ClassUnimplementedMethodsFix fix = new ClassUnimplementedMethodsFix();
fix.doc = doc;
fix.classDef = clazz;
fix.methods = missingMethods;
fixList.add(fix);
ErrorDescription errorDescription = ErrorDescriptionFactory.createErrorDescription(
Severity.HINT,
"Implement Abstract Methods",
fixList,
doc,
clazz.position().startLine()
);
errors.add(errorDescription);
}
}
if ( !errors.isEmpty()){
errorsOut.addAll(errors);
//HintsController.setErrors(doc, "mirah", errors);
}
}
public void processClosures(Parser.Result t, SchedulerEvent se){
FileObject sourceFile = t.getSnapshot().getSource().getFileObject();
Document doc = t.getSnapshot().getSource().getDocument(false);
SourceQuery q = new SourceQuery(doc);
SourceQuery closures = q.findClosures("*");
List<ErrorDescription> errors = new ArrayList<ErrorDescription>();
for ( Node node : closures){
List<Fix> fixList = new ArrayList<Fix>();
Set<Method> abstractMethodsToAdd = new HashSet<Method>();
SourceQuery $ = new SourceQuery(doc, node);
ClosureDefinition closure = (ClosureDefinition)node;
ClassQuery superQuery = null;
if ( closure.superclass() != null ){
superQuery = new ClassQuery(closure.superclass().typeref().name(), sourceFile, true);
} else if ( closure.interfaces() != null && closure.interfaces_size() > 0 ){
superQuery = new ClassQuery(closure.interfaces(0).typeref().name(), sourceFile, true);
}
if ( superQuery != null ){
Set<Method> abstractMethods = superQuery.getAbstractMethods();
Map<String,Method> methodMap = new HashMap<String,Method>();
for ( Method m : abstractMethods ){
methodMap.put(ClassQuery.getMethodId(m), m);
}
SourceQuery implementedMethods = $.findMethods("*");
for ( Node n : implementedMethods ){
MethodDefinition md = (MethodDefinition)n;
methodMap.remove(q.getMethodId(md));
}
if ( !methodMap.isEmpty() ){
// There are some abstract methods that need to be implemented
abstractMethodsToAdd.addAll(methodMap.values());
ClosureAbstractMethodsFix fix = new ClosureAbstractMethodsFix();
fix.doc = doc;
fix.closure = closure;
fix.methods = abstractMethodsToAdd;
fixList.add(fix);
ErrorDescription errorDescription = ErrorDescriptionFactory.createErrorDescription(
Severity.HINT,
"Implement Abstract Methods",
fixList,
doc,
closure.position().startLine()
);
errors.add(errorDescription);
}
}
}
if ( !errors.isEmpty()){
errorsOut.addAll(errors);
//HintsController.setErrors(doc, "mirah", errors);
}
}
@Override
public int getPriority() {
return 100;
}
@Override
public Class<? extends Scheduler> getSchedulerClass() {
return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER;
}
@Override
public void cancel() {
}
private static class ClosureAbstractMethodsFix implements Fix {
Document doc;
ClosureDefinition closure;
Set<Method> methods;
@Override
public String getText() {
return "Implement abstract methods";
}
@Override
public ChangeInfo implement() throws Exception {
DocumentQuery q = new DocumentQuery(doc);
int indent = q.getIndent(closure.position().startChar());
int indentSize = IndentUtils.indentLevelSize(doc);
indent+=indentSize;
StringBuilder sb = new StringBuilder();
sb.append("\n");
CodeFormatter fmt = new CodeFormatter();
fmt.indent(sb, indent);
Set<String> requiredImports = new HashSet<String>();
for ( Method m : methods ){
sb.append(fmt.formatMethod(m, indent, indentSize));
requiredImports.addAll(fmt.getRequiredImports(m));
}
int nextDoPos = q.getAfterNextDo(closure.position().startChar());
if ( nextDoPos != -1 ){
doc.insertString(nextDoPos, sb.toString(), new SimpleAttributeSet());
}
for ( String importCls : requiredImports ){
q.addImport(importCls);
}
return null;
}
}
private static class ClassUnimplementedMethodsFix implements Fix {
Document doc;
ClassDefinition classDef;
Set<Method> methods;
@Override
public String getText() {
return "Implement abstract methods";
}
@Override
public ChangeInfo implement() throws Exception {
DocumentQuery q = new DocumentQuery(doc);
int indent = q.getIndent(classDef.position().startChar());
int indentSize = IndentUtils.indentLevelSize(doc);
indent+=indentSize;
StringBuilder sb = new StringBuilder();
sb.append("\n");
CodeFormatter fmt = new CodeFormatter();
fmt.indent(sb, indent);
Set<String> requiredImports = new HashSet<String>();
for ( Method m : methods ){
sb.append(fmt.formatMethod(m, indent, indentSize));
requiredImports.addAll(fmt.getRequiredImports(m));
}
int prevEndPos = q.getBeforePrevEnd(classDef.position().endChar());
if ( prevEndPos != -1 ){
doc.insertString(prevEndPos, sb.toString(), new SimpleAttributeSet());
}
for ( String importCls : requiredImports ){
q.addImport(importCls);
}
return null;
}
}
}