/* * 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; import ca.weblite.netbeans.mirah.lexer.MirahTokenId; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyledDocument; import mirah.impl.Tokens; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.editor.BaseDocument; import org.netbeans.modules.parsing.api.Source; import org.netbeans.spi.editor.hints.ChangeInfo; import org.netbeans.spi.editor.hints.Fix; import org.netbeans.spi.editor.hints.LazyFixList; import org.openide.filesystems.FileObject; import org.openide.util.RequestProcessor; /** * * @author shannah */ public class ImportFixList implements LazyFixList, Runnable { private Source source; private String className; private PropertyChangeSupport pcs; private List<Fix> fixes = new ArrayList<Fix>(); private boolean computed = false; public ImportFixList(Source source, String className){ pcs = new PropertyChangeSupport(this); this.source = source; this.className = className; } @Override public boolean probablyContainsFixes() { synchronized(fixes){ return !fixes.isEmpty(); } } @Override public List<Fix> getFixes() { synchronized(fixes){ return Collections.unmodifiableList(fixes); } } @Override public boolean isComputed() { return computed; } @Override public void addPropertyChangeListener(PropertyChangeListener pl) { pcs.addPropertyChangeListener(pl); } @Override public void removePropertyChangeListener(PropertyChangeListener pl) { pcs.removePropertyChangeListener(pl); } @Override public void run() { System.err.println("In ImportFixList.run()"); FileObject fo = source.getFileObject(); ClassIndex idx = new ClassIndex(); ClassIndex.CompoundQuery q = new ClassIndex.CompoundQuery(0); ClassPath[] classPaths = new ClassPath[]{ ClassPath.getClassPath(fo, ClassPath.SOURCE), ClassPath.getClassPath(fo, ClassPath.EXECUTE), ClassPath.getClassPath(fo, ClassPath.COMPILE), ClassPath.getClassPath(fo, ClassPath.BOOT) }; int priority = 10; for ( ClassPath cp : classPaths){ q.addQuery(new ClassIndex.ClassPathQuery(priority--, className, "", cp)); } ClassIndex.Future results = new ClassIndex.Future(){ @Override protected void resultsAdded() { synchronized(fixes){ fixes.clear(); } Set<String> matches = new HashSet<String>(); matches.addAll(this.getMatches()); for ( String match : matches ){ Fix fix = new ImportFix(match); synchronized(fixes){ fixes.add(fix); } pcs.firePropertyChange(LazyFixList.PROP_FIXES, fixes, fixes); } } }; idx.findClass(q, results); computed = true; pcs.firePropertyChange(LazyFixList.PROP_COMPUTED, false, true); } class ImportFix implements Fix{ private String fullClassName; ImportFix(String fqn){ if ( fqn.indexOf(".") == 0 ){ fqn = fqn.substring(1); } this.fullClassName = fqn; } @Override public String getText() { return "Add import "+fullClassName; } @Override public ChangeInfo implement() throws Exception { Document doc = source.getDocument(true); TokenHierarchy<?> hi = TokenHierarchy.get(doc); int caretOffset = 0; TokenSequence<MirahTokenId> seq = mirahTokenSequence(doc, caretOffset, false); // Find the first package or import and place the import after that. MirahTokenId PKG = MirahTokenId.get(Tokens.tPackage.ordinal()); MirahTokenId IMPORT = MirahTokenId.get(Tokens.tImport.ordinal()); MirahTokenId EOL = MirahTokenId.get(Tokens.tNL.ordinal()); int pos = 0; Token pkg = null; Token firstImport = null; do { Token curr = seq.token(); MirahTokenId currTok = (MirahTokenId)curr.id(); if ( PKG.equals(currTok) ){ pkg = curr; } else if ( IMPORT.equals(currTok)){ firstImport = curr; } } while ( seq.moveNext()); if ( firstImport != null ){ doc.insertString(firstImport.offset(hi), "import "+fullClassName+"\n", new SimpleAttributeSet()); } else if ( pkg != null ){ seq.move(pkg.offset(hi)); while ( seq.moveNext() ){ if ( EOL.equals(seq.token().id())){ doc.insertString(seq.token().offset(hi), "\nimport "+fullClassName+"\n", new SimpleAttributeSet()); break; } } } else { doc.insertString(0, "import "+fullClassName+"\n", new SimpleAttributeSet()); } return null; } } /** * Get token sequence positioned over a token. * * @param doc * @param caretOffset * @param backwardBias * @return token sequence positioned over a token that "contains" the offset * or null if the document does not contain any java token sequence or the * offset is at doc-or-section-start-and-bwd-bias or * doc-or-section-end-and-fwd-bias. */ private static TokenSequence<MirahTokenId> mirahTokenSequence(Document doc, int caretOffset, boolean backwardBias) { try { ((BaseDocument)doc).readLock(); TokenHierarchy<?> hi = TokenHierarchy.get(doc); List<TokenSequence<?>> tsList = hi.embeddedTokenSequences(caretOffset, backwardBias); // Go from inner to outer TSes for (int i = tsList.size() - 1; i >= 0; i--) { TokenSequence<?> ts = tsList.get(i); if (ts.languagePath().innerLanguage() == MirahTokenId.getLanguage()) { TokenSequence<MirahTokenId> javaInnerTS = (TokenSequence<MirahTokenId>) ts; return javaInnerTS; } } return null; } finally { ((BaseDocument)doc).readUnlock(); } } }